@cadraos/sdk 0.3.14-p46.4 → 0.3.15

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.
Files changed (38) hide show
  1. package/README.md +104 -53
  2. package/dist/ai-functions/react/index.cjs +176 -0
  3. package/dist/ai-functions/react/index.cjs.map +1 -0
  4. package/dist/ai-functions/react/index.d.cts +129 -0
  5. package/dist/ai-functions/react/index.d.ts +129 -0
  6. package/dist/ai-functions/react/index.js +153 -0
  7. package/dist/ai-functions/react/index.js.map +1 -0
  8. package/dist/ai-functions/server/index.cjs +296 -0
  9. package/dist/ai-functions/server/index.cjs.map +1 -0
  10. package/dist/ai-functions/server/index.d.cts +334 -0
  11. package/dist/ai-functions/server/index.d.ts +334 -0
  12. package/dist/ai-functions/server/index.js +284 -0
  13. package/dist/ai-functions/server/index.js.map +1 -0
  14. package/dist/chat/index.cjs +303 -17
  15. package/dist/chat/index.cjs.map +1 -1
  16. package/dist/chat/index.d.cts +211 -0
  17. package/dist/chat/index.d.ts +211 -0
  18. package/dist/chat/index.js +303 -17
  19. package/dist/chat/index.js.map +1 -1
  20. package/dist/index.cjs +225 -3
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +279 -1
  23. package/dist/index.d.ts +279 -1
  24. package/dist/index.js +219 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/kanban/index.cjs +218 -3
  27. package/dist/kanban/index.cjs.map +1 -1
  28. package/dist/kanban/index.d.cts +211 -0
  29. package/dist/kanban/index.d.ts +211 -0
  30. package/dist/kanban/index.js +218 -3
  31. package/dist/kanban/index.js.map +1 -1
  32. package/dist/teams/index.cjs +218 -3
  33. package/dist/teams/index.cjs.map +1 -1
  34. package/dist/teams/index.d.cts +211 -0
  35. package/dist/teams/index.d.ts +211 -0
  36. package/dist/teams/index.js +218 -3
  37. package/dist/teams/index.js.map +1 -1
  38. package/package.json +21 -1
