@tanstack/ai 0.47.2 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/esm/activities/chat/adapter.d.ts +5 -4
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +4 -7
- package/dist/esm/activities/chat/index.js +201 -243
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +126 -31
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +38 -17
- package/dist/esm/activities/chat/stream/processor.js +197 -110
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +3 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +15 -10
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +6 -6
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +7 -8
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +59 -54
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/client.d.ts +8 -1
- package/dist/esm/client.js +7 -2
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +6 -2
- package/dist/esm/middlewares/otel.js +12 -5
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/stream-to-response.js +13 -6
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.js +4 -2
- package/dist/esm/stream-to-websocket.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
- package/dist/esm/strip-to-spec-middleware.js +24 -22
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/types.d.ts +82 -305
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
- package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
- package/dist/esm/utilities/ag-ui-usage.js +66 -0
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
- package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
- package/dist/esm/utilities/ag-ui-wire.js +71 -30
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +3 -3
- package/dist/esm/utilities/chat-params.js +10 -30
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/chunk-ids.d.ts +5 -0
- package/dist/esm/utilities/chunk-ids.js +25 -0
- package/dist/esm/utilities/chunk-ids.js.map +1 -0
- package/dist/esm/utilities/errors.d.ts +9 -0
- package/dist/esm/utilities/errors.js +13 -1
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/merge-metadata.d.ts +14 -0
- package/dist/esm/utilities/merge-metadata.js +43 -0
- package/dist/esm/utilities/merge-metadata.js.map +1 -0
- package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
- package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
- package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
- package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
- package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
- package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
- package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
- package/dist/esm/utilities/spec-event-keys.js +56 -0
- package/dist/esm/utilities/spec-event-keys.js.map +1 -0
- package/dist/esm/utilities/structured-output-events.d.ts +3 -3
- package/dist/esm/utilities/structured-output-events.js.map +1 -1
- package/package.json +3 -3
- package/src/activities/chat/adapter.ts +4 -4
- package/src/activities/chat/index.ts +398 -400
- package/src/activities/chat/messages.ts +183 -36
- package/src/activities/chat/stream/processor.ts +313 -239
- package/src/activities/chat/tools/tool-calls.ts +23 -22
- package/src/activities/generateVideo/index.ts +7 -6
- package/src/activities/stream-generation-result.ts +8 -12
- package/src/activities/summarize/chat-stream-summarize.ts +98 -70
- package/src/adapter-internals.ts +2 -0
- package/src/client.ts +13 -0
- package/src/index.ts +6 -0
- package/src/middlewares/otel.ts +16 -8
- package/src/stream-to-response.ts +16 -4
- package/src/stream-to-websocket.ts +3 -1
- package/src/strip-to-spec-middleware.ts +48 -24
- package/src/types.ts +109 -393
- package/src/utilities/adapter-yield-chunk.ts +30 -0
- package/src/utilities/ag-ui-usage.test.ts +194 -0
- package/src/utilities/ag-ui-usage.ts +148 -0
- package/src/utilities/ag-ui-wire.ts +149 -31
- package/src/utilities/chat-params.ts +22 -39
- package/src/utilities/chunk-ids.ts +24 -0
- package/src/utilities/errors.ts +23 -0
- package/src/utilities/merge-metadata.test.ts +117 -0
- package/src/utilities/merge-metadata.ts +59 -0
- package/src/utilities/normalize-stream-chunk.test.ts +423 -0
- package/src/utilities/normalize-stream-chunk.ts +186 -0
- package/src/utilities/reasoning-encrypted-value.ts +18 -0
- package/src/utilities/restore-inbound-chunk.test.ts +133 -0
- package/src/utilities/restore-inbound-chunk.ts +72 -0
- package/src/utilities/spec-event-keys.test.ts +34 -0
- package/src/utilities/spec-event-keys.ts +74 -0
- package/src/utilities/structured-output-events.ts +3 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/utilities/ag-ui-usage.ts
|
|
2
|
+
function definedDetails(value) {
|
|
3
|
+
return Object.keys(value).length > 0 ? value : void 0;
|
|
4
|
+
}
|
|
5
|
+
function withoutKey(value, key) {
|
|
6
|
+
const next = { ...value };
|
|
7
|
+
delete next[key];
|
|
8
|
+
return next;
|
|
9
|
+
}
|
|
10
|
+
function isTanstackUsage(usage) {
|
|
11
|
+
return typeof usage === "object" && usage != null && !Array.isArray(usage) && "promptTokens" in usage;
|
|
12
|
+
}
|
|
13
|
+
function toSpecTokenUsage(usage, options) {
|
|
14
|
+
const { promptTokens, completionTokens, totalTokens, promptTokensDetails, completionTokensDetails, ...rest } = usage;
|
|
15
|
+
const spec = {
|
|
16
|
+
...options?.provider !== void 0 ? { provider: options.provider } : {},
|
|
17
|
+
...options?.model !== void 0 ? { model: options.model } : {},
|
|
18
|
+
inputTokens: promptTokens,
|
|
19
|
+
outputTokens: completionTokens,
|
|
20
|
+
totalTokens
|
|
21
|
+
};
|
|
22
|
+
const cachedInputTokens = promptTokensDetails?.cachedTokens;
|
|
23
|
+
if (cachedInputTokens !== void 0) spec.cachedInputTokens = cachedInputTokens;
|
|
24
|
+
const reasoningTokens = completionTokensDetails?.reasoningTokens;
|
|
25
|
+
if (reasoningTokens !== void 0) spec.reasoningTokens = reasoningTokens;
|
|
26
|
+
const leftoverPrompt = promptTokensDetails ? definedDetails(withoutKey(promptTokensDetails, "cachedTokens")) : void 0;
|
|
27
|
+
const leftoverCompletion = completionTokensDetails ? definedDetails(withoutKey(completionTokensDetails, "reasoningTokens")) : void 0;
|
|
28
|
+
return {
|
|
29
|
+
usage: [spec],
|
|
30
|
+
leftover: definedDetails({
|
|
31
|
+
...rest,
|
|
32
|
+
...leftoverPrompt !== void 0 ? { promptTokensDetails: leftoverPrompt } : {},
|
|
33
|
+
...leftoverCompletion !== void 0 ? { completionTokensDetails: leftoverCompletion } : {}
|
|
34
|
+
})
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function rebuildTokenUsage(usage, leftover) {
|
|
38
|
+
if (isTanstackUsage(usage)) return usage;
|
|
39
|
+
if (Array.isArray(usage)) return fromSpecTokenUsage(usage, leftover);
|
|
40
|
+
return fromSpecTokenUsage(void 0, leftover);
|
|
41
|
+
}
|
|
42
|
+
function fromSpecTokenUsage(usage, leftover) {
|
|
43
|
+
const spec = usage?.[0];
|
|
44
|
+
if (spec == null && leftover == null) return;
|
|
45
|
+
const { promptTokensDetails: leftoverPromptDetails, completionTokensDetails: leftoverCompletionDetails, ...leftoverRest } = leftover ?? {};
|
|
46
|
+
const promptTokensDetails = definedDetails({
|
|
47
|
+
...spec?.cachedInputTokens !== void 0 ? { cachedTokens: spec.cachedInputTokens } : {},
|
|
48
|
+
...leftoverPromptDetails
|
|
49
|
+
});
|
|
50
|
+
const completionTokensDetails = definedDetails({
|
|
51
|
+
...spec?.reasoningTokens !== void 0 ? { reasoningTokens: spec.reasoningTokens } : {},
|
|
52
|
+
...leftoverCompletionDetails
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
promptTokens: spec?.inputTokens ?? 0,
|
|
56
|
+
completionTokens: spec?.outputTokens ?? 0,
|
|
57
|
+
totalTokens: spec?.totalTokens ?? 0,
|
|
58
|
+
...leftoverRest,
|
|
59
|
+
...promptTokensDetails !== void 0 ? { promptTokensDetails } : {},
|
|
60
|
+
...completionTokensDetails !== void 0 ? { completionTokensDetails } : {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { fromSpecTokenUsage, isTanstackUsage, rebuildTokenUsage, toSpecTokenUsage };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=ag-ui-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ag-ui-usage.js","names":[],"sources":["../../../src/utilities/ag-ui-usage.ts"],"sourcesContent":["import type { TokenUsage } from '../types'\n\n/** AG-UI spec `usage[]` item (provider/model labels + token counts only). */\nexport interface SpecTokenUsage {\n provider?: string\n model?: string\n inputTokens?: number\n outputTokens?: number\n totalTokens?: number\n reasoningTokens?: number\n cachedInputTokens?: number\n}\n\nexport interface ToSpecTokenUsageOptions {\n provider?: string\n model?: string\n}\n\n/** TokenUsage fields that have no AG-UI `usage[]` equivalent. */\nexport type TokenUsageLeftover = Omit<\n TokenUsage,\n 'promptTokens' | 'completionTokens' | 'totalTokens'\n>\n\nfunction definedDetails<T extends object>(value: T): T | undefined {\n return Object.keys(value).length > 0 ? value : undefined\n}\n\nfunction withoutKey<T extends object, K extends keyof T>(\n value: T,\n key: K,\n): Omit<T, K> {\n const next = { ...value }\n delete next[key]\n return next\n}\n\nexport function isTanstackUsage(usage: unknown): usage is TokenUsage {\n return (\n typeof usage === 'object' &&\n usage != null &&\n !Array.isArray(usage) &&\n 'promptTokens' in usage\n )\n}\n\nexport function toSpecTokenUsage(\n usage: TokenUsage,\n options?: ToSpecTokenUsageOptions,\n): { usage: Array<SpecTokenUsage>; leftover?: TokenUsageLeftover } {\n const {\n promptTokens,\n completionTokens,\n totalTokens,\n promptTokensDetails,\n completionTokensDetails,\n ...rest\n } = usage\n\n const spec: SpecTokenUsage = {\n ...(options?.provider !== undefined ? { provider: options.provider } : {}),\n ...(options?.model !== undefined ? { model: options.model } : {}),\n inputTokens: promptTokens,\n outputTokens: completionTokens,\n totalTokens,\n }\n const cachedInputTokens = promptTokensDetails?.cachedTokens\n if (cachedInputTokens !== undefined) {\n spec.cachedInputTokens = cachedInputTokens\n }\n const reasoningTokens = completionTokensDetails?.reasoningTokens\n if (reasoningTokens !== undefined) {\n spec.reasoningTokens = reasoningTokens\n }\n\n const leftoverPrompt = promptTokensDetails\n ? definedDetails(withoutKey(promptTokensDetails, 'cachedTokens'))\n : undefined\n const leftoverCompletion = completionTokensDetails\n ? definedDetails(withoutKey(completionTokensDetails, 'reasoningTokens'))\n : undefined\n\n return {\n usage: [spec],\n leftover: definedDetails({\n ...rest,\n ...(leftoverPrompt !== undefined\n ? { promptTokensDetails: leftoverPrompt }\n : {}),\n ...(leftoverCompletion !== undefined\n ? { completionTokensDetails: leftoverCompletion }\n : {}),\n }),\n }\n}\n\nexport function rebuildTokenUsage(\n usage: unknown,\n leftover?: TokenUsageLeftover,\n): TokenUsage | undefined {\n if (isTanstackUsage(usage)) {\n return usage\n }\n if (Array.isArray(usage)) {\n return fromSpecTokenUsage(usage, leftover)\n }\n return fromSpecTokenUsage(undefined, leftover)\n}\n\nexport function fromSpecTokenUsage(\n usage: ReadonlyArray<SpecTokenUsage> | undefined,\n leftover?: TokenUsageLeftover,\n): TokenUsage | undefined {\n const spec = usage?.[0]\n if (spec == null && leftover == null) {\n return undefined\n }\n\n const {\n promptTokensDetails: leftoverPromptDetails,\n completionTokensDetails: leftoverCompletionDetails,\n ...leftoverRest\n } = leftover ?? {}\n\n const promptTokensDetails = definedDetails({\n ...(spec?.cachedInputTokens !== undefined\n ? { cachedTokens: spec.cachedInputTokens }\n : {}),\n ...leftoverPromptDetails,\n })\n const completionTokensDetails = definedDetails({\n ...(spec?.reasoningTokens !== undefined\n ? { reasoningTokens: spec.reasoningTokens }\n : {}),\n ...leftoverCompletionDetails,\n })\n\n return {\n promptTokens: spec?.inputTokens ?? 0,\n completionTokens: spec?.outputTokens ?? 0,\n totalTokens: spec?.totalTokens ?? 0,\n ...leftoverRest,\n ...(promptTokensDetails !== undefined ? { promptTokensDetails } : {}),\n ...(completionTokensDetails !== undefined\n ? { completionTokensDetails }\n : {}),\n }\n}\n"],"mappings":";AAwBA,SAAS,eAAiC,OAAyB;CACjE,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ,KAAA;AACjD;AAEA,SAAS,WACP,OACA,KACY;CACZ,MAAM,OAAO,EAAE,GAAG,MAAM;CACxB,OAAO,KAAK;CACZ,OAAO;AACT;AAEA,SAAgB,gBAAgB,OAAqC;CACnE,OACE,OAAO,UAAU,YACjB,SAAS,QACT,CAAC,MAAM,QAAQ,KAAK,KACpB,kBAAkB;AAEtB;AAEA,SAAgB,iBACd,OACA,SACiE;CACjE,MAAM,EACJ,cACA,kBACA,aACA,qBACA,yBACA,GAAG,SACD;CAEJ,MAAM,OAAuB;EAC3B,GAAI,SAAS,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACxE,GAAI,SAAS,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAC/D,aAAa;EACb,cAAc;EACd;CACF;CACA,MAAM,oBAAoB,qBAAqB;CAC/C,IAAI,sBAAsB,KAAA,GACxB,KAAK,oBAAoB;CAE3B,MAAM,kBAAkB,yBAAyB;CACjD,IAAI,oBAAoB,KAAA,GACtB,KAAK,kBAAkB;CAGzB,MAAM,iBAAiB,sBACnB,eAAe,WAAW,qBAAqB,cAAc,CAAC,IAC9D,KAAA;CACJ,MAAM,qBAAqB,0BACvB,eAAe,WAAW,yBAAyB,iBAAiB,CAAC,IACrE,KAAA;CAEJ,OAAO;EACL,OAAO,CAAC,IAAI;EACZ,UAAU,eAAe;GACvB,GAAG;GACH,GAAI,mBAAmB,KAAA,IACnB,EAAE,qBAAqB,eAAe,IACtC,CAAC;GACL,GAAI,uBAAuB,KAAA,IACvB,EAAE,yBAAyB,mBAAmB,IAC9C,CAAC;EACP,CAAC;CACH;AACF;AAEA,SAAgB,kBACd,OACA,UACwB;CACxB,IAAI,gBAAgB,KAAK,GACvB,OAAO;CAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,mBAAmB,OAAO,QAAQ;CAE3C,OAAO,mBAAmB,KAAA,GAAW,QAAQ;AAC/C;AAEA,SAAgB,mBACd,OACA,UACwB;CACxB,MAAM,OAAO,QAAQ;CACrB,IAAI,QAAQ,QAAQ,YAAY,MAC9B;CAGF,MAAM,EACJ,qBAAqB,uBACrB,yBAAyB,2BACzB,GAAG,iBACD,YAAY,CAAC;CAEjB,MAAM,sBAAsB,eAAe;EACzC,GAAI,MAAM,sBAAsB,KAAA,IAC5B,EAAE,cAAc,KAAK,kBAAkB,IACvC,CAAC;EACL,GAAG;CACL,CAAC;CACD,MAAM,0BAA0B,eAAe;EAC7C,GAAI,MAAM,oBAAoB,KAAA,IAC1B,EAAE,iBAAiB,KAAK,gBAAgB,IACxC,CAAC;EACL,GAAG;CACL,CAAC;CAED,OAAO;EACL,cAAc,MAAM,eAAe;EACnC,kBAAkB,MAAM,gBAAgB;EACxC,aAAa,MAAM,eAAe;EAClC,GAAG;EACH,GAAI,wBAAwB,KAAA,IAAY,EAAE,oBAAoB,IAAI,CAAC;EACnE,GAAI,4BAA4B,KAAA,IAC5B,EAAE,wBAAwB,IAC1B,CAAC;CACP;AACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContentPart, UIMessage } from '../types.js';
|
|
2
|
+
import { MetadataRecord } from './merge-metadata.js';
|
|
2
3
|
type AGUITextInputContent = {
|
|
3
4
|
type: 'text';
|
|
4
5
|
text: string;
|
|
@@ -13,6 +14,7 @@ type AGUIToolCallMirror = {
|
|
|
13
14
|
name: string;
|
|
14
15
|
arguments: string;
|
|
15
16
|
};
|
|
17
|
+
encryptedValue?: string;
|
|
16
18
|
};
|
|
17
19
|
type AGUIToolMessage = {
|
|
18
20
|
role: 'tool';
|
|
@@ -25,20 +27,25 @@ type AGUIReasoningMessage = {
|
|
|
25
27
|
role: 'reasoning';
|
|
26
28
|
id: string;
|
|
27
29
|
content: string;
|
|
30
|
+
encryptedValue?: string;
|
|
31
|
+
metadata?: MetadataRecord;
|
|
28
32
|
};
|
|
29
|
-
|
|
33
|
+
/** Spec AG-UI message. No `parts`, no `createdAt` Date. */
|
|
34
|
+
type WireAnchorMessage = {
|
|
35
|
+
id: string;
|
|
36
|
+
role: UIMessage['role'];
|
|
37
|
+
name?: string;
|
|
30
38
|
content?: string | Array<AGUIInputContent>;
|
|
31
39
|
toolCalls?: Array<AGUIToolCallMirror>;
|
|
40
|
+
metadata?: MetadataRecord;
|
|
32
41
|
};
|
|
33
42
|
export type WireMessage = WireAnchorMessage | AGUIToolMessage | AGUIReasoningMessage;
|
|
34
43
|
/**
|
|
35
44
|
* Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
|
|
36
|
-
* wire shape.
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict
|
|
41
|
-
* AG-UI server consumers.
|
|
45
|
+
* wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
|
|
46
|
+
* `toolCalls`, `metadata`). Tool results and thinking parts on assistant
|
|
47
|
+
* messages are additionally emitted as fan-out `{role:'tool',...}` and
|
|
48
|
+
* `{role:'reasoning',...}` entries for strict AG-UI server consumers.
|
|
42
49
|
*/
|
|
43
50
|
export declare function uiMessagesToWire(messages: Array<UIMessage>): Array<WireMessage>;
|
|
44
51
|
export {};
|
|
@@ -1,43 +1,39 @@
|
|
|
1
|
+
import { tanstackMetadata } from "./merge-metadata.js";
|
|
1
2
|
//#region src/utilities/ag-ui-wire.ts
|
|
2
3
|
/**
|
|
3
4
|
* Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
|
|
4
|
-
* wire shape.
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict
|
|
9
|
-
* AG-UI server consumers.
|
|
5
|
+
* wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
|
|
6
|
+
* `toolCalls`, `metadata`). Tool results and thinking parts on assistant
|
|
7
|
+
* messages are additionally emitted as fan-out `{role:'tool',...}` and
|
|
8
|
+
* `{role:'reasoning',...}` entries for strict AG-UI server consumers.
|
|
10
9
|
*/
|
|
11
10
|
function uiMessagesToWire(messages) {
|
|
12
11
|
const wire = [];
|
|
13
12
|
for (const msg of messages) {
|
|
14
13
|
const parts = msg.parts ?? [];
|
|
15
14
|
if (msg.role === "system") {
|
|
16
|
-
wire.push({
|
|
17
|
-
...msg,
|
|
18
|
-
content: parts.length > 0 ? collectText(parts) : msg.content ?? ""
|
|
19
|
-
});
|
|
15
|
+
wire.push(toAnchor(msg, { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts));
|
|
20
16
|
continue;
|
|
21
17
|
}
|
|
22
18
|
if (msg.role === "user") {
|
|
23
|
-
wire.push({
|
|
24
|
-
...msg,
|
|
25
|
-
content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? ""
|
|
26
|
-
});
|
|
19
|
+
wire.push(toAnchor(msg, { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts));
|
|
27
20
|
continue;
|
|
28
21
|
}
|
|
29
|
-
for (const part of parts) if (part.type === "thinking")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
for (const part of parts) if (part.type === "thinking") {
|
|
23
|
+
const reasoning = {
|
|
24
|
+
role: "reasoning",
|
|
25
|
+
id: deriveReasoningId(msg.id, part),
|
|
26
|
+
content: part.content
|
|
27
|
+
};
|
|
28
|
+
if (part.signature) reasoning.encryptedValue = part.signature;
|
|
29
|
+
wire.push(reasoning);
|
|
30
|
+
}
|
|
34
31
|
const text = collectText(parts);
|
|
35
32
|
const toolCalls = collectToolCalls(parts);
|
|
36
|
-
wire.push({
|
|
37
|
-
...msg,
|
|
33
|
+
wire.push(toAnchor(msg, {
|
|
38
34
|
...text !== "" && { content: text },
|
|
39
35
|
...toolCalls && { toolCalls }
|
|
40
|
-
});
|
|
36
|
+
}, parts));
|
|
41
37
|
for (const part of parts) if (part.type === "tool-result") wire.push({
|
|
42
38
|
role: "tool",
|
|
43
39
|
id: deriveToolMessageId(part.toolCallId),
|
|
@@ -48,6 +44,41 @@ function uiMessagesToWire(messages) {
|
|
|
48
44
|
}
|
|
49
45
|
return wire;
|
|
50
46
|
}
|
|
47
|
+
function toAnchor(msg, extras, parts) {
|
|
48
|
+
const metadata = messageMetadata(msg, parts);
|
|
49
|
+
const name = msg.name;
|
|
50
|
+
return {
|
|
51
|
+
id: msg.id,
|
|
52
|
+
role: msg.role,
|
|
53
|
+
...name !== void 0 && { name },
|
|
54
|
+
...extras,
|
|
55
|
+
...metadata !== void 0 && { metadata }
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function messageMetadata(msg, parts) {
|
|
59
|
+
const base = { ...msg.metadata ?? {} };
|
|
60
|
+
const tanstack = { ...tanstackMetadata(msg) ?? {} };
|
|
61
|
+
if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString();
|
|
62
|
+
const leftover = unfinishedStructuredOutput(parts);
|
|
63
|
+
if (leftover) tanstack.structuredOutput = leftover;
|
|
64
|
+
const toolCallMetadata = {};
|
|
65
|
+
for (const part of parts) if (part.type === "tool-call" && part.metadata !== void 0) toolCallMetadata[part.id] = part.metadata;
|
|
66
|
+
if (Object.keys(toolCallMetadata).length > 0) tanstack.toolCallMetadata = toolCallMetadata;
|
|
67
|
+
const uiResources = parts.filter((p) => p.type === "ui-resource");
|
|
68
|
+
if (uiResources.length > 0) tanstack.uiResources = uiResources;
|
|
69
|
+
if (Object.keys(tanstack).length > 0) base.tanstack = tanstack;
|
|
70
|
+
return Object.keys(base).length > 0 ? base : void 0;
|
|
71
|
+
}
|
|
72
|
+
function unfinishedStructuredOutput(parts) {
|
|
73
|
+
for (const p of parts) if (p.type === "structured-output" && p.status !== "complete") return structuredOutputLeftover(p);
|
|
74
|
+
}
|
|
75
|
+
function structuredOutputLeftover(part) {
|
|
76
|
+
return {
|
|
77
|
+
status: part.status,
|
|
78
|
+
raw: part.raw,
|
|
79
|
+
...part.errorMessage !== void 0 && { errorMessage: part.errorMessage }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
51
82
|
function collectText(parts) {
|
|
52
83
|
const out = [];
|
|
53
84
|
for (const p of parts) if (p.type === "text") out.push(p.content);
|
|
@@ -64,16 +95,26 @@ function collectUserContent(parts) {
|
|
|
64
95
|
else if (p.type === "image" || p.type === "audio" || p.type === "video" || p.type === "document") out.push(p);
|
|
65
96
|
return out;
|
|
66
97
|
}
|
|
98
|
+
function thoughtSignatureFromMetadata(metadata) {
|
|
99
|
+
if (metadata == null || typeof metadata !== "object" || Array.isArray(metadata)) return;
|
|
100
|
+
if (!("thoughtSignature" in metadata)) return void 0;
|
|
101
|
+
const value = metadata.thoughtSignature;
|
|
102
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
103
|
+
}
|
|
67
104
|
function collectToolCalls(parts) {
|
|
68
105
|
const calls = [];
|
|
69
|
-
for (const p of parts) if (p.type === "tool-call")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
106
|
+
for (const p of parts) if (p.type === "tool-call") {
|
|
107
|
+
const encryptedValue = thoughtSignatureFromMetadata(p.metadata);
|
|
108
|
+
calls.push({
|
|
109
|
+
id: p.id,
|
|
110
|
+
type: "function",
|
|
111
|
+
function: {
|
|
112
|
+
name: p.name,
|
|
113
|
+
arguments: p.arguments
|
|
114
|
+
},
|
|
115
|
+
...encryptedValue !== void 0 ? { encryptedValue } : {}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
77
118
|
return calls.length > 0 ? calls : void 0;
|
|
78
119
|
}
|
|
79
120
|
function deriveReasoningId(messageId, part) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type { ContentPart, MessagePart, UIMessage } from '../types'\n\ntype AGUITextInputContent = { type: 'text'; text: string }\ntype AGUIInputContent =\n | AGUITextInputContent\n | (ContentPart & { type: 'image' | 'audio' | 'video' | 'document' })\n\ntype AGUIToolCallMirror = {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\ntype AGUIToolMessage = {\n role: 'tool'\n id: string\n toolCallId: string\n content: string\n error?: string\n}\n\ntype AGUIReasoningMessage = {\n role: 'reasoning'\n id: string\n content: string\n}\n\ntype WireAnchorMessage = UIMessage & {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n}\n\nexport type WireMessage =\n | WireAnchorMessage\n | AGUIToolMessage\n | AGUIReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`\n * wire shape. Each anchor (system/user/assistant) carries the canonical\n * `parts` array verbatim plus AG-UI mirror fields (`content`, `toolCalls`)\n * so AG-UI Zod parsing succeeds. Tool results and thinking parts on\n * assistant messages are additionally emitted as fan-out\n * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict\n * AG-UI server consumers.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage>,\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n\n for (const msg of messages) {\n // Defensive: if parts is missing (ModelMessage-shaped input), pass through as-is.\n // UIMessage always has parts; ModelMessage uses content directly.\n const parts: ReadonlyArray<MessagePart> =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- runtime input may be ModelMessage-shaped (no `parts`); cast forces the optional-chain fallback below to remain in scope\n (msg.parts as ReadonlyArray<MessagePart> | undefined) ?? []\n\n if (msg.role === 'system') {\n wire.push({\n ...msg,\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n })\n continue\n }\n\n if (msg.role === 'user') {\n wire.push({\n ...msg,\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n })\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n wire.push({\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n })\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push({\n ...msg,\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n })\n\n for (const part of parts) {\n if (part.type === 'tool-result') {\n wire.push({\n role: 'tool',\n id: deriveToolMessageId(part.toolCallId),\n toolCallId: part.toolCallId,\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<AGUIInputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<AGUIInputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<AGUIToolCallMirror> | undefined {\n const calls: Array<AGUIToolCallMirror> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":";;;;;;;;;;AA8CA,SAAgB,iBACd,UACoB;CACpB,MAAM,OAA2B,CAAC;CAElC,KAAK,MAAM,OAAO,UAAU;EAG1B,MAAM,QAEH,IAAI,SAAoD,CAAC;EAE5D,IAAI,IAAI,SAAS,UAAU;GACzB,KAAK,KAAK;IACR,GAAG;IACH,SACE,MAAM,SAAS,IACX,YAAY,KAAK,IACf,IAA6B,WAAW;GAClD,CAAC;GACD;EACF;EAEA,IAAI,IAAI,SAAS,QAAQ;GACvB,KAAK,KAAK;IACR,GAAG;IACH,SACE,MAAM,SAAS,IACX,mBAAmB,KAAK,IACtB,IAA6B,WAAW;GAClD,CAAC;GACD;EACF;EAGA,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,YAChB,KAAK,KAAK;GACR,MAAM;GACN,IAAI,kBAAkB,IAAI,IAAI,IAAI;GAClC,SAAS,KAAK;EAChB,CAAC;EAIL,MAAM,OAAO,YAAY,KAAK;EAC9B,MAAM,YAAY,iBAAiB,KAAK;EACxC,KAAK,KAAK;GACR,GAAG;GACH,GAAI,SAAS,MAAM,EAAE,SAAS,KAAK;GACnC,GAAI,aAAa,EAAE,UAAU;EAC/B,CAAC;EAED,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,eAChB,KAAK,KAAK;GACR,MAAM;GACN,IAAI,oBAAoB,KAAK,UAAU;GACvC,YAAY,KAAK;GACjB,SACE,OAAO,KAAK,YAAY,WACpB,KAAK,UACL,KAAK,UAAU,KAAK,OAAO;GACjC,GAAI,KAAK,UAAU,KAAA,KAAa,EAAE,OAAO,KAAK,MAAM;EACtD,CAAC;CAGP;CAEA,OAAO;AACT;AAEA,SAAS,YAAY,OAA2C;CAW9D,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,QACb,IAAI,KAAK,EAAE,OAAO;MACb,IACL,EAAE,SAAS,uBACX,EAAE,WAAW,cACb,EAAE,QAAQ,IAEV,IAAI,KAAK,EAAE,GAAG;CAGlB,OAAO,IAAI,KAAK,EAAE;AACpB;AAEA,SAAS,mBACP,OACkC;CAQlC,IAAI,CAPkB,MAAM,MACzB,MACC,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,UAEV,GACH,OAAO,YAAY,KAAK;CAE1B,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,QACb,IAAI,KAAK;EAAE,MAAM;EAAQ,MAAM,EAAE;CAAQ,CAAC;MACrC,IACL,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,YAEX,IAAI,KAAK,CAAC;CAGd,OAAO;AACT;AAEA,SAAS,iBACP,OACuC;CACvC,MAAM,QAAmC,CAAC;CAC1C,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,aACb,MAAM,KAAK;EACT,IAAI,EAAE;EACN,MAAM;EACN,UAAU;GAAE,MAAM,EAAE;GAAM,WAAW,EAAE;EAAU;CACnD,CAAC;CAGL,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;AACpC;AAEA,SAAS,kBAAkB,WAAmB,MAA2B;CACvE,OAAO,GAAG,UAAU,aAAc,KAAyB,MAAM,YAAa,KAA6B,OAAO;AACpH;AAEA,SAAS,oBAAoB,YAA4B;CACvD,OAAO,QAAQ;AACjB;AAEA,SAAS,YAAY,GAAmB;CAItC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAK,IAAI,KAAK,EAAE,WAAW,CAAC,IAAK;CACpE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE;AAChC"}
|
|
1
|
+
{"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n StructuredOutputPart,\n TanStackMessageMetadata,\n UIMessage,\n UIResourcePart,\n} from '../types'\nimport type { MetadataRecord } from './merge-metadata'\nimport { tanstackMetadata } from './merge-metadata'\n\ntype AGUITextInputContent = { type: 'text'; text: string }\ntype AGUIInputContent =\n | AGUITextInputContent\n | (ContentPart & { type: 'image' | 'audio' | 'video' | 'document' })\n\ntype AGUIToolCallMirror = {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n encryptedValue?: string\n}\n\ntype AGUIToolMessage = {\n role: 'tool'\n id: string\n toolCallId: string\n content: string\n error?: string\n}\n\ntype AGUIReasoningMessage = {\n role: 'reasoning'\n id: string\n content: string\n encryptedValue?: string\n metadata?: MetadataRecord\n}\n\n/** Spec AG-UI message. No `parts`, no `createdAt` Date. */\ntype WireAnchorMessage = {\n id: string\n role: UIMessage['role']\n name?: string\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n metadata?: MetadataRecord\n}\n\nexport type WireMessage =\n | WireAnchorMessage\n | AGUIToolMessage\n | AGUIReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`\n * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,\n * `toolCalls`, `metadata`). Tool results and thinking parts on assistant\n * messages are additionally emitted as fan-out `{role:'tool',...}` and\n * `{role:'reasoning',...}` entries for strict AG-UI server consumers.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage>,\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n\n for (const msg of messages) {\n // Defensive: ModelMessage-shaped input has no `parts`; fall back to `content`.\n const parts: ReadonlyArray<MessagePart> =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- runtime input may be ModelMessage-shaped (no `parts`); cast forces the optional-chain fallback below to remain in scope\n (msg.parts as ReadonlyArray<MessagePart> | undefined) ?? []\n\n if (msg.role === 'system') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n if (msg.role === 'user') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n const reasoning: AGUIReasoningMessage = {\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n }\n if (part.signature) {\n reasoning.encryptedValue = part.signature\n }\n wire.push(reasoning)\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push(\n toAnchor(\n msg,\n {\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n },\n parts,\n ),\n )\n\n for (const part of parts) {\n if (part.type === 'tool-result') {\n wire.push({\n role: 'tool',\n id: deriveToolMessageId(part.toolCallId),\n toolCallId: part.toolCallId,\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction toAnchor(\n msg: UIMessage,\n extras: {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n },\n parts: ReadonlyArray<MessagePart>,\n): WireAnchorMessage {\n const metadata = messageMetadata(msg, parts)\n const name = (msg as { name?: string }).name\n return {\n id: msg.id,\n role: msg.role,\n ...(name !== undefined && { name }),\n ...extras,\n ...(metadata !== undefined && { metadata }),\n }\n}\n\nfunction messageMetadata(\n msg: UIMessage,\n parts: ReadonlyArray<MessagePart>,\n): MetadataRecord | undefined {\n const base: MetadataRecord = { ...(msg.metadata ?? {}) }\n const tanstack: MetadataRecord = { ...(tanstackMetadata(msg) ?? {}) }\n if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString()\n\n const leftover = unfinishedStructuredOutput(parts)\n if (leftover) tanstack.structuredOutput = leftover\n\n const toolCallMetadata: Record<string, unknown> = {}\n for (const part of parts) {\n if (part.type === 'tool-call' && part.metadata !== undefined) {\n toolCallMetadata[part.id] = part.metadata\n }\n }\n if (Object.keys(toolCallMetadata).length > 0) {\n tanstack.toolCallMetadata = toolCallMetadata\n }\n\n const uiResources = parts.filter(\n (p): p is UIResourcePart => p.type === 'ui-resource',\n )\n if (uiResources.length > 0) tanstack.uiResources = uiResources\n\n if (Object.keys(tanstack).length > 0) base.tanstack = tanstack\n return Object.keys(base).length > 0 ? base : undefined\n}\n\nfunction unfinishedStructuredOutput(\n parts: ReadonlyArray<MessagePart>,\n): TanStackMessageMetadata['structuredOutput'] | undefined {\n for (const p of parts) {\n if (p.type === 'structured-output' && p.status !== 'complete') {\n return structuredOutputLeftover(p)\n }\n }\n return undefined\n}\n\nfunction structuredOutputLeftover(\n part: StructuredOutputPart,\n): NonNullable<TanStackMessageMetadata['structuredOutput']> {\n return {\n status: part.status,\n raw: part.raw,\n ...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),\n }\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<AGUIInputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<AGUIInputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction thoughtSignatureFromMetadata(metadata: unknown): string | undefined {\n if (\n metadata == null ||\n typeof metadata !== 'object' ||\n Array.isArray(metadata)\n ) {\n return undefined\n }\n if (!('thoughtSignature' in metadata)) return undefined\n const value = metadata.thoughtSignature\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<AGUIToolCallMirror> | undefined {\n const calls: Array<AGUIToolCallMirror> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n const encryptedValue = thoughtSignatureFromMetadata(p.metadata)\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n ...(encryptedValue !== undefined ? { encryptedValue } : {}),\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":"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"}
|
|
@@ -63,8 +63,8 @@ export declare function chatParamsFromRequestBody(body: unknown): Promise<{
|
|
|
63
63
|
export declare function chatParamsFromRequest(req: Request): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>>;
|
|
64
64
|
/**
|
|
65
65
|
* Client-declared tool stub (no execute). `name` is `string`, so arrays that
|
|
66
|
-
* include these stubs intentionally widen
|
|
67
|
-
*
|
|
66
|
+
* include these stubs intentionally widen tool-name discrimination —
|
|
67
|
+
* pass server tools alone when you need a closed name union.
|
|
68
68
|
*/
|
|
69
69
|
export type ClientToolDeclaration = {
|
|
70
70
|
name: string;
|
|
@@ -90,7 +90,7 @@ export type MergedAgentTools<TServerTools extends ReadonlyArray<AnyTool>> = Read
|
|
|
90
90
|
* Typing:
|
|
91
91
|
* - Empty `clientTools` preserves the server tuple (closed name union).
|
|
92
92
|
* - Non-empty `clientTools` returns a widened array that honestly includes
|
|
93
|
-
* client stubs, so
|
|
93
|
+
* client stubs, so the merged array does not claim a closed server-only
|
|
94
94
|
* name union.
|
|
95
95
|
*
|
|
96
96
|
* @param serverTools - The server's tool array (e.g. from
|
|
@@ -1,29 +1,5 @@
|
|
|
1
1
|
import { AGUIError } from "@ag-ui/core";
|
|
2
2
|
//#region src/utilities/chat-params.ts
|
|
3
|
-
var KNOWN_PART_TYPES = /* @__PURE__ */ new Set([
|
|
4
|
-
"text",
|
|
5
|
-
"image",
|
|
6
|
-
"audio",
|
|
7
|
-
"video",
|
|
8
|
-
"document",
|
|
9
|
-
"tool-call",
|
|
10
|
-
"tool-result",
|
|
11
|
-
"thinking",
|
|
12
|
-
"structured-output"
|
|
13
|
-
]);
|
|
14
|
-
function isValidParts(value) {
|
|
15
|
-
if (!Array.isArray(value)) return false;
|
|
16
|
-
for (const p of value) {
|
|
17
|
-
if (!p || typeof p !== "object") return false;
|
|
18
|
-
const type = p.type;
|
|
19
|
-
if (typeof type !== "string" || !KNOWN_PART_TYPES.has(type)) return false;
|
|
20
|
-
if (type === "structured-output") {
|
|
21
|
-
const raw = p.raw;
|
|
22
|
-
if (raw !== void 0 && typeof raw !== "string") return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
3
|
/**
|
|
28
4
|
* Keyed by `AGUIRole` so a role added upstream fails to compile here until it
|
|
29
5
|
* is handled, rather than silently falling through as an unknown role.
|
|
@@ -61,7 +37,7 @@ function requireArray(value, at) {
|
|
|
61
37
|
/**
|
|
62
38
|
* Assert one AG-UI `Message`, discriminating on `role` exactly as the upstream
|
|
63
39
|
* `MessageSchema` discriminated union does. The record view is retained on the
|
|
64
|
-
* asserted type so callers can still inspect
|
|
40
|
+
* asserted type so callers can still inspect extras like `metadata`.
|
|
65
41
|
*/
|
|
66
42
|
function assertAGUIMessage(value, at) {
|
|
67
43
|
requireString(value.id, `${at}.id`);
|
|
@@ -92,12 +68,16 @@ function validateMessage(value, index) {
|
|
|
92
68
|
const at = `messages[${index}]`;
|
|
93
69
|
if (!isRecord(value)) invalidBody(`${at} must be an object`);
|
|
94
70
|
assertAGUIMessage(value, at);
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
Reflect.deleteProperty(withoutParts, "parts");
|
|
98
|
-
return withoutParts;
|
|
71
|
+
if (value.metadata !== void 0) {
|
|
72
|
+
if (!isRecord(value.metadata)) invalidBody(`${at}.metadata must be an object`);
|
|
99
73
|
}
|
|
100
|
-
return value;
|
|
74
|
+
return dropInboundParts(value);
|
|
75
|
+
}
|
|
76
|
+
function dropInboundParts(message) {
|
|
77
|
+
if (!("parts" in message)) return message;
|
|
78
|
+
const rest = { ...message };
|
|
79
|
+
delete rest.parts;
|
|
80
|
+
return rest;
|
|
101
81
|
}
|
|
102
82
|
function validateTool(value, index) {
|
|
103
83
|
const at = `tools[${index}]`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-params.js","names":[],"sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError } from '@ag-ui/core'\nimport type {\n Context as AGUIContext,\n Message as AGUIMessage,\n Role as AGUIRole,\n} from '@ag-ui/core'\nimport type {\n AnyTool,\n JSONSchema,\n ModelMessage,\n RunAgentResumeItem,\n UIMessage,\n} from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n 'structured-output',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n if (type === 'structured-output') {\n const raw = (p as { raw?: unknown }).raw\n if (raw !== undefined && typeof raw !== 'string') return false\n }\n }\n return true\n}\n\n/**\n * Keyed by `AGUIRole` so a role added upstream fails to compile here until it\n * is handled, rather than silently falling through as an unknown role.\n */\nconst AGUI_ROLES: Record<AGUIRole, true> = {\n developer: true,\n system: true,\n assistant: true,\n user: true,\n tool: true,\n activity: true,\n reasoning: true,\n}\n\nfunction isAGUIRole(value: unknown): value is AGUIRole {\n return typeof value === 'string' && value in AGUI_ROLES\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Reject the request body, pointing at the migration guide. Mirrors the\n * message the previous `RunAgentInputSchema.safeParse` failure produced.\n */\nfunction invalidBody(reason: string): never {\n throw new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${reason}`,\n )\n}\n\nfunction requireString(value: unknown, at: string): string {\n if (typeof value !== 'string') invalidBody(`${at} must be a string`)\n return value\n}\n\nfunction requireArray(value: unknown, at: string): Array<unknown> {\n if (!Array.isArray(value)) invalidBody(`${at} must be an array`)\n return value\n}\n\n/**\n * Assert one AG-UI `Message`, discriminating on `role` exactly as the upstream\n * `MessageSchema` discriminated union does. The record view is retained on the\n * asserted type so callers can still inspect non-AG-UI extras like `parts`.\n */\nfunction assertAGUIMessage(\n value: Record<string, unknown>,\n at: string,\n): asserts value is Record<string, unknown> & AGUIMessage {\n requireString(value.id, `${at}.id`)\n\n const role = value.role\n if (!isAGUIRole(role)) {\n invalidBody(\n `${at}.role must be one of ${Object.keys(AGUI_ROLES).join(' | ')}`,\n )\n }\n\n switch (role) {\n case 'assistant':\n // Both optional: a tool-calling turn carries no text content.\n if (value.content !== undefined) {\n requireString(value.content, `${at}.content`)\n }\n if (value.toolCalls !== undefined) {\n requireArray(value.toolCalls, `${at}.toolCalls`)\n }\n break\n case 'user':\n if (typeof value.content !== 'string' && !Array.isArray(value.content)) {\n invalidBody(\n `${at}.content must be a string or an array of content parts`,\n )\n }\n break\n case 'tool':\n requireString(value.content, `${at}.content`)\n requireString(value.toolCallId, `${at}.toolCallId`)\n break\n case 'activity':\n requireString(value.activityType, `${at}.activityType`)\n if (!isRecord(value.content)) {\n invalidBody(`${at}.content must be an object`)\n }\n break\n case 'developer':\n case 'system':\n case 'reasoning':\n requireString(value.content, `${at}.content`)\n break\n }\n}\n\nfunction validateMessage(value: unknown, index: number): AGUIMessage {\n const at = `messages[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n assertAGUIMessage(value, at)\n\n // `parts` is TanStack's canonical extra, carried through so the UIMessage\n // path inside `chat()` can use it. Keep it only when it holds recognized\n // part types — the previous schema-based path dropped `parts` during parse\n // and re-attached it from the raw body behind this same check.\n if ('parts' in value && !isValidParts(value.parts)) {\n const withoutParts = { ...value }\n Reflect.deleteProperty(withoutParts, 'parts')\n return withoutParts\n }\n return value\n}\n\nfunction validateTool(\n value: unknown,\n index: number,\n): { name: string; description: string; parameters: JSONSchema } {\n const at = `tools[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n name: requireString(value.name, `${at}.name`),\n description: requireString(value.description, `${at}.description`),\n // Upstream `ToolSchema` types this as optional `any`; it reaches the\n // provider as a raw JSON Schema either way.\n parameters: value.parameters as JSONSchema,\n }\n}\n\nfunction validateContext(value: unknown, index: number): AGUIContext {\n const at = `context[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n description: requireString(value.description, `${at}.description`),\n value: requireString(value.value, `${at}.value`),\n }\n}\n\nfunction validateResumeEntry(\n value: unknown,\n index: number,\n): RunAgentResumeItem {\n const at = `resume[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n const status = value.status\n if (status !== 'resolved' && status !== 'cancelled') {\n invalidBody(`${at}.status must be \"resolved\" or \"cancelled\"`)\n }\n const entry: RunAgentResumeItem = {\n interruptId: requireString(value.interruptId, `${at}.interruptId`),\n status,\n }\n // Omit the key entirely when absent, matching the optional-field shape the\n // schema produced.\n if (value.payload !== undefined) entry.payload = value.payload\n if (value.metadata !== undefined) {\n if (!isRecord(value.metadata)) {\n invalidBody(`${at}.metadata must be an object`)\n }\n entry.metadata = value.metadata\n }\n return entry\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * Validated structurally against the AG-UI `RunAgentInput` contract without a\n * schema library, so this package pulls in no validation runtime of its own.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI `RunAgentInput`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport async function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n resume?: Array<RunAgentResumeItem>\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n if (!isRecord(body)) invalidBody('body must be a JSON object')\n\n const threadId = requireString(body.threadId, 'threadId')\n const runId = requireString(body.runId, 'runId')\n const parentRunId =\n body.parentRunId === undefined\n ? undefined\n : requireString(body.parentRunId, 'parentRunId')\n\n const messages = requireArray(body.messages, 'messages').map(validateMessage)\n const tools = requireArray(body.tools, 'tools').map(validateTool)\n const aguiContext = requireArray(body.context, 'context').map(validateContext)\n const resume =\n body.resume === undefined\n ? undefined\n : requireArray(body.resume, 'resume').map(validateResumeEntry)\n\n if (body.forwardedProps !== undefined && !isRecord(body.forwardedProps)) {\n invalidBody('forwardedProps must be an object')\n }\n\n return {\n // Unknown top-level fields (e.g. a legacy `cursor`) are dropped by\n // construction: only the fields below are copied onto the result.\n messages: messages as Array<UIMessage | ModelMessage>,\n threadId,\n runId,\n parentRunId,\n tools,\n forwardedProps: (body.forwardedProps ?? {}) as Record<string, unknown>,\n state: body.state,\n resume: resume as Array<RunAgentResumeItem> | undefined,\n context: aguiContext,\n aguiContext,\n }\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Client-declared tool stub (no execute). `name` is `string`, so arrays that\n * include these stubs intentionally widen `TypedStreamChunk` tool-name\n * discrimination — pass server tools alone when you need a closed name union.\n */\nexport type ClientToolDeclaration = {\n name: string\n description: string\n inputSchema: JSONSchema\n}\n\nexport type MergedAgentTools<TServerTools extends ReadonlyArray<AnyTool>> =\n ReadonlyArray<TServerTools[number] | ClientToolDeclaration>\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * Typing:\n * - Empty `clientTools` preserves the server tuple (closed name union).\n * - Non-empty `clientTools` returns a widened array that honestly includes\n * client stubs, so `TypedStreamChunk` does not claim a closed server-only\n * name union.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(serverTools: TServerTools, clientTools: readonly []): TServerTools\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): MergedAgentTools<TServerTools>\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): TServerTools | MergedAgentTools<TServerTools> {\n if (clientTools.length === 0) {\n return serverTools\n }\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<TServerTools[number] | ClientToolDeclaration> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n })\n }\n return merged\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-params.js","names":[],"sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError } from '@ag-ui/core'\nimport type {\n Context as AGUIContext,\n Message as AGUIMessage,\n Role as AGUIRole,\n} from '@ag-ui/core'\nimport type {\n AnyTool,\n JSONSchema,\n ModelMessage,\n RunAgentResumeItem,\n UIMessage,\n} from '../types'\n\n/**\n * Keyed by `AGUIRole` so a role added upstream fails to compile here until it\n * is handled, rather than silently falling through as an unknown role.\n */\nconst AGUI_ROLES: Record<AGUIRole, true> = {\n developer: true,\n system: true,\n assistant: true,\n user: true,\n tool: true,\n activity: true,\n reasoning: true,\n}\n\nfunction isAGUIRole(value: unknown): value is AGUIRole {\n return typeof value === 'string' && value in AGUI_ROLES\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Reject the request body, pointing at the migration guide. Mirrors the\n * message the previous `RunAgentInputSchema.safeParse` failure produced.\n */\nfunction invalidBody(reason: string): never {\n throw new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${reason}`,\n )\n}\n\nfunction requireString(value: unknown, at: string): string {\n if (typeof value !== 'string') invalidBody(`${at} must be a string`)\n return value\n}\n\nfunction requireArray(value: unknown, at: string): Array<unknown> {\n if (!Array.isArray(value)) invalidBody(`${at} must be an array`)\n return value\n}\n\n/**\n * Assert one AG-UI `Message`, discriminating on `role` exactly as the upstream\n * `MessageSchema` discriminated union does. The record view is retained on the\n * asserted type so callers can still inspect extras like `metadata`.\n */\nfunction assertAGUIMessage(\n value: Record<string, unknown>,\n at: string,\n): asserts value is Record<string, unknown> & AGUIMessage {\n requireString(value.id, `${at}.id`)\n\n const role = value.role\n if (!isAGUIRole(role)) {\n invalidBody(\n `${at}.role must be one of ${Object.keys(AGUI_ROLES).join(' | ')}`,\n )\n }\n\n switch (role) {\n case 'assistant':\n // Both optional: a tool-calling turn carries no text content.\n if (value.content !== undefined) {\n requireString(value.content, `${at}.content`)\n }\n if (value.toolCalls !== undefined) {\n requireArray(value.toolCalls, `${at}.toolCalls`)\n }\n break\n case 'user':\n if (typeof value.content !== 'string' && !Array.isArray(value.content)) {\n invalidBody(\n `${at}.content must be a string or an array of content parts`,\n )\n }\n break\n case 'tool':\n requireString(value.content, `${at}.content`)\n requireString(value.toolCallId, `${at}.toolCallId`)\n break\n case 'activity':\n requireString(value.activityType, `${at}.activityType`)\n if (!isRecord(value.content)) {\n invalidBody(`${at}.content must be an object`)\n }\n break\n case 'developer':\n case 'system':\n case 'reasoning':\n requireString(value.content, `${at}.content`)\n break\n }\n}\n\nfunction validateMessage(value: unknown, index: number): AGUIMessage {\n const at = `messages[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n assertAGUIMessage(value, at)\n\n if (value.metadata !== undefined) {\n if (!isRecord(value.metadata)) {\n invalidBody(`${at}.metadata must be an object`)\n }\n }\n\n // Hard cut: inbound `parts` from old clients are dropped. Content,\n // toolCalls, and metadata stay on the record. Copy so we do not mutate\n // the caller's message object.\n return dropInboundParts(value)\n}\n\nfunction dropInboundParts(\n message: AGUIMessage & { parts?: unknown },\n): AGUIMessage {\n if (!('parts' in message)) return message\n const rest = { ...message }\n delete rest.parts\n return rest\n}\n\nfunction validateTool(\n value: unknown,\n index: number,\n): { name: string; description: string; parameters: JSONSchema } {\n const at = `tools[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n name: requireString(value.name, `${at}.name`),\n description: requireString(value.description, `${at}.description`),\n // Upstream `ToolSchema` types this as optional `any`; it reaches the\n // provider as a raw JSON Schema either way.\n parameters: value.parameters as JSONSchema,\n }\n}\n\nfunction validateContext(value: unknown, index: number): AGUIContext {\n const at = `context[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n return {\n description: requireString(value.description, `${at}.description`),\n value: requireString(value.value, `${at}.value`),\n }\n}\n\nfunction validateResumeEntry(\n value: unknown,\n index: number,\n): RunAgentResumeItem {\n const at = `resume[${index}]`\n if (!isRecord(value)) invalidBody(`${at} must be an object`)\n const status = value.status\n if (status !== 'resolved' && status !== 'cancelled') {\n invalidBody(`${at}.status must be \"resolved\" or \"cancelled\"`)\n }\n const entry: RunAgentResumeItem = {\n interruptId: requireString(value.interruptId, `${at}.interruptId`),\n status,\n }\n // Omit the key entirely when absent, matching the optional-field shape the\n // schema produced.\n if (value.payload !== undefined) entry.payload = value.payload\n if (value.metadata !== undefined) {\n if (!isRecord(value.metadata)) {\n invalidBody(`${at}.metadata must be an object`)\n }\n entry.metadata = value.metadata\n }\n return entry\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * Validated structurally against the AG-UI `RunAgentInput` contract without a\n * schema library, so this package pulls in no validation runtime of its own.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI `RunAgentInput`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport async function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n resume?: Array<RunAgentResumeItem>\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n if (!isRecord(body)) invalidBody('body must be a JSON object')\n\n const threadId = requireString(body.threadId, 'threadId')\n const runId = requireString(body.runId, 'runId')\n const parentRunId =\n body.parentRunId === undefined\n ? undefined\n : requireString(body.parentRunId, 'parentRunId')\n\n const messages = requireArray(body.messages, 'messages').map(validateMessage)\n const tools = requireArray(body.tools, 'tools').map(validateTool)\n const aguiContext = requireArray(body.context, 'context').map(validateContext)\n const resume =\n body.resume === undefined\n ? undefined\n : requireArray(body.resume, 'resume').map(validateResumeEntry)\n\n if (body.forwardedProps !== undefined && !isRecord(body.forwardedProps)) {\n invalidBody('forwardedProps must be an object')\n }\n\n return {\n // Unknown top-level fields (e.g. a legacy `cursor`) are dropped by\n // construction: only the fields below are copied onto the result.\n messages: messages as Array<UIMessage | ModelMessage>,\n threadId,\n runId,\n parentRunId,\n tools,\n forwardedProps: (body.forwardedProps ?? {}) as Record<string, unknown>,\n state: body.state,\n resume: resume as Array<RunAgentResumeItem> | undefined,\n context: aguiContext,\n aguiContext,\n }\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Client-declared tool stub (no execute). `name` is `string`, so arrays that\n * include these stubs intentionally widen tool-name discrimination —\n * pass server tools alone when you need a closed name union.\n */\nexport type ClientToolDeclaration = {\n name: string\n description: string\n inputSchema: JSONSchema\n}\n\nexport type MergedAgentTools<TServerTools extends ReadonlyArray<AnyTool>> =\n ReadonlyArray<TServerTools[number] | ClientToolDeclaration>\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * Typing:\n * - Empty `clientTools` preserves the server tuple (closed name union).\n * - Non-empty `clientTools` returns a widened array that honestly includes\n * client stubs, so the merged array does not claim a closed server-only\n * name union.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(serverTools: TServerTools, clientTools: readonly []): TServerTools\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): MergedAgentTools<TServerTools>\nexport function mergeAgentTools<\n const TServerTools extends ReadonlyArray<AnyTool>,\n>(\n serverTools: TServerTools,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): TServerTools | MergedAgentTools<TServerTools> {\n if (clientTools.length === 0) {\n return serverTools\n }\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<TServerTools[number] | ClientToolDeclaration> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n })\n }\n return merged\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { StreamChunk } from '../types.js';
|
|
2
|
+
/** Run id on the spec event, or `metadata.tanstack.runId` after normalize. */
|
|
3
|
+
export declare function getChunkRunId(chunk: StreamChunk): string | undefined;
|
|
4
|
+
/** Thread id on the spec event, or `metadata.tanstack.threadId` after normalize. */
|
|
5
|
+
export declare function getChunkThreadId(chunk: StreamChunk): string | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tanstackMetadata } from "./merge-metadata.js";
|
|
2
|
+
//#region src/utilities/chunk-ids.ts
|
|
3
|
+
function stringField(value) {
|
|
4
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
5
|
+
}
|
|
6
|
+
/** Run id on the spec event, or `metadata.tanstack.runId` after normalize. */
|
|
7
|
+
function getChunkRunId(chunk) {
|
|
8
|
+
if ("runId" in chunk) {
|
|
9
|
+
const top = stringField(chunk.runId);
|
|
10
|
+
if (top !== void 0) return top;
|
|
11
|
+
}
|
|
12
|
+
return stringField(tanstackMetadata(chunk)?.runId);
|
|
13
|
+
}
|
|
14
|
+
/** Thread id on the spec event, or `metadata.tanstack.threadId` after normalize. */
|
|
15
|
+
function getChunkThreadId(chunk) {
|
|
16
|
+
if ("threadId" in chunk) {
|
|
17
|
+
const top = stringField(chunk.threadId);
|
|
18
|
+
if (top !== void 0) return top;
|
|
19
|
+
}
|
|
20
|
+
return stringField(tanstackMetadata(chunk)?.threadId);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getChunkRunId, getChunkThreadId };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=chunk-ids.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-ids.js","names":[],"sources":["../../../src/utilities/chunk-ids.ts"],"sourcesContent":["import type { StreamChunk } from '../types'\nimport { tanstackMetadata } from './merge-metadata'\n\nfunction stringField(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/** Run id on the spec event, or `metadata.tanstack.runId` after normalize. */\nexport function getChunkRunId(chunk: StreamChunk): string | undefined {\n if ('runId' in chunk) {\n const top = stringField(chunk.runId)\n if (top !== undefined) return top\n }\n return stringField(tanstackMetadata(chunk)?.runId)\n}\n\n/** Thread id on the spec event, or `metadata.tanstack.threadId` after normalize. */\nexport function getChunkThreadId(chunk: StreamChunk): string | undefined {\n if ('threadId' in chunk) {\n const top = stringField(chunk.threadId)\n if (top !== undefined) return top\n }\n return stringField(tanstackMetadata(chunk)?.threadId)\n}\n"],"mappings":";;AAGA,SAAS,YAAY,OAAoC;CACvD,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;;AAGA,SAAgB,cAAc,OAAwC;CACpE,IAAI,WAAW,OAAO;EACpB,MAAM,MAAM,YAAY,MAAM,KAAK;EACnC,IAAI,QAAQ,KAAA,GAAW,OAAO;CAChC;CACA,OAAO,YAAY,iBAAiB,KAAK,CAAC,EAAE,KAAK;AACnD;;AAGA,SAAgB,iBAAiB,OAAwC;CACvE,IAAI,cAAc,OAAO;EACvB,MAAM,MAAM,YAAY,MAAM,QAAQ;EACtC,IAAI,QAAQ,KAAA,GAAW,OAAO;CAChC;CACA,OAAO,YAAY,iBAAiB,KAAK,CAAC,EAAE,QAAQ;AACtD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StreamChunk } from '../types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Best-effort extraction of a human-readable message from an unknown thrown
|
|
3
4
|
* value, returning `undefined` when none can be found.
|
|
@@ -11,3 +12,11 @@ export declare function errorMessage(err: unknown): string | undefined;
|
|
|
11
12
|
* metric attribute), falling back to `'Error'` when no name is available.
|
|
12
13
|
*/
|
|
13
14
|
export declare function errorTypeName(err: unknown): string;
|
|
15
|
+
/**
|
|
16
|
+
* Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.
|
|
17
|
+
* Preserves the provider code and sanitized raw event when available, while
|
|
18
|
+
* accepting the deprecated nested error payload for backward compatibility.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runErrorEventToError(chunk: Extract<StreamChunk, {
|
|
21
|
+
type: 'RUN_ERROR';
|
|
22
|
+
}>): Error;
|
|
@@ -26,7 +26,19 @@ function errorTypeName(err) {
|
|
|
26
26
|
}
|
|
27
27
|
return "Error";
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.
|
|
31
|
+
* Preserves the provider code and sanitized raw event when available, while
|
|
32
|
+
* accepting the deprecated nested error payload for backward compatibility.
|
|
33
|
+
*/
|
|
34
|
+
function runErrorEventToError(chunk) {
|
|
35
|
+
const error = new Error(chunk.message || chunk.error?.message || "An error occurred");
|
|
36
|
+
const code = chunk.code ?? chunk.error?.code;
|
|
37
|
+
if (code !== void 0) Object.assign(error, { code });
|
|
38
|
+
if (chunk.rawEvent !== void 0) Object.assign(error, { rawEvent: chunk.rawEvent });
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
29
41
|
//#endregion
|
|
30
|
-
export { errorMessage, errorTypeName };
|
|
42
|
+
export { errorMessage, errorTypeName, runErrorEventToError };
|
|
31
43
|
|
|
32
44
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../../src/utilities/errors.ts"],"sourcesContent":["/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../../src/utilities/errors.ts"],"sourcesContent":["import type { StreamChunk } from '../types'\n\n/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n\n/**\n * Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.\n * Preserves the provider code and sanitized raw event when available, while\n * accepting the deprecated nested error payload for backward compatibility.\n */\nexport function runErrorEventToError(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n): Error {\n const error = new Error(\n chunk.message || chunk.error?.message || 'An error occurred',\n )\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n return error\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,aAAa,KAAkC;CAC7D,IAAI,eAAe,OAAO,OAAO,IAAI;CACrC,IAAI,OAAO,QAAQ,UAAU,OAAO;CACpC,IAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,KAAK;EACtD,MAAM,IAAK,IAA8B;EACzC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;AAEF;;;;;AAMA,SAAgB,cAAc,KAAsB;CAClD,IAAI,eAAe,OAAO,OAAO,IAAI,QAAQ;CAC7C,IAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,KAAK;EACnD,MAAM,IAAK,IAA2B;EACtC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,OACO;CACP,MAAM,QAAQ,IAAI,MAChB,MAAM,WAAW,MAAM,OAAO,WAAW,mBAC3C;CACA,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO;CACxC,IAAI,SAAS,KAAA,GACX,OAAO,OAAO,OAAO,EAAE,KAAK,CAAC;CAE/B,IAAI,MAAM,aAAa,KAAA,GACrB,OAAO,OAAO,OAAO,EAAE,UAAU,MAAM,SAAS,CAAC;CAEnD,OAAO;AACT"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TanStackMessageMetadata, TanStackRunMetadata } from '../types.js';
|
|
2
|
+
export type MetadataRecord = Record<string, any>;
|
|
3
|
+
/** `metadata.tanstack` on a message or run event. Internal getter return type. */
|
|
4
|
+
export type TanStackMetadata = TanStackMessageMetadata & TanStackRunMetadata;
|
|
5
|
+
export declare function mergeMetadata(current: MetadataRecord | undefined, incoming: MetadataRecord | null | undefined): MetadataRecord | undefined;
|
|
6
|
+
/** Read `metadata.tanstack`. Not part of the public SDK surface. */
|
|
7
|
+
export declare function tanstackMetadata(value: {
|
|
8
|
+
metadata?: MetadataRecord | null;
|
|
9
|
+
} | MetadataRecord | undefined): TanStackMetadata | undefined;
|
|
10
|
+
export declare function withTanstackMetadata<T>(value: T & {
|
|
11
|
+
metadata?: MetadataRecord | null;
|
|
12
|
+
}, tanstack: MetadataRecord): Omit<T, 'metadata'> & {
|
|
13
|
+
metadata: MetadataRecord;
|
|
14
|
+
};
|