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