@copilotkit/runtime 1.72.0 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.cjs +82 -8
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +55 -8
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +53 -6
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +83 -10
- package/dist/agent/index.mjs.map +1 -1
- package/dist/agent/learned-skills.cjs +77 -0
- package/dist/agent/learned-skills.cjs.map +1 -0
- package/dist/agent/learned-skills.d.cts +16 -0
- package/dist/agent/learned-skills.d.cts.map +1 -0
- package/dist/agent/learned-skills.d.mts +16 -0
- package/dist/agent/learned-skills.d.mts.map +1 -0
- package/dist/agent/learned-skills.mjs +75 -0
- package/dist/agent/learned-skills.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +27 -20
- package/dist/index.d.mts +27 -20
- package/dist/index.mjs +1 -1
- package/dist/internal/learned-skills.cjs +22 -0
- package/dist/internal/learned-skills.d.cts +10 -0
- package/dist/internal/learned-skills.d.mts +10 -0
- package/dist/internal/learned-skills.mjs +10 -0
- package/dist/langgraph.d.cts +3 -1
- package/dist/langgraph.d.mts +3 -1
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.cjs.map +1 -1
- package/dist/lib/index.d.cts +2 -2
- package/dist/lib/index.d.cts.map +1 -1
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.mts.map +1 -1
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/integrations/nextjs/app-router.cjs.map +1 -1
- package/dist/lib/integrations/nextjs/app-router.mjs.map +1 -1
- package/dist/lib/integrations/node-express/index.cjs.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs.map +1 -1
- package/dist/lib/integrations/node-http/index.cjs.map +1 -1
- package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
- package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs.map +1 -1
- package/dist/lib/integrations/shared.cjs.map +1 -1
- package/dist/lib/integrations/shared.d.cts +1 -1
- package/dist/lib/integrations/shared.d.cts.map +1 -1
- package/dist/lib/integrations/shared.d.mts +1 -1
- package/dist/lib/integrations/shared.d.mts.map +1 -1
- package/dist/lib/integrations/shared.mjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs +2 -16
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts +2 -2
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts +2 -2
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +3 -17
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/package.cjs +9 -3
- package/dist/package.mjs +9 -3
- package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
- package/dist/service-adapters/shared/sdk-client-utils.cjs.map +1 -1
- package/dist/service-adapters/shared/sdk-client-utils.d.cts.map +1 -1
- package/dist/service-adapters/shared/sdk-client-utils.d.mts.map +1 -1
- package/dist/service-adapters/shared/sdk-client-utils.mjs.map +1 -1
- package/dist/v2/index.cjs +7 -0
- package/dist/v2/index.d.cts +6 -5
- package/dist/v2/index.d.mts +6 -5
- package/dist/v2/index.mjs +3 -3
- package/dist/v2/runtime/core/channel-manager.d.cts +1 -1
- package/dist/v2/runtime/core/channel-manager.d.mts +2 -1
- package/dist/v2/runtime/core/channel-manager.d.mts.map +1 -1
- package/dist/v2/runtime/core/debug-event-bus.cjs +22 -0
- package/dist/v2/runtime/core/debug-event-bus.cjs.map +1 -1
- package/dist/v2/runtime/core/debug-event-bus.d.cts +1 -1
- package/dist/v2/runtime/core/debug-event-bus.d.mts +1 -1
- package/dist/v2/runtime/core/debug-event-bus.mjs +22 -1
- package/dist/v2/runtime/core/debug-event-bus.mjs.map +1 -1
- package/dist/v2/runtime/core/runtime.cjs +10 -5
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +29 -3
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +28 -2
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +11 -6
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs +3 -2
- package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs +3 -2
- package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-debug-events.cjs +2 -1
- package/dist/v2/runtime/handlers/handle-debug-events.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-debug-events.mjs +3 -1
- package/dist/v2/runtime/handlers/handle-debug-events.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +5 -3
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +5 -3
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-suggest.cjs +4 -3
- package/dist/v2/runtime/handlers/handle-suggest.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-suggest.mjs +5 -4
- package/dist/v2/runtime/handlers/handle-suggest.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/cloud-telemetry.cjs +31 -0
- package/dist/v2/runtime/handlers/shared/cloud-telemetry.cjs.map +1 -0
- package/dist/v2/runtime/handlers/shared/cloud-telemetry.mjs +30 -0
- package/dist/v2/runtime/handlers/shared/cloud-telemetry.mjs.map +1 -0
- package/dist/v2/runtime/handlers/shared/sse-keep-alive.cjs +69 -0
- package/dist/v2/runtime/handlers/shared/sse-keep-alive.cjs.map +1 -0
- package/dist/v2/runtime/handlers/shared/sse-keep-alive.mjs +67 -0
- package/dist/v2/runtime/handlers/shared/sse-keep-alive.mjs.map +1 -0
- package/dist/v2/runtime/handlers/shared/sse-response.cjs +35 -4
- package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs +35 -4
- package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/connect.cjs +1 -0
- package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/connect.mjs +1 -0
- package/dist/v2/runtime/handlers/sse/connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.cjs +2 -0
- package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.mjs +2 -0
- package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
- package/dist/v2/runtime/index.d.cts +3 -3
- package/dist/v2/runtime/index.d.mts +3 -3
- package/dist/v2/runtime/intelligence-platform/client.d.cts +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +1 -1
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.cjs +132 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.d.cts +11 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.d.mts +11 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.mjs +128 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/archive.mjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.cjs +48 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.d.cts +27 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.d.mts +27 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.mjs +48 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/config.mjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.cjs +11 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.d.cts +8 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.d.mts +8 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.mjs +11 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/errors.mjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/index.d.cts +8 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/index.d.mts +8 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.cjs +132 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.d.cts +31 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.d.mts +31 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.mjs +132 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/registry.mjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.cjs +42 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.d.cts +13 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.d.mts +13 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.mjs +39 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/skill-content.mjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.cjs +93 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.d.cts +28 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.d.mts +28 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.mjs +92 -0
- package/dist/v2/runtime/intelligence-platform/skill-registry/snapshot.mjs.map +1 -0
- package/dist/v2/runtime/runner/agent-runner.d.cts +1 -1
- package/dist/v2/runtime/runner/in-memory.d.cts +1 -1
- package/dist/v2/runtime/runner/index.d.cts +2 -2
- package/dist/v2/runtime/runner/index.d.mts +2 -2
- package/dist/v2/runtime/runner/index.mjs +2 -2
- package/dist/v2/runtime/runner/intelligence.d.cts +1 -1
- package/dist/v2/runtime/telemetry/events.d.cts +12 -0
- package/dist/v2/runtime/telemetry/events.d.cts.map +1 -1
- package/dist/v2/runtime/telemetry/events.d.mts +12 -0
- package/dist/v2/runtime/telemetry/events.d.mts.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.cjs +3 -2
- package/dist/v2/runtime/telemetry/telemetry-client.cjs.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.d.cts.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.d.mts.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.mjs +7 -6
- package/dist/v2/runtime/telemetry/telemetry-client.mjs.map +1 -1
- package/package.json +9 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-suggest.mjs","names":[],"sources":["../../../../src/v2/runtime/handlers/handle-suggest.ts"],"sourcesContent":["import type { AbstractAgent, BaseEvent } from \"@ag-ui/client\";\nimport {
|
|
1
|
+
{"version":3,"file":"handle-suggest.mjs","names":[],"sources":["../../../../src/v2/runtime/handlers/handle-suggest.ts"],"sourcesContent":["import type { AbstractAgent, BaseEvent } from \"@ag-ui/client\";\nimport { createRunEventFinalizer } from \"@copilotkit/shared\";\nimport { Observable } from \"rxjs\";\nimport type { CopilotRuntimeLike } from \"../core/runtime\";\nimport {\n mergeForwardableHeaders,\n resolveForwardHeadersPolicy,\n} from \"./header-utils\";\nimport { cloneAgentForRequest, parseRunRequest } from \"./shared/agent-utils\";\nimport { createSseEventResponse } from \"./shared/sse-response\";\n\n/**\n * Parameters for {@link handleSuggestAgent}.\n */\ninterface SuggestAgentParameters {\n /** The runtime whose registry the agent is resolved from. */\n runtime: CopilotRuntimeLike;\n /** The incoming `POST /agent/:agentId/suggest` request. */\n request: Request;\n /** The route-resolved agent id to run. */\n agentId: string;\n}\n\n/**\n * Stateless suggestion run.\n *\n * Executes the provider agent **directly** and streams its AG-UI events back as\n * SSE — the same wire format as `/agent/:id/run`, so the client consumes it with\n * the stock `HttpAgent` transport and chips fill in as the provider emits them.\n * It deliberately does not go through `runtime.runner`: the runner's\n * `run()` writes to a module-level store keyed by threadId (backing the SSE\n * runtime's local thread endpoints), which would leak the throwaway suggestion\n * thread. This handler runs the agent's event pipeline **without** that\n * persistence — no thread, lock, gateway, name-gen, or run telemetry — so\n * dynamic suggestions are side-effect-free in every runtime mode.\n *\n * The only per-request configuration it applies is forwarding the request's\n * allowlisted headers (`authorization` + `x-*`) onto the agent clone. It does\n * **not** attach any request middleware — no A2UI, no MCPApps, no\n * OpenGenerativeUI, no Intelligence enterprise-learning tools. The **client**\n * (the core suggestion engine) forces `toolChoice: copilotkitSuggest` in the\n * request body; this handler does not set tool choice itself and relies on it\n * being present in the incoming `input`. Given that forced tool choice, any\n * middleware-injected tools are dead weight, and MCPApps setup can incur a\n * `listTools` network round-trip per suggestion under `available: \"always\"` — a\n * side effect this path must never pay.\n *\n * When the client aborts the HTTP request (via its `AbortController`), the\n * server-side run is cancelled best-effort so an aborted suggestion does not\n * keep running a provider call to completion.\n *\n * @param params - The runtime, request, and resolved agent id.\n * @returns A `text/event-stream` `Response` of the provider agent's AG-UI events\n * on success, or the resolution `Response` (e.g. 404/400) when agent/body\n * resolution fails before streaming begins.\n */\nexport async function handleSuggestAgent({\n runtime,\n request,\n agentId,\n}: SuggestAgentParameters): Promise<Response> {\n const agent = await cloneAgentForRequest(runtime, agentId, request);\n if (agent instanceof Response) {\n return agent;\n }\n\n // Carry the registry key onto the clone so any framework relying on it during\n // the run sees the correct agentId.\n agent.agentId = agentId;\n\n const input = await parseRunRequest(request);\n if (input instanceof Response) {\n return input;\n }\n\n // Forward eligible inbound headers onto the clone under the runtime's resolved\n // forwarding policy (same helper the run handler uses: `authorization` /\n // custom `x-*`, infra/proxy/platform headers denylisted, server-set headers\n // winning on collision — #5712). Unlike a full agent run, the suggest path\n // intentionally attaches no middleware (see the handler docblock): the forced\n // `copilotkitSuggest` tool choice makes any middleware-injected tools dead\n // weight, and MCPApps setup can trigger a `listTools` network round-trip we\n // must never incur per suggestion.\n //\n // `AbstractAgent` doesn't declare `headers` on its base type (concrete\n // framework agents add it), so narrow to the header-carrying shape rather\n // than casting to `any`.\n const headerCarryingAgent = agent as AbstractAgent & {\n headers?: Record<string, string>;\n };\n headerCarryingAgent.headers = mergeForwardableHeaders(\n headerCarryingAgent.headers,\n request,\n runtime.forwardHeadersPolicy ?? resolveForwardHeadersPolicy(undefined),\n );\n\n agent.setMessages(input.messages);\n agent.setState(input.state);\n agent.threadId = input.threadId;\n\n // Stream the provider agent's events over SSE without persistence. This is\n // the runner's event pipeline (`agent.runAgent({ onEvent })` + terminal-event\n // finalization) minus the `GLOBAL_STORE` writes that would leak a thread.\n // `captureTelemetry: false` keeps suggestions out of run telemetry; omitting\n // `debugEventBus` keeps them out of the inspector's run trace.\n return createSseEventResponse({\n request,\n agentId,\n captureTelemetry: false,\n keepAliveIntervalSeconds: runtime.sseKeepAliveIntervalSeconds,\n observableFactory: () =>\n new Observable<BaseEvent>((subscriber) => {\n // Tracks open message/tool lifecycles so the missing terminal events\n // (an unclosed message/tool call, or a `RUN_FINISHED`) can be appended\n // — the same closure the runner applies — so the client sees a\n // well-formed AG-UI sequence. Nothing is persisted here, so only the\n // open ids are kept, never the streamed events themselves.\n const finalizer = createRunEventFinalizer();\n let settled = false;\n\n void agent\n .runAgent(input, {\n onEvent: ({ event }) => {\n finalizer.observe(event);\n subscriber.next(event);\n },\n })\n .then(() => {\n for (const event of finalizer.finalize({ stopRequested: false })) {\n subscriber.next(event);\n }\n settled = true;\n subscriber.complete();\n })\n .catch((error: unknown) => {\n settled = true;\n subscriber.error(error);\n });\n\n // Teardown fires when the response stream is torn down (client abort /\n // disconnect). Cancel the still-running provider call best-effort.\n return () => {\n if (!settled && typeof agent.abortRun === \"function\") {\n try {\n agent.abortRun();\n } catch {\n // best-effort — nothing actionable if aborting the run fails.\n }\n }\n };\n }),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,eAAsB,mBAAmB,EACvC,SACA,SACA,WAC4C;CAC5C,MAAM,QAAQ,MAAM,qBAAqB,SAAS,SAAS,QAAQ;AACnE,KAAI,iBAAiB,SACnB,QAAO;AAKT,OAAM,UAAU;CAEhB,MAAM,QAAQ,MAAM,gBAAgB,QAAQ;AAC5C,KAAI,iBAAiB,SACnB,QAAO;CAeT,MAAM,sBAAsB;AAG5B,qBAAoB,UAAU,wBAC5B,oBAAoB,SACpB,SACA,QAAQ,wBAAwB,4BAA4B,OAAU,CACvE;AAED,OAAM,YAAY,MAAM,SAAS;AACjC,OAAM,SAAS,MAAM,MAAM;AAC3B,OAAM,WAAW,MAAM;AAOvB,QAAO,uBAAuB;EAC5B;EACA;EACA,kBAAkB;EAClB,0BAA0B,QAAQ;EAClC,yBACE,IAAI,YAAuB,eAAe;GAMxC,MAAM,YAAY,yBAAyB;GAC3C,IAAI,UAAU;AAEd,GAAK,MACF,SAAS,OAAO,EACf,UAAU,EAAE,YAAY;AACtB,cAAU,QAAQ,MAAM;AACxB,eAAW,KAAK,MAAM;MAEzB,CAAC,CACD,WAAW;AACV,SAAK,MAAM,SAAS,UAAU,SAAS,EAAE,eAAe,OAAO,CAAC,CAC9D,YAAW,KAAK,MAAM;AAExB,cAAU;AACV,eAAW,UAAU;KACrB,CACD,OAAO,UAAmB;AACzB,cAAU;AACV,eAAW,MAAM,MAAM;KACvB;AAIJ,gBAAa;AACX,QAAI,CAAC,WAAW,OAAO,MAAM,aAAa,WACxC,KAAI;AACF,WAAM,UAAU;YACV;;IAKZ;EACL,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require("reflect-metadata");
|
|
2
|
+
const require_runtime = require('../../../../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
let _copilotkit_shared = require("@copilotkit/shared");
|
|
4
|
+
|
|
5
|
+
//#region src/v2/runtime/handlers/shared/cloud-telemetry.ts
|
|
6
|
+
/**
|
|
7
|
+
* Whether the caller forwarded CopilotCloud guardrails configuration.
|
|
8
|
+
*
|
|
9
|
+
* Guardrails reach a runtime as `forwardedProps.cloud.guardrails`. An earlier
|
|
10
|
+
* version read the body only when the CopilotCloud key header was present,
|
|
11
|
+
* because in principle only a CopilotCloud client sends guardrails and that
|
|
12
|
+
* client always sends its key. Production does not hold to that: of 112
|
|
13
|
+
* guardrails-enabled requests over 90 days, 2 carried no key, and they
|
|
14
|
+
* undercounted as `false`. The signal is rare enough — 13 people across 9
|
|
15
|
+
* Cloud keys in a year — that losing 2% of it to save a body clone is a bad
|
|
16
|
+
* trade, so the body is now always read.
|
|
17
|
+
*
|
|
18
|
+
* `readBody` clones rather than consuming, so the handler can still parse the
|
|
19
|
+
* request afterwards.
|
|
20
|
+
*/
|
|
21
|
+
async function readGuardrailsEnabled(request) {
|
|
22
|
+
try {
|
|
23
|
+
return ((await (0, _copilotkit_shared.readBody)(request))?.forwardedProps)?.cloud?.guardrails !== void 0;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
exports.readGuardrailsEnabled = readGuardrailsEnabled;
|
|
31
|
+
//# sourceMappingURL=cloud-telemetry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-telemetry.cjs","names":[],"sources":["../../../../../src/v2/runtime/handlers/shared/cloud-telemetry.ts"],"sourcesContent":["// The CopilotCloud guardrails flag on `oss.runtime.copilot_request_created`.\n//\n// It lives here because the v1 entrypoint used to compute it in its own\n// middleware and emit a second copy of the event. That copy is gone; this is\n// what it carried and the v2 handlers did not.\n//\n// The v1 copy also carried `cloud.base_url`, which is deliberately not\n// ported. The cross-language conformance suite pins this event to exactly\n// requestType, cloud.guardrails.enabled and cloud.api_key_provided\n// (`tools/runtime-conformance/telemetry-cases.mjs`), and the four native\n// runtimes send nothing else. A near-constant read off an env var is not\n// worth diverging the canonical event shape for.\n\nimport { readBody } from \"@copilotkit/shared\";\nimport type { RunAgentInput } from \"@ag-ui/client\";\n\n/**\n * Whether the caller forwarded CopilotCloud guardrails configuration.\n *\n * Guardrails reach a runtime as `forwardedProps.cloud.guardrails`. An earlier\n * version read the body only when the CopilotCloud key header was present,\n * because in principle only a CopilotCloud client sends guardrails and that\n * client always sends its key. Production does not hold to that: of 112\n * guardrails-enabled requests over 90 days, 2 carried no key, and they\n * undercounted as `false`. The signal is rare enough — 13 people across 9\n * Cloud keys in a year — that losing 2% of it to save a body clone is a bad\n * trade, so the body is now always read.\n *\n * `readBody` clones rather than consuming, so the handler can still parse the\n * request afterwards.\n */\nexport async function readGuardrailsEnabled(\n request: Request,\n): Promise<boolean> {\n try {\n const body = (await readBody(request)) as RunAgentInput | undefined;\n const forwardedProps = body?.forwardedProps as\n | { cloud?: { guardrails?: unknown } }\n | undefined;\n return forwardedProps?.cloud?.guardrails !== undefined;\n } catch {\n // Telemetry must never decide whether a request is served.\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+BA,eAAsB,sBACpB,SACkB;AAClB,KAAI;AAKF,WAJc,uCAAe,QAAQ,GACR,iBAGN,OAAO,eAAe;SACvC;AAEN,SAAO"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { readBody } from "@copilotkit/shared";
|
|
3
|
+
|
|
4
|
+
//#region src/v2/runtime/handlers/shared/cloud-telemetry.ts
|
|
5
|
+
/**
|
|
6
|
+
* Whether the caller forwarded CopilotCloud guardrails configuration.
|
|
7
|
+
*
|
|
8
|
+
* Guardrails reach a runtime as `forwardedProps.cloud.guardrails`. An earlier
|
|
9
|
+
* version read the body only when the CopilotCloud key header was present,
|
|
10
|
+
* because in principle only a CopilotCloud client sends guardrails and that
|
|
11
|
+
* client always sends its key. Production does not hold to that: of 112
|
|
12
|
+
* guardrails-enabled requests over 90 days, 2 carried no key, and they
|
|
13
|
+
* undercounted as `false`. The signal is rare enough — 13 people across 9
|
|
14
|
+
* Cloud keys in a year — that losing 2% of it to save a body clone is a bad
|
|
15
|
+
* trade, so the body is now always read.
|
|
16
|
+
*
|
|
17
|
+
* `readBody` clones rather than consuming, so the handler can still parse the
|
|
18
|
+
* request afterwards.
|
|
19
|
+
*/
|
|
20
|
+
async function readGuardrailsEnabled(request) {
|
|
21
|
+
try {
|
|
22
|
+
return ((await readBody(request))?.forwardedProps)?.cloud?.guardrails !== void 0;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { readGuardrailsEnabled };
|
|
30
|
+
//# sourceMappingURL=cloud-telemetry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-telemetry.mjs","names":[],"sources":["../../../../../src/v2/runtime/handlers/shared/cloud-telemetry.ts"],"sourcesContent":["// The CopilotCloud guardrails flag on `oss.runtime.copilot_request_created`.\n//\n// It lives here because the v1 entrypoint used to compute it in its own\n// middleware and emit a second copy of the event. That copy is gone; this is\n// what it carried and the v2 handlers did not.\n//\n// The v1 copy also carried `cloud.base_url`, which is deliberately not\n// ported. The cross-language conformance suite pins this event to exactly\n// requestType, cloud.guardrails.enabled and cloud.api_key_provided\n// (`tools/runtime-conformance/telemetry-cases.mjs`), and the four native\n// runtimes send nothing else. A near-constant read off an env var is not\n// worth diverging the canonical event shape for.\n\nimport { readBody } from \"@copilotkit/shared\";\nimport type { RunAgentInput } from \"@ag-ui/client\";\n\n/**\n * Whether the caller forwarded CopilotCloud guardrails configuration.\n *\n * Guardrails reach a runtime as `forwardedProps.cloud.guardrails`. An earlier\n * version read the body only when the CopilotCloud key header was present,\n * because in principle only a CopilotCloud client sends guardrails and that\n * client always sends its key. Production does not hold to that: of 112\n * guardrails-enabled requests over 90 days, 2 carried no key, and they\n * undercounted as `false`. The signal is rare enough — 13 people across 9\n * Cloud keys in a year — that losing 2% of it to save a body clone is a bad\n * trade, so the body is now always read.\n *\n * `readBody` clones rather than consuming, so the handler can still parse the\n * request afterwards.\n */\nexport async function readGuardrailsEnabled(\n request: Request,\n): Promise<boolean> {\n try {\n const body = (await readBody(request)) as RunAgentInput | undefined;\n const forwardedProps = body?.forwardedProps as\n | { cloud?: { guardrails?: unknown } }\n | undefined;\n return forwardedProps?.cloud?.guardrails !== undefined;\n } catch {\n // Telemetry must never decide whether a request is served.\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,eAAsB,sBACpB,SACkB;AAClB,KAAI;AAKF,WAJc,MAAM,SAAS,QAAQ,GACR,iBAGN,OAAO,eAAe;SACvC;AAEN,SAAO"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require("reflect-metadata");
|
|
2
|
+
|
|
3
|
+
//#region src/v2/runtime/handlers/shared/sse-keep-alive.ts
|
|
4
|
+
/**
|
|
5
|
+
* Default silence, in seconds, before an SSE response carries a keep-alive
|
|
6
|
+
* comment. Sits under the tightest common proxy idle timeouts (55–60 s) and
|
|
7
|
+
* beside `lockHeartbeatIntervalSeconds`, which shares the unit and default.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS = 15;
|
|
10
|
+
/**
|
|
11
|
+
* Longest interval the timer can honour. Timers take a signed 32-bit
|
|
12
|
+
* millisecond delay; Node silently replaces anything larger with 1 ms, which
|
|
13
|
+
* would turn a keep-alive into a flood.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS = (2 ** 31 - 1) / 1e3;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the `sseKeepAliveIntervalSeconds` runtime option once, at
|
|
18
|
+
* construction, so every SSE call site reads one validated value.
|
|
19
|
+
* `undefined` takes the default; `0` disables the keep-alive.
|
|
20
|
+
*/
|
|
21
|
+
function resolveSseKeepAliveIntervalSeconds(value) {
|
|
22
|
+
const seconds = value ?? DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS;
|
|
23
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS) throw new RangeError(`sseKeepAliveIntervalSeconds must be between 0 and ${MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS}, got ${String(value)}`);
|
|
24
|
+
return seconds;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* SSE comment frame. Parsers drop comment lines, so no AG-UI event is
|
|
28
|
+
* invented and the verifier, history and middleware never see it.
|
|
29
|
+
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
|
30
|
+
*/
|
|
31
|
+
const KEEP_ALIVE_FRAME = new TextEncoder().encode(": keep-alive\n\n");
|
|
32
|
+
/**
|
|
33
|
+
* Keeps a quiet SSE body open by writing a comment after `idleMs` of silence.
|
|
34
|
+
*
|
|
35
|
+
* Any byte from `source` resets the clock, so an active stream adds nothing
|
|
36
|
+
* to the wire. A back-pressured reader skips the tick instead of queueing
|
|
37
|
+
* frames. The timer stops when the pipe settles, which covers both the source
|
|
38
|
+
* completing and the reader cancelling, on every supported Node version.
|
|
39
|
+
*/
|
|
40
|
+
function keepAliveSse(source, idleMs) {
|
|
41
|
+
let timer;
|
|
42
|
+
const stop = () => {
|
|
43
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
44
|
+
timer = void 0;
|
|
45
|
+
};
|
|
46
|
+
const arm = (controller) => {
|
|
47
|
+
stop();
|
|
48
|
+
timer = setTimeout(() => {
|
|
49
|
+
if (controller.desiredSize === 1) controller.enqueue(KEEP_ALIVE_FRAME);
|
|
50
|
+
arm(controller);
|
|
51
|
+
}, idleMs);
|
|
52
|
+
timer.unref?.();
|
|
53
|
+
};
|
|
54
|
+
const transform = new TransformStream({
|
|
55
|
+
start: arm,
|
|
56
|
+
transform(chunk, controller) {
|
|
57
|
+
controller.enqueue(chunk);
|
|
58
|
+
arm(controller);
|
|
59
|
+
},
|
|
60
|
+
flush: stop
|
|
61
|
+
}, void 0, { highWaterMark: 1 });
|
|
62
|
+
source.pipeTo(transform.writable).catch(() => {}).finally(stop);
|
|
63
|
+
return transform.readable;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.keepAliveSse = keepAliveSse;
|
|
68
|
+
exports.resolveSseKeepAliveIntervalSeconds = resolveSseKeepAliveIntervalSeconds;
|
|
69
|
+
//# sourceMappingURL=sse-keep-alive.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-keep-alive.cjs","names":[],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-keep-alive.ts"],"sourcesContent":["/**\n * Default silence, in seconds, before an SSE response carries a keep-alive\n * comment. Sits under the tightest common proxy idle timeouts (55–60 s) and\n * beside `lockHeartbeatIntervalSeconds`, which shares the unit and default.\n */\nexport const DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS = 15;\n\n/**\n * Longest interval the timer can honour. Timers take a signed 32-bit\n * millisecond delay; Node silently replaces anything larger with 1 ms, which\n * would turn a keep-alive into a flood.\n */\nexport const MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS = (2 ** 31 - 1) / 1000;\n\n/**\n * Resolves the `sseKeepAliveIntervalSeconds` runtime option once, at\n * construction, so every SSE call site reads one validated value.\n * `undefined` takes the default; `0` disables the keep-alive.\n */\nexport function resolveSseKeepAliveIntervalSeconds(\n value: number | undefined,\n): number {\n const seconds = value ?? DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS;\n if (\n !Number.isFinite(seconds) ||\n seconds < 0 ||\n seconds > MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS\n ) {\n throw new RangeError(\n `sseKeepAliveIntervalSeconds must be between 0 and ${MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS}, got ${String(value)}`,\n );\n }\n return seconds;\n}\n\n/**\n * SSE comment frame. Parsers drop comment lines, so no AG-UI event is\n * invented and the verifier, history and middleware never see it.\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes\n */\nconst KEEP_ALIVE_FRAME = new TextEncoder().encode(\": keep-alive\\n\\n\");\n\n/**\n * Keeps a quiet SSE body open by writing a comment after `idleMs` of silence.\n *\n * Any byte from `source` resets the clock, so an active stream adds nothing\n * to the wire. A back-pressured reader skips the tick instead of queueing\n * frames. The timer stops when the pipe settles, which covers both the source\n * completing and the reader cancelling, on every supported Node version.\n */\nexport function keepAliveSse(\n source: ReadableStream<Uint8Array>,\n idleMs: number,\n): ReadableStream<Uint8Array> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const stop = () => {\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n };\n const arm = (controller: TransformStreamDefaultController<Uint8Array>) => {\n stop();\n timer = setTimeout(() => {\n // `desiredSize` is 1 only while the readable queue is empty; 0 means a\n // slow reader still holds the previous chunk, null means it went away.\n if (controller.desiredSize === 1) controller.enqueue(KEEP_ALIVE_FRAME);\n arm(controller);\n }, idleMs);\n timer.unref?.();\n };\n const transform = new TransformStream<Uint8Array, Uint8Array>(\n {\n start: arm,\n transform(chunk, controller) {\n controller.enqueue(chunk);\n arm(controller);\n },\n flush: stop,\n },\n undefined,\n { highWaterMark: 1 },\n );\n // `flush` stops the timer when the source completes. The pipe settling\n // covers the reader cancelling, on every supported Node version; the source\n // writer surfaces that cancellation to the caller.\n void source\n .pipeTo(transform.writable)\n .catch(() => {})\n .finally(stop);\n return transform.readable;\n}\n"],"mappings":";;;;;;;;AAKA,MAAa,0CAA0C;;;;;;AAOvD,MAAa,uCAAuC,KAAK,KAAK,KAAK;;;;;;AAOnE,SAAgB,mCACd,OACQ;CACR,MAAM,UAAU,SAAS;AACzB,KACE,CAAC,OAAO,SAAS,QAAQ,IACzB,UAAU,KACV,UAAU,oCAEV,OAAM,IAAI,WACR,qDAAqD,oCAAoC,QAAQ,OAAO,MAAM,GAC/G;AAEH,QAAO;;;;;;;AAQT,MAAM,mBAAmB,IAAI,aAAa,CAAC,OAAO,mBAAmB;;;;;;;;;AAUrE,SAAgB,aACd,QACA,QAC4B;CAC5B,IAAI;CACJ,MAAM,aAAa;AACjB,MAAI,UAAU,OAAW,cAAa,MAAM;AAC5C,UAAQ;;CAEV,MAAM,OAAO,eAA6D;AACxE,QAAM;AACN,UAAQ,iBAAiB;AAGvB,OAAI,WAAW,gBAAgB,EAAG,YAAW,QAAQ,iBAAiB;AACtE,OAAI,WAAW;KACd,OAAO;AACV,QAAM,SAAS;;CAEjB,MAAM,YAAY,IAAI,gBACpB;EACE,OAAO;EACP,UAAU,OAAO,YAAY;AAC3B,cAAW,QAAQ,MAAM;AACzB,OAAI,WAAW;;EAEjB,OAAO;EACR,EACD,QACA,EAAE,eAAe,GAAG,CACrB;AAID,CAAK,OACF,OAAO,UAAU,SAAS,CAC1B,YAAY,GAAG,CACf,QAAQ,KAAK;AAChB,QAAO,UAAU"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
//#region src/v2/runtime/handlers/shared/sse-keep-alive.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default silence, in seconds, before an SSE response carries a keep-alive
|
|
5
|
+
* comment. Sits under the tightest common proxy idle timeouts (55–60 s) and
|
|
6
|
+
* beside `lockHeartbeatIntervalSeconds`, which shares the unit and default.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS = 15;
|
|
9
|
+
/**
|
|
10
|
+
* Longest interval the timer can honour. Timers take a signed 32-bit
|
|
11
|
+
* millisecond delay; Node silently replaces anything larger with 1 ms, which
|
|
12
|
+
* would turn a keep-alive into a flood.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS = (2 ** 31 - 1) / 1e3;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the `sseKeepAliveIntervalSeconds` runtime option once, at
|
|
17
|
+
* construction, so every SSE call site reads one validated value.
|
|
18
|
+
* `undefined` takes the default; `0` disables the keep-alive.
|
|
19
|
+
*/
|
|
20
|
+
function resolveSseKeepAliveIntervalSeconds(value) {
|
|
21
|
+
const seconds = value ?? DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS;
|
|
22
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS) throw new RangeError(`sseKeepAliveIntervalSeconds must be between 0 and ${MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS}, got ${String(value)}`);
|
|
23
|
+
return seconds;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* SSE comment frame. Parsers drop comment lines, so no AG-UI event is
|
|
27
|
+
* invented and the verifier, history and middleware never see it.
|
|
28
|
+
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
|
29
|
+
*/
|
|
30
|
+
const KEEP_ALIVE_FRAME = new TextEncoder().encode(": keep-alive\n\n");
|
|
31
|
+
/**
|
|
32
|
+
* Keeps a quiet SSE body open by writing a comment after `idleMs` of silence.
|
|
33
|
+
*
|
|
34
|
+
* Any byte from `source` resets the clock, so an active stream adds nothing
|
|
35
|
+
* to the wire. A back-pressured reader skips the tick instead of queueing
|
|
36
|
+
* frames. The timer stops when the pipe settles, which covers both the source
|
|
37
|
+
* completing and the reader cancelling, on every supported Node version.
|
|
38
|
+
*/
|
|
39
|
+
function keepAliveSse(source, idleMs) {
|
|
40
|
+
let timer;
|
|
41
|
+
const stop = () => {
|
|
42
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
43
|
+
timer = void 0;
|
|
44
|
+
};
|
|
45
|
+
const arm = (controller) => {
|
|
46
|
+
stop();
|
|
47
|
+
timer = setTimeout(() => {
|
|
48
|
+
if (controller.desiredSize === 1) controller.enqueue(KEEP_ALIVE_FRAME);
|
|
49
|
+
arm(controller);
|
|
50
|
+
}, idleMs);
|
|
51
|
+
timer.unref?.();
|
|
52
|
+
};
|
|
53
|
+
const transform = new TransformStream({
|
|
54
|
+
start: arm,
|
|
55
|
+
transform(chunk, controller) {
|
|
56
|
+
controller.enqueue(chunk);
|
|
57
|
+
arm(controller);
|
|
58
|
+
},
|
|
59
|
+
flush: stop
|
|
60
|
+
}, void 0, { highWaterMark: 1 });
|
|
61
|
+
source.pipeTo(transform.writable).catch(() => {}).finally(stop);
|
|
62
|
+
return transform.readable;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { keepAliveSse, resolveSseKeepAliveIntervalSeconds };
|
|
67
|
+
//# sourceMappingURL=sse-keep-alive.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-keep-alive.mjs","names":[],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-keep-alive.ts"],"sourcesContent":["/**\n * Default silence, in seconds, before an SSE response carries a keep-alive\n * comment. Sits under the tightest common proxy idle timeouts (55–60 s) and\n * beside `lockHeartbeatIntervalSeconds`, which shares the unit and default.\n */\nexport const DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS = 15;\n\n/**\n * Longest interval the timer can honour. Timers take a signed 32-bit\n * millisecond delay; Node silently replaces anything larger with 1 ms, which\n * would turn a keep-alive into a flood.\n */\nexport const MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS = (2 ** 31 - 1) / 1000;\n\n/**\n * Resolves the `sseKeepAliveIntervalSeconds` runtime option once, at\n * construction, so every SSE call site reads one validated value.\n * `undefined` takes the default; `0` disables the keep-alive.\n */\nexport function resolveSseKeepAliveIntervalSeconds(\n value: number | undefined,\n): number {\n const seconds = value ?? DEFAULT_SSE_KEEP_ALIVE_INTERVAL_SECONDS;\n if (\n !Number.isFinite(seconds) ||\n seconds < 0 ||\n seconds > MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS\n ) {\n throw new RangeError(\n `sseKeepAliveIntervalSeconds must be between 0 and ${MAX_SSE_KEEP_ALIVE_INTERVAL_SECONDS}, got ${String(value)}`,\n );\n }\n return seconds;\n}\n\n/**\n * SSE comment frame. Parsers drop comment lines, so no AG-UI event is\n * invented and the verifier, history and middleware never see it.\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes\n */\nconst KEEP_ALIVE_FRAME = new TextEncoder().encode(\": keep-alive\\n\\n\");\n\n/**\n * Keeps a quiet SSE body open by writing a comment after `idleMs` of silence.\n *\n * Any byte from `source` resets the clock, so an active stream adds nothing\n * to the wire. A back-pressured reader skips the tick instead of queueing\n * frames. The timer stops when the pipe settles, which covers both the source\n * completing and the reader cancelling, on every supported Node version.\n */\nexport function keepAliveSse(\n source: ReadableStream<Uint8Array>,\n idleMs: number,\n): ReadableStream<Uint8Array> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const stop = () => {\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n };\n const arm = (controller: TransformStreamDefaultController<Uint8Array>) => {\n stop();\n timer = setTimeout(() => {\n // `desiredSize` is 1 only while the readable queue is empty; 0 means a\n // slow reader still holds the previous chunk, null means it went away.\n if (controller.desiredSize === 1) controller.enqueue(KEEP_ALIVE_FRAME);\n arm(controller);\n }, idleMs);\n timer.unref?.();\n };\n const transform = new TransformStream<Uint8Array, Uint8Array>(\n {\n start: arm,\n transform(chunk, controller) {\n controller.enqueue(chunk);\n arm(controller);\n },\n flush: stop,\n },\n undefined,\n { highWaterMark: 1 },\n );\n // `flush` stops the timer when the source completes. The pipe settling\n // covers the reader cancelling, on every supported Node version; the source\n // writer surfaces that cancellation to the caller.\n void source\n .pipeTo(transform.writable)\n .catch(() => {})\n .finally(stop);\n return transform.readable;\n}\n"],"mappings":";;;;;;;AAKA,MAAa,0CAA0C;;;;;;AAOvD,MAAa,uCAAuC,KAAK,KAAK,KAAK;;;;;;AAOnE,SAAgB,mCACd,OACQ;CACR,MAAM,UAAU,SAAS;AACzB,KACE,CAAC,OAAO,SAAS,QAAQ,IACzB,UAAU,KACV,UAAU,oCAEV,OAAM,IAAI,WACR,qDAAqD,oCAAoC,QAAQ,OAAO,MAAM,GAC/G;AAEH,QAAO;;;;;;;AAQT,MAAM,mBAAmB,IAAI,aAAa,CAAC,OAAO,mBAAmB;;;;;;;;;AAUrE,SAAgB,aACd,QACA,QAC4B;CAC5B,IAAI;CACJ,MAAM,aAAa;AACjB,MAAI,UAAU,OAAW,cAAa,MAAM;AAC5C,UAAQ;;CAEV,MAAM,OAAO,eAA6D;AACxE,QAAM;AACN,UAAQ,iBAAiB;AAGvB,OAAI,WAAW,gBAAgB,EAAG,YAAW,QAAQ,iBAAiB;AACtE,OAAI,WAAW;KACd,OAAO;AACV,QAAM,SAAS;;CAEjB,MAAM,YAAY,IAAI,gBACpB;EACE,OAAO;EACP,UAAU,OAAO,YAAY;AAC3B,cAAW,QAAQ,MAAM;AACzB,OAAI,WAAW;;EAEjB,OAAO;EACR,EACD,QACA,EAAE,eAAe,GAAG,CACrB;AAID,CAAK,OACF,OAAO,UAAU,SAAS,CAC1B,YAAY,GAAG,CACf,QAAQ,KAAK;AAChB,QAAO,UAAU"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
require("reflect-metadata");
|
|
2
2
|
const require_runtime = require('../../../../_virtual/_rolldown/runtime.cjs');
|
|
3
3
|
const require_logger = require('../../../../lib/logger.cjs');
|
|
4
|
+
const require_sse_keep_alive = require('./sse-keep-alive.cjs');
|
|
4
5
|
const require_telemetry_client = require('../../telemetry/telemetry-client.cjs');
|
|
5
6
|
let _ag_ui_encoder = require("@ag-ui/encoder");
|
|
6
7
|
|
|
7
8
|
//#region src/v2/runtime/handlers/shared/sse-response.ts
|
|
8
|
-
function createSseEventResponse({ request, observableFactory, debugEventBus, agentId, debug, logger, telemetry: telemetry$1 = require_telemetry_client.default, captureTelemetry = true, runtimeErrorReporter, startTime }) {
|
|
9
|
+
function createSseEventResponse({ request, observableFactory, debugEventBus, agentId, debug, logger, telemetry: telemetry$1 = require_telemetry_client.default, executionSeed, captureTelemetry = true, runtimeErrorReporter, startTime, keepAliveIntervalSeconds }) {
|
|
10
|
+
const keepAliveSeconds = require_sse_keep_alive.resolveSseKeepAliveIntervalSeconds(keepAliveIntervalSeconds);
|
|
9
11
|
const stream = new TransformStream();
|
|
10
12
|
const writer = stream.writable.getWriter();
|
|
11
13
|
const encoder = new _ag_ui_encoder.EventEncoder();
|
|
@@ -53,8 +55,10 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
53
55
|
if (debug?.lifecycle) debugLogger.debug("SSE stream opened");
|
|
54
56
|
let eventCount = 0;
|
|
55
57
|
let loggedEventCount = 0;
|
|
58
|
+
const executionInfo = { ...executionSeed };
|
|
56
59
|
subscription = observable.subscribe({
|
|
57
60
|
next: async (event) => {
|
|
61
|
+
collectExecutionInfo(event, executionInfo);
|
|
58
62
|
if (event.type === "RUN_STARTED") {
|
|
59
63
|
const e = event;
|
|
60
64
|
debugThreadId = e.threadId ?? "";
|
|
@@ -96,13 +100,16 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
96
100
|
},
|
|
97
101
|
error: async (error) => {
|
|
98
102
|
reportAgentError(error, "sse.subscription");
|
|
99
|
-
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_errored", {
|
|
103
|
+
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_errored", {
|
|
104
|
+
...executionInfo,
|
|
105
|
+
error: error instanceof Error ? error.message : String(error)
|
|
106
|
+
});
|
|
100
107
|
if (debug?.lifecycle) debugLogger.debug({ error: error instanceof Error ? error.message : String(error) }, "SSE stream errored");
|
|
101
108
|
logError(error);
|
|
102
109
|
await closeStream();
|
|
103
110
|
},
|
|
104
111
|
complete: async () => {
|
|
105
|
-
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_ended",
|
|
112
|
+
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_ended", executionInfo);
|
|
106
113
|
if (debug?.lifecycle) debugLogger.debug({
|
|
107
114
|
eventCount,
|
|
108
115
|
loggedEventCount
|
|
@@ -119,7 +126,8 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
119
126
|
request.signal.addEventListener("abort", () => {
|
|
120
127
|
subscription?.unsubscribe();
|
|
121
128
|
});
|
|
122
|
-
|
|
129
|
+
const body = keepAliveSeconds > 0 ? require_sse_keep_alive.keepAliveSse(stream.readable, keepAliveSeconds * 1e3) : stream.readable;
|
|
130
|
+
return new Response(body, {
|
|
123
131
|
status: 200,
|
|
124
132
|
headers: {
|
|
125
133
|
"Content-Type": "text/event-stream",
|
|
@@ -145,6 +153,29 @@ function summarizeEvent(event) {
|
|
|
145
153
|
if (e.stepName) summary.stepName = e.stepName;
|
|
146
154
|
return summary;
|
|
147
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Accumulate provider, model, and LangGraph facts from one upstream event.
|
|
158
|
+
*
|
|
159
|
+
* Mutates rather than returns so the caller keeps one record across the whole
|
|
160
|
+
* stream: these arrive on different events and the last one wins. Only fields
|
|
161
|
+
* the upstream actually sent are set, so an agent that reports none leaves the
|
|
162
|
+
* record empty and the stream events carry `{}` as before.
|
|
163
|
+
*
|
|
164
|
+
* `rawEvent` is the untransformed upstream payload, present on AG-UI events
|
|
165
|
+
* that wrap one. Its shape is the provider's, not ours, hence the narrowing.
|
|
166
|
+
*/
|
|
167
|
+
function collectExecutionInfo(event, into) {
|
|
168
|
+
const rawEvent = event.rawEvent;
|
|
169
|
+
if (!rawEvent) return;
|
|
170
|
+
const model = rawEvent.data?.output?.model;
|
|
171
|
+
if (model) {
|
|
172
|
+
into.model = model;
|
|
173
|
+
into.provider = model;
|
|
174
|
+
}
|
|
175
|
+
const metadata = rawEvent.metadata;
|
|
176
|
+
if (metadata?.langgraph_host) into.langGraphHost = metadata.langgraph_host;
|
|
177
|
+
if (metadata?.langgraph_version) into.langGraphVersion = metadata.langgraph_version;
|
|
178
|
+
}
|
|
148
179
|
|
|
149
180
|
//#endregion
|
|
150
181
|
exports.createSseEventResponse = createSseEventResponse;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse-response.cjs","names":["defaultTelemetry","EventEncoder","createLogger"],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-response.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\nimport { EventEncoder } from \"@ag-ui/encoder\";\nimport type { Observable, Subscription } from \"rxjs\";\nimport type { ResolvedDebugConfig } from \"@copilotkit/shared\";\nimport { createLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport type { CopilotRuntimeLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport { telemetry as defaultTelemetry } from \"../../telemetry\";\nimport type { TelemetryCapture } from \"../../telemetry/telemetry-client\";\nimport type { DebugEventBus } from \"../../core/debug-event-bus\";\nimport type {\n RuntimeErrorPhase,\n RuntimeErrorReporter,\n} from \"../../core/runtime-error-reporter\";\n\ninterface CreateSseEventResponseParams {\n request: Request;\n observableFactory: () =>\n | Promise<Observable<BaseEvent>>\n | Observable<BaseEvent>;\n debugEventBus?: DebugEventBus;\n agentId?: string;\n debug?: ResolvedDebugConfig;\n /** Pre-created logger instance to avoid creating a new pino logger per request. */\n logger?: CopilotRuntimeLogger;\n /** Runtime-bound telemetry capture. Falls back for external direct callers. */\n telemetry?: TelemetryCapture;\n /**\n * Whether to emit `oss.runtime.agent_execution_stream_*` telemetry for this\n * stream. Defaults to `true`. The stateless `/suggest` path sets this to\n * `false`: a suggestion is a side-effect-free structured completion, not a\n * tracked run, and under `available: \"always\"` it fires often enough to flood\n * run telemetry.\n */\n captureTelemetry?: boolean;\n runtimeErrorReporter?: RuntimeErrorReporter;\n startTime?: number;\n}\n\nexport function createSseEventResponse({\n request,\n observableFactory,\n debugEventBus,\n agentId,\n debug,\n logger,\n telemetry = defaultTelemetry,\n captureTelemetry = true,\n runtimeErrorReporter,\n startTime,\n}: CreateSseEventResponseParams): Response {\n const stream = new TransformStream();\n const writer = stream.writable.getWriter();\n const encoder = new EventEncoder();\n let streamClosed = false;\n let debugThreadId = \"\";\n let debugRunId = \"\";\n\n const debugLogger = debug?.enabled\n ? (logger ??\n createLogger({ level: \"debug\", component: \"copilotkit-debug\" }))\n : undefined;\n\n const closeStream = async () => {\n if (!streamClosed) {\n try {\n await writer.close();\n streamClosed = true;\n } catch {\n // Stream already closed.\n }\n }\n };\n\n const logError = (error: unknown) => {\n console.error(\"Error running agent:\", error);\n console.error(\n \"Error stack:\",\n error instanceof Error ? error.stack : \"No stack trace\",\n );\n console.error(\"Error details:\", {\n name: error instanceof Error ? error.name : \"Unknown\",\n message: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error ? error.cause : undefined,\n });\n };\n\n let subscription: Subscription | undefined;\n let agentErrorReported = false;\n\n const reportAgentError = (error: unknown, phase: RuntimeErrorPhase) => {\n if (agentErrorReported) return;\n agentErrorReported = true;\n runtimeErrorReporter?.report({\n request,\n error,\n operation: \"agent.run\",\n agentId,\n threadId: debugThreadId || undefined,\n runId: debugRunId || undefined,\n phase,\n startTime,\n });\n };\n\n (async () => {\n const observable = await observableFactory();\n\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_started\", {});\n }\n\n if (debug?.lifecycle) {\n debugLogger!.debug(\"SSE stream opened\");\n }\n\n let eventCount = 0;\n let loggedEventCount = 0;\n\n subscription = observable.subscribe({\n next: async (event) => {\n // Extract threadId/runId from RUN_STARTED\n if (event.type === \"RUN_STARTED\") {\n const e = event as { threadId?: string; runId?: string };\n debugThreadId = e.threadId ?? \"\";\n debugRunId = e.runId ?? \"\";\n }\n\n if (event.type === \"RUN_ERROR\") {\n const e = event as {\n message?: unknown;\n threadId?: string;\n runId?: string;\n };\n debugThreadId = e.threadId ?? debugThreadId;\n debugRunId = e.runId ?? debugRunId;\n reportAgentError(\n new Error(\n typeof e.message === \"string\"\n ? e.message\n : \"Runner reported a run error\",\n ),\n \"sse.subscription\",\n );\n }\n\n // Broadcast to debug listeners BEFORE the stream-closed gate below.\n // Intentional: debug subscribers (e.g. the VS Code Inspector panel)\n // should still receive trailing events after the SSE client for\n // this request closed its connection — they're independent\n // consumers observing the underlying runtime, not the request's\n // response stream.\n //\n // Wrapped in try/catch so a buggy debug subscriber can't propagate\n // an exception into this observer — if the throw reached the\n // `next` callback it would get routed to `error` by RxJS, closing\n // the SSE stream for an unrelated reason. Log via `logError` and\n // move on.\n if (debugEventBus) {\n try {\n debugEventBus.broadcast(event, {\n agentId: agentId ?? \"\",\n threadId: debugThreadId,\n runId: debugRunId,\n });\n } catch (broadcastError) {\n logError(broadcastError);\n }\n }\n\n if (!request.signal.aborted && !streamClosed) {\n try {\n eventCount++;\n if (debug?.events) {\n loggedEventCount++;\n if (debug.verbose) {\n debugLogger!.debug({ event }, \"Event emitted\");\n } else {\n debugLogger!.debug(\n { type: event.type, ...summarizeEvent(event) },\n \"Event emitted\",\n );\n }\n }\n await writer.write(encoder.encodeBinary(event));\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n streamClosed = true;\n } else {\n // Non-abort write failures (backpressure disconnects,\n // transform-stream exceptions, …) were previously swallowed\n // silently — `streamClosed` stayed `false` and the next\n // event re-attempted a broken writer. Log and mark the\n // stream closed so we stop trying.\n logError(error);\n streamClosed = true;\n }\n }\n }\n },\n error: async (error) => {\n reportAgentError(error, \"sse.subscription\");\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_errored\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { error: error instanceof Error ? error.message : String(error) },\n \"SSE stream errored\",\n );\n }\n logError(error);\n await closeStream();\n },\n complete: async () => {\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_ended\", {});\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { eventCount, loggedEventCount },\n \"SSE stream completed\",\n );\n }\n await closeStream();\n },\n });\n\n // If the client disconnected before the subscription was created,\n // unsubscribe immediately to avoid leaking the observable.\n if (request.signal.aborted) {\n subscription.unsubscribe();\n }\n })().catch(async (error) => {\n reportAgentError(error, \"sse.factory\");\n logError(error);\n await closeStream();\n });\n\n request.signal.addEventListener(\"abort\", () => {\n subscription?.unsubscribe();\n });\n\n return new Response(stream.readable, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nfunction summarizeEvent(event: BaseEvent): Record<string, unknown> {\n const e = event as any;\n const summary: Record<string, unknown> = {};\n\n if (e.messageId) summary.messageId = e.messageId;\n if (e.toolCallId) summary.toolCallId = e.toolCallId;\n if (e.toolCallName) summary.toolCallName = e.toolCallName;\n if (e.role) summary.role = e.role;\n if (e.delta != null && typeof e.delta === \"string\")\n summary.deltaLength = e.delta.length;\n if (e.snapshot && typeof e.snapshot === \"object\")\n summary.snapshotKeys = Object.keys(e.snapshot);\n if (e.delta && Array.isArray(e.delta))\n summary.operationCount = e.delta.length;\n if (e.threadId) summary.threadId = e.threadId;\n if (e.runId) summary.runId = e.runId;\n if (e.message) summary.message = e.message;\n if (e.code) summary.code = e.code;\n if (e.stepName) summary.stepName = e.stepName;\n\n return summary;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sse-response.cjs","names":["defaultTelemetry","resolveSseKeepAliveIntervalSeconds","EventEncoder","createLogger","keepAliveSse"],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-response.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\nimport { EventEncoder } from \"@ag-ui/encoder\";\nimport type { Observable, Subscription } from \"rxjs\";\nimport {\n keepAliveSse,\n resolveSseKeepAliveIntervalSeconds,\n} from \"./sse-keep-alive\";\nimport type { ResolvedDebugConfig } from \"@copilotkit/shared\";\nimport { createLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport type { CopilotRuntimeLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport { telemetry as defaultTelemetry } from \"../../telemetry\";\nimport type { TelemetryCapture } from \"../../telemetry/telemetry-client\";\nimport type { AgentExecutionResponseInfo } from \"../../telemetry/events\";\nimport type { DebugEventBus } from \"../../core/debug-event-bus\";\nimport type {\n RuntimeErrorPhase,\n RuntimeErrorReporter,\n} from \"../../core/runtime-error-reporter\";\n\ninterface CreateSseEventResponseParams {\n request: Request;\n observableFactory: () =>\n | Promise<Observable<BaseEvent>>\n | Observable<BaseEvent>;\n debugEventBus?: DebugEventBus;\n agentId?: string;\n debug?: ResolvedDebugConfig;\n /** Pre-created logger instance to avoid creating a new pino logger per request. */\n logger?: CopilotRuntimeLogger;\n /** Runtime-bound telemetry capture. Falls back for external direct callers. */\n telemetry?: TelemetryCapture;\n /**\n * Execution facts known before the stream opens, merged under anything the\n * upstream reports as it runs. Carries `llmHostClass`, which is knowable\n * from the agent's configuration and never appears on a streamed event.\n */\n executionSeed?: AgentExecutionResponseInfo;\n /**\n * Whether to emit `oss.runtime.agent_execution_stream_*` telemetry for this\n * stream. Defaults to `true`. The stateless `/suggest` path sets this to\n * `false`: a suggestion is a side-effect-free structured completion, not a\n * tracked run, and under `available: \"always\"` it fires often enough to flood\n * run telemetry.\n */\n captureTelemetry?: boolean;\n runtimeErrorReporter?: RuntimeErrorReporter;\n startTime?: number;\n /**\n * Seconds of silence before a `: keep-alive` SSE comment is written.\n * `0` disables the keep-alive. Resolved by\n * {@link resolveSseKeepAliveIntervalSeconds}, which supplies the default\n * and enforces the timer bound for every caller.\n */\n keepAliveIntervalSeconds?: number;\n}\n\nexport function createSseEventResponse({\n request,\n observableFactory,\n debugEventBus,\n agentId,\n debug,\n logger,\n telemetry = defaultTelemetry,\n executionSeed,\n captureTelemetry = true,\n runtimeErrorReporter,\n startTime,\n keepAliveIntervalSeconds,\n}: CreateSseEventResponseParams): Response {\n const keepAliveSeconds = resolveSseKeepAliveIntervalSeconds(\n keepAliveIntervalSeconds,\n );\n const stream = new TransformStream();\n const writer = stream.writable.getWriter();\n const encoder = new EventEncoder();\n let streamClosed = false;\n let debugThreadId = \"\";\n let debugRunId = \"\";\n\n const debugLogger = debug?.enabled\n ? (logger ??\n createLogger({ level: \"debug\", component: \"copilotkit-debug\" }))\n : undefined;\n\n const closeStream = async () => {\n if (!streamClosed) {\n try {\n await writer.close();\n streamClosed = true;\n } catch {\n // Stream already closed.\n }\n }\n };\n\n const logError = (error: unknown) => {\n console.error(\"Error running agent:\", error);\n console.error(\n \"Error stack:\",\n error instanceof Error ? error.stack : \"No stack trace\",\n );\n console.error(\"Error details:\", {\n name: error instanceof Error ? error.name : \"Unknown\",\n message: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error ? error.cause : undefined,\n });\n };\n\n let subscription: Subscription | undefined;\n let agentErrorReported = false;\n\n const reportAgentError = (error: unknown, phase: RuntimeErrorPhase) => {\n if (agentErrorReported) return;\n agentErrorReported = true;\n runtimeErrorReporter?.report({\n request,\n error,\n operation: \"agent.run\",\n agentId,\n threadId: debugThreadId || undefined,\n runId: debugRunId || undefined,\n phase,\n startTime,\n });\n };\n\n (async () => {\n const observable = await observableFactory();\n\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_started\", {});\n }\n\n if (debug?.lifecycle) {\n debugLogger!.debug(\"SSE stream opened\");\n }\n\n let eventCount = 0;\n let loggedEventCount = 0;\n\n // Provider/model/LangGraph facts, scraped off raw upstream events as\n // they pass and reported once the stream finishes. This moved here from\n // the v1 TelemetryAgentRunner, which wrapped the runner purely to\n // collect it and emitted its own duplicate copy of every stream event\n // to carry it. v2 callers had no equivalent and reported `{}`.\n // Seeded rather than empty: `llmHostClass` comes from the agent's config,\n // not from the wire, so nothing streamed will ever fill it in. Scraped\n // fields still win, since `collectExecutionInfo` writes over this.\n const executionInfo: AgentExecutionResponseInfo = { ...executionSeed };\n\n subscription = observable.subscribe({\n next: async (event) => {\n collectExecutionInfo(event, executionInfo);\n\n // Extract threadId/runId from RUN_STARTED\n if (event.type === \"RUN_STARTED\") {\n const e = event as { threadId?: string; runId?: string };\n debugThreadId = e.threadId ?? \"\";\n debugRunId = e.runId ?? \"\";\n }\n\n if (event.type === \"RUN_ERROR\") {\n const e = event as {\n message?: unknown;\n threadId?: string;\n runId?: string;\n };\n debugThreadId = e.threadId ?? debugThreadId;\n debugRunId = e.runId ?? debugRunId;\n reportAgentError(\n new Error(\n typeof e.message === \"string\"\n ? e.message\n : \"Runner reported a run error\",\n ),\n \"sse.subscription\",\n );\n }\n\n // Broadcast to debug listeners BEFORE the stream-closed gate below.\n // Intentional: debug subscribers (e.g. the VS Code Inspector panel)\n // should still receive trailing events after the SSE client for\n // this request closed its connection — they're independent\n // consumers observing the underlying runtime, not the request's\n // response stream.\n //\n // Wrapped in try/catch so a buggy debug subscriber can't propagate\n // an exception into this observer — if the throw reached the\n // `next` callback it would get routed to `error` by RxJS, closing\n // the SSE stream for an unrelated reason. Log via `logError` and\n // move on.\n if (debugEventBus) {\n try {\n debugEventBus.broadcast(event, {\n agentId: agentId ?? \"\",\n threadId: debugThreadId,\n runId: debugRunId,\n });\n } catch (broadcastError) {\n logError(broadcastError);\n }\n }\n\n if (!request.signal.aborted && !streamClosed) {\n try {\n eventCount++;\n if (debug?.events) {\n loggedEventCount++;\n if (debug.verbose) {\n debugLogger!.debug({ event }, \"Event emitted\");\n } else {\n debugLogger!.debug(\n { type: event.type, ...summarizeEvent(event) },\n \"Event emitted\",\n );\n }\n }\n await writer.write(encoder.encodeBinary(event));\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n streamClosed = true;\n } else {\n // Non-abort write failures (backpressure disconnects,\n // transform-stream exceptions, …) were previously swallowed\n // silently — `streamClosed` stayed `false` and the next\n // event re-attempted a broken writer. Log and mark the\n // stream closed so we stop trying.\n logError(error);\n streamClosed = true;\n }\n }\n }\n },\n error: async (error) => {\n reportAgentError(error, \"sse.subscription\");\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_errored\", {\n ...executionInfo,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { error: error instanceof Error ? error.message : String(error) },\n \"SSE stream errored\",\n );\n }\n logError(error);\n await closeStream();\n },\n complete: async () => {\n if (captureTelemetry) {\n telemetry.capture(\n \"oss.runtime.agent_execution_stream_ended\",\n executionInfo,\n );\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { eventCount, loggedEventCount },\n \"SSE stream completed\",\n );\n }\n await closeStream();\n },\n });\n\n // If the client disconnected before the subscription was created,\n // unsubscribe immediately to avoid leaking the observable.\n if (request.signal.aborted) {\n subscription.unsubscribe();\n }\n })().catch(async (error) => {\n reportAgentError(error, \"sse.factory\");\n logError(error);\n await closeStream();\n });\n\n request.signal.addEventListener(\"abort\", () => {\n subscription?.unsubscribe();\n });\n\n const body =\n keepAliveSeconds > 0\n ? keepAliveSse(stream.readable, keepAliveSeconds * 1_000)\n : stream.readable;\n\n return new Response(body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nfunction summarizeEvent(event: BaseEvent): Record<string, unknown> {\n const e = event as any;\n const summary: Record<string, unknown> = {};\n\n if (e.messageId) summary.messageId = e.messageId;\n if (e.toolCallId) summary.toolCallId = e.toolCallId;\n if (e.toolCallName) summary.toolCallName = e.toolCallName;\n if (e.role) summary.role = e.role;\n if (e.delta != null && typeof e.delta === \"string\")\n summary.deltaLength = e.delta.length;\n if (e.snapshot && typeof e.snapshot === \"object\")\n summary.snapshotKeys = Object.keys(e.snapshot);\n if (e.delta && Array.isArray(e.delta))\n summary.operationCount = e.delta.length;\n if (e.threadId) summary.threadId = e.threadId;\n if (e.runId) summary.runId = e.runId;\n if (e.message) summary.message = e.message;\n if (e.code) summary.code = e.code;\n if (e.stepName) summary.stepName = e.stepName;\n\n return summary;\n}\n\n/**\n * Accumulate provider, model, and LangGraph facts from one upstream event.\n *\n * Mutates rather than returns so the caller keeps one record across the whole\n * stream: these arrive on different events and the last one wins. Only fields\n * the upstream actually sent are set, so an agent that reports none leaves the\n * record empty and the stream events carry `{}` as before.\n *\n * `rawEvent` is the untransformed upstream payload, present on AG-UI events\n * that wrap one. Its shape is the provider's, not ours, hence the narrowing.\n */\nfunction collectExecutionInfo(\n event: BaseEvent,\n into: AgentExecutionResponseInfo,\n): void {\n const rawEvent = (\n event as {\n rawEvent?: {\n metadata?: Record<string, unknown>;\n data?: Record<string, unknown>;\n };\n }\n ).rawEvent;\n if (!rawEvent) return;\n\n const model = (rawEvent.data as { output?: { model?: string } } | undefined)\n ?.output?.model;\n if (model) {\n into.model = model;\n // Carried forward from the v1 implementation, which set both from the\n // same field. The upstream sends no separate provider name.\n into.provider = model;\n }\n\n const metadata = rawEvent.metadata as\n | { langgraph_host?: string; langgraph_version?: string }\n | undefined;\n if (metadata?.langgraph_host) into.langGraphHost = metadata.langgraph_host;\n if (metadata?.langgraph_version) {\n into.langGraphVersion = metadata.langgraph_version;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { createLogger } from "../../../../lib/logger.mjs";
|
|
3
|
+
import { keepAliveSse, resolveSseKeepAliveIntervalSeconds } from "./sse-keep-alive.mjs";
|
|
3
4
|
import telemetry from "../../telemetry/telemetry-client.mjs";
|
|
4
5
|
import { EventEncoder } from "@ag-ui/encoder";
|
|
5
6
|
|
|
6
7
|
//#region src/v2/runtime/handlers/shared/sse-response.ts
|
|
7
|
-
function createSseEventResponse({ request, observableFactory, debugEventBus, agentId, debug, logger, telemetry: telemetry$1 = telemetry, captureTelemetry = true, runtimeErrorReporter, startTime }) {
|
|
8
|
+
function createSseEventResponse({ request, observableFactory, debugEventBus, agentId, debug, logger, telemetry: telemetry$1 = telemetry, executionSeed, captureTelemetry = true, runtimeErrorReporter, startTime, keepAliveIntervalSeconds }) {
|
|
9
|
+
const keepAliveSeconds = resolveSseKeepAliveIntervalSeconds(keepAliveIntervalSeconds);
|
|
8
10
|
const stream = new TransformStream();
|
|
9
11
|
const writer = stream.writable.getWriter();
|
|
10
12
|
const encoder = new EventEncoder();
|
|
@@ -52,8 +54,10 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
52
54
|
if (debug?.lifecycle) debugLogger.debug("SSE stream opened");
|
|
53
55
|
let eventCount = 0;
|
|
54
56
|
let loggedEventCount = 0;
|
|
57
|
+
const executionInfo = { ...executionSeed };
|
|
55
58
|
subscription = observable.subscribe({
|
|
56
59
|
next: async (event) => {
|
|
60
|
+
collectExecutionInfo(event, executionInfo);
|
|
57
61
|
if (event.type === "RUN_STARTED") {
|
|
58
62
|
const e = event;
|
|
59
63
|
debugThreadId = e.threadId ?? "";
|
|
@@ -95,13 +99,16 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
95
99
|
},
|
|
96
100
|
error: async (error) => {
|
|
97
101
|
reportAgentError(error, "sse.subscription");
|
|
98
|
-
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_errored", {
|
|
102
|
+
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_errored", {
|
|
103
|
+
...executionInfo,
|
|
104
|
+
error: error instanceof Error ? error.message : String(error)
|
|
105
|
+
});
|
|
99
106
|
if (debug?.lifecycle) debugLogger.debug({ error: error instanceof Error ? error.message : String(error) }, "SSE stream errored");
|
|
100
107
|
logError(error);
|
|
101
108
|
await closeStream();
|
|
102
109
|
},
|
|
103
110
|
complete: async () => {
|
|
104
|
-
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_ended",
|
|
111
|
+
if (captureTelemetry) telemetry$1.capture("oss.runtime.agent_execution_stream_ended", executionInfo);
|
|
105
112
|
if (debug?.lifecycle) debugLogger.debug({
|
|
106
113
|
eventCount,
|
|
107
114
|
loggedEventCount
|
|
@@ -118,7 +125,8 @@ function createSseEventResponse({ request, observableFactory, debugEventBus, age
|
|
|
118
125
|
request.signal.addEventListener("abort", () => {
|
|
119
126
|
subscription?.unsubscribe();
|
|
120
127
|
});
|
|
121
|
-
|
|
128
|
+
const body = keepAliveSeconds > 0 ? keepAliveSse(stream.readable, keepAliveSeconds * 1e3) : stream.readable;
|
|
129
|
+
return new Response(body, {
|
|
122
130
|
status: 200,
|
|
123
131
|
headers: {
|
|
124
132
|
"Content-Type": "text/event-stream",
|
|
@@ -144,6 +152,29 @@ function summarizeEvent(event) {
|
|
|
144
152
|
if (e.stepName) summary.stepName = e.stepName;
|
|
145
153
|
return summary;
|
|
146
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Accumulate provider, model, and LangGraph facts from one upstream event.
|
|
157
|
+
*
|
|
158
|
+
* Mutates rather than returns so the caller keeps one record across the whole
|
|
159
|
+
* stream: these arrive on different events and the last one wins. Only fields
|
|
160
|
+
* the upstream actually sent are set, so an agent that reports none leaves the
|
|
161
|
+
* record empty and the stream events carry `{}` as before.
|
|
162
|
+
*
|
|
163
|
+
* `rawEvent` is the untransformed upstream payload, present on AG-UI events
|
|
164
|
+
* that wrap one. Its shape is the provider's, not ours, hence the narrowing.
|
|
165
|
+
*/
|
|
166
|
+
function collectExecutionInfo(event, into) {
|
|
167
|
+
const rawEvent = event.rawEvent;
|
|
168
|
+
if (!rawEvent) return;
|
|
169
|
+
const model = rawEvent.data?.output?.model;
|
|
170
|
+
if (model) {
|
|
171
|
+
into.model = model;
|
|
172
|
+
into.provider = model;
|
|
173
|
+
}
|
|
174
|
+
const metadata = rawEvent.metadata;
|
|
175
|
+
if (metadata?.langgraph_host) into.langGraphHost = metadata.langgraph_host;
|
|
176
|
+
if (metadata?.langgraph_version) into.langGraphVersion = metadata.langgraph_version;
|
|
177
|
+
}
|
|
147
178
|
|
|
148
179
|
//#endregion
|
|
149
180
|
export { createSseEventResponse };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse-response.mjs","names":["defaultTelemetry"],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-response.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\nimport { EventEncoder } from \"@ag-ui/encoder\";\nimport type { Observable, Subscription } from \"rxjs\";\nimport type { ResolvedDebugConfig } from \"@copilotkit/shared\";\nimport { createLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport type { CopilotRuntimeLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport { telemetry as defaultTelemetry } from \"../../telemetry\";\nimport type { TelemetryCapture } from \"../../telemetry/telemetry-client\";\nimport type { DebugEventBus } from \"../../core/debug-event-bus\";\nimport type {\n RuntimeErrorPhase,\n RuntimeErrorReporter,\n} from \"../../core/runtime-error-reporter\";\n\ninterface CreateSseEventResponseParams {\n request: Request;\n observableFactory: () =>\n | Promise<Observable<BaseEvent>>\n | Observable<BaseEvent>;\n debugEventBus?: DebugEventBus;\n agentId?: string;\n debug?: ResolvedDebugConfig;\n /** Pre-created logger instance to avoid creating a new pino logger per request. */\n logger?: CopilotRuntimeLogger;\n /** Runtime-bound telemetry capture. Falls back for external direct callers. */\n telemetry?: TelemetryCapture;\n /**\n * Whether to emit `oss.runtime.agent_execution_stream_*` telemetry for this\n * stream. Defaults to `true`. The stateless `/suggest` path sets this to\n * `false`: a suggestion is a side-effect-free structured completion, not a\n * tracked run, and under `available: \"always\"` it fires often enough to flood\n * run telemetry.\n */\n captureTelemetry?: boolean;\n runtimeErrorReporter?: RuntimeErrorReporter;\n startTime?: number;\n}\n\nexport function createSseEventResponse({\n request,\n observableFactory,\n debugEventBus,\n agentId,\n debug,\n logger,\n telemetry = defaultTelemetry,\n captureTelemetry = true,\n runtimeErrorReporter,\n startTime,\n}: CreateSseEventResponseParams): Response {\n const stream = new TransformStream();\n const writer = stream.writable.getWriter();\n const encoder = new EventEncoder();\n let streamClosed = false;\n let debugThreadId = \"\";\n let debugRunId = \"\";\n\n const debugLogger = debug?.enabled\n ? (logger ??\n createLogger({ level: \"debug\", component: \"copilotkit-debug\" }))\n : undefined;\n\n const closeStream = async () => {\n if (!streamClosed) {\n try {\n await writer.close();\n streamClosed = true;\n } catch {\n // Stream already closed.\n }\n }\n };\n\n const logError = (error: unknown) => {\n console.error(\"Error running agent:\", error);\n console.error(\n \"Error stack:\",\n error instanceof Error ? error.stack : \"No stack trace\",\n );\n console.error(\"Error details:\", {\n name: error instanceof Error ? error.name : \"Unknown\",\n message: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error ? error.cause : undefined,\n });\n };\n\n let subscription: Subscription | undefined;\n let agentErrorReported = false;\n\n const reportAgentError = (error: unknown, phase: RuntimeErrorPhase) => {\n if (agentErrorReported) return;\n agentErrorReported = true;\n runtimeErrorReporter?.report({\n request,\n error,\n operation: \"agent.run\",\n agentId,\n threadId: debugThreadId || undefined,\n runId: debugRunId || undefined,\n phase,\n startTime,\n });\n };\n\n (async () => {\n const observable = await observableFactory();\n\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_started\", {});\n }\n\n if (debug?.lifecycle) {\n debugLogger!.debug(\"SSE stream opened\");\n }\n\n let eventCount = 0;\n let loggedEventCount = 0;\n\n subscription = observable.subscribe({\n next: async (event) => {\n // Extract threadId/runId from RUN_STARTED\n if (event.type === \"RUN_STARTED\") {\n const e = event as { threadId?: string; runId?: string };\n debugThreadId = e.threadId ?? \"\";\n debugRunId = e.runId ?? \"\";\n }\n\n if (event.type === \"RUN_ERROR\") {\n const e = event as {\n message?: unknown;\n threadId?: string;\n runId?: string;\n };\n debugThreadId = e.threadId ?? debugThreadId;\n debugRunId = e.runId ?? debugRunId;\n reportAgentError(\n new Error(\n typeof e.message === \"string\"\n ? e.message\n : \"Runner reported a run error\",\n ),\n \"sse.subscription\",\n );\n }\n\n // Broadcast to debug listeners BEFORE the stream-closed gate below.\n // Intentional: debug subscribers (e.g. the VS Code Inspector panel)\n // should still receive trailing events after the SSE client for\n // this request closed its connection — they're independent\n // consumers observing the underlying runtime, not the request's\n // response stream.\n //\n // Wrapped in try/catch so a buggy debug subscriber can't propagate\n // an exception into this observer — if the throw reached the\n // `next` callback it would get routed to `error` by RxJS, closing\n // the SSE stream for an unrelated reason. Log via `logError` and\n // move on.\n if (debugEventBus) {\n try {\n debugEventBus.broadcast(event, {\n agentId: agentId ?? \"\",\n threadId: debugThreadId,\n runId: debugRunId,\n });\n } catch (broadcastError) {\n logError(broadcastError);\n }\n }\n\n if (!request.signal.aborted && !streamClosed) {\n try {\n eventCount++;\n if (debug?.events) {\n loggedEventCount++;\n if (debug.verbose) {\n debugLogger!.debug({ event }, \"Event emitted\");\n } else {\n debugLogger!.debug(\n { type: event.type, ...summarizeEvent(event) },\n \"Event emitted\",\n );\n }\n }\n await writer.write(encoder.encodeBinary(event));\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n streamClosed = true;\n } else {\n // Non-abort write failures (backpressure disconnects,\n // transform-stream exceptions, …) were previously swallowed\n // silently — `streamClosed` stayed `false` and the next\n // event re-attempted a broken writer. Log and mark the\n // stream closed so we stop trying.\n logError(error);\n streamClosed = true;\n }\n }\n }\n },\n error: async (error) => {\n reportAgentError(error, \"sse.subscription\");\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_errored\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { error: error instanceof Error ? error.message : String(error) },\n \"SSE stream errored\",\n );\n }\n logError(error);\n await closeStream();\n },\n complete: async () => {\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_ended\", {});\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { eventCount, loggedEventCount },\n \"SSE stream completed\",\n );\n }\n await closeStream();\n },\n });\n\n // If the client disconnected before the subscription was created,\n // unsubscribe immediately to avoid leaking the observable.\n if (request.signal.aborted) {\n subscription.unsubscribe();\n }\n })().catch(async (error) => {\n reportAgentError(error, \"sse.factory\");\n logError(error);\n await closeStream();\n });\n\n request.signal.addEventListener(\"abort\", () => {\n subscription?.unsubscribe();\n });\n\n return new Response(stream.readable, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nfunction summarizeEvent(event: BaseEvent): Record<string, unknown> {\n const e = event as any;\n const summary: Record<string, unknown> = {};\n\n if (e.messageId) summary.messageId = e.messageId;\n if (e.toolCallId) summary.toolCallId = e.toolCallId;\n if (e.toolCallName) summary.toolCallName = e.toolCallName;\n if (e.role) summary.role = e.role;\n if (e.delta != null && typeof e.delta === \"string\")\n summary.deltaLength = e.delta.length;\n if (e.snapshot && typeof e.snapshot === \"object\")\n summary.snapshotKeys = Object.keys(e.snapshot);\n if (e.delta && Array.isArray(e.delta))\n summary.operationCount = e.delta.length;\n if (e.threadId) summary.threadId = e.threadId;\n if (e.runId) summary.runId = e.runId;\n if (e.message) summary.message = e.message;\n if (e.code) summary.code = e.code;\n if (e.stepName) summary.stepName = e.stepName;\n\n return summary;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sse-response.mjs","names":["defaultTelemetry"],"sources":["../../../../../src/v2/runtime/handlers/shared/sse-response.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\nimport { EventEncoder } from \"@ag-ui/encoder\";\nimport type { Observable, Subscription } from \"rxjs\";\nimport {\n keepAliveSse,\n resolveSseKeepAliveIntervalSeconds,\n} from \"./sse-keep-alive\";\nimport type { ResolvedDebugConfig } from \"@copilotkit/shared\";\nimport { createLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport type { CopilotRuntimeLogger } from \"../../../../v1-deprecated/lib/logger\";\nimport { telemetry as defaultTelemetry } from \"../../telemetry\";\nimport type { TelemetryCapture } from \"../../telemetry/telemetry-client\";\nimport type { AgentExecutionResponseInfo } from \"../../telemetry/events\";\nimport type { DebugEventBus } from \"../../core/debug-event-bus\";\nimport type {\n RuntimeErrorPhase,\n RuntimeErrorReporter,\n} from \"../../core/runtime-error-reporter\";\n\ninterface CreateSseEventResponseParams {\n request: Request;\n observableFactory: () =>\n | Promise<Observable<BaseEvent>>\n | Observable<BaseEvent>;\n debugEventBus?: DebugEventBus;\n agentId?: string;\n debug?: ResolvedDebugConfig;\n /** Pre-created logger instance to avoid creating a new pino logger per request. */\n logger?: CopilotRuntimeLogger;\n /** Runtime-bound telemetry capture. Falls back for external direct callers. */\n telemetry?: TelemetryCapture;\n /**\n * Execution facts known before the stream opens, merged under anything the\n * upstream reports as it runs. Carries `llmHostClass`, which is knowable\n * from the agent's configuration and never appears on a streamed event.\n */\n executionSeed?: AgentExecutionResponseInfo;\n /**\n * Whether to emit `oss.runtime.agent_execution_stream_*` telemetry for this\n * stream. Defaults to `true`. The stateless `/suggest` path sets this to\n * `false`: a suggestion is a side-effect-free structured completion, not a\n * tracked run, and under `available: \"always\"` it fires often enough to flood\n * run telemetry.\n */\n captureTelemetry?: boolean;\n runtimeErrorReporter?: RuntimeErrorReporter;\n startTime?: number;\n /**\n * Seconds of silence before a `: keep-alive` SSE comment is written.\n * `0` disables the keep-alive. Resolved by\n * {@link resolveSseKeepAliveIntervalSeconds}, which supplies the default\n * and enforces the timer bound for every caller.\n */\n keepAliveIntervalSeconds?: number;\n}\n\nexport function createSseEventResponse({\n request,\n observableFactory,\n debugEventBus,\n agentId,\n debug,\n logger,\n telemetry = defaultTelemetry,\n executionSeed,\n captureTelemetry = true,\n runtimeErrorReporter,\n startTime,\n keepAliveIntervalSeconds,\n}: CreateSseEventResponseParams): Response {\n const keepAliveSeconds = resolveSseKeepAliveIntervalSeconds(\n keepAliveIntervalSeconds,\n );\n const stream = new TransformStream();\n const writer = stream.writable.getWriter();\n const encoder = new EventEncoder();\n let streamClosed = false;\n let debugThreadId = \"\";\n let debugRunId = \"\";\n\n const debugLogger = debug?.enabled\n ? (logger ??\n createLogger({ level: \"debug\", component: \"copilotkit-debug\" }))\n : undefined;\n\n const closeStream = async () => {\n if (!streamClosed) {\n try {\n await writer.close();\n streamClosed = true;\n } catch {\n // Stream already closed.\n }\n }\n };\n\n const logError = (error: unknown) => {\n console.error(\"Error running agent:\", error);\n console.error(\n \"Error stack:\",\n error instanceof Error ? error.stack : \"No stack trace\",\n );\n console.error(\"Error details:\", {\n name: error instanceof Error ? error.name : \"Unknown\",\n message: error instanceof Error ? error.message : String(error),\n cause: error instanceof Error ? error.cause : undefined,\n });\n };\n\n let subscription: Subscription | undefined;\n let agentErrorReported = false;\n\n const reportAgentError = (error: unknown, phase: RuntimeErrorPhase) => {\n if (agentErrorReported) return;\n agentErrorReported = true;\n runtimeErrorReporter?.report({\n request,\n error,\n operation: \"agent.run\",\n agentId,\n threadId: debugThreadId || undefined,\n runId: debugRunId || undefined,\n phase,\n startTime,\n });\n };\n\n (async () => {\n const observable = await observableFactory();\n\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_started\", {});\n }\n\n if (debug?.lifecycle) {\n debugLogger!.debug(\"SSE stream opened\");\n }\n\n let eventCount = 0;\n let loggedEventCount = 0;\n\n // Provider/model/LangGraph facts, scraped off raw upstream events as\n // they pass and reported once the stream finishes. This moved here from\n // the v1 TelemetryAgentRunner, which wrapped the runner purely to\n // collect it and emitted its own duplicate copy of every stream event\n // to carry it. v2 callers had no equivalent and reported `{}`.\n // Seeded rather than empty: `llmHostClass` comes from the agent's config,\n // not from the wire, so nothing streamed will ever fill it in. Scraped\n // fields still win, since `collectExecutionInfo` writes over this.\n const executionInfo: AgentExecutionResponseInfo = { ...executionSeed };\n\n subscription = observable.subscribe({\n next: async (event) => {\n collectExecutionInfo(event, executionInfo);\n\n // Extract threadId/runId from RUN_STARTED\n if (event.type === \"RUN_STARTED\") {\n const e = event as { threadId?: string; runId?: string };\n debugThreadId = e.threadId ?? \"\";\n debugRunId = e.runId ?? \"\";\n }\n\n if (event.type === \"RUN_ERROR\") {\n const e = event as {\n message?: unknown;\n threadId?: string;\n runId?: string;\n };\n debugThreadId = e.threadId ?? debugThreadId;\n debugRunId = e.runId ?? debugRunId;\n reportAgentError(\n new Error(\n typeof e.message === \"string\"\n ? e.message\n : \"Runner reported a run error\",\n ),\n \"sse.subscription\",\n );\n }\n\n // Broadcast to debug listeners BEFORE the stream-closed gate below.\n // Intentional: debug subscribers (e.g. the VS Code Inspector panel)\n // should still receive trailing events after the SSE client for\n // this request closed its connection — they're independent\n // consumers observing the underlying runtime, not the request's\n // response stream.\n //\n // Wrapped in try/catch so a buggy debug subscriber can't propagate\n // an exception into this observer — if the throw reached the\n // `next` callback it would get routed to `error` by RxJS, closing\n // the SSE stream for an unrelated reason. Log via `logError` and\n // move on.\n if (debugEventBus) {\n try {\n debugEventBus.broadcast(event, {\n agentId: agentId ?? \"\",\n threadId: debugThreadId,\n runId: debugRunId,\n });\n } catch (broadcastError) {\n logError(broadcastError);\n }\n }\n\n if (!request.signal.aborted && !streamClosed) {\n try {\n eventCount++;\n if (debug?.events) {\n loggedEventCount++;\n if (debug.verbose) {\n debugLogger!.debug({ event }, \"Event emitted\");\n } else {\n debugLogger!.debug(\n { type: event.type, ...summarizeEvent(event) },\n \"Event emitted\",\n );\n }\n }\n await writer.write(encoder.encodeBinary(event));\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n streamClosed = true;\n } else {\n // Non-abort write failures (backpressure disconnects,\n // transform-stream exceptions, …) were previously swallowed\n // silently — `streamClosed` stayed `false` and the next\n // event re-attempted a broken writer. Log and mark the\n // stream closed so we stop trying.\n logError(error);\n streamClosed = true;\n }\n }\n }\n },\n error: async (error) => {\n reportAgentError(error, \"sse.subscription\");\n if (captureTelemetry) {\n telemetry.capture(\"oss.runtime.agent_execution_stream_errored\", {\n ...executionInfo,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { error: error instanceof Error ? error.message : String(error) },\n \"SSE stream errored\",\n );\n }\n logError(error);\n await closeStream();\n },\n complete: async () => {\n if (captureTelemetry) {\n telemetry.capture(\n \"oss.runtime.agent_execution_stream_ended\",\n executionInfo,\n );\n }\n if (debug?.lifecycle) {\n debugLogger!.debug(\n { eventCount, loggedEventCount },\n \"SSE stream completed\",\n );\n }\n await closeStream();\n },\n });\n\n // If the client disconnected before the subscription was created,\n // unsubscribe immediately to avoid leaking the observable.\n if (request.signal.aborted) {\n subscription.unsubscribe();\n }\n })().catch(async (error) => {\n reportAgentError(error, \"sse.factory\");\n logError(error);\n await closeStream();\n });\n\n request.signal.addEventListener(\"abort\", () => {\n subscription?.unsubscribe();\n });\n\n const body =\n keepAliveSeconds > 0\n ? keepAliveSse(stream.readable, keepAliveSeconds * 1_000)\n : stream.readable;\n\n return new Response(body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n}\n\nfunction summarizeEvent(event: BaseEvent): Record<string, unknown> {\n const e = event as any;\n const summary: Record<string, unknown> = {};\n\n if (e.messageId) summary.messageId = e.messageId;\n if (e.toolCallId) summary.toolCallId = e.toolCallId;\n if (e.toolCallName) summary.toolCallName = e.toolCallName;\n if (e.role) summary.role = e.role;\n if (e.delta != null && typeof e.delta === \"string\")\n summary.deltaLength = e.delta.length;\n if (e.snapshot && typeof e.snapshot === \"object\")\n summary.snapshotKeys = Object.keys(e.snapshot);\n if (e.delta && Array.isArray(e.delta))\n summary.operationCount = e.delta.length;\n if (e.threadId) summary.threadId = e.threadId;\n if (e.runId) summary.runId = e.runId;\n if (e.message) summary.message = e.message;\n if (e.code) summary.code = e.code;\n if (e.stepName) summary.stepName = e.stepName;\n\n return summary;\n}\n\n/**\n * Accumulate provider, model, and LangGraph facts from one upstream event.\n *\n * Mutates rather than returns so the caller keeps one record across the whole\n * stream: these arrive on different events and the last one wins. Only fields\n * the upstream actually sent are set, so an agent that reports none leaves the\n * record empty and the stream events carry `{}` as before.\n *\n * `rawEvent` is the untransformed upstream payload, present on AG-UI events\n * that wrap one. Its shape is the provider's, not ours, hence the narrowing.\n */\nfunction collectExecutionInfo(\n event: BaseEvent,\n into: AgentExecutionResponseInfo,\n): void {\n const rawEvent = (\n event as {\n rawEvent?: {\n metadata?: Record<string, unknown>;\n data?: Record<string, unknown>;\n };\n }\n ).rawEvent;\n if (!rawEvent) return;\n\n const model = (rawEvent.data as { output?: { model?: string } } | undefined)\n ?.output?.model;\n if (model) {\n into.model = model;\n // Carried forward from the v1 implementation, which set both from the\n // same field. The upstream sends no separate provider name.\n into.provider = model;\n }\n\n const metadata = rawEvent.metadata as\n | { langgraph_host?: string; langgraph_version?: string }\n | undefined;\n if (metadata?.langgraph_host) into.langGraphHost = metadata.langgraph_host;\n if (metadata?.langgraph_version) {\n into.langGraphVersion = metadata.langgraph_version;\n }\n}\n"],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ function handleSseConnect({ runtime, request, agentId, threadId, agent }) {
|
|
|
9
9
|
debugEventBus: runtime.debugEventBus,
|
|
10
10
|
agentId,
|
|
11
11
|
telemetry: runtime.telemetry,
|
|
12
|
+
keepAliveIntervalSeconds: runtime.sseKeepAliveIntervalSeconds,
|
|
12
13
|
observableFactory: () => runtime.runner.connect({
|
|
13
14
|
threadId,
|
|
14
15
|
agentId,
|