@tangle-network/agent-app 0.43.54 → 0.43.56
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 +41 -35
- package/dist/assistant/index.d.ts +2 -2
- package/dist/assistant/index.js +7 -6
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-DX2KIzMC.d.ts → attachment-validation-D0csjCvz.d.ts} +1 -1
- package/dist/chat-routes/index.d.ts +12 -9
- package/dist/chat-routes/index.js +328 -146
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +2 -2
- package/dist/chat-store/index.js +1 -1
- package/dist/{chunk-3EKOSBYL.js → chunk-K63OWUDJ.js} +2 -2
- package/dist/{chunk-EIG7ZQW2.js → chunk-RVUISYUE.js} +15 -3
- package/dist/chunk-RVUISYUE.js.map +1 -0
- package/dist/{chunk-JGYOYY5D.js → chunk-XKBIYSSM.js} +1 -1
- package/dist/chunk-XKBIYSSM.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{parts-IB-Kbb7z.d.ts → parts-1sRYSPjF.d.ts} +58 -1
- package/dist/web-react/index.d.ts +16 -4
- package/dist/web-react/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-EIG7ZQW2.js.map +0 -1
- package/dist/chunk-JGYOYY5D.js.map +0 -1
- /package/dist/{chunk-3EKOSBYL.js.map → chunk-K63OWUDJ.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { L as ChatTurnRequestPayload, M as ChatTurnPartInput, g as ChatMessagePart, N as ChatTurnFilePartInput, a as ChatAttachmentPart, C as ChatAttachmentKind, f as ChatMentionPart } from '../parts-
|
|
2
|
-
export { O as ChatAttachmentInput, e as ChatMentionKind, P as ChatTurnInputError, Q as ChatTurnTextPartInput, R as DISPATCH_MAX_MEDIA_PARTS, T as DISPATCH_MAX_PARTS, U as DISPATCH_REQUEST_MAX_BYTES, V as DISPATCH_STRUCTURAL_RESERVE_BYTES, K as FileMention, W as FileMentionsToPartsOptions, X as INLINE_PARTS_MAX_BYTES, Y as MENTION_MAX_COUNT, Z as
|
|
3
|
-
export { A as ALLOWED_ATTACHMENT_SNIFFED_MIMES, a as ATTACHMENT_ACCEPT, b as ATTACHMENT_MAX_COUNT, c as AttachmentTypeCheckResult, C as CreateSandboxFileIndexRouteOptions, F as FileIndexAuthorization, d as FileIndexCache, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse, M as MAX_ATTACHMENT_TOTAL_BYTES, h as MAX_BINARY_ATTACHMENT_BYTES, i as MAX_TEXT_ATTACHMENT_BYTES, S as SandboxFileTreeSource, j as SandboxTreeFile, k as SandboxTreeResult, l as SniffResult, m as attachmentSizeErrorMessage, n as attachmentTotalSizeErrorMessage, o as checkAttachmentType, p as createSandboxFileIndexRoute, s as sanitizeAttachmentFileName, q as sniffBinary } from '../attachment-validation-
|
|
1
|
+
import { L as ChatTurnRequestPayload, M as ChatTurnPartInput, g as ChatMessagePart, N as ChatTurnFilePartInput, a as ChatAttachmentPart, C as ChatAttachmentKind, f as ChatMentionPart } from '../parts-1sRYSPjF.js';
|
|
2
|
+
export { O as ChatAttachmentInput, e as ChatMentionKind, P as ChatTurnInputError, Q as ChatTurnTextPartInput, R as DISPATCH_MAX_MEDIA_PARTS, T as DISPATCH_MAX_PARTS, U as DISPATCH_REQUEST_MAX_BYTES, V as DISPATCH_STRUCTURAL_RESERVE_BYTES, K as FileMention, W as FileMentionsToPartsOptions, X as INLINE_PARTS_MAX_BYTES, Y as MENTION_MAX_COUNT, Z as ProducerErrorEvent, _ as ProducerNoticeEvent, $ as ProducerPassthroughEvent, a0 as ProducerPassthroughEventType, a1 as ProducerReasoningEvent, a2 as ProducerTextEvent, a3 as ProducerToolCallEvent, a4 as ProducerToolResultEvent, a5 as ProducerUsageEvent, a6 as ProducerWireEvent, a7 as SandboxMentionPathCheck, a8 as assertPromptPartsWithinCap, a9 as base64WireLen, aa as buildMentionPromptBlock, ab as chatTurnRequestInit, ac as fileMentionsToParts, ad as formatBytes, ae as mediaTypeForMentionPath, af as mentionKindForPath, ag as parseChatTurnParts, ah as parseFileMentions, ai as promptPartsByteSize, aj as validateSandboxMentionPath } from '../parts-1sRYSPjF.js';
|
|
3
|
+
export { A as ALLOWED_ATTACHMENT_SNIFFED_MIMES, a as ATTACHMENT_ACCEPT, b as ATTACHMENT_MAX_COUNT, c as AttachmentTypeCheckResult, C as CreateSandboxFileIndexRouteOptions, F as FileIndexAuthorization, d as FileIndexCache, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse, M as MAX_ATTACHMENT_TOTAL_BYTES, h as MAX_BINARY_ATTACHMENT_BYTES, i as MAX_TEXT_ATTACHMENT_BYTES, S as SandboxFileTreeSource, j as SandboxTreeFile, k as SandboxTreeResult, l as SniffResult, m as attachmentSizeErrorMessage, n as attachmentTotalSizeErrorMessage, o as checkAttachmentType, p as createSandboxFileIndexRoute, s as sanitizeAttachmentFileName, q as sniffBinary } from '../attachment-validation-D0csjCvz.js';
|
|
4
4
|
import { ChatTurnIdentity, ChatTurnProducer } from '@tangle-network/agent-runtime';
|
|
5
5
|
import { InteractionAnswerRoute, InteractionAnswerRouteOptions } from '../interactions/index.js';
|
|
6
6
|
import { PersistedChatMessageForTurn } from '../stream/index.js';
|
|
@@ -335,7 +335,8 @@ declare function createChatTurnRoutes<TContext = void>(options: CreateChatTurnRo
|
|
|
335
335
|
* the `ChatTurnProducer` shape agent-runtime's `handleChatTurn` consumes AND
|
|
336
336
|
* the client vocabulary `/web-react`'s `dispatchChatStreamLine` already parses
|
|
337
337
|
* (`text` / `reasoning` / `tool_call` / `tool_result` / `usage` /
|
|
338
|
-
* `interaction`). Legal and tax each hand-rolled
|
|
338
|
+
* `notice` / structured `error` / `interaction`). Legal and tax each hand-rolled
|
|
339
|
+
* this mapping differently;
|
|
339
340
|
* this is that middle, composed from `/stream`'s normalizers — no new loop
|
|
340
341
|
* logic, no SDK import (the event source is an injected `AsyncIterable`).
|
|
341
342
|
*
|
|
@@ -376,11 +377,13 @@ interface SandboxChatProducerOptions {
|
|
|
376
377
|
model?: string;
|
|
377
378
|
/** Which ask kinds the product renders a card for. Anything else is
|
|
378
379
|
* auto-declined (see `declineInteraction`) so the run never hangs in the
|
|
379
|
-
* broker waiting on a card no client will show. Default: question/plan.
|
|
380
|
+
* broker waiting on a card no client will show. Default: question/plan.
|
|
381
|
+
* Products with per-turn plan mode can close over it without another option:
|
|
382
|
+
* `(kind) => kind === 'question' || (kind === 'plan' && planEnabled)`. */
|
|
380
383
|
isRenderableInteraction?: (kind: string) => boolean;
|
|
381
384
|
/** Resolve a non-renderable ask (wire `respondToSessionInteraction` with the
|
|
382
|
-
* session's sidecar connection). Without it,
|
|
383
|
-
*
|
|
385
|
+
* session's sidecar connection). Without it, a failure notice is emitted and
|
|
386
|
+
* the run stays blocked until the broker times out. */
|
|
384
387
|
declineInteraction?: (id: string) => Promise<void>;
|
|
385
388
|
/** Opt-in eager promotion of harness-emitted `file` parts. Unset, a `file`
|
|
386
389
|
* part persists exactly as the harness sent it — a transient `url` (a
|
|
@@ -497,8 +500,8 @@ interface DetachedTurnOptions {
|
|
|
497
500
|
}
|
|
498
501
|
interface DetachedTurnResult {
|
|
499
502
|
/** `completed` — clean drain: persist + bill. `failed` — a terminal error
|
|
500
|
-
*
|
|
501
|
-
*
|
|
503
|
+
* event, including the producer's structured `sandbox.stream_failed` event
|
|
504
|
+
* when the raw sandbox stream throws: skip billing, render an error row. */
|
|
502
505
|
state: 'completed' | 'failed';
|
|
503
506
|
text: string;
|
|
504
507
|
/** The structured assistant body to persist (tool calls, file/plan/interaction
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
createSandboxFileIndexRoute,
|
|
12
12
|
sanitizeAttachmentFileName,
|
|
13
13
|
sniffBinary
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-K63OWUDJ.js";
|
|
15
15
|
import {
|
|
16
16
|
DEFAULT_STALE_TURN_LOCK_GRACE_MS,
|
|
17
17
|
DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
|
|
@@ -54,21 +54,25 @@ import {
|
|
|
54
54
|
promptPartsByteSize,
|
|
55
55
|
toChatMessageParts,
|
|
56
56
|
validateSandboxMentionPath
|
|
57
|
-
} from "../chunk-
|
|
57
|
+
} from "../chunk-XKBIYSSM.js";
|
|
58
58
|
import {
|
|
59
59
|
asRecord,
|
|
60
60
|
asString,
|
|
61
61
|
finalizeAssistantParts,
|
|
62
|
+
finalizePendingInteractionParts,
|
|
62
63
|
getPartKey,
|
|
63
64
|
mergePersistedPart,
|
|
64
65
|
normalizePersistedPart,
|
|
65
|
-
normalizeToolEvent
|
|
66
|
+
normalizeToolEvent,
|
|
67
|
+
terminalizeDanglingAssistantToolUpdates
|
|
66
68
|
} from "../chunk-5MG74GVQ.js";
|
|
67
69
|
import {
|
|
68
70
|
cancelStatusFor,
|
|
69
71
|
interactionPartKey,
|
|
70
72
|
interactionToPersistedPart,
|
|
71
73
|
isRenderableInteractionKind,
|
|
74
|
+
noticePart,
|
|
75
|
+
noticePartKey,
|
|
72
76
|
parseInteractionCancel,
|
|
73
77
|
parseInteractionRequest
|
|
74
78
|
} from "../chunk-XAWFPMAR.js";
|
|
@@ -573,6 +577,72 @@ function usageFromStepFinish(part, usage) {
|
|
|
573
577
|
const cost = Number(part.cost);
|
|
574
578
|
if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost;
|
|
575
579
|
}
|
|
580
|
+
function toFiniteNumber(value) {
|
|
581
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
582
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
583
|
+
const parsed = Number(value);
|
|
584
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
function extractReportedTurnUsage(data) {
|
|
589
|
+
const tokenUsage = asRecord(data?.tokenUsage);
|
|
590
|
+
if (!tokenUsage) return null;
|
|
591
|
+
const inputTokens = toFiniteNumber(tokenUsage.inputTokens);
|
|
592
|
+
const outputTokens = toFiniteNumber(tokenUsage.outputTokens);
|
|
593
|
+
if (inputTokens === null || outputTokens === null) return null;
|
|
594
|
+
const reported = { inputTokens, outputTokens };
|
|
595
|
+
const reasoningTokens = toFiniteNumber(tokenUsage.reasoningTokens);
|
|
596
|
+
if (reasoningTokens !== null) reported.reasoningTokens = reasoningTokens;
|
|
597
|
+
const cacheReadTokens = toFiniteNumber(tokenUsage.cacheReadInputTokens);
|
|
598
|
+
if (cacheReadTokens !== null) reported.cacheReadTokens = cacheReadTokens;
|
|
599
|
+
const cacheWriteTokens = toFiniteNumber(tokenUsage.cacheCreationInputTokens);
|
|
600
|
+
if (cacheWriteTokens !== null) reported.cacheWriteTokens = cacheWriteTokens;
|
|
601
|
+
const costUsd = toFiniteNumber(data?.totalCostUsd) ?? toFiniteNumber(tokenUsage.cost);
|
|
602
|
+
if (costUsd !== null) reported.costUsd = costUsd;
|
|
603
|
+
return reported;
|
|
604
|
+
}
|
|
605
|
+
function applyTerminalUsage(reported, usage) {
|
|
606
|
+
usage.inputTokens = reported.inputTokens;
|
|
607
|
+
usage.outputTokens = reported.outputTokens;
|
|
608
|
+
if (reported.reasoningTokens !== void 0) usage.reasoningTokens = reported.reasoningTokens;
|
|
609
|
+
if (reported.cacheReadTokens !== void 0) usage.cacheReadTokens = reported.cacheReadTokens;
|
|
610
|
+
if (reported.cacheWriteTokens !== void 0) usage.cacheWriteTokens = reported.cacheWriteTokens;
|
|
611
|
+
if (reported.costUsd !== void 0) usage.costUsd = reported.costUsd;
|
|
612
|
+
}
|
|
613
|
+
function sandboxStreamErrorMessage(data) {
|
|
614
|
+
const record = asRecord(data);
|
|
615
|
+
const message = asString(record?.message) ?? asString(record?.error);
|
|
616
|
+
if (message) return message;
|
|
617
|
+
try {
|
|
618
|
+
return JSON.stringify(data) || "Sandbox stream returned an error event without details.";
|
|
619
|
+
} catch {
|
|
620
|
+
return "Sandbox stream returned an error event without details.";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function toProducerWireEvent(event) {
|
|
624
|
+
return event;
|
|
625
|
+
}
|
|
626
|
+
function sandboxStreamFailureDiagnostic(error) {
|
|
627
|
+
const streamMessage = error?.streamMessage;
|
|
628
|
+
const message = typeof streamMessage === "string" ? streamMessage : error instanceof Error ? error.message : "Sandbox stream failed";
|
|
629
|
+
const diagnostics = asRecord(error?.diagnostics);
|
|
630
|
+
let diagnosticText;
|
|
631
|
+
if (diagnostics) {
|
|
632
|
+
try {
|
|
633
|
+
diagnosticText = JSON.stringify(diagnostics);
|
|
634
|
+
} catch {
|
|
635
|
+
diagnosticText = "[unserializable diagnostics]";
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const userMessage = [
|
|
639
|
+
"The sandbox model stream stopped before a clean completion.",
|
|
640
|
+
`Error: ${message}`,
|
|
641
|
+
"Please retry. If it repeats, send these support details to the team."
|
|
642
|
+
].join("\n\n");
|
|
643
|
+
const failureNote = diagnosticText ? `sandbox-stream: ${message}; ${diagnosticText}` : `sandbox-stream: ${message}`;
|
|
644
|
+
return { userMessage, failureNote };
|
|
645
|
+
}
|
|
576
646
|
function createSandboxChatProducer(options) {
|
|
577
647
|
const log = options.log ?? ((message, meta) => console.error(message, meta ?? ""));
|
|
578
648
|
const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind;
|
|
@@ -584,6 +654,9 @@ function createSandboxChatProducer(options) {
|
|
|
584
654
|
const announcedTools = /* @__PURE__ */ new Set();
|
|
585
655
|
const settledTools = /* @__PURE__ */ new Set();
|
|
586
656
|
let stepCounter = 0;
|
|
657
|
+
let danglingToolsTerminalized = false;
|
|
658
|
+
let interactionOutcome = "answered";
|
|
659
|
+
let warningCount = 0;
|
|
587
660
|
const promotedFileParts = /* @__PURE__ */ new Map();
|
|
588
661
|
function recordPersistedPart(part, delta, keyOverride) {
|
|
589
662
|
const persisted = normalizePersistedPart(part);
|
|
@@ -592,179 +665,288 @@ function createSandboxChatProducer(options) {
|
|
|
592
665
|
if (!partMap.has(key)) partOrder.push(key);
|
|
593
666
|
partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta));
|
|
594
667
|
}
|
|
668
|
+
function* emitTerminalizedTools() {
|
|
669
|
+
if (danglingToolsTerminalized) return;
|
|
670
|
+
danglingToolsTerminalized = true;
|
|
671
|
+
const updates = terminalizeDanglingAssistantToolUpdates(partOrder, partMap, fullText);
|
|
672
|
+
for (const part of updates) {
|
|
673
|
+
const toolId = asString(part.id);
|
|
674
|
+
if (!toolId || settledTools.has(toolId)) continue;
|
|
675
|
+
settledTools.add(toolId);
|
|
676
|
+
const state = asRecord(part.state);
|
|
677
|
+
yield {
|
|
678
|
+
type: "tool_result",
|
|
679
|
+
toolCallId: toolId,
|
|
680
|
+
toolName: asString(part.tool) ?? "tool",
|
|
681
|
+
outcome: {
|
|
682
|
+
ok: false,
|
|
683
|
+
...asString(state?.error) ? { message: asString(state?.error) } : {}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function* emitFinalUsage() {
|
|
689
|
+
const promptTokens = usage.inputTokens ?? 0;
|
|
690
|
+
const completionTokens = usage.outputTokens ?? 0;
|
|
691
|
+
if (promptTokens || completionTokens) {
|
|
692
|
+
yield { type: "usage", usage: { promptTokens, completionTokens } };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
595
695
|
async function* stream() {
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (
|
|
612
|
-
|
|
696
|
+
try {
|
|
697
|
+
for await (const raw of options.events) {
|
|
698
|
+
const record = asRecord(raw);
|
|
699
|
+
if (!record || typeof record.type !== "string") continue;
|
|
700
|
+
const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) });
|
|
701
|
+
const event = normalized.type === "message.part.updated" ? normalized : { type: record.type, data: asRecord(record.data) };
|
|
702
|
+
if (event.type === "message.part.updated") {
|
|
703
|
+
const part = asRecord(event.data?.part);
|
|
704
|
+
if (!part) continue;
|
|
705
|
+
const rawDelta = event.data?.delta;
|
|
706
|
+
const partType = String(part.type ?? "");
|
|
707
|
+
if (partType === "text" || partType === "reasoning") {
|
|
708
|
+
const key = getPartKey(part);
|
|
709
|
+
const delta = textDelta(tracker, key, part, rawDelta);
|
|
710
|
+
recordPersistedPart(part, delta || void 0);
|
|
711
|
+
if (delta) {
|
|
712
|
+
if (partType === "text") fullText += delta;
|
|
713
|
+
yield { type: partType, text: delta };
|
|
714
|
+
}
|
|
715
|
+
continue;
|
|
613
716
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
717
|
+
if (partType === "tool") {
|
|
718
|
+
recordPersistedPart(part, void 0);
|
|
719
|
+
const persisted = partMap.get(getPartKey(part));
|
|
720
|
+
const state = asRecord(persisted?.state);
|
|
721
|
+
const toolId = String(persisted?.id ?? "");
|
|
722
|
+
const toolName = String(persisted?.tool ?? "tool");
|
|
723
|
+
if (toolId && !announcedTools.has(toolId)) {
|
|
724
|
+
announcedTools.add(toolId);
|
|
725
|
+
yield {
|
|
726
|
+
type: "tool_call",
|
|
727
|
+
call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} }
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const status = String(state?.status ?? "");
|
|
731
|
+
if (toolId && (status === "completed" || status === "error") && !settledTools.has(toolId)) {
|
|
732
|
+
settledTools.add(toolId);
|
|
733
|
+
yield {
|
|
734
|
+
type: "tool_result",
|
|
735
|
+
toolCallId: toolId,
|
|
736
|
+
toolName,
|
|
737
|
+
outcome: {
|
|
738
|
+
ok: status === "completed",
|
|
739
|
+
...state?.output !== void 0 ? { result: state.output } : {},
|
|
740
|
+
...asString(state?.error) ? { message: asString(state?.error) } : {}
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
continue;
|
|
628
745
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
...state?.output !== void 0 ? { result: state.output } : {},
|
|
639
|
-
...asString(state?.error) ? { message: asString(state?.error) } : {}
|
|
640
|
-
}
|
|
641
|
-
};
|
|
746
|
+
if (partType === "step-finish") {
|
|
747
|
+
usageFromStepFinish(part, usage);
|
|
748
|
+
recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
|
|
749
|
+
const promptTokens = usage.inputTokens ?? 0;
|
|
750
|
+
const completionTokens = usage.outputTokens ?? 0;
|
|
751
|
+
if (promptTokens || completionTokens) {
|
|
752
|
+
yield { type: "usage", usage: { promptTokens, completionTokens } };
|
|
753
|
+
}
|
|
754
|
+
continue;
|
|
642
755
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
usageFromStepFinish(part, usage);
|
|
647
|
-
recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
|
|
648
|
-
const promptTokens = usage.inputTokens ?? 0;
|
|
649
|
-
const completionTokens = usage.outputTokens ?? 0;
|
|
650
|
-
if (promptTokens || completionTokens) {
|
|
651
|
-
yield { type: "usage", usage: { promptTokens, completionTokens } };
|
|
756
|
+
if (partType === "step-start") {
|
|
757
|
+
recordPersistedPart(part, void 0, `step-start:#${stepCounter}`);
|
|
758
|
+
continue;
|
|
652
759
|
}
|
|
760
|
+
if (partType === "file" && options.promoteFilePart) {
|
|
761
|
+
const promote = options.promoteFilePart;
|
|
762
|
+
const rawId = asString(part.id);
|
|
763
|
+
const rawUrl = asString(part.url);
|
|
764
|
+
const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : void 0;
|
|
765
|
+
const attempt = () => Promise.resolve().then(() => promote(part)).catch((err) => {
|
|
766
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
767
|
+
log("[chat-routes] file part promotion threw", { key: memoKey ?? "(keyless)", error: reason });
|
|
768
|
+
return { succeeded: false, reason };
|
|
769
|
+
});
|
|
770
|
+
let pending;
|
|
771
|
+
if (memoKey) {
|
|
772
|
+
pending = promotedFileParts.get(memoKey) ?? attempt();
|
|
773
|
+
promotedFileParts.set(memoKey, pending);
|
|
774
|
+
} else {
|
|
775
|
+
pending = attempt();
|
|
776
|
+
}
|
|
777
|
+
const outcome = await pending;
|
|
778
|
+
if (outcome.succeeded) {
|
|
779
|
+
recordPersistedPart(outcome.part, void 0, outcome.key);
|
|
780
|
+
} else if (outcome.part) {
|
|
781
|
+
recordPersistedPart(outcome.part, void 0, outcome.key);
|
|
782
|
+
} else {
|
|
783
|
+
recordPersistedPart(part, void 0);
|
|
784
|
+
}
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
recordPersistedPart(part, void 0);
|
|
653
788
|
continue;
|
|
654
789
|
}
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const promote = options.promoteFilePart;
|
|
661
|
-
const rawId = asString(part.id);
|
|
662
|
-
const rawUrl = asString(part.url);
|
|
663
|
-
const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : void 0;
|
|
664
|
-
const attempt = () => Promise.resolve().then(() => promote(part)).catch((err) => {
|
|
665
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
666
|
-
log("[chat-routes] file part promotion threw", { key: memoKey ?? "(keyless)", error: reason });
|
|
667
|
-
return { succeeded: false, reason };
|
|
668
|
-
});
|
|
669
|
-
let pending;
|
|
670
|
-
if (memoKey) {
|
|
671
|
-
pending = promotedFileParts.get(memoKey) ?? attempt();
|
|
672
|
-
promotedFileParts.set(memoKey, pending);
|
|
673
|
-
} else {
|
|
674
|
-
pending = attempt();
|
|
790
|
+
if (event.type === "interaction") {
|
|
791
|
+
const parsed = parseInteractionRequest(asRecord(record.data));
|
|
792
|
+
if (!parsed.succeeded) {
|
|
793
|
+
log("[chat-routes] dropping malformed interaction event", { error: parsed.error });
|
|
794
|
+
continue;
|
|
675
795
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
796
|
+
if (renderable(parsed.value.kind)) {
|
|
797
|
+
recordPersistedPart(
|
|
798
|
+
interactionToPersistedPart(parsed.value, "pending"),
|
|
799
|
+
void 0,
|
|
800
|
+
interactionPartKey(parsed.value.id)
|
|
801
|
+
);
|
|
802
|
+
yield toProducerWireEvent(record);
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
let declineFailed = false;
|
|
806
|
+
if (options.declineInteraction) {
|
|
807
|
+
try {
|
|
808
|
+
await options.declineInteraction(parsed.value.id);
|
|
809
|
+
} catch (err) {
|
|
810
|
+
declineFailed = true;
|
|
811
|
+
log("[chat-routes] failed to auto-decline interaction", {
|
|
812
|
+
id: parsed.value.id,
|
|
813
|
+
error: err instanceof Error ? err.message : String(err)
|
|
814
|
+
});
|
|
815
|
+
}
|
|
681
816
|
} else {
|
|
682
|
-
|
|
817
|
+
declineFailed = true;
|
|
818
|
+
log("[chat-routes] non-renderable interaction with no declineInteraction wired", {
|
|
819
|
+
id: parsed.value.id,
|
|
820
|
+
kind: parsed.value.kind
|
|
821
|
+
});
|
|
683
822
|
}
|
|
823
|
+
const text = declineFailed ? `The agent requested ${parsed.value.kind} approval; declining it failed \u2014 it will expire on its own.` : `The agent requested ${parsed.value.kind} approval \u2014 auto-declined by policy.`;
|
|
824
|
+
const notice = noticePart("auto-declined", `auto-declined-${parsed.value.id}`, text);
|
|
825
|
+
recordPersistedPart(notice, void 0, noticePartKey(notice.id));
|
|
826
|
+
yield { type: "notice", id: notice.id, noticeKind: "auto-declined", text };
|
|
684
827
|
continue;
|
|
685
828
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
829
|
+
if (event.type === "interaction.cancel") {
|
|
830
|
+
const parsed = parseInteractionCancel(asRecord(record.data));
|
|
831
|
+
if (!parsed.succeeded) {
|
|
832
|
+
log("[chat-routes] dropping malformed interaction.cancel event", { error: parsed.error });
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const key = interactionPartKey(parsed.value.id);
|
|
836
|
+
const existing = partMap.get(key);
|
|
837
|
+
if (existing?.type === "interaction" && existing.status === "pending") {
|
|
838
|
+
recordPersistedPart({
|
|
839
|
+
...existing,
|
|
840
|
+
status: cancelStatusFor(parsed.value.reason),
|
|
841
|
+
...parsed.value.reason ? { cancelReason: parsed.value.reason } : {}
|
|
842
|
+
}, void 0, key);
|
|
843
|
+
}
|
|
844
|
+
yield toProducerWireEvent(record);
|
|
693
845
|
continue;
|
|
694
846
|
}
|
|
695
|
-
if (
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
847
|
+
if (event.type === "plan.submitted") {
|
|
848
|
+
const parsed = parsePlanSubmittedEvent(record);
|
|
849
|
+
if (!parsed.succeeded) {
|
|
850
|
+
log("[chat-routes] dropping malformed plan.submitted event", { error: parsed.error });
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
recordPersistedPart(planToPersistedPart(parsed.value), void 0);
|
|
854
|
+
yield toProducerWireEvent(record);
|
|
702
855
|
continue;
|
|
703
856
|
}
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
857
|
+
if (event.type === "warning") {
|
|
858
|
+
const message = asString(event.data?.message);
|
|
859
|
+
if (message) {
|
|
860
|
+
warningCount += 1;
|
|
861
|
+
const code = asString(event.data?.code);
|
|
862
|
+
const text = code ? `${code}: ${message}` : message;
|
|
863
|
+
const notice = noticePart("warning", `warning-${warningCount}`, text);
|
|
864
|
+
recordPersistedPart(notice, void 0, noticePartKey(notice.id));
|
|
865
|
+
yield { type: "notice", id: notice.id, noticeKind: "warning", text };
|
|
712
866
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
id: parsed.value.id,
|
|
716
|
-
kind: parsed.value.kind
|
|
717
|
-
});
|
|
867
|
+
yield toProducerWireEvent(record);
|
|
868
|
+
continue;
|
|
718
869
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
870
|
+
if (event.type === "result") {
|
|
871
|
+
const finalText = asString(event.data?.finalText);
|
|
872
|
+
if (finalText) fullText = finalText;
|
|
873
|
+
const reported = extractReportedTurnUsage(asRecord(event.data));
|
|
874
|
+
if (reported) {
|
|
875
|
+
applyTerminalUsage(reported, usage);
|
|
876
|
+
yield* emitFinalUsage();
|
|
877
|
+
} else {
|
|
878
|
+
const resultUsage = asRecord(event.data?.usage);
|
|
879
|
+
if (resultUsage) {
|
|
880
|
+
const input = Number(resultUsage.inputTokens);
|
|
881
|
+
const output = Number(resultUsage.outputTokens);
|
|
882
|
+
if (Number.isFinite(input)) usage.inputTokens = input;
|
|
883
|
+
if (Number.isFinite(output)) usage.outputTokens = output;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
yield* emitTerminalizedTools();
|
|
725
887
|
continue;
|
|
726
888
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
889
|
+
if (event.type === "done") {
|
|
890
|
+
const reported = extractReportedTurnUsage(asRecord(event.data));
|
|
891
|
+
if (reported) {
|
|
892
|
+
applyTerminalUsage(reported, usage);
|
|
893
|
+
yield* emitFinalUsage();
|
|
894
|
+
}
|
|
895
|
+
yield* emitTerminalizedTools();
|
|
896
|
+
yield toProducerWireEvent(record);
|
|
897
|
+
continue;
|
|
735
898
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
899
|
+
if (event.type === "error") {
|
|
900
|
+
const message = sandboxStreamErrorMessage(event.data);
|
|
901
|
+
const errorContent = fullText.trim() ? `The sandbox model stream stopped before a clean completion.
|
|
902
|
+
|
|
903
|
+
Error: ${message}` : `The sandbox agent returned an error before producing a visible answer.
|
|
904
|
+
|
|
905
|
+
Error: ${message}`;
|
|
906
|
+
const errorDelta = fullText ? `
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
${errorContent}` : errorContent;
|
|
910
|
+
fullText += errorDelta;
|
|
911
|
+
interactionOutcome = "expired";
|
|
912
|
+
yield { type: "text", text: errorDelta };
|
|
913
|
+
yield* emitTerminalizedTools();
|
|
914
|
+
yield toProducerWireEvent(record);
|
|
743
915
|
continue;
|
|
744
916
|
}
|
|
745
|
-
|
|
746
|
-
yield event;
|
|
747
|
-
continue;
|
|
917
|
+
yield toProducerWireEvent(record);
|
|
748
918
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
919
|
+
} catch (streamErr) {
|
|
920
|
+
const diagnostic = sandboxStreamFailureDiagnostic(streamErr);
|
|
921
|
+
log("[chat-routes] sandbox stream failed", {
|
|
922
|
+
failureNote: diagnostic.failureNote,
|
|
923
|
+
error: streamErr instanceof Error ? streamErr.message : String(streamErr)
|
|
924
|
+
});
|
|
925
|
+
const errorDelta = fullText ? `
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
${diagnostic.userMessage}` : diagnostic.userMessage;
|
|
929
|
+
fullText += errorDelta;
|
|
930
|
+
interactionOutcome = "expired";
|
|
931
|
+
yield { type: "text", text: errorDelta };
|
|
932
|
+
yield* emitTerminalizedTools();
|
|
933
|
+
yield {
|
|
934
|
+
type: "error",
|
|
935
|
+
data: {
|
|
936
|
+
message: diagnostic.userMessage,
|
|
937
|
+
code: "sandbox.stream_failed",
|
|
938
|
+
details: { failureNote: diagnostic.failureNote }
|
|
758
939
|
}
|
|
759
|
-
|
|
760
|
-
}
|
|
761
|
-
yield event;
|
|
940
|
+
};
|
|
762
941
|
}
|
|
763
942
|
}
|
|
764
943
|
return {
|
|
765
944
|
stream: stream(),
|
|
766
945
|
finalText: () => fullText,
|
|
767
|
-
assistantParts: () =>
|
|
946
|
+
assistantParts: () => finalizePendingInteractionParts(
|
|
947
|
+
finalizeAssistantParts(partOrder, partMap, fullText),
|
|
948
|
+
interactionOutcome
|
|
949
|
+
),
|
|
768
950
|
usage: () => usage,
|
|
769
951
|
...options.model ? { model: options.model } : {}
|
|
770
952
|
};
|