@tangle-network/agent-app 0.60.197 → 0.60.199
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/dist/chat-routes/index.js +6 -4
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/turn-routes.d.ts +28 -0
- package/dist/{chunk-XJEIGVMQ.js → chunk-2OWT3TNV.js} +2 -2
- package/dist/{chunk-XP77DJEF.js → chunk-6XXKFYIM.js} +72 -30
- package/dist/chunk-6XXKFYIM.js.map +1 -0
- package/dist/chunk-7XQIY2LU.js +151 -0
- package/dist/chunk-7XQIY2LU.js.map +1 -0
- package/dist/chunk-BKPJFES3.js +229 -0
- package/dist/chunk-BKPJFES3.js.map +1 -0
- package/dist/{chunk-7WEPGGOW.js → chunk-EGBNNTAL.js} +2 -2
- package/dist/{chunk-7WEPGGOW.js.map → chunk-EGBNNTAL.js.map} +1 -1
- package/dist/{chunk-SUIH5B55.js → chunk-RJJRRR5Z.js} +184 -16
- package/dist/chunk-RJJRRR5Z.js.map +1 -0
- package/dist/{chunk-FPOOBGRG.js → chunk-T4BLISYH.js} +40 -5
- package/dist/chunk-T4BLISYH.js.map +1 -0
- package/dist/public-consultation/index.d.ts +1 -1
- package/dist/public-consultation/index.js +2 -1
- package/dist/public-consultation/index.js.map +1 -1
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/router-turn.d.ts +14 -1
- package/dist/runtime/router-turn.js +3 -1
- package/dist/sandbox/index.d.ts +19 -0
- package/dist/sandbox/index.js +2 -1
- package/dist/sandbox/prompt-trace.d.ts +20 -0
- package/dist/sandbox/sandbox-run-tool.d.ts +8 -0
- package/dist/trace/index.d.ts +0 -1
- package/dist/trace/index.js +0 -240
- package/dist/trace/index.js.map +1 -1
- package/dist/turn-trace/index.d.ts +11 -0
- package/dist/turn-trace/index.js +121 -0
- package/dist/turn-trace/index.js.map +1 -0
- package/dist/turn-trace/otel.d.ts +36 -0
- package/dist/turn-trace/recorder.d.ts +122 -0
- package/dist/turn-trace/server-timing.d.ts +15 -0
- package/dist/turn-trace/traced-fetch.d.ts +31 -0
- package/dist/workspace-apps/index.js +1 -1
- package/dist/workspace-apps/react.js +1 -1
- package/package.json +6 -1
- package/dist/chunk-FPOOBGRG.js.map +0 -1
- package/dist/chunk-SUIH5B55.js.map +0 -1
- package/dist/chunk-XP77DJEF.js.map +0 -1
- package/dist/trace/stage-timing.d.ts +0 -61
- /package/dist/{chunk-XJEIGVMQ.js.map → chunk-2OWT3TNV.js.map} +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
nextCallIndex,
|
|
3
|
+
parseServerTiming,
|
|
4
|
+
serverTimingAttrs,
|
|
5
|
+
tracedModelFetch
|
|
6
|
+
} from "../chunk-7XQIY2LU.js";
|
|
7
|
+
import {
|
|
8
|
+
TURN_SPAN_KINDS,
|
|
9
|
+
createTurnTrace,
|
|
10
|
+
isTraceId,
|
|
11
|
+
summarizeTurnSpans,
|
|
12
|
+
turnTraceId
|
|
13
|
+
} from "../chunk-BKPJFES3.js";
|
|
14
|
+
|
|
15
|
+
// src/turn-trace/otel.ts
|
|
16
|
+
import {
|
|
17
|
+
createOtelExporter,
|
|
18
|
+
padSpanId,
|
|
19
|
+
padTraceId,
|
|
20
|
+
toOtelAttributes
|
|
21
|
+
} from "@tangle-network/agent-runtime";
|
|
22
|
+
var SPAN_KIND = { "model.call": "LLM", "tool.call": "TOOL" };
|
|
23
|
+
var msToNs = (ms) => (BigInt(Math.floor(ms)) * 1000000n).toString();
|
|
24
|
+
function buildTurnTraceOtelSpans(snapshot, labels = {}) {
|
|
25
|
+
const traceId = padTraceId(snapshot.traceId);
|
|
26
|
+
const root = padSpanId(snapshot.rootSpanId);
|
|
27
|
+
const { summary } = snapshot;
|
|
28
|
+
const rootAttrs = {
|
|
29
|
+
...labels,
|
|
30
|
+
"openinference.span.kind": "CHAIN",
|
|
31
|
+
"tangle.execution_id": snapshot.executionId,
|
|
32
|
+
"tangle.trace_id": snapshot.traceId,
|
|
33
|
+
"tangle.started_at_ms": snapshot.startedAt,
|
|
34
|
+
"tangle.completed_at_ms": snapshot.endedAt,
|
|
35
|
+
"tangle.duration_ms": summary.wallMs,
|
|
36
|
+
"tangle.turn.wall_ms": summary.wallMs,
|
|
37
|
+
"tangle.turn.covered_ms": summary.coveredMs,
|
|
38
|
+
"tangle.turn.coverage_pct": summary.coveragePct,
|
|
39
|
+
"tangle.turn.uncovered_ms": summary.uncoveredMs,
|
|
40
|
+
"tangle.turn.largest_gap_ms": summary.largestGapMs,
|
|
41
|
+
"tangle.turn.spans": summary.spans,
|
|
42
|
+
"tangle.turn.model_calls": summary.modelCalls,
|
|
43
|
+
"tangle.turn.tool_calls": summary.toolCalls,
|
|
44
|
+
...summary.dropped ? { "tangle.turn.spans_dropped": summary.dropped } : {},
|
|
45
|
+
...Object.fromEntries(TURN_SPAN_KINDS.map((kind) => [`tangle.turn.${kind}_ms`, summary.byKind[kind]]))
|
|
46
|
+
};
|
|
47
|
+
const spans = [{
|
|
48
|
+
traceId,
|
|
49
|
+
spanId: root,
|
|
50
|
+
name: "agent_app.turn",
|
|
51
|
+
kind: 1,
|
|
52
|
+
startTimeUnixNano: msToNs(snapshot.startedAt),
|
|
53
|
+
endTimeUnixNano: msToNs(snapshot.endedAt),
|
|
54
|
+
attributes: toOtelAttributes(rootAttrs),
|
|
55
|
+
status: snapshot.status === "ok" ? { code: 1 } : { code: 2, ...snapshot.error ? { message: snapshot.error } : {} }
|
|
56
|
+
}];
|
|
57
|
+
for (const span of snapshot.spans) {
|
|
58
|
+
const attrs = {
|
|
59
|
+
...span.attrs,
|
|
60
|
+
"openinference.span.kind": SPAN_KIND[span.name] ?? "CHAIN",
|
|
61
|
+
"tangle.turn.span_kind": span.kind,
|
|
62
|
+
"agent.parent.confidence": "explicit",
|
|
63
|
+
...span.name === "tool.call" ? { "gen_ai.operation.name": "execute_tool" } : {},
|
|
64
|
+
...span.unfinished ? { "tangle.turn.unfinished": true } : {},
|
|
65
|
+
...span.error ? { "error.message": span.error } : {}
|
|
66
|
+
};
|
|
67
|
+
spans.push({
|
|
68
|
+
traceId,
|
|
69
|
+
spanId: padSpanId(span.spanId),
|
|
70
|
+
parentSpanId: padSpanId(span.parentSpanId),
|
|
71
|
+
name: span.name,
|
|
72
|
+
kind: 1,
|
|
73
|
+
startTimeUnixNano: msToNs(span.startMs),
|
|
74
|
+
endTimeUnixNano: msToNs(span.endMs),
|
|
75
|
+
attributes: toOtelAttributes(attrs),
|
|
76
|
+
status: span.error ? { code: 2, message: span.error } : { code: 1 }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return spans;
|
|
80
|
+
}
|
|
81
|
+
var DEFAULT_INTELLIGENCE_BASE_URL = "https://intelligence.tangle.tools";
|
|
82
|
+
function createIntelligenceTurnTraceSink(options) {
|
|
83
|
+
let exporter;
|
|
84
|
+
const exporterFor = (apiKey) => {
|
|
85
|
+
exporter ??= createOtelExporter({
|
|
86
|
+
endpoint: `${(options.baseUrl ?? DEFAULT_INTELLIGENCE_BASE_URL).replace(/\/+$/, "")}/v1/otlp`,
|
|
87
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
88
|
+
serviceName: options.project,
|
|
89
|
+
resourceAttributes: { "tangle.project": options.project }
|
|
90
|
+
});
|
|
91
|
+
return exporter;
|
|
92
|
+
};
|
|
93
|
+
return async (snapshot, labels) => {
|
|
94
|
+
if (!options.apiKey) return;
|
|
95
|
+
try {
|
|
96
|
+
const sink = exporterFor(options.apiKey);
|
|
97
|
+
if (!sink) return;
|
|
98
|
+
for (const span of buildTurnTraceOtelSpans(snapshot, { project: options.project, ...labels })) sink.exportSpan(span);
|
|
99
|
+
await sink.flush();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
try {
|
|
102
|
+
options.onError?.(error instanceof Error ? error.message.slice(0, 160) : "turn trace export failed");
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
TURN_SPAN_KINDS,
|
|
110
|
+
buildTurnTraceOtelSpans,
|
|
111
|
+
createIntelligenceTurnTraceSink,
|
|
112
|
+
createTurnTrace,
|
|
113
|
+
isTraceId,
|
|
114
|
+
nextCallIndex,
|
|
115
|
+
parseServerTiming,
|
|
116
|
+
serverTimingAttrs,
|
|
117
|
+
summarizeTurnSpans,
|
|
118
|
+
tracedModelFetch,
|
|
119
|
+
turnTraceId
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/turn-trace/otel.ts"],"sourcesContent":["/**\n * A finished turn trace as OTLP spans, sent to Tangle Intelligence.\n *\n * The span builders and the exporter are agent-runtime's (`createOtelExporter`,\n * `toOtelAttributes`, `padTraceId`); this file only maps the turn's tree onto\n * them, using the GenAI semantic-convention keys the runtime's own builders\n * write (`gen_ai.operation.name`, `gen_ai.request.model`, `gen_ai.usage.*`,\n * `gen_ai.tool.name`) and OpenInference's span kind.\n *\n * Server only: it imports the runtime root, which a browser bundle must not.\n */\nimport {\n createOtelExporter,\n padSpanId,\n padTraceId,\n toOtelAttributes,\n type OtelExporter,\n type OtelSpan,\n} from '@tangle-network/agent-runtime'\nimport { TURN_SPAN_KINDS, type TurnSpanAttrs, type TurnSpanKind, type TurnTraceSink, type TurnTraceSnapshot } from './recorder'\n\n/** OpenInference span kind per turn span: model calls read as LLM, tool calls as TOOL. */\nconst SPAN_KIND: Partial<Record<string, 'LLM' | 'TOOL'>> = { 'model.call': 'LLM', 'tool.call': 'TOOL' }\n\nconst msToNs = (ms: number) => (BigInt(Math.floor(ms)) * 1_000_000n).toString()\n\n/**\n * The root span (`agent_app.turn`) carries the host's labels and the turn's\n * numbers: wall time, the share inside child spans, and the time credited to\n * each kind of span. Children follow under it.\n */\nexport function buildTurnTraceOtelSpans(snapshot: TurnTraceSnapshot, labels: TurnSpanAttrs = {}): OtelSpan[] {\n const traceId = padTraceId(snapshot.traceId)\n const root = padSpanId(snapshot.rootSpanId)\n const { summary } = snapshot\n const rootAttrs: Record<string, string | number | boolean> = {\n ...labels,\n 'openinference.span.kind': 'CHAIN',\n 'tangle.execution_id': snapshot.executionId,\n 'tangle.trace_id': snapshot.traceId,\n 'tangle.started_at_ms': snapshot.startedAt,\n 'tangle.completed_at_ms': snapshot.endedAt,\n 'tangle.duration_ms': summary.wallMs,\n 'tangle.turn.wall_ms': summary.wallMs,\n 'tangle.turn.covered_ms': summary.coveredMs,\n 'tangle.turn.coverage_pct': summary.coveragePct,\n 'tangle.turn.uncovered_ms': summary.uncoveredMs,\n 'tangle.turn.largest_gap_ms': summary.largestGapMs,\n 'tangle.turn.spans': summary.spans,\n 'tangle.turn.model_calls': summary.modelCalls,\n 'tangle.turn.tool_calls': summary.toolCalls,\n ...(summary.dropped ? { 'tangle.turn.spans_dropped': summary.dropped } : {}),\n ...Object.fromEntries(TURN_SPAN_KINDS.map((kind: TurnSpanKind) => [`tangle.turn.${kind}_ms`, summary.byKind[kind]])),\n }\n const spans: OtelSpan[] = [{\n traceId,\n spanId: root,\n name: 'agent_app.turn',\n kind: 1,\n startTimeUnixNano: msToNs(snapshot.startedAt),\n endTimeUnixNano: msToNs(snapshot.endedAt),\n attributes: toOtelAttributes(rootAttrs),\n status: snapshot.status === 'ok' ? { code: 1 } : { code: 2, ...(snapshot.error ? { message: snapshot.error } : {}) },\n }]\n for (const span of snapshot.spans) {\n const attrs: Record<string, string | number | boolean> = {\n ...span.attrs,\n 'openinference.span.kind': SPAN_KIND[span.name] ?? 'CHAIN',\n 'tangle.turn.span_kind': span.kind,\n 'agent.parent.confidence': 'explicit',\n ...(span.name === 'tool.call' ? { 'gen_ai.operation.name': 'execute_tool' } : {}),\n ...(span.unfinished ? { 'tangle.turn.unfinished': true } : {}),\n ...(span.error ? { 'error.message': span.error } : {}),\n }\n spans.push({\n traceId,\n spanId: padSpanId(span.spanId),\n parentSpanId: padSpanId(span.parentSpanId),\n name: span.name,\n kind: 1,\n startTimeUnixNano: msToNs(span.startMs),\n endTimeUnixNano: msToNs(span.endMs),\n attributes: toOtelAttributes(attrs),\n status: span.error ? { code: 2, message: span.error } : { code: 1 },\n })\n }\n return spans\n}\n\nexport interface IntelligenceTurnTraceSinkOptions {\n /** The Intelligence project the spans belong to, e.g. `legal-agent`. */\n project: string\n /** The tenant key the ingest requires. Without one the sink does nothing. */\n apiKey: string | undefined\n /** Default `https://intelligence.tangle.tools`; spans go to `<base>/v1/otlp`. */\n baseUrl?: string\n /** Called with a short reason when a delivery fails. Never receives span content. */\n onError?: (reason: string) => void\n}\n\nconst DEFAULT_INTELLIGENCE_BASE_URL = 'https://intelligence.tangle.tools'\n\n/**\n * The sink a chat route or a Worker turn hands each finished trace to. The\n * exporter is built on first use, inside a request, because a Worker refuses a\n * timer at module scope. Each trace is flushed before the sink returns, so a\n * host that runs it under `waitUntil` keeps the isolate alive until delivery.\n */\nexport function createIntelligenceTurnTraceSink(options: IntelligenceTurnTraceSinkOptions): TurnTraceSink {\n let exporter: OtelExporter | undefined\n const exporterFor = (apiKey: string): OtelExporter | undefined => {\n exporter ??= createOtelExporter({\n endpoint: `${(options.baseUrl ?? DEFAULT_INTELLIGENCE_BASE_URL).replace(/\\/+$/, '')}/v1/otlp`,\n headers: { authorization: `Bearer ${apiKey}` },\n serviceName: options.project,\n resourceAttributes: { 'tangle.project': options.project },\n })\n return exporter\n }\n return async (snapshot, labels) => {\n if (!options.apiKey) return\n try {\n const sink = exporterFor(options.apiKey)\n if (!sink) return\n for (const span of buildTurnTraceOtelSpans(snapshot, { project: options.project, ...labels })) sink.exportSpan(span)\n await sink.flush()\n } catch (error) {\n try { options.onError?.(error instanceof Error ? error.message.slice(0, 160) : 'turn trace export failed') } catch { /* telemetry only */ }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A finished turn trace as OTLP spans, sent to Tangle Intelligence.
|
|
3
|
+
*
|
|
4
|
+
* The span builders and the exporter are agent-runtime's (`createOtelExporter`,
|
|
5
|
+
* `toOtelAttributes`, `padTraceId`); this file only maps the turn's tree onto
|
|
6
|
+
* them, using the GenAI semantic-convention keys the runtime's own builders
|
|
7
|
+
* write (`gen_ai.operation.name`, `gen_ai.request.model`, `gen_ai.usage.*`,
|
|
8
|
+
* `gen_ai.tool.name`) and OpenInference's span kind.
|
|
9
|
+
*
|
|
10
|
+
* Server only: it imports the runtime root, which a browser bundle must not.
|
|
11
|
+
*/
|
|
12
|
+
import { type OtelSpan } from '@tangle-network/agent-runtime';
|
|
13
|
+
import { type TurnSpanAttrs, type TurnTraceSink, type TurnTraceSnapshot } from './recorder';
|
|
14
|
+
/**
|
|
15
|
+
* The root span (`agent_app.turn`) carries the host's labels and the turn's
|
|
16
|
+
* numbers: wall time, the share inside child spans, and the time credited to
|
|
17
|
+
* each kind of span. Children follow under it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildTurnTraceOtelSpans(snapshot: TurnTraceSnapshot, labels?: TurnSpanAttrs): OtelSpan[];
|
|
20
|
+
export interface IntelligenceTurnTraceSinkOptions {
|
|
21
|
+
/** The Intelligence project the spans belong to, e.g. `legal-agent`. */
|
|
22
|
+
project: string;
|
|
23
|
+
/** The tenant key the ingest requires. Without one the sink does nothing. */
|
|
24
|
+
apiKey: string | undefined;
|
|
25
|
+
/** Default `https://intelligence.tangle.tools`; spans go to `<base>/v1/otlp`. */
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/** Called with a short reason when a delivery fails. Never receives span content. */
|
|
28
|
+
onError?: (reason: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The sink a chat route or a Worker turn hands each finished trace to. The
|
|
32
|
+
* exporter is built on first use, inside a request, because a Worker refuses a
|
|
33
|
+
* timer at module scope. Each trace is flushed before the sink returns, so a
|
|
34
|
+
* host that runs it under `waitUntil` keeps the isolate alive until delivery.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createIntelligenceTurnTraceSink(options: IntelligenceTurnTraceSinkOptions): TurnTraceSink;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One trace per agent turn: a root span for the turn's wall time and named
|
|
3
|
+
* child spans for where that time went (admission, each model call, each tool
|
|
4
|
+
* call, the box claim and prompt, persistence).
|
|
5
|
+
*
|
|
6
|
+
* This module only records. It has no imports, so the Router engine, the
|
|
7
|
+
* sandbox helpers and the chat route share it from any runtime, a Worker
|
|
8
|
+
* included. `./otel` turns a finished trace into OTLP spans for Tangle
|
|
9
|
+
* Intelligence.
|
|
10
|
+
*
|
|
11
|
+
* The turn's trace id is its execution id in the charset every hop accepts
|
|
12
|
+
* ({@link turnTraceId}), sent as `x-tangle-trace-id` on every Router and
|
|
13
|
+
* Sandbox call, so a Router dispatch row and a sandbox record join the turn's
|
|
14
|
+
* spans by one id.
|
|
15
|
+
*/
|
|
16
|
+
/** What a span's time is spent on. Each instant of the turn is credited to the deepest open span's kind. */
|
|
17
|
+
export type TurnSpanKind = 'admission' | 'router' | 'model' | 'network' | 'tool' | 'sandbox' | 'persist' | 'other';
|
|
18
|
+
export declare const TURN_SPAN_KINDS: readonly TurnSpanKind[];
|
|
19
|
+
/** Bounded primitive metadata. Never prompts, answers, tool arguments or credentials. */
|
|
20
|
+
export type TurnSpanAttrs = Record<string, string | number | boolean>;
|
|
21
|
+
export interface TurnSpanRecord {
|
|
22
|
+
spanId: string;
|
|
23
|
+
/** The root span's id for a top-level child. */
|
|
24
|
+
parentSpanId: string;
|
|
25
|
+
name: string;
|
|
26
|
+
kind: TurnSpanKind;
|
|
27
|
+
startMs: number;
|
|
28
|
+
endMs: number;
|
|
29
|
+
attrs: TurnSpanAttrs;
|
|
30
|
+
/** Set when the span ended in failure; a short code or message, never content. */
|
|
31
|
+
error?: string;
|
|
32
|
+
/** True when the turn finished while the span was still open. */
|
|
33
|
+
unfinished?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface TurnSpan {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
/** Merge attributes. A later value replaces an earlier one. */
|
|
38
|
+
set(attrs: TurnSpanAttrs): TurnSpan;
|
|
39
|
+
/** End now. A second call changes nothing. `error` marks the span failed. */
|
|
40
|
+
end(attrs?: TurnSpanAttrs, error?: unknown): void;
|
|
41
|
+
/** A child that starts now. */
|
|
42
|
+
child(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs): TurnSpan;
|
|
43
|
+
/** A child whose times are already known. */
|
|
44
|
+
record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs): TurnSpan;
|
|
45
|
+
}
|
|
46
|
+
/** How much of the turn's wall time is inside named spans, and what the time was spent on. */
|
|
47
|
+
export interface TurnTraceSummary {
|
|
48
|
+
traceId: string;
|
|
49
|
+
wallMs: number;
|
|
50
|
+
/** Wall time inside at least one child span. */
|
|
51
|
+
coveredMs: number;
|
|
52
|
+
/** `coveredMs / wallMs` as a percentage, one decimal. */
|
|
53
|
+
coveragePct: number;
|
|
54
|
+
uncoveredMs: number;
|
|
55
|
+
/** The longest stretch with no span open: where an unnamed wait hides. */
|
|
56
|
+
largestGapMs: number;
|
|
57
|
+
/** Wall time credited to the deepest open span's kind. Sums to `coveredMs`. */
|
|
58
|
+
byKind: Record<TurnSpanKind, number>;
|
|
59
|
+
spans: number;
|
|
60
|
+
modelCalls: number;
|
|
61
|
+
toolCalls: number;
|
|
62
|
+
/** Spans refused after the per-turn cap. */
|
|
63
|
+
dropped: number;
|
|
64
|
+
}
|
|
65
|
+
export interface TurnTraceSnapshot {
|
|
66
|
+
traceId: string;
|
|
67
|
+
/** The id the root span is exported under. */
|
|
68
|
+
rootSpanId: string;
|
|
69
|
+
executionId: string;
|
|
70
|
+
startedAt: number;
|
|
71
|
+
endedAt: number;
|
|
72
|
+
spans: TurnSpanRecord[];
|
|
73
|
+
summary: TurnTraceSummary;
|
|
74
|
+
/** `ok`, or the failure the turn ended with. */
|
|
75
|
+
status: 'ok' | 'error';
|
|
76
|
+
error?: string;
|
|
77
|
+
}
|
|
78
|
+
/** Where a finished turn's trace goes. `labels` are the host's metadata for the root span (session, user, workspace). */
|
|
79
|
+
export type TurnTraceSink = (snapshot: TurnTraceSnapshot, labels: TurnSpanAttrs) => void | Promise<void>;
|
|
80
|
+
export interface TurnTrace {
|
|
81
|
+
/** The turn's execution id in the charset the Router and Sandbox accept. Send it as `x-tangle-trace-id`. */
|
|
82
|
+
readonly traceId: string;
|
|
83
|
+
readonly executionId: string;
|
|
84
|
+
readonly startedAt: number;
|
|
85
|
+
/** The span a top-level child hangs from. */
|
|
86
|
+
readonly root: TurnSpan;
|
|
87
|
+
/** Name the turn's execution id once it is known. Spans recorded earlier keep their times. */
|
|
88
|
+
bind(executionId: string): void;
|
|
89
|
+
start(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan;
|
|
90
|
+
record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan;
|
|
91
|
+
/** Run `work` inside a span; the span ends when it settles, failed if it throws. */
|
|
92
|
+
measure<T>(name: string, kind: TurnSpanKind, work: (span: TurnSpan) => Promise<T> | T, attrs?: TurnSpanAttrs, parent?: TurnSpan): Promise<T>;
|
|
93
|
+
/** Read the current numbers without ending the trace. */
|
|
94
|
+
summary(now?: number): TurnTraceSummary;
|
|
95
|
+
/** End the trace: open spans close at `endedAt`, and later spans are refused. A second call returns the first result. */
|
|
96
|
+
finish(outcome?: {
|
|
97
|
+
error?: unknown;
|
|
98
|
+
endedAt?: number;
|
|
99
|
+
}): TurnTraceSnapshot;
|
|
100
|
+
}
|
|
101
|
+
export interface CreateTurnTraceOptions {
|
|
102
|
+
/** Epoch ms the turn began (default now). A route passes its request time so admission is inside the trace. */
|
|
103
|
+
startedAt?: number;
|
|
104
|
+
executionId?: string;
|
|
105
|
+
now?: () => number;
|
|
106
|
+
/** Most spans one turn keeps (default 1000); the rest are counted as dropped. */
|
|
107
|
+
maxSpans?: number;
|
|
108
|
+
}
|
|
109
|
+
/** True when the Router and the Sandbox API keep this id as a `x-tangle-trace-id`. */
|
|
110
|
+
export declare function isTraceId(value: string): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The turn's execution id as a trace id. Execution ids carry `:` (`app:session:turn`),
|
|
113
|
+
* which the Router drops from `x-tangle-trace-id`, so every character outside
|
|
114
|
+
* `[A-Za-z0-9_-]` becomes `_`. An id already in the charset is returned as it is.
|
|
115
|
+
*/
|
|
116
|
+
export declare function turnTraceId(executionId: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* Coverage and the per-kind split for a set of spans over `[startMs, endMs]`.
|
|
119
|
+
* Exported for a reader that holds the spans of a finished trace.
|
|
120
|
+
*/
|
|
121
|
+
export declare function summarizeTurnSpans(spans: readonly Pick<TurnSpanRecord, 'spanId' | 'parentSpanId' | 'kind' | 'name' | 'startMs' | 'endMs'>[], rootSpanId: string, startMs: number, endMs: number, traceId: string, dropped?: number): TurnTraceSummary;
|
|
122
|
+
export declare function createTurnTrace(options?: CreateTurnTraceOptions): TurnTrace;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** One entry of a `Server-Timing` response header. */
|
|
2
|
+
export interface ServerTimingEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
/** Milliseconds, when the entry carries `dur`. */
|
|
5
|
+
durMs?: number;
|
|
6
|
+
/** `desc`, unquoted: a count or a label, never a duration. */
|
|
7
|
+
desc?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse a `Server-Timing` header, as the Router and the Sandbox orchestrator
|
|
11
|
+
* send it. A malformed entry is skipped; the result never throws.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseServerTiming(header: string | null | undefined): ServerTimingEntry[];
|
|
14
|
+
/** Flat span attributes for the entries: `server_timing.<name>_ms` for a duration, `server_timing.<name>` for a `desc`. */
|
|
15
|
+
export declare function serverTimingAttrs(entries: readonly ServerTimingEntry[]): Record<string, string | number>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TurnSpan, TurnTrace } from './recorder';
|
|
2
|
+
/** 1-based count of `name` spans started through {@link nextCallIndex} on this trace. */
|
|
3
|
+
export declare function nextCallIndex(trace: TurnTrace, name: string): number;
|
|
4
|
+
export interface TracedModelFetchOptions {
|
|
5
|
+
/** The span a model call hangs from. Default: the trace's root. */
|
|
6
|
+
parent?: TurnSpan;
|
|
7
|
+
/** The model the request asks for. */
|
|
8
|
+
model?: string;
|
|
9
|
+
/** Receives each model call's span as it starts, so the engine can add the token counts when usage arrives. */
|
|
10
|
+
onSpan?: (span: TurnSpan) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `fetch` for a model request, recording one `model.call` span per send: the
|
|
14
|
+
* wall time from the request to the end of its body, the time to the first
|
|
15
|
+
* byte, and the Router's own `Server-Timing` so the wait before the first byte
|
|
16
|
+
* splits into network and server time.
|
|
17
|
+
*
|
|
18
|
+
* Children partition the call:
|
|
19
|
+
* - `network`: the wait for headers, less the Router's server time;
|
|
20
|
+
* - `router.pre_upstream`: the Router's work before its provider fetch
|
|
21
|
+
* (`upstream_start`);
|
|
22
|
+
* - `router.upstream_wait`: the provider's time to response headers
|
|
23
|
+
* (`upstream_headers`);
|
|
24
|
+
* - `model.stream`: headers to the end of the body.
|
|
25
|
+
* Without a `Server-Timing` header the first byte's wait is one `model.wait`
|
|
26
|
+
* span, because the split is not known.
|
|
27
|
+
*
|
|
28
|
+
* The response body is passed through unchanged; the span ends when the body
|
|
29
|
+
* ends, errors or is cancelled.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tracedModelFetch(trace: TurnTrace, inner: typeof fetch, options?: TracedModelFetchOptions): typeof fetch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.60.
|
|
3
|
+
"version": "0.60.199",
|
|
4
4
|
"packageManager": "pnpm@11.24.0",
|
|
5
5
|
"description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
|
|
6
6
|
"keywords": [
|
|
@@ -473,6 +473,11 @@
|
|
|
473
473
|
"import": "./dist/trace/index.js",
|
|
474
474
|
"default": "./dist/trace/index.js"
|
|
475
475
|
},
|
|
476
|
+
"./turn-trace": {
|
|
477
|
+
"types": "./dist/turn-trace/index.d.ts",
|
|
478
|
+
"import": "./dist/turn-trace/index.js",
|
|
479
|
+
"default": "./dist/turn-trace/index.js"
|
|
480
|
+
},
|
|
476
481
|
"./sequences": {
|
|
477
482
|
"types": "./dist/sequences/index.d.ts",
|
|
478
483
|
"import": "./dist/sequences/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/loop.ts","../src/runtime/openai-stream.ts","../src/runtime/turn-knowledge.ts","../src/runtime/reply-language.ts","../src/runtime/router-turn.ts","../src/runtime/turn-continuation.ts"],"sourcesContent":["/**\n * The bounded agent tool-loop — owned by `@tangle-network/agent-runtime`.\n *\n * A model turn may emit tool calls (integration-hub actions, the app tools from\n * `../tools`, delegation). The loop streams a turn, collects the executable tool\n * calls, dispatches each, appends the results to history in OpenAI\n * function-calling shape, and re-runs so the model reads them — bounded by\n * `maxToolTurns`, a wall-clock `deadlineMs`, and a `maxCostUsd` budget.\n *\n * The history shape is the OpenAI function-calling contract: the assistant turn\n * that emitted tool calls is preserved as an `assistant` message carrying its\n * `tool_calls` array, and each result is its own `{ role: 'tool', tool_call_id,\n * content }` message keyed to the call. A strict model (Claude, and any\n * OpenAI-compatible provider that validates tool history) needs this to read its\n * own tool use back; folding results into a `user` message makes such models\n * re-issue the same call in a loop.\n *\n * The loop is substrate-owned (`runToolLoop` / `streamToolLoop`); the app\n * supplies `streamTurn` (wrapping its model endpoint) and `executeToolCall`\n * (routing to its integration + app-tool executors). The app-facing names below\n * are 1:1 aliases of the canonical symbols, kept so this package's consumers and\n * the in-package `createAgentRuntime` read against a single, stable vocabulary.\n *\n * This is the LEAF the runtime barrel and its children both import — keeping the\n * tool-loop vocabulary out of any import cycle. The barrel re-exports it.\n */\n\nexport {\n runToolLoop as runAppToolLoop,\n streamToolLoop as streamAppToolLoop,\n} from '@tangle-network/agent-runtime/tool-loop'\nexport type {\n ToolLoopCall as LoopToolCall,\n ToolLoopAssistantToolCall as LoopAssistantToolCall,\n ToolLoopMessage as LoopMessage,\n ToolLoopEvent,\n ToolLoopStopReason,\n ToolLoopResult,\n RunToolLoopOptions as AppToolLoopOptions,\n StreamToolLoopOptions as StreamAppToolLoopOptions,\n StreamToolLoopYield as StreamLoopYield,\n} from '@tangle-network/agent-runtime/tool-loop'\n\n/**\n * Events the app's OpenAI-compat stream adapter ({@link toLoopEvents}) yields.\n *\n * This is the app's own `Raw` event type for the streaming loop — the canonical\n * `streamToolLoop<Raw>` is generic over it. It widens the substrate's\n * tool-loop event with `reasoning` (DeepSeek/router `reasoning_content` /\n * `thinking` deltas, rendered as thinking sections) and `usage` (per-message\n * token accounting) — neither belongs in the substrate's loop contract, so they\n * stay here. The adapter maps each into the `streamTurn` seam; `text` and\n * `tool_call` drive the loop, `reasoning` / `usage` pass through to the UI.\n */\nexport type LoopEvent =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n /** The model has begun a tool call: its name is known, its arguments are\n * still streaming. Emitted once per call, before its `tool_call`. Lets a\n * caller start slow work the call will need (resuming a sandbox box for\n * `sandbox_run`) while the arguments arrive. Consumers that do not care\n * ignore it: it never drives the loop. */\n | { type: 'tool_call_start'; toolCallId?: string; toolName: string }\n | {\n type: 'tool_call'\n call: import('@tangle-network/agent-runtime/tool-loop').ToolLoopCall\n }\n | { type: 'usage'; usage: { promptTokens: number; completionTokens: number } }\n | { type: 'other'; event: unknown }\n","/**\n * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.\n *\n * `streamAppToolLoop` takes a `streamTurn` seam that yields `LoopEvent`s. A\n * sandboxed agent produces those from its container; a browser/edge copilot\n * instead calls a model directly. The Tangle Router, the tcloud SDK, and most\n * providers all speak the OpenAI Chat Completions streaming shape — so the ONE\n * reusable piece is assembling that stream (content deltas + FRAGMENTED\n * tool-call deltas) into `LoopEvent`s. That assembly is the boilerplate every\n * copilot would re-write (and get wrong — OpenAI streams tool-call arguments in\n * pieces across chunks).\n *\n * This does NOT implement an HTTP client beyond a minimal `fetch` + SSE reader\n * (browser/edge/Node-safe, zero deps). For richer transport use the tcloud SDK\n * or the Vercel AI SDK and pipe their stream through {@link toLoopEvents}.\n */\nimport type { LoopEvent, LoopMessage, LoopToolCall } from './loop'\nimport { normalizeModelId } from './model-catalog'\n\n/** Minimal OpenAI Chat Completions streaming chunk (structural — no `openai` dep). */\nexport interface OpenAIStreamChunk {\n /** The model that produced this chunk. The Tangle Router reports the DATED\n * upstream id here (`gpt-5-2025-08-07`) whether or not it substituted, so\n * this is a served-model source only after id folding — see\n * {@link OpenAICompatServedModel}. */\n model?: string\n choices?: Array<{\n delta?: {\n content?: string | null\n /** Reasoning deltas — DeepSeek/router use `reasoning_content`; some proxies use `thinking`. */\n reasoning_content?: string | null\n thinking?: string | null\n tool_calls?: Array<{\n index: number\n id?: string\n function?: { name?: string; arguments?: string }\n /** Provider-specific call data the next request must echo back. Gemini 3\n * sends `{ google: { thought_signature } }` here and answers HTTP 400\n * when a later request's assistant `tool_calls` entry lacks it. */\n extra_content?: unknown\n }>\n }\n finish_reason?: string | null\n }>\n /** Token accounting (requires `stream_options.include_usage`). Cumulative for\n * the call: the Tangle Router can send it on more than one chunk. */\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n } | null\n}\n\ninterface PartialToolCall {\n id?: string\n name: string\n args: string\n extraContent?: unknown\n announced: boolean\n}\n\n/**\n * Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content\n * delta → a `text` event; tool-call deltas are accumulated by index across\n * chunks and emitted as one complete `tool_call` event when the stream finishes\n * (arguments JSON-parsed; an empty/garbled args string yields `{}` rather than\n * throwing). Works for the Tangle Router, tcloud, or any OpenAI-compat source.\n *\n * A call's `extra_content` is kept as `providerFields.extra_content`, which the\n * tool loop echoes on the call's assistant `tool_calls` entry in the next request.\n * Usage is cumulative per call and may arrive on several chunks, so it is\n * yielded once, the last value, when the stream ends.\n *\n * Each call also yields one `tool_call_start` as soon as its name arrives,\n * while its arguments are still streaming, so a caller can start work the call\n * will need. Providers send the whole name in the call's first delta.\n */\nexport async function* toLoopEvents(chunks: AsyncIterable<OpenAIStreamChunk>): AsyncIterable<LoopEvent> {\n const calls = new Map<number, PartialToolCall>()\n let usage: { promptTokens: number; completionTokens: number } | undefined\n for await (const chunk of chunks) {\n // Usage can ride a chunk with an empty choices array — read it before the\n // choice guard. Each report is cumulative, so the last one is the call's.\n if (chunk.usage?.prompt_tokens != null || chunk.usage?.completion_tokens != null) {\n usage = {\n promptTokens: chunk.usage.prompt_tokens ?? 0,\n completionTokens: chunk.usage.completion_tokens ?? 0,\n }\n }\n const choice = chunk.choices?.[0]\n if (!choice) continue\n const content = choice.delta?.content\n if (content) yield { type: 'text', text: content }\n const reasoning = choice.delta?.reasoning_content ?? choice.delta?.thinking\n if (reasoning) yield { type: 'reasoning', text: reasoning }\n for (const tc of choice.delta?.tool_calls ?? []) {\n const cur = calls.get(tc.index) ?? { name: '', args: '', announced: false }\n if (tc.id) cur.id = tc.id\n if (tc.function?.name) cur.name += tc.function.name\n if (tc.function?.arguments) cur.args += tc.function.arguments\n if (tc.extra_content !== undefined) cur.extraContent = tc.extra_content\n calls.set(tc.index, cur)\n if (!cur.announced && cur.name) {\n cur.announced = true\n yield { type: 'tool_call_start', ...(cur.id ? { toolCallId: cur.id } : {}), toolName: cur.name }\n }\n }\n }\n if (usage) yield { type: 'usage', usage }\n for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {\n if (!c.name) continue\n yield {\n type: 'tool_call',\n call: {\n toolCallId: c.id,\n toolName: c.name,\n args: safeParse(c.args),\n ...(c.extraContent !== undefined ? { providerFields: { extra_content: c.extraContent } } : {}),\n } satisfies LoopToolCall,\n }\n }\n}\n\nfunction safeParse(s: string): Record<string, unknown> {\n if (!s.trim()) return {}\n try {\n const v = JSON.parse(s)\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}\n } catch {\n return {}\n }\n}\n\n/**\n * Which model actually served one direct-router turn.\n *\n * The router substitutes models on purpose — a quota-walled primary comes back\n * `200` answered by a different model — and says so in response headers. This\n * lane used to drop the whole `Response` after taking `.body`, so a turn\n * requested as `claude-sonnet-4-6` and answered by `openai/gpt-5` was recorded\n * by its caller as Claude: per-model quality scoring blamed the wrong model and\n * cost used the wrong price basis.\n *\n * Map this onto the shell's existing attribution contract rather than inventing\n * a second channel — `ChatTurnRouteProducer.modelAttribution()` (`/chat-routes`):\n *\n * modelAttribution: () => ({ requestedModel, servedModel, echoReceived: true })\n *\n * Leave that contract's `servedSource` unset: its union is sandbox\n * profile-resolution vocabulary with no router analogue.\n */\nexport interface OpenAICompatServedModel {\n /** The model id this turn asked for (`OpenAICompatStreamTurnOptions.model`). */\n requestedModel: string\n /** The model the router/provider reports having actually served. */\n servedModel: string\n /** Where `servedModel` was read from. The header is the router's own\n * substitution signal; the body is the backstop that survives CORS. */\n source: 'router_header' | 'response_body'\n /** True when served differs from requested after id folding. Folded, not\n * compared raw: the body reports a dated id on EVERY turn, so `!==` would\n * claim a substitution every time. */\n substituted: boolean\n /** `x-tangle-failover` `trigger=` — why the router swapped. Absent when the\n * router did not inject the substitute (a caller-supplied fallback chain\n * sets the served-model header without the failover one). */\n trigger?: string\n /** `x-tangle-failover` `degraded=`. */\n degraded?: boolean\n}\n\n/** Parse `from=…; to=…; trigger=…; degraded=…` down to the fields we report.\n * Absent/garbled segments are simply omitted — attribution is best-effort and\n * must never fail a turn that is otherwise streaming fine. */\nfunction parseFailoverHeader(raw: string | null): { trigger?: string; degraded?: boolean } {\n if (!raw) return {}\n const fields = new Map<string, string>()\n for (const segment of raw.split(';')) {\n const eq = segment.indexOf('=')\n if (eq === -1) continue\n fields.set(segment.slice(0, eq).trim().toLowerCase(), segment.slice(eq + 1).trim())\n }\n const trigger = fields.get('trigger')\n const degraded = fields.get('degraded')\n return {\n ...(trigger ? { trigger } : {}),\n ...(degraded != null ? { degraded: degraded === 'true' } : {}),\n }\n}\n\n/** Define options for configuring an OpenAI-compatible streaming chat turn including API details and tools */\nexport interface OpenAICompatStreamTurnOptions {\n /** OpenAI-compat base URL (e.g. the Tangle Router `https://router.tangle.tools/v1`). */\n baseUrl: string\n apiKey: string\n model: string\n /** OpenAI tool definitions — pass `buildAppToolOpenAITools(taxonomy)` so the\n * model can call the app tools. Omit for a tool-free copilot. */\n tools?: unknown[]\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra body fields (e.g. `max_tokens`). */\n extraBody?: Record<string, unknown>\n /** Extra request headers, e.g. a trace id the router records with the generation. */\n headers?: Record<string, string>\n /**\n * Called at most ONCE per turn, as soon as the serving model is\n * determinable, with what actually answered. Never called when neither the\n * header nor the body names a model — silence means \"learned nothing\", not\n * \"nothing was substituted\".\n *\n * `streamTurn` runs once per TOOL turn, so a multi-turn `runAppToolLoop`\n * fires this once per turn; take the last for row attribution.\n */\n onServedModel?: (served: OpenAICompatServedModel) => void\n}\n\n/**\n * Build a `streamTurn` that calls an OpenAI-compatible `/chat/completions`\n * endpoint (Tangle Router / tcloud / any compat provider) with `stream: true`\n * and yields `LoopEvent`s via {@link toLoopEvents}. Browser/edge/Node-safe —\n * just `fetch` + an SSE reader. Drop straight into `streamAppToolLoop`:\n *\n * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }\n * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })\n */\nexport function createOpenAICompatStreamTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n streamChatCompletions(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n stream: true,\n stream_options: { include_usage: true },\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }, opts.onServedModel ? { requestedModel: opts.model, report: opts.onServedModel } : undefined, opts.headers),\n )\n}\n\n/**\n * The buffered sibling of {@link createOpenAICompatStreamTurn}: one\n * `/chat/completions` request without `stream`, answered by one JSON\n * completion, yielded as the same `LoopEvent`s (the same tool-call assembly and\n * served-model attribution). For an endpoint or ledger that expects one buffered\n * completion per model call.\n */\nexport function createOpenAICompatCompletionTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n completionAsChunk(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }, opts.onServedModel ? { requestedModel: opts.model, report: opts.onServedModel } : undefined, opts.headers),\n )\n}\n\n/** One buffered OpenAI-compat completion (structural — no `openai` dep). */\ninterface OpenAICompletion {\n model?: string\n choices?: Array<{\n message?: {\n content?: string | null\n reasoning_content?: string | null\n tool_calls?: Array<{ id?: string; function?: { name?: string; arguments?: string }; extra_content?: unknown }>\n }\n finish_reason?: string | null\n }>\n usage?: { prompt_tokens?: number; completion_tokens?: number } | null\n}\n\nasync function* completionAsChunk(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n attribution?: { requestedModel: string; report: (served: OpenAICompatServedModel) => void },\n headers?: Record<string, string>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { ...headers, Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify(body),\n })\n if (!res.ok) throw await httpFailure(res, 'completion')\n const completion = await res.json() as OpenAICompletion\n if (attribution) {\n const served = servedFromHeader(res, attribution.requestedModel)\n ?? (completion.model ? servedFromBody(completion.model, attribution.requestedModel) : undefined)\n if (served) attribution.report(served)\n }\n const choice = completion.choices?.[0]\n const message = choice?.message\n // The whole completion as one chunk, so `toLoopEvents` assembles it exactly as a stream.\n yield {\n ...(completion.model ? { model: completion.model } : {}),\n choices: [{\n delta: {\n content: message?.content ?? null,\n reasoning_content: message?.reasoning_content ?? null,\n tool_calls: (message?.tool_calls ?? []).map((call, index) => ({\n index,\n ...(call.id ? { id: call.id } : {}),\n function: { name: call.function?.name ?? '', arguments: call.function?.arguments ?? '' },\n ...(call.extra_content !== undefined ? { extra_content: call.extra_content } : {}),\n })),\n },\n finish_reason: choice?.finish_reason ?? null,\n }],\n ...(completion.usage ? { usage: completion.usage } : {}),\n }\n}\n\nasync function httpFailure(res: Response, kind: 'stream' | 'completion' | 'responses'): Promise<Error> {\n const text = res.body ? await res.text().catch(() => '') : ''\n const error = new Error(`OpenAI-compat ${kind} failed (HTTP ${res.status})${text ? `: ${text.slice(0, 1000)}` : ''}`)\n // Stamp the NUMERIC status. `isUpstreamUnavailable` (`/model-resolution`)\n // reads a numeric field first and prose only as a backstop, and a\n // Cloudflare EDGE 502 gives it nothing else to read: `content-type:\n // text/plain`, a body of `error code: 502`, and none of the router's own\n // `x-tangle-*` headers, because the origin never ran. Carrying the status\n // as data keeps THIS path — the browser/edge copilot lane, which calls the\n // router directly — out of the string-matching business entirely.\n Object.assign(error, { status: res.status })\n return error\n}\n\nfunction servedFromHeader(res: Response, requestedModel: string): OpenAICompatServedModel | undefined {\n const servedHeader = res.headers.get('x-tangle-served-model')\n if (!servedHeader) return undefined\n return {\n requestedModel,\n servedModel: servedHeader,\n source: 'router_header',\n substituted: normalizeModelId(servedHeader) !== normalizeModelId(requestedModel),\n ...parseFailoverHeader(res.headers.get('x-tangle-failover')),\n }\n}\n\nfunction servedFromBody(model: string, requestedModel: string): OpenAICompatServedModel {\n return {\n requestedModel,\n servedModel: model,\n source: 'response_body',\n substituted: normalizeModelId(model) !== normalizeModelId(requestedModel),\n }\n}\n\n/** Stream + parse an OpenAI-compat SSE response into chunks. Tolerates `data:`\n * framing, multi-line buffers, and the terminal `[DONE]`. */\nasync function* streamChatCompletions(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n attribution?: { requestedModel: string; report: (served: OpenAICompatServedModel) => void },\n headers?: Record<string, string>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { ...headers, Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) throw await httpFailure(res, 'stream')\n // Served-model attribution, fired at most once, as early as it is knowable.\n //\n // The header comes first because it is the router's OWN substitution signal:\n // `X-Tangle-Served-Model` is set only when the served model differs from the\n // requested one, so its presence is authoritative and it arrives before the\n // first byte of content. `X-Tangle-Failover` rides along only when the router\n // itself picked the substitute (a caller-supplied fallback chain sets the\n // former without the latter), which is why `trigger`/`degraded` are optional.\n //\n // The body is the backstop for when the header cannot reach us. In a BROWSER,\n // headers are invisible to JS unless the server lists them in\n // `Access-Control-Expose-Headers` — tangle-router#324 added both, but that\n // only helps once it is DEPLOYED, and any other OpenAI-compat endpoint\n // pointed at this client exposes nothing. The chunk `model` field is not\n // subject to CORS, so it keeps the browser lane attributable regardless.\n let reported = false\n const report = (served: OpenAICompatServedModel): void => {\n if (reported || !attribution) return\n reported = true\n attribution.report(served)\n }\n if (attribution) {\n const served = servedFromHeader(res, attribution.requestedModel)\n if (served) report(served)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n let chunk: OpenAIStreamChunk\n try {\n chunk = JSON.parse(data) as OpenAIStreamChunk\n } catch {\n continue /* skip a partial/garbled SSE frame */\n }\n // Body fallback: only when the header said nothing. Fold both ids before\n // comparing — the router reports the dated upstream id (`gpt-5-2025-08-07`)\n // for a request of `openai/gpt-5` even with NO substitution, so a raw\n // `!==` would report a swap on literally every turn.\n if (attribution && !reported && chunk.model) report(servedFromBody(chunk.model, attribution.requestedModel))\n yield chunk\n }\n }\n}\n\n/** OpenAI models answer reasoning summaries only on the Responses API; chat completions carry none. */\nexport function isOpenAIResponsesModel(model: string): boolean {\n return /^(?:openai\\/)?(?:gpt-|o\\d|chatgpt-)/i.test(model.trim())\n}\n\n/** The chat-completions history the tool loop keeps, as Responses `instructions` and `input` items. */\nexport function toResponsesInput(messages: LoopMessage[]): { instructions?: string; input: Array<Record<string, unknown>> } {\n const instructions: string[] = []\n const input: Array<Record<string, unknown>> = []\n for (const message of messages) {\n const content = typeof message.content === 'string' ? message.content : ''\n if (message.role === 'system') {\n if (content) instructions.push(content)\n } else if (message.role === 'tool') {\n input.push({ type: 'function_call_output', call_id: message.tool_call_id ?? '', output: content })\n } else if (message.role === 'assistant' && message.tool_calls?.length) {\n if (content.trim()) input.push({ role: 'assistant', content })\n for (const call of message.tool_calls) {\n input.push({ type: 'function_call', call_id: call.id, name: call.function.name, arguments: call.function.arguments })\n }\n } else if (message.role === 'user' || message.role === 'assistant') {\n input.push({ role: message.role, content })\n }\n }\n return { ...(instructions.length ? { instructions: instructions.join('\\n\\n') } : {}), input }\n}\n\n/** Chat-completions tool definitions as Responses function tools. */\nfunction toResponsesTools(tools: unknown[] | undefined): Array<Record<string, unknown>> {\n return (tools ?? []).flatMap((tool) => {\n const fn = (tool as { function?: { name?: string; description?: string; parameters?: unknown } }).function\n return fn?.name ? [{ type: 'function', name: fn.name, ...(fn.description ? { description: fn.description } : {}),\n parameters: fn.parameters ?? { type: 'object', properties: {} } }] : []\n })\n}\n\n/**\n * Build a `streamTurn` on the OpenAI Responses API (`POST /responses`, streamed),\n * for OpenAI models whose reasoning reaches a caller only there, as\n * `response.reasoning_summary_text.delta` events. Each Responses event is\n * mapped onto the chat-completions chunk shape {@link toLoopEvents} reads, so\n * text, reasoning, fragmented function calls, usage and served-model\n * attribution behave exactly as on {@link createOpenAICompatStreamTurn}.\n *\n * `extraBody` keys from the chat path are translated: `max_tokens` and\n * `max_completion_tokens` become `max_output_tokens`, and `reasoning_effort`\n * becomes `reasoning.effort` with `summary: 'auto'`. Stateless: earlier tool\n * calls are replayed as `function_call` / `function_call_output` items.\n */\nexport function createOpenAIResponsesTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: LoopMessage[]) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n const { max_tokens: maxTokens, max_completion_tokens: maxCompletion, reasoning_effort: effort, ...rest } = opts.extraBody ?? {}\n const maxOutput = maxCompletion ?? maxTokens\n return (messages) => {\n const { instructions, input } = toResponsesInput(messages)\n const tools = toResponsesTools(opts.tools)\n return toLoopEvents(streamResponses(doFetch, `${base}/responses`, opts.apiKey, {\n model: opts.model,\n ...(instructions ? { instructions } : {}),\n input,\n stream: true,\n store: false,\n ...(tools.length ? { tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...(maxOutput !== undefined ? { max_output_tokens: maxOutput } : {}),\n ...(effort !== undefined ? { reasoning: { effort, summary: 'auto' } } : {}),\n ...rest,\n }, opts.onServedModel ? { requestedModel: opts.model, report: opts.onServedModel } : undefined, opts.headers))\n }\n}\n\ninterface ResponsesEvent {\n type?: string\n delta?: string\n output_index?: number\n summary_index?: number\n item?: { type?: string; call_id?: string; name?: string; arguments?: string }\n response?: { model?: string; usage?: { input_tokens?: number; output_tokens?: number }; error?: { code?: string; message?: string } }\n error?: { code?: string; message?: string }\n message?: string\n}\n\n/** Stream a Responses API call as chat-completions chunks. A failed or errored response throws. */\nasync function* streamResponses(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n attribution?: { requestedModel: string; report: (served: OpenAICompatServedModel) => void },\n headers?: Record<string, string>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { ...headers, Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) throw await httpFailure(res, 'responses')\n let reported = false\n const report = (served: OpenAICompatServedModel) => {\n if (reported || !attribution) return\n reported = true\n attribution.report(served)\n }\n if (attribution) {\n const served = servedFromHeader(res, attribution.requestedModel)\n if (served) report(served)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let lastSummary: string | null = null\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n let event: ResponsesEvent\n try {\n event = JSON.parse(data) as ResponsesEvent\n } catch {\n continue\n }\n switch (event.type) {\n case 'response.output_text.delta':\n if (event.delta) yield { choices: [{ delta: { content: event.delta } }] }\n break\n case 'response.reasoning_summary_text.delta': {\n if (!event.delta) break\n // Separate summary parts the way the model wrote them.\n const part = `${event.output_index ?? 0}:${event.summary_index ?? 0}`\n const lead = lastSummary !== null && lastSummary !== part ? '\\n\\n' : ''\n lastSummary = part\n yield { choices: [{ delta: { reasoning_content: lead + event.delta } }] }\n break\n }\n case 'response.output_item.added':\n if (event.item?.type === 'function_call' && event.item.name) {\n yield { choices: [{ delta: { tool_calls: [{ index: event.output_index ?? 0,\n ...(event.item.call_id ? { id: event.item.call_id } : {}),\n function: { name: event.item.name, arguments: event.item.arguments ?? '' } }] } }] }\n }\n break\n case 'response.function_call_arguments.delta':\n if (event.delta) yield { choices: [{ delta: { tool_calls: [{ index: event.output_index ?? 0, function: { arguments: event.delta } }] } }] }\n break\n case 'response.completed':\n case 'response.incomplete': {\n const model = event.response?.model\n if (attribution && !reported && model) report(servedFromBody(model, attribution.requestedModel))\n const usage = event.response?.usage\n yield { ...(model ? { model } : {}), choices: [],\n ...(usage ? { usage: { prompt_tokens: usage.input_tokens ?? 0, completion_tokens: usage.output_tokens ?? 0 } } : {}) }\n return\n }\n case 'response.failed':\n case 'error': {\n const failure = event.response?.error ?? event.error ?? { message: event.message }\n throw new Error(`OpenAI-compat responses failed: ${(failure?.code ? `${failure.code}: ` : '') + (failure?.message ?? 'no detail')}`.slice(0, 300))\n }\n default:\n break\n }\n }\n }\n}\n","/**\n * Knowledge in every turn: the index of an app's knowledge files, the full\n * text of the small ones, and the rule that keeps answers on them.\n *\n * Measured on Hospitality production (2026-10-11): a Router turn that had to\n * discover files through list/read tools answered 17/17 correctly but needed\n * 3 tool calls (first words 12-17 s), and a turn that skipped the tools\n * answered from model defaults (\"checkout 11:00\" when the file said 12 pm).\n * Putting the text in the prompt removes both failure modes.\n */\n/** The fields read from an agent-runtime workspace-store entry; structural so older runtimes still type-check. */\ninterface KnowledgeEntry { path: string; kind: string; content?: string; bytes?: { size: number } }\n\n/** One knowledge file a turn carries; `text` is null when it is too large or not readable here. */\nexport interface TurnKnowledgeFile {\n path: string\n size: number\n text: string | null\n}\n\nexport interface TurnKnowledgeBudget {\n /** Files at most this many bytes have their text included. Default 8 000. */\n fileBytes?: number\n /** Total text bytes across files. Default 16 000. */\n textBytes?: number\n /** Most files listed. Default 60. */\n maxFiles?: number\n}\n\nconst DEFAULT_FILE_BYTES = 8_000\nconst DEFAULT_TEXT_BYTES = 16_000\nconst DEFAULT_MAX_FILES = 60\n\n/** Apply the byte budget in path order: small files keep their text until the total is spent. */\nexport function budgetTurnKnowledge(files: readonly TurnKnowledgeFile[], budget: TurnKnowledgeBudget = {}): TurnKnowledgeFile[] {\n const fileBytes = budget.fileBytes ?? DEFAULT_FILE_BYTES\n let remaining = budget.textBytes ?? DEFAULT_TEXT_BYTES\n return [...files].sort((a, b) => a.path.localeCompare(b.path)).slice(0, budget.maxFiles ?? DEFAULT_MAX_FILES)\n .map(file => {\n const fits = file.text !== null && file.size <= fileBytes && file.size <= remaining\n if (fits) remaining -= file.size\n return { path: file.path, size: file.size, text: fits ? file.text : null }\n })\n}\n\n/**\n * The prompt lines for a turn's knowledge. `readTool` names the tool that\n * opens a listed file whose text is not shown; without one, such a file is\n * listed by name only.\n */\nexport function turnKnowledgeInstructions(files: readonly TurnKnowledgeFile[], options: { readTool?: string } = {}): string[] {\n const read = options.readTool\n return [\n 'Answer questions about this business or its work (hours, times, policies, codes, passwords, prices, contacts, amenities, procedures) only from the knowledge in these instructions, the knowledge files below, or a tool result. Never give a typical or assumed value, and do not say something is absent unless a source says so.' +\n (read ? ` When a listed file without its text may cover the question, read it with ${read} before answering.` : '') +\n ' When nothing covers it, say you do not have that information and offer to ask the owner.' +\n ' When two sources disagree, give each value with the source that states it and ask the owner which is current; never pick one silently.',\n ...files.map(file => file.text !== null ? `Knowledge file ${file.path}:\\n${file.text}`\n : `Knowledge file ${file.path} (${file.size} bytes, text not shown)${read ? `: read it with ${read} when the question may be covered by it.` : '.'}`),\n // The disagree rule above, read before the files, named a conflict in 10 of 20 bench turns\n // (hospitality, 3 of 5 in production); repeated after the files as a check it named 20 of 20,\n // and 10 of 10 single-file answers stayed unchanged (gemini-3.5-flash-lite, 2026-10-12,\n // hospitality-agent #441).\n ...(files.length > 1 ? ['Before answering from these files, check every file that states the asked fact. When they state different values, give each value with its file name and ask the owner which is current.'] : []),\n ]\n}\n\n/** A workspace store's knowledge files (kind `file` under `prefix`) as turn knowledge, within the budget. */\nexport async function workspaceKnowledgeForTurn(\n store: { list(workspaceId: string, prefix: string): Promise<readonly KnowledgeEntry[]> },\n workspaceId: string,\n options: TurnKnowledgeBudget & { prefix?: string } = {},\n): Promise<TurnKnowledgeFile[]> {\n const entries = await store.list(workspaceId, options.prefix ?? '/files/')\n return budgetTurnKnowledge(entries.filter(entry => entry.kind === 'file').map(entry => ({\n path: entry.path,\n size: entry.content !== undefined ? new TextEncoder().encode(entry.content).byteLength : entry.bytes?.size ?? 0,\n text: entry.content ?? null,\n })), options)\n}\n","/**\n * The language a Router turn replies in, for every agent app: the sender's current\n * message decides, then the conversation's. It names no language on purpose. On\n * 2026-10-12 a hospitality turn whose context said \"saved language for notices is\n * Spanish\" answered 10 of 19 English questions in Spanish; a model follows any\n * language the prompt names. Saved preferences belong to messages the app starts.\n */\nexport function replyLanguageInstruction(): string {\n return 'Reply in the language of the sender\\'s current message, or in the language they ask for in it. ' +\n 'When its language is unclear (a short acknowledgement, a photo, a voice note), continue in the conversation\\'s language. ' +\n 'Change a saved language only when the sender asks for it in this message; a message written in another language is not that request.'\n}\n\n// ISO 639-1. Names come from Intl, in English, Spanish and the language's own name,\n// so a request for any language is recognised without a hand-kept list.\nconst LANGUAGE_CODES = ('aa ab af ak am an ar as av ay az ba be bg bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es ' +\n 'et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ' +\n 'ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu ' +\n 'rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo ' +\n 'xh yi yo za zh zu').split(' ')\n// Always recognised, whatever the runtime's Intl data: generic words and the common\n// languages in English, Spanish and their own names (plus हिंदी beside हिन्दी).\nconst CORE_NAMES = [\n 'language', 'languages', 'idioma', 'idiomas', 'langue', 'lingua', 'língua', 'sprache',\n 'english', 'inglés', 'ingles', 'spanish', 'español', 'espanol', 'castellano', 'french', 'français', 'francés',\n 'portuguese', 'português', 'portugués', 'german', 'deutsch', 'alemán', 'italian', 'italiano', 'japanese', '日本語',\n 'japonés', 'chinese', '中文', '汉语', 'chino', 'korean', '한국어', 'coreano', 'arabic', 'العربية', 'árabe', 'hindi',\n 'हिन्दी', 'हिंदी', 'russian', 'русский', 'ruso', 'dutch', 'nederlands', 'neerlandés', 'hebrew', 'עברית', 'hebreo',\n 'turkish', 'türkçe', 'turco', 'vietnamese', 'tiếng việt', 'vietnamita', 'catalan', 'català', 'catalán',\n]\n// Language names that are everyday words: \"ya he ido\", \"él cree\", \"que me venda\",\n// \"odia\", \"from afar\", \"ewe\", \"Latin America\", \"shoe polish\".\nconst COMMON_WORDS = new Set(['ido', 'cree', 'venda', 'odia', 'afar', 'ewe', 'latin', 'polish'])\n\nlet patterns: RegExp[] | undefined\nfunction languagePatterns(): RegExp[] {\n if (patterns) return patterns\n const names = new Set(CORE_NAMES)\n for (const locale of ['en', 'es', ...LANGUAGE_CODES]) {\n let display: Intl.DisplayNames\n // A runtime without Intl language data still has CORE_NAMES.\n try { display = new Intl.DisplayNames([locale], { type: 'language' }) } catch { continue }\n for (const code of locale === 'en' || locale === 'es' ? LANGUAGE_CODES : [locale]) {\n const name = display.of(code)?.toLowerCase()\n if (name && name !== code && name.length >= 3 && !COMMON_WORDS.has(name)) names.add(name)\n }\n }\n // Latin-script names need word edges (\"pedido\" must not match Ido); scripts written\n // without spaces, such as 日本語, match anywhere.\n return patterns = [...names].map(name => {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n return /^[\\p{Script=Latin}\\s'-]+$/u.test(name) ? new RegExp(`(?<!\\\\p{L})${escaped}(?!\\\\p{L})`, 'iu') : new RegExp(escaped, 'iu')\n })\n}\n\n/**\n * True when the message names a language. A tool that saves a reply-language\n * preference requires it, using its `context.userMessage`, so the model cannot\n * save a language the sender never asked for.\n */\nexport function asksForLanguage(message: string | undefined): boolean {\n return typeof message === 'string' && languagePatterns().some(pattern => pattern.test(message))\n}\n","/**\n * One conversational turn answered by a model through the Tangle Router, with\n * the app's own tools executed in-process — no sandbox in the path.\n *\n * This is the default runtime for conversational turns in an agent app: the\n * product supplies the principal's tools, the profile prompt and the\n * conversation; the engine streams words as the model writes them, runs tool\n * calls through the bounded `streamAppToolLoop`, and returns one outcome.\n * Long, file or code work belongs to the sandbox, reached through a tool the\n * product adds to `tools`.\n *\n * Every failure is an outcome, never a throw. A durable turn owner retries\n * thrown errors, and a retry after a tool ran would repeat that tool's effect\n * (\"never re-run a turn automatically\", docs/agent-app-lessons.md). The only\n * automatic repeat is one model call that failed before any word or tool.\n *\n * Bounded paid lanes run this same engine inside a protected Router model grant\n * (`runProtectedRouterTurn` in `./protected-router-turn`, outside this entry so a\n * Worker bundle never pulls the grant client).\n */\nimport { streamAppToolLoop, type LoopEvent, type LoopMessage, type LoopToolCall, type ToolLoopStopReason } from './loop'\nimport { DEFAULT_TANGLE_ROUTER_BASE_URL } from './model'\nimport { createOpenAICompatCompletionTurn, createOpenAICompatStreamTurn, createOpenAIResponsesTurn, isOpenAIResponsesModel } from './openai-stream'\nimport { turnKnowledgeInstructions, type TurnKnowledgeFile } from './turn-knowledge'\nimport { replyLanguageInstruction } from './reply-language'\nimport { placeExecution, type PlacementRecord, type VacuousAxis } from '@tangle-network/agent-runtime/placement'\nexport { DURABLE_HOP_BUDGET_MS, turnCheckpointPath, workspaceTurnCheckpoints, type CheckpointWorkspaceStore, type TurnCheckpointStore } from './turn-continuation'\nexport { budgetTurnKnowledge, turnKnowledgeInstructions, workspaceKnowledgeForTurn, type TurnKnowledgeBudget, type TurnKnowledgeFile } from './turn-knowledge'\nexport { asksForLanguage, replyLanguageInstruction } from './reply-language'\n\n/** A tool the turn may call. Structurally an `McpToolDefinition` whose env is already bound. */\nexport interface RouterTurnTool {\n name: string\n description: string\n /** JSON Schema for the arguments object. */\n inputSchema: Record<string, unknown>\n run(args: Record<string, unknown>, context: RouterTurnToolContext): Promise<unknown>\n}\n\n/** What a running tool gets besides its arguments. */\nexport interface RouterTurnToolContext {\n /**\n * Report a typed side event while the tool runs (a graph edit, a file written, a\n * progress step). It streams at once as a `tool_event` and is recorded in the\n * outcome's `toolEvents`. Events sent after the call settles are dropped.\n */\n emit(kind: string, data?: unknown): void\n /** The sender's message this turn answers, so a tool can act only on what was asked (see {@link asksForLanguage}). */\n userMessage: string\n}\n\n/** One side event a tool reported while it ran. */\nexport interface RouterTurnToolEvent {\n callId: string\n toolName: string\n kind: string\n data: unknown\n at: number\n}\n\n/** What the turn is doing, in the order it happens, for a live view. */\nexport type RouterTurnEvent =\n | { type: 'text'; segment: number; delta: string }\n /** Hidden reasoning the model streamed (`reasoning_content` / `thinking`), for a thinking view. Never part of the answer. */\n | { type: 'reasoning'; delta: string }\n | { type: 'tool'; callId: string; toolName: string; status: 'running'; input: Record<string, unknown>; startedAt: number }\n | { type: 'tool'; callId: string; toolName: string; status: 'completed'; output: unknown; startedAt: number; endedAt: number }\n | { type: 'tool'; callId: string; toolName: string; status: 'error'; error: { code: string; message: string }; startedAt: number; endedAt: number }\n | { type: 'usage'; promptTokens: number; completionTokens: number }\n | ({ type: 'tool_event' } & RouterTurnToolEvent)\n\nexport interface RouterTurnFailure {\n /** `model_http_<status>`, `model_unavailable`, `model_empty`, `turn_<stopReason>`, `turn_aborted`,\n * `turn_tool_limit`, `router_unconfigured` or `placement_fallback_refused`; under a protected grant also the `grant_*` and\n * `settlement_*` codes of `./protected-router-turn`. */\n code: string\n status?: number\n message: string\n /** The model service's own error text, at most 1000 characters (e.g. `key_budget_exhausted`), so an app can\n * tell a spend cap from a missing balance. Never a credential. */\n detail?: string\n}\n\nexport interface RouterTurnOutcome {\n /** Every text segment the model wrote, joined by blank lines. */\n text: string\n /** The model the router reports having served, when it said. */\n servedModel: string | null\n toolCalls: Array<{ callId: string; toolName: string; ok: boolean }>\n /** Token use summed over every model call in the turn, as the router reported it. */\n usage: { promptTokens: number; completionTokens: number; modelCalls: number }\n /** Reasoning the turn streamed (deltas and characters). Zero when the model sent none; absent when no model was called. */\n reasoning?: { deltas: number; chars: number }\n /** Every side event the turn's tools reported, in order (at most 500). */\n toolEvents: RouterTurnToolEvent[]\n failure?: RouterTurnFailure\n /**\n * Set instead of a failure when the turn reached its budget at a clean point\n * (every tool call answered). The turn is not over: persist this and call\n * `runRouterTurn` again with `continueFrom` in a durable process to finish it.\n * `text` holds everything the turn has written so far, across hops.\n */\n continuation?: RouterTurnContinuation\n /** Where the turn was placed, when the caller asked for placement. */\n placement?: PlacementRecord\n}\n\n/** A checkpointed Router turn: the conversation after the system prompt, ready to continue. JSON-safe. */\nexport interface RouterTurnContinuation {\n messages: LoopMessage[]\n /** Hops this turn has already run, including this one. */\n hops: number\n /** Reply text every hop so far wrote; the final hop's outcome `text` includes it. */\n text: string\n}\n\n/**\n * What placement reads for a Router turn: the resolved profile, the tools the\n * turn would hold, and a model the sender chose. Declared data only.\n */\nexport interface RouterTurnPlacementInput {\n profile: object\n toolNames?: readonly string[]\n /** A model other than the profile's needs the sandbox to choose between them. */\n requestedModel?: string\n /** Deny-only permissions grant nothing a Router turn lacks. Default: ['permissions']. */\n vacuousAxes?: readonly VacuousAxis[]\n /** The app inlines the profile's skill text into the prompt. */\n inlineSkills?: boolean\n}\n\n/**\n * Where one conversational turn runs: `inference` or `loop` run here in\n * `runRouterTurn`; `sandbox` means the profile or request needs a computer\n * (a harness, files, MCP servers, skills that are not inlined, another model).\n * Throws `PlacementRefusedError` when no tier can meet every requirement.\n */\nexport function placeRouterTurn(input: RouterTurnPlacementInput): PlacementRecord {\n return placeExecution({\n profile: input.profile,\n requirements: {\n ...(input.toolNames?.length ? { tools: [...input.toolNames] } : {}),\n ...(input.requestedModel ? { model: input.requestedModel } : {}),\n },\n context: { interactive: false, inferenceAvailable: true, loopAvailable: true },\n vacuousAxes: input.vacuousAxes ?? ['permissions'],\n ...(input.inlineSkills ? { inlineSkills: true } : {}),\n })\n}\n\n/** One tool call's result as the model reads it. */\nexport type RouterTurnToolOutcome = { ok: true; result: unknown } | { ok: false; code: string; message: string }\n\nexport interface RouterTurnOptions {\n apiKey: string\n model: string\n /** Defaults to the Tangle Router. */\n baseUrl?: string\n systemPrompt: string\n userMessage: string\n /** Earlier turns. Plain text messages, or OpenAI function-calling history (an assistant message\n * carrying `tool_calls` and the `tool` messages answering them) so the model sees what it did. */\n history?: Array<{ role: 'user' | 'assistant'; content: string } | LoopMessage>\n tools?: RouterTurnTool[]\n /** Wall-clock budget in ms for this hop. Default 90 s. At the budget the outcome carries a\n * `continuation` instead of failing; the model call or tool already running finishes first. */\n budgetMs?: number\n /** Continue a checkpointed turn; `userMessage` and `history` are ignored. */\n continueFrom?: RouterTurnContinuation\n /** Runaway backstop on model turns. Default 12. */\n maxToolTurns?: number\n maxTokens?: number\n /** Sent as `x-tangle-trace-id` so the router's generation joins the app's trace. */\n traceId?: string\n fetchImpl?: typeof fetch\n onEvent?: (event: RouterTurnEvent) => void\n /** Map a thrown tool error to what the model reads. Default: an error carrying string `code`\n * (and numeric `status`) is reported with its message; anything else is a generic failure. */\n toolError?: (error: unknown) => { code: string; message: string; status?: number } | null\n /** Called for a tool failure the default mapping hides from the model, for the app's logs. */\n onToolFailure?: (toolName: string, error: unknown) => void\n /** Cancels the turn: the model call in flight is aborted, no further tool starts, and the\n * outcome carries `turn_aborted`. Effects of tools that already ran are kept. */\n signal?: AbortSignal\n /** Default true: one repeat of a model call that failed before any word or tool, and of a blank\n * answer. Pass false when a second billable call needs the caller's own decision; the failure is\n * then returned as it happened. */\n repeat?: boolean\n /** Default true. False takes each model call as one buffered completion instead of a stream. */\n stream?: boolean\n /** Sent as `reasoning_effort`. */\n reasoningEffort?: string\n /** Sent as `max_completion_tokens`: one model call's visible and reasoning tokens together. */\n maxCompletionTokens?: number\n /** At most this many tool calls run. The next one is refused before it runs and the turn ends\n * with `turn_tool_limit`. */\n maxToolCalls?: number\n /** Replace the text the model reads for one tool result. `toolName` is the app's name. */\n renderToolResult?: (toolName: string, outcome: RouterTurnToolOutcome) => string\n /**\n * Place the turn before any model call, using `model` as the executed model rather than\n * the optional `requestedModel` hint. A turn placed in the sandbox returns at once\n * with `placement` and failure `placement_sandbox`, so the app runs it there instead.\n */\n placement?: Omit<RouterTurnPlacementInput, 'toolNames'>\n temperature?: number\n /** Models tried in order when a model call fails before any word or tool, or answers blank. Without it,\n * that safe repeat uses `model` again. `repeat: false` turns both off. */\n fallbackModels?: readonly string[]\n /** Resend one model request that answered 408, 425, 429 or 5xx, or failed to connect, before its stream\n * began, with exponential backoff (a `retry-after` up to 8 s is honoured). `attempts` counts the first\n * send (default 1). `timeoutMs` bounds each attempt, stream included. */\n retry?: { attempts?: number; timeoutMs?: number; baseDelayMs?: number }\n /** Characters of one tool result the model reads when no `renderToolResult` is given. Default 12 000. */\n toolResultLimit?: number\n /**\n * Which model API carries the turn. `auto` (default) uses the Responses API for an OpenAI model asked to\n * reason (`reasoningEffort` other than `none`), since OpenAI returns reasoning summaries only there, and\n * chat completions otherwise. A buffered turn (`stream: false`) always uses chat completions.\n */\n api?: 'auto' | 'chat' | 'responses'\n}\n\nconst DEFAULT_BUDGET_MS = 90_000\nconst RETRY_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])\n// The Router names a refusal no resend of the same model can fix: every\n// credential for it is out of quota, rejected or suspended (tangle-router\n// `X-Tangle-Failure-Category`). The turn moves to its next fallback model at once.\nconst DEAD_CREDENTIAL_CATEGORIES = new Set(['provider_quota_exhausted', 'provider_key_invalid', 'provider_account_suspended'])\nconst MAX_RETRY_DELAY_MS = 8_000\nconst TOOL_EVENT_LIMIT = 500\n/** Marks a tool's typed event inside a loop progress update. */\nconst TOOL_EVENT = '__routerTurnToolEvent'\nconst TOOL_RESULT_LIMIT = 12_000\n\n/** OpenAI-compatible function names allow only `[A-Za-z0-9_-]{1,64}`; app tool names often use dots. */\nexport function routerFunctionName(name: string): string {\n return name.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64)\n}\n\n/**\n * The lean system prompt for a Router turn: the profile's system prompt, appended prompt and\n * instructions, the turn's knowledge files, then caller context, with tool names rewritten to the function\n * names the model actually calls.\n */\nexport function composeRouterTurnPrompt(\n profile: { prompt?: { systemPrompt?: string; appendSystemPrompt?: string; instructions?: readonly string[] } },\n options: {\n context?: readonly string[]\n toolNames?: readonly string[]\n /** Knowledge files every turn carries; see {@link turnKnowledgeInstructions}. */\n knowledge?: { files: readonly TurnKnowledgeFile[]; readTool?: string }\n } = {},\n): string {\n const knowledge = options.knowledge ? turnKnowledgeInstructions(options.knowledge.files, { readTool: options.knowledge.readTool }) : []\n const text = [profile.prompt?.systemPrompt ?? '', profile.prompt?.appendSystemPrompt ?? '', ...(profile.prompt?.instructions ?? []), ...knowledge,\n replyLanguageInstruction(), ...(options.context ?? [])]\n .filter(part => part.trim()).join('\\n\\n')\n return [...(options.toolNames ?? [])].sort((a, b) => b.length - a.length)\n .reduce((out, name) => out.split(name).join(routerFunctionName(name)), text)\n}\n\nconst joinText = (parts: string[]) => parts.map(part => part.trim()).filter(Boolean).join('\\n\\n')\n\nfunction defaultToolError(error: unknown): { code: string; message: string; status?: number } | null {\n const value = error as { code?: unknown; status?: unknown; message?: unknown; issues?: unknown }\n if (Array.isArray(value?.issues)) {\n return { code: 'invalid_arguments', message: JSON.stringify(value.issues).slice(0, 600) }\n }\n if (typeof value?.code === 'string' && typeof value.message === 'string') {\n return { code: value.code, message: value.message, ...(typeof value.status === 'number' ? { status: value.status } : {}) }\n }\n return null\n}\n\nfunction renderResult(outcome: RouterTurnToolOutcome, limit = TOOL_RESULT_LIMIT): string {\n if (!outcome.ok) return `Error (${outcome.code}): ${outcome.message}`\n let text: string\n try { text = typeof outcome.result === 'string' ? outcome.result : JSON.stringify(outcome.result ?? null) }\n catch { text = String(outcome.result) }\n return text.length > limit ? `${text.slice(0, limit)}…` : text\n}\n\nexport async function runRouterTurn(options: RouterTurnOptions): Promise<RouterTurnOutcome> {\n // A continued turn answers the last user message in its checkpoint.\n const lastUser = options.continueFrom?.messages.filter(message => message.role === 'user').at(-1)?.content\n const currentUserMessage = options.continueFrom ? (typeof lastUser === 'string' ? lastUser : '') : options.userMessage\n const placement = options.placement\n ? placeRouterTurn({ ...options.placement, requestedModel: options.model, toolNames: (options.tools ?? []).map(tool => tool.name) })\n : undefined\n if (placement?.chosen === 'sandbox') {\n return { text: '', servedModel: null, toolCalls: [], usage: { promptTokens: 0, completionTokens: 0, modelCalls: 0 }, toolEvents: [], placement,\n failure: { code: 'placement_sandbox', message: placement.reason } }\n }\n const { apiKey } = options\n if (!apiKey) {\n return { text: '', servedModel: null, toolCalls: [], usage: { promptTokens: 0, completionTokens: 0, modelCalls: 0 }, toolEvents: [],\n ...(placement ? { placement } : {}),\n failure: { code: 'router_unconfigured', message: 'The model credential is not configured.' } }\n }\n const emit = options.onEvent ?? (() => {})\n const { signal } = options\n // A cancelled turn sends nothing more, whatever the injected fetch does with the signal.\n const attempts = Math.max(1, Math.floor(options.retry?.attempts ?? 1))\n const attemptTimeoutMs = options.retry?.timeoutMs\n const baseDelayMs = options.retry?.baseDelayMs ?? 500\n const pause = (ms: number) => new Promise<void>((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason)\n const timer = setTimeout(resolve, ms)\n signal?.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason) }, { once: true })\n })\n const fetchImpl: typeof fetch | undefined = signal || attempts > 1 || attemptTimeoutMs\n ? async (input, init) => {\n for (let attempt = 1; ; attempt++) {\n signal?.throwIfAborted()\n const signals = [signal, attemptTimeoutMs ? AbortSignal.timeout(attemptTimeoutMs) : undefined]\n .filter((value): value is AbortSignal => Boolean(value))\n const attemptSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0]\n const backoff = () => Math.min(MAX_RETRY_DELAY_MS, baseDelayMs * 2 ** (attempt - 1)) * (0.5 + Math.random() / 2)\n let response: Response\n try {\n // A cancelled turn sends nothing more, whatever the injected fetch does with the signal.\n response = await (options.fetchImpl ?? fetch)(input, { ...init, ...(attemptSignal ? { signal: attemptSignal } : {}) })\n } catch (error) {\n if (signal?.aborted || attempt >= attempts) throw error\n await pause(backoff())\n continue\n }\n // Nothing has streamed yet, so a resend repeats no word and no tool.\n if (attempt >= attempts || !RETRY_STATUS.has(response.status)) return response\n if (DEAD_CREDENTIAL_CATEGORIES.has(response.headers.get('x-tangle-failure-category') ?? '')) return response\n const retryAfter = Number(response.headers.get('retry-after'))\n await response.body?.cancel().catch(() => {})\n await pause(Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter * 1000, MAX_RETRY_DELAY_MS) : backoff())\n }\n }\n : options.fetchImpl\n const extraBody = {\n ...(options.maxTokens ? { max_tokens: options.maxTokens } : {}),\n ...(options.maxCompletionTokens ? { max_completion_tokens: options.maxCompletionTokens } : {}),\n ...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),\n }\n const byFunction = new Map<string, RouterTurnTool>()\n for (const tool of options.tools ?? []) {\n const fn = routerFunctionName(tool.name)\n if (!byFunction.has(fn)) byFunction.set(fn, tool)\n }\n let servedModel: string | null = null\n const reasoningAsked = Boolean(options.reasoningEffort) && options.reasoningEffort !== 'none'\n const useResponses = (model: string) => options.stream !== false &&\n (options.api === 'responses' || ((options.api ?? 'auto') === 'auto' && reasoningAsked && isOpenAIResponsesModel(model)))\n const streamTurnFor = (model: string) => (options.stream === false ? createOpenAICompatCompletionTurn\n : useResponses(model) ? createOpenAIResponsesTurn : createOpenAICompatStreamTurn)({\n baseUrl: options.baseUrl ?? DEFAULT_TANGLE_ROUTER_BASE_URL,\n apiKey,\n model,\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\n tools: [...byFunction.entries()].map(([name, tool]) => ({\n type: 'function', function: { name, description: tool.description, parameters: tool.inputSchema },\n })),\n ...(Object.keys(extraBody).length ? { extraBody } : {}),\n ...(options.traceId ? { headers: { 'x-tangle-trace-id': options.traceId } } : {}),\n ...(fetchImpl ? { fetchImpl } : {}),\n onServedModel: served => { servedModel = served.servedModel },\n })\n const segments: string[] = []\n const usage = { promptTokens: 0, completionTokens: 0, modelCalls: 0 }\n // How much reasoning the turn streamed: the number an app logs to measure reasoning coverage.\n const reasoning = { deltas: 0, chars: 0 }\n const toolCalls: RouterTurnOutcome['toolCalls'] = []\n const toolEvents: RouterTurnToolEvent[] = []\n const started = new Map<string, number>()\n let segmentOpen = false\n let toolsRan = 0\n let checkpoint: LoopMessage[] | null = null\n let toolLimitReached = false\n const deadline = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS)\n const callIdOf = (id: string | undefined, toolName: string) => id ?? `${toolName}-${started.size}`\n\n let placementFailure: RouterTurnFailure | undefined\n const run = async (model: string): Promise<ToolLoopStopReason | 'tool_limit' | 'placement_refused'> => {\n // A safe model retry still cannot change execution tier after this turn began.\n if (placement && options.placement && model !== options.model) {\n const next = placeRouterTurn({ ...options.placement, requestedModel: model,\n toolNames: (options.tools ?? []).map(tool => tool.name) })\n if (next.chosen !== placement.chosen) {\n placementFailure = { code: 'placement_fallback_refused', message: `The fallback model cannot run in this turn's placement: ${next.reason}` }\n return 'placement_refused'\n }\n }\n const streamTurn = streamTurnFor(model)\n for await (const step of streamAppToolLoop<LoopEvent>({\n systemPrompt: options.systemPrompt,\n ...(options.continueFrom\n ? { priorMessages: options.continueFrom.messages }\n : { userMessage: options.userMessage, priorMessages: (options.history ?? []) as LoopMessage[] }),\n streamTurn,\n extractText: event => event.type === 'text' ? event.text : '',\n extractToolCall: event => event.type === 'tool_call' ? event.call : null,\n isExecutableTool: name => byFunction.has(name),\n deadlineMs: deadline,\n maxToolTurns: options.maxToolTurns ?? 12,\n labelFor: call => byFunction.get(call.toolName)?.name ?? call.toolName,\n renderResult: (label, outcome) => options.renderToolResult ? options.renderToolResult(label, outcome) : renderResult(outcome, options.toolResultLimit),\n executeToolCall: async (call: LoopToolCall, context) => {\n const tool = byFunction.get(call.toolName)\n if (!tool) return { ok: false, code: 'unknown_tool', message: `No tool named ${call.toolName}` }\n if (signal?.aborted) return { ok: false, code: 'aborted', message: 'The turn was cancelled before this tool ran.' }\n if (options.maxToolCalls !== undefined && toolsRan >= options.maxToolCalls) {\n toolLimitReached = true\n return { ok: false, code: 'tool_limit', message: 'The turn reached its tool-call limit.' }\n }\n toolsRan++\n // A tool's typed event rides the loop's progress channel, so it streams in order before the result.\n const emitEvent = (kind: string, data?: unknown) => context?.progress({ data: { [TOOL_EVENT]: kind, data } })\n try {\n return { ok: true, result: await tool.run(call.args, { emit: emitEvent, userMessage: currentUserMessage }) }\n } catch (error) {\n const mapped = (options.toolError ?? defaultToolError)(error)\n if (mapped) return { ok: false, ...mapped }\n options.onToolFailure?.(tool.name, error)\n return { ok: false, code: 'tool_failed', message: 'The tool failed; tell the user what could not be completed.' }\n }\n },\n })) {\n if (step.kind === 'event') {\n const event = step.event\n if (event.type === 'text' && event.text) {\n if (!segmentOpen) { segments.push(''); segmentOpen = true }\n segments[segments.length - 1] += event.text\n emit({ type: 'text', segment: segments.length, delta: event.text })\n } else if (event.type === 'reasoning' && event.text) {\n reasoning.deltas++\n reasoning.chars += event.text.length\n emit({ type: 'reasoning', delta: event.text })\n } else if (event.type === 'usage') {\n usage.promptTokens += event.usage.promptTokens\n usage.completionTokens += event.usage.completionTokens\n usage.modelCalls++\n emit({ type: 'usage', ...event.usage })\n } else if (event.type === 'tool_call') {\n segmentOpen = false\n const tool = byFunction.get(event.call.toolName)\n const callId = callIdOf(event.call.toolCallId, event.call.toolName)\n started.set(callId, Date.now())\n emit({ type: 'tool', callId, toolName: tool?.name ?? event.call.toolName, status: 'running',\n input: event.call.args, startedAt: started.get(callId)! })\n }\n } else if (step.kind === 'tool_progress') {\n const tool = byFunction.get(step.toolName)\n const marked = step.update.data as { [TOOL_EVENT]?: unknown; data?: unknown } | undefined\n // A tool's own emit carries its kind; plain loop progress (a step count, a message) is kind `progress`.\n const typed = typeof marked?.[TOOL_EVENT] === 'string'\n const event: RouterTurnToolEvent = {\n callId: callIdOf(step.toolCallId, step.toolName), toolName: tool?.name ?? step.toolName,\n kind: typed ? marked![TOOL_EVENT] as string : 'progress',\n data: typed ? marked!.data ?? null : step.update, at: Date.now(),\n }\n if (toolEvents.length < TOOL_EVENT_LIMIT) toolEvents.push(event)\n emit({ type: 'tool_event', ...event })\n } else if (step.kind === 'tool_result') {\n const tool = byFunction.get(step.toolName)\n const callId = callIdOf(step.toolCallId, step.toolName)\n const startedAt = started.get(callId) ?? Date.now()\n const toolName = tool?.name ?? step.toolName\n toolCalls.push({ callId, toolName, ok: step.outcome.ok })\n emit(step.outcome.ok\n ? { type: 'tool', callId, toolName, status: 'completed', output: step.outcome.result, startedAt, endedAt: Date.now() }\n : { type: 'tool', callId, toolName, status: 'error', startedAt, endedAt: Date.now(),\n error: { code: step.outcome.code, message: step.outcome.message } })\n // The refused call never ran; the turn ends here rather than asking the model again.\n if (toolLimitReached) return 'tool_limit'\n } else if (step.kind === 'capped') {\n if (step.stopReason === 'deadline' && step.messages) checkpoint = step.messages\n return step.stopReason\n }\n }\n return 'completed'\n }\n\n const outcome = (failure?: RouterTurnFailure): RouterTurnOutcome => ({\n text: joinText([options.continueFrom?.text ?? '', ...segments]),\n servedModel, toolCalls, usage: { ...usage }, reasoning: { ...reasoning }, toolEvents: [...toolEvents], ...(placement ? { placement } : {}), ...(failure ? { failure } : {}),\n })\n const repeat = options.repeat !== false\n // The safe repeat walks this chain: the next fallback model, or the same model once.\n const repeats = !repeat ? [] : options.fallbackModels?.length ? [...options.fallbackModels] : [options.model]\n let stop: ToolLoopStopReason | 'tool_limit' | 'placement_refused'\n try {\n try {\n stop = await run(options.model)\n } catch (error) {\n // Safe to repeat only while nothing was said and no tool ran.\n const next = repeats.shift()\n if (next === undefined || signal?.aborted || toolsRan > 0 || segments.length > 0) throw error\n stop = await run(next)\n }\n } catch (error) {\n if (signal?.aborted) return outcome({ code: 'turn_aborted', message: 'The turn was cancelled.' })\n const status = (error as { status?: unknown }).status\n // The stream adapter puts the service's own text after the status; keep it, short, for the app's error categories.\n const detail = error instanceof Error ? error.message.match(/\\(HTTP \\d+\\): ([\\s\\S]*)$/)?.[1]?.slice(0, 1000) : undefined\n return outcome(typeof status === 'number'\n ? { code: `model_http_${status}`, status, message: 'The model service did not answer.', ...(detail ? { detail } : {}) }\n : { code: 'model_unavailable', message: 'The model service did not answer.' })\n }\n if (placementFailure) return outcome(placementFailure)\n if (stop === 'deadline' && checkpoint) {\n return { ...outcome(), continuation: { messages: checkpoint,\n hops: (options.continueFrom?.hops ?? 0) + 1,\n text: outcome().text } }\n }\n if (stop !== 'completed') return outcome({ code: `turn_${stop}`, message: 'The reply was stopped before it finished.' })\n // A model can end its turn with neither a word nor a tool call (gemini-2.5-flash did on 8 of\n // 53 \"remember\" turns, 2026-10-11). Nothing was said and nothing ran, so one repeat is safe;\n // an answer still blank is a typed failure, never a successful empty reply.\n if (!segments.some(part => part.trim()) && !options.continueFrom?.text.trim()) {\n const next = repeats.shift()\n if (toolsRan === 0 && next !== undefined) {\n try { stop = await run(next) } catch { stop = 'completed' }\n if (placementFailure) return outcome(placementFailure)\n if (stop !== 'completed') return outcome({ code: `turn_${stop}`, message: 'The reply was stopped before it finished.' })\n }\n if (!segments.some(part => part.trim())) {\n return outcome({ code: 'model_empty', message: 'The model finished without writing a reply.' })\n }\n }\n return outcome()\n}\n","/**\n * A Router turn never fails because of time. `runRouterTurn` stops at its hop\n * budget at a clean point and returns a `continuation`; the host saves it here\n * and runs the next hop in a durable process (a Durable Object alarm or a Queue\n * consumer: 15 minutes of wall time each), posting into the same conversation.\n *\n * Checkpoints live in the app's workspace store under `/turns/<turnId>/`, which\n * a Files view (`/files/`) never lists.\n */\nimport type { RouterTurnContinuation } from './router-turn'\n\n/** Wall time a durable hop may plan for: a Durable Object alarm or Queue consumer gets 15 minutes. */\nexport const DURABLE_HOP_BUDGET_MS = 14 * 60_000\n\n/** Where a turn's checkpoint waits between hops. */\nexport interface TurnCheckpointStore {\n save(turnId: string, continuation: RouterTurnContinuation): Promise<void>\n load(turnId: string): Promise<RouterTurnContinuation | null>\n clear(turnId: string): Promise<void>\n}\n\ninterface CheckpointEntry { content?: string; revision: number }\n\n/** The fields of an agent-runtime `WorkspaceStore` a checkpoint needs; structural so any adapter fits. */\nexport interface CheckpointWorkspaceStore {\n get(workspaceId: string, path: string): Promise<CheckpointEntry | null>\n put(input: { workspaceId: string; path: string; kind: 'file'; ifRevision: number | null; actor: string; content: string }): Promise<unknown>\n delete(input: { workspaceId: string; path: string; actor: string }): Promise<unknown>\n}\n\nexport function turnCheckpointPath(turnId: string): string {\n return `/turns/${encodeURIComponent(turnId)}/checkpoint.json`\n}\n\n/** Turn checkpoints in a workspace store, written as `actor`. */\nexport function workspaceTurnCheckpoints(store: CheckpointWorkspaceStore, workspaceId: string, actor: string): TurnCheckpointStore {\n return {\n async save(turnId, continuation) {\n const path = turnCheckpointPath(turnId)\n const current = await store.get(workspaceId, path)\n await store.put({ workspaceId, path, kind: 'file', ifRevision: current?.revision ?? null, actor,\n content: JSON.stringify(continuation) })\n },\n async load(turnId) {\n const entry = await store.get(workspaceId, turnCheckpointPath(turnId))\n return entry?.content ? JSON.parse(entry.content) as RouterTurnContinuation : null\n },\n async clear(turnId) {\n await store.delete({ workspaceId, path: turnCheckpointPath(turnId), actor })\n },\n }\n}\n"],"mappings":"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","names":["outcome"]}
|