Based on the git diff showing extensive package-lock.json changes and modifications to the agent system prompt and configuration, here's the commit message:

```
feat: add AI SDK dependencies and refine agent voice interaction config

- Add @ai-sdk packages for enhanced AI capabilities
- Install Deepgram SDK, livekit-client, and werift for voice/WebRTC
- Expand agent system prompt with multi-tool workflows and Claude Code interaction patterns
- Adjust VAD timing (1.2s silence threshold) and turn detection for better voice UX
- Switch TTS to inworld/inworld-tts-1 with Olivia voice
- Configure interruption timing (0.2s detection, no word requirement)
- Add comprehensive guidelines for conversational responses vs structured output
- Document plan mode triggers and Claude response type handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
```
This commit is contained in:
Mohamed Boudra
2025-10-19 15:41:42 +02:00
parent dabcf63ebc
commit 40b199e329
44 changed files with 12183 additions and 1715 deletions
@@ -0,0 +1,173 @@
import { streamText, tool } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { z } from 'zod';
import {
listTerminals,
createTerminal,
captureTerminal,
sendText,
renameTerminal,
killTerminal,
} from '../daemon/terminal-manager.js';
let openaiClient: ReturnType<typeof createOpenAI> | null = null;
/**
* Initialize OpenAI client with API key
*/
export function initializeOpenAI(apiKey: string): void {
openaiClient = createOpenAI({ apiKey });
}
/**
* Terminal tools using Vercel AI SDK tool() function
* These are automatically executed when the LLM calls them
*/
export const terminalTools = {
list_terminals: tool({
description: 'List all available terminals in the voice-dev session',
parameters: z.object({}),
execute: async () => {
const terminals = await listTerminals();
return { terminals };
},
}),
create_terminal: tool({
description: 'Create a new terminal with specified name and working directory',
parameters: z.object({
name: z.string().describe('Unique name for the terminal'),
workingDirectory: z.string().describe('Working directory path'),
initialCommand: z.string().optional().describe('Optional command to run on creation'),
}),
execute: async ({ name, workingDirectory, initialCommand }) => {
const terminal = await createTerminal({ name, workingDirectory, initialCommand });
return { terminal };
},
}),
capture_terminal: tool({
description: 'Capture and return the output from a terminal',
parameters: z.object({
terminalId: z.string().describe('Terminal ID (e.g., @123)'),
lines: z.number().optional().describe('Number of lines to capture (default: 200)'),
wait: z.number().optional().describe('Milliseconds to wait before capture'),
}),
execute: async ({ terminalId, lines, wait }) => {
const output = await captureTerminal(terminalId, lines, wait);
return { output };
},
}),
send_text: tool({
description: 'Send text or commands to a terminal',
parameters: z.object({
terminalId: z.string().describe('Terminal ID (e.g., @123)'),
text: z.string().describe('Text to send to the terminal'),
pressEnter: z.boolean().optional().describe('Whether to press Enter after text'),
return_output: z
.object({
lines: z.number().optional(),
wait: z.number().optional(),
})
.optional()
.describe('Capture output after sending'),
}),
execute: async ({ terminalId, text, pressEnter, return_output }) => {
const output = await sendText(terminalId, text, pressEnter, return_output);
return { output };
},
}),
rename_terminal: tool({
description: 'Rename an existing terminal',
parameters: z.object({
terminalId: z.string().describe('Terminal ID to rename'),
newName: z.string().describe('New unique name for the terminal'),
}),
execute: async ({ terminalId, newName }) => {
await renameTerminal(terminalId, newName);
return { success: true };
},
}),
kill_terminal: tool({
description: 'Close and destroy a terminal',
parameters: z.object({
terminalId: z.string().describe('Terminal ID to kill'),
}),
execute: async ({ terminalId }) => {
await killTerminal(terminalId);
return { success: true };
},
}),
};
/**
* Message interface for conversation
*/
export interface Message {
role: 'system' | 'user' | 'assistant';
content: string;
}
/**
* Streaming LLM parameters
*/
export interface StreamLLMParams {
messages: Message[];
onChunk?: (chunk: string) => void;
onToolCall?: (toolName: string, args: any) => void;
onToolResult?: (toolName: string, result: any) => void;
onFinish?: (fullText: string) => void;
}
/**
* Stream LLM response with automatic tool execution
*/
export async function streamLLM(params: StreamLLMParams): Promise<string> {
if (!openaiClient) {
throw new Error('OpenAI client not initialized. Call initializeOpenAI() first.');
}
const result = await streamText({
model: openaiClient('gpt-4o') as any, // Type assertion to work around AI SDK version conflicts
messages: params.messages,
tools: terminalTools,
maxSteps: 5, // Allow up to 5 tool executions per turn
onStepFinish: async (event) => {
// Called after each step (tool call or text generation)
if (event.toolCalls && event.toolCalls.length > 0) {
for (const toolCall of event.toolCalls) {
if (params.onToolCall) {
params.onToolCall(toolCall.toolName, toolCall.args);
}
}
}
if (event.toolResults && event.toolResults.length > 0) {
for (const toolResult of event.toolResults) {
if (params.onToolResult) {
params.onToolResult(toolResult.toolName, toolResult.result);
}
}
}
},
});
let fullText = '';
// Stream text chunks to the caller
for await (const chunk of result.textStream) {
fullText += chunk;
if (params.onChunk) {
params.onChunk(chunk);
}
}
if (params.onFinish) {
params.onFinish(fullText);
}
return fullText;
}