@@ -68,6 +68,14 @@ var CadraError = class extends Error {
68
68
  details;
69
69
  /** Request ID from server (for debugging) */
70
70
  requestId;
71
+ /**
72
+ * The server's own `error.code` exactly as sent on the wire (e.g.
73
+ * `FUNCTION_NOT_PUBLISHED`, `DUPLICATE_REQUEST`, `VARIABLE_TYPE`).
74
+ * Populated by `parseApiError` for EVERY status. `code` keeps its historic
75
+ * per-class values (`conflict`, `server_error`, `validation_error`, …) for
76
+ * compatibility; branch on `serverCode` when you need the precise reason.
77
+ */
78
+ serverCode;
71
79
  constructor(message, code, status, details, requestId) {
72
80
  super(message);
73
81
  this.name = "CadraError";
@@ -145,6 +153,12 @@ function parseApiError(status, body, requestId) {
145
153
  const message = errorBody?.error?.message ?? "An error occurred";
146
154
  const code = errorBody?.error?.code ?? "unknown_error";
147
155
  const details = errorBody?.error?.details;
156
+ const serverCode = typeof errorBody?.error?.code === "string" ? errorBody.error.code : void 0;
157
+ const error = buildApiError(status, message, code, details, requestId);
158
+ if (serverCode !== void 0) error.serverCode = serverCode;
159
+ return error;
160
+ }
161
+ function buildApiError(status, message, code, details, requestId) {
148
162
  switch (status) {
149
163
  case 400:
150
164
  case 422: {
@@ -180,6 +194,42 @@ function parseApiError(status, body, requestId) {
180
194
  }
181
195
  }
182
196
 
197
+ // src/errors/ai-function-errors.ts
198
+ var AiFunctionDuplicateError = class extends ConflictError {
199
+ /** The existing run's execution id. */
200
+ executionId;
201
+ /** The existing run's status (`running`, a terminal status, or `abandoned`). */
202
+ runStatus;
203
+ constructor(message, details, requestId) {
204
+ super(message, details, requestId);
205
+ this.name = "AiFunctionDuplicateError";
206
+ this.serverCode = "DUPLICATE_REQUEST";
207
+ this.executionId = typeof details?.executionId === "string" ? details.executionId : void 0;
208
+ this.runStatus = typeof details?.status === "string" ? details.status : void 0;
209
+ }
210
+ };
211
+ var AiFunctionChargedError = class extends CadraError {
212
+ constructor(source) {
213
+ super(source.message, source.code, source.status, source.details, source.requestId);
214
+ this.name = "AiFunctionChargedError";
215
+ this.serverCode = source.serverCode;
216
+ }
217
+ };
218
+ var AiFunctionParameterError = class extends ValidationError {
219
+ functionKey;
220
+ issues;
221
+ constructor(functionKey, issues) {
222
+ super(
223
+ `Invalid parameters for AI function "${functionKey}": ` + issues.map((i) => `${i.name} (${i.code})`).join(", "),
224
+ issues.map((i) => ({ field: i.name, message: i.code })),
225
+ { functionKey, issues }
226
+ );
227
+ this.name = "AiFunctionParameterError";
228
+ this.functionKey = functionKey;
229
+ this.issues = issues;
230
+ }
231
+ };
232
+
183
233
  // src/utils/retry.ts
184
234
  var DEFAULT_RETRY_CONFIG = {
185
235
  maxRetries: 3,
@@ -244,6 +294,10 @@ var HttpClient = class {
244
294
  constructor(config) {
245
295
  this.config = config;
246
296
  }
297
+ /** The client-wide request timeout (ms) this client was configured with. */
298
+ get timeoutMs() {
299
+ return this.config.timeout;
300
+ }
247
301
  /**
248
302
  * Get authorization header value
249
303
  */
@@ -298,7 +352,8 @@ var HttpClient = class {
298
352
  headers["Idempotency-Key"] = options.idempotencyKey;
299
353
  }
300
354
  const controller = new AbortController();
301
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
355
+ const timeoutMs = options.timeoutMs ?? this.config.timeout;
356
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
302
357
  try {
303
358
  const doFetch = this.config.fetch ?? fetch;
304
359
  const response = await doFetch(url, {
@@ -334,7 +389,7 @@ var HttpClient = class {
334
389
  } catch (error) {
335
390
  clearTimeout(timeoutId);
336
391
  if (error instanceof Error && error.name === "AbortError") {
337
- throw new TimeoutError(`Request timed out after ${this.config.timeout}ms`);
392
+ throw new TimeoutError(`Request timed out after ${options.timeoutMs ?? this.config.timeout}ms`);
338
393
  }
339
394
  if (error instanceof TypeError && error.message.includes("fetch")) {
340
395
  throw new NetworkError(`Network error: ${error.message}`);
@@ -366,7 +421,8 @@ var HttpClient = class {
366
421
  path,
367
422
  body,
368
423
  idempotencyKey: options?.idempotencyKey,
369
- skipRetry: options?.skipRetry
424
+ skipRetry: options?.skipRetry,
425
+ timeoutMs: options?.timeoutMs
370
426
  });
371
427
  }
372
428
  /**
@@ -1125,6 +1181,10 @@ var ExecutionStreamControllerImpl = class {
1125
1181
  toolName,
1126
1182
  result: data.result,
1127
1183
  durationMs: data.durationMs ?? 0,
1184
+ // CAD-243: pass the producer's failure verdict through.
1185
+ ...data.isError === true ? { isError: true } : {},
1186
+ ...typeof data.status === "string" ? { status: data.status } : {},
1187
+ ...typeof data.error === "string" ? { error: data.error } : {},
1128
1188
  forwardedFrom: event.data._forwardedFrom,
1129
1189
  // CAD-87 S1-001 EventIdentity → (execId, seq) reducer dedup (S1-003).
1130
1190
  sourceExecutionId: data.sourceExecutionId ?? data.executionId,
@@ -3363,6 +3423,156 @@ var RemoteRunnersAPI = class extends BaseResourceAPI {
3363
3423
  }
3364
3424
  };
3365
3425
 
3426
+ // src/resources/ai-functions.ts
3427
+ var AI_FUNCTION_RUN_TIMEOUT_MS = 31e4;
3428
+ var AI_FUNCTION_DESCRIPTOR_TTL_MS = 6e4;
3429
+ function checkAiFunctionParameters(parameters, variables) {
3430
+ const issues = [];
3431
+ const declared = /* @__PURE__ */ new Set();
3432
+ for (const p of parameters) {
3433
+ declared.add(p.name);
3434
+ const has = Object.prototype.hasOwnProperty.call(variables, p.name);
3435
+ const value = has ? variables[p.name] : void 0;
3436
+ if (value === void 0 || value === null) {
3437
+ if (p.required) issues.push({ name: p.name, code: "missing" });
3438
+ continue;
3439
+ }
3440
+ switch (p.type) {
3441
+ case "number":
3442
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3443
+ issues.push({ name: p.name, code: "type" });
3444
+ }
3445
+ break;
3446
+ case "boolean":
3447
+ if (typeof value !== "boolean") issues.push({ name: p.name, code: "type" });
3448
+ break;
3449
+ case "enum":
3450
+ if (typeof value !== "string") issues.push({ name: p.name, code: "type" });
3451
+ else if (p.options && !p.options.includes(value)) {
3452
+ issues.push({ name: p.name, code: "option" });
3453
+ }
3454
+ break;
3455
+ case "string":
3456
+ case "text":
3457
+ if (typeof value !== "string") issues.push({ name: p.name, code: "type" });
3458
+ else if (typeof p.maxLength === "number" && value.length > p.maxLength) {
3459
+ issues.push({ name: p.name, code: "too_long" });
3460
+ }
3461
+ break;
3462
+ case "image":
3463
+ if (typeof value !== "string") issues.push({ name: p.name, code: "type" });
3464
+ break;
3465
+ }
3466
+ }
3467
+ for (const name of Object.keys(variables)) {
3468
+ if (!declared.has(name)) issues.push({ name, code: "unknown" });
3469
+ }
3470
+ return issues;
3471
+ }
3472
+ var AiFunctionsAPI = class extends BaseResourceAPI {
3473
+ descriptorCache = /* @__PURE__ */ new Map();
3474
+ constructor(http) {
3475
+ super(http, "/api/v1/ai-functions");
3476
+ }
3477
+ /**
3478
+ * List the functions this org can call — the index (D27a). Each entry carries
3479
+ * the live version's typed parameters; never prompt text.
3480
+ */
3481
+ async list() {
3482
+ const data = await this.httpGet("");
3483
+ const functions = Array.isArray(data) ? data : data?.functions ?? [];
3484
+ for (const d of functions) this.cacheDescriptor(d);
3485
+ return functions;
3486
+ }
3487
+ /**
3488
+ * Get one function's descriptor by key (D27a).
3489
+ * `NotFoundError` (`FUNCTION_NOT_FOUND`) / `ConflictError` (`FUNCTION_NOT_PUBLISHED`).
3490
+ */
3491
+ async get(functionKey) {
3492
+ const data = await this.httpGet(
3493
+ `/${encodeURIComponent(functionKey)}`
3494
+ );
3495
+ const descriptor = data && typeof data === "object" && "function" in data && !("functionKey" in data) ? data.function : data;
3496
+ this.cacheDescriptor(descriptor, functionKey);
3497
+ return descriptor;
3498
+ }
3499
+ /**
3500
+ * Read one of this org's own runs (safe projection, §6.5).
3501
+ */
3502
+ async getRun(executionId) {
3503
+ return this.httpGet(`/runs/${encodeURIComponent(executionId)}`);
3504
+ }
3505
+ /** Forget cached descriptors (one key, or all). */
3506
+ clearDescriptorCache(functionKey) {
3507
+ if (functionKey) this.descriptorCache.delete(functionKey);
3508
+ else this.descriptorCache.clear();
3509
+ }
3510
+ async run(refOrKey, parametersOrOptions, shorthandOpts) {
3511
+ let ref;
3512
+ let options;
3513
+ if (typeof refOrKey === "string") {
3514
+ ref = { functionKey: refOrKey };
3515
+ options = { ...shorthandOpts ?? {}, variables: parametersOrOptions };
3516
+ } else {
3517
+ ref = refOrKey;
3518
+ options = parametersOrOptions;
3519
+ }
3520
+ const variables = options.variables ?? {};
3521
+ const functionKey = "functionKey" in ref ? ref.functionKey : void 0;
3522
+ const shouldCheck = functionKey !== void 0 && options.version === void 0 && options.validate !== false;
3523
+ if (shouldCheck) {
3524
+ const descriptor = await this.getCachedDescriptor(functionKey);
3525
+ const issues = checkAiFunctionParameters(descriptor.parameters, variables);
3526
+ if (issues.length > 0) {
3527
+ throw new AiFunctionParameterError(functionKey, issues);
3528
+ }
3529
+ }
3530
+ const body = {
3531
+ ref,
3532
+ variables,
3533
+ version: options.version,
3534
+ idempotencyKey: options.idempotencyKey ?? generateExecutionId(),
3535
+ params: options.params
3536
+ };
3537
+ try {
3538
+ return await this.httpPost("/run", body, {
3539
+ skipRetry: true,
3540
+ timeoutMs: Math.max(AI_FUNCTION_RUN_TIMEOUT_MS, this.http.timeoutMs ?? 0)
3541
+ });
3542
+ } catch (error) {
3543
+ throw this.mapRunError(error, functionKey);
3544
+ }
3545
+ }
3546
+ // ── internals ──────────────────────────────────────────────────────────────
3547
+ cacheDescriptor(descriptor, key) {
3548
+ if (!descriptor || typeof descriptor !== "object") return;
3549
+ const cacheKey = key ?? descriptor.functionKey;
3550
+ if (!cacheKey) return;
3551
+ this.descriptorCache.set(cacheKey, {
3552
+ descriptor,
3553
+ expiresAt: Date.now() + AI_FUNCTION_DESCRIPTOR_TTL_MS
3554
+ });
3555
+ }
3556
+ async getCachedDescriptor(functionKey) {
3557
+ const hit = this.descriptorCache.get(functionKey);
3558
+ if (hit && hit.expiresAt > Date.now()) return hit.descriptor;
3559
+ return this.get(functionKey);
3560
+ }
3561
+ mapRunError(error, functionKey) {
3562
+ if (!(error instanceof CadraError)) return error;
3563
+ if (error.serverCode === "DUPLICATE_REQUEST") {
3564
+ return new AiFunctionDuplicateError(error.message, error.details, error.requestId);
3565
+ }
3566
+ if (error.details?.charged === true) {
3567
+ return new AiFunctionChargedError(error);
3568
+ }
3569
+ if (functionKey && error.serverCode?.startsWith("VARIABLE_")) {
3570
+ this.descriptorCache.delete(functionKey);
3571
+ }
3572
+ return error;
3573
+ }
3574
+ };
3575
+
3366
3576
  // src/client.ts
3367
3577
  var DEFAULT_BASE_URL = "https://api.cadraos.com";
3368
3578
  var DEFAULT_TIMEOUT = 3e4;
@@ -3409,6 +3619,10 @@ var CadraClient = class {
3409
3619
  * Remote runner operations (list)
3410
3620
  */
3411
3621
  remoteRunners;
3622
+ /**
3623
+ * AI function operations (list, get, run, getRun) — p93
3624
+ */
3625
+ aiFunctions;
3412
3626
  /**
3413
3627
  * Create a new AI SaaS client
3414
3628
  *
@@ -3498,6 +3712,7 @@ var CadraClient = class {
3498
3712
  this.kanban = new KanbanAPI(this.httpClient, getStreamConfig);
3499
3713
  this.remoteAgents = new RemoteAgentsAPI(this.httpClient, getStreamConfig);
3500
3714
  this.remoteRunners = new RemoteRunnersAPI(this.httpClient);
3715
+ this.aiFunctions = new AiFunctionsAPI(this.httpClient);
3501
3716
  }
3502
3717
  /**
3503
3718
  * Validate client options
@@ -4187,6 +4402,32 @@ function useExecutionStream(options) {
4187
4402
  };
4188
4403
  }
4189
4404
 
4405
+ // src/chat/utils/toolOutcome.ts
4406
+ var MAX_ERROR_CHARS = 500;
4407
+ function asRecord(value) {
4408
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4409
+ }
4410
+ function errorFrom(value) {
4411
+ if (typeof value === "string" && value.trim()) return value;
4412
+ const rec = asRecord(value);
4413
+ if (rec && typeof rec.message === "string" && rec.message.trim()) return rec.message;
4414
+ return void 0;
4415
+ }
4416
+ function isFailurePayload(result) {
4417
+ const rec = asRecord(result);
4418
+ if (!rec) return false;
4419
+ return rec.success === false || rec.isError === true;
4420
+ }
4421
+ function cap(text) {
4422
+ return text.length > MAX_ERROR_CHARS ? `${text.slice(0, MAX_ERROR_CHARS)}\u2026` : text;
4423
+ }
4424
+ function resolveToolOutcome(input) {
4425
+ const failed = input.isError === true || input.status === "failed" || input.success === false || input.payloadFailure === true || isFailurePayload(input.result);
4426
+ if (!failed) return { status: "completed" };
4427
+ const error = errorFrom(input.error) ?? errorFrom(asRecord(input.result)?.error) ?? "Tool call failed";
4428
+ return { status: "failed", error: cap(error) };
4429
+ }
4430
+
4190
4431
  // src/chat/hooks/history-cache.ts
4191
4432
  var DEFAULT_HISTORY_STALE_MS = 1e4;
4192
4433
  var cache = /* @__PURE__ */ new Map();
@@ -4362,9 +4603,10 @@ function applyEventInner(state, event) {
4362
4603
  if (tc) {
4363
4604
  toolCalls.set(event.id, {
4364
4605
  ...tc,
4365
- status: "completed",
4606
+ status: event.status ?? "completed",
4366
4607
  result: event.result,
4367
- durationMs: event.durationMs
4608
+ durationMs: event.durationMs,
4609
+ ...event.error !== void 0 ? { error: event.error } : {}
4368
4610
  });
4369
4611
  return { ...state, toolCalls };
4370
4612
  }
@@ -4373,8 +4615,9 @@ function applyEventInner(state, event) {
4373
4615
  name: event.toolName ?? "unknown",
4374
4616
  args: {},
4375
4617
  result: event.result,
4376
- status: "completed",
4377
- durationMs: event.durationMs
4618
+ status: event.status ?? "completed",
4619
+ durationMs: event.durationMs,
4620
+ ...event.error !== void 0 ? { error: event.error } : {}
4378
4621
  });
4379
4622
  if (state.seenToolBlockIds.has(event.id)) {
4380
4623
  return { ...state, toolCalls };
@@ -4624,8 +4867,16 @@ function useAgentChat(options) {
4624
4867
  const streamMsgId = streamingMessageIdRef.current;
4625
4868
  setMessages((prev) => {
4626
4869
  const resolvedContent = streamingContentRef.current || finalOutput || "";
4870
+ const streamIdx = prev.findIndex((msg) => msg.id === streamMsgId);
4871
+ let turnStart = 0;
4872
+ for (let i = streamIdx - 1; i >= 0; i--) {
4873
+ if (prev[i].role === "user") {
4874
+ turnStart = i + 1;
4875
+ break;
4876
+ }
4877
+ }
4627
4878
  const duplicateExists = resolvedContent && prev.some(
4628
- (msg) => msg.id !== streamMsgId && msg.role === "assistant" && !msg.isStreaming && msg.content === resolvedContent
4879
+ (msg, idx) => idx >= turnStart && msg.id !== streamMsgId && msg.role === "assistant" && !msg.isStreaming && msg.content === resolvedContent
4629
4880
  );
4630
4881
  if (duplicateExists) {
4631
4882
  return prev.filter((msg) => msg.id !== streamMsgId);
@@ -4746,6 +4997,17 @@ function useAgentChat(options) {
4746
4997
  const resultToolCallId = log.metadata?.toolCallId;
4747
4998
  const result = log.metadata?.output;
4748
4999
  const durationMs = log.durationMs;
5000
+ const outcome = resolveToolOutcome({
5001
+ result,
5002
+ success: log.metadata?.success,
5003
+ payloadFailure: log.metadata?.payloadFailure,
5004
+ isError: log.metadata?.isError,
5005
+ error: log.metadata?.error
5006
+ });
5007
+ const outcomeFields = {
5008
+ status: outcome.status,
5009
+ ...outcome.error ? { error: outcome.error } : {}
5010
+ };
4749
5011
  let matchedToolCall = resultToolCallId ? pendingToolCallsRef.current.get(resultToolCallId) : void 0;
4750
5012
  if (!matchedToolCall) {
4751
5013
  for (const [, tc] of pendingToolCallsRef.current) {
@@ -4759,7 +5021,7 @@ function useAgentChat(options) {
4759
5021
  const completedToolCall = {
4760
5022
  ...matchedToolCall,
4761
5023
  result,
4762
- status: "completed",
5024
+ ...outcomeFields,
4763
5025
  durationMs
4764
5026
  };
4765
5027
  updateToolCall(completedToolCall);
@@ -4770,7 +5032,7 @@ function useAgentChat(options) {
4770
5032
  name: resultToolName || "unknown",
4771
5033
  args: {},
4772
5034
  result,
4773
- status: "completed",
5035
+ ...outcomeFields,
4774
5036
  durationMs
4775
5037
  };
4776
5038
  updateToolCall(lateToolCall);
@@ -4781,7 +5043,8 @@ function useAgentChat(options) {
4781
5043
  id: resultToolCallId,
4782
5044
  toolName: resultToolName,
4783
5045
  result,
4784
- durationMs
5046
+ durationMs,
5047
+ ...outcomeFields
4785
5048
  });
4786
5049
  firedToolResultsRef.current.add(resultToolCallId);
4787
5050
  }
@@ -4891,6 +5154,17 @@ function useAgentChat(options) {
4891
5154
  const resultToolCallId = log.metadata?.toolCallId;
4892
5155
  const result = log.metadata?.output;
4893
5156
  const durationMs = log.durationMs;
5157
+ const outcome = resolveToolOutcome({
5158
+ result,
5159
+ success: log.metadata?.success,
5160
+ payloadFailure: log.metadata?.payloadFailure,
5161
+ isError: log.metadata?.isError,
5162
+ error: log.metadata?.error
5163
+ });
5164
+ const outcomeFields = {
5165
+ status: outcome.status,
5166
+ ...outcome.error ? { error: outcome.error } : {}
5167
+ };
4894
5168
  let matchedToolCall = resultToolCallId ? pendingToolCallsRef.current.get(resultToolCallId) : void 0;
4895
5169
  if (!matchedToolCall) {
4896
5170
  for (const [, tc] of pendingToolCallsRef.current) {
@@ -4904,7 +5178,7 @@ function useAgentChat(options) {
4904
5178
  const completedToolCall = {
4905
5179
  ...matchedToolCall,
4906
5180
  result,
4907
- status: "completed",
5181
+ ...outcomeFields,
4908
5182
  durationMs
4909
5183
  };
4910
5184
  updateToolCall(completedToolCall);
@@ -4915,7 +5189,7 @@ function useAgentChat(options) {
4915
5189
  name: resultToolName || "unknown",
4916
5190
  args: {},
4917
5191
  result,
4918
- status: "completed",
5192
+ ...outcomeFields,
4919
5193
  durationMs
4920
5194
  };
4921
5195
  updateToolCall(lateToolCall);
@@ -4926,7 +5200,8 @@ function useAgentChat(options) {
4926
5200
  id: resultToolCallId,
4927
5201
  toolName: resultToolName,
4928
5202
  result,
4929
- durationMs
5203
+ durationMs,
5204
+ ...outcomeFields
4930
5205
  });
4931
5206
  }
4932
5207
  if (resultToolCallId) {
@@ -5175,12 +5450,22 @@ function useAgentChat(options) {
5175
5450
  if (SILENT_TOOL_NAMES.has(toolName) || isUuidLikeToolName(toolName)) return;
5176
5451
  if (firedToolResultsRef.current.has(toolCallId)) return;
5177
5452
  const existing = pendingToolCallsRef.current.get(toolCallId);
5178
- if (existing && existing.status === "completed") return;
5453
+ if (existing && (existing.status === "completed" || existing.status === "failed")) return;
5454
+ const outcome = resolveToolOutcome({
5455
+ result: data.result,
5456
+ isError: data.isError,
5457
+ status: data.status,
5458
+ error: data.error
5459
+ });
5460
+ const outcomeFields = {
5461
+ status: outcome.status,
5462
+ ...outcome.error ? { error: outcome.error } : {}
5463
+ };
5179
5464
  if (existing) {
5180
5465
  const completedToolCall = {
5181
5466
  ...existing,
5182
5467
  result: data.result,
5183
- status: "completed",
5468
+ ...outcomeFields,
5184
5469
  durationMs: data.durationMs
5185
5470
  };
5186
5471
  updateToolCall(completedToolCall);
@@ -5191,7 +5476,7 @@ function useAgentChat(options) {
5191
5476
  name: toolName,
5192
5477
  args: {},
5193
5478
  result: data.result,
5194
- status: "completed",
5479
+ ...outcomeFields,
5195
5480
  durationMs: data.durationMs
5196
5481
  };
5197
5482
  updateToolCall(lateToolCall);
@@ -5202,6 +5487,7 @@ function useAgentChat(options) {
5202
5487
  toolName,
5203
5488
  result: data.result,
5204
5489
  durationMs: data.durationMs,
5490
+ ...outcomeFields,
5205
5491
  // CAD-87 S1-003: (execId, seq) reducer dedup for replayed events.
5206
5492
  execId: data.sourceExecutionId,
5207
5493
  seq: data.seq