diff --git a/apps/rowboat/app/lib/agents.ts b/apps/rowboat/app/lib/agents.ts index a8f617e10..6a8601965 100644 --- a/apps/rowboat/app/lib/agents.ts +++ b/apps/rowboat/app/lib/agents.ts @@ -24,7 +24,7 @@ import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } // Make everything available as a promise const PROVIDER_API_KEY = process.env.PROVIDER_API_KEY || process.env.OPENAI_API_KEY || ''; const PROVIDER_BASE_URL = process.env.PROVIDER_BASE_URL || undefined; -const MODEL = process.env.PROVIDER_DEFAULT_MODEL || 'gpt-4o'; +const MODEL = process.env.PROVIDER_DEFAULT_MODEL || 'gpt-5'; const openai = createOpenAI({ apiKey: PROVIDER_API_KEY, @@ -517,10 +517,10 @@ ${config.description} ## About You -${config.outputVisibility === 'user_facing' - ? CONVERSATION_TYPE_INSTRUCTIONS() - : config.type === 'pipeline' - ? PIPELINE_TYPE_INSTRUCTIONS() +${config.outputVisibility === 'user_facing' + ? CONVERSATION_TYPE_INSTRUCTIONS() + : config.type === 'pipeline' + ? PIPELINE_TYPE_INSTRUCTIONS() : TASK_TYPE_INSTRUCTIONS()} ## Instructions @@ -535,12 +535,12 @@ ${CHILD_TRANSFER_RELATED_INSTRUCTIONS} `; let { sanitized, entities } = sanitizeTextWithMentions(instructions, workflow, config); - + // Remove agent transfer instructions for pipeline agents if (config.type === 'pipeline') { sanitized = sanitized.replace(CHILD_TRANSFER_RELATED_INSTRUCTIONS, ''); } - + agentLogger.log(`instructions: ${JSON.stringify(sanitized)}`); agentLogger.log(`mentions: ${JSON.stringify(entities)}`); @@ -561,11 +561,7 @@ ${CHILD_TRANSFER_RELATED_INSTRUCTIONS} name: config.name, instructions: sanitized, tools: agentTools, - model: aisdk(openai(config.model)), - // model: config.model, - modelSettings: { - temperature: 0.0, - } + model: aisdk(openai(config.model)) }); agentLogger.log(`created agent`); @@ -626,7 +622,7 @@ function getStartOfTurnAgentName( } } return stack; - } + } logger = logger.child(`getStartOfTurnAgentName`); const startAgentStack = createAgentCallStack(messages); @@ -640,7 +636,7 @@ function getStartOfTurnAgentName( logger.log(`last agent ${lastAgentName} not found in agent config, returning start agent: ${workflow.startAgent}`); return workflow.startAgent; } - + // For other agents, check control type switch (lastAgentConfig.controlType) { case 'retain': @@ -757,7 +753,7 @@ function ensureSystemMessage(logger: PrefixLogger, messages: z.infer): { ...acc, [prompt.name]: prompt }), {}); - + const pipelineConfig: Record> = (workflow.pipelines || []).reduce((acc, pipeline) => ({ ...acc, [pipeline.name]: pipeline }), {}); - + return { agentConfig, toolConfig, promptConfig, pipelineConfig }; } @@ -815,12 +811,12 @@ function createTools( ): Record { const tools: Record = {}; const toolLogger = logger.child('createTools'); - + toolLogger.log(`=== CREATING ${Object.keys(toolConfig).length} TOOLS ===`); for (const [toolName, config] of Object.entries(toolConfig)) { toolLogger.log(`creating tool: ${toolName} (type: ${config.mockTool ? 'mock' : config.isMcp ? 'mcp' : config.isComposio ? 'composio' : 'webhook'})`); - + if (config.mockTool) { tools[toolName] = createMockTool(logger, config); toolLogger.log(`✓ created mock tool: ${toolName}`); @@ -835,7 +831,7 @@ function createTools( toolLogger.log(`✓ created webhook tool: ${toolName} (fallback)`); } } - + toolLogger.log(`=== TOOL CREATION COMPLETE ===`); return tools; } @@ -869,12 +865,12 @@ function createAgents( // create agents for (const [agentName, config] of Object.entries(agentConfig)) { agentsLogger.log(`creating agent: ${agentName} (type: ${config.outputVisibility}, control: ${config.controlType})`); - + // Pipeline agents get special handling: // - Different instruction template (PIPELINE_TYPE_INSTRUCTIONS) // - Filtered mentions (tools only, no agents) // - No agent transfer instructions - + const { agent, entities } = createAgent( logger, projectId, @@ -884,7 +880,7 @@ function createAgents( promptConfig, ); agents[agentName] = agent; - + // Add pipeline entities to the agent's available mentions (unless it's a pipeline agent itself) // Pipeline agents cannot reference other agents or pipelines, only tools let agentEntities = entities; @@ -894,7 +890,7 @@ function createAgents( } else { agentsLogger.log(`${agentName} (pipeline agent) can reference: ${entities.length} entities only`); } - + mentions[agentName] = agentEntities; originalInstructions[agentName] = agent.instructions as string; // handoffs will be set after all agents are created @@ -906,17 +902,17 @@ function createAgents( for (const [agentName, agent] of Object.entries(agents)) { const connectedAgentNames = (mentions[agentName] || []).filter(e => e.type === 'agent').map(e => e.name); const connectedPipelineNames = (mentions[agentName] || []).filter(e => e.type === 'pipeline').map(e => e.name); - + // Pipeline agents have no agent handoffs (filtered out in validatePipelineAgentMentions) // They only have tool connections, no agent transfers allowed - + // Filter out pipeline agents from being handoff targets // Only allow handoffs to non-pipeline agents const validAgentNames = connectedAgentNames.filter(name => { const targetConfig = agentConfig[name]; return targetConfig && targetConfig.type !== 'pipeline'; }); - + // Convert pipeline mentions to handoffs to the first agent in each pipeline const pipelineFirstAgents: string[] = []; for (const pipelineName of connectedPipelineNames) { @@ -929,10 +925,10 @@ function createAgents( } } } - + // Combine regular agent handoffs with pipeline first agents const allHandoffTargets = [...validAgentNames, ...pipelineFirstAgents]; - + // Only store Agent objects in handoffs (filter out Handoff if present) const agentHandoffs = allHandoffTargets.map(e => agents[e]).filter(Boolean) as Agent[]; agent.handoffs = agentHandoffs; @@ -944,30 +940,30 @@ function createAgents( agentsLogger.log(`=== SETTING UP PIPELINE CHAINS ===`); for (const [pipelineName, pipeline] of Object.entries(pipelineConfig)) { agentsLogger.log(`setting up pipeline chain: ${pipelineName} -> [${pipeline.agents.join(' -> ')}]`); - + for (let i = 0; i < pipeline.agents.length; i++) { const currentAgentName = pipeline.agents[i]; const currentAgent = agents[currentAgentName]; - + if (!currentAgent) { agentsLogger.log(`warning: pipeline agent ${currentAgentName} not found in agent config`); continue; } - + // Pipeline agents have NO handoffs - they just execute once currentAgent.handoffs = []; - + // Add pipeline metadata to the agent for easy lookup (currentAgent as any).pipelineName = pipelineName; (currentAgent as any).pipelineIndex = i; (currentAgent as any).isLastInPipeline = i === pipeline.agents.length - 1; - + // Update originalHandoffs to reflect the final pipeline state originalHandoffs[currentAgentName] = []; - + agentsLogger.log(`pipeline agent ${currentAgentName} has no handoffs (will be controlled by pipeline controller)`); agentsLogger.log(`pipeline agent ${currentAgentName} metadata: pipeline=${pipelineName}, index=${i}, isLast=${i === pipeline.agents.length - 1}`); - + // Configure pipeline agents to relinquish control after completing their task const agentConfigObj = agentConfig[currentAgentName]; if (agentConfigObj && agentConfigObj.type === 'pipeline') { @@ -1023,13 +1019,13 @@ function maybeInjectGiveUpControlInstructions( injectLogger.log(`isInternal: ${isInternal}`); injectLogger.log(`isPipeline: ${isPipeline}`); injectLogger.log(`isRetain: ${isRetain}`); - + // For pipeline agents, they should continue pipeline execution, so no need to inject give up control if (isPipeline) { injectLogger.log(`Pipeline agent ${childAgentName} continues pipeline execution, no give up control needed`); return; } - + if (!isInternal && isRetain) { // inject give up control instructions agents[childAgentName].instructions = getGiveUpControlInstructions(agents[childAgentName], parentAgentName, injectLogger); @@ -1056,20 +1052,20 @@ function handlePipelineAgentExecution( const pipelineName = (currentAgent as any).pipelineName; const pipelineIndex = (currentAgent as any).pipelineIndex; const isLastInPipeline = (currentAgent as any).isLastInPipeline; - + if (!pipelineName || pipelineIndex === undefined) { logger.log(`warning: pipeline agent ${currentAgentName} missing pipeline metadata`); return { nextAgentName: null, shouldContinue: false }; } - + const pipeline = pipelineConfig[pipelineName]; if (!pipeline) { logger.log(`warning: pipeline ${pipelineName} not found in config`); return { nextAgentName: null, shouldContinue: false }; } - + let nextAgentName: string | null = null; - + if (!isLastInPipeline) { // Not the last agent - continue to next agent in pipeline nextAgentName = pipeline.agents[pipelineIndex + 1]; @@ -1079,24 +1075,24 @@ function handlePipelineAgentExecution( nextAgentName = stack.pop()!; logger.log(`-- pipeline controller: ${currentAgentName} -> ${nextAgentName} (pipeline ${pipelineName} complete, returning to caller)`); } - + if (nextAgentName) { // Create transfer events for pipeline continuation const transferEvents = createTransferEvents(currentAgentName, nextAgentName); const [transferStart, transferComplete] = transferEvents; - + // Add messages to turn turnMsgs.push(transferStart); turnMsgs.push(transferComplete); - + // Update transfer counter transferCounter.increment(currentAgentName, nextAgentName); - + logger.log(`switched to agent: ${nextAgentName} || reason: pipeline controller transfer`); - + return { nextAgentName, shouldContinue: true, transferEvents }; } - + return { nextAgentName: null, shouldContinue: false }; } @@ -1133,7 +1129,7 @@ export async function* streamResponse( logger.log(`pipelines: ${Object.keys(pipelineConfig).length} (${Object.keys(pipelineConfig).join(', ')})`); logger.log(`start agent: ${workflow.startAgent}`); logger.log(`=== END CONFIGURATION ===`); - + const stack: string[] = []; logger.log(`initialized stack: ${JSON.stringify(stack)}`); @@ -1149,7 +1145,7 @@ export async function* streamResponse( // get the agent that should be starting this turn const startOfTurnAgentName = getStartOfTurnAgentName(logger, messages, agentConfig, workflow); logger.log(`🎯 START AGENT DECISION: ${startOfTurnAgentName}`); - + let agentName = startOfTurnAgentName; // start the turn loop @@ -1157,7 +1153,7 @@ export async function* streamResponse( const turnMsgs: z.infer[] = [...messages]; logger.log('🎬 STARTING AGENT TURN'); - + // stack-based agent execution loop let iter = 0; const MAXTURNITERATIONS = 10; @@ -1243,7 +1239,7 @@ export async function* streamResponse( // handle handoff event if (event.name === 'handoff_occurred' && event.item.type === 'handoff_output_item') { eventLogger.log(`🔄 HANDOFF EVENT: ${agentName} -> ${event.item.targetAgent.name}`); - + // skip if its the same agent if (agentName === event.item.targetAgent.name) { eventLogger.log(`⚠️ SKIPPING: handoff to same agent: ${agentName}`); @@ -1298,10 +1294,10 @@ export async function* streamResponse( loopLogger.log(`📚 STACK PUSH: ${agentName} (new agent ${newAgentName} is internal/pipeline)`); loopLogger.log(`📚 STACK NOW: [${stack.join(' -> ')}]`); } - + // set this as the new agent name agentName = newAgentName; - + } // handle tool call result @@ -1365,14 +1361,14 @@ export async function* streamResponse( transferCounter, createTransferEvents ); - + // Emit transfer events if they exist if (result.transferEvents) { const [transferStart, transferComplete] = result.transferEvents; yield* emitEvent(eventLogger, transferStart); yield* emitEvent(eventLogger, transferComplete); } - + if (result.shouldContinue) { agentName = result.nextAgentName!; // Run the turn from the next agent @@ -1388,7 +1384,7 @@ export async function* streamResponse( agentName = workflow.startAgent; loopLogger.log(`-- using start agent: ${agentName} || reason: ${current} is an internal agent, it put out a message and it has a control type of ${currentAgentConfig?.controlType}, hence the flow of control needs to return to the start agent`); } - + // Only emit transfer events if we're actually changing agents if (agentName !== current) { loopLogger.log(`-- stack is now: ${JSON.stringify(stack)}`); diff --git a/apps/rowboat/app/lib/copilot/copilot.ts b/apps/rowboat/app/lib/copilot/copilot.ts index 4bd7b483d..1f40abe8a 100644 --- a/apps/rowboat/app/lib/copilot/copilot.ts +++ b/apps/rowboat/app/lib/copilot/copilot.ts @@ -16,8 +16,8 @@ import { composio, getTool } from "../composio/composio"; const PROVIDER_API_KEY = process.env.PROVIDER_API_KEY || process.env.OPENAI_API_KEY || ''; const PROVIDER_BASE_URL = process.env.PROVIDER_BASE_URL || undefined; -const COPILOT_MODEL = process.env.PROVIDER_COPILOT_MODEL || 'gpt-4.1'; -const AGENT_MODEL = process.env.PROVIDER_DEFAULT_MODEL || 'gpt-4.1'; +const COPILOT_MODEL = process.env.PROVIDER_COPILOT_MODEL || 'gpt-5'; +const AGENT_MODEL = process.env.PROVIDER_DEFAULT_MODEL || 'gpt-5'; const WORKFLOW_SCHEMA = JSON.stringify(zodToJsonSchema(Workflow)); @@ -138,7 +138,7 @@ async function searchRelevantTools(query: string): Promise { })); // Format the response - const toolConfigs = workflowTools.map(tool => + const toolConfigs = workflowTools.map(tool => `**${tool.name}**:\n\`\`\`json\n${JSON.stringify(tool, null, 2)}\n\`\`\`` ).join('\n\n'); diff --git a/apps/rowboat/package-lock.json b/apps/rowboat/package-lock.json index 465e2391f..f5de43f3a 100644 --- a/apps/rowboat/package-lock.json +++ b/apps/rowboat/package-lock.json @@ -8,7 +8,7 @@ "name": "demo.rowboatlabs.com", "version": "0.1.0", "dependencies": { - "@ai-sdk/openai": "^1.3.21", + "@ai-sdk/openai": "^1.3.24", "@auth0/nextjs-auth0": "^4.7.0", "@aws-sdk/client-s3": "^3.743.0", "@aws-sdk/s3-request-presigner": "^3.743.0", @@ -30,7 +30,7 @@ "@openai/agents-extensions": "^0.0.9", "@primer/react": "^37.27.0", "@qdrant/js-client-rest": "^1.13.0", - "ai": "^4.3.13", + "ai": "^4.3.19", "awilix": "^12.0.5", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.1", @@ -85,12 +85,12 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "1.3.21", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.21.tgz", - "integrity": "sha512-ipAhkRKUd2YaMmn7DAklX3N7Ywx/rCsJHVyb0V/lKRqPcc612qAFVbjg+Uve8QYJlbPxgfsM4s9JmCFp6PSdYw==", + "version": "1.3.24", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.24.tgz", + "integrity": "sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q==", "dependencies": { "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.7" + "@ai-sdk/provider-utils": "2.2.8" }, "engines": { "node": ">=18" @@ -111,9 +111,9 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.7.tgz", - "integrity": "sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", @@ -136,7 +136,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -145,12 +144,12 @@ } }, "node_modules/@ai-sdk/react": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.11.tgz", - "integrity": "sha512-+kPqLkJ3TWP6czaJPV+vzAKSUcKQ1598BUrcLHt56sH99+LhmIIW3ylZp0OfC3O6TR3eO1Lt0Yzw4R0mK6g9Gw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", "dependencies": { - "@ai-sdk/provider-utils": "2.2.7", - "@ai-sdk/ui-utils": "1.2.10", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -168,12 +167,12 @@ } }, "node_modules/@ai-sdk/ui-utils": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.10.tgz", - "integrity": "sha512-GUj+LBoAlRQF1dL/M49jtufGqtLOMApxTpCmVjoRpIPt/dFALVL9RfqfvxwztyIwbK+IxGzcYjSGRsrWrj+86g==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", "dependencies": { "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.7", + "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "engines": { @@ -8540,14 +8539,14 @@ } }, "node_modules/ai": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.13.tgz", - "integrity": "sha512-cC5HXItuOwGykSMacCPzNp6+NMTxeuTjOenztVgSJhdC9Z4OrzBxwkyeDAf4h1QP938ZFi7IBdq3u4lxVoVmvw==", + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", + "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", "dependencies": { "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.7", - "@ai-sdk/react": "1.2.11", - "@ai-sdk/ui-utils": "1.2.10", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, @@ -11218,11 +11217,11 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", - "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/express": { @@ -12852,9 +12851,9 @@ } }, "node_modules/jsondiffpatch/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -18156,10 +18155,9 @@ } }, "node_modules/zod": { - "version": "3.25.67", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", - "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", - "license": "MIT", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/apps/rowboat/package.json b/apps/rowboat/package.json index 170e6919d..eca908b2a 100644 --- a/apps/rowboat/package.json +++ b/apps/rowboat/package.json @@ -16,7 +16,7 @@ "jobs-worker": "tsx app/scripts/jobs-worker.ts" }, "dependencies": { - "@ai-sdk/openai": "^1.3.21", + "@ai-sdk/openai": "^1.3.24", "@auth0/nextjs-auth0": "^4.7.0", "@aws-sdk/client-s3": "^3.743.0", "@aws-sdk/s3-request-presigner": "^3.743.0", @@ -38,7 +38,7 @@ "@openai/agents-extensions": "^0.0.9", "@primer/react": "^37.27.0", "@qdrant/js-client-rest": "^1.13.0", - "ai": "^4.3.13", + "ai": "^4.3.19", "awilix": "^12.0.5", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.1",