@copilotkit/runtime 1.56.4 → 1.56.5
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/converters/tanstack.cjs +121 -25
- package/dist/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs +121 -25
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs +8 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.mts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs +8 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs.map +1 -1
- package/dist/package.cjs +6 -6
- package/dist/package.mjs +6 -6
- package/dist/v2/runtime/endpoints/express.cjs +5 -5
- package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/express.mjs +5 -5
- package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
- package/package.json +7 -7
- package/src/agent/__tests__/agent-test-helpers.ts +31 -1
- package/src/agent/__tests__/converter-tanstack.test.ts +280 -0
- package/src/agent/converters/tanstack.ts +167 -10
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +8 -1
- package/src/v2/runtime/__tests__/express-fetch-bridge.test.ts +1 -1
- package/src/v2/runtime/endpoints/express.ts +9 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.mjs","names":["LangGraphAgent","AGUILangGraphAgent"],"sources":["../../../../../src/lib/runtime/agent-integrations/langgraph/agent.ts"],"sourcesContent":["import { map, Observable } from \"rxjs\";\nimport { LangGraphEventTypes } from \"../../../../agents/langgraph/events\";\nimport { BaseEvent, RawEvent } from \"@ag-ui/core\";\nimport {\n LangGraphAgent as AGUILangGraphAgent,\n LangGraphHttpAgent,\n type LangGraphAgentConfig,\n ProcessedEvents,\n SchemaKeys,\n type State,\n StateEnrichment,\n} from \"@ag-ui/langgraph\";\nimport { Message as LangGraphMessage } from \"@langchain/langgraph-sdk/dist/types.messages\";\nimport { ThreadState } from \"@langchain/langgraph-sdk\";\n\ninterface CopilotKitStateEnrichment {\n copilotkit: {\n actions: StateEnrichment[\"ag-ui\"][\"tools\"];\n context: StateEnrichment[\"ag-ui\"][\"context\"];\n };\n}\n\nimport { RunAgentInput, EventType, CustomEvent } from \"@ag-ui/client\";\n\n// Import and re-export from separate file to maintain API compatibility\nimport {\n CustomEventNames,\n TextMessageEvents,\n ToolCallEvents,\n PredictStateTool,\n} from \"./consts\";\nexport { CustomEventNames };\n\nexport class LangGraphAgent extends AGUILangGraphAgent {\n constructor(config: LangGraphAgentConfig) {\n super(config);\n }\n\n dispatchEvent(event: ProcessedEvents) {\n if (event.type === EventType.CUSTOM) {\n // const event = processedEvent as unknown as CustomEvent;\n const customEvent = event as unknown as CustomEvent;\n\n if (customEvent.name === CustomEventNames.CopilotKitManuallyEmitMessage) {\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_START,\n role: \"assistant\",\n messageId: customEvent.value.message_id,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: customEvent.value.message_id,\n delta: customEvent.value.message,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_END,\n messageId: customEvent.value.message_id,\n rawEvent: event,\n });\n return true;\n }\n\n if (\n customEvent.name === CustomEventNames.CopilotKitManuallyEmitToolCall\n ) {\n this.subscriber.next({\n type: EventType.TOOL_CALL_START,\n toolCallId: customEvent.value.id,\n toolCallName: customEvent.value.name,\n parentMessageId: customEvent.value.id,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: customEvent.value.id,\n delta: customEvent.value.args,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TOOL_CALL_END,\n toolCallId: customEvent.value.id,\n rawEvent: event,\n });\n return true;\n }\n\n if (\n customEvent.name ===\n CustomEventNames.CopilotKitManuallyEmitIntermediateState\n ) {\n this.activeRun.manuallyEmittedState = customEvent.value;\n this.dispatchEvent({\n type: EventType.STATE_SNAPSHOT,\n snapshot: this.getStateSnapshot({\n values: this.activeRun.manuallyEmittedState,\n } as ThreadState<State>),\n rawEvent: event,\n });\n return true;\n }\n\n if (customEvent.name === CustomEventNames.CopilotKitExit) {\n this.subscriber.next({\n type: EventType.CUSTOM,\n name: \"Exit\",\n value: true,\n });\n return true;\n }\n }\n\n // Intercept all text message and tool call events and check if should disable\n const rawEvent = (event as ToolCallEvents | TextMessageEvents).rawEvent;\n if (!rawEvent) {\n this.subscriber.next(event);\n return true;\n }\n\n const isMessageEvent =\n event.type === EventType.TEXT_MESSAGE_START ||\n event.type === EventType.TEXT_MESSAGE_CONTENT ||\n event.type === EventType.TEXT_MESSAGE_END;\n const isToolEvent =\n event.type === EventType.TOOL_CALL_START ||\n event.type === EventType.TOOL_CALL_ARGS ||\n event.type === EventType.TOOL_CALL_END;\n if (\"copilotkit:emit-tool-calls\" in (rawEvent.metadata || {})) {\n if (\n rawEvent.metadata[\"copilotkit:emit-tool-calls\"] === false &&\n isToolEvent\n ) {\n return false;\n }\n }\n if (\"copilotkit:emit-messages\" in (rawEvent.metadata || {})) {\n if (\n rawEvent.metadata[\"copilotkit:emit-messages\"] === false &&\n isMessageEvent\n ) {\n // Clean up tracked message state to prevent stale records from\n // leaking into subsequent nodes that have emit-messages enabled.\n if (this.activeRun?.id) {\n this.messagesInProcess[this.activeRun.id] = null;\n }\n return false;\n }\n }\n\n this.subscriber.next(event);\n return true;\n }\n\n // @ts-ignore\n run(input: RunAgentInput): Observable<BaseEvent> {\n return super.run(input).pipe(\n map((processedEvent) => {\n // Turn raw event into emit state snapshot from tool call event\n if (processedEvent.type === EventType.RAW) {\n // Get the LangGraph event from the AGUI event.\n const event =\n (processedEvent as RawEvent).event ??\n (processedEvent as RawEvent).rawEvent;\n\n const eventType = event.event;\n const toolCallData = event.data?.chunk?.tool_call_chunks?.[0];\n const toolCallUsedToPredictState = event.metadata?.[\n \"copilotkit:emit-intermediate-state\"\n ]?.some(\n (predictStateTool: PredictStateTool) =>\n predictStateTool.tool === toolCallData?.name,\n );\n\n if (\n eventType === LangGraphEventTypes.OnChatModelStream &&\n toolCallUsedToPredictState\n ) {\n return {\n type: EventType.CUSTOM,\n name: \"PredictState\",\n value: event.metadata[\"copilotkit:emit-intermediate-state\"],\n };\n }\n }\n\n return processedEvent;\n }),\n );\n }\n\n langGraphDefaultMergeState(\n state: State,\n messages: LangGraphMessage[],\n input: RunAgentInput,\n ): State<StateEnrichment & CopilotKitStateEnrichment> {\n const aguiMergedState = super.langGraphDefaultMergeState(\n state,\n messages,\n input,\n );\n const { tools: returnedTools, \"ag-ui\": agui } = aguiMergedState;\n // tolerate undefined and de-duplicate by stable key (id | name | key)\n const rawCombinedTools = [\n ...((returnedTools as any[]) ?? []),\n ...((agui?.tools as any[]) ?? []),\n ];\n const combinedTools = Array.from(\n new Map(\n rawCombinedTools.map((t: any) => [\n t?.id ?? t?.name ?? t?.key ?? JSON.stringify(t),\n t,\n ]),\n ).values(),\n );\n\n return {\n ...aguiMergedState,\n copilotkit: {\n actions: combinedTools,\n context: agui?.context ?? [],\n },\n };\n }\n\n async getSchemaKeys(): Promise<SchemaKeys> {\n const CONSTANT_KEYS = [\"copilotkit\"];\n const schemaKeys = await super.getSchemaKeys();\n return {\n config: schemaKeys.config,\n input: schemaKeys.input ? [...schemaKeys.input, ...CONSTANT_KEYS] : null,\n output: schemaKeys.output\n ? [...schemaKeys.output, ...CONSTANT_KEYS]\n : null,\n context: schemaKeys.context\n ? [...schemaKeys.context, ...CONSTANT_KEYS]\n : null,\n };\n }\n}\n\nexport { LangGraphHttpAgent };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent.mjs","names":["LangGraphAgent","AGUILangGraphAgent"],"sources":["../../../../../src/lib/runtime/agent-integrations/langgraph/agent.ts"],"sourcesContent":["import { map, Observable } from \"rxjs\";\nimport { LangGraphEventTypes } from \"../../../../agents/langgraph/events\";\nimport { BaseEvent, RawEvent } from \"@ag-ui/core\";\nimport {\n LangGraphAgent as AGUILangGraphAgent,\n LangGraphHttpAgent,\n type LangGraphAgentConfig,\n ProcessedEvents,\n SchemaKeys,\n type State,\n StateEnrichment,\n} from \"@ag-ui/langgraph\";\nimport { Message as LangGraphMessage } from \"@langchain/langgraph-sdk/dist/types.messages\";\nimport { ThreadState } from \"@langchain/langgraph-sdk\";\n\ninterface CopilotKitStateEnrichment {\n copilotkit: {\n actions: StateEnrichment[\"ag-ui\"][\"tools\"];\n context: StateEnrichment[\"ag-ui\"][\"context\"];\n };\n}\n\nimport { RunAgentInput, EventType, CustomEvent } from \"@ag-ui/client\";\n\n// Import and re-export from separate file to maintain API compatibility\nimport {\n CustomEventNames,\n TextMessageEvents,\n ToolCallEvents,\n PredictStateTool,\n} from \"./consts\";\nexport { CustomEventNames };\n\nexport class LangGraphAgent extends AGUILangGraphAgent {\n constructor(config: LangGraphAgentConfig) {\n super(config);\n }\n\n dispatchEvent(event: ProcessedEvents) {\n if (event.type === EventType.CUSTOM) {\n // const event = processedEvent as unknown as CustomEvent;\n const customEvent = event as unknown as CustomEvent;\n\n if (customEvent.name === CustomEventNames.CopilotKitManuallyEmitMessage) {\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_START,\n role: \"assistant\",\n messageId: customEvent.value.message_id,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: customEvent.value.message_id,\n delta: customEvent.value.message,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TEXT_MESSAGE_END,\n messageId: customEvent.value.message_id,\n rawEvent: event,\n });\n return true;\n }\n\n if (\n customEvent.name === CustomEventNames.CopilotKitManuallyEmitToolCall\n ) {\n this.subscriber.next({\n type: EventType.TOOL_CALL_START,\n toolCallId: customEvent.value.id,\n toolCallName: customEvent.value.name,\n parentMessageId: customEvent.value.id,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: customEvent.value.id,\n delta: customEvent.value.args,\n rawEvent: event,\n });\n this.subscriber.next({\n type: EventType.TOOL_CALL_END,\n toolCallId: customEvent.value.id,\n rawEvent: event,\n });\n return true;\n }\n\n if (\n customEvent.name ===\n CustomEventNames.CopilotKitManuallyEmitIntermediateState\n ) {\n this.activeRun.manuallyEmittedState = customEvent.value;\n this.dispatchEvent({\n type: EventType.STATE_SNAPSHOT,\n snapshot: this.getStateSnapshot({\n values: this.activeRun.manuallyEmittedState,\n } as ThreadState<State>),\n rawEvent: event,\n });\n return true;\n }\n\n if (customEvent.name === CustomEventNames.CopilotKitExit) {\n this.subscriber.next({\n type: EventType.CUSTOM,\n name: \"Exit\",\n value: true,\n });\n return true;\n }\n }\n\n // Intercept all text message and tool call events and check if should disable\n const rawEvent = (event as ToolCallEvents | TextMessageEvents).rawEvent;\n if (!rawEvent) {\n this.subscriber.next(event);\n return true;\n }\n\n const isMessageEvent =\n event.type === EventType.TEXT_MESSAGE_START ||\n event.type === EventType.TEXT_MESSAGE_CONTENT ||\n event.type === EventType.TEXT_MESSAGE_END;\n const isToolEvent =\n event.type === EventType.TOOL_CALL_START ||\n event.type === EventType.TOOL_CALL_ARGS ||\n event.type === EventType.TOOL_CALL_END;\n if (\"copilotkit:emit-tool-calls\" in (rawEvent.metadata || {})) {\n if (\n rawEvent.metadata[\"copilotkit:emit-tool-calls\"] === false &&\n isToolEvent\n ) {\n return false;\n }\n }\n if (\"copilotkit:emit-messages\" in (rawEvent.metadata || {})) {\n if (\n rawEvent.metadata[\"copilotkit:emit-messages\"] === false &&\n isMessageEvent\n ) {\n // Clean up tracked message state to prevent stale records from\n // leaking into subsequent nodes that have emit-messages enabled.\n if (this.activeRun?.id) {\n this.messagesInProcess[this.activeRun.id] = null;\n }\n return false;\n }\n }\n\n this.subscriber.next(event);\n return true;\n }\n\n // @ts-ignore\n run(input: RunAgentInput): Observable<BaseEvent> {\n const enrichedInput = {\n ...input,\n forwardedProps: {\n ...input.forwardedProps,\n streamSubgraphs: input.forwardedProps?.streamSubgraphs ?? true,\n },\n };\n return super.run(enrichedInput).pipe(\n map((processedEvent) => {\n // Turn raw event into emit state snapshot from tool call event\n if (processedEvent.type === EventType.RAW) {\n // Get the LangGraph event from the AGUI event.\n const event =\n (processedEvent as RawEvent).event ??\n (processedEvent as RawEvent).rawEvent;\n\n const eventType = event.event;\n const toolCallData = event.data?.chunk?.tool_call_chunks?.[0];\n const toolCallUsedToPredictState = event.metadata?.[\n \"copilotkit:emit-intermediate-state\"\n ]?.some(\n (predictStateTool: PredictStateTool) =>\n predictStateTool.tool === toolCallData?.name,\n );\n\n if (\n eventType === LangGraphEventTypes.OnChatModelStream &&\n toolCallUsedToPredictState\n ) {\n return {\n type: EventType.CUSTOM,\n name: \"PredictState\",\n value: event.metadata[\"copilotkit:emit-intermediate-state\"],\n };\n }\n }\n\n return processedEvent;\n }),\n );\n }\n\n langGraphDefaultMergeState(\n state: State,\n messages: LangGraphMessage[],\n input: RunAgentInput,\n ): State<StateEnrichment & CopilotKitStateEnrichment> {\n const aguiMergedState = super.langGraphDefaultMergeState(\n state,\n messages,\n input,\n );\n const { tools: returnedTools, \"ag-ui\": agui } = aguiMergedState;\n // tolerate undefined and de-duplicate by stable key (id | name | key)\n const rawCombinedTools = [\n ...((returnedTools as any[]) ?? []),\n ...((agui?.tools as any[]) ?? []),\n ];\n const combinedTools = Array.from(\n new Map(\n rawCombinedTools.map((t: any) => [\n t?.id ?? t?.name ?? t?.key ?? JSON.stringify(t),\n t,\n ]),\n ).values(),\n );\n\n return {\n ...aguiMergedState,\n copilotkit: {\n actions: combinedTools,\n context: agui?.context ?? [],\n },\n };\n }\n\n async getSchemaKeys(): Promise<SchemaKeys> {\n const CONSTANT_KEYS = [\"copilotkit\"];\n const schemaKeys = await super.getSchemaKeys();\n return {\n config: schemaKeys.config,\n input: schemaKeys.input ? [...schemaKeys.input, ...CONSTANT_KEYS] : null,\n output: schemaKeys.output\n ? [...schemaKeys.output, ...CONSTANT_KEYS]\n : null,\n context: schemaKeys.context\n ? [...schemaKeys.context, ...CONSTANT_KEYS]\n : null,\n };\n }\n}\n\nexport { LangGraphHttpAgent };\n"],"mappings":";;;;;;;;AAiCA,IAAaA,mBAAb,cAAoCC,eAAmB;CACrD,YAAY,QAA8B;AACxC,QAAM,OAAO;;CAGf,cAAc,OAAwB;AACpC,MAAI,MAAM,SAAS,UAAU,QAAQ;GAEnC,MAAM,cAAc;AAEpB,OAAI,YAAY,SAAS,iBAAiB,+BAA+B;AACvE,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,MAAM;KACN,WAAW,YAAY,MAAM;KAC7B,UAAU;KACX,CAAC;AACF,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,WAAW,YAAY,MAAM;KAC7B,OAAO,YAAY,MAAM;KACzB,UAAU;KACX,CAAC;AACF,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,WAAW,YAAY,MAAM;KAC7B,UAAU;KACX,CAAC;AACF,WAAO;;AAGT,OACE,YAAY,SAAS,iBAAiB,gCACtC;AACA,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,YAAY,YAAY,MAAM;KAC9B,cAAc,YAAY,MAAM;KAChC,iBAAiB,YAAY,MAAM;KACnC,UAAU;KACX,CAAC;AACF,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,YAAY,YAAY,MAAM;KAC9B,OAAO,YAAY,MAAM;KACzB,UAAU;KACX,CAAC;AACF,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,YAAY,YAAY,MAAM;KAC9B,UAAU;KACX,CAAC;AACF,WAAO;;AAGT,OACE,YAAY,SACZ,iBAAiB,yCACjB;AACA,SAAK,UAAU,uBAAuB,YAAY;AAClD,SAAK,cAAc;KACjB,MAAM,UAAU;KAChB,UAAU,KAAK,iBAAiB,EAC9B,QAAQ,KAAK,UAAU,sBACxB,CAAuB;KACxB,UAAU;KACX,CAAC;AACF,WAAO;;AAGT,OAAI,YAAY,SAAS,iBAAiB,gBAAgB;AACxD,SAAK,WAAW,KAAK;KACnB,MAAM,UAAU;KAChB,MAAM;KACN,OAAO;KACR,CAAC;AACF,WAAO;;;EAKX,MAAM,WAAY,MAA6C;AAC/D,MAAI,CAAC,UAAU;AACb,QAAK,WAAW,KAAK,MAAM;AAC3B,UAAO;;EAGT,MAAM,iBACJ,MAAM,SAAS,UAAU,sBACzB,MAAM,SAAS,UAAU,wBACzB,MAAM,SAAS,UAAU;EAC3B,MAAM,cACJ,MAAM,SAAS,UAAU,mBACzB,MAAM,SAAS,UAAU,kBACzB,MAAM,SAAS,UAAU;AAC3B,MAAI,iCAAiC,SAAS,YAAY,EAAE,GAC1D;OACE,SAAS,SAAS,kCAAkC,SACpD,YAEA,QAAO;;AAGX,MAAI,+BAA+B,SAAS,YAAY,EAAE,GACxD;OACE,SAAS,SAAS,gCAAgC,SAClD,gBACA;AAGA,QAAI,KAAK,WAAW,GAClB,MAAK,kBAAkB,KAAK,UAAU,MAAM;AAE9C,WAAO;;;AAIX,OAAK,WAAW,KAAK,MAAM;AAC3B,SAAO;;CAIT,IAAI,OAA6C;EAC/C,MAAM,gBAAgB;GACpB,GAAG;GACH,gBAAgB;IACd,GAAG,MAAM;IACT,iBAAiB,MAAM,gBAAgB,mBAAmB;IAC3D;GACF;AACD,SAAO,MAAM,IAAI,cAAc,CAAC,KAC9B,KAAK,mBAAmB;AAEtB,OAAI,eAAe,SAAS,UAAU,KAAK;IAEzC,MAAM,QACH,eAA4B,SAC5B,eAA4B;IAE/B,MAAM,YAAY,MAAM;IACxB,MAAM,eAAe,MAAM,MAAM,OAAO,mBAAmB;IAC3D,MAAM,6BAA6B,MAAM,WACvC,uCACC,MACA,qBACC,iBAAiB,SAAS,cAAc,KAC3C;AAED,QACE,cAAc,oBAAoB,qBAClC,2BAEA,QAAO;KACL,MAAM,UAAU;KAChB,MAAM;KACN,OAAO,MAAM,SAAS;KACvB;;AAIL,UAAO;IACP,CACH;;CAGH,2BACE,OACA,UACA,OACoD;EACpD,MAAM,kBAAkB,MAAM,2BAC5B,OACA,UACA,MACD;EACD,MAAM,EAAE,OAAO,eAAe,SAAS,SAAS;EAEhD,MAAM,mBAAmB,CACvB,GAAK,iBAA2B,EAAE,EAClC,GAAK,MAAM,SAAmB,EAAE,CACjC;EACD,MAAM,gBAAgB,MAAM,KAC1B,IAAI,IACF,iBAAiB,KAAK,MAAW,CAC/B,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,KAAK,UAAU,EAAE,EAC/C,EACD,CAAC,CACH,CAAC,QAAQ,CACX;AAED,SAAO;GACL,GAAG;GACH,YAAY;IACV,SAAS;IACT,SAAS,MAAM,WAAW,EAAE;IAC7B;GACF;;CAGH,MAAM,gBAAqC;EACzC,MAAM,gBAAgB,CAAC,aAAa;EACpC,MAAM,aAAa,MAAM,MAAM,eAAe;AAC9C,SAAO;GACL,QAAQ,WAAW;GACnB,OAAO,WAAW,QAAQ,CAAC,GAAG,WAAW,OAAO,GAAG,cAAc,GAAG;GACpE,QAAQ,WAAW,SACf,CAAC,GAAG,WAAW,QAAQ,GAAG,cAAc,GACxC;GACJ,SAAS,WAAW,UAChB,CAAC,GAAG,WAAW,SAAS,GAAG,cAAc,GACzC;GACL"}
|
package/dist/package.cjs
CHANGED
|
@@ -5,7 +5,7 @@ const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
|
5
5
|
var require_package = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
6
6
|
module.exports = {
|
|
7
7
|
"name": "@copilotkit/runtime",
|
|
8
|
-
"version": "1.56.
|
|
8
|
+
"version": "1.56.5",
|
|
9
9
|
"private": false,
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
|
@@ -82,10 +82,10 @@ var require_package = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, m
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@ag-ui/a2ui-middleware": "0.0.5",
|
|
85
|
-
"@ag-ui/client": "0.0.
|
|
86
|
-
"@ag-ui/core": "0.0.
|
|
87
|
-
"@ag-ui/encoder": "0.0.
|
|
88
|
-
"@ag-ui/langgraph": "0.0.
|
|
85
|
+
"@ag-ui/client": "0.0.53",
|
|
86
|
+
"@ag-ui/core": "0.0.53",
|
|
87
|
+
"@ag-ui/encoder": "0.0.53",
|
|
88
|
+
"@ag-ui/langgraph": "0.0.31",
|
|
89
89
|
"@ag-ui/mcp-apps-middleware": "0.0.3",
|
|
90
90
|
"@ai-sdk/anthropic": "^3.0.49",
|
|
91
91
|
"@ai-sdk/google": "^3.0.33",
|
|
@@ -123,7 +123,7 @@ var require_package = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, m
|
|
|
123
123
|
"zod": "^3.23.3"
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
|
-
"@copilotkit/aimock": "
|
|
126
|
+
"@copilotkit/aimock": "latest",
|
|
127
127
|
"@swc/core": "1.5.28",
|
|
128
128
|
"@types/cors": "^2.8.17",
|
|
129
129
|
"@types/express": "^4.17.21",
|
package/dist/package.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { __commonJSMin } from "./_virtual/_rolldown/runtime.mjs";
|
|
|
5
5
|
var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6
6
|
module.exports = {
|
|
7
7
|
"name": "@copilotkit/runtime",
|
|
8
|
-
"version": "1.56.
|
|
8
|
+
"version": "1.56.5",
|
|
9
9
|
"private": false,
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
|
@@ -82,10 +82,10 @@ var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@ag-ui/a2ui-middleware": "0.0.5",
|
|
85
|
-
"@ag-ui/client": "0.0.
|
|
86
|
-
"@ag-ui/core": "0.0.
|
|
87
|
-
"@ag-ui/encoder": "0.0.
|
|
88
|
-
"@ag-ui/langgraph": "0.0.
|
|
85
|
+
"@ag-ui/client": "0.0.53",
|
|
86
|
+
"@ag-ui/core": "0.0.53",
|
|
87
|
+
"@ag-ui/encoder": "0.0.53",
|
|
88
|
+
"@ag-ui/langgraph": "0.0.31",
|
|
89
89
|
"@ag-ui/mcp-apps-middleware": "0.0.3",
|
|
90
90
|
"@ai-sdk/anthropic": "^3.0.49",
|
|
91
91
|
"@ai-sdk/google": "^3.0.33",
|
|
@@ -123,7 +123,7 @@ var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
123
123
|
"zod": "^3.23.3"
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
|
-
"@copilotkit/aimock": "
|
|
126
|
+
"@copilotkit/aimock": "latest",
|
|
127
127
|
"@swc/core": "1.5.28",
|
|
128
128
|
"@types/cors": "^2.8.17",
|
|
129
129
|
"@types/express": "^4.17.21",
|
|
@@ -44,13 +44,13 @@ function createCopilotExpressHandler({ runtime, basePath, mode = "multi-route",
|
|
|
44
44
|
if (mode === "single-route") {
|
|
45
45
|
router.post(normalizedBase, expressHandler);
|
|
46
46
|
router.options(normalizedBase, expressHandler);
|
|
47
|
-
} else if (normalizedBase === "/") router.all(
|
|
48
|
-
else {
|
|
49
|
-
router.all(`${normalizedBase}/*`, expressHandler);
|
|
50
|
-
router.all(normalizedBase, expressHandler);
|
|
51
|
-
}
|
|
47
|
+
} else if (normalizedBase === "/") router.all(/.*/, expressHandler);
|
|
48
|
+
else router.all(new RegExp(`^${escapeRegExp(normalizedBase)}(\\/.*)?$`), expressHandler);
|
|
52
49
|
return router;
|
|
53
50
|
}
|
|
51
|
+
function escapeRegExp(s) {
|
|
52
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
53
|
+
}
|
|
54
54
|
function normalizeBasePath(path) {
|
|
55
55
|
if (!path) throw new Error("basePath must be provided for Express endpoint");
|
|
56
56
|
if (!path.startsWith("/")) return `/${path}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.cjs","names":["createExpressNodeHandler","createCopilotRuntimeHandler"],"sources":["../../../../src/v2/runtime/endpoints/express.ts"],"sourcesContent":["import express from \"express\";\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Router,\n} from \"express\";\nimport cors from \"cors\";\nimport type { CorsOptions } from \"cors\";\nimport type { CopilotRuntimeLike } from \"../core/runtime\";\nimport { createCopilotRuntimeHandler } from \"../core/fetch-handler\";\nimport { createExpressNodeHandler } from \"./express-fetch-bridge\";\nimport type { CopilotRuntimeHooks } from \"../core/hooks\";\n\nexport interface CopilotExpressEndpointParams {\n runtime: CopilotRuntimeLike;\n basePath: string;\n\n /**\n * Endpoint mode.\n * - `\"multi-route\"` (default): separate routes for each operation\n * - `\"single-route\"`: single POST endpoint with JSON envelope dispatch\n */\n mode?: \"multi-route\" | \"single-route\";\n\n /**\n * CORS configuration for the Express router.\n * - `true` (default): permissive CORS (`origin: \"*\"`, all methods, all headers).\n * - `false`: no CORS middleware is applied — handle it yourself.\n * - object: passed directly to the Express `cors()` middleware.\n */\n cors?: boolean | CorsOptions;\n\n /**\n * Lifecycle hooks for request processing.\n */\n hooks?: CopilotRuntimeHooks;\n}\n\n/**\n * Creates an Express router that serves the CopilotKit runtime.\n *\n * In **multi-route** mode (default) the router exposes:\n * - `GET {basePath}/info` — runtime info\n * - `POST {basePath}/agent/:agentId/run` — start an agent run\n * - `POST {basePath}/agent/:agentId/connect` — connect to an agent run\n * - `POST {basePath}/agent/:agentId/stop/:threadId` — stop an agent run\n * - `POST {basePath}/transcribe` — transcribe audio\n *\n * In **single-route** mode a single `POST {basePath}` endpoint accepts a JSON\n * envelope `{ method, params, body }` and dispatches to the appropriate handler.\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { CopilotRuntime } from \"@copilotkit/runtime/v2\";\n * import { createCopilotExpressHandler } from \"@copilotkit/runtime/v2/express\";\n *\n * const runtime = new CopilotRuntime({\n * agents: { default: new BuiltInAgent({ model: \"openai/gpt-4o-mini\" }) },\n * });\n *\n * const app = express();\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * cors: true,\n * }));\n * app.listen(4000);\n * ```\n *\n * @example Single-route mode with lifecycle hooks\n * ```typescript\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * mode: \"single-route\",\n * hooks: {\n * onRequest: ({ request }) => {\n * if (!request.headers.get(\"authorization\")) {\n * throw new Response(\"Unauthorized\", { status: 401 });\n * }\n * },\n * },\n * }));\n * ```\n */\n/** @deprecated Use `createCopilotExpressHandler` instead. */\nexport { createCopilotExpressHandler as createCopilotEndpointExpress };\n\nexport function createCopilotExpressHandler({\n runtime,\n basePath,\n mode = \"multi-route\",\n cors: corsOption = true,\n hooks,\n}: CopilotExpressEndpointParams): Router {\n const normalizedBase = normalizeBasePath(basePath);\n\n const handler = createCopilotRuntimeHandler({\n runtime,\n basePath: normalizedBase,\n mode,\n cors: false, // CORS is handled at the Express middleware layer\n hooks,\n });\n\n const nodeHandler = createExpressNodeHandler(handler);\n\n const expressHandler = async (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) => {\n try {\n await nodeHandler(req, res);\n } catch (err) {\n next(err);\n }\n };\n\n const router = express.Router();\n\n // CORS middleware\n if (corsOption) {\n const corsConfig: CorsOptions =\n corsOption === true\n ? {\n origin: \"*\",\n methods: [\n \"GET\",\n \"HEAD\",\n \"PUT\",\n \"POST\",\n \"DELETE\",\n \"PATCH\",\n \"OPTIONS\",\n ],\n allowedHeaders: [\"*\"],\n }\n : corsOption;\n router.use(cors(corsConfig));\n }\n\n // Route mounting\n if (mode === \"single-route\") {\n router.post(normalizedBase, expressHandler);\n router.options(normalizedBase, expressHandler);\n } else if (normalizedBase === \"/\") {\n router.all(
|
|
1
|
+
{"version":3,"file":"express.cjs","names":["createExpressNodeHandler","createCopilotRuntimeHandler"],"sources":["../../../../src/v2/runtime/endpoints/express.ts"],"sourcesContent":["import express from \"express\";\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Router,\n} from \"express\";\nimport cors from \"cors\";\nimport type { CorsOptions } from \"cors\";\nimport type { CopilotRuntimeLike } from \"../core/runtime\";\nimport { createCopilotRuntimeHandler } from \"../core/fetch-handler\";\nimport { createExpressNodeHandler } from \"./express-fetch-bridge\";\nimport type { CopilotRuntimeHooks } from \"../core/hooks\";\n\nexport interface CopilotExpressEndpointParams {\n runtime: CopilotRuntimeLike;\n basePath: string;\n\n /**\n * Endpoint mode.\n * - `\"multi-route\"` (default): separate routes for each operation\n * - `\"single-route\"`: single POST endpoint with JSON envelope dispatch\n */\n mode?: \"multi-route\" | \"single-route\";\n\n /**\n * CORS configuration for the Express router.\n * - `true` (default): permissive CORS (`origin: \"*\"`, all methods, all headers).\n * - `false`: no CORS middleware is applied — handle it yourself.\n * - object: passed directly to the Express `cors()` middleware.\n */\n cors?: boolean | CorsOptions;\n\n /**\n * Lifecycle hooks for request processing.\n */\n hooks?: CopilotRuntimeHooks;\n}\n\n/**\n * Creates an Express router that serves the CopilotKit runtime.\n *\n * In **multi-route** mode (default) the router exposes:\n * - `GET {basePath}/info` — runtime info\n * - `POST {basePath}/agent/:agentId/run` — start an agent run\n * - `POST {basePath}/agent/:agentId/connect` — connect to an agent run\n * - `POST {basePath}/agent/:agentId/stop/:threadId` — stop an agent run\n * - `POST {basePath}/transcribe` — transcribe audio\n *\n * In **single-route** mode a single `POST {basePath}` endpoint accepts a JSON\n * envelope `{ method, params, body }` and dispatches to the appropriate handler.\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { CopilotRuntime } from \"@copilotkit/runtime/v2\";\n * import { createCopilotExpressHandler } from \"@copilotkit/runtime/v2/express\";\n *\n * const runtime = new CopilotRuntime({\n * agents: { default: new BuiltInAgent({ model: \"openai/gpt-4o-mini\" }) },\n * });\n *\n * const app = express();\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * cors: true,\n * }));\n * app.listen(4000);\n * ```\n *\n * @example Single-route mode with lifecycle hooks\n * ```typescript\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * mode: \"single-route\",\n * hooks: {\n * onRequest: ({ request }) => {\n * if (!request.headers.get(\"authorization\")) {\n * throw new Response(\"Unauthorized\", { status: 401 });\n * }\n * },\n * },\n * }));\n * ```\n */\n/** @deprecated Use `createCopilotExpressHandler` instead. */\nexport { createCopilotExpressHandler as createCopilotEndpointExpress };\n\nexport function createCopilotExpressHandler({\n runtime,\n basePath,\n mode = \"multi-route\",\n cors: corsOption = true,\n hooks,\n}: CopilotExpressEndpointParams): Router {\n const normalizedBase = normalizeBasePath(basePath);\n\n const handler = createCopilotRuntimeHandler({\n runtime,\n basePath: normalizedBase,\n mode,\n cors: false, // CORS is handled at the Express middleware layer\n hooks,\n });\n\n const nodeHandler = createExpressNodeHandler(handler);\n\n const expressHandler = async (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) => {\n try {\n await nodeHandler(req, res);\n } catch (err) {\n next(err);\n }\n };\n\n const router = express.Router();\n\n // CORS middleware\n if (corsOption) {\n const corsConfig: CorsOptions =\n corsOption === true\n ? {\n origin: \"*\",\n methods: [\n \"GET\",\n \"HEAD\",\n \"PUT\",\n \"POST\",\n \"DELETE\",\n \"PATCH\",\n \"OPTIONS\",\n ],\n allowedHeaders: [\"*\"],\n }\n : corsOption;\n router.use(cors(corsConfig));\n }\n\n // Route mounting\n if (mode === \"single-route\") {\n router.post(normalizedBase, expressHandler);\n router.options(normalizedBase, expressHandler);\n } else if (normalizedBase === \"/\") {\n router.all(/.*/, expressHandler);\n } else {\n router.all(\n new RegExp(`^${escapeRegExp(normalizedBase)}(\\\\/.*)?$`),\n expressHandler,\n );\n }\n\n return router;\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction normalizeBasePath(path: string): string {\n if (!path) {\n throw new Error(\"basePath must be provided for Express endpoint\");\n }\n\n if (!path.startsWith(\"/\")) {\n return `/${path}`;\n }\n\n if (path.length > 1 && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n}\n"],"mappings":";;;;;;;;;;AA0FA,SAAgB,4BAA4B,EAC1C,SACA,UACA,OAAO,eACP,MAAM,aAAa,MACnB,SACuC;CACvC,MAAM,iBAAiB,kBAAkB,SAAS;CAUlD,MAAM,cAAcA,sDARJC,kDAA4B;EAC1C;EACA,UAAU;EACV;EACA,MAAM;EACN;EACD,CAAC,CAEmD;CAErD,MAAM,iBAAiB,OACrB,KACA,KACA,SACG;AACH,MAAI;AACF,SAAM,YAAY,KAAK,IAAI;WACpB,KAAK;AACZ,QAAK,IAAI;;;CAIb,MAAM,SAAS,gBAAQ,QAAQ;AAG/B,KAAI,YAAY;EACd,MAAM,aACJ,eAAe,OACX;GACE,QAAQ;GACR,SAAS;IACP;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACD,gBAAgB,CAAC,IAAI;GACtB,GACD;AACN,SAAO,sBAAS,WAAW,CAAC;;AAI9B,KAAI,SAAS,gBAAgB;AAC3B,SAAO,KAAK,gBAAgB,eAAe;AAC3C,SAAO,QAAQ,gBAAgB,eAAe;YACrC,mBAAmB,IAC5B,QAAO,IAAI,MAAM,eAAe;KAEhC,QAAO,IACL,IAAI,OAAO,IAAI,aAAa,eAAe,CAAC,WAAW,EACvD,eACD;AAGH,QAAO;;AAGT,SAAS,aAAa,GAAmB;AACvC,QAAO,EAAE,QAAQ,uBAAuB,OAAO;;AAGjD,SAAS,kBAAkB,MAAsB;AAC/C,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,iDAAiD;AAGnE,KAAI,CAAC,KAAK,WAAW,IAAI,CACvB,QAAO,IAAI;AAGb,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI,CACvC,QAAO,KAAK,MAAM,GAAG,GAAG;AAG1B,QAAO"}
|
|
@@ -41,13 +41,13 @@ function createCopilotExpressHandler({ runtime, basePath, mode = "multi-route",
|
|
|
41
41
|
if (mode === "single-route") {
|
|
42
42
|
router.post(normalizedBase, expressHandler);
|
|
43
43
|
router.options(normalizedBase, expressHandler);
|
|
44
|
-
} else if (normalizedBase === "/") router.all(
|
|
45
|
-
else {
|
|
46
|
-
router.all(`${normalizedBase}/*`, expressHandler);
|
|
47
|
-
router.all(normalizedBase, expressHandler);
|
|
48
|
-
}
|
|
44
|
+
} else if (normalizedBase === "/") router.all(/.*/, expressHandler);
|
|
45
|
+
else router.all(new RegExp(`^${escapeRegExp(normalizedBase)}(\\/.*)?$`), expressHandler);
|
|
49
46
|
return router;
|
|
50
47
|
}
|
|
48
|
+
function escapeRegExp(s) {
|
|
49
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
}
|
|
51
51
|
function normalizeBasePath(path) {
|
|
52
52
|
if (!path) throw new Error("basePath must be provided for Express endpoint");
|
|
53
53
|
if (!path.startsWith("/")) return `/${path}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.mjs","names":[],"sources":["../../../../src/v2/runtime/endpoints/express.ts"],"sourcesContent":["import express from \"express\";\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Router,\n} from \"express\";\nimport cors from \"cors\";\nimport type { CorsOptions } from \"cors\";\nimport type { CopilotRuntimeLike } from \"../core/runtime\";\nimport { createCopilotRuntimeHandler } from \"../core/fetch-handler\";\nimport { createExpressNodeHandler } from \"./express-fetch-bridge\";\nimport type { CopilotRuntimeHooks } from \"../core/hooks\";\n\nexport interface CopilotExpressEndpointParams {\n runtime: CopilotRuntimeLike;\n basePath: string;\n\n /**\n * Endpoint mode.\n * - `\"multi-route\"` (default): separate routes for each operation\n * - `\"single-route\"`: single POST endpoint with JSON envelope dispatch\n */\n mode?: \"multi-route\" | \"single-route\";\n\n /**\n * CORS configuration for the Express router.\n * - `true` (default): permissive CORS (`origin: \"*\"`, all methods, all headers).\n * - `false`: no CORS middleware is applied — handle it yourself.\n * - object: passed directly to the Express `cors()` middleware.\n */\n cors?: boolean | CorsOptions;\n\n /**\n * Lifecycle hooks for request processing.\n */\n hooks?: CopilotRuntimeHooks;\n}\n\n/**\n * Creates an Express router that serves the CopilotKit runtime.\n *\n * In **multi-route** mode (default) the router exposes:\n * - `GET {basePath}/info` — runtime info\n * - `POST {basePath}/agent/:agentId/run` — start an agent run\n * - `POST {basePath}/agent/:agentId/connect` — connect to an agent run\n * - `POST {basePath}/agent/:agentId/stop/:threadId` — stop an agent run\n * - `POST {basePath}/transcribe` — transcribe audio\n *\n * In **single-route** mode a single `POST {basePath}` endpoint accepts a JSON\n * envelope `{ method, params, body }` and dispatches to the appropriate handler.\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { CopilotRuntime } from \"@copilotkit/runtime/v2\";\n * import { createCopilotExpressHandler } from \"@copilotkit/runtime/v2/express\";\n *\n * const runtime = new CopilotRuntime({\n * agents: { default: new BuiltInAgent({ model: \"openai/gpt-4o-mini\" }) },\n * });\n *\n * const app = express();\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * cors: true,\n * }));\n * app.listen(4000);\n * ```\n *\n * @example Single-route mode with lifecycle hooks\n * ```typescript\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * mode: \"single-route\",\n * hooks: {\n * onRequest: ({ request }) => {\n * if (!request.headers.get(\"authorization\")) {\n * throw new Response(\"Unauthorized\", { status: 401 });\n * }\n * },\n * },\n * }));\n * ```\n */\n/** @deprecated Use `createCopilotExpressHandler` instead. */\nexport { createCopilotExpressHandler as createCopilotEndpointExpress };\n\nexport function createCopilotExpressHandler({\n runtime,\n basePath,\n mode = \"multi-route\",\n cors: corsOption = true,\n hooks,\n}: CopilotExpressEndpointParams): Router {\n const normalizedBase = normalizeBasePath(basePath);\n\n const handler = createCopilotRuntimeHandler({\n runtime,\n basePath: normalizedBase,\n mode,\n cors: false, // CORS is handled at the Express middleware layer\n hooks,\n });\n\n const nodeHandler = createExpressNodeHandler(handler);\n\n const expressHandler = async (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) => {\n try {\n await nodeHandler(req, res);\n } catch (err) {\n next(err);\n }\n };\n\n const router = express.Router();\n\n // CORS middleware\n if (corsOption) {\n const corsConfig: CorsOptions =\n corsOption === true\n ? {\n origin: \"*\",\n methods: [\n \"GET\",\n \"HEAD\",\n \"PUT\",\n \"POST\",\n \"DELETE\",\n \"PATCH\",\n \"OPTIONS\",\n ],\n allowedHeaders: [\"*\"],\n }\n : corsOption;\n router.use(cors(corsConfig));\n }\n\n // Route mounting\n if (mode === \"single-route\") {\n router.post(normalizedBase, expressHandler);\n router.options(normalizedBase, expressHandler);\n } else if (normalizedBase === \"/\") {\n router.all(
|
|
1
|
+
{"version":3,"file":"express.mjs","names":[],"sources":["../../../../src/v2/runtime/endpoints/express.ts"],"sourcesContent":["import express from \"express\";\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Router,\n} from \"express\";\nimport cors from \"cors\";\nimport type { CorsOptions } from \"cors\";\nimport type { CopilotRuntimeLike } from \"../core/runtime\";\nimport { createCopilotRuntimeHandler } from \"../core/fetch-handler\";\nimport { createExpressNodeHandler } from \"./express-fetch-bridge\";\nimport type { CopilotRuntimeHooks } from \"../core/hooks\";\n\nexport interface CopilotExpressEndpointParams {\n runtime: CopilotRuntimeLike;\n basePath: string;\n\n /**\n * Endpoint mode.\n * - `\"multi-route\"` (default): separate routes for each operation\n * - `\"single-route\"`: single POST endpoint with JSON envelope dispatch\n */\n mode?: \"multi-route\" | \"single-route\";\n\n /**\n * CORS configuration for the Express router.\n * - `true` (default): permissive CORS (`origin: \"*\"`, all methods, all headers).\n * - `false`: no CORS middleware is applied — handle it yourself.\n * - object: passed directly to the Express `cors()` middleware.\n */\n cors?: boolean | CorsOptions;\n\n /**\n * Lifecycle hooks for request processing.\n */\n hooks?: CopilotRuntimeHooks;\n}\n\n/**\n * Creates an Express router that serves the CopilotKit runtime.\n *\n * In **multi-route** mode (default) the router exposes:\n * - `GET {basePath}/info` — runtime info\n * - `POST {basePath}/agent/:agentId/run` — start an agent run\n * - `POST {basePath}/agent/:agentId/connect` — connect to an agent run\n * - `POST {basePath}/agent/:agentId/stop/:threadId` — stop an agent run\n * - `POST {basePath}/transcribe` — transcribe audio\n *\n * In **single-route** mode a single `POST {basePath}` endpoint accepts a JSON\n * envelope `{ method, params, body }` and dispatches to the appropriate handler.\n *\n * @example\n * ```typescript\n * import express from \"express\";\n * import { CopilotRuntime } from \"@copilotkit/runtime/v2\";\n * import { createCopilotExpressHandler } from \"@copilotkit/runtime/v2/express\";\n *\n * const runtime = new CopilotRuntime({\n * agents: { default: new BuiltInAgent({ model: \"openai/gpt-4o-mini\" }) },\n * });\n *\n * const app = express();\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * cors: true,\n * }));\n * app.listen(4000);\n * ```\n *\n * @example Single-route mode with lifecycle hooks\n * ```typescript\n * app.use(createCopilotExpressHandler({\n * runtime,\n * basePath: \"/api/copilotkit\",\n * mode: \"single-route\",\n * hooks: {\n * onRequest: ({ request }) => {\n * if (!request.headers.get(\"authorization\")) {\n * throw new Response(\"Unauthorized\", { status: 401 });\n * }\n * },\n * },\n * }));\n * ```\n */\n/** @deprecated Use `createCopilotExpressHandler` instead. */\nexport { createCopilotExpressHandler as createCopilotEndpointExpress };\n\nexport function createCopilotExpressHandler({\n runtime,\n basePath,\n mode = \"multi-route\",\n cors: corsOption = true,\n hooks,\n}: CopilotExpressEndpointParams): Router {\n const normalizedBase = normalizeBasePath(basePath);\n\n const handler = createCopilotRuntimeHandler({\n runtime,\n basePath: normalizedBase,\n mode,\n cors: false, // CORS is handled at the Express middleware layer\n hooks,\n });\n\n const nodeHandler = createExpressNodeHandler(handler);\n\n const expressHandler = async (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) => {\n try {\n await nodeHandler(req, res);\n } catch (err) {\n next(err);\n }\n };\n\n const router = express.Router();\n\n // CORS middleware\n if (corsOption) {\n const corsConfig: CorsOptions =\n corsOption === true\n ? {\n origin: \"*\",\n methods: [\n \"GET\",\n \"HEAD\",\n \"PUT\",\n \"POST\",\n \"DELETE\",\n \"PATCH\",\n \"OPTIONS\",\n ],\n allowedHeaders: [\"*\"],\n }\n : corsOption;\n router.use(cors(corsConfig));\n }\n\n // Route mounting\n if (mode === \"single-route\") {\n router.post(normalizedBase, expressHandler);\n router.options(normalizedBase, expressHandler);\n } else if (normalizedBase === \"/\") {\n router.all(/.*/, expressHandler);\n } else {\n router.all(\n new RegExp(`^${escapeRegExp(normalizedBase)}(\\\\/.*)?$`),\n expressHandler,\n );\n }\n\n return router;\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction normalizeBasePath(path: string): string {\n if (!path) {\n throw new Error(\"basePath must be provided for Express endpoint\");\n }\n\n if (!path.startsWith(\"/\")) {\n return `/${path}`;\n }\n\n if (path.length > 1 && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n}\n"],"mappings":";;;;;;;AA0FA,SAAgB,4BAA4B,EAC1C,SACA,UACA,OAAO,eACP,MAAM,aAAa,MACnB,SACuC;CACvC,MAAM,iBAAiB,kBAAkB,SAAS;CAUlD,MAAM,cAAc,yBARJ,4BAA4B;EAC1C;EACA,UAAU;EACV;EACA,MAAM;EACN;EACD,CAAC,CAEmD;CAErD,MAAM,iBAAiB,OACrB,KACA,KACA,SACG;AACH,MAAI;AACF,SAAM,YAAY,KAAK,IAAI;WACpB,KAAK;AACZ,QAAK,IAAI;;;CAIb,MAAM,SAAS,QAAQ,QAAQ;AAG/B,KAAI,YAAY;EACd,MAAM,aACJ,eAAe,OACX;GACE,QAAQ;GACR,SAAS;IACP;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACD,gBAAgB,CAAC,IAAI;GACtB,GACD;AACN,SAAO,IAAI,KAAK,WAAW,CAAC;;AAI9B,KAAI,SAAS,gBAAgB;AAC3B,SAAO,KAAK,gBAAgB,eAAe;AAC3C,SAAO,QAAQ,gBAAgB,eAAe;YACrC,mBAAmB,IAC5B,QAAO,IAAI,MAAM,eAAe;KAEhC,QAAO,IACL,IAAI,OAAO,IAAI,aAAa,eAAe,CAAC,WAAW,EACvD,eACD;AAGH,QAAO;;AAGT,SAAS,aAAa,GAAmB;AACvC,QAAO,EAAE,QAAQ,uBAAuB,OAAO;;AAGjD,SAAS,kBAAkB,MAAsB;AAC/C,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,iDAAiD;AAGnE,KAAI,CAAC,KAAK,WAAW,IAAI,CACvB,QAAO,IAAI;AAGb,KAAI,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI,CACvC,QAAO,KAAK,MAAM,GAAG,GAAG;AAG1B,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/runtime",
|
|
3
|
-
"version": "1.56.
|
|
3
|
+
"version": "1.56.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@ag-ui/a2ui-middleware": "0.0.5",
|
|
80
|
-
"@ag-ui/client": "0.0.
|
|
81
|
-
"@ag-ui/core": "0.0.
|
|
82
|
-
"@ag-ui/encoder": "0.0.
|
|
83
|
-
"@ag-ui/langgraph": "0.0.
|
|
80
|
+
"@ag-ui/client": "0.0.53",
|
|
81
|
+
"@ag-ui/core": "0.0.53",
|
|
82
|
+
"@ag-ui/encoder": "0.0.53",
|
|
83
|
+
"@ag-ui/langgraph": "0.0.31",
|
|
84
84
|
"@ag-ui/mcp-apps-middleware": "0.0.3",
|
|
85
85
|
"@ai-sdk/anthropic": "^3.0.49",
|
|
86
86
|
"@ai-sdk/google": "^3.0.33",
|
|
@@ -115,10 +115,10 @@
|
|
|
115
115
|
"uuid": "^10.0.0",
|
|
116
116
|
"ws": "^8.18.0",
|
|
117
117
|
"zod": "^3.23.3",
|
|
118
|
-
"@copilotkit/shared": "1.56.
|
|
118
|
+
"@copilotkit/shared": "1.56.5"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
|
-
"@copilotkit/aimock": "
|
|
121
|
+
"@copilotkit/aimock": "latest",
|
|
122
122
|
"@swc/core": "1.5.28",
|
|
123
123
|
"@types/cors": "^2.8.17",
|
|
124
124
|
"@types/express": "^4.17.21",
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* BuiltInAgent-specific factories, mock stream builders, and assertion helpers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { EventType
|
|
8
|
+
import { EventType } from "@ag-ui/client";
|
|
9
|
+
import type { BaseEvent, RunAgentInput } from "@ag-ui/client";
|
|
9
10
|
import type { Observable } from "rxjs";
|
|
10
11
|
import { BuiltInAgent } from "../index";
|
|
11
12
|
import type { AgentFactoryContext, BuiltInAgentFactoryConfig } from "../index";
|
|
@@ -85,6 +86,35 @@ export function tanstackToolCallEnd(toolCallId: string) {
|
|
|
85
86
|
return { type: "TOOL_CALL_END", toolCallId } as const;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/** TanStack tool-call result chunk. `content` is what the tool's `execute()` returned. */
|
|
90
|
+
export function tanstackToolCallResult(toolCallId: string, content: unknown) {
|
|
91
|
+
return { type: "TOOL_CALL_RESULT", toolCallId, content } as const;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** TanStack reasoning lifecycle chunk builders */
|
|
95
|
+
export function tanstackReasoningStart(messageId: string) {
|
|
96
|
+
return { type: "REASONING_START", messageId } as const;
|
|
97
|
+
}
|
|
98
|
+
export function tanstackReasoningMessageStart(messageId: string) {
|
|
99
|
+
return {
|
|
100
|
+
type: "REASONING_MESSAGE_START",
|
|
101
|
+
messageId,
|
|
102
|
+
role: "reasoning",
|
|
103
|
+
} as const;
|
|
104
|
+
}
|
|
105
|
+
export function tanstackReasoningMessageContent(
|
|
106
|
+
messageId: string,
|
|
107
|
+
delta: string,
|
|
108
|
+
) {
|
|
109
|
+
return { type: "REASONING_MESSAGE_CONTENT", messageId, delta } as const;
|
|
110
|
+
}
|
|
111
|
+
export function tanstackReasoningMessageEnd(messageId: string) {
|
|
112
|
+
return { type: "REASONING_MESSAGE_END", messageId } as const;
|
|
113
|
+
}
|
|
114
|
+
export function tanstackReasoningEnd(messageId: string) {
|
|
115
|
+
return { type: "REASONING_END", messageId } as const;
|
|
116
|
+
}
|
|
117
|
+
|
|
88
118
|
// ---------------------------------------------------------------------------
|
|
89
119
|
// Mock async iterable builders
|
|
90
120
|
// ---------------------------------------------------------------------------
|
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
tanstackToolCallStart,
|
|
12
12
|
tanstackToolCallArgs,
|
|
13
13
|
tanstackToolCallEnd,
|
|
14
|
+
tanstackToolCallResult,
|
|
15
|
+
tanstackReasoningStart,
|
|
16
|
+
tanstackReasoningMessageStart,
|
|
17
|
+
tanstackReasoningMessageContent,
|
|
18
|
+
tanstackReasoningMessageEnd,
|
|
19
|
+
tanstackReasoningEnd,
|
|
14
20
|
} from "./agent-test-helpers";
|
|
15
21
|
|
|
16
22
|
describe("TanStack AI converter (via Agent)", () => {
|
|
@@ -312,3 +318,277 @@ describe("TanStack AI converter (via Agent)", () => {
|
|
|
312
318
|
});
|
|
313
319
|
});
|
|
314
320
|
});
|
|
321
|
+
|
|
322
|
+
describe("TanStack AI converter — state tools", () => {
|
|
323
|
+
it("emits STATE_SNAPSHOT before TOOL_CALL_RESULT for AGUISendStateSnapshot", async () => {
|
|
324
|
+
const snapshot = { counter: 5, items: ["x", "y"] };
|
|
325
|
+
const agent = createAgent("tanstack", [
|
|
326
|
+
tanstackToolCallStart("call1", "AGUISendStateSnapshot"),
|
|
327
|
+
tanstackToolCallEnd("call1"),
|
|
328
|
+
tanstackToolCallResult("call1", { success: true, snapshot }),
|
|
329
|
+
]);
|
|
330
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
331
|
+
|
|
332
|
+
expectLifecycleWrapped(events);
|
|
333
|
+
|
|
334
|
+
const snapshotIdx = events.findIndex(
|
|
335
|
+
(e) => e.type === EventType.STATE_SNAPSHOT,
|
|
336
|
+
);
|
|
337
|
+
const resultIdx = events.findIndex(
|
|
338
|
+
(e) => e.type === EventType.TOOL_CALL_RESULT,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(snapshotIdx).toBeGreaterThanOrEqual(0);
|
|
342
|
+
expect(resultIdx).toBeGreaterThanOrEqual(0);
|
|
343
|
+
expect(snapshotIdx).toBeLessThan(resultIdx);
|
|
344
|
+
expect(eventField<unknown>(events[snapshotIdx], "snapshot")).toEqual(
|
|
345
|
+
snapshot,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("emits STATE_DELTA before TOOL_CALL_RESULT for AGUISendStateDelta", async () => {
|
|
350
|
+
const delta = [{ op: "replace", path: "/counter", value: 7 }];
|
|
351
|
+
const agent = createAgent("tanstack", [
|
|
352
|
+
tanstackToolCallStart("call1", "AGUISendStateDelta"),
|
|
353
|
+
tanstackToolCallEnd("call1"),
|
|
354
|
+
tanstackToolCallResult("call1", { success: true, delta }),
|
|
355
|
+
]);
|
|
356
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
357
|
+
|
|
358
|
+
expectLifecycleWrapped(events);
|
|
359
|
+
|
|
360
|
+
const deltaIdx = events.findIndex((e) => e.type === EventType.STATE_DELTA);
|
|
361
|
+
const resultIdx = events.findIndex(
|
|
362
|
+
(e) => e.type === EventType.TOOL_CALL_RESULT,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
expect(deltaIdx).toBeGreaterThanOrEqual(0);
|
|
366
|
+
expect(deltaIdx).toBeLessThan(resultIdx);
|
|
367
|
+
expect(eventField<unknown>(events[deltaIdx], "delta")).toEqual(delta);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("emits STATE_SNAPSHOT when payload arrives in raw.result instead of raw.content", async () => {
|
|
371
|
+
// Regression: serialization fell back to raw.result (`?? raw.result ?? null`)
|
|
372
|
+
// but state-tool detection only inspected raw.content, so STATE_* events
|
|
373
|
+
// were silently dropped if upstream used `result` for the state-tool body.
|
|
374
|
+
// See the "TOOL_CALL_RESULT with object content serializes to JSON" test
|
|
375
|
+
// above which already exercises the `result` field on a non-state tool.
|
|
376
|
+
const snapshot = { counter: 99 };
|
|
377
|
+
const agent = createAgent("tanstack", [
|
|
378
|
+
tanstackToolCallStart("call1", "AGUISendStateSnapshot"),
|
|
379
|
+
tanstackToolCallEnd("call1"),
|
|
380
|
+
{
|
|
381
|
+
type: "TOOL_CALL_RESULT",
|
|
382
|
+
toolCallId: "call1",
|
|
383
|
+
result: { success: true, snapshot },
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
387
|
+
|
|
388
|
+
expectLifecycleWrapped(events);
|
|
389
|
+
|
|
390
|
+
const snapshotIdx = events.findIndex(
|
|
391
|
+
(e) => e.type === EventType.STATE_SNAPSHOT,
|
|
392
|
+
);
|
|
393
|
+
expect(snapshotIdx).toBeGreaterThanOrEqual(0);
|
|
394
|
+
expect(eventField<unknown>(events[snapshotIdx], "snapshot")).toEqual(
|
|
395
|
+
snapshot,
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("does NOT emit STATE_* for non-state tool results", async () => {
|
|
400
|
+
const agent = createAgent("tanstack", [
|
|
401
|
+
tanstackToolCallStart("call1", "getWeather"),
|
|
402
|
+
tanstackToolCallEnd("call1"),
|
|
403
|
+
tanstackToolCallResult("call1", {
|
|
404
|
+
snapshot: { spoofed: true },
|
|
405
|
+
delta: [{ op: "x" }],
|
|
406
|
+
}),
|
|
407
|
+
]);
|
|
408
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
409
|
+
|
|
410
|
+
expectLifecycleWrapped(events);
|
|
411
|
+
|
|
412
|
+
expect(
|
|
413
|
+
events.find(
|
|
414
|
+
(e) =>
|
|
415
|
+
e.type === EventType.STATE_SNAPSHOT ||
|
|
416
|
+
e.type === EventType.STATE_DELTA,
|
|
417
|
+
),
|
|
418
|
+
).toBeUndefined();
|
|
419
|
+
expect(
|
|
420
|
+
events.find((e) => e.type === EventType.TOOL_CALL_RESULT),
|
|
421
|
+
).toBeDefined();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe("TanStack AI converter — reasoning", () => {
|
|
426
|
+
it("emits the full REASONING lifecycle for reasoning chunks", async () => {
|
|
427
|
+
const agent = createAgent("tanstack", [
|
|
428
|
+
tanstackReasoningStart("r1"),
|
|
429
|
+
tanstackReasoningMessageStart("r1"),
|
|
430
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
431
|
+
tanstackReasoningMessageEnd("r1"),
|
|
432
|
+
tanstackReasoningEnd("r1"),
|
|
433
|
+
]);
|
|
434
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
435
|
+
|
|
436
|
+
expectLifecycleWrapped(events);
|
|
437
|
+
|
|
438
|
+
// Strip the lifecycle wrap and inspect the inner sequence.
|
|
439
|
+
const inner = events.slice(1, -1).map((e) => e.type);
|
|
440
|
+
expect(inner).toEqual([
|
|
441
|
+
EventType.REASONING_START,
|
|
442
|
+
EventType.REASONING_MESSAGE_START,
|
|
443
|
+
EventType.REASONING_MESSAGE_CONTENT,
|
|
444
|
+
EventType.REASONING_MESSAGE_END,
|
|
445
|
+
EventType.REASONING_END,
|
|
446
|
+
]);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("auto-closes an open reasoning lifecycle when text starts", async () => {
|
|
450
|
+
const agent = createAgent("tanstack", [
|
|
451
|
+
tanstackReasoningStart("r1"),
|
|
452
|
+
tanstackReasoningMessageStart("r1"),
|
|
453
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
454
|
+
// No REASONING_MESSAGE_END / REASONING_END before text
|
|
455
|
+
tanstackTextChunk("Hi"),
|
|
456
|
+
]);
|
|
457
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
458
|
+
const types = events.map((e) => e.type);
|
|
459
|
+
|
|
460
|
+
const reasonEndIdx = types.indexOf(EventType.REASONING_END);
|
|
461
|
+
const reasonMsgEndIdx = types.indexOf(EventType.REASONING_MESSAGE_END);
|
|
462
|
+
const textIdx = types.indexOf(EventType.TEXT_MESSAGE_CHUNK);
|
|
463
|
+
|
|
464
|
+
expect(reasonMsgEndIdx).toBeGreaterThan(-1);
|
|
465
|
+
expect(reasonEndIdx).toBeGreaterThan(-1);
|
|
466
|
+
expect(reasonEndIdx).toBeLessThan(textIdx);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("auto-closes an open reasoning lifecycle when a tool call starts", async () => {
|
|
470
|
+
const agent = createAgent("tanstack", [
|
|
471
|
+
tanstackReasoningStart("r1"),
|
|
472
|
+
tanstackReasoningMessageStart("r1"),
|
|
473
|
+
tanstackReasoningMessageContent("r1", "..."),
|
|
474
|
+
tanstackToolCallStart("t1", "x"),
|
|
475
|
+
]);
|
|
476
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
477
|
+
const types = events.map((e) => e.type);
|
|
478
|
+
|
|
479
|
+
expect(types).toContain(EventType.REASONING_MESSAGE_END);
|
|
480
|
+
expect(types).toContain(EventType.REASONING_END);
|
|
481
|
+
expect(types.indexOf(EventType.REASONING_END)).toBeLessThan(
|
|
482
|
+
types.indexOf(EventType.TOOL_CALL_START),
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("auto-closes when the stream ends without explicit reasoning end", async () => {
|
|
487
|
+
const agent = createAgent("tanstack", [
|
|
488
|
+
tanstackReasoningStart("r1"),
|
|
489
|
+
tanstackReasoningMessageStart("r1"),
|
|
490
|
+
tanstackReasoningMessageContent("r1", "x"),
|
|
491
|
+
]);
|
|
492
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
493
|
+
const types = events.map((e) => e.type);
|
|
494
|
+
|
|
495
|
+
expect(types).toContain(EventType.REASONING_MESSAGE_END);
|
|
496
|
+
expect(types).toContain(EventType.REASONING_END);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("emits REASONING_MESSAGE_END before REASONING_END when upstream sends END with message still open", async () => {
|
|
500
|
+
// Regression: if the converter received REASONING_END while a message
|
|
501
|
+
// was still open, it cleared run-open and emitted END only — leaving
|
|
502
|
+
// message-open true. The next non-reasoning chunk then triggered
|
|
503
|
+
// closeReasoningIfOpen, which emitted MSG_END AFTER END (wrong order).
|
|
504
|
+
// Fix: REASONING_END handler closes any open message first.
|
|
505
|
+
const agent = createAgent("tanstack", [
|
|
506
|
+
tanstackReasoningStart("r1"),
|
|
507
|
+
tanstackReasoningMessageStart("r1"),
|
|
508
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
509
|
+
// Upstream skips MSG_END and goes straight to END
|
|
510
|
+
tanstackReasoningEnd("r1"),
|
|
511
|
+
tanstackTextChunk("Hi"),
|
|
512
|
+
]);
|
|
513
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
514
|
+
const types = events.map((e) => e.type);
|
|
515
|
+
|
|
516
|
+
const msgEndIdx = types.indexOf(EventType.REASONING_MESSAGE_END);
|
|
517
|
+
const endIdx = types.indexOf(EventType.REASONING_END);
|
|
518
|
+
|
|
519
|
+
expect(msgEndIdx).toBeGreaterThan(-1);
|
|
520
|
+
expect(endIdx).toBeGreaterThan(-1);
|
|
521
|
+
expect(msgEndIdx).toBeLessThan(endIdx);
|
|
522
|
+
// No duplicate MSG_END or END from auto-close on the text chunk
|
|
523
|
+
expect(
|
|
524
|
+
types.filter((t) => t === EventType.REASONING_MESSAGE_END),
|
|
525
|
+
).toHaveLength(1);
|
|
526
|
+
expect(types.filter((t) => t === EventType.REASONING_END)).toHaveLength(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("auto-closes prior reasoning run when a new REASONING_START arrives without END", async () => {
|
|
530
|
+
// Regression: REASONING_START used to overwrite reasoningMessageId
|
|
531
|
+
// unconditionally, orphaning the prior run's MSG_END / END.
|
|
532
|
+
// Fix: REASONING_START handler calls closeReasoningIfOpen() first.
|
|
533
|
+
const agent = createAgent("tanstack", [
|
|
534
|
+
tanstackReasoningStart("r1"),
|
|
535
|
+
tanstackReasoningMessageStart("r1"),
|
|
536
|
+
tanstackReasoningMessageContent("r1", "first"),
|
|
537
|
+
// Second START with no intervening END
|
|
538
|
+
tanstackReasoningStart("r2"),
|
|
539
|
+
tanstackReasoningMessageStart("r2"),
|
|
540
|
+
tanstackReasoningMessageContent("r2", "second"),
|
|
541
|
+
tanstackReasoningMessageEnd("r2"),
|
|
542
|
+
tanstackReasoningEnd("r2"),
|
|
543
|
+
]);
|
|
544
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
545
|
+
const types = events.map((e) => e.type);
|
|
546
|
+
|
|
547
|
+
// Two complete START → MSG_START → ... → MSG_END → END sequences
|
|
548
|
+
expect(types.filter((t) => t === EventType.REASONING_START)).toHaveLength(
|
|
549
|
+
2,
|
|
550
|
+
);
|
|
551
|
+
expect(types.filter((t) => t === EventType.REASONING_END)).toHaveLength(2);
|
|
552
|
+
expect(
|
|
553
|
+
types.filter((t) => t === EventType.REASONING_MESSAGE_END),
|
|
554
|
+
).toHaveLength(2);
|
|
555
|
+
|
|
556
|
+
// First START's prior message gets closed BEFORE the second START
|
|
557
|
+
const firstEndIdx = types.indexOf(EventType.REASONING_END);
|
|
558
|
+
const secondStartIdx = types.indexOf(
|
|
559
|
+
EventType.REASONING_START,
|
|
560
|
+
firstEndIdx + 1,
|
|
561
|
+
);
|
|
562
|
+
expect(firstEndIdx).toBeLessThan(secondStartIdx);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("does NOT duplicate REASONING_MESSAGE_END when upstream emits it explicitly before text", async () => {
|
|
566
|
+
// Regression: a single isInReasoning flag conflated message-open with
|
|
567
|
+
// run-open, so closeReasoningIfOpen on TEXT_MESSAGE_CONTENT emitted a
|
|
568
|
+
// second REASONING_MESSAGE_END after upstream's own. Track message-open
|
|
569
|
+
// and run-open separately so closeReasoningIfOpen owes only what's still
|
|
570
|
+
// open.
|
|
571
|
+
const agent = createAgent("tanstack", [
|
|
572
|
+
tanstackReasoningStart("r1"),
|
|
573
|
+
tanstackReasoningMessageStart("r1"),
|
|
574
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
575
|
+
tanstackReasoningMessageEnd("r1"),
|
|
576
|
+
// No explicit REASONING_END before text — closeReasoningIfOpen should
|
|
577
|
+
// emit REASONING_END but NOT a second REASONING_MESSAGE_END.
|
|
578
|
+
tanstackTextChunk("Hi"),
|
|
579
|
+
]);
|
|
580
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
581
|
+
const types = events.map((e) => e.type);
|
|
582
|
+
|
|
583
|
+
const msgEndCount = types.filter(
|
|
584
|
+
(t) => t === EventType.REASONING_MESSAGE_END,
|
|
585
|
+
).length;
|
|
586
|
+
const endCount = types.filter((t) => t === EventType.REASONING_END).length;
|
|
587
|
+
|
|
588
|
+
expect(msgEndCount).toBe(1);
|
|
589
|
+
expect(endCount).toBe(1);
|
|
590
|
+
expect(types.indexOf(EventType.REASONING_END)).toBeLessThan(
|
|
591
|
+
types.indexOf(EventType.TEXT_MESSAGE_CHUNK),
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
});
|