@assistant-ui/react-ai-sdk 1.3.31 → 1.3.32
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/assistant-stream/dist/core/tool/schema-utils.js +5 -2
- package/dist/assistant-stream/dist/core/tool/schema-utils.js.map +1 -1
- package/dist/assistant-stream/dist/core/tool/tool-types.d.ts.map +1 -1
- package/dist/frontendTools.d.ts +1 -1
- package/dist/frontendTools.d.ts.map +1 -1
- package/dist/frontendTools.js +3 -27
- package/dist/frontendTools.js.map +1 -1
- package/dist/generativeTools.d.ts +19 -3
- package/dist/generativeTools.d.ts.map +1 -1
- package/dist/generativeTools.js +108 -10
- package/dist/generativeTools.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/modelContentEnvelope.d.ts +3 -3
- package/dist/modelContentEnvelope.d.ts.map +1 -1
- package/dist/modelContentEnvelope.js.map +1 -1
- package/dist/toolOutputConversion.d.ts +34 -0
- package/dist/toolOutputConversion.d.ts.map +1 -0
- package/dist/toolOutputConversion.js +31 -0
- package/dist/toolOutputConversion.js.map +1 -0
- package/dist/ui/use-chat/useAISDKRuntime.js +2 -1
- package/dist/ui/use-chat/useAISDKRuntime.js.map +1 -1
- package/dist/ui/utils/toCreateMessage.js +4 -0
- package/dist/ui/utils/toCreateMessage.js.map +1 -1
- package/package.json +5 -4
- package/src/frontendTools.ts +4 -27
- package/src/generativeTools.test.ts +407 -0
- package/src/generativeTools.ts +239 -19
- package/src/index.ts +3 -0
- package/src/modelContentEnvelope.ts +7 -5
- package/src/toolOutputConversion.ts +29 -0
- package/src/ui/use-chat/useAISDKRuntime.test.ts +49 -0
- package/src/ui/use-chat/useAISDKRuntime.ts +2 -1
- package/src/ui/utils/toCreateMessage.test.ts +54 -0
- package/src/ui/utils/toCreateMessage.ts +5 -0
|
@@ -30,7 +30,10 @@ function toJSONSchema(schema) {
|
|
|
30
30
|
return schema;
|
|
31
31
|
}
|
|
32
32
|
function defaultToolFilter(_name, tool) {
|
|
33
|
-
return !tool.disabled && tool.type !== "backend";
|
|
33
|
+
return !tool.disabled && tool.type !== "backend" && (tool.type !== "frontend" || tool.execute !== void 0);
|
|
34
|
+
}
|
|
35
|
+
function toolHasUploadableParameters(tool) {
|
|
36
|
+
return tool.parameters !== void 0 && !tool.unstable_backendDefault?.parameters;
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* Converts a record of tools to a record of tool definitions with JSON Schema parameters.
|
|
@@ -44,7 +47,7 @@ function defaultToolFilter(_name, tool) {
|
|
|
44
47
|
function toToolsJSONSchema(tools, options = {}) {
|
|
45
48
|
if (!tools) return {};
|
|
46
49
|
const filter = options.filter ?? defaultToolFilter;
|
|
47
|
-
return Object.fromEntries(Object.entries(tools).filter(([name, tool]) => filter(name, tool)
|
|
50
|
+
return Object.fromEntries(Object.entries(tools).filter(([name, tool]) => filter(name, tool)).filter((entry) => toolHasUploadableParameters(entry[1])).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, tool]) => [name, {
|
|
48
51
|
...tool.description && { description: tool.description },
|
|
49
52
|
parameters: toJSONSchema(tool.parameters),
|
|
50
53
|
...tool.providerOptions && { providerOptions: tool.providerOptions }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-utils.js","names":[],"sources":["../../../../../../assistant-stream/dist/core/tool/schema-utils.js"],"sourcesContent":["//#region src/core/tool/schema-utils.ts\nfunction isStandardSchema(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"~standard\" in schema && typeof schema[\"~standard\"] === \"object\";\n}\nfunction hasToJSONSchemaMethod(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"toJSONSchema\" in schema && typeof schema.toJSONSchema === \"function\";\n}\nfunction hasToJSONMethod(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"toJSON\" in schema && typeof schema.toJSON === \"function\";\n}\n/**\n* Converts a schema to JSONSchema7.\n* Supports:\n* - StandardSchemaV1 with ~standard.toJSONSchema (e.g., Zod v4)\n* - StandardSchemaV1 with ~standard.jsonSchema.input() (e.g., Zod v4)\n* - Objects with toJSONSchema() method (e.g., Zod v4)\n* - Objects with toJSON() method\n* - Plain JSONSchema7 objects (must have a \"type\" property)\n*/\nfunction toJSONSchema(schema) {\n\tif (isStandardSchema(schema)) {\n\t\tconst toJSONSchemaMethod = schema[\"~standard\"].toJSONSchema;\n\t\tif (typeof toJSONSchemaMethod === \"function\") return toJSONSchemaMethod();\n\t\tconst jsonSchema = schema[\"~standard\"].jsonSchema;\n\t\tif (typeof jsonSchema === \"object\" && jsonSchema !== null && typeof jsonSchema.input === \"function\") return jsonSchema.input();\n\t}\n\tif (hasToJSONSchemaMethod(schema)) return schema.toJSONSchema();\n\tif (hasToJSONMethod(schema)) return schema.toJSON();\n\tif (isStandardSchema(schema)) throw new Error(\"Could not convert schema to JSON Schema. The schema implements Standard Schema but does not support JSON Schema conversion. If you are using Zod, please upgrade to Zod v4 (npm install zod@latest). Alternatively, pass a plain JSON Schema object instead.\");\n\treturn schema;\n}\n/**\n* Returns a copy of the JSON Schema with `required` removed recursively,\n* making every property optional. Array item schemas are left unchanged.\n*/\nfunction toPartialJSONSchema(schema) {\n\tconst { required: _, ...result } = schema;\n\tif (result.properties) result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, prop]) => {\n\t\tif (typeof prop === \"object\" && prop !== null && !Array.isArray(prop)) {\n\t\t\tconst p = prop;\n\t\t\treturn [key, p.properties != null ? toPartialJSONSchema(p) : prop];\n\t\t}\n\t\treturn [key, prop];\n\t}));\n\treturn result;\n}\nfunction defaultToolFilter(_name, tool) {\n\treturn !tool.disabled && tool.type !== \"backend\";\n}\n/**\n* Converts a record of tools to a record of tool definitions with JSON Schema parameters.\n* By default, filters out disabled tools and backend tools.\n*\n* Entries are emitted in alphabetical order so the resulting request body is\n* byte-identical regardless of the order in which tools were registered. This\n* keeps provider prompt caches stable across renders that mount tools in\n* different orders.\n*/\nfunction toToolsJSONSchema(tools, options = {}) {\n\tif (!tools) return {};\n\tconst filter = options.filter ?? defaultToolFilter;\n\treturn Object.fromEntries(Object.entries(tools).filter(([name, tool]) => filter(name, tool)
|
|
1
|
+
{"version":3,"file":"schema-utils.js","names":[],"sources":["../../../../../../assistant-stream/dist/core/tool/schema-utils.js"],"sourcesContent":["//#region src/core/tool/schema-utils.ts\nfunction isStandardSchema(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"~standard\" in schema && typeof schema[\"~standard\"] === \"object\";\n}\nfunction hasToJSONSchemaMethod(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"toJSONSchema\" in schema && typeof schema.toJSONSchema === \"function\";\n}\nfunction hasToJSONMethod(schema) {\n\treturn typeof schema === \"object\" && schema !== null && \"toJSON\" in schema && typeof schema.toJSON === \"function\";\n}\n/**\n* Converts a schema to JSONSchema7.\n* Supports:\n* - StandardSchemaV1 with ~standard.toJSONSchema (e.g., Zod v4)\n* - StandardSchemaV1 with ~standard.jsonSchema.input() (e.g., Zod v4)\n* - Objects with toJSONSchema() method (e.g., Zod v4)\n* - Objects with toJSON() method\n* - Plain JSONSchema7 objects (must have a \"type\" property)\n*/\nfunction toJSONSchema(schema) {\n\tif (isStandardSchema(schema)) {\n\t\tconst toJSONSchemaMethod = schema[\"~standard\"].toJSONSchema;\n\t\tif (typeof toJSONSchemaMethod === \"function\") return toJSONSchemaMethod();\n\t\tconst jsonSchema = schema[\"~standard\"].jsonSchema;\n\t\tif (typeof jsonSchema === \"object\" && jsonSchema !== null && typeof jsonSchema.input === \"function\") return jsonSchema.input();\n\t}\n\tif (hasToJSONSchemaMethod(schema)) return schema.toJSONSchema();\n\tif (hasToJSONMethod(schema)) return schema.toJSON();\n\tif (isStandardSchema(schema)) throw new Error(\"Could not convert schema to JSON Schema. The schema implements Standard Schema but does not support JSON Schema conversion. If you are using Zod, please upgrade to Zod v4 (npm install zod@latest). Alternatively, pass a plain JSON Schema object instead.\");\n\treturn schema;\n}\n/**\n* Returns a copy of the JSON Schema with `required` removed recursively,\n* making every property optional. Array item schemas are left unchanged.\n*/\nfunction toPartialJSONSchema(schema) {\n\tconst { required: _, ...result } = schema;\n\tif (result.properties) result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, prop]) => {\n\t\tif (typeof prop === \"object\" && prop !== null && !Array.isArray(prop)) {\n\t\t\tconst p = prop;\n\t\t\treturn [key, p.properties != null ? toPartialJSONSchema(p) : prop];\n\t\t}\n\t\treturn [key, prop];\n\t}));\n\treturn result;\n}\nfunction defaultToolFilter(_name, tool) {\n\treturn !tool.disabled && tool.type !== \"backend\" && (tool.type !== \"frontend\" || tool.execute !== void 0);\n}\nfunction toolHasUploadableParameters(tool) {\n\treturn tool.parameters !== void 0 && !tool.unstable_backendDefault?.parameters;\n}\n/**\n* Converts a record of tools to a record of tool definitions with JSON Schema parameters.\n* By default, filters out disabled tools and backend tools.\n*\n* Entries are emitted in alphabetical order so the resulting request body is\n* byte-identical regardless of the order in which tools were registered. This\n* keeps provider prompt caches stable across renders that mount tools in\n* different orders.\n*/\nfunction toToolsJSONSchema(tools, options = {}) {\n\tif (!tools) return {};\n\tconst filter = options.filter ?? defaultToolFilter;\n\treturn Object.fromEntries(Object.entries(tools).filter(([name, tool]) => filter(name, tool)).filter((entry) => toolHasUploadableParameters(entry[1])).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([name, tool]) => [name, {\n\t\t...tool.description && { description: tool.description },\n\t\tparameters: toJSONSchema(tool.parameters),\n\t\t...tool.providerOptions && { providerOptions: tool.providerOptions }\n\t}]));\n}\n//#endregion\nexport { toJSONSchema, toPartialJSONSchema, toToolsJSONSchema };\n\n//# sourceMappingURL=schema-utils.js.map"],"mappings":";AACA,SAAS,iBAAiB,QAAQ;CACjC,OAAO,OAAO,WAAW,YAAY,WAAW,QAAQ,eAAe,UAAU,OAAO,OAAO,iBAAiB;AACjH;AACA,SAAS,sBAAsB,QAAQ;CACtC,OAAO,OAAO,WAAW,YAAY,WAAW,QAAQ,kBAAkB,UAAU,OAAO,OAAO,iBAAiB;AACpH;AACA,SAAS,gBAAgB,QAAQ;CAChC,OAAO,OAAO,WAAW,YAAY,WAAW,QAAQ,YAAY,UAAU,OAAO,OAAO,WAAW;AACxG;;;;;;;;;;AAUA,SAAS,aAAa,QAAQ;CAC7B,IAAI,iBAAiB,MAAM,GAAG;EAC7B,MAAM,qBAAqB,OAAO,aAAa;EAC/C,IAAI,OAAO,uBAAuB,YAAY,OAAO,mBAAmB;EACxE,MAAM,aAAa,OAAO,aAAa;EACvC,IAAI,OAAO,eAAe,YAAY,eAAe,QAAQ,OAAO,WAAW,UAAU,YAAY,OAAO,WAAW,MAAM;CAC9H;CACA,IAAI,sBAAsB,MAAM,GAAG,OAAO,OAAO,aAAa;CAC9D,IAAI,gBAAgB,MAAM,GAAG,OAAO,OAAO,OAAO;CAClD,IAAI,iBAAiB,MAAM,GAAG,MAAM,IAAI,MAAM,8PAA8P;CAC5S,OAAO;AACR;AAgBA,SAAS,kBAAkB,OAAO,MAAM;CACvC,OAAO,CAAC,KAAK,YAAY,KAAK,SAAS,cAAc,KAAK,SAAS,cAAc,KAAK,YAAY,KAAK;AACxG;AACA,SAAS,4BAA4B,MAAM;CAC1C,OAAO,KAAK,eAAe,KAAK,KAAK,CAAC,KAAK,yBAAyB;AACrE;;;;;;;;;;AAUA,SAAS,kBAAkB,OAAO,UAAU,CAAC,GAAG;CAC/C,IAAI,CAAC,OAAO,OAAO,CAAC;CACpB,MAAM,SAAS,QAAQ,UAAU;CACjC,OAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,EAAE,QAAQ,CAAC,MAAM,UAAU,OAAO,MAAM,IAAI,CAAC,EAAE,QAAQ,UAAU,4BAA4B,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM;EACjO,GAAG,KAAK,eAAe,EAAE,aAAa,KAAK,YAAY;EACvD,YAAY,aAAa,KAAK,UAAU;EACxC,GAAG,KAAK,mBAAmB,EAAE,iBAAiB,KAAK,gBAAgB;CACpE,CAAC,CAAC,CAAC;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-types.d.ts","names":["type","text","data","mediaType","filename","TArgs","TResult","toolCallId","input","output","options","ToolModelContentPart","Promise","Record","get","PathT","TypePath","fieldPath","TypeAtPath","streamValues","AsyncIterableStream","DeepPartial","streamText","U","forEach","Array","ToolResponse","args","ToolCallArgsReader","response","ToolCallResponseReader","result","abortSignal","AbortSignal","human","payload","ToolExecutionContext","context","ToolCallReader","reader","ToolExecuteFunction","streamCall","ToolStreamCallFunction","display","ToolDisplay","ToolBase","description","parameters","disabled","execute","toModelOutput","experimental_onSchemaValidationError","providerOptions","StandardSchemaV1","JSONSchema7","ToolModelOutputFunction","OnSchemaValidationErrorFunction","ProviderOptions","FrontendTool","BackendTool","HumanTool","ToolWithoutType","BackendToolDeclaration","Omit"],"sources":["../../../../../../assistant-stream/dist/core/tool/tool-types.d.ts"],"mappings":";;KAOK,oBAAA;EAEMC,4EADmED,IAAAA,UACnEC;EAAAA,SAAAA,IAAAA;AAAAA;EAQAE,4EANmEH,IAAAA;EAOnEI;;AAAQ;AAkGiE;EAlGzEA,SAFAF,IAAAA,UAiHS;EAAA,SAhHTC,SAAAA,UAgHkB;EAAA,SA/GlBC,QAAAA;AAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+GN,eAAA,GAAkB,MAAM,SAAS,MAAA"}
|
|
1
|
+
{"version":3,"file":"tool-types.d.ts","names":["type","text","data","mediaType","filename","TArgs","TResult","toolCallId","input","output","options","ToolModelContentPart","Promise","Record","get","PathT","TypePath","fieldPath","TypeAtPath","streamValues","AsyncIterableStream","DeepPartial","streamText","U","forEach","Array","ToolResponse","args","ToolCallArgsReader","response","ToolCallResponseReader","result","abortSignal","AbortSignal","human","payload","ToolExecutionContext","context","ToolCallReader","reader","ToolExecuteFunction","streamCall","ToolStreamCallFunction","display","ToolDisplay","ToolBase","description","parameters","disabled","execute","toModelOutput","experimental_onSchemaValidationError","providerOptions","StandardSchemaV1","JSONSchema7","ToolModelOutputFunction","OnSchemaValidationErrorFunction","ProviderOptions","providerId","supportsDeferredResults","url","headers","redirect","command","env","cwd","server","McpServerConfig","FrontendTool","BackendTool","HumanTool","ProviderTool","McpTool","ToolWithoutType","BackendToolDeclaration","Omit"],"sources":["../../../../../../assistant-stream/dist/core/tool/tool-types.d.ts"],"mappings":";;KAOK,oBAAA;EAEMC,4EADmED,IAAAA,UACnEC;EAAAA,SAAAA,IAAAA;AAAAA;EAQAE,4EANmEH,IAAAA;EAOnEI;;AAAQ;AAkGiE;EAlGzEA,SAFAF,IAAAA,UAiHS;EAAA,SAhHTC,SAAAA,UAgHkB;EAAA,SA/GlBC,QAAAA;AAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+GN,eAAA,GAAkB,MAAM,SAAS,MAAA"}
|
package/dist/frontendTools.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontendTools.d.ts","names":[],"sources":["../src/frontendTools.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"frontendTools.d.ts","names":[],"sources":["../src/frontendTools.ts"],"mappings":";;;;cAKa,oBAAA;EAAwB;AAAA;EAAc,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;cAQtC,aAAA,GAAiB,KAAA,EAAO,MAAA,SAAe,cAAA,MAAkB,OAAA"}
|
package/dist/frontendTools.js
CHANGED
|
@@ -1,35 +1,11 @@
|
|
|
1
1
|
import { unwrapModelContentEnvelope } from "./modelContentEnvelope.js";
|
|
2
|
+
import { toAISDKContent, toAISDKDefaultOutput } from "./toolOutputConversion.js";
|
|
2
3
|
import { jsonSchema } from "ai";
|
|
3
4
|
//#region src/frontendTools.ts
|
|
4
|
-
const toAISDKContent = (parts) => ({
|
|
5
|
-
type: "content",
|
|
6
|
-
value: parts.map((part) => {
|
|
7
|
-
if (part.type === "text") return {
|
|
8
|
-
type: "text",
|
|
9
|
-
text: part.text
|
|
10
|
-
};
|
|
11
|
-
return part.mediaType.startsWith("image/") ? {
|
|
12
|
-
type: "image-data",
|
|
13
|
-
data: part.data,
|
|
14
|
-
mediaType: part.mediaType
|
|
15
|
-
} : {
|
|
16
|
-
type: "file-data",
|
|
17
|
-
data: part.data,
|
|
18
|
-
mediaType: part.mediaType,
|
|
19
|
-
...part.filename !== void 0 && { filename: part.filename }
|
|
20
|
-
};
|
|
21
|
-
})
|
|
22
|
-
});
|
|
23
5
|
const defaultToModelOutput = ({ output }) => {
|
|
24
|
-
const { modelContent } = unwrapModelContentEnvelope(output);
|
|
6
|
+
const { result, modelContent } = unwrapModelContentEnvelope(output);
|
|
25
7
|
if (modelContent !== void 0) return toAISDKContent(modelContent);
|
|
26
|
-
return
|
|
27
|
-
type: "text",
|
|
28
|
-
value: output
|
|
29
|
-
} : {
|
|
30
|
-
type: "json",
|
|
31
|
-
value: output ?? null
|
|
32
|
-
};
|
|
8
|
+
return toAISDKDefaultOutput(result);
|
|
33
9
|
};
|
|
34
10
|
const frontendTools = (tools) => Object.fromEntries(Object.entries(tools).map(([name, t]) => [name, {
|
|
35
11
|
...t.description !== void 0 && { description: t.description },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontendTools.js","names":[],"sources":["../src/frontendTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { ToolJSONSchema
|
|
1
|
+
{"version":3,"file":"frontendTools.js","names":[],"sources":["../src/frontendTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { ToolJSONSchema } from \"assistant-stream\";\nimport { unwrapModelContentEnvelope } from \"./modelContentEnvelope\";\nimport { toAISDKContent, toAISDKDefaultOutput } from \"./toolOutputConversion\";\n\nexport const defaultToModelOutput = ({ output }: { output: unknown }) => {\n const { result, modelContent } = unwrapModelContentEnvelope(output);\n if (modelContent !== undefined) {\n return toAISDKContent(modelContent);\n }\n return toAISDKDefaultOutput(result);\n};\n\nexport const frontendTools = (tools: Record<string, ToolJSONSchema>): ToolSet =>\n Object.fromEntries(\n Object.entries(tools).map(([name, t]) => [\n name,\n {\n ...(t.description !== undefined && { description: t.description }),\n inputSchema: jsonSchema(t.parameters),\n toModelOutput: defaultToModelOutput,\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n },\n ]),\n ) as ToolSet;\n"],"mappings":";;;;AAKA,MAAa,wBAAwB,EAAE,aAAkC;CACvE,MAAM,EAAE,QAAQ,iBAAiB,2BAA2B,MAAM;CAClE,IAAI,iBAAiB,KAAA,GACnB,OAAO,eAAe,YAAY;CAEpC,OAAO,qBAAqB,MAAM;AACpC;AAEA,MAAa,iBAAiB,UAC5B,OAAO,YACL,OAAO,QAAQ,KAAK,EAAE,KAAK,CAAC,MAAM,OAAO,CACvC,MACA;CACE,GAAI,EAAE,gBAAgB,KAAA,KAAa,EAAE,aAAa,EAAE,YAAY;CAChE,aAAa,WAAW,EAAE,UAAU;CACpC,eAAe;CACf,GAAI,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB;AAChE,CACF,CAAC,CACH"}
|
|
@@ -7,7 +7,7 @@ interface GenerativeToolsOptions {
|
|
|
7
7
|
/**
|
|
8
8
|
* The server build of a generative toolkit (schema + server `execute`). Typed
|
|
9
9
|
* as the canonical {@link Toolkit} so callers don't need to cast; the server
|
|
10
|
-
* build carries `execute`, recovered internally as {@link
|
|
10
|
+
* build carries `execute`, recovered internally as {@link ToolkitDefinition}.
|
|
11
11
|
*/
|
|
12
12
|
toolkit: Toolkit;
|
|
13
13
|
/**
|
|
@@ -17,6 +17,15 @@ interface GenerativeToolsOptions {
|
|
|
17
17
|
*/
|
|
18
18
|
frontendTools?: Record<string, ToolJSONSchema>;
|
|
19
19
|
}
|
|
20
|
+
type AISDKToolkitOptions = {
|
|
21
|
+
toolkit: Toolkit;
|
|
22
|
+
};
|
|
23
|
+
type AISDKToolkitToolsOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Tools uploaded by the frontend request body.
|
|
26
|
+
*/
|
|
27
|
+
frontend?: Record<string, ToolJSONSchema>;
|
|
28
|
+
};
|
|
20
29
|
/**
|
|
21
30
|
* Builds an AI SDK `ToolSet` for server-side use with `streamText` /
|
|
22
31
|
* `generateText` from a generative `toolkit` and the frontend-uploaded tools.
|
|
@@ -26,7 +35,8 @@ interface GenerativeToolsOptions {
|
|
|
26
35
|
* resolves to the server build — schema + `execute`, with `render` stripped) and
|
|
27
36
|
* pass it here. Tools without an `execute` are still exposed to the model but
|
|
28
37
|
* left for the client to fulfill. `frontendTools` lets the client contribute
|
|
29
|
-
* tools that aren't in the static toolkit.
|
|
38
|
+
* tools that aren't in the static toolkit. Use {@link AISDKToolkit} when the
|
|
39
|
+
* toolkit contains MCP entries.
|
|
30
40
|
*
|
|
31
41
|
* @example
|
|
32
42
|
* ```ts
|
|
@@ -39,6 +49,12 @@ interface GenerativeToolsOptions {
|
|
|
39
49
|
* ```
|
|
40
50
|
*/
|
|
41
51
|
declare const generativeTools: (options: GenerativeToolsOptions) => ToolSet;
|
|
52
|
+
declare class AISDKToolkit {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(options: AISDKToolkitOptions);
|
|
55
|
+
tools(options?: AISDKToolkitToolsOptions): Promise<ToolSet>;
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
42
58
|
//#endregion
|
|
43
|
-
export { GenerativeToolsOptions, generativeTools };
|
|
59
|
+
export { AISDKToolkit, AISDKToolkitOptions, AISDKToolkitToolsOptions, GenerativeToolsOptions, generativeTools };
|
|
44
60
|
//# sourceMappingURL=generativeTools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generativeTools.d.ts","names":[],"sources":["../src/generativeTools.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"generativeTools.d.ts","names":[],"sources":["../src/generativeTools.ts"],"mappings":";;;;;UAiCiB,sBAAA;;;AAAjB;;;EAME,OAAA,EAAS,OAAA;EAMsB;;;;;EAA/B,aAAA,GAAgB,MAAA,SAAe,cAAA;AAAA;AAAA,KAGrB,mBAAA;EACV,OAAA,EAAS,OAAO;AAAA;AAAA,KAGN,wBAAA;EAJA;;;EAQV,QAAA,GAAW,MAAM,SAAS,cAAA;AAAA;AAJ5B;;;;;;;;AAI0C;AAyB1C;;;;;;;;AAUC;AAED;;;;AAzCA,cA6Ba,eAAA,GAAmB,OAAA,EAAS,sBAAA,KAAyB,OAUjE;AAAA,cAEY,YAAA;EAAA;cAIC,OAAA,EAAS,mBAAA;EAIf,KAAA,CAAM,OAAA,GAAS,wBAAA,GAAgC,OAAA,CAAQ,OAAA;EASvD,KAAA,IAAS,OAAA;AAAA"}
|
package/dist/generativeTools.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { unwrapModelContentEnvelope } from "./modelContentEnvelope.js";
|
|
2
|
+
import { toAISDKContent, toAISDKDefaultOutput } from "./toolOutputConversion.js";
|
|
3
|
+
import { frontendTools } from "./frontendTools.js";
|
|
2
4
|
import { toJSONSchema } from "./assistant-stream/dist/core/tool/schema-utils.js";
|
|
3
5
|
import { jsonSchema } from "ai";
|
|
6
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
7
|
+
import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
|
|
4
8
|
//#region src/generativeTools.ts
|
|
5
9
|
const EMPTY_SCHEMA = {
|
|
6
10
|
type: "object",
|
|
@@ -10,6 +14,7 @@ const humanNotSupported = () => {
|
|
|
10
14
|
throw new Error("`human()` is not available during server-side tool execution.");
|
|
11
15
|
};
|
|
12
16
|
const neverAbort = new AbortController().signal;
|
|
17
|
+
const parametersToInputSchema = (parameters) => jsonSchema(parameters ? toJSONSchema(parameters) : EMPTY_SCHEMA);
|
|
13
18
|
/**
|
|
14
19
|
* Builds an AI SDK `ToolSet` for server-side use with `streamText` /
|
|
15
20
|
* `generateText` from a generative `toolkit` and the frontend-uploaded tools.
|
|
@@ -19,7 +24,8 @@ const neverAbort = new AbortController().signal;
|
|
|
19
24
|
* resolves to the server build — schema + `execute`, with `render` stripped) and
|
|
20
25
|
* pass it here. Tools without an `execute` are still exposed to the model but
|
|
21
26
|
* left for the client to fulfill. `frontendTools` lets the client contribute
|
|
22
|
-
* tools that aren't in the static toolkit.
|
|
27
|
+
* tools that aren't in the static toolkit. Use {@link AISDKToolkit} when the
|
|
28
|
+
* toolkit contains MCP entries.
|
|
23
29
|
*
|
|
24
30
|
* @example
|
|
25
31
|
* ```ts
|
|
@@ -31,16 +37,99 @@ const neverAbort = new AbortController().signal;
|
|
|
31
37
|
* });
|
|
32
38
|
* ```
|
|
33
39
|
*/
|
|
34
|
-
const generativeTools = (options) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
40
|
+
const generativeTools = (options) => {
|
|
41
|
+
assertNoMcpToolkitTools(options.toolkit);
|
|
42
|
+
return {
|
|
43
|
+
...options.frontendTools ? frontendTools(options.frontendTools) : {},
|
|
44
|
+
...toProviderToolSet(options.toolkit),
|
|
45
|
+
...toServerToolSet(options.toolkit)
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
var AISDKToolkit = class {
|
|
49
|
+
#toolkit;
|
|
50
|
+
#mcpClients = /* @__PURE__ */ new Map();
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.#toolkit = options.toolkit;
|
|
53
|
+
}
|
|
54
|
+
async tools(options = {}) {
|
|
55
|
+
return {
|
|
56
|
+
...options.frontend ? frontendTools(options.frontend) : {},
|
|
57
|
+
...await this.#mcpTools(),
|
|
58
|
+
...toProviderToolSet(this.#toolkit),
|
|
59
|
+
...toServerToolSet(this.#toolkit)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async close() {
|
|
63
|
+
const clientPromises = [...this.#mcpClients.values()];
|
|
64
|
+
this.#mcpClients.clear();
|
|
65
|
+
const results = await Promise.allSettled(clientPromises);
|
|
66
|
+
const clients = results.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
67
|
+
const closeResults = await Promise.allSettled(clients.map((client) => client.close()));
|
|
68
|
+
const errors = [...results.flatMap((result) => result.status === "rejected" ? [result.reason] : []), ...closeResults.flatMap((result) => result.status === "rejected" ? [result.reason] : [])];
|
|
69
|
+
if (errors.length === 1) throw errors[0];
|
|
70
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to close one or more MCP clients");
|
|
71
|
+
}
|
|
72
|
+
async #mcpTools() {
|
|
73
|
+
const toolSets = await Promise.all(Object.entries(this.#toolkit).filter((entry) => isMcpToolkitTool(entry[1])).map(async ([name, tool]) => {
|
|
74
|
+
return [name, await (await this.#mcpClient(name, tool.server)).tools()];
|
|
75
|
+
}));
|
|
76
|
+
const tools = {};
|
|
77
|
+
const toolSources = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const [serverName, toolSet] of toolSets) for (const [toolName, tool] of Object.entries(toolSet)) {
|
|
79
|
+
const existingServerName = toolSources.get(toolName);
|
|
80
|
+
if (existingServerName) throw new Error(`MCP tool name collision: "${toolName}" is exposed by both "${existingServerName}" and "${serverName}". Rename one of the toolkit entries or expose distinct MCP tool names.`);
|
|
81
|
+
toolSources.set(toolName, serverName);
|
|
82
|
+
tools[toolName] = tool;
|
|
83
|
+
}
|
|
84
|
+
return tools;
|
|
85
|
+
}
|
|
86
|
+
#mcpClient(name, config) {
|
|
87
|
+
const existing = this.#mcpClients.get(name);
|
|
88
|
+
if (existing) return existing;
|
|
89
|
+
let next;
|
|
90
|
+
next = createMCPClient(toMCPClientConfig(config)).catch((error) => {
|
|
91
|
+
if (this.#mcpClients.get(name) === next) this.#mcpClients.delete(name);
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
this.#mcpClients.set(name, next);
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const toMCPClientConfig = (config) => {
|
|
99
|
+
if (config.type === "stdio") return { transport: new Experimental_StdioMCPTransport({
|
|
100
|
+
command: config.command,
|
|
101
|
+
...config.args && { args: [...config.args] },
|
|
102
|
+
...config.env && { env: config.env },
|
|
103
|
+
...config.cwd && { cwd: config.cwd }
|
|
104
|
+
}) };
|
|
105
|
+
return { transport: {
|
|
106
|
+
type: config.type,
|
|
107
|
+
url: config.url,
|
|
108
|
+
...config.headers && { headers: config.headers },
|
|
109
|
+
...config.redirect && { redirect: config.redirect }
|
|
110
|
+
} };
|
|
111
|
+
};
|
|
112
|
+
const isMcpToolkitTool = (tool) => tool.type === "mcp" && !tool.disabled;
|
|
113
|
+
const assertNoMcpToolkitTools = (toolkit) => {
|
|
114
|
+
const mcpToolName = Object.entries(toolkit).find(([, tool]) => isMcpToolkitTool(tool))?.[0];
|
|
115
|
+
if (!mcpToolName) return;
|
|
116
|
+
throw new Error(`MCP toolkit entry "${mcpToolName}" requires AISDKToolkit. Use new AISDKToolkit({ toolkit }).tools(...) instead of generativeTools(...).`);
|
|
117
|
+
};
|
|
118
|
+
const toAISDKToModelOutput = (toModelOutput) => async (options) => {
|
|
119
|
+
const { result, modelContent } = unwrapModelContentEnvelope(options.output);
|
|
120
|
+
if (modelContent !== void 0) return toAISDKContent(modelContent);
|
|
121
|
+
if (!toModelOutput) return toAISDKDefaultOutput(result);
|
|
122
|
+
return toAISDKContent(await toModelOutput({
|
|
123
|
+
...options,
|
|
124
|
+
output: result
|
|
125
|
+
}));
|
|
126
|
+
};
|
|
127
|
+
const toServerToolSet = (toolkit) => Object.fromEntries(Object.entries(toolkit).filter(([, t]) => t.type !== "mcp" && t.type !== "provider" && !t.disabled).map(([name, t]) => {
|
|
39
128
|
const execute = t.execute;
|
|
40
129
|
return [name, {
|
|
41
130
|
...t.description !== void 0 && { description: t.description },
|
|
42
|
-
inputSchema:
|
|
43
|
-
toModelOutput: t.toModelOutput
|
|
131
|
+
inputSchema: parametersToInputSchema(t.parameters),
|
|
132
|
+
toModelOutput: toAISDKToModelOutput(t.toModelOutput),
|
|
44
133
|
...t.providerOptions && { providerOptions: t.providerOptions },
|
|
45
134
|
...execute && { execute: (args, callOptions) => execute(args, {
|
|
46
135
|
toolCallId: callOptions.toolCallId,
|
|
@@ -49,7 +138,16 @@ const toServerToolSet = (toolkit) => Object.fromEntries(Object.entries(toolkit).
|
|
|
49
138
|
}) }
|
|
50
139
|
}];
|
|
51
140
|
}));
|
|
141
|
+
const toProviderToolSet = (toolkit) => Object.fromEntries(Object.entries(toolkit).filter((entry) => isProviderToolkitTool(entry[1])).map(([name, t]) => [name, {
|
|
142
|
+
type: "provider",
|
|
143
|
+
id: t.providerId,
|
|
144
|
+
args: t.args,
|
|
145
|
+
...t.parameters && { inputSchema: parametersToInputSchema(t.parameters) },
|
|
146
|
+
...t.providerOptions && { providerOptions: t.providerOptions },
|
|
147
|
+
...t.supportsDeferredResults !== void 0 && { supportsDeferredResults: t.supportsDeferredResults }
|
|
148
|
+
}]));
|
|
149
|
+
const isProviderToolkitTool = (tool) => tool.type === "provider" && !tool.disabled;
|
|
52
150
|
//#endregion
|
|
53
|
-
export { generativeTools };
|
|
151
|
+
export { AISDKToolkit, generativeTools };
|
|
54
152
|
|
|
55
153
|
//# sourceMappingURL=generativeTools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generativeTools.js","names":[],"sources":["../src/generativeTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport { toJSONSchema, type ToolJSONSchema } from \"assistant-stream\";\nimport type { Toolkit, ToolkitDeclaration } from \"@assistant-ui/core/react\";\nimport { defaultToModelOutput, frontendTools } from \"./frontendTools\";\n\nconst EMPTY_SCHEMA = { type: \"object\" as const, properties: {} };\n\nconst humanNotSupported = (): never => {\n throw new Error(\n \"`human()` is not available during server-side tool execution.\",\n );\n};\n\n// AI SDK leaves `abortSignal` optional; assistant-ui's execute requires one.\nconst neverAbort = new AbortController().signal;\n\nexport interface GenerativeToolsOptions {\n /**\n * The server build of a generative toolkit (schema + server `execute`). Typed\n * as the canonical {@link Toolkit} so callers don't need to cast; the server\n * build carries `execute`, recovered internally as {@link ToolkitDeclaration}.\n */\n toolkit: Toolkit;\n /**\n * Tools uploaded by the frontend (the request body's `tools`). Merged in\n * alongside the `toolkit`; a server `execute` from `toolkit` takes precedence\n * over an uploaded entry of the same name.\n */\n frontendTools?: Record<string, ToolJSONSchema>;\n}\n\n/**\n * Builds an AI SDK `ToolSet` for server-side use with `streamText` /\n * `generateText` from a generative `toolkit` and the frontend-uploaded tools.\n *\n * Each toolkit tool's `execute` runs on the server. Pair this with the\n * `\"use generative\"` compiler: import the toolkit in a server route (where it\n * resolves to the server build — schema + `execute`, with `render` stripped) and\n * pass it here. Tools without an `execute` are still exposed to the model but\n * left for the client to fulfill. `frontendTools` lets the client contribute\n * tools that aren't in the static toolkit.\n *\n * @example\n * ```ts\n * const { tools } = await req.json();\n * streamText({\n * model,\n * messages,\n * tools: generativeTools({ toolkit: docsToolkit, frontendTools: tools }),\n * });\n * ```\n */\nexport const generativeTools = (options: GenerativeToolsOptions): ToolSet => ({\n ...(options.frontendTools ? frontendTools(options.frontendTools) : {}),\n // `toolkit` last so its server-side `execute` wins over an uploaded entry of\n // the same name. The cast recovers the declaration shape — the server build\n // carries `execute`, which the canonical `Toolkit` type erases.\n ...toServerToolSet(options.toolkit as ToolkitDeclaration),\n});\n\nconst toServerToolSet = (toolkit: ToolkitDeclaration): ToolSet =>\n Object.fromEntries(\n Object.entries(toolkit)\n .filter(([, t]) => !t.disabled)\n .map(([name, t]) => {\n const execute = t.execute;\n return [\n name,\n {\n ...(t.description !== undefined && { description: t.description }),\n inputSchema: jsonSchema(\n t.parameters ? toJSONSchema(t.parameters) : EMPTY_SCHEMA,\n ),\n toModelOutput: t.toModelOutput ?? defaultToModelOutput,\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n ...(execute && {\n execute: (\n args: unknown,\n callOptions: { toolCallId: string; abortSignal?: AbortSignal },\n ) =>\n execute(args as never, {\n toolCallId: callOptions.toolCallId,\n abortSignal: callOptions.abortSignal ?? neverAbort,\n human: humanNotSupported,\n }),\n }),\n },\n ];\n }),\n ) as ToolSet;\n"],"mappings":";;;;AAKA,MAAM,eAAe;CAAE,MAAM;CAAmB,YAAY,CAAC;AAAE;AAE/D,MAAM,0BAAiC;CACrC,MAAM,IAAI,MACR,+DACF;AACF;AAGA,MAAM,aAAa,IAAI,gBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;AAsCzC,MAAa,mBAAmB,aAA8C;CAC5E,GAAI,QAAQ,gBAAgB,cAAc,QAAQ,aAAa,IAAI,CAAC;CAIpE,GAAG,gBAAgB,QAAQ,OAA6B;AAC1D;AAEA,MAAM,mBAAmB,YACvB,OAAO,YACL,OAAO,QAAQ,OAAO,EACnB,QAAQ,GAAG,OAAO,CAAC,EAAE,QAAQ,EAC7B,KAAK,CAAC,MAAM,OAAO;CAClB,MAAM,UAAU,EAAE;CAClB,OAAO,CACL,MACA;EACE,GAAI,EAAE,gBAAgB,KAAA,KAAa,EAAE,aAAa,EAAE,YAAY;EAChE,aAAa,WACX,EAAE,aAAa,aAAa,EAAE,UAAU,IAAI,YAC9C;EACA,eAAe,EAAE,iBAAiB;EAClC,GAAI,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB;EAC9D,GAAI,WAAW,EACb,UACE,MACA,gBAEA,QAAQ,MAAe;GACrB,YAAY,YAAY;GACxB,aAAa,YAAY,eAAe;GACxC,OAAO;EACT,CAAC,EACL;CACF,CACF;AACF,CAAC,CACL"}
|
|
1
|
+
{"version":3,"file":"generativeTools.js","names":["#toolkit","#mcpClients","#mcpTools","#mcpClient"],"sources":["../src/generativeTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { MCPClient, MCPClientConfig } from \"@ai-sdk/mcp\";\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport { Experimental_StdioMCPTransport } from \"@ai-sdk/mcp/mcp-stdio\";\nimport {\n toJSONSchema,\n type Tool,\n type McpServerConfig,\n type ToolJSONSchema,\n type ToolModelOutputFunction,\n} from \"assistant-stream\";\nimport type { Toolkit, ToolkitDefinition } from \"@assistant-ui/core/react\";\nimport { frontendTools } from \"./frontendTools\";\nimport { toAISDKContent, toAISDKDefaultOutput } from \"./toolOutputConversion\";\nimport {\n unwrapModelContentEnvelope,\n type ModelContentEnvelope,\n} from \"./modelContentEnvelope\";\n\nconst EMPTY_SCHEMA = { type: \"object\" as const, properties: {} };\n\nconst humanNotSupported = (): never => {\n throw new Error(\n \"`human()` is not available during server-side tool execution.\",\n );\n};\n\n// AI SDK leaves `abortSignal` optional; assistant-ui's execute requires one.\nconst neverAbort = new AbortController().signal;\n\nconst parametersToInputSchema = (parameters: Tool[\"parameters\"] | undefined) =>\n jsonSchema(parameters ? toJSONSchema(parameters) : EMPTY_SCHEMA);\n\nexport interface GenerativeToolsOptions {\n /**\n * The server build of a generative toolkit (schema + server `execute`). Typed\n * as the canonical {@link Toolkit} so callers don't need to cast; the server\n * build carries `execute`, recovered internally as {@link ToolkitDefinition}.\n */\n toolkit: Toolkit;\n /**\n * Tools uploaded by the frontend (the request body's `tools`). Merged in\n * alongside the `toolkit`; a server `execute` from `toolkit` takes precedence\n * over an uploaded entry of the same name.\n */\n frontendTools?: Record<string, ToolJSONSchema>;\n}\n\nexport type AISDKToolkitOptions = {\n toolkit: Toolkit;\n};\n\nexport type AISDKToolkitToolsOptions = {\n /**\n * Tools uploaded by the frontend request body.\n */\n frontend?: Record<string, ToolJSONSchema>;\n};\n\n/**\n * Builds an AI SDK `ToolSet` for server-side use with `streamText` /\n * `generateText` from a generative `toolkit` and the frontend-uploaded tools.\n *\n * Each toolkit tool's `execute` runs on the server. Pair this with the\n * `\"use generative\"` compiler: import the toolkit in a server route (where it\n * resolves to the server build — schema + `execute`, with `render` stripped) and\n * pass it here. Tools without an `execute` are still exposed to the model but\n * left for the client to fulfill. `frontendTools` lets the client contribute\n * tools that aren't in the static toolkit. Use {@link AISDKToolkit} when the\n * toolkit contains MCP entries.\n *\n * @example\n * ```ts\n * const { tools } = await req.json();\n * streamText({\n * model,\n * messages,\n * tools: generativeTools({ toolkit: docsToolkit, frontendTools: tools }),\n * });\n * ```\n */\nexport const generativeTools = (options: GenerativeToolsOptions): ToolSet => {\n assertNoMcpToolkitTools(options.toolkit);\n return {\n ...(options.frontendTools ? frontendTools(options.frontendTools) : {}),\n // `toolkit` last so its server-side `execute` wins over an uploaded entry of\n // the same name. The cast recovers the declaration shape — the server build\n // carries `execute`, which the canonical `Toolkit` type erases.\n ...toProviderToolSet(options.toolkit),\n ...toServerToolSet(options.toolkit as ToolkitDefinition),\n };\n};\n\nexport class AISDKToolkit {\n readonly #toolkit: Toolkit;\n readonly #mcpClients = new Map<string, Promise<MCPClient>>();\n\n constructor(options: AISDKToolkitOptions) {\n this.#toolkit = options.toolkit;\n }\n\n async tools(options: AISDKToolkitToolsOptions = {}): Promise<ToolSet> {\n return {\n ...(options.frontend ? frontendTools(options.frontend) : {}),\n ...(await this.#mcpTools()),\n ...toProviderToolSet(this.#toolkit),\n ...toServerToolSet(this.#toolkit as ToolkitDefinition),\n };\n }\n\n async close(): Promise<void> {\n const clientPromises = [...this.#mcpClients.values()];\n this.#mcpClients.clear();\n const results = await Promise.allSettled(clientPromises);\n const clients = results.flatMap((result) =>\n result.status === \"fulfilled\" ? [result.value] : [],\n );\n const closeResults = await Promise.allSettled(\n clients.map((client) => client.close()),\n );\n const errors = [\n ...results.flatMap((result) =>\n result.status === \"rejected\" ? [result.reason] : [],\n ),\n ...closeResults.flatMap((result) =>\n result.status === \"rejected\" ? [result.reason] : [],\n ),\n ];\n if (errors.length === 1) throw errors[0];\n if (errors.length > 1) {\n throw new AggregateError(\n errors,\n \"Failed to close one or more MCP clients\",\n );\n }\n }\n\n async #mcpTools(): Promise<ToolSet> {\n const toolSets = await Promise.all(\n Object.entries(this.#toolkit)\n .filter((entry): entry is [string, McpToolkitTool] =>\n isMcpToolkitTool(entry[1]),\n )\n .map(async ([name, tool]) => {\n const client = await this.#mcpClient(name, tool.server);\n return [name, await client.tools()] as const;\n }),\n );\n\n const tools: ToolSet = {};\n const toolSources = new Map<string, string>();\n for (const [serverName, toolSet] of toolSets) {\n for (const [toolName, tool] of Object.entries(toolSet)) {\n const existingServerName = toolSources.get(toolName);\n if (existingServerName) {\n throw new Error(\n `MCP tool name collision: \"${toolName}\" is exposed by both \"${existingServerName}\" and \"${serverName}\". Rename one of the toolkit entries or expose distinct MCP tool names.`,\n );\n }\n toolSources.set(toolName, serverName);\n tools[toolName] = tool;\n }\n }\n return tools;\n }\n\n #mcpClient(name: string, config: McpServerConfig): Promise<MCPClient> {\n const existing = this.#mcpClients.get(name);\n if (existing) return existing;\n let next: Promise<MCPClient>;\n next = createMCPClient(toMCPClientConfig(config)).catch((error) => {\n if (this.#mcpClients.get(name) === next) {\n this.#mcpClients.delete(name);\n }\n throw error;\n });\n this.#mcpClients.set(name, next);\n return next;\n }\n}\n\nconst toMCPClientConfig = (config: McpServerConfig): MCPClientConfig => {\n if (config.type === \"stdio\") {\n return {\n transport: new Experimental_StdioMCPTransport({\n command: config.command,\n ...(config.args && { args: [...config.args] }),\n ...(config.env && { env: config.env }),\n ...(config.cwd && { cwd: config.cwd }),\n }),\n };\n }\n\n return {\n transport: {\n type: config.type,\n url: config.url,\n ...(config.headers && { headers: config.headers }),\n ...(config.redirect && { redirect: config.redirect }),\n },\n };\n};\n\ntype ToolkitTool = Toolkit[string];\n\ntype McpToolkitTool = ToolkitTool & {\n type: \"mcp\";\n server: McpServerConfig;\n};\n\nconst isMcpToolkitTool = (tool: ToolkitTool): tool is McpToolkitTool =>\n tool.type === \"mcp\" && !tool.disabled;\n\nconst assertNoMcpToolkitTools = (toolkit: Toolkit): void => {\n const mcpToolName = Object.entries(toolkit).find(([, tool]) =>\n isMcpToolkitTool(tool),\n )?.[0];\n if (!mcpToolName) return;\n\n throw new Error(\n `MCP toolkit entry \"${mcpToolName}\" requires AISDKToolkit. Use new AISDKToolkit({ toolkit }).tools(...) instead of generativeTools(...).`,\n );\n};\n\ntype AISDKToModelOutputOptions<TArgs, TResult> = Omit<\n Parameters<ToolModelOutputFunction<TArgs, TResult>>[0],\n \"output\"\n> & {\n output: TResult | ModelContentEnvelope<TResult>;\n};\n\nconst toAISDKToModelOutput =\n <TArgs, TResult>(toModelOutput?: ToolModelOutputFunction<TArgs, TResult>) =>\n async (options: AISDKToModelOutputOptions<TArgs, TResult>) => {\n const { result, modelContent } = unwrapModelContentEnvelope(options.output);\n\n if (modelContent !== undefined) {\n return toAISDKContent(modelContent);\n }\n\n if (!toModelOutput) {\n return toAISDKDefaultOutput(result);\n }\n\n const parts = await toModelOutput({\n ...options,\n output: result,\n });\n return toAISDKContent(parts);\n };\n\nconst toServerToolSet = (toolkit: ToolkitDefinition): ToolSet =>\n Object.fromEntries(\n Object.entries(toolkit)\n .filter(\n ([, t]) => t.type !== \"mcp\" && t.type !== \"provider\" && !t.disabled,\n )\n .map(([name, t]) => {\n const execute = t.execute;\n return [\n name,\n {\n ...(t.description !== undefined && { description: t.description }),\n inputSchema: parametersToInputSchema(t.parameters),\n toModelOutput: toAISDKToModelOutput(t.toModelOutput),\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n ...(execute && {\n execute: (\n args: unknown,\n callOptions: { toolCallId: string; abortSignal?: AbortSignal },\n ) =>\n execute(args as never, {\n toolCallId: callOptions.toolCallId,\n abortSignal: callOptions.abortSignal ?? neverAbort,\n human: humanNotSupported,\n }),\n }),\n },\n ];\n }),\n ) as ToolSet;\n\nconst toProviderToolSet = (toolkit: Toolkit): ToolSet =>\n Object.fromEntries(\n Object.entries(toolkit)\n .filter((entry): entry is [string, ProviderToolkitTool] =>\n isProviderToolkitTool(entry[1]),\n )\n .map(([name, t]) => [\n name,\n {\n type: \"provider\",\n id: t.providerId,\n args: t.args,\n ...(t.parameters && {\n inputSchema: parametersToInputSchema(t.parameters),\n }),\n ...(t.providerOptions && { providerOptions: t.providerOptions }),\n ...(t.supportsDeferredResults !== undefined && {\n supportsDeferredResults: t.supportsDeferredResults,\n }),\n },\n ]),\n ) as ToolSet;\n\ntype ProviderToolkitTool = Extract<Toolkit[string], { type: \"provider\" }>;\n\nconst isProviderToolkitTool = (\n tool: Toolkit[string],\n): tool is ProviderToolkitTool => tool.type === \"provider\" && !tool.disabled;\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/react" />
|
|
2
2
|
import { frontendTools } from "./frontendTools.js";
|
|
3
|
-
import { GenerativeToolsOptions, generativeTools } from "./generativeTools.js";
|
|
3
|
+
import { AISDKToolkit, AISDKToolkitOptions, AISDKToolkitToolsOptions, GenerativeToolsOptions, generativeTools } from "./generativeTools.js";
|
|
4
4
|
import { useAISDKRuntime } from "./ui/use-chat/useAISDKRuntime.js";
|
|
5
5
|
import { UseChatRuntimeOptions, useChatRuntime } from "./ui/use-chat/useChatRuntime.js";
|
|
6
6
|
import { RESUMABLE_STREAM_ID_HEADER } from "./assistant-stream/dist/resumable/createResumableAssistantStreamResponse.js";
|
|
@@ -8,4 +8,4 @@ import { AssistantChatResumableOptions, ResumableClientStorage, createResumableS
|
|
|
8
8
|
import { AssistantChatTransport } from "./ui/use-chat/AssistantChatTransport.js";
|
|
9
9
|
import { injectQuoteContext } from "./injectQuoteContext.js";
|
|
10
10
|
import { ThreadTokenUsage, TokenUsageExtractableMessage, getThreadMessageTokenUsage, useThreadTokenUsage } from "./usage.js";
|
|
11
|
-
export { type AssistantChatResumableOptions, AssistantChatTransport, type GenerativeToolsOptions, RESUMABLE_STREAM_ID_HEADER, type ResumableClientStorage, type ThreadTokenUsage, type TokenUsageExtractableMessage, type UseChatRuntimeOptions, createResumableSessionStorage, frontendTools, generativeTools, getThreadMessageTokenUsage, injectQuoteContext, useAISDKRuntime, useChatRuntime, useThreadTokenUsage };
|
|
11
|
+
export { AISDKToolkit, type AISDKToolkitOptions, type AISDKToolkitToolsOptions, type AssistantChatResumableOptions, AssistantChatTransport, type GenerativeToolsOptions, RESUMABLE_STREAM_ID_HEADER, type ResumableClientStorage, type ThreadTokenUsage, type TokenUsageExtractableMessage, type UseChatRuntimeOptions, createResumableSessionStorage, frontendTools, generativeTools, getThreadMessageTokenUsage, injectQuoteContext, useAISDKRuntime, useChatRuntime, useThreadTokenUsage };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { frontendTools } from "./frontendTools.js";
|
|
2
|
-
import { generativeTools } from "./generativeTools.js";
|
|
2
|
+
import { AISDKToolkit, generativeTools } from "./generativeTools.js";
|
|
3
3
|
import { useAISDKRuntime } from "./ui/use-chat/useAISDKRuntime.js";
|
|
4
4
|
import { RESUMABLE_STREAM_ID_HEADER } from "./assistant-stream/dist/resumable/createResumableAssistantStreamResponse.js";
|
|
5
5
|
import { createResumableSessionStorage } from "./ui/resumable.js";
|
|
@@ -7,4 +7,4 @@ import { AssistantChatTransport } from "./ui/use-chat/AssistantChatTransport.js"
|
|
|
7
7
|
import { useChatRuntime } from "./ui/use-chat/useChatRuntime.js";
|
|
8
8
|
import { injectQuoteContext } from "./injectQuoteContext.js";
|
|
9
9
|
import { getThreadMessageTokenUsage, useThreadTokenUsage } from "./usage.js";
|
|
10
|
-
export { AssistantChatTransport, RESUMABLE_STREAM_ID_HEADER, createResumableSessionStorage, frontendTools, generativeTools, getThreadMessageTokenUsage, injectQuoteContext, useAISDKRuntime, useChatRuntime, useThreadTokenUsage };
|
|
10
|
+
export { AISDKToolkit, AssistantChatTransport, RESUMABLE_STREAM_ID_HEADER, createResumableSessionStorage, frontendTools, generativeTools, getThreadMessageTokenUsage, injectQuoteContext, useAISDKRuntime, useChatRuntime, useThreadTokenUsage };
|
|
@@ -5,10 +5,10 @@ type ModelContentEnvelope<TResult = unknown> = {
|
|
|
5
5
|
readonly [ENVELOPE_KEY]: readonly ToolModelContentPart[];
|
|
6
6
|
readonly value: TResult;
|
|
7
7
|
};
|
|
8
|
-
declare function isModelContentEnvelope(value:
|
|
8
|
+
declare function isModelContentEnvelope<TResult = unknown>(value: TResult | ModelContentEnvelope<TResult>): value is ModelContentEnvelope<TResult>;
|
|
9
9
|
declare function wrapModelContentEnvelope<TResult>(result: TResult, modelContent: readonly ToolModelContentPart[]): ModelContentEnvelope<TResult>;
|
|
10
|
-
declare function unwrapModelContentEnvelope(output:
|
|
11
|
-
result:
|
|
10
|
+
declare function unwrapModelContentEnvelope<TResult>(output: TResult | ModelContentEnvelope<TResult>): {
|
|
11
|
+
result: TResult;
|
|
12
12
|
modelContent?: readonly ToolModelContentPart[];
|
|
13
13
|
};
|
|
14
14
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modelContentEnvelope.d.ts","names":[],"sources":["../src/modelContentEnvelope.ts"],"mappings":";;cAEM,YAAA;AAAA,KAEM,oBAAA;EAAA,UACA,YAAA,YAAwB,oBAAA;EAAA,SACzB,KAAA,EAAO,OAAA;AAAA;AAAA,iBAGF,sBAAA,
|
|
1
|
+
{"version":3,"file":"modelContentEnvelope.d.ts","names":[],"sources":["../src/modelContentEnvelope.ts"],"mappings":";;cAEM,YAAA;AAAA,KAEM,oBAAA;EAAA,UACA,YAAA,YAAwB,oBAAA;EAAA,SACzB,KAAA,EAAO,OAAA;AAAA;AAAA,iBAGF,sBAAA,oBACd,KAAA,EAAO,OAAA,GAAU,oBAAA,CAAqB,OAAA,IACrC,KAAA,IAAS,oBAAA,CAAqB,OAAA;AAAA,iBASjB,wBAAA,UACd,MAAA,EAAQ,OAAA,EACR,YAAA,WAAuB,oBAAA,KACtB,oBAAA,CAAqB,OAAA;AAAA,iBAIR,0BAAA,UACd,MAAA,EAAQ,OAAA,GAAU,oBAAA,CAAqB,OAAA;EAEvC,MAAA,EAAQ,OAAA;EACR,YAAA,YAAwB,oBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modelContentEnvelope.js","names":[],"sources":["../src/modelContentEnvelope.ts"],"sourcesContent":["import type { ToolModelContentPart } from \"assistant-stream\";\n\nconst ENVELOPE_KEY = \"__aui_modelContent\";\n\nexport type ModelContentEnvelope<TResult = unknown> = {\n readonly [ENVELOPE_KEY]: readonly ToolModelContentPart[];\n readonly value: TResult;\n};\n\nexport function isModelContentEnvelope(\n value:
|
|
1
|
+
{"version":3,"file":"modelContentEnvelope.js","names":[],"sources":["../src/modelContentEnvelope.ts"],"sourcesContent":["import type { ToolModelContentPart } from \"assistant-stream\";\n\nconst ENVELOPE_KEY = \"__aui_modelContent\";\n\nexport type ModelContentEnvelope<TResult = unknown> = {\n readonly [ENVELOPE_KEY]: readonly ToolModelContentPart[];\n readonly value: TResult;\n};\n\nexport function isModelContentEnvelope<TResult = unknown>(\n value: TResult | ModelContentEnvelope<TResult>,\n): value is ModelContentEnvelope<TResult> {\n return (\n value != null &&\n typeof value === \"object\" &&\n ENVELOPE_KEY in value &&\n Array.isArray((value as Record<string, unknown>)[ENVELOPE_KEY])\n );\n}\n\nexport function wrapModelContentEnvelope<TResult>(\n result: TResult,\n modelContent: readonly ToolModelContentPart[],\n): ModelContentEnvelope<TResult> {\n return { [ENVELOPE_KEY]: modelContent, value: result };\n}\n\nexport function unwrapModelContentEnvelope<TResult>(\n output: TResult | ModelContentEnvelope<TResult>,\n): {\n result: TResult;\n modelContent?: readonly ToolModelContentPart[];\n} {\n if (isModelContentEnvelope(output)) {\n return {\n result: output.value,\n modelContent: output[ENVELOPE_KEY],\n };\n }\n return { result: output };\n}\n"],"mappings":";AAEA,MAAM,eAAe;AAOrB,SAAgB,uBACd,OACwC;CACxC,OACE,SAAS,QACT,OAAO,UAAU,YACjB,gBAAgB,SAChB,MAAM,QAAS,MAAkC,aAAa;AAElE;AAEA,SAAgB,yBACd,QACA,cAC+B;CAC/B,OAAO;GAAG,eAAe;EAAc,OAAO;CAAO;AACvD;AAEA,SAAgB,2BACd,QAIA;CACA,IAAI,uBAAuB,MAAM,GAC/B,OAAO;EACL,QAAQ,OAAO;EACf,cAAc,OAAO;CACvB;CAEF,OAAO,EAAE,QAAQ,OAAO;AAC1B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ToolModelContentPart } from "./assistant-stream/dist/core/tool/tool-types.js";
|
|
2
|
+
import { JSONValue } from "ai";
|
|
3
|
+
|
|
4
|
+
//#region src/toolOutputConversion.d.ts
|
|
5
|
+
declare const toAISDKContent: (parts: readonly ToolModelContentPart[]) => {
|
|
6
|
+
type: "content";
|
|
7
|
+
value: ({
|
|
8
|
+
type: "text";
|
|
9
|
+
text: string;
|
|
10
|
+
data?: never;
|
|
11
|
+
mediaType?: never;
|
|
12
|
+
} | {
|
|
13
|
+
type: "image-data";
|
|
14
|
+
data: string;
|
|
15
|
+
mediaType: string;
|
|
16
|
+
text?: never;
|
|
17
|
+
} | {
|
|
18
|
+
filename?: string;
|
|
19
|
+
type: "file-data";
|
|
20
|
+
data: string;
|
|
21
|
+
mediaType: string;
|
|
22
|
+
text?: never;
|
|
23
|
+
})[];
|
|
24
|
+
};
|
|
25
|
+
declare const toAISDKDefaultOutput: (output: unknown) => {
|
|
26
|
+
type: "text";
|
|
27
|
+
value: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "json";
|
|
30
|
+
value: JSONValue;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { toAISDKContent, toAISDKDefaultOutput };
|
|
34
|
+
//# sourceMappingURL=toolOutputConversion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolOutputConversion.d.ts","names":[],"sources":["../src/toolOutputConversion.ts"],"mappings":";;;;cAGa,cAAA,GAAkB,KAAA,WAAgB,oBAAoB;;;;;;;;;;;;;;;;;;;;cAsBtD,oBAAA,GAAwB,MAAA;;;;;SAGqB,SAAS;AAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/toolOutputConversion.ts
|
|
2
|
+
const toAISDKContent = (parts) => ({
|
|
3
|
+
type: "content",
|
|
4
|
+
value: parts.map((part) => {
|
|
5
|
+
if (part.type === "text") return {
|
|
6
|
+
type: "text",
|
|
7
|
+
text: part.text
|
|
8
|
+
};
|
|
9
|
+
return part.mediaType.startsWith("image/") ? {
|
|
10
|
+
type: "image-data",
|
|
11
|
+
data: part.data,
|
|
12
|
+
mediaType: part.mediaType
|
|
13
|
+
} : {
|
|
14
|
+
type: "file-data",
|
|
15
|
+
data: part.data,
|
|
16
|
+
mediaType: part.mediaType,
|
|
17
|
+
...part.filename !== void 0 && { filename: part.filename }
|
|
18
|
+
};
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
const toAISDKDefaultOutput = (output) => typeof output === "string" ? {
|
|
22
|
+
type: "text",
|
|
23
|
+
value: output
|
|
24
|
+
} : {
|
|
25
|
+
type: "json",
|
|
26
|
+
value: output ?? null
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { toAISDKContent, toAISDKDefaultOutput };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=toolOutputConversion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolOutputConversion.js","names":[],"sources":["../src/toolOutputConversion.ts"],"sourcesContent":["import type { JSONValue } from \"ai\";\nimport type { ToolModelContentPart } from \"assistant-stream\";\n\nexport const toAISDKContent = (parts: readonly ToolModelContentPart[]) => ({\n type: \"content\" as const,\n value: parts.map((part) => {\n if (part.type === \"text\") {\n return { type: \"text\" as const, text: part.text };\n }\n const isImage = part.mediaType.startsWith(\"image/\");\n return isImage\n ? {\n type: \"image-data\" as const,\n data: part.data,\n mediaType: part.mediaType,\n }\n : {\n type: \"file-data\" as const,\n data: part.data,\n mediaType: part.mediaType,\n ...(part.filename !== undefined && { filename: part.filename }),\n };\n }),\n});\n\nexport const toAISDKDefaultOutput = (output: unknown) =>\n typeof output === \"string\"\n ? { type: \"text\" as const, value: output }\n : { type: \"json\" as const, value: (output ?? null) as JSONValue };\n"],"mappings":";AAGA,MAAa,kBAAkB,WAA4C;CACzE,MAAM;CACN,OAAO,MAAM,KAAK,SAAS;EACzB,IAAI,KAAK,SAAS,QAChB,OAAO;GAAE,MAAM;GAAiB,MAAM,KAAK;EAAK;EAGlD,OADgB,KAAK,UAAU,WAAW,QAC7B,IACT;GACE,MAAM;GACN,MAAM,KAAK;GACX,WAAW,KAAK;EAClB,IACA;GACE,MAAM;GACN,MAAM,KAAK;GACX,WAAW,KAAK;GAChB,GAAI,KAAK,aAAa,KAAA,KAAa,EAAE,UAAU,KAAK,SAAS;EAC/D;CACN,CAAC;AACH;AAEA,MAAa,wBAAwB,WACnC,OAAO,WAAW,WACd;CAAE,MAAM;CAAiB,OAAO;AAAO,IACvC;CAAE,MAAM;CAAiB,OAAQ,UAAU;AAAmB"}
|
|
@@ -65,8 +65,9 @@ const useAISDKRuntime = (chatHelpers, adapter = {}) => {
|
|
|
65
65
|
if (!isToolUIPart(part)) return part;
|
|
66
66
|
if (part.state === "output-available" || part.state === "output-error") return part;
|
|
67
67
|
hasChanges = true;
|
|
68
|
+
const { approval: _approval, ...rest } = part;
|
|
68
69
|
return {
|
|
69
|
-
...
|
|
70
|
+
...rest,
|
|
70
71
|
state: "output-error",
|
|
71
72
|
errorText: "User cancelled tool call by sending a new message."
|
|
72
73
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAISDKRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useAISDKRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart, generateId } from \"ai\";\nimport {\n useExternalStoreRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport type { ToolExecutionStatus } from \"@assistant-ui/core\";\nimport type {\n ExternalStoreAdapter,\n ExternalStoreSharedOptions,\n ThreadHistoryAdapter,\n AssistantRuntime,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatItem,\n MessageFormatRepository,\n AppendMessage,\n RunConfig,\n McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport {\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { sliceMessagesUntil } from \"../utils/sliceMessagesUntil\";\nimport { toCreateMessage } from \"../utils/toCreateMessage\";\nimport { vercelAttachmentAdapter } from \"../utils/vercelAttachmentAdapter\";\nimport { getVercelAIMessages } from \"../getVercelAIMessages\";\nimport { AISDKMessageConverter } from \"../utils/convertMessage\";\nimport { wrapModelContentEnvelope } from \"../../modelContentEnvelope\";\nimport {\n type AISDKStorageFormat,\n aiSDKV6FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport {\n useExternalHistory,\n toExportedMessageRepository,\n} from \"./useExternalHistory\";\nimport { useStreamingTiming } from \"./useStreamingTiming\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nconst toUIMessage = <UI_MESSAGE extends UIMessage>(\n createMessage: CreateUIMessage<UI_MESSAGE>,\n fallbackRole: UI_MESSAGE[\"role\"],\n): UI_MESSAGE =>\n ({\n ...createMessage,\n id: createMessage.id ?? generateId(),\n role: createMessage.role ?? fallbackRole,\n }) as UI_MESSAGE;\n\nexport type AISDKRuntimeAdapter = ExternalStoreSharedOptions & {\n adapters?:\n | (NonNullable<ExternalStoreAdapter[\"adapters\"]> & {\n history?: ThreadHistoryAdapter | undefined;\n })\n | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n /**\n * Whether to automatically cancel pending interactive tool calls when the user sends a new message.\n *\n * When enabled (default), the pending tool calls will be marked as failed with an error message\n * indicating the user cancelled the tool call by sending a new message.\n *\n * @default true\n */\n cancelPendingToolCallsOnSend?: boolean | undefined;\n /**\n * Called when `runtime.thread.resumeRun(config)` is invoked.\n *\n * When omitted, `resumeRun` throws `\"Runtime does not support resuming runs.\"`.\n * Provide this to bridge resume invocations into a custom replay channel\n * (for example, an SSE reconnect endpoint keyed by turn id).\n */\n onResume?: ExternalStoreAdapter[\"onResume\"];\n};\n\nexport const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>,\n adapter: AISDKRuntimeAdapter = {},\n) => {\n const {\n adapters,\n toCreateMessage: customToCreateMessage,\n cancelPendingToolCallsOnSend = true,\n onResume,\n } = adapter;\n const contextAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const toolArgsKeyOrderCacheRef = useRef<Map<string, Map<string, string[]>>>(\n new Map(),\n );\n const toolLastInputCacheRef = useRef<Map<string, ReadonlyJSONObject>>(\n new Map(),\n );\n const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());\n const lastRunConfigRef = useRef<RunConfig | undefined>(undefined);\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const isRunning =\n chatHelpers.status === \"submitted\" ||\n chatHelpers.status === \"streaming\" ||\n hasExecutingTools;\n\n const messageTiming = useStreamingTiming(chatHelpers.messages, isRunning);\n\n // Flag the streaming message optimistic: its id can be swapped for a server\n // id mid-run, and the repository then drops the orphaned pre-swap id (#4037).\n const lastMessage = chatHelpers.messages.at(-1);\n const optimisticMessageId =\n isRunning && lastMessage?.role === \"assistant\" ? lastMessage.id : undefined;\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n messageTiming,\n toolArgsKeyOrderCache: toolArgsKeyOrderCacheRef.current,\n toolLastInputCache: toolLastInputCacheRef.current,\n mcpAppMetadataCache: mcpAppMetadataCacheRef.current,\n ...(optimisticMessageId && { optimisticMessageId }),\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, messageTiming, optimisticMessageId, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const isLoading = useExternalHistory(\n runtimeRef,\n adapters?.history ?? contextAdapters?.history,\n AISDKMessageConverter.toThreadMessages as (\n messages: UI_MESSAGE[],\n ) => ThreadMessage[],\n aiSDKV6FormatAdapter as MessageFormatAdapter<\n UI_MESSAGE,\n AISDKStorageFormat\n >,\n (messages) => {\n chatHelpers.setMessages(messages);\n },\n );\n\n const completePendingToolCalls = async () => {\n if (!cancelPendingToolCallsOnSend) return;\n\n // The runtime auto-aborts in-flight tool invocations when a new run\n // is dispatched (append() / startRun()). All we need to do here is\n // mark any tool without a result as cancelled in the UI message list.\n\n // Mark any tool without a result as cancelled (uses setMessages to avoid triggering sendAutomaticallyWhen)\n chatHelpers.setMessages((messages) => {\n const lastMessage = messages.at(-1);\n if (lastMessage?.role !== \"assistant\") return messages;\n\n let hasChanges = false;\n const parts = lastMessage.parts?.map((part) => {\n if (!isToolUIPart(part)) return part;\n if (part.state === \"output-available\" || part.state === \"output-error\")\n return part;\n\n hasChanges = true;\n return {\n ...part,\n state: \"output-error\" as const,\n errorText: \"User cancelled tool call by sending a new message.\",\n };\n });\n\n if (!hasChanges) return messages;\n return [...messages.slice(0, -1), { ...lastMessage, parts }];\n });\n };\n\n const runtime = useExternalStoreRuntime({\n isRunning,\n messages,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n setMessages: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onImport: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onExportExternalState: (): MessageFormatRepository<UI_MESSAGE> => {\n const exported = runtimeRef.current.thread.export();\n\n const expandedMessages: MessageFormatItem<UI_MESSAGE>[] = [];\n const lastInnerIdMap = new Map<string, string>();\n\n for (const item of exported.messages) {\n const innerMessages = getExternalStoreMessages<UI_MESSAGE>(\n item.message,\n );\n let parentId =\n item.parentId != null\n ? (lastInnerIdMap.get(item.parentId) ?? item.parentId)\n : null;\n for (const innerMessage of innerMessages) {\n expandedMessages.push({ parentId, message: innerMessage });\n parentId = aiSDKV6FormatAdapter.getId(innerMessage as UIMessage);\n }\n if (innerMessages.length > 0) {\n lastInnerIdMap.set(\n item.message.id,\n aiSDKV6FormatAdapter.getId(\n innerMessages[innerMessages.length - 1]! as UIMessage,\n ),\n );\n }\n }\n\n const result: MessageFormatRepository<UI_MESSAGE> = {\n messages: expandedMessages,\n };\n\n if (exported.headId != null) {\n result.headId = lastInnerIdMap.get(exported.headId) ?? exported.headId;\n }\n\n return result;\n },\n onLoadExternalState: (repo: MessageFormatRepository<UI_MESSAGE>) => {\n // Convert MessageFormatRepository to ExportedMessageRepository\n const exportedRepo = toExportedMessageRepository(\n AISDKMessageConverter.toThreadMessages,\n repo,\n );\n\n // Import into the thread's MessageRepository\n runtimeRef.current.thread.import(exportedRepo);\n },\n onCancel: async () => {\n chatHelpers.stop();\n },\n onNew: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...current,\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n await completePendingToolCalls();\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...sliceMessagesUntil(current, message.parentId),\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n chatHelpers.setMessages((current) =>\n sliceMessagesUntil(current, message.parentId),\n );\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n lastRunConfigRef.current = config.runConfig;\n const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);\n chatHelpers.setMessages(newMessages);\n\n await chatHelpers.regenerate({ metadata: config.runConfig });\n },\n onAddToolResult: ({\n toolCallId,\n toolName,\n result,\n isError,\n modelContent,\n }) => {\n const options = { metadata: lastRunConfigRef.current };\n if (isError) {\n chatHelpers.addToolOutput({\n state: \"output-error\",\n tool: toolName ?? toolCallId,\n toolCallId,\n errorText:\n typeof result === \"string\" ? result : JSON.stringify(result),\n options,\n });\n } else {\n const output =\n modelContent !== undefined\n ? wrapModelContentEnvelope(result, modelContent)\n : result;\n chatHelpers.addToolResult({\n tool: toolName,\n toolCallId,\n output,\n options,\n });\n }\n },\n onRespondToToolApproval: ({ approvalId, approved, reason }) => {\n void chatHelpers.addToolApprovalResponse({\n id: approvalId,\n approved,\n ...(reason != null && { reason }),\n options: { metadata: lastRunConfigRef.current },\n });\n },\n ...pickExternalStoreSharedOptions(adapter),\n ...(onResume && { onResume }),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAISDKRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useAISDKRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart, generateId } from \"ai\";\nimport {\n useExternalStoreRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport type { ToolExecutionStatus } from \"@assistant-ui/core\";\nimport type {\n ExternalStoreAdapter,\n ExternalStoreSharedOptions,\n ThreadHistoryAdapter,\n AssistantRuntime,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatItem,\n MessageFormatRepository,\n AppendMessage,\n RunConfig,\n McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport {\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { sliceMessagesUntil } from \"../utils/sliceMessagesUntil\";\nimport { toCreateMessage } from \"../utils/toCreateMessage\";\nimport { vercelAttachmentAdapter } from \"../utils/vercelAttachmentAdapter\";\nimport { getVercelAIMessages } from \"../getVercelAIMessages\";\nimport { AISDKMessageConverter } from \"../utils/convertMessage\";\nimport { wrapModelContentEnvelope } from \"../../modelContentEnvelope\";\nimport {\n type AISDKStorageFormat,\n aiSDKV6FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport {\n useExternalHistory,\n toExportedMessageRepository,\n} from \"./useExternalHistory\";\nimport { useStreamingTiming } from \"./useStreamingTiming\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nconst toUIMessage = <UI_MESSAGE extends UIMessage>(\n createMessage: CreateUIMessage<UI_MESSAGE>,\n fallbackRole: UI_MESSAGE[\"role\"],\n): UI_MESSAGE =>\n ({\n ...createMessage,\n id: createMessage.id ?? generateId(),\n role: createMessage.role ?? fallbackRole,\n }) as UI_MESSAGE;\n\nexport type AISDKRuntimeAdapter = ExternalStoreSharedOptions & {\n adapters?:\n | (NonNullable<ExternalStoreAdapter[\"adapters\"]> & {\n history?: ThreadHistoryAdapter | undefined;\n })\n | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n /**\n * Whether to automatically cancel pending interactive tool calls when the user sends a new message.\n *\n * When enabled (default), the pending tool calls will be marked as failed with an error message\n * indicating the user cancelled the tool call by sending a new message.\n *\n * @default true\n */\n cancelPendingToolCallsOnSend?: boolean | undefined;\n /**\n * Called when `runtime.thread.resumeRun(config)` is invoked.\n *\n * When omitted, `resumeRun` throws `\"Runtime does not support resuming runs.\"`.\n * Provide this to bridge resume invocations into a custom replay channel\n * (for example, an SSE reconnect endpoint keyed by turn id).\n */\n onResume?: ExternalStoreAdapter[\"onResume\"];\n};\n\nexport const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>,\n adapter: AISDKRuntimeAdapter = {},\n) => {\n const {\n adapters,\n toCreateMessage: customToCreateMessage,\n cancelPendingToolCallsOnSend = true,\n onResume,\n } = adapter;\n const contextAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const toolArgsKeyOrderCacheRef = useRef<Map<string, Map<string, string[]>>>(\n new Map(),\n );\n const toolLastInputCacheRef = useRef<Map<string, ReadonlyJSONObject>>(\n new Map(),\n );\n const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());\n const lastRunConfigRef = useRef<RunConfig | undefined>(undefined);\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const isRunning =\n chatHelpers.status === \"submitted\" ||\n chatHelpers.status === \"streaming\" ||\n hasExecutingTools;\n\n const messageTiming = useStreamingTiming(chatHelpers.messages, isRunning);\n\n // Flag the streaming message optimistic: its id can be swapped for a server\n // id mid-run, and the repository then drops the orphaned pre-swap id (#4037).\n const lastMessage = chatHelpers.messages.at(-1);\n const optimisticMessageId =\n isRunning && lastMessage?.role === \"assistant\" ? lastMessage.id : undefined;\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n messageTiming,\n toolArgsKeyOrderCache: toolArgsKeyOrderCacheRef.current,\n toolLastInputCache: toolLastInputCacheRef.current,\n mcpAppMetadataCache: mcpAppMetadataCacheRef.current,\n ...(optimisticMessageId && { optimisticMessageId }),\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, messageTiming, optimisticMessageId, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const isLoading = useExternalHistory(\n runtimeRef,\n adapters?.history ?? contextAdapters?.history,\n AISDKMessageConverter.toThreadMessages as (\n messages: UI_MESSAGE[],\n ) => ThreadMessage[],\n aiSDKV6FormatAdapter as MessageFormatAdapter<\n UI_MESSAGE,\n AISDKStorageFormat\n >,\n (messages) => {\n chatHelpers.setMessages(messages);\n },\n );\n\n const completePendingToolCalls = async () => {\n if (!cancelPendingToolCallsOnSend) return;\n\n // The runtime auto-aborts in-flight tool invocations when a new run\n // is dispatched (append() / startRun()). All we need to do here is\n // mark any tool without a result as cancelled in the UI message list.\n\n // Mark any tool without a result as cancelled (uses setMessages to avoid triggering sendAutomaticallyWhen)\n chatHelpers.setMessages((messages) => {\n const lastMessage = messages.at(-1);\n if (lastMessage?.role !== \"assistant\") return messages;\n\n let hasChanges = false;\n const parts = lastMessage.parts?.map((part) => {\n if (!isToolUIPart(part)) return part;\n if (part.state === \"output-available\" || part.state === \"output-error\")\n return part;\n\n hasChanges = true;\n const { approval: _approval, ...rest } = part;\n return {\n ...rest,\n state: \"output-error\" as const,\n errorText: \"User cancelled tool call by sending a new message.\",\n };\n });\n\n if (!hasChanges) return messages;\n return [...messages.slice(0, -1), { ...lastMessage, parts }];\n });\n };\n\n const runtime = useExternalStoreRuntime({\n isRunning,\n messages,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n setMessages: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onImport: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onExportExternalState: (): MessageFormatRepository<UI_MESSAGE> => {\n const exported = runtimeRef.current.thread.export();\n\n const expandedMessages: MessageFormatItem<UI_MESSAGE>[] = [];\n const lastInnerIdMap = new Map<string, string>();\n\n for (const item of exported.messages) {\n const innerMessages = getExternalStoreMessages<UI_MESSAGE>(\n item.message,\n );\n let parentId =\n item.parentId != null\n ? (lastInnerIdMap.get(item.parentId) ?? item.parentId)\n : null;\n for (const innerMessage of innerMessages) {\n expandedMessages.push({ parentId, message: innerMessage });\n parentId = aiSDKV6FormatAdapter.getId(innerMessage as UIMessage);\n }\n if (innerMessages.length > 0) {\n lastInnerIdMap.set(\n item.message.id,\n aiSDKV6FormatAdapter.getId(\n innerMessages[innerMessages.length - 1]! as UIMessage,\n ),\n );\n }\n }\n\n const result: MessageFormatRepository<UI_MESSAGE> = {\n messages: expandedMessages,\n };\n\n if (exported.headId != null) {\n result.headId = lastInnerIdMap.get(exported.headId) ?? exported.headId;\n }\n\n return result;\n },\n onLoadExternalState: (repo: MessageFormatRepository<UI_MESSAGE>) => {\n // Convert MessageFormatRepository to ExportedMessageRepository\n const exportedRepo = toExportedMessageRepository(\n AISDKMessageConverter.toThreadMessages,\n repo,\n );\n\n // Import into the thread's MessageRepository\n runtimeRef.current.thread.import(exportedRepo);\n },\n onCancel: async () => {\n chatHelpers.stop();\n },\n onNew: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...current,\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n await completePendingToolCalls();\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...sliceMessagesUntil(current, message.parentId),\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n chatHelpers.setMessages((current) =>\n sliceMessagesUntil(current, message.parentId),\n );\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n lastRunConfigRef.current = config.runConfig;\n const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);\n chatHelpers.setMessages(newMessages);\n\n await chatHelpers.regenerate({ metadata: config.runConfig });\n },\n onAddToolResult: ({\n toolCallId,\n toolName,\n result,\n isError,\n modelContent,\n }) => {\n const options = { metadata: lastRunConfigRef.current };\n if (isError) {\n chatHelpers.addToolOutput({\n state: \"output-error\",\n tool: toolName ?? toolCallId,\n toolCallId,\n errorText:\n typeof result === \"string\" ? result : JSON.stringify(result),\n options,\n });\n } else {\n const output =\n modelContent !== undefined\n ? wrapModelContentEnvelope(result, modelContent)\n : result;\n chatHelpers.addToolResult({\n tool: toolName,\n toolCallId,\n output,\n options,\n });\n }\n },\n onRespondToToolApproval: ({ approvalId, approved, reason }) => {\n void chatHelpers.addToolApprovalResponse({\n id: approvalId,\n approved,\n ...(reason != null && { reason }),\n options: { metadata: lastRunConfigRef.current },\n });\n },\n ...pickExternalStoreSharedOptions(adapter),\n ...(onResume && { onResume }),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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"}
|