/** * agentic-app-template's code-mode worker entry (`@openai/agents`). Mirrors * `@guuey/host`'s `run-openai.ts` — the platform's own OpenAI wiring over this * exact snapshot shape: build an `Agent` from the resolved system prompt + * MCP endpoints, run it streamed, emit every raw `RunStreamEvent` to the * Router via `emit.native`, and resolve the turn's final text from * `stream.finalOutput`. * * `MaxTurnsExceededError` handling mirrors `run-openai.ts`'s INTENT — there * max-turns is a soft terminal (`done(finalText, "max_turns")` after a * sentinel), NOT a hard `error`; the partial output is still the turn's * result. `serveNative`'s handler contract has neither a stopReason nor a * sentinel channel (a return is always `done(.., "end_turn")`; a throw is a * hard `error` that would discard the partial text), so the cutoff is made * visible by appending a note to the returned result instead of rethrowing. */ import { Agent, MaxTurnsExceededError, MCPServerStreamableHttp, run } from "@openai/agents"; import type { MCPServer } from "@openai/agents"; import { mcpToolCustomData, serveNative } from "@guuey/worker"; import { loadAgent, systemPrompt, mcpEndpoints, withHistory } from "./agent-config.js"; await serveNative( async (invoke, emit) => { const agent = loadAgent(invoke); const endpoints = mcpEndpoints(invoke, agent); const mcpServers: MCPServerStreamableHttp[] = Object.entries(endpoints).map( ([name, ep]) => new MCPServerStreamableHttp({ url: ep.url, name, ...(Object.keys(ep.headers).length > 0 ? { requestInit: { headers: ep.headers } } : {}), // The ONLY channel that carries an MCP result's structuredContent + // _meta onto the wire (guuey#981): the card's ui:// locator and the // ggui render-cache marker ride here. Without it a code-mode OpenAI // app shows "✓ ggui render" and no card. customDataExtractor: mcpToolCustomData, }) ); try { for (const server of mcpServers) { await server.connect(); } const sdkAgent = new Agent({ name: "guuey-agent", instructions: systemPrompt(invoke, agent) ?? "You are a helpful assistant.", mcpServers: mcpServers as MCPServer[], ...(agent.model ? { model: agent.model } : {}), }); const stream = await run(sdkAgent, withHistory(invoke), { stream: true, ...(agent.runtime?.maxTurns ? { maxTurns: agent.runtime.maxTurns } : {}), }); for await (const event of stream) { emit.native(JSON.parse(JSON.stringify(event))); } try { await stream.completed; } catch (err) { if (err instanceof MaxTurnsExceededError) { // Soft terminal (see module doc): keep whatever partial output the // stream produced and make the cutoff visible in the result text. const partial = typeof stream.finalOutput === "string" ? stream.finalOutput : ""; const note = "[agent stopped: maxTurns limit reached]"; return partial ? `${partial}\n\n${note}` : note; } throw err; } return typeof stream.finalOutput === "string" ? stream.finalOutput : ""; } finally { await Promise.allSettled(mcpServers.map((s) => s.close())); } }, { framework: "openai-agents-sdk", sdkName: "@openai/agents" } );