@anvia/client 1.0.0-rc.5 → 1.0.0-rc.9

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 CHANGED
@@ -38,6 +38,10 @@ subpath; importing Client does not load the Agent runtime or its infrastructure
38
38
  - `parseUIMessage` and `parseUIMessages` validate externally loaded UI state.
39
39
 
40
40
  Tool-call start, delta, and end events are automatic when the provider exposes streamed arguments.
41
+ `UIToolMessagePart` states are exact: `input-streaming` carries raw partial text,
42
+ `input-available` carries parsed JSON input, and terminal `output-available` or `error` parts retain
43
+ that input with their result. `uiMessagesToMessages()` rejects partial calls instead of replaying
44
+ incomplete JSON or inventing empty arguments.
41
45
  Errors are masked by default. Use `mapError` only at the server adapter boundary when an application
42
46
  intentionally exposes a safe error shape. Non-JSON outputs require an explicit `mapOutput`; returning
43
47
  `undefined` intentionally omits the output, while returning `null` exposes JSON `null`.
package/dist/index.d.ts CHANGED
@@ -72,6 +72,32 @@ type UIDataPart<Data extends ClientDataMap> = {
72
72
  data: Data[Name];
73
73
  };
74
74
  }[keyof Data & string];
75
+ type UIToolMessagePartBase = {
76
+ id: string;
77
+ type: "tool";
78
+ toolName: string;
79
+ toolCallId: string;
80
+ callId?: string;
81
+ internalCallId?: string;
82
+ turn?: number;
83
+ signature?: string;
84
+ };
85
+ type UIToolMessagePart = (UIToolMessagePartBase & {
86
+ state: "input-streaming";
87
+ input: string;
88
+ }) | (UIToolMessagePartBase & {
89
+ state: "input-available";
90
+ input: JsonValue;
91
+ }) | (UIToolMessagePartBase & {
92
+ state: "output-available";
93
+ input: JsonValue;
94
+ output: JsonValue;
95
+ resultContent?: readonly ToolResultContentPart[];
96
+ }) | (UIToolMessagePartBase & {
97
+ state: "error";
98
+ input: JsonValue;
99
+ error: UIError;
100
+ });
75
101
  type UIMessagePart<Data extends ClientDataMap = ClientDataMap> = {
76
102
  id: string;
77
103
  type: "text";
@@ -83,21 +109,7 @@ type UIMessagePart<Data extends ClientDataMap = ClientDataMap> = {
83
109
  text: string;
84
110
  reasoningId?: string;
85
111
  content?: readonly ReasoningDetail[];
86
- } | {
87
- id: string;
88
- type: "tool";
89
- toolName: string;
90
- toolCallId: string;
91
- callId?: string;
92
- internalCallId?: string;
93
- turn?: number;
94
- state: "input-streaming" | "input-available" | "output-available" | "error";
95
- input?: JsonValue;
96
- output?: JsonValue;
97
- resultContent?: readonly ToolResultContentPart[];
98
- signature?: string;
99
- error?: UIError;
100
- } | {
112
+ } | UIToolMessagePart | {
101
113
  id: string;
102
114
  type: "source";
103
115
  source: CompletionSource;
@@ -110,9 +122,6 @@ type UIMessagePart<Data extends ClientDataMap = ClientDataMap> = {
110
122
  type: "error";
111
123
  error: UIError;
112
124
  };
113
- type UIToolMessagePart = Extract<UIMessagePart, {
114
- type: "tool";
115
- }>;
116
125
  type ClientStreamCursor = {
117
126
  streamId: string;
118
127
  after: number;
@@ -243,7 +252,7 @@ type ClientStandardStreamEvent<Metadata extends JsonObject, Data extends ClientD
243
252
  callId?: string;
244
253
  internalCallId?: string;
245
254
  toolName: string;
246
- input?: JsonValue;
255
+ input: JsonValue;
247
256
  result: {
248
257
  status: "success";
249
258
  output: JsonValue;
package/dist/index.js CHANGED
@@ -59,11 +59,12 @@ function uiMessagesToMessages(messages) {
59
59
  if (part.content !== void 0) reasoning = { ...reasoning, details: part.content };
60
60
  content2.push(reasoning);
61
61
  } else if (part.type === "tool") {
62
+ assertReplayableToolPart(part);
62
63
  let toolCall = {
63
64
  type: "tool-call",
64
65
  toolCallId: part.toolCallId,
65
66
  toolName: part.toolName,
66
- input: part.input ?? {}
67
+ input: part.input
67
68
  };
68
69
  if (part.callId !== void 0) toolCall = { ...toolCall, callId: part.callId };
69
70
  if (part.signature !== void 0) toolCall = { ...toolCall, signature: part.signature };
@@ -90,9 +91,12 @@ function uiMessagesToMessages(messages) {
90
91
  continue;
91
92
  }
92
93
  assertOnlyParts(message, ["tool", "data", "error"]);
93
- const content = message.parts.flatMap(
94
- (part) => part.type === "tool" && (part.state === "output-available" || part.state === "error") ? [toolResultFromPart(part)] : []
95
- );
94
+ const content = [];
95
+ for (const part of message.parts) {
96
+ if (part.type !== "tool") continue;
97
+ assertToolResultPart(part);
98
+ content.push(toolResultFromPart(part));
99
+ }
96
100
  result.push({ role: "tool", content, ...metadataField(message.metadata) });
97
101
  }
98
102
  return result;
@@ -147,7 +151,13 @@ function messagesToUIMessages(messages) {
147
151
  for (const content of message.content) {
148
152
  if (content.type !== "tool-result") continue;
149
153
  const location = (content.callId === void 0 ? void 0 : byCallId.get(content.callId)) ?? byToolCallId.get(content.toolCallId);
150
- const part = toolResultToUIMessagePart(content, toolNameAt(result, location));
154
+ const ownerPart = toolPartAt(result, location);
155
+ if (ownerPart === void 0 || ownerPart.state === "input-streaming") {
156
+ throw new TypeError(
157
+ `Tool result "${content.toolCallId}" requires a matching completed tool call.`
158
+ );
159
+ }
160
+ const part = toolResultToUIMessagePart(content, ownerPart.toolName, ownerPart.input);
151
161
  if (message.metadata === void 0 && location !== void 0) {
152
162
  const owner = result[location.messageIndex];
153
163
  const existing = owner?.parts[location.partIndex];
@@ -233,12 +243,12 @@ function toolResultFromPart(part) {
233
243
  }
234
244
  function toolOutputFromPart(part) {
235
245
  if (part.state === "error") {
236
- return { type: "error-text", value: part.error?.message ?? "Tool execution failed." };
246
+ return { type: "error-text", value: part.error.message };
237
247
  }
238
248
  if (part.resultContent !== void 0) {
239
249
  return { type: "content", value: part.resultContent };
240
250
  }
241
- const output = part.output ?? null;
251
+ const output = part.output;
242
252
  return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
243
253
  }
244
254
  function attachmentToUserContent(attachment) {
@@ -293,13 +303,14 @@ function contentToAttachment(content) {
293
303
  else attachment.text = content.data.text;
294
304
  return attachment;
295
305
  }
296
- function toolResultToUIMessagePart(content, fallbackToolName) {
306
+ function toolResultToUIMessagePart(content, fallbackToolName, input) {
297
307
  const output = content.output;
298
308
  const common = {
299
309
  id: toolPartId(content.toolCallId),
300
310
  type: "tool",
301
- toolName: content.toolName || fallbackToolName || "tool",
302
- toolCallId: content.toolCallId
311
+ toolName: content.toolName || fallbackToolName,
312
+ toolCallId: content.toolCallId,
313
+ input
303
314
  };
304
315
  if (content.callId !== void 0) Object.assign(common, { callId: content.callId });
305
316
  if (output.type === "error-text" || output.type === "error-json") {
@@ -334,10 +345,28 @@ function toolResultOutput(output) {
334
345
  (part) => part.type === "text" ? { ...part } : { ...part, data: { ...part.data } }
335
346
  );
336
347
  }
337
- function toolNameAt(messages, location) {
348
+ function toolPartAt(messages, location) {
338
349
  if (location === void 0) return void 0;
339
350
  const part = messages[location.messageIndex]?.parts[location.partIndex];
340
- return part?.type === "tool" ? part.toolName : void 0;
351
+ return part?.type === "tool" ? part : void 0;
352
+ }
353
+ function assertReplayableToolPart(part) {
354
+ if (part.state === "input-streaming") {
355
+ throw new TypeError(
356
+ `Tool call "${part.toolCallId}" is still streaming and cannot be converted to a model message.`
357
+ );
358
+ }
359
+ if (!isJsonValue(part.input)) {
360
+ throw new TypeError(`Tool call "${part.toolCallId}" input must be a strict JSON value.`);
361
+ }
362
+ }
363
+ function assertToolResultPart(part) {
364
+ assertReplayableToolPart(part);
365
+ if (part.state === "input-available") {
366
+ throw new TypeError(
367
+ `Tool message part "${part.toolCallId}" requires an output or execution error.`
368
+ );
369
+ }
341
370
  }
342
371
  function textFromParts(parts) {
343
372
  return parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
@@ -612,6 +641,7 @@ function parseClientStreamEvent(value, options = {}) {
612
641
  ]);
613
642
  requireStrings(event, ["messageId", "partId", "toolCallId", "toolName"], value);
614
643
  requireOptionalStrings(event, ["callId", "internalCallId"], value);
644
+ if (!isJsonValue2(event.input)) invalid("tool_result.input", value);
615
645
  if (!isRecord(event.result)) invalid("tool_result.result", value);
616
646
  if (event.result.status === "success") {
617
647
  if (!hasOnlyKeys(event.result, ["status", "output", "content"]) || !isJsonValue2(event.result.output) || event.result.content !== void 0 && (!Array.isArray(event.result.content) || !event.result.content.every(isToolResultContent))) {
@@ -930,7 +960,7 @@ function isUIMessagePart(value) {
930
960
  if (value.type === "error") {
931
961
  return hasOnlyKeys(value, ["id", "type", "error"]) && isClientStreamError(value.error);
932
962
  }
933
- return value.type === "tool" && hasOnlyKeys(value, [
963
+ if (value.type !== "tool" || !hasOnlyKeys(value, [
934
964
  "id",
935
965
  "type",
936
966
  "toolName",
@@ -944,9 +974,19 @@ function isUIMessagePart(value) {
944
974
  "resultContent",
945
975
  "signature",
946
976
  "error"
947
- ]) && typeof value.toolName === "string" && typeof value.toolCallId === "string" && (value.callId === void 0 || typeof value.callId === "string") && (value.internalCallId === void 0 || typeof value.internalCallId === "string") && (value.turn === void 0 || isPositiveEventId(value.turn)) && (value.signature === void 0 || typeof value.signature === "string") && (value.input === void 0 || isJsonValue2(value.input)) && (value.output === void 0 || isJsonValue2(value.output)) && (value.resultContent === void 0 || Array.isArray(value.resultContent) && value.resultContent.every(isToolResultContent)) && (value.error === void 0 || isClientStreamError(value.error)) && ["input-streaming", "input-available", "output-available", "error"].includes(
948
- value.state
949
- );
977
+ ]) || typeof value.toolName !== "string" || typeof value.toolCallId !== "string" || value.callId !== void 0 && typeof value.callId !== "string" || value.internalCallId !== void 0 && typeof value.internalCallId !== "string" || value.turn !== void 0 && !isPositiveEventId(value.turn) || value.signature !== void 0 && typeof value.signature !== "string") {
978
+ return false;
979
+ }
980
+ if (value.state === "input-streaming") {
981
+ return typeof value.input === "string" && value.output === void 0 && value.resultContent === void 0 && value.error === void 0;
982
+ }
983
+ if (value.state === "input-available") {
984
+ return isJsonValue2(value.input) && value.output === void 0 && value.resultContent === void 0 && value.error === void 0;
985
+ }
986
+ if (value.state === "output-available") {
987
+ return isJsonValue2(value.input) && isJsonValue2(value.output) && (value.resultContent === void 0 || Array.isArray(value.resultContent) && value.resultContent.every(isToolResultContent)) && value.error === void 0;
988
+ }
989
+ return value.state === "error" && isJsonValue2(value.input) && value.output === void 0 && value.resultContent === void 0 && isClientStreamError(value.error);
950
990
  }
951
991
  function isReasoningContent(value) {
952
992
  if (!isRecord(value) || typeof value.type !== "string") return false;
@@ -1759,25 +1799,43 @@ function contentToUIMessageParts(content, state) {
1759
1799
  }
1760
1800
  if (contentPart.type === "tool-call") {
1761
1801
  const tool = getToolState(state, contentPart.toolCallId, contentPart.toolName);
1762
- const part = {
1802
+ const partBase = {
1763
1803
  id: tool.partId,
1764
1804
  type: "tool",
1765
1805
  toolName: contentPart.toolName,
1766
- toolCallId: contentPart.toolCallId,
1767
- state: tool.result?.status === "error" ? "error" : tool.result ? "output-available" : "input-available",
1768
- input: contentPart.input
1806
+ toolCallId: contentPart.toolCallId
1769
1807
  };
1770
- if (contentPart.callId !== void 0) part.callId = contentPart.callId;
1771
- if (tool.internalCallId !== void 0) part.internalCallId = tool.internalCallId;
1772
- if (state.turn !== void 0) part.turn = state.turn;
1773
- if (contentPart.signature !== void 0) part.signature = contentPart.signature;
1808
+ if (contentPart.callId !== void 0) Object.assign(partBase, { callId: contentPart.callId });
1809
+ if (tool.internalCallId !== void 0) {
1810
+ Object.assign(partBase, { internalCallId: tool.internalCallId });
1811
+ }
1812
+ if (state.turn !== void 0) Object.assign(partBase, { turn: state.turn });
1813
+ if (contentPart.signature !== void 0) {
1814
+ Object.assign(partBase, { signature: contentPart.signature });
1815
+ }
1774
1816
  if (tool.result?.status === "success") {
1775
- part.output = tool.result.output;
1817
+ const part = {
1818
+ ...partBase,
1819
+ state: "output-available",
1820
+ input: contentPart.input,
1821
+ output: tool.result.output
1822
+ };
1776
1823
  if (tool.result.content !== void 0) part.resultContent = tool.result.content;
1777
- } else if (tool.result?.status === "error") {
1778
- part.error = tool.result.error;
1824
+ return part;
1779
1825
  }
1780
- return part;
1826
+ if (tool.result?.status === "error") {
1827
+ return {
1828
+ ...partBase,
1829
+ state: "error",
1830
+ input: contentPart.input,
1831
+ error: tool.result.error
1832
+ };
1833
+ }
1834
+ return {
1835
+ ...partBase,
1836
+ state: "input-available",
1837
+ input: contentPart.input
1838
+ };
1781
1839
  }
1782
1840
  const attachment = contentAttachment(contentPart);
1783
1841
  return {
@@ -2193,21 +2251,22 @@ function applyClientStreamEvent(messages, event) {
2193
2251
  }
2194
2252
  case "tool_call_delta":
2195
2253
  return updatePart(messages, event.messageId, event.partId, (current) => {
2196
- const part = current?.type === "tool" ? { ...current } : {
2254
+ const part = current?.type === "tool" ? toolPartBase(current) : {
2197
2255
  id: event.partId,
2198
2256
  type: "tool",
2199
2257
  toolName: event.toolName ?? "",
2200
- toolCallId: event.toolCallId,
2201
- state: "input-streaming"
2258
+ toolCallId: event.toolCallId
2202
2259
  };
2203
- const previous = typeof part.input === "string" ? part.input : "";
2204
- part.input = event.mode === "replace" ? event.delta : `${previous}${event.delta}`;
2205
- part.state = "input-streaming";
2260
+ const previous = current?.type === "tool" && typeof current.input === "string" ? current.input : "";
2206
2261
  if (event.toolName !== void 0) part.toolName = event.toolName;
2207
2262
  if (event.callId !== void 0) part.callId = event.callId;
2208
2263
  if (event.signature !== void 0) part.signature = event.signature;
2209
2264
  if (event.turn !== void 0) part.turn = event.turn;
2210
- return part;
2265
+ return {
2266
+ ...part,
2267
+ state: "input-streaming",
2268
+ input: event.mode === "replace" ? event.delta : `${previous}${event.delta}`
2269
+ };
2211
2270
  });
2212
2271
  case "tool_call_end": {
2213
2272
  const part = {
@@ -2225,26 +2284,31 @@ function applyClientStreamEvent(messages, event) {
2225
2284
  }
2226
2285
  case "tool_result":
2227
2286
  return updatePart(messages, event.messageId, event.partId, (current) => {
2228
- const part = current?.type === "tool" ? { ...current } : {
2287
+ const part = current?.type === "tool" ? toolPartBase(current) : {
2229
2288
  id: event.partId,
2230
2289
  type: "tool",
2231
2290
  toolName: event.toolName,
2232
- toolCallId: event.toolCallId,
2233
- state: "input-available"
2291
+ toolCallId: event.toolCallId
2234
2292
  };
2235
2293
  if (event.callId !== void 0) part.callId = event.callId;
2236
2294
  if (event.internalCallId !== void 0) part.internalCallId = event.internalCallId;
2237
- if (event.input !== void 0) part.input = event.input;
2238
2295
  if (event.turn !== void 0) part.turn = event.turn;
2239
2296
  if (event.result.status === "error") {
2240
- part.state = "error";
2241
- part.error = event.result.error;
2242
- } else {
2243
- part.state = "output-available";
2244
- part.output = event.result.output;
2245
- if (event.result.content !== void 0) part.resultContent = event.result.content;
2297
+ return {
2298
+ ...part,
2299
+ state: "error",
2300
+ input: event.input,
2301
+ error: event.result.error
2302
+ };
2246
2303
  }
2247
- return part;
2304
+ const completed = {
2305
+ ...part,
2306
+ state: "output-available",
2307
+ input: event.input,
2308
+ output: event.result.output
2309
+ };
2310
+ if (event.result.content !== void 0) completed.resultContent = event.result.content;
2311
+ return completed;
2248
2312
  });
2249
2313
  case "source":
2250
2314
  return upsertPart(messages, event.messageId, {
@@ -2314,6 +2378,21 @@ function applyClientStreamEvent(messages, event) {
2314
2378
  return messages;
2315
2379
  }
2316
2380
  }
2381
+ function toolPartBase(part) {
2382
+ const base = {
2383
+ id: part.id,
2384
+ type: part.type,
2385
+ toolName: part.toolName,
2386
+ toolCallId: part.toolCallId
2387
+ };
2388
+ if (part.callId !== void 0) Object.assign(base, { callId: part.callId });
2389
+ if (part.internalCallId !== void 0) {
2390
+ Object.assign(base, { internalCallId: part.internalCallId });
2391
+ }
2392
+ if (part.turn !== void 0) Object.assign(base, { turn: part.turn });
2393
+ if (part.signature !== void 0) Object.assign(base, { signature: part.signature });
2394
+ return base;
2395
+ }
2317
2396
  function reconcileFinalParts(current, final) {
2318
2397
  const clientOnly = current.filter(
2319
2398
  (part) => part.type === "source" || part.type === "data" || part.type === "error"