@anvia/client 1.6.0 → 1.8.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.
@@ -0,0 +1,965 @@
1
+ import { DurableHttpError } from "./durable.js";
2
+ import { n as createClientId, t as CLIENT_STREAM_PROTOCOL } from "./types-CqJxMo_1.js";
3
+ import { isJsonValue } from "@anvia/core/completion";
4
+ // src/durable-chat-map.ts
5
+ function createDurableChatMapState(sequence = 0) {
6
+ return {
7
+ sequence,
8
+ openedOperations: /* @__PURE__ */ new Set(),
9
+ openedParts: /* @__PURE__ */ new Set(),
10
+ tools: /* @__PURE__ */ new Map(),
11
+ runOpened: false
12
+ };
13
+ }
14
+ /**
15
+ * Map one durable event. Events at or before the applied cursor produce nothing, so a reconnect
16
+ * that overlaps the stored sequence does not double-apply.
17
+ */
18
+ function mapDurableChatEvent(event, state, settledOutcome) {
19
+ if (!Number.isSafeInteger(event.sequence) || event.sequence <= state.sequence) return {
20
+ events: [],
21
+ state
22
+ };
23
+ const draft = fork(state, event.sequence);
24
+ return {
25
+ events: project(event, draft, settledOutcome),
26
+ state: draft
27
+ };
28
+ }
29
+ /**
30
+ * True when a status event needs the run's outcome, which the event itself no longer carries
31
+ * (DUR-07). The caller reads it from the run snapshot and passes it to `mapDurableChatEvent`.
32
+ * Journals written before the change still carry `data.outcome`, so nothing is fetched for them.
33
+ */
34
+ function needsSettledOutcome(event) {
35
+ if (event.type !== "status") return false;
36
+ const data = asRecord(event.data);
37
+ if (data === void 0 || data.outcome !== void 0) return false;
38
+ const status = text(data, "status");
39
+ return status === "completed" || status === "waiting" || status === "failed";
40
+ }
41
+ /** The status a status event reports, so a caller can check a snapshot is still at that point. */
42
+ function statusEventStatus(event) {
43
+ return event.type === "status" ? text(asRecord(event.data), "status") : void 0;
44
+ }
45
+ /**
46
+ * One assistant message per model operation. Retries keep the identity and reset its parts, so a
47
+ * streamed journal never leaves the empty placeholders that per-attempt ids would create.
48
+ */
49
+ function durableChatMessageId(runId, operationId) {
50
+ return `${runId}:${operationId}`;
51
+ }
52
+ function project(event, state, settledOutcome) {
53
+ const data = asRecord(event.data);
54
+ switch (event.type) {
55
+ case "submitted": return openRun(event.runId, state);
56
+ case "status": return mapStatus(event, data, state, settledOutcome);
57
+ case "model_started": return mapModelStarted(event, data, state);
58
+ case "model_attempt_started": return mapAttemptStarted(event, data, state);
59
+ case "model_delta": return mapModelDelta(event, data, state);
60
+ case "model_attempt_failed": return mapAttemptFailed(event, data, state);
61
+ case "model_completed": return mapModelCompleted(event, data, state);
62
+ case "tool_started": return mapToolStarted(event, data, state);
63
+ case "tool_completed": return mapToolCompleted(event, data, state);
64
+ case "compaction_completed": return mapCompaction(event, data);
65
+ case "steering_queued":
66
+ case "steering_applied": return [located(event, {
67
+ type: "data",
68
+ name: event.type,
69
+ data: data ?? {},
70
+ transient: true
71
+ })];
72
+ default: return [];
73
+ }
74
+ }
75
+ function mapStatus(event, data, state, settledOutcome) {
76
+ const status = text(data, "status");
77
+ const outcome = asRecord(data?.outcome) ?? settledOutcome;
78
+ if (status === "needs_attention") return needsAttention(event, data);
79
+ if (status === "failed") return failed(event, data, outcome);
80
+ if (status === "cancelled") return [located(event, {
81
+ type: "run_end",
82
+ status: "cancelled"
83
+ })];
84
+ if (status === "waiting" || status === "completed") return settled(event, outcome, status);
85
+ if (!state.runOpened && (status === "queued" || status === "pending" || status === "running")) return openRun(event.runId, state);
86
+ return [];
87
+ }
88
+ function openRun(runId, state) {
89
+ state.runOpened = true;
90
+ return [{
91
+ type: "run_start",
92
+ source: "agent",
93
+ runId
94
+ }];
95
+ }
96
+ function mapModelStarted(event, data, state) {
97
+ const operationId = text(data, "operationId") ?? "model";
98
+ const messageId = durableChatMessageId(event.runId, operationId);
99
+ state.openedOperations.add(operationId);
100
+ state.messageId = messageId;
101
+ const generation = {
102
+ type: "generation_start",
103
+ runId: event.runId
104
+ };
105
+ const model = modelOf(data);
106
+ if (model !== void 0) generation.model = model;
107
+ const events = [];
108
+ if (!state.runOpened) events.push({
109
+ type: "run_start",
110
+ source: "agent",
111
+ runId: event.runId
112
+ });
113
+ state.runOpened = true;
114
+ events.push({
115
+ type: "turn_start",
116
+ runId: event.runId
117
+ }, generation, {
118
+ type: "message_start",
119
+ messageId,
120
+ role: "assistant",
121
+ runId: event.runId
122
+ });
123
+ return events;
124
+ }
125
+ function mapAttemptStarted(event, data, state) {
126
+ const operationId = text(data, "operationId");
127
+ if (operationId === void 0) return [];
128
+ const attempt = number(data, "attempt");
129
+ const messageId = state.messageId ?? durableChatMessageId(event.runId, operationId);
130
+ const opened = state.openedOperations.has(operationId);
131
+ const retried = opened && (attempt === void 0 || attempt > 1);
132
+ const events = [];
133
+ if (retried && state.messageId !== void 0) events.push({
134
+ type: "message_reset",
135
+ messageId: state.messageId,
136
+ runId: event.runId
137
+ });
138
+ else if (!opened) events.push({
139
+ type: "message_start",
140
+ messageId,
141
+ role: "assistant",
142
+ runId: event.runId
143
+ });
144
+ state.openedOperations.add(operationId);
145
+ state.messageId = messageId;
146
+ return events;
147
+ }
148
+ function mapModelDelta(event, data, state) {
149
+ const nested = asRecord(data?.event);
150
+ const kind = text(nested, "type");
151
+ if (nested === void 0 || kind === void 0) return [];
152
+ const operationId = text(data, "operationId") ?? "model";
153
+ const messageId = state.messageId ?? durableChatMessageId(event.runId, operationId);
154
+ state.messageId = messageId;
155
+ const turn = number(nested, "turn");
156
+ if (kind === "text_delta") {
157
+ const delta = text(nested, "delta");
158
+ if (delta === void 0) return [];
159
+ return withStart(state, messageId, "text", void 0, event.runId, turn, (partId, started) => {
160
+ const events = [];
161
+ if (!started) events.push(part(event.runId, turn, {
162
+ type: "text_start",
163
+ messageId,
164
+ partId
165
+ }));
166
+ events.push(part(event.runId, turn, {
167
+ type: "text_delta",
168
+ messageId,
169
+ partId,
170
+ delta
171
+ }));
172
+ return events;
173
+ });
174
+ }
175
+ if (kind === "reasoning_delta") {
176
+ const delta = text(nested, "delta");
177
+ if (delta === void 0) return [];
178
+ const reasoningId = text(nested, "id");
179
+ return withStart(state, messageId, "reasoning", reasoningId ?? "default", event.runId, turn, (partId, started) => {
180
+ const events = [];
181
+ if (!started) {
182
+ const start = part(event.runId, turn, {
183
+ type: "reasoning_start",
184
+ messageId,
185
+ partId
186
+ });
187
+ if (reasoningId !== void 0 && start.type === "reasoning_start") start.reasoningId = reasoningId;
188
+ events.push(start);
189
+ }
190
+ const deltaEvent = part(event.runId, turn, {
191
+ type: "reasoning_delta",
192
+ messageId,
193
+ partId,
194
+ delta
195
+ });
196
+ const contentType = reasoningType(text(nested, "contentType"));
197
+ const signature = text(nested, "signature");
198
+ if (deltaEvent.type === "reasoning_delta") {
199
+ if (contentType !== void 0) deltaEvent.contentType = contentType;
200
+ if (signature !== void 0) deltaEvent.signature = signature;
201
+ }
202
+ events.push(deltaEvent);
203
+ return events;
204
+ });
205
+ }
206
+ if (kind === "tool_call_delta") {
207
+ const toolCallId = text(nested, "id");
208
+ if (toolCallId === void 0) return [];
209
+ return withStart(state, messageId, "tool", toolCallId, event.runId, turn, (partId, started) => {
210
+ const events = [];
211
+ const callId = text(nested, "callId");
212
+ const toolName = text(nested, "name");
213
+ if (!started) {
214
+ const start = part(event.runId, turn, {
215
+ type: "tool_call_start",
216
+ messageId,
217
+ partId,
218
+ toolCallId
219
+ });
220
+ if (start.type === "tool_call_start") {
221
+ if (callId !== void 0) start.callId = callId;
222
+ if (toolName !== void 0) start.toolName = toolName;
223
+ }
224
+ events.push(start);
225
+ }
226
+ const delta = text(nested, "argumentsDelta");
227
+ if (delta === void 0) return events;
228
+ const tool = part(event.runId, turn, {
229
+ type: "tool_call_delta",
230
+ messageId,
231
+ partId,
232
+ toolCallId,
233
+ delta,
234
+ mode: nested.argumentsMode === "replace" ? "replace" : "append"
235
+ });
236
+ if (tool.type === "tool_call_delta") {
237
+ if (callId !== void 0) tool.callId = callId;
238
+ if (toolName !== void 0) tool.toolName = toolName;
239
+ const signature = text(nested, "signature");
240
+ if (signature !== void 0) tool.signature = signature;
241
+ }
242
+ events.push(tool);
243
+ return events;
244
+ });
245
+ }
246
+ if (kind === "tool_call") {
247
+ const call = asToolCall(nested.toolCall);
248
+ return call === void 0 ? [] : [toolCallEnd(event.runId, messageId, call, turn)];
249
+ }
250
+ if (kind === "source") {
251
+ const source = asSource(nested.source);
252
+ if (source === void 0) return [];
253
+ return [part(event.runId, turn, {
254
+ type: "source",
255
+ messageId,
256
+ partId: partId(messageId, "source", JSON.stringify(source)),
257
+ source
258
+ })];
259
+ }
260
+ if (kind === "provider_tool_call") {
261
+ const toolCall = asProviderToolCall(nested.toolCall);
262
+ return toolCall === void 0 ? [] : [part(event.runId, turn, {
263
+ type: "provider_tool_call",
264
+ toolCall
265
+ })];
266
+ }
267
+ return [];
268
+ }
269
+ function mapAttemptFailed(event, data, state) {
270
+ const attemptId = text(data, "attemptId");
271
+ return [located(event, {
272
+ type: "data",
273
+ name: "durable_model_attempt_failed",
274
+ data: {
275
+ operationId: text(data, "operationId") ?? null,
276
+ attemptId: attemptId ?? null,
277
+ messageId: state.messageId ?? null,
278
+ error: text(data, "error") ?? "Durable model attempt failed."
279
+ },
280
+ transient: true
281
+ })];
282
+ }
283
+ function mapModelCompleted(event, data, state) {
284
+ const operationId = text(data, "operationId") ?? "model";
285
+ const messageId = state.messageId ?? durableChatMessageId(event.runId, operationId);
286
+ state.messageId = messageId;
287
+ const result = asRecord(data?.result);
288
+ const choice = assistantParts(result?.choice);
289
+ const events = [];
290
+ const texts = choice.filter((part) => part.type === "text");
291
+ const textJoined = texts.map((part) => part.text).join("");
292
+ const textPartId = partId(messageId, "text");
293
+ if (textJoined.length > 0 || state.openedParts.has(textPartId)) {
294
+ if (!state.openedParts.has(textPartId)) {
295
+ state.openedParts.add(textPartId);
296
+ events.push({
297
+ type: "text_start",
298
+ messageId,
299
+ partId: textPartId,
300
+ runId: event.runId
301
+ });
302
+ }
303
+ const end = {
304
+ type: "text_end",
305
+ messageId,
306
+ partId: textPartId,
307
+ text: textJoined,
308
+ runId: event.runId
309
+ };
310
+ const signature = texts.length === 1 ? texts[0]?.signature : void 0;
311
+ if (signature !== void 0 && end.type === "text_end") end.signature = signature;
312
+ events.push(end);
313
+ }
314
+ for (const content of choice) {
315
+ if (content.type === "reasoning") events.push(reasoningEnd(event.runId, messageId, content, state));
316
+ if (content.type === "tool-call") events.push(toolCallEnd(event.runId, messageId, content));
317
+ }
318
+ const messageEnd = {
319
+ type: "message_end",
320
+ messageId,
321
+ runId: event.runId
322
+ };
323
+ const usage = asUsage(result?.usage);
324
+ const contextUsage = asContextUsage(result?.contextUsage);
325
+ if (usage !== void 0 && messageEnd.type === "message_end") messageEnd.usage = usage;
326
+ if (contextUsage !== void 0 && messageEnd.type === "message_end") messageEnd.contextUsage = contextUsage;
327
+ if (typeof result?.messageId === "string" && messageEnd.type === "message_end") messageEnd.modelMessageId = result.messageId;
328
+ events.push(messageEnd);
329
+ return events;
330
+ }
331
+ function mapToolStarted(event, data, state) {
332
+ const operationId = text(data, "operationId");
333
+ const call = asToolCall(asRecord(data?.input)?.toolCall);
334
+ if (operationId === void 0 || call === void 0) return [];
335
+ state.tools.set(operationId, call);
336
+ const messageId = state.messageId ?? durableChatMessageId(event.runId, operationId);
337
+ return [toolCallEnd(event.runId, messageId, call)];
338
+ }
339
+ function mapToolCompleted(event, data, state) {
340
+ const operationId = text(data, "operationId");
341
+ const call = operationId === void 0 ? void 0 : state.tools.get(operationId);
342
+ const saved = asRecord(data?.result);
343
+ const output = asToolOutput(saved?.output);
344
+ if (output === void 0) return [];
345
+ const messageId = state.messageId ?? durableChatMessageId(event.runId, operationId ?? "tools");
346
+ const toolCallId = call?.toolCallId ?? operationId ?? "tool";
347
+ const failed = saved?.failed === true;
348
+ const result = {
349
+ type: "tool_result",
350
+ messageId,
351
+ partId: partId(messageId, "tool", toolCallId),
352
+ toolCallId,
353
+ toolName: call?.toolName ?? "tool",
354
+ input: call?.input ?? null,
355
+ runId: event.runId,
356
+ result: failed ? {
357
+ status: "error",
358
+ error: {
359
+ message: text(saved, "error") ?? "Tool failed.",
360
+ retryable: false
361
+ }
362
+ } : {
363
+ status: "success",
364
+ output: toolOutputValue(output)
365
+ }
366
+ };
367
+ if (call?.callId !== void 0 && result.type === "tool_result") result.callId = call.callId;
368
+ return [result];
369
+ }
370
+ function mapCompaction(event, data) {
371
+ const info = asCompaction(data);
372
+ return info === void 0 ? [] : [located(event, {
373
+ type: "memory_compaction",
374
+ ...info
375
+ })];
376
+ }
377
+ function needsAttention(event, data) {
378
+ const operationId = text(data, "blockedOperation");
379
+ const details = { retryable: true };
380
+ if (operationId !== void 0) details.operationId = operationId;
381
+ return [located(event, {
382
+ type: "error",
383
+ error: {
384
+ name: "DurableNeedsAttention",
385
+ message: text(data, "error") ?? "Durable run needs attention.",
386
+ code: "needs_attention",
387
+ retryable: true,
388
+ details
389
+ }
390
+ }), located(event, {
391
+ type: "run_end",
392
+ status: "error"
393
+ })];
394
+ }
395
+ function failed(event, data, outcome) {
396
+ const error = located(event, {
397
+ type: "error",
398
+ error: {
399
+ name: "DurableRunError",
400
+ message: text(data, "error") ?? "Durable run failed.",
401
+ code: "failed",
402
+ retryable: false
403
+ }
404
+ });
405
+ const usage = asUsage(outcome?.usage);
406
+ if (usage !== void 0 && error.type === "error") error.usage = usage;
407
+ return [error, runEnd(event, outcome, "error")];
408
+ }
409
+ function settled(event, outcome, status) {
410
+ const outcomeType = text(outcome, "type");
411
+ if (status === "waiting" || outcomeType === "interaction") {
412
+ const interaction = asInteraction(outcome?.interaction);
413
+ const events = [];
414
+ if (interaction !== void 0) events.push(located(event, {
415
+ type: "interaction",
416
+ interaction
417
+ }));
418
+ events.push(runEnd(event, outcome, "suspended"));
419
+ return events;
420
+ }
421
+ if (outcomeType === "blocked") return [runEnd(event, outcome, "blocked")];
422
+ return [runEnd(event, outcome, "completed")];
423
+ }
424
+ function runEnd(event, outcome, status) {
425
+ const end = located(event, {
426
+ type: "run_end",
427
+ status
428
+ });
429
+ if (end.type !== "run_end") return end;
430
+ const value = text(outcome, "text");
431
+ const usage = asUsage(outcome?.usage);
432
+ const contextUsage = asContextUsage(outcome?.contextUsage);
433
+ const trace = asTrace(outcome?.trace);
434
+ const compaction = asCompaction(asRecord(outcome?.memoryCompaction));
435
+ if (value !== void 0) end.text = value;
436
+ if (outcome?.output !== void 0 && isJsonValue(outcome.output)) end.output = outcome.output;
437
+ if (usage !== void 0) end.usage = usage;
438
+ if (contextUsage !== void 0) end.contextUsage = contextUsage;
439
+ if (trace !== void 0) end.trace = trace;
440
+ if (compaction !== void 0) end.memoryCompaction = compaction;
441
+ return end;
442
+ }
443
+ function reasoningEnd(runId, messageId, content, state) {
444
+ const id = partId(messageId, "reasoning", content.id ?? "default");
445
+ const end = {
446
+ type: "reasoning_end",
447
+ messageId,
448
+ partId: id,
449
+ text: content.text,
450
+ runId
451
+ };
452
+ if (!state.openedParts.has(id)) state.openedParts.add(id);
453
+ if (content.details !== void 0 && end.type === "reasoning_end") end.content = content.details;
454
+ return end;
455
+ }
456
+ function toolCallEnd(runId, messageId, call, turn) {
457
+ const event = part(runId, turn, {
458
+ type: "tool_call_end",
459
+ messageId,
460
+ partId: partId(messageId, "tool", call.toolCallId),
461
+ toolCallId: call.toolCallId,
462
+ toolName: call.toolName,
463
+ input: call.input
464
+ });
465
+ if (event.type !== "tool_call_end") return event;
466
+ if (call.callId !== void 0) event.callId = call.callId;
467
+ if (call.signature !== void 0) event.signature = call.signature;
468
+ return event;
469
+ }
470
+ function withStart(state, messageId, kind, suffix, runId, turn, build) {
471
+ const id = partId(messageId, kind, suffix);
472
+ const started = state.openedParts.has(id);
473
+ if (!started) state.openedParts.add(id);
474
+ return build(id, started);
475
+ }
476
+ function part(runId, turn, event) {
477
+ const locatedEvent = {
478
+ ...event,
479
+ runId
480
+ };
481
+ if (turn !== void 0 && "turn" in locatedEvent) locatedEvent.turn = turn;
482
+ else if (turn !== void 0) return {
483
+ ...locatedEvent,
484
+ turn
485
+ };
486
+ return locatedEvent;
487
+ }
488
+ function located(event, fields) {
489
+ return {
490
+ ...fields,
491
+ runId: event.runId
492
+ };
493
+ }
494
+ function fork(state, sequence) {
495
+ return {
496
+ messageId: state.messageId,
497
+ sequence,
498
+ openedOperations: new Set(state.openedOperations),
499
+ openedParts: new Set(state.openedParts),
500
+ tools: new Map(state.tools),
501
+ runOpened: state.runOpened
502
+ };
503
+ }
504
+ function partId(messageId, kind, suffix) {
505
+ return suffix === void 0 ? `${messageId}:${kind}` : `${messageId}:${kind}:${suffix}`;
506
+ }
507
+ function asRecord(value) {
508
+ if (typeof value !== "object" || value === null || Array.isArray(value) || !isJsonValue(value)) return;
509
+ return value;
510
+ }
511
+ function text(value, key) {
512
+ const field = value?.[key];
513
+ return typeof field === "string" ? field : void 0;
514
+ }
515
+ function number(value, key) {
516
+ const field = value?.[key];
517
+ return typeof field === "number" && Number.isFinite(field) ? field : void 0;
518
+ }
519
+ function modelOf(data) {
520
+ const input = asRecord(data?.input);
521
+ const provider = text(input, "provider");
522
+ const modelId = text(input, "modelId");
523
+ return provider === void 0 || modelId === void 0 ? void 0 : {
524
+ provider,
525
+ modelId
526
+ };
527
+ }
528
+ const REASONING = [
529
+ "text",
530
+ "summary",
531
+ "encrypted",
532
+ "redacted"
533
+ ];
534
+ function reasoningType(value) {
535
+ return REASONING.find((type) => type === value);
536
+ }
537
+ function asToolCall(value) {
538
+ const call = asRecord(value);
539
+ if (call?.type !== "tool-call") return void 0;
540
+ const toolCallId = text(call, "toolCallId");
541
+ const toolName = text(call, "toolName");
542
+ if (toolCallId === void 0 || toolName === void 0 || !isJsonValue(call.input)) return void 0;
543
+ const parsed = {
544
+ type: "tool-call",
545
+ toolCallId,
546
+ toolName,
547
+ input: call.input
548
+ };
549
+ const callId = text(call, "callId");
550
+ const signature = text(call, "signature");
551
+ if (callId === void 0) return signature === void 0 ? parsed : {
552
+ ...parsed,
553
+ signature
554
+ };
555
+ if (signature === void 0) return {
556
+ ...parsed,
557
+ callId
558
+ };
559
+ return {
560
+ ...parsed,
561
+ callId,
562
+ signature
563
+ };
564
+ }
565
+ function asSource(value) {
566
+ return asRecord(value);
567
+ }
568
+ function asProviderToolCall(value) {
569
+ const call = asRecord(value);
570
+ return call === void 0 ? void 0 : call;
571
+ }
572
+ function assistantParts(value) {
573
+ if (!Array.isArray(value)) return [];
574
+ return value.flatMap((item) => {
575
+ const part = assistantPart(item);
576
+ return part === void 0 ? [] : [part];
577
+ });
578
+ }
579
+ function assistantPart(value) {
580
+ const part = asRecord(value);
581
+ const type = text(part, "type");
582
+ if (part === void 0 || type === void 0) return void 0;
583
+ if (type === "text" && typeof part.text === "string") return typeof part.signature === "string" ? {
584
+ type: "text",
585
+ text: part.text,
586
+ signature: part.signature
587
+ } : {
588
+ type: "text",
589
+ text: part.text
590
+ };
591
+ if (type === "reasoning" && typeof part.text === "string") {
592
+ const details = asReasoningDetails(part.details);
593
+ const base = {
594
+ type: "reasoning",
595
+ text: part.text
596
+ };
597
+ if (typeof part.id !== "string") return details === void 0 ? base : {
598
+ ...base,
599
+ details
600
+ };
601
+ return details === void 0 ? {
602
+ ...base,
603
+ id: part.id
604
+ } : {
605
+ ...base,
606
+ id: part.id,
607
+ details
608
+ };
609
+ }
610
+ if (type === "tool-call") return asToolCall(part);
611
+ }
612
+ function asReasoningDetails(value) {
613
+ if (!Array.isArray(value) || !isJsonValue(value)) return void 0;
614
+ return value;
615
+ }
616
+ function asUsage(value) {
617
+ const usage = asRecord(value);
618
+ if (usage === void 0) return void 0;
619
+ const keys = [
620
+ "inputTokens",
621
+ "outputTokens",
622
+ "totalTokens",
623
+ "cachedInputTokens",
624
+ "cacheCreationInputTokens"
625
+ ];
626
+ const counts = {};
627
+ for (const key of keys) {
628
+ const count = number(usage, key);
629
+ if (count === void 0) return void 0;
630
+ counts[key] = count;
631
+ }
632
+ const parsed = counts;
633
+ const details = asRecord(usage.details);
634
+ if (details !== void 0 && Object.values(details).every((item) => typeof item === "number")) parsed.details = details;
635
+ return parsed;
636
+ }
637
+ function asContextUsage(value) {
638
+ const usage = asRecord(value);
639
+ const model = asRecord(usage?.model);
640
+ const context = asRecord(model?.context);
641
+ if (usage === void 0 || typeof model?.modelId !== "string" || context === void 0) return void 0;
642
+ const contextWindow = number(context, "contextWindow");
643
+ const usedTokens = number(usage, "usedTokens");
644
+ const remainingTokens = number(usage, "remainingTokens");
645
+ const usedPercent = number(usage, "usedPercent");
646
+ const remainingPercent = number(usage, "remainingPercent");
647
+ if (contextWindow === void 0 || usedTokens === void 0 || remainingTokens === void 0 || usedPercent === void 0 || remainingPercent === void 0) return;
648
+ const parsed = {
649
+ model: {
650
+ modelId: model.modelId,
651
+ context: { contextWindow }
652
+ },
653
+ usedTokens,
654
+ remainingTokens,
655
+ usedPercent,
656
+ remainingPercent
657
+ };
658
+ const maxInputTokens = number(context, "maxInputTokens");
659
+ const maxOutputTokens = number(context, "maxOutputTokens");
660
+ if (maxInputTokens !== void 0) parsed.model.context.maxInputTokens = maxInputTokens;
661
+ if (maxOutputTokens !== void 0) parsed.model.context.maxOutputTokens = maxOutputTokens;
662
+ return parsed;
663
+ }
664
+ function asTrace(value) {
665
+ const trace = asRecord(value);
666
+ const observer = text(trace, "observer");
667
+ if (observer === void 0) return void 0;
668
+ const parsed = { observer };
669
+ const traceId = text(trace, "traceId");
670
+ const observationId = text(trace, "observationId");
671
+ if (traceId !== void 0) parsed.traceId = traceId;
672
+ if (observationId !== void 0) parsed.observationId = observationId;
673
+ return parsed;
674
+ }
675
+ function asCompaction(value) {
676
+ if (value === void 0) return void 0;
677
+ const keys = [
678
+ "originalMessageCount",
679
+ "compactedMessageCount",
680
+ "retainedMessageCount",
681
+ "originalTokenCount",
682
+ "compactedTokenCount",
683
+ "retainedTokenCount",
684
+ "resultTokenCount",
685
+ "attempts"
686
+ ];
687
+ const info = {};
688
+ for (const key of keys) {
689
+ const count = number(value, key);
690
+ if (count === void 0) return void 0;
691
+ info[key] = count;
692
+ }
693
+ const usage = asUsage(value.usage);
694
+ if (usage === void 0) return void 0;
695
+ return {
696
+ ...info,
697
+ usage
698
+ };
699
+ }
700
+ function asInteraction(value) {
701
+ const request = asRecord(value);
702
+ const type = text(request, "type");
703
+ const id = text(request, "id");
704
+ const toolName = text(request, "toolName");
705
+ const toolCallId = text(request, "toolCallId");
706
+ const internalCallId = text(request, "internalCallId");
707
+ if (request === void 0 || id === void 0 || toolName === void 0 || toolCallId === void 0 || internalCallId === void 0) return;
708
+ const callId = text(request, "callId");
709
+ const base = {
710
+ id,
711
+ toolName,
712
+ toolCallId,
713
+ internalCallId
714
+ };
715
+ if (callId !== void 0) base.callId = callId;
716
+ if (type === "tool-approval" && isJsonValue(request.input)) {
717
+ const approval = {
718
+ ...base,
719
+ type: "tool-approval",
720
+ input: request.input
721
+ };
722
+ const reason = text(request, "reason");
723
+ return reason === void 0 ? approval : {
724
+ ...approval,
725
+ reason
726
+ };
727
+ }
728
+ if (type === "tool-question" && Array.isArray(request.questions) && isJsonValue(request.questions)) return {
729
+ ...base,
730
+ type,
731
+ questions: request.questions
732
+ };
733
+ }
734
+ function asToolOutput(value) {
735
+ const output = asRecord(value);
736
+ const type = text(output, "type");
737
+ if (output === void 0 || type === void 0) return void 0;
738
+ if ((type === "text" || type === "error-text") && typeof output.value === "string") return {
739
+ type,
740
+ value: output.value
741
+ };
742
+ if ((type === "json" || type === "error-json") && isJsonValue(output.value)) return {
743
+ type,
744
+ value: output.value
745
+ };
746
+ if (type === "execution-denied") {
747
+ const reason = text(output, "reason");
748
+ return reason === void 0 ? { type } : {
749
+ type,
750
+ reason
751
+ };
752
+ }
753
+ if (type === "content" && Array.isArray(output.value) && isJsonValue(output.value)) return {
754
+ type,
755
+ value: output.value
756
+ };
757
+ }
758
+ function toolOutputValue(output) {
759
+ if (output.type === "execution-denied") return output.reason === void 0 ? { type: output.type } : {
760
+ type: output.type,
761
+ reason: output.reason
762
+ };
763
+ return output.value;
764
+ }
765
+ // src/durable-chat.ts
766
+ /**
767
+ * Thrown when a submission is rejected because the session already has unfinished work (HTTP 409).
768
+ * `runId` is that run when it could be read back from the session; `undefined` when the lookup
769
+ * failed. Reattach to `runId` instead of submitting again. Aborting a stream never raises this.
770
+ */
771
+ var DurableBusyError = class extends Error {
772
+ runId;
773
+ code = "session_busy";
774
+ constructor(runId, message = "Session already has unfinished work.") {
775
+ super(message);
776
+ this.runId = runId;
777
+ this.name = "DurableBusyError";
778
+ }
779
+ };
780
+ const UNFINISHED = /* @__PURE__ */ new Set([
781
+ "queued",
782
+ "retry_wait",
783
+ "pending",
784
+ "running",
785
+ "waiting",
786
+ "needs_attention"
787
+ ]);
788
+ /**
789
+ * Transport `useChat` can pass to talk to `@anvia/durable`.
790
+ *
791
+ * Only the last user message of a `messages` request is submitted; durable keeps the transcript,
792
+ * so the rest of the client history is discarded. An `interaction_response` is delivered to the
793
+ * remembered run and then the stream resumes from the last applied sequence. Abort detaches the
794
+ * SSE subscription and does not cancel the run.
795
+ */
796
+ function createDurableChatTransport(options) {
797
+ const session = {
798
+ cursor: 0,
799
+ map: createDurableChatMapState()
800
+ };
801
+ return {
802
+ controller: {
803
+ get activeRunId() {
804
+ return session.activeRunId;
805
+ },
806
+ reattach(runId, after = 0) {
807
+ assertCursor(after);
808
+ session.activeRunId = requireId(runId, "runId");
809
+ session.cursor = after;
810
+ session.map = createDurableChatMapState(after);
811
+ },
812
+ cancel(runId) {
813
+ return options.client.cancel(resolveRun(session, runId));
814
+ },
815
+ retry(runId) {
816
+ return options.client.retry(resolveRun(session, runId));
817
+ },
818
+ resolveTool(operationId, output, runId) {
819
+ return options.client.resolveTool(resolveRun(session, runId), operationId, output);
820
+ },
821
+ resolveMemoryWrite(operationKey, resolution, runId) {
822
+ const resolve = options.client.resolveMemoryWrite;
823
+ if (resolve === void 0) return Promise.reject(/* @__PURE__ */ new TypeError("The durable client does not support resolveMemoryWrite."));
824
+ return resolve.call(options.client, resolveRun(session, runId), operationKey, resolution);
825
+ }
826
+ },
827
+ async *send(send) {
828
+ const target = await begin(options, session, send);
829
+ if (session.activeRunId !== target.runId || target.after < session.cursor) session.map = createDurableChatMapState(target.after);
830
+ session.activeRunId = target.runId;
831
+ session.cursor = target.after;
832
+ yield* streamFrames(options.client, session, target.runId, send.abortSignal);
833
+ }
834
+ };
835
+ }
836
+ async function begin(options, session, send) {
837
+ const resume = send.resume ?? send.request.resume;
838
+ if (resume !== void 0) {
839
+ assertCursor(resume.after);
840
+ return {
841
+ runId: requireId(resume.streamId, "streamId"),
842
+ after: resume.after
843
+ };
844
+ }
845
+ if (send.request.type === "interaction_response") {
846
+ if (session.activeRunId === void 0) throw new Error("No active durable run to receive the interaction response.");
847
+ await options.client.respond(session.activeRunId, send.request.interactionId, send.request.response, send.abortSignal === void 0 ? {} : { abortSignal: send.abortSignal });
848
+ return {
849
+ runId: session.activeRunId,
850
+ after: session.cursor
851
+ };
852
+ }
853
+ return {
854
+ runId: await submitPrompt(options, send),
855
+ after: 0
856
+ };
857
+ }
858
+ async function submitPrompt(options, send) {
859
+ if (send.request.type !== "messages") throw new TypeError("Unsupported durable chat request.");
860
+ const prompt = lastUserPrompt(send.request.messages);
861
+ const requestId = options.requestId?.() ?? createClientId("durable");
862
+ const requestOptions = { enqueue: options.enqueue ?? true };
863
+ if (send.abortSignal !== void 0) requestOptions.abortSignal = send.abortSignal;
864
+ try {
865
+ return (await options.client.submit({
866
+ agentId: options.agentId,
867
+ sessionId: options.sessionId,
868
+ requestId,
869
+ prompt
870
+ }, requestOptions)).run.id;
871
+ } catch (error) {
872
+ if (!isSessionBusy(error)) throw error;
873
+ throw new DurableBusyError(await findBusyRun(options, send.abortSignal), error.message);
874
+ }
875
+ }
876
+ async function* streamFrames(client, session, runId, abortSignal) {
877
+ yield {
878
+ type: "stream_start",
879
+ protocol: CLIENT_STREAM_PROTOCOL,
880
+ streamId: runId,
881
+ eventId: 0,
882
+ resumable: true
883
+ };
884
+ let lastEventId = session.cursor;
885
+ const streamOptions = abortSignal === void 0 ? { after: session.cursor } : {
886
+ after: session.cursor,
887
+ abortSignal
888
+ };
889
+ try {
890
+ for await (const event of client.stream(runId, streamOptions)) {
891
+ const settled = needsSettledOutcome(event) ? await snapshotOutcome(client, runId, event, abortSignal) : void 0;
892
+ const mapped = mapDurableChatEvent(event, session.map, settled);
893
+ session.map = mapped.state;
894
+ session.cursor = mapped.state.sequence;
895
+ lastEventId = mapped.state.sequence;
896
+ for (const clientEvent of mapped.events) yield {
897
+ type: "stream_event",
898
+ streamId: runId,
899
+ eventId: event.sequence,
900
+ event: clientEvent
901
+ };
902
+ }
903
+ } catch (error) {
904
+ if (!isAbort(error, abortSignal)) yield {
905
+ type: "stream_end",
906
+ streamId: runId,
907
+ eventId: lastEventId,
908
+ status: "error"
909
+ };
910
+ throw error;
911
+ }
912
+ yield {
913
+ type: "stream_end",
914
+ streamId: runId,
915
+ eventId: lastEventId,
916
+ status: "completed"
917
+ };
918
+ }
919
+ /**
920
+ * The outcome for a settled status event, read from the run snapshot (DUR-07 keeps it out of the
921
+ * event). If the run has moved past that status, the snapshot describes a later point, so nothing
922
+ * is returned and the event maps without its outcome.
923
+ */
924
+ async function snapshotOutcome(client, runId, event, abortSignal) {
925
+ const snapshot = await client.snapshot(runId, abortSignal === void 0 ? {} : { abortSignal });
926
+ if (snapshot.run.status !== statusEventStatus(event)) return void 0;
927
+ const outcome = snapshot.run.outcome;
928
+ return typeof outcome === "object" && outcome !== null && !Array.isArray(outcome) && isJsonValue(outcome) ? outcome : void 0;
929
+ }
930
+ function lastUserPrompt(messages) {
931
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
932
+ const message = messages[index];
933
+ if (message?.role === "user") return message;
934
+ }
935
+ throw new TypeError("Durable chat requires a user message to submit.");
936
+ }
937
+ function isSessionBusy(error) {
938
+ return error instanceof DurableHttpError && error.status === 409;
939
+ }
940
+ async function findBusyRun(options, abortSignal) {
941
+ try {
942
+ return (await options.client.listRuns({
943
+ sessionId: options.sessionId,
944
+ limit: 100
945
+ }, abortSignal === void 0 ? {} : { abortSignal })).runs.find((run) => UNFINISHED.has(run.status))?.id;
946
+ } catch {
947
+ return;
948
+ }
949
+ }
950
+ function resolveRun(session, runId) {
951
+ return requireId(runId ?? session.activeRunId, "run");
952
+ }
953
+ function requireId(value, label) {
954
+ if (value === void 0 || value.trim().length === 0) throw new Error(label === "run" ? "No active durable run." : `${label} must not be empty.`);
955
+ return value;
956
+ }
957
+ function assertCursor(after) {
958
+ if (!Number.isSafeInteger(after) || after < 0) throw new TypeError("Invalid durable resume cursor.");
959
+ }
960
+ function isAbort(error, signal) {
961
+ return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
962
+ }
963
+ export { DurableBusyError, createDurableChatTransport };
964
+
965
+ //# sourceMappingURL=durable-chat.js.map