Merge pull request #22 from boudra/local-streaming-speech-tts
voice: harden local voice agent path and UUID validation
This commit is contained in:
@@ -13,6 +13,7 @@ import { Route as DocsRouteImport } from './routes/docs'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||
import { Route as DocsWorktreesRouteImport } from './routes/docs/worktrees'
|
||||
import { Route as DocsVoiceRouteImport } from './routes/docs/voice'
|
||||
import { Route as DocsSecurityRouteImport } from './routes/docs/security'
|
||||
import { Route as DocsConfigurationRouteImport } from './routes/docs/configuration'
|
||||
import { Route as DocsCliRouteImport } from './routes/docs/cli'
|
||||
@@ -38,6 +39,11 @@ const DocsWorktreesRoute = DocsWorktreesRouteImport.update({
|
||||
path: '/worktrees',
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
const DocsVoiceRoute = DocsVoiceRouteImport.update({
|
||||
id: '/voice',
|
||||
path: '/voice',
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any)
|
||||
const DocsSecurityRoute = DocsSecurityRouteImport.update({
|
||||
id: '/security',
|
||||
path: '/security',
|
||||
@@ -66,6 +72,7 @@ export interface FileRoutesByFullPath {
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
}
|
||||
@@ -75,6 +82,7 @@ export interface FileRoutesByTo {
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs': typeof DocsIndexRoute
|
||||
}
|
||||
@@ -86,6 +94,7 @@ export interface FileRoutesById {
|
||||
'/docs/cli': typeof DocsCliRoute
|
||||
'/docs/configuration': typeof DocsConfigurationRoute
|
||||
'/docs/security': typeof DocsSecurityRoute
|
||||
'/docs/voice': typeof DocsVoiceRoute
|
||||
'/docs/worktrees': typeof DocsWorktreesRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
}
|
||||
@@ -98,6 +107,7 @@ export interface FileRouteTypes {
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
@@ -107,6 +117,7 @@ export interface FileRouteTypes {
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs'
|
||||
id:
|
||||
@@ -117,6 +128,7 @@ export interface FileRouteTypes {
|
||||
| '/docs/cli'
|
||||
| '/docs/configuration'
|
||||
| '/docs/security'
|
||||
| '/docs/voice'
|
||||
| '/docs/worktrees'
|
||||
| '/docs/'
|
||||
fileRoutesById: FileRoutesById
|
||||
@@ -156,6 +168,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof DocsWorktreesRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/voice': {
|
||||
id: '/docs/voice'
|
||||
path: '/voice'
|
||||
fullPath: '/docs/voice'
|
||||
preLoaderRoute: typeof DocsVoiceRouteImport
|
||||
parentRoute: typeof DocsRoute
|
||||
}
|
||||
'/docs/security': {
|
||||
id: '/docs/security'
|
||||
path: '/security'
|
||||
@@ -192,6 +211,7 @@ interface DocsRouteChildren {
|
||||
DocsCliRoute: typeof DocsCliRoute
|
||||
DocsConfigurationRoute: typeof DocsConfigurationRoute
|
||||
DocsSecurityRoute: typeof DocsSecurityRoute
|
||||
DocsVoiceRoute: typeof DocsVoiceRoute
|
||||
DocsWorktreesRoute: typeof DocsWorktreesRoute
|
||||
DocsIndexRoute: typeof DocsIndexRoute
|
||||
}
|
||||
@@ -201,6 +221,7 @@ const DocsRouteChildren: DocsRouteChildren = {
|
||||
DocsCliRoute: DocsCliRoute,
|
||||
DocsConfigurationRoute: DocsConfigurationRoute,
|
||||
DocsSecurityRoute: DocsSecurityRoute,
|
||||
DocsVoiceRoute: DocsVoiceRoute,
|
||||
DocsWorktreesRoute: DocsWorktreesRoute,
|
||||
DocsIndexRoute: DocsIndexRoute,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const Route = createFileRoute('/docs')({
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Getting started', href: '/docs' },
|
||||
{ name: 'Voice', href: '/docs/voice' },
|
||||
{ name: 'Git worktrees', href: '/docs/worktrees' },
|
||||
{ name: 'CLI', href: '/docs/cli' },
|
||||
{ name: 'Configuration', href: '/docs/configuration' },
|
||||
|
||||
@@ -67,8 +67,7 @@ function Configuration() {
|
||||
"$schema": "https://paseo.sh/schemas/paseo.config.v1.json",
|
||||
"version": 1,
|
||||
"providers": {
|
||||
"openai": { "apiKey": "..." },
|
||||
"openrouter": { "apiKey": "..." }
|
||||
"openai": { "apiKey": "..." }
|
||||
},
|
||||
"daemon": {
|
||||
"listen": "127.0.0.1:6767",
|
||||
@@ -79,13 +78,32 @@ function Configuration() {
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Voice</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Voice is configured through <code className="font-mono">features.dictation</code> and{' '}
|
||||
<code className="font-mono">features.voiceMode</code>, with provider credentials under{' '}
|
||||
<code className="font-mono">providers</code>.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
For voice philosophy, architecture, and complete local/OpenAI setup examples, see{' '}
|
||||
<a href="/docs/voice" className="underline hover:text-white/80">Voice docs</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Common env vars</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li><code className="font-mono">PASEO_HOME</code> — set Paseo home directory</li>
|
||||
<li><code className="font-mono">PASEO_LISTEN</code> — override <code className="font-mono">daemon.listen</code></li>
|
||||
<li><code className="font-mono">PASEO_ALLOWED_HOSTS</code> — override/extend <code className="font-mono">daemon.allowedHosts</code></li>
|
||||
<li><code className="font-mono">OPENAI_API_KEY</code> and <code className="font-mono">OPENROUTER_API_KEY</code> — override provider keys</li>
|
||||
<li><code className="font-mono">OPENAI_API_KEY</code> — override OpenAI provider key</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — override voice LLM provider (<code className="font-mono">claude</code>, <code className="font-mono">codex</code>, <code className="font-mono">opencode</code>)</li>
|
||||
<li><code className="font-mono">PASEO_DICTATION_STT_PROVIDER</code>, <code className="font-mono">PASEO_VOICE_STT_PROVIDER</code>, <code className="font-mono">PASEO_VOICE_TTS_PROVIDER</code> — override voice provider selection (<code className="font-mono">local</code> or <code className="font-mono">openai</code>)</li>
|
||||
<li><code className="font-mono">PASEO_LOCAL_MODELS_DIR</code> and <code className="font-mono">PASEO_LOCAL_AUTO_DOWNLOAD</code> — control local model directory and download behavior</li>
|
||||
<li><code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — override local dictation STT model</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — override local voice STT/TTS models</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice TTS tuning</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -101,4 +119,3 @@ function Configuration() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -87,20 +87,25 @@ function GettingStarted() {
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Voice Setup</h2>
|
||||
<p className="text-white/60">
|
||||
Voice features currently require an OpenAI API key. Set it as an environment variable before running the server:
|
||||
Paseo includes first-class voice support with a local-first architecture and configurable speech
|
||||
providers.
|
||||
</p>
|
||||
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
|
||||
<span className="text-muted-foreground select-none">$ </span>
|
||||
<span>export OPENAI_API_KEY=your-key-here</span>
|
||||
</div>
|
||||
<p className="text-white/60">
|
||||
Local voice support is coming soon.
|
||||
For architecture, local model behavior, and provider configuration, see the Voice docs page.
|
||||
</p>
|
||||
<a href="/docs/voice" className="underline hover:text-white/80">
|
||||
Voice docs
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Next</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<a href="/docs/voice" className="underline hover:text-white/80">
|
||||
Voice
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/configuration" className="underline hover:text-white/80">
|
||||
Configuration
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/docs/voice')({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: 'Voice - Paseo Docs' },
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Paseo voice architecture, local-first model execution, and provider configuration.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
component: VoiceDocs,
|
||||
})
|
||||
|
||||
function VoiceDocs() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Voice</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo has first-class voice support for dictation and realtime conversations with your coding
|
||||
environment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Philosophy</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Voice is local-first. You can run speech fully on-device, or choose OpenAI for speech features.
|
||||
For voice reasoning/orchestration, Paseo reuses agent providers already installed and authenticated
|
||||
on your machine.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
This keeps credentials and execution in your environment and avoids introducing a separate
|
||||
cloud-only voice stack.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Architecture</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>Speech I/O: STT and TTS providers per feature (<code className="font-mono">local</code> or <code className="font-mono">openai</code>)</li>
|
||||
<li>Local speech runtime: ONNX models executed on CPU by default</li>
|
||||
<li>Voice LLM orchestration: hidden agent session using your configured provider (<code className="font-mono">claude</code>, <code className="font-mono">codex</code>, or <code className="font-mono">opencode</code>)</li>
|
||||
<li>Tooling path: MCP stdio bridge for voice tools and agent control</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Local Speech</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Local speech defaults to model IDs <code className="font-mono">parakeet-tdt-0.6b-v3-int8</code>{' '}
|
||||
(STT) and <code className="font-mono">pocket-tts-onnx-int8</code> (TTS).
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Missing models are downloaded at daemon startup into{' '}
|
||||
<code className="font-mono">$PASEO_HOME/models/local-speech</code> when auto-download is enabled.
|
||||
Downloads happen only for missing files.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"version": 1,
|
||||
"features": {
|
||||
"dictation": { "stt": { "provider": "local", "model": "parakeet-tdt-0.6b-v3-int8" } },
|
||||
"voiceMode": {
|
||||
"llm": { "provider": "claude", "model": "haiku" },
|
||||
"stt": { "provider": "local", "model": "parakeet-tdt-0.6b-v3-int8" },
|
||||
"tts": { "provider": "local", "model": "pocket-tts-onnx-int8" }
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"local": {
|
||||
"modelsDir": "~/.paseo/models/local-speech",
|
||||
"autoDownload": true
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">OpenAI Speech Option</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
You can switch dictation, voice STT, and voice TTS to OpenAI by setting provider fields to{' '}
|
||||
<code className="font-mono">openai</code> and providing <code className="font-mono">OPENAI_API_KEY</code>.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"version": 1,
|
||||
"features": {
|
||||
"dictation": { "stt": { "provider": "openai" } },
|
||||
"voiceMode": {
|
||||
"stt": { "provider": "openai" },
|
||||
"tts": { "provider": "openai" }
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"openai": { "apiKey": "..." }
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Environment Variables</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li><code className="font-mono">OPENAI_API_KEY</code> — OpenAI speech credentials</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — voice agent provider override</li>
|
||||
<li><code className="font-mono">PASEO_LOCAL_MODELS_DIR</code>, <code className="font-mono">PASEO_LOCAL_AUTO_DOWNLOAD</code> — local model storage and download policy</li>
|
||||
<li><code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — local dictation STT model ID</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — local voice STT/TTS model IDs</li>
|
||||
<li><code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice TTS tuning</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Operational Notes</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Realtime voice can launch and control agents. Treat voice prompts with the same care as direct
|
||||
agent instructions, especially when specifying working directories or destructive operations.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -286,8 +286,8 @@ function FAQ() {
|
||||
<FAQItem question="Is this free?">
|
||||
Paseo is free and open source. It wraps CLI tools like Claude Code and
|
||||
Codex, which you'll need to have installed and configured with your
|
||||
own credentials. Voice features currently require an OpenAI API key,
|
||||
but local voice is coming soon.
|
||||
own credentials. Voice is local-first by default and can optionally use
|
||||
OpenAI speech providers if you configure them.
|
||||
</FAQItem>
|
||||
<FAQItem question="Does my code leave my machine?">
|
||||
Paseo itself doesn't send your code anywhere. Agents run locally and
|
||||
|
||||
Reference in New Issue
Block a user