@assistant-ui/ai-sdk 0.0.10 → 0.0.12
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/converters/convertMessage.d.ts +8 -6
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +106 -28
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/toolApprovalAnswers.d.ts +3 -0
- package/dist/converters/toolApprovalAnswers.d.ts.map +1 -0
- package/dist/converters/toolApprovalAnswers.js +17 -0
- package/dist/converters/toolApprovalAnswers.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.d.ts.map +1 -1
- package/dist/index.native.js +1 -1
- package/dist/model-context/injectInteractableContext.d.ts +3 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectInteractableContext.js +3 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +1 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.js +1 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +5 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +37 -4
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/DynamicChatTransport.d.ts +49 -0
- package/dist/runtime/DynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/DynamicChatTransport.js +147 -0
- package/dist/runtime/DynamicChatTransport.js.map +1 -0
- package/dist/runtime/getResumableAdapter.d.ts +5 -0
- package/dist/runtime/getResumableAdapter.d.ts.map +1 -0
- package/dist/runtime/getResumableAdapter.js +12 -0
- package/dist/runtime/getResumableAdapter.js.map +1 -0
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/toolHistoryCodec.d.ts +20 -0
- package/dist/runtime/toolHistoryCodec.d.ts.map +1 -0
- package/dist/runtime/toolHistoryCodec.js +107 -0
- package/dist/runtime/toolHistoryCodec.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +4 -1
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +154 -155
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +14 -2
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatRuntime.js +12 -3
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts +3 -2
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +56 -36
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useDynamicChatTransport.d.ts +4 -0
- package/dist/runtime/useDynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/useDynamicChatTransport.js +64 -0
- package/dist/runtime/useDynamicChatTransport.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +11 -105
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.js +1 -1
- package/dist/runtime/useStreamingTiming.js +2 -2
- package/dist/runtime/useStreamingTiming.js.map +1 -1
- package/dist/tools/generativeTools.d.ts +2 -1
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.js +17 -6
- package/dist/tools/generativeTools.js.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.js +1 -2
- package/dist/utils/sliceMessagesUntil.js.map +1 -1
- package/package.json +12 -10
- package/src/converters/convertMessage.test.ts +350 -2
- package/src/converters/convertMessage.tool-args-status.test.tsx +185 -0
- package/src/converters/convertMessage.ts +154 -23
- package/src/converters/toCreateMessage.test.ts +27 -0
- package/src/converters/toolApprovalAnswers.ts +27 -0
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
- package/src/model-context/injectInteractableContext.ts +3 -0
- package/src/model-context/injectQuoteContext.ts +1 -0
- package/src/runtime/AISDKChat.integration.test.tsx +57 -2
- package/src/runtime/AISDKThreads.cloud.test.ts +3 -0
- package/src/runtime/AISDKThreads.test.ts +181 -0
- package/src/runtime/AISDKThreads.ts +31 -4
- package/src/runtime/DynamicChatTransport.test.ts +203 -0
- package/src/runtime/DynamicChatTransport.ts +273 -0
- package/src/runtime/__tests__/controlled-transport.ts +3 -0
- package/src/runtime/getResumableAdapter.ts +16 -0
- package/src/runtime/toolHistoryCodec.test.ts +161 -0
- package/src/runtime/toolHistoryCodec.ts +207 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +12 -0
- package/src/runtime/useAISDKRuntime.reload.test.tsx +219 -0
- package/src/runtime/useAISDKRuntime.test.ts +538 -5
- package/src/runtime/useAISDKRuntime.ts +161 -51
- package/src/runtime/useChatRuntime.integration.test.tsx +319 -4
- package/src/runtime/useChatRuntime.local-storage.test.tsx +123 -0
- package/src/runtime/useChatRuntime.test.ts +107 -1
- package/src/runtime/useChatRuntime.ts +26 -4
- package/src/runtime/useChatThread.binding.test.tsx +143 -0
- package/src/runtime/useChatThread.ts +98 -81
- package/src/runtime/useDynamicChatTransport.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +205 -0
- package/src/runtime/useExternalHistory.ts +14 -206
- package/src/runtime/useStreamingTiming.ts +2 -2
- package/src/tools/generativeTools.test.ts +190 -2
- package/src/tools/generativeTools.ts +28 -8
- package/src/utils/sliceMessagesUntil.test.ts +2 -6
- package/src/utils/sliceMessagesUntil.ts +1 -5
- package/dist/converters/modelContentEnvelope.d.ts +0 -14
- package/dist/converters/modelContentEnvelope.d.ts.map +0 -1
- package/dist/converters/modelContentEnvelope.js +0 -22
- package/dist/converters/modelContentEnvelope.js.map +0 -1
- package/dist/converters/toolOutputConversion.d.ts +0 -26
- package/dist/converters/toolOutputConversion.d.ts.map +0 -1
- package/dist/converters/toolOutputConversion.js +0 -31
- package/dist/converters/toolOutputConversion.js.map +0 -1
- package/dist/tools/frontendTools.d.ts +0 -30
- package/dist/tools/frontendTools.d.ts.map +0 -1
- package/dist/tools/frontendTools.js +0 -33
- package/dist/tools/frontendTools.js.map +0 -1
- package/src/converters/modelContentEnvelope.ts +0 -41
- package/src/converters/toolOutputConversion.ts +0 -26
- package/src/tools/frontendTools.test.ts +0 -205
- package/src/tools/frontendTools.ts +0 -83
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
//#region src/converters/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
|
-
const mediaType = typeof part.mediaType === "string" ? part.mediaType : "application/octet-stream";
|
|
10
|
-
return {
|
|
11
|
-
type: "file",
|
|
12
|
-
data: {
|
|
13
|
-
type: "data",
|
|
14
|
-
data: part.data
|
|
15
|
-
},
|
|
16
|
-
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"toolOutputConversion.js","names":["JSONValue","ToolModelContentPart","toAISDKContent","parts","type","const","value","map","part","text","mediaType","data","filename","undefined","toAISDKDefaultOutput","output"],"sources":["../../src/converters/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 mediaType =\n typeof part.mediaType === \"string\"\n ? part.mediaType\n : \"application/octet-stream\";\n return {\n type: \"file\" as const,\n data: { type: \"data\" as const, data: part.data },\n 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,MAAaE,kBAAkBC,WAA4C;CACzEC,MAAM;CACNE,OAAOH,MAAMI,KAAKC,SAAS;EACzB,IAAIA,KAAKJ,SAAS,QAChB,OAAO;GAAEA,MAAM;GAAiBK,MAAMD,KAAKC;EAAK;EAElD,MAAMC,YACJ,OAAOF,KAAKE,cAAc,WACtBF,KAAKE,YACL;EACN,OAAO;GACLN,MAAM;GACNO,MAAM;IAAEP,MAAM;IAAiBO,MAAMH,KAAKG;GAAK;GAC/CD;GACA,GAAIF,KAAKI,aAAaC,KAAAA,KAAa,EAAED,UAAUJ,KAAKI,SAAS;EAC/D;CACF,CAAC;AACH;AAEA,MAAaE,wBAAwBC,WACnC,OAAOA,WAAW,WACd;CAAEX,MAAM;CAAiBE,OAAOS;AAAO,IACvC;CAAEX,MAAM;CAAiBE,OAAQS,UAAU;AAAmB"}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type ToolSet } from "ai";
|
|
2
|
-
import type { ToolJSONSchema } from "assistant-stream";
|
|
3
|
-
/** Frontend tool definitions uploaded by AssistantChatTransport. */
|
|
4
|
-
export type FrontendTools = Record<string, ToolJSONSchema>;
|
|
5
|
-
export declare const defaultToModelOutput: ({ output }: {
|
|
6
|
-
output: unknown;
|
|
7
|
-
}) => {
|
|
8
|
-
type: "content";
|
|
9
|
-
value: ({
|
|
10
|
-
type: "text";
|
|
11
|
-
text: string;
|
|
12
|
-
} | {
|
|
13
|
-
filename?: string;
|
|
14
|
-
type: "file";
|
|
15
|
-
data: {
|
|
16
|
-
type: "data";
|
|
17
|
-
data: string;
|
|
18
|
-
};
|
|
19
|
-
mediaType: string;
|
|
20
|
-
text?: never;
|
|
21
|
-
})[];
|
|
22
|
-
} | {
|
|
23
|
-
type: "text";
|
|
24
|
-
value: string;
|
|
25
|
-
} | {
|
|
26
|
-
type: "json";
|
|
27
|
-
value: import("ai").JSONValue;
|
|
28
|
-
};
|
|
29
|
-
export declare const frontendTools: (tools: FrontendTools) => ToolSet;
|
|
30
|
-
//# sourceMappingURL=frontendTools.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frontendTools.d.ts","sourceRoot":"","sources":["../../src/tools/frontendTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOvD,oEAAoE;AACpE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE3D,eAAO,MAAM,oBAAoB,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;CAMnE,CAAC;AAiDF,eAAO,MAAM,aAAa,GAAI,OAAO,aAAa,KAAG,OAgBpD,CAAC"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { unwrapModelContentEnvelope } from "../converters/modelContentEnvelope.js";
|
|
2
|
-
import { toAISDKContent, toAISDKDefaultOutput } from "../converters/toolOutputConversion.js";
|
|
3
|
-
import { jsonSchema } from "ai";
|
|
4
|
-
//#region src/tools/frontendTools.ts
|
|
5
|
-
const defaultToModelOutput = ({ output }) => {
|
|
6
|
-
const { result, modelContent } = unwrapModelContentEnvelope(output);
|
|
7
|
-
if (modelContent !== void 0) return toAISDKContent(modelContent);
|
|
8
|
-
return toAISDKDefaultOutput(result);
|
|
9
|
-
};
|
|
10
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
-
function validateFrontendTool(name, tool) {
|
|
12
|
-
if (!isPlainObject(tool)) throw new Error(`frontendTools() expected tool "${name}" to be an object with a JSON Schema parameters object.`);
|
|
13
|
-
if (!isPlainObject(tool.parameters)) throw new Error(`frontendTools() expected tool "${name}" to include a JSON Schema parameters object.`);
|
|
14
|
-
if (tool.description !== void 0 && typeof tool.description !== "string") throw new Error(`frontendTools() expected tool "${name}" description to be a string.`);
|
|
15
|
-
if (tool.providerOptions !== void 0 && !isPlainObject(tool.providerOptions)) throw new Error(`frontendTools() expected tool "${name}" providerOptions to be an object.`);
|
|
16
|
-
}
|
|
17
|
-
function validateFrontendTools(tools) {
|
|
18
|
-
if (!isPlainObject(tools)) throw new Error("frontendTools() expected tools to be an object keyed by tool name.");
|
|
19
|
-
for (const [name, tool] of Object.entries(tools)) validateFrontendTool(name, tool);
|
|
20
|
-
}
|
|
21
|
-
const frontendTools = (tools) => {
|
|
22
|
-
validateFrontendTools(tools);
|
|
23
|
-
return Object.fromEntries(Object.entries(tools).map(([name, tool]) => [name, {
|
|
24
|
-
...tool.description !== void 0 && { description: tool.description },
|
|
25
|
-
inputSchema: jsonSchema(tool.parameters),
|
|
26
|
-
toModelOutput: defaultToModelOutput,
|
|
27
|
-
...tool.providerOptions && { providerOptions: tool.providerOptions }
|
|
28
|
-
}]));
|
|
29
|
-
};
|
|
30
|
-
//#endregion
|
|
31
|
-
export { defaultToModelOutput, frontendTools };
|
|
32
|
-
|
|
33
|
-
//# sourceMappingURL=frontendTools.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frontendTools.js","names":["jsonSchema","ToolSet","ToolJSONSchema","unwrapModelContentEnvelope","toAISDKContent","toAISDKDefaultOutput","FrontendTools","Record","defaultToModelOutput","output","result","modelContent","undefined","isPlainObject","value","Array","isArray","validateFrontendTool","name","tool","Error","parameters","description","providerOptions","validateFrontendTools","tools","Object","entries","frontendTools","fromEntries","map","inputSchema","toModelOutput"],"sources":["../../src/tools/frontendTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { ToolJSONSchema } from \"assistant-stream\";\nimport { unwrapModelContentEnvelope } from \"../converters/modelContentEnvelope\";\nimport {\n toAISDKContent,\n toAISDKDefaultOutput,\n} from \"../converters/toolOutputConversion\";\n\n/** Frontend tool definitions uploaded by AssistantChatTransport. */\nexport type FrontendTools = Record<string, ToolJSONSchema>;\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\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction validateFrontendTool(\n name: string,\n tool: unknown,\n): asserts tool is ToolJSONSchema {\n if (!isPlainObject(tool)) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" to be an object with a JSON Schema parameters object.`,\n );\n }\n\n if (!isPlainObject(tool.parameters)) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" to include a JSON Schema parameters object.`,\n );\n }\n\n if (tool.description !== undefined && typeof tool.description !== \"string\") {\n throw new Error(\n `frontendTools() expected tool \"${name}\" description to be a string.`,\n );\n }\n\n if (\n tool.providerOptions !== undefined &&\n !isPlainObject(tool.providerOptions)\n ) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" providerOptions to be an object.`,\n );\n }\n}\n\nfunction validateFrontendTools(tools: unknown): asserts tools is FrontendTools {\n if (!isPlainObject(tools)) {\n throw new Error(\n \"frontendTools() expected tools to be an object keyed by tool name.\",\n );\n }\n\n for (const [name, tool] of Object.entries(tools)) {\n validateFrontendTool(name, tool);\n }\n}\n\nexport const frontendTools = (tools: FrontendTools): ToolSet => {\n validateFrontendTools(tools);\n\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => [\n name,\n {\n ...(tool.description !== undefined && {\n description: tool.description,\n }),\n inputSchema: jsonSchema(tool.parameters),\n toModelOutput: defaultToModelOutput,\n ...(tool.providerOptions && { providerOptions: tool.providerOptions }),\n },\n ]),\n ) as ToolSet;\n};\n"],"mappings":";;;;AAWA,MAAaQ,wBAAwB,EAAEC,aAAkC;CACvE,MAAM,EAAEC,QAAQC,iBAAiBR,2BAA2BM,MAAM;CAClE,IAAIE,iBAAiBC,KAAAA,GACnB,OAAOR,eAAeO,YAAY;CAEpC,OAAON,qBAAqBK,MAAM;AACpC;AAEA,MAAMG,iBAAiBC,UACrB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,QAAQF,KAAK;AAErE,SAASG,qBACPC,MACAC,MACgC;CAChC,IAAI,CAACN,cAAcM,IAAI,GACrB,MAAM,IAAIC,MACR,kCAAkCF,KAAI,wDACxC;CAGF,IAAI,CAACL,cAAcM,KAAKE,UAAU,GAChC,MAAM,IAAID,MACR,kCAAkCF,KAAI,8CACxC;CAGF,IAAIC,KAAKG,gBAAgBV,KAAAA,KAAa,OAAOO,KAAKG,gBAAgB,UAChE,MAAM,IAAIF,MACR,kCAAkCF,KAAI,8BACxC;CAGF,IACEC,KAAKI,oBAAoBX,KAAAA,KACzB,CAACC,cAAcM,KAAKI,eAAe,GAEnC,MAAM,IAAIH,MACR,kCAAkCF,KAAI,mCACxC;AAEJ;AAEA,SAASM,sBAAsBC,OAAgD;CAC7E,IAAI,CAACZ,cAAcY,KAAK,GACtB,MAAM,IAAIL,MACR,oEACF;CAGF,KAAK,MAAM,CAACF,MAAMC,SAASO,OAAOC,QAAQF,KAAK,GAC7CR,qBAAqBC,MAAMC,IAAI;AAEnC;AAEA,MAAaS,iBAAiBH,UAAkC;CAC9DD,sBAAsBC,KAAK;CAE3B,OAAOC,OAAOG,YACZH,OAAOC,QAAQF,KAAK,CAAC,CAACK,KAAK,CAACZ,MAAMC,UAAU,CAC1CD,MACA;EACE,GAAIC,KAAKG,gBAAgBV,KAAAA,KAAa,EACpCU,aAAaH,KAAKG,YACpB;EACAS,aAAa/B,WAAWmB,KAAKE,UAAU;EACvCW,eAAexB;EACf,GAAIW,KAAKI,mBAAmB,EAAEA,iBAAiBJ,KAAKI,gBAAgB;CACtE,CAAC,CACF,CACH;AACF"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ToolModelContentPart } from "assistant-stream";
|
|
2
|
-
|
|
3
|
-
const ENVELOPE_KEY = "__aui_modelContent";
|
|
4
|
-
|
|
5
|
-
export type ModelContentEnvelope<TResult = unknown> = {
|
|
6
|
-
readonly [ENVELOPE_KEY]: readonly ToolModelContentPart[];
|
|
7
|
-
readonly value: TResult;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export function isModelContentEnvelope<TResult = unknown>(
|
|
11
|
-
value: TResult | ModelContentEnvelope<TResult>,
|
|
12
|
-
): value is ModelContentEnvelope<TResult> {
|
|
13
|
-
return (
|
|
14
|
-
value != null &&
|
|
15
|
-
typeof value === "object" &&
|
|
16
|
-
ENVELOPE_KEY in value &&
|
|
17
|
-
Array.isArray((value as Record<string, unknown>)[ENVELOPE_KEY])
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function wrapModelContentEnvelope<TResult>(
|
|
22
|
-
result: TResult,
|
|
23
|
-
modelContent: readonly ToolModelContentPart[],
|
|
24
|
-
): ModelContentEnvelope<TResult> {
|
|
25
|
-
return { [ENVELOPE_KEY]: modelContent, value: result };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function unwrapModelContentEnvelope<TResult>(
|
|
29
|
-
output: TResult | ModelContentEnvelope<TResult>,
|
|
30
|
-
): {
|
|
31
|
-
result: TResult;
|
|
32
|
-
modelContent?: readonly ToolModelContentPart[];
|
|
33
|
-
} {
|
|
34
|
-
if (isModelContentEnvelope(output)) {
|
|
35
|
-
return {
|
|
36
|
-
result: output.value,
|
|
37
|
-
modelContent: output[ENVELOPE_KEY],
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return { result: output };
|
|
41
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { JSONValue } from "ai";
|
|
2
|
-
import type { ToolModelContentPart } from "assistant-stream";
|
|
3
|
-
|
|
4
|
-
export const toAISDKContent = (parts: readonly ToolModelContentPart[]) => ({
|
|
5
|
-
type: "content" as const,
|
|
6
|
-
value: parts.map((part) => {
|
|
7
|
-
if (part.type === "text") {
|
|
8
|
-
return { type: "text" as const, text: part.text };
|
|
9
|
-
}
|
|
10
|
-
const mediaType =
|
|
11
|
-
typeof part.mediaType === "string"
|
|
12
|
-
? part.mediaType
|
|
13
|
-
: "application/octet-stream";
|
|
14
|
-
return {
|
|
15
|
-
type: "file" as const,
|
|
16
|
-
data: { type: "data" as const, data: part.data },
|
|
17
|
-
mediaType,
|
|
18
|
-
...(part.filename !== undefined && { filename: part.filename }),
|
|
19
|
-
};
|
|
20
|
-
}),
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
export const toAISDKDefaultOutput = (output: unknown) =>
|
|
24
|
-
typeof output === "string"
|
|
25
|
-
? { type: "text" as const, value: output }
|
|
26
|
-
: { type: "json" as const, value: (output ?? null) as JSONValue };
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { frontendTools } from "./frontendTools";
|
|
3
|
-
import { wrapModelContentEnvelope } from "../converters/modelContentEnvelope";
|
|
4
|
-
|
|
5
|
-
describe("frontendTools", () => {
|
|
6
|
-
it("forwards description and inputSchema for each tool", () => {
|
|
7
|
-
const tools = frontendTools({
|
|
8
|
-
getWeather: {
|
|
9
|
-
description: "Get the weather",
|
|
10
|
-
parameters: {
|
|
11
|
-
type: "object",
|
|
12
|
-
properties: { city: { type: "string" } },
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
expect(tools.getWeather?.description).toBe("Get the weather");
|
|
18
|
-
expect(tools.getWeather?.inputSchema).toBeDefined();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("registers a toModelOutput that unwraps the modelContent envelope", async () => {
|
|
22
|
-
const tools = frontendTools({
|
|
23
|
-
readPdf: {
|
|
24
|
-
parameters: { type: "object" },
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const envelope = wrapModelContentEnvelope(
|
|
29
|
-
{ mediaType: "application/pdf", base64: "JVBERi0xLjQK" },
|
|
30
|
-
[
|
|
31
|
-
{ type: "text", text: "PDF contents:" },
|
|
32
|
-
{
|
|
33
|
-
type: "file",
|
|
34
|
-
data: "JVBERi0xLjQK",
|
|
35
|
-
mediaType: "application/pdf",
|
|
36
|
-
filename: "doc.pdf",
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const output = await tools.readPdf!.toModelOutput!({
|
|
42
|
-
toolCallId: "tc-1",
|
|
43
|
-
input: {},
|
|
44
|
-
output: envelope,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(output).toEqual({
|
|
48
|
-
type: "content",
|
|
49
|
-
value: [
|
|
50
|
-
{ type: "text", text: "PDF contents:" },
|
|
51
|
-
{
|
|
52
|
-
type: "file",
|
|
53
|
-
mediaType: "application/pdf",
|
|
54
|
-
data: { type: "data", data: "JVBERi0xLjQK" },
|
|
55
|
-
filename: "doc.pdf",
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("emits a file part for image media types", async () => {
|
|
62
|
-
const tools = frontendTools({
|
|
63
|
-
screenshot: {
|
|
64
|
-
parameters: { type: "object" },
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const envelope = wrapModelContentEnvelope(
|
|
69
|
-
{ mediaType: "image/png", base64: "iVBORw0KGgo=" },
|
|
70
|
-
[
|
|
71
|
-
{
|
|
72
|
-
type: "file",
|
|
73
|
-
data: "iVBORw0KGgo=",
|
|
74
|
-
mediaType: "image/png",
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const output = await tools.screenshot!.toModelOutput!({
|
|
80
|
-
toolCallId: "tc-1",
|
|
81
|
-
input: {},
|
|
82
|
-
output: envelope,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(output).toEqual({
|
|
86
|
-
type: "content",
|
|
87
|
-
value: [
|
|
88
|
-
{
|
|
89
|
-
type: "file",
|
|
90
|
-
data: { type: "data", data: "iVBORw0KGgo=" },
|
|
91
|
-
mediaType: "image/png",
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("falls back to a JSON output for plain (non-envelope) results", async () => {
|
|
98
|
-
const tools = frontendTools({
|
|
99
|
-
getWeather: {
|
|
100
|
-
parameters: { type: "object" },
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const output = await tools.getWeather!.toModelOutput!({
|
|
105
|
-
toolCallId: "tc-1",
|
|
106
|
-
input: {},
|
|
107
|
-
output: { temp: 72 },
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(output).toEqual({ type: "json", value: { temp: 72 } });
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("falls back to a text output for plain string results", async () => {
|
|
114
|
-
const tools = frontendTools({
|
|
115
|
-
echo: {
|
|
116
|
-
parameters: { type: "object" },
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const output = await tools.echo!.toModelOutput!({
|
|
121
|
-
toolCallId: "tc-1",
|
|
122
|
-
input: {},
|
|
123
|
-
output: "hello",
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
expect(output).toEqual({ type: "text", value: "hello" });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("forwards providerOptions verbatim when present", () => {
|
|
130
|
-
const tools = frontendTools({
|
|
131
|
-
getWeather: {
|
|
132
|
-
description: "Get the weather",
|
|
133
|
-
parameters: { type: "object", properties: {} },
|
|
134
|
-
providerOptions: { anthropic: { deferLoading: true } },
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(tools.getWeather?.providerOptions).toEqual({
|
|
139
|
-
anthropic: { deferLoading: true },
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("omits providerOptions when absent", () => {
|
|
144
|
-
const tools = frontendTools({
|
|
145
|
-
getWeather: {
|
|
146
|
-
parameters: { type: "object", properties: {} },
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
expect(tools.getWeather).not.toHaveProperty("providerOptions");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("names the malformed frontend tool when parameters are missing", () => {
|
|
154
|
-
expect(() =>
|
|
155
|
-
frontendTools({
|
|
156
|
-
getWeather: {
|
|
157
|
-
description: "Get the weather",
|
|
158
|
-
},
|
|
159
|
-
} as never),
|
|
160
|
-
).toThrow(
|
|
161
|
-
'frontendTools() expected tool "getWeather" to include a JSON Schema parameters object.',
|
|
162
|
-
);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("rejects non-object tools request bodies", () => {
|
|
166
|
-
expect(() => frontendTools(null as never)).toThrow(
|
|
167
|
-
"frontendTools() expected tools to be an object keyed by tool name.",
|
|
168
|
-
);
|
|
169
|
-
expect(() => frontendTools([] as never)).toThrow(
|
|
170
|
-
"frontendTools() expected tools to be an object keyed by tool name.",
|
|
171
|
-
);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("names frontend tool entries with malformed field types", () => {
|
|
175
|
-
expect(() =>
|
|
176
|
-
frontendTools({
|
|
177
|
-
getWeather: null,
|
|
178
|
-
} as never),
|
|
179
|
-
).toThrow(
|
|
180
|
-
'frontendTools() expected tool "getWeather" to be an object with a JSON Schema parameters object.',
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
expect(() =>
|
|
184
|
-
frontendTools({
|
|
185
|
-
getWeather: {
|
|
186
|
-
description: 123,
|
|
187
|
-
parameters: { type: "object" },
|
|
188
|
-
},
|
|
189
|
-
} as never),
|
|
190
|
-
).toThrow(
|
|
191
|
-
'frontendTools() expected tool "getWeather" description to be a string.',
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
expect(() =>
|
|
195
|
-
frontendTools({
|
|
196
|
-
getWeather: {
|
|
197
|
-
parameters: { type: "object" },
|
|
198
|
-
providerOptions: "anthropic",
|
|
199
|
-
},
|
|
200
|
-
} as never),
|
|
201
|
-
).toThrow(
|
|
202
|
-
'frontendTools() expected tool "getWeather" providerOptions to be an object.',
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { jsonSchema, type ToolSet } from "ai";
|
|
2
|
-
import type { ToolJSONSchema } from "assistant-stream";
|
|
3
|
-
import { unwrapModelContentEnvelope } from "../converters/modelContentEnvelope";
|
|
4
|
-
import {
|
|
5
|
-
toAISDKContent,
|
|
6
|
-
toAISDKDefaultOutput,
|
|
7
|
-
} from "../converters/toolOutputConversion";
|
|
8
|
-
|
|
9
|
-
/** Frontend tool definitions uploaded by AssistantChatTransport. */
|
|
10
|
-
export type FrontendTools = Record<string, ToolJSONSchema>;
|
|
11
|
-
|
|
12
|
-
export const defaultToModelOutput = ({ output }: { output: unknown }) => {
|
|
13
|
-
const { result, modelContent } = unwrapModelContentEnvelope(output);
|
|
14
|
-
if (modelContent !== undefined) {
|
|
15
|
-
return toAISDKContent(modelContent);
|
|
16
|
-
}
|
|
17
|
-
return toAISDKDefaultOutput(result);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
21
|
-
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
-
|
|
23
|
-
function validateFrontendTool(
|
|
24
|
-
name: string,
|
|
25
|
-
tool: unknown,
|
|
26
|
-
): asserts tool is ToolJSONSchema {
|
|
27
|
-
if (!isPlainObject(tool)) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`frontendTools() expected tool "${name}" to be an object with a JSON Schema parameters object.`,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!isPlainObject(tool.parameters)) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`frontendTools() expected tool "${name}" to include a JSON Schema parameters object.`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (tool.description !== undefined && typeof tool.description !== "string") {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`frontendTools() expected tool "${name}" description to be a string.`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
tool.providerOptions !== undefined &&
|
|
47
|
-
!isPlainObject(tool.providerOptions)
|
|
48
|
-
) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`frontendTools() expected tool "${name}" providerOptions to be an object.`,
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function validateFrontendTools(tools: unknown): asserts tools is FrontendTools {
|
|
56
|
-
if (!isPlainObject(tools)) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
"frontendTools() expected tools to be an object keyed by tool name.",
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const [name, tool] of Object.entries(tools)) {
|
|
63
|
-
validateFrontendTool(name, tool);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const frontendTools = (tools: FrontendTools): ToolSet => {
|
|
68
|
-
validateFrontendTools(tools);
|
|
69
|
-
|
|
70
|
-
return Object.fromEntries(
|
|
71
|
-
Object.entries(tools).map(([name, tool]) => [
|
|
72
|
-
name,
|
|
73
|
-
{
|
|
74
|
-
...(tool.description !== undefined && {
|
|
75
|
-
description: tool.description,
|
|
76
|
-
}),
|
|
77
|
-
inputSchema: jsonSchema(tool.parameters),
|
|
78
|
-
toModelOutput: defaultToModelOutput,
|
|
79
|
-
...(tool.providerOptions && { providerOptions: tool.providerOptions }),
|
|
80
|
-
},
|
|
81
|
-
]),
|
|
82
|
-
) as ToolSet;
|
|
83
|
-
};
|