assistant-stream 0.2.39 → 0.2.41

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.
@@ -1 +1 @@
1
- {"version":3,"file":"toolResultStream.d.ts","sourceRoot":"","sources":["../../../src/core/tool/toolResultStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuC,MAAM,cAAc,CAAC;AAGzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2ElD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,EACvC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,6BAyClE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EACD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GACpB,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,GACxC,SAAS,EACb,WAAW,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,EAC9C,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,uBAWlE"}
1
+ {"version":3,"file":"toolResultStream.d.ts","sourceRoot":"","sources":["../../../src/core/tool/toolResultStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuC,MAAM,cAAc,CAAC;AAGzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2ElD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,EACvC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,6BAiElE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EACD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GACpB,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,GACxC,SAAS,EACb,WAAW,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,EAC9C,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,uBAWlE"}
@@ -37,41 +37,59 @@ function getToolStreamResponse(tools, abortSignal, reader, context, human) {
37
37
  });
38
38
  }
39
39
  async function unstable_runPendingTools(message, tools, abortSignal, human) {
40
- for (const part of message.parts) {
41
- if (part.type === "tool-call") {
42
- const promiseOrUndefined = getToolResponse(
43
- tools,
44
- abortSignal,
45
- part,
46
- human ?? (async () => {
47
- throw new Error(
48
- "Tool human input is not supported in this context"
49
- );
50
- })
51
- );
52
- if (promiseOrUndefined) {
53
- const result = await promiseOrUndefined;
54
- const updatedParts = message.parts.map((p) => {
55
- if (p.type === "tool-call" && p.toolCallId === part.toolCallId) {
56
- return {
57
- ...p,
58
- state: "result",
59
- ...result.artifact !== void 0 ? { artifact: result.artifact } : {},
60
- result: result.result,
61
- isError: result.isError
62
- };
63
- }
64
- return p;
65
- });
66
- message = {
67
- ...message,
68
- parts: updatedParts,
69
- content: updatedParts
40
+ const toolCallPromises = message.parts.filter((part) => part.type === "tool-call").map(async (part) => {
41
+ const promiseOrUndefined = getToolResponse(
42
+ tools,
43
+ abortSignal,
44
+ part,
45
+ human ?? (async () => {
46
+ throw new Error(
47
+ "Tool human input is not supported in this context"
48
+ );
49
+ })
50
+ );
51
+ if (promiseOrUndefined) {
52
+ const result = await promiseOrUndefined;
53
+ return {
54
+ toolCallId: part.toolCallId,
55
+ result
56
+ };
57
+ }
58
+ return null;
59
+ });
60
+ const toolCallResults = (await Promise.all(toolCallPromises)).filter(
61
+ (result) => result !== null
62
+ );
63
+ if (toolCallResults.length === 0) {
64
+ return message;
65
+ }
66
+ const toolCallResultsById = toolCallResults.reduce(
67
+ (acc, { toolCallId, result }) => {
68
+ acc[toolCallId] = result;
69
+ return acc;
70
+ },
71
+ {}
72
+ );
73
+ const updatedParts = message.parts.map((p) => {
74
+ if (p.type === "tool-call") {
75
+ const toolResponse = toolCallResultsById[p.toolCallId];
76
+ if (toolResponse) {
77
+ return {
78
+ ...p,
79
+ state: "result",
80
+ ...toolResponse.artifact !== void 0 ? { artifact: toolResponse.artifact } : {},
81
+ result: toolResponse.result,
82
+ isError: toolResponse.isError
70
83
  };
71
84
  }
72
85
  }
73
- }
74
- return message;
86
+ return p;
87
+ });
88
+ return {
89
+ ...message,
90
+ parts: updatedParts,
91
+ content: updatedParts
92
+ };
75
93
  }
76
94
  function toolResultStream(tools, abortSignal, human) {
77
95
  const toolsFn = typeof tools === "function" ? tools : () => tools;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/tool/toolResultStream.ts"],"sourcesContent":["import { Tool, ToolCallReader, ToolExecuteFunction } from \"./tool-types\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { ToolResponse } from \"./ToolResponse\";\nimport { ToolExecutionStream } from \"./ToolExecutionStream\";\nimport { AssistantMessage } from \"../utils/types\";\nimport { ReadonlyJSONObject, ReadonlyJSONValue } from \"../../utils\";\n\nconst isStandardSchemaV1 = (\n schema: unknown,\n): schema is StandardSchemaV1<unknown> => {\n return (\n typeof schema === \"object\" &&\n schema !== null &&\n \"~standard\" in schema &&\n (schema as StandardSchemaV1<unknown>)[\"~standard\"].version === 1\n );\n};\n\nfunction getToolResponse(\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n toolCall: {\n toolCallId: string;\n toolName: string;\n args: ReadonlyJSONObject;\n },\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n const tool = tools?.[toolCall.toolName];\n if (!tool || !tool.execute) return undefined;\n\n const getResult = async (\n toolExecute: ToolExecuteFunction<ReadonlyJSONObject, unknown>,\n ): Promise<ToolResponse<ReadonlyJSONValue>> => {\n let executeFn = toolExecute;\n\n if (isStandardSchemaV1(tool.parameters)) {\n let result = tool.parameters[\"~standard\"].validate(toolCall.args);\n if (result instanceof Promise) result = await result;\n\n if (result.issues) {\n executeFn =\n tool.experimental_onSchemaValidationError ??\n (() => {\n throw new Error(\n `Function parameter validation failed. ${JSON.stringify(result.issues)}`,\n );\n });\n }\n }\n\n const result = (await executeFn(toolCall.args, {\n toolCallId: toolCall.toolCallId,\n abortSignal,\n human: (payload: unknown) => human(toolCall.toolCallId, payload),\n })) as unknown as ReadonlyJSONValue;\n return ToolResponse.toResponse(result);\n };\n\n return getResult(tool.execute);\n}\n\nfunction getToolStreamResponse(\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n reader: ToolCallReader<any, ReadonlyJSONValue>,\n context: {\n toolCallId: string;\n toolName: string;\n },\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n tools?.[context.toolName]?.streamCall?.(reader, {\n toolCallId: context.toolCallId,\n abortSignal,\n human: (payload: unknown) => human(context.toolCallId, payload),\n });\n}\n\nexport async function unstable_runPendingTools(\n message: AssistantMessage,\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n // TODO parallel tool calling\n for (const part of message.parts) {\n if (part.type === \"tool-call\") {\n const promiseOrUndefined = getToolResponse(\n tools,\n abortSignal,\n part,\n human ??\n (async () => {\n throw new Error(\n \"Tool human input is not supported in this context\",\n );\n }),\n );\n if (promiseOrUndefined) {\n const result = await promiseOrUndefined;\n const updatedParts = message.parts.map((p) => {\n if (p.type === \"tool-call\" && p.toolCallId === part.toolCallId) {\n return {\n ...p,\n state: \"result\" as const,\n ...(result.artifact !== undefined\n ? { artifact: result.artifact }\n : {}),\n result: result.result as ReadonlyJSONValue,\n isError: result.isError,\n };\n }\n return p;\n });\n message = {\n ...message,\n parts: updatedParts,\n content: updatedParts,\n };\n }\n }\n }\n return message;\n}\n\nexport function toolResultStream(\n tools:\n | Record<string, Tool>\n | (() => Record<string, Tool> | undefined)\n | undefined,\n abortSignal: AbortSignal | (() => AbortSignal),\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n const toolsFn = typeof tools === \"function\" ? tools : () => tools;\n const abortSignalFn =\n typeof abortSignal === \"function\" ? abortSignal : () => abortSignal;\n return new ToolExecutionStream({\n execute: (toolCall) =>\n getToolResponse(toolsFn(), abortSignalFn(), toolCall, human),\n streamCall: ({ reader, ...context }) =>\n getToolStreamResponse(toolsFn(), abortSignalFn(), reader, context, human),\n });\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AAIpC,IAAM,qBAAqB,CACzB,WACwC;AACxC,SACE,OAAO,WAAW,YAClB,WAAW,QACX,eAAe,UACd,OAAqC,WAAW,EAAE,YAAY;AAEnE;AAEA,SAAS,gBACP,OACA,aACA,UAKA,OACA;AACA,QAAM,OAAO,QAAQ,SAAS,QAAQ;AACtC,MAAI,CAAC,QAAQ,CAAC,KAAK,QAAS,QAAO;AAEnC,QAAM,YAAY,OAChB,gBAC6C;AAC7C,QAAI,YAAY;AAEhB,QAAI,mBAAmB,KAAK,UAAU,GAAG;AACvC,UAAIA,UAAS,KAAK,WAAW,WAAW,EAAE,SAAS,SAAS,IAAI;AAChE,UAAIA,mBAAkB,QAAS,CAAAA,UAAS,MAAMA;AAE9C,UAAIA,QAAO,QAAQ;AACjB,oBACE,KAAK,yCACJ,MAAM;AACL,gBAAM,IAAI;AAAA,YACR,yCAAyC,KAAK,UAAUA,QAAO,MAAM,CAAC;AAAA,UACxE;AAAA,QACF;AAAA,MACJ;AAAA,IACF;AAEA,UAAM,SAAU,MAAM,UAAU,SAAS,MAAM;AAAA,MAC7C,YAAY,SAAS;AAAA,MACrB;AAAA,MACA,OAAO,CAAC,YAAqB,MAAM,SAAS,YAAY,OAAO;AAAA,IACjE,CAAC;AACD,WAAO,aAAa,WAAW,MAAM;AAAA,EACvC;AAEA,SAAO,UAAU,KAAK,OAAO;AAC/B;AAEA,SAAS,sBACP,OACA,aACA,QACA,SAIA,OACA;AACA,UAAQ,QAAQ,QAAQ,GAAG,aAAa,QAAQ;AAAA,IAC9C,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,OAAO,CAAC,YAAqB,MAAM,QAAQ,YAAY,OAAO;AAAA,EAChE,CAAC;AACH;AAEA,eAAsB,yBACpB,SACA,OACA,aACA,OACA;AAEA,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,aAAa;AAC7B,YAAM,qBAAqB;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA,UACG,YAAY;AACX,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACJ;AACA,UAAI,oBAAoB;AACtB,cAAM,SAAS,MAAM;AACrB,cAAM,eAAe,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC5C,cAAI,EAAE,SAAS,eAAe,EAAE,eAAe,KAAK,YAAY;AAC9D,mBAAO;AAAA,cACL,GAAG;AAAA,cACH,OAAO;AAAA,cACP,GAAI,OAAO,aAAa,SACpB,EAAE,UAAU,OAAO,SAAS,IAC5B,CAAC;AAAA,cACL,QAAQ,OAAO;AAAA,cACf,SAAS,OAAO;AAAA,YAClB;AAAA,UACF;AACA,iBAAO;AAAA,QACT,CAAC;AACD,kBAAU;AAAA,UACR,GAAG;AAAA,UACH,OAAO;AAAA,UACP,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iBACd,OAIA,aACA,OACA;AACA,QAAM,UAAU,OAAO,UAAU,aAAa,QAAQ,MAAM;AAC5D,QAAM,gBACJ,OAAO,gBAAgB,aAAa,cAAc,MAAM;AAC1D,SAAO,IAAI,oBAAoB;AAAA,IAC7B,SAAS,CAAC,aACR,gBAAgB,QAAQ,GAAG,cAAc,GAAG,UAAU,KAAK;AAAA,IAC7D,YAAY,CAAC,EAAE,QAAQ,GAAG,QAAQ,MAChC,sBAAsB,QAAQ,GAAG,cAAc,GAAG,QAAQ,SAAS,KAAK;AAAA,EAC5E,CAAC;AACH;","names":["result"]}
1
+ {"version":3,"sources":["../../../src/core/tool/toolResultStream.ts"],"sourcesContent":["import { Tool, ToolCallReader, ToolExecuteFunction } from \"./tool-types\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { ToolResponse } from \"./ToolResponse\";\nimport { ToolExecutionStream } from \"./ToolExecutionStream\";\nimport { AssistantMessage } from \"../utils/types\";\nimport { ReadonlyJSONObject, ReadonlyJSONValue } from \"../../utils\";\n\nconst isStandardSchemaV1 = (\n schema: unknown,\n): schema is StandardSchemaV1<unknown> => {\n return (\n typeof schema === \"object\" &&\n schema !== null &&\n \"~standard\" in schema &&\n (schema as StandardSchemaV1<unknown>)[\"~standard\"].version === 1\n );\n};\n\nfunction getToolResponse(\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n toolCall: {\n toolCallId: string;\n toolName: string;\n args: ReadonlyJSONObject;\n },\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n const tool = tools?.[toolCall.toolName];\n if (!tool || !tool.execute) return undefined;\n\n const getResult = async (\n toolExecute: ToolExecuteFunction<ReadonlyJSONObject, unknown>,\n ): Promise<ToolResponse<ReadonlyJSONValue>> => {\n let executeFn = toolExecute;\n\n if (isStandardSchemaV1(tool.parameters)) {\n let result = tool.parameters[\"~standard\"].validate(toolCall.args);\n if (result instanceof Promise) result = await result;\n\n if (result.issues) {\n executeFn =\n tool.experimental_onSchemaValidationError ??\n (() => {\n throw new Error(\n `Function parameter validation failed. ${JSON.stringify(result.issues)}`,\n );\n });\n }\n }\n\n const result = (await executeFn(toolCall.args, {\n toolCallId: toolCall.toolCallId,\n abortSignal,\n human: (payload: unknown) => human(toolCall.toolCallId, payload),\n })) as unknown as ReadonlyJSONValue;\n return ToolResponse.toResponse(result);\n };\n\n return getResult(tool.execute);\n}\n\nfunction getToolStreamResponse(\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n reader: ToolCallReader<any, ReadonlyJSONValue>,\n context: {\n toolCallId: string;\n toolName: string;\n },\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n tools?.[context.toolName]?.streamCall?.(reader, {\n toolCallId: context.toolCallId,\n abortSignal,\n human: (payload: unknown) => human(context.toolCallId, payload),\n });\n}\n\nexport async function unstable_runPendingTools(\n message: AssistantMessage,\n tools: Record<string, Tool> | undefined,\n abortSignal: AbortSignal,\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n const toolCallPromises = message.parts\n .filter((part) => part.type === \"tool-call\")\n .map(async (part) => {\n const promiseOrUndefined = getToolResponse(\n tools,\n abortSignal,\n part,\n human ??\n (async () => {\n throw new Error(\n \"Tool human input is not supported in this context\",\n );\n }),\n );\n if (promiseOrUndefined) {\n const result = await promiseOrUndefined;\n return {\n toolCallId: part.toolCallId,\n result,\n };\n }\n return null;\n });\n\n const toolCallResults = (await Promise.all(toolCallPromises)).filter(\n (result) => result !== null,\n ) as { toolCallId: string; result: ToolResponse<ReadonlyJSONValue> }[];\n\n if (toolCallResults.length === 0) {\n return message;\n }\n\n const toolCallResultsById = toolCallResults.reduce(\n (acc, { toolCallId, result }) => {\n acc[toolCallId] = result;\n return acc;\n },\n {} as Record<string, ToolResponse<ReadonlyJSONValue>>,\n );\n\n const updatedParts = message.parts.map((p) => {\n if (p.type === \"tool-call\") {\n const toolResponse = toolCallResultsById[p.toolCallId];\n if (toolResponse) {\n return {\n ...p,\n state: \"result\" as const,\n ...(toolResponse.artifact !== undefined\n ? { artifact: toolResponse.artifact }\n : {}),\n result: toolResponse.result as ReadonlyJSONValue,\n isError: toolResponse.isError,\n };\n }\n }\n return p;\n });\n\n return {\n ...message,\n parts: updatedParts,\n content: updatedParts,\n };\n}\n\nexport function toolResultStream(\n tools:\n | Record<string, Tool>\n | (() => Record<string, Tool> | undefined)\n | undefined,\n abortSignal: AbortSignal | (() => AbortSignal),\n human: (toolCallId: string, payload: unknown) => Promise<unknown>,\n) {\n const toolsFn = typeof tools === \"function\" ? tools : () => tools;\n const abortSignalFn =\n typeof abortSignal === \"function\" ? abortSignal : () => abortSignal;\n return new ToolExecutionStream({\n execute: (toolCall) =>\n getToolResponse(toolsFn(), abortSignalFn(), toolCall, human),\n streamCall: ({ reader, ...context }) =>\n getToolStreamResponse(toolsFn(), abortSignalFn(), reader, context, human),\n });\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AAIpC,IAAM,qBAAqB,CACzB,WACwC;AACxC,SACE,OAAO,WAAW,YAClB,WAAW,QACX,eAAe,UACd,OAAqC,WAAW,EAAE,YAAY;AAEnE;AAEA,SAAS,gBACP,OACA,aACA,UAKA,OACA;AACA,QAAM,OAAO,QAAQ,SAAS,QAAQ;AACtC,MAAI,CAAC,QAAQ,CAAC,KAAK,QAAS,QAAO;AAEnC,QAAM,YAAY,OAChB,gBAC6C;AAC7C,QAAI,YAAY;AAEhB,QAAI,mBAAmB,KAAK,UAAU,GAAG;AACvC,UAAIA,UAAS,KAAK,WAAW,WAAW,EAAE,SAAS,SAAS,IAAI;AAChE,UAAIA,mBAAkB,QAAS,CAAAA,UAAS,MAAMA;AAE9C,UAAIA,QAAO,QAAQ;AACjB,oBACE,KAAK,yCACJ,MAAM;AACL,gBAAM,IAAI;AAAA,YACR,yCAAyC,KAAK,UAAUA,QAAO,MAAM,CAAC;AAAA,UACxE;AAAA,QACF;AAAA,MACJ;AAAA,IACF;AAEA,UAAM,SAAU,MAAM,UAAU,SAAS,MAAM;AAAA,MAC7C,YAAY,SAAS;AAAA,MACrB;AAAA,MACA,OAAO,CAAC,YAAqB,MAAM,SAAS,YAAY,OAAO;AAAA,IACjE,CAAC;AACD,WAAO,aAAa,WAAW,MAAM;AAAA,EACvC;AAEA,SAAO,UAAU,KAAK,OAAO;AAC/B;AAEA,SAAS,sBACP,OACA,aACA,QACA,SAIA,OACA;AACA,UAAQ,QAAQ,QAAQ,GAAG,aAAa,QAAQ;AAAA,IAC9C,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,OAAO,CAAC,YAAqB,MAAM,QAAQ,YAAY,OAAO;AAAA,EAChE,CAAC;AACH;AAEA,eAAsB,yBACpB,SACA,OACA,aACA,OACA;AACA,QAAM,mBAAmB,QAAQ,MAC9B,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW,EAC1C,IAAI,OAAO,SAAS;AACnB,UAAM,qBAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UACG,YAAY;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACJ;AACA,QAAI,oBAAoB;AACtB,YAAM,SAAS,MAAM;AACrB,aAAO;AAAA,QACL,YAAY,KAAK;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AAEH,QAAM,mBAAmB,MAAM,QAAQ,IAAI,gBAAgB,GAAG;AAAA,IAC5D,CAAC,WAAW,WAAW;AAAA,EACzB;AAEA,MAAI,gBAAgB,WAAW,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,sBAAsB,gBAAgB;AAAA,IAC1C,CAAC,KAAK,EAAE,YAAY,OAAO,MAAM;AAC/B,UAAI,UAAU,IAAI;AAClB,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC5C,QAAI,EAAE,SAAS,aAAa;AAC1B,YAAM,eAAe,oBAAoB,EAAE,UAAU;AACrD,UAAI,cAAc;AAChB,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO;AAAA,UACP,GAAI,aAAa,aAAa,SAC1B,EAAE,UAAU,aAAa,SAAS,IAClC,CAAC;AAAA,UACL,QAAQ,aAAa;AAAA,UACrB,SAAS,aAAa;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO;AAAA,IACP,SAAS;AAAA,EACX;AACF;AAEO,SAAS,iBACd,OAIA,aACA,OACA;AACA,QAAM,UAAU,OAAO,UAAU,aAAa,QAAQ,MAAM;AAC5D,QAAM,gBACJ,OAAO,gBAAgB,aAAa,cAAc,MAAM;AAC1D,SAAO,IAAI,oBAAoB;AAAA,IAC7B,SAAS,CAAC,aACR,gBAAgB,QAAQ,GAAG,cAAc,GAAG,UAAU,KAAK;AAAA,IAC7D,YAAY,CAAC,EAAE,QAAQ,GAAG,QAAQ,MAChC,sBAAsB,QAAQ,GAAG,cAAc,GAAG,QAAQ,SAAS,KAAK;AAAA,EAC5E,CAAC;AACH;","names":["result"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=toolResultStream.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolResultStream.test.d.ts","sourceRoot":"","sources":["../../../src/core/tool/toolResultStream.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistant-stream",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,9 +25,9 @@
25
25
  "sideEffects": false,
26
26
  "devDependencies": {
27
27
  "@standard-schema/spec": "^1.0.0",
28
- "@types/node": "^24.10.0",
28
+ "@types/node": "^24.10.1",
29
29
  "tsx": "^4.20.6",
30
- "vitest": "^4.0.6",
30
+ "vitest": "^4.0.10",
31
31
  "@assistant-ui/x-buildutils": "0.0.1"
32
32
  },
33
33
  "publishConfig": {
@@ -0,0 +1,400 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { unstable_runPendingTools } from "./toolResultStream";
3
+ import { AssistantMessage, ToolCallPart } from "../utils/types";
4
+ import { Tool } from "./tool-types";
5
+
6
+ const createDelayedTool = (delay: number, result?: string): Tool => ({
7
+ parameters: { type: "object", properties: {} },
8
+ execute: async () => {
9
+ await new Promise((resolve) => setTimeout(resolve, delay));
10
+ return result ?? `Tool with ${delay}ms delay executed`;
11
+ },
12
+ });
13
+
14
+ describe("unstable_runPendingTools", () => {
15
+ describe("parallel execution", () => {
16
+ it("should run tool calls in parallel", async () => {
17
+ const tool1 = createDelayedTool(100, "Tool 1");
18
+ const tool2 = createDelayedTool(100, "Tool 2");
19
+ const tool3 = createDelayedTool(100, "Tool 3");
20
+
21
+ const tools: Record<string, Tool> = {
22
+ tool1,
23
+ tool2,
24
+ tool3,
25
+ };
26
+
27
+ const message: AssistantMessage = {
28
+ role: "assistant",
29
+ status: {
30
+ type: "requires-action",
31
+ reason: "tool-calls",
32
+ },
33
+ parts: [
34
+ {
35
+ type: "tool-call",
36
+ toolCallId: "1",
37
+ toolName: "tool1",
38
+ args: {},
39
+ } as ToolCallPart,
40
+ {
41
+ type: "tool-call",
42
+ toolCallId: "2",
43
+ toolName: "tool2",
44
+ args: {},
45
+ } as ToolCallPart,
46
+ {
47
+ type: "tool-call",
48
+ toolCallId: "3",
49
+ toolName: "tool3",
50
+ args: {},
51
+ } as ToolCallPart,
52
+ ],
53
+ content: [],
54
+ metadata: {
55
+ unstable_state: {},
56
+ unstable_data: [],
57
+ unstable_annotations: [],
58
+ steps: [],
59
+ custom: {},
60
+ },
61
+ };
62
+
63
+ const startTime = Date.now();
64
+ const updatedMessage = await unstable_runPendingTools(
65
+ message,
66
+ tools,
67
+ new AbortController().signal,
68
+ async () => {},
69
+ );
70
+ const endTime = Date.now();
71
+
72
+ const executionTime = endTime - startTime;
73
+
74
+ expect(executionTime).toBeGreaterThanOrEqual(100);
75
+ // The execution time should be less than the sum of the delays of both tools.
76
+ expect(executionTime).toBeLessThan(300);
77
+
78
+ expect(updatedMessage.parts).toHaveLength(3);
79
+ expect(updatedMessage.parts[0]).toMatchObject({
80
+ type: "tool-call",
81
+ toolCallId: "1",
82
+ state: "result",
83
+ result: "Tool 1",
84
+ isError: false,
85
+ });
86
+ expect(updatedMessage.parts[1]).toMatchObject({
87
+ type: "tool-call",
88
+ toolCallId: "2",
89
+ state: "result",
90
+ result: "Tool 2",
91
+ isError: false,
92
+ });
93
+ expect(updatedMessage.parts[2]).toMatchObject({
94
+ type: "tool-call",
95
+ toolCallId: "3",
96
+ state: "result",
97
+ result: "Tool 3",
98
+ isError: false,
99
+ });
100
+ });
101
+
102
+ it("should verify parallel execution via execution order", async () => {
103
+ let tool1Started = false;
104
+ let tool2Started = false;
105
+ let tool1Finished = false;
106
+
107
+ const tool1: Tool = {
108
+ parameters: {
109
+ type: "object",
110
+ properties: {},
111
+ },
112
+ execute: async () => {
113
+ tool1Started = true;
114
+ await new Promise((resolve) => setTimeout(resolve, 50));
115
+ tool1Finished = true;
116
+ return "Tool 1 executed";
117
+ },
118
+ };
119
+
120
+ const tool2: Tool = {
121
+ parameters: { type: "object", properties: {} },
122
+ execute: async () => {
123
+ tool2Started = true;
124
+ // In parallel execution, tool2 should start before tool1 finishes
125
+ expect(tool1Finished).toBe(false);
126
+ await new Promise((resolve) => setTimeout(resolve, 50));
127
+ return "Tool 2 executed";
128
+ },
129
+ };
130
+
131
+ const tools = { tool1, tool2 };
132
+
133
+ const message: AssistantMessage = {
134
+ role: "assistant",
135
+ status: { type: "requires-action", reason: "tool-calls" },
136
+ parts: [
137
+ {
138
+ type: "tool-call",
139
+ toolCallId: "1",
140
+ toolName: "tool1",
141
+ args: {},
142
+ } as ToolCallPart,
143
+ {
144
+ type: "tool-call",
145
+ toolCallId: "2",
146
+ toolName: "tool2",
147
+ args: {},
148
+ } as ToolCallPart,
149
+ ],
150
+ content: [],
151
+ metadata: {
152
+ unstable_state: {},
153
+ unstable_data: [],
154
+ unstable_annotations: [],
155
+ steps: [],
156
+ custom: {},
157
+ },
158
+ };
159
+
160
+ await unstable_runPendingTools(
161
+ message,
162
+ tools,
163
+ new AbortController().signal,
164
+ async () => {},
165
+ );
166
+
167
+ // Verifying that both tools started (proving parallel execution)
168
+ expect(tool1Started).toBe(true);
169
+ expect(tool2Started).toBe(true);
170
+ });
171
+ });
172
+
173
+ describe("edge cases", () => {
174
+ it("should return original message when no tool calls exist", async () => {
175
+ const message: AssistantMessage = {
176
+ role: "assistant",
177
+ status: {
178
+ reason: "stop",
179
+ type: "complete",
180
+ },
181
+ parts: [
182
+ {
183
+ type: "text",
184
+ text: "Hello",
185
+ status: {
186
+ type: "complete",
187
+ reason: "stop",
188
+ },
189
+ },
190
+ ],
191
+ content: [],
192
+ metadata: {
193
+ unstable_state: {},
194
+ unstable_data: [],
195
+ unstable_annotations: [],
196
+ steps: [],
197
+ custom: {},
198
+ },
199
+ };
200
+
201
+ const result = await unstable_runPendingTools(
202
+ message,
203
+ {},
204
+ new AbortController().signal,
205
+ async () => {},
206
+ );
207
+
208
+ expect(result).toEqual(message);
209
+ });
210
+
211
+ it("should handle missing tool gracefully", async () => {
212
+ const message: AssistantMessage = {
213
+ role: "assistant",
214
+ status: {
215
+ type: "requires-action",
216
+ reason: "tool-calls",
217
+ },
218
+ parts: [
219
+ {
220
+ type: "tool-call",
221
+ toolCallId: "1",
222
+ toolName: "nonexistentTool",
223
+ args: {},
224
+ status: { type: "requires-action", reason: "tool-call-result" },
225
+ } as ToolCallPart,
226
+ ],
227
+ content: [],
228
+ metadata: {
229
+ unstable_state: {},
230
+ unstable_data: [],
231
+ unstable_annotations: [],
232
+ steps: [],
233
+ custom: {},
234
+ },
235
+ };
236
+
237
+ const result = await unstable_runPendingTools(
238
+ message,
239
+ {},
240
+ new AbortController().signal,
241
+ async () => {},
242
+ );
243
+
244
+ // Tool call should remain unchanged (no result added)
245
+ expect(result.parts[0]).toMatchObject({
246
+ type: "tool-call",
247
+ toolCallId: "1",
248
+ toolName: "nonexistentTool",
249
+ });
250
+ expect(result.parts[0]).not.toHaveProperty("state");
251
+ expect(result.parts[0]).not.toHaveProperty("result");
252
+ });
253
+
254
+ it("should handle mixed text and tool-call parts", async () => {
255
+ const tool: Tool = {
256
+ parameters: {
257
+ type: "object",
258
+ properties: {},
259
+ },
260
+ execute: async () => "executed",
261
+ };
262
+
263
+ const message: AssistantMessage = {
264
+ role: "assistant",
265
+ status: {
266
+ type: "requires-action",
267
+ reason: "tool-calls",
268
+ },
269
+ parts: [
270
+ {
271
+ type: "text",
272
+ text: "Let me call a tool",
273
+ status: {
274
+ type: "complete",
275
+ reason: "stop",
276
+ },
277
+ },
278
+ {
279
+ type: "tool-call",
280
+ toolCallId: "1",
281
+ toolName: "tool",
282
+ args: {},
283
+ status: {
284
+ type: "requires-action",
285
+ reason: "tool-call-result",
286
+ },
287
+ } as ToolCallPart,
288
+ {
289
+ type: "text",
290
+ text: "Done",
291
+ status: {
292
+ type: "complete",
293
+ reason: "stop",
294
+ },
295
+ },
296
+ ],
297
+ content: [],
298
+ metadata: {
299
+ unstable_state: {},
300
+ unstable_data: [],
301
+ unstable_annotations: [],
302
+ steps: [],
303
+ custom: {},
304
+ },
305
+ };
306
+
307
+ const result = await unstable_runPendingTools(
308
+ message,
309
+ { tool },
310
+ new AbortController().signal,
311
+ async () => {},
312
+ );
313
+
314
+ expect(result.parts).toHaveLength(3);
315
+ expect(result.parts[0]).toEqual({
316
+ type: "text",
317
+ text: "Let me call a tool",
318
+ status: { type: "complete", reason: "stop" },
319
+ });
320
+ expect(result.parts[1]).toMatchObject({
321
+ type: "tool-call",
322
+ state: "result",
323
+ result: "executed",
324
+ });
325
+ expect(result.parts[2]).toEqual({
326
+ type: "text",
327
+ text: "Done",
328
+ status: { type: "complete", reason: "stop" },
329
+ });
330
+ });
331
+
332
+ it("should handle tools with different execution times", async () => {
333
+ const fastTool = createDelayedTool(10, "fast");
334
+ const slowTool = createDelayedTool(100, "slow");
335
+
336
+ const tools = { fastTool, slowTool };
337
+
338
+ const message: AssistantMessage = {
339
+ role: "assistant",
340
+ status: {
341
+ type: "requires-action",
342
+ reason: "tool-calls",
343
+ },
344
+ parts: [
345
+ {
346
+ type: "tool-call",
347
+ toolCallId: "1",
348
+ toolName: "slowTool",
349
+ args: {},
350
+ status: {
351
+ type: "requires-action",
352
+ reason: "tool-call-result",
353
+ },
354
+ } as ToolCallPart,
355
+ {
356
+ type: "tool-call",
357
+ toolCallId: "2",
358
+ toolName: "fastTool",
359
+ args: {},
360
+ status: {
361
+ type: "requires-action",
362
+ reason: "tool-call-result",
363
+ },
364
+ } as ToolCallPart,
365
+ ],
366
+ content: [],
367
+ metadata: {
368
+ unstable_state: {},
369
+ unstable_data: [],
370
+ unstable_annotations: [],
371
+ steps: [],
372
+ custom: {},
373
+ },
374
+ };
375
+
376
+ const updatedMessage = await unstable_runPendingTools(
377
+ message,
378
+ tools,
379
+ new AbortController().signal,
380
+ async () => {},
381
+ );
382
+
383
+ // Both should complete successfully
384
+ expect(updatedMessage.parts[0]).toMatchObject({
385
+ type: "tool-call",
386
+ toolCallId: "1",
387
+ state: "result",
388
+ result: "slow",
389
+ isError: false,
390
+ });
391
+ expect(updatedMessage.parts[1]).toMatchObject({
392
+ type: "tool-call",
393
+ toolCallId: "2",
394
+ state: "result",
395
+ result: "fast",
396
+ isError: false,
397
+ });
398
+ });
399
+ });
400
+ });
@@ -83,9 +83,9 @@ export async function unstable_runPendingTools(
83
83
  abortSignal: AbortSignal,
84
84
  human: (toolCallId: string, payload: unknown) => Promise<unknown>,
85
85
  ) {
86
- // TODO parallel tool calling
87
- for (const part of message.parts) {
88
- if (part.type === "tool-call") {
86
+ const toolCallPromises = message.parts
87
+ .filter((part) => part.type === "tool-call")
88
+ .map(async (part) => {
89
89
  const promiseOrUndefined = getToolResponse(
90
90
  tools,
91
91
  abortSignal,
@@ -99,29 +99,53 @@ export async function unstable_runPendingTools(
99
99
  );
100
100
  if (promiseOrUndefined) {
101
101
  const result = await promiseOrUndefined;
102
- const updatedParts = message.parts.map((p) => {
103
- if (p.type === "tool-call" && p.toolCallId === part.toolCallId) {
104
- return {
105
- ...p,
106
- state: "result" as const,
107
- ...(result.artifact !== undefined
108
- ? { artifact: result.artifact }
109
- : {}),
110
- result: result.result as ReadonlyJSONValue,
111
- isError: result.isError,
112
- };
113
- }
114
- return p;
115
- });
116
- message = {
117
- ...message,
118
- parts: updatedParts,
119
- content: updatedParts,
102
+ return {
103
+ toolCallId: part.toolCallId,
104
+ result,
120
105
  };
121
106
  }
122
- }
107
+ return null;
108
+ });
109
+
110
+ const toolCallResults = (await Promise.all(toolCallPromises)).filter(
111
+ (result) => result !== null,
112
+ ) as { toolCallId: string; result: ToolResponse<ReadonlyJSONValue> }[];
113
+
114
+ if (toolCallResults.length === 0) {
115
+ return message;
123
116
  }
124
- return message;
117
+
118
+ const toolCallResultsById = toolCallResults.reduce(
119
+ (acc, { toolCallId, result }) => {
120
+ acc[toolCallId] = result;
121
+ return acc;
122
+ },
123
+ {} as Record<string, ToolResponse<ReadonlyJSONValue>>,
124
+ );
125
+
126
+ const updatedParts = message.parts.map((p) => {
127
+ if (p.type === "tool-call") {
128
+ const toolResponse = toolCallResultsById[p.toolCallId];
129
+ if (toolResponse) {
130
+ return {
131
+ ...p,
132
+ state: "result" as const,
133
+ ...(toolResponse.artifact !== undefined
134
+ ? { artifact: toolResponse.artifact }
135
+ : {}),
136
+ result: toolResponse.result as ReadonlyJSONValue,
137
+ isError: toolResponse.isError,
138
+ };
139
+ }
140
+ }
141
+ return p;
142
+ });
143
+
144
+ return {
145
+ ...message,
146
+ parts: updatedParts,
147
+ content: updatedParts,
148
+ };
125
149
  }
126
150
 
127
151
  export function toolResultStream(