Prompt is too long

This commit is contained in:
Mohamed Boudra
2025-10-19 16:23:13 +02:00
parent 40b199e329
commit c3049b8fa0
67 changed files with 504 additions and 17776 deletions
@@ -1,6 +1,6 @@
import { streamText, tool } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { z } from 'zod';
import { stepCountIs, streamText, tool } from "ai";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { z } from "zod";
import {
listTerminals,
createTerminal,
@@ -8,16 +8,8 @@ import {
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 });
}
} from "../daemon/terminal-manager.js";
import invariant from "tiny-invariant";
/**
* Terminal tools using Vercel AI SDK tool() function
@@ -25,8 +17,8 @@ export function initializeOpenAI(apiKey: string): void {
*/
export const terminalTools = {
list_terminals: tool({
description: 'List all available terminals in the voice-dev session',
parameters: z.object({}),
description: "List all available terminals in the voice-dev session",
inputSchema: z.object({}),
execute: async () => {
const terminals = await listTerminals();
return { terminals };
@@ -34,24 +26,38 @@ export const terminalTools = {
}),
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'),
description:
"Create a new terminal with specified name and working directory",
inputSchema: 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 });
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'),
description: "Capture and return the output from a terminal",
inputSchema: 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);
@@ -60,30 +66,38 @@ export const terminalTools = {
}),
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'),
description: "Send text or commands to a terminal",
inputSchema: 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'),
.describe("Capture output after sending"),
}),
execute: async ({ terminalId, text, pressEnter, return_output }) => {
const output = await sendText(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'),
description: "Rename an existing terminal",
inputSchema: 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);
@@ -92,9 +106,9 @@ export const terminalTools = {
}),
kill_terminal: tool({
description: 'Close and destroy a terminal',
parameters: z.object({
terminalId: z.string().describe('Terminal ID to kill'),
description: "Close and destroy a terminal",
inputSchema: z.object({
terminalId: z.string().describe("Terminal ID to kill"),
}),
execute: async ({ terminalId }) => {
await killTerminal(terminalId);
@@ -107,7 +121,7 @@ export const terminalTools = {
* Message interface for conversation
*/
export interface Message {
role: 'system' | 'user' | 'assistant';
role: "system" | "user" | "assistant";
content: string;
}
@@ -126,21 +140,23 @@ export interface StreamLLMParams {
* 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.');
}
invariant(process.env.OPENROUTER_API_KEY, "OPENROUTER_API_KEY is required");
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = await streamText({
model: openaiClient('gpt-4o') as any, // Type assertion to work around AI SDK version conflicts
model: openrouter("anthropic/claude-haiku-4.5"),
messages: params.messages,
tools: terminalTools,
maxSteps: 5, // Allow up to 5 tool executions per turn
stopWhen: stepCountIs(10),
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);
params.onToolCall(toolCall.toolName, toolCall.input);
}
}
}
@@ -148,14 +164,14 @@ export async function streamLLM(params: StreamLLMParams): Promise<string> {
if (event.toolResults && event.toolResults.length > 0) {
for (const toolResult of event.toolResults) {
if (params.onToolResult) {
params.onToolResult(toolResult.toolName, toolResult.result);
params.onToolResult(toolResult.toolName, toolResult.output);
}
}
}
},
});
let fullText = '';
let fullText = "";
// Stream text chunks to the caller
for await (const chunk of result.textStream) {
@@ -59,15 +59,8 @@ export async function processUserMessage(params: {
});
conversation.lastActivity = new Date();
// Broadcast user message to WebSocket clients
if (params.wsServer) {
params.wsServer.broadcastActivityLog({
id: uuidv4(),
timestamp: new Date(),
type: 'transcript',
content: params.message,
});
}
// Note: User message is already broadcast by the caller (e.g., after STT in index.ts)
// No need to broadcast again here to avoid duplication
let assistantResponse = '';