@tangle-network/agent-app 0.60.198 → 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-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/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,229 @@
|
|
|
1
|
+
// src/turn-trace/recorder.ts
|
|
2
|
+
var TURN_SPAN_KINDS = ["admission", "router", "model", "network", "tool", "sandbox", "persist", "other"];
|
|
3
|
+
var TRACE_ID_PATTERN = /^[\w-]{8,200}$/;
|
|
4
|
+
var MAX_ATTRS = 48;
|
|
5
|
+
var MAX_ATTR_STRING = 200;
|
|
6
|
+
function isTraceId(value) {
|
|
7
|
+
return TRACE_ID_PATTERN.test(value);
|
|
8
|
+
}
|
|
9
|
+
function turnTraceId(executionId) {
|
|
10
|
+
if (TRACE_ID_PATTERN.test(executionId)) return executionId;
|
|
11
|
+
const id = executionId.replace(/[^\w-]/g, "_").slice(0, 200);
|
|
12
|
+
return id.length >= 8 ? id : id.padEnd(8, "_");
|
|
13
|
+
}
|
|
14
|
+
function randomHex(bytes) {
|
|
15
|
+
const out = new Uint8Array(bytes);
|
|
16
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") globalThis.crypto.getRandomValues(out);
|
|
17
|
+
else for (let i = 0; i < bytes; i++) out[i] = Math.floor(Math.random() * 256);
|
|
18
|
+
return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
19
|
+
}
|
|
20
|
+
function boundedAttrs(attrs) {
|
|
21
|
+
const out = {};
|
|
22
|
+
let count = 0;
|
|
23
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
24
|
+
if (count >= MAX_ATTRS) break;
|
|
25
|
+
if (typeof value === "number" && !Number.isFinite(value)) continue;
|
|
26
|
+
out[key.slice(0, 80)] = typeof value === "string" ? value.slice(0, MAX_ATTR_STRING) : value;
|
|
27
|
+
count++;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function errorText(error) {
|
|
32
|
+
if (error instanceof Error) return (error.name === "Error" ? error.message : `${error.name}: ${error.message}`).slice(0, MAX_ATTR_STRING);
|
|
33
|
+
return String(error).slice(0, MAX_ATTR_STRING);
|
|
34
|
+
}
|
|
35
|
+
var zeroKinds = () => ({
|
|
36
|
+
admission: 0,
|
|
37
|
+
router: 0,
|
|
38
|
+
model: 0,
|
|
39
|
+
network: 0,
|
|
40
|
+
tool: 0,
|
|
41
|
+
sandbox: 0,
|
|
42
|
+
persist: 0,
|
|
43
|
+
other: 0
|
|
44
|
+
});
|
|
45
|
+
var round1 = (value) => Math.round(value * 10) / 10;
|
|
46
|
+
function summarizeTurnSpans(spans, rootSpanId, startMs, endMs, traceId, dropped = 0) {
|
|
47
|
+
const wallMs = Math.max(0, endMs - startMs);
|
|
48
|
+
const parent = new Map(spans.map((span) => [span.spanId, span.parentSpanId]));
|
|
49
|
+
const depthOf = (id) => {
|
|
50
|
+
let depth = 0;
|
|
51
|
+
for (let at = parent.get(id); at !== void 0 && at !== rootSpanId; at = parent.get(at)) {
|
|
52
|
+
depth++;
|
|
53
|
+
if (depth > 64) break;
|
|
54
|
+
}
|
|
55
|
+
return depth;
|
|
56
|
+
};
|
|
57
|
+
const clipped = spans.map((span) => ({ span, start: Math.max(startMs, span.startMs), end: Math.min(endMs, span.endMs), depth: depthOf(span.spanId) })).filter((entry) => entry.end > entry.start);
|
|
58
|
+
const points = [.../* @__PURE__ */ new Set([startMs, endMs, ...clipped.flatMap((entry) => [entry.start, entry.end])])].sort((a, b) => a - b);
|
|
59
|
+
const byKind = zeroKinds();
|
|
60
|
+
let coveredMs = 0;
|
|
61
|
+
let largestGapMs = 0;
|
|
62
|
+
let gap = 0;
|
|
63
|
+
for (let i = 0; i + 1 < points.length; i++) {
|
|
64
|
+
const from = points[i];
|
|
65
|
+
const to = points[i + 1];
|
|
66
|
+
let deepest;
|
|
67
|
+
for (const entry of clipped) {
|
|
68
|
+
if (entry.start <= from && entry.end >= to && (!deepest || entry.depth > deepest.depth || entry.depth === deepest.depth && entry.start > deepest.start)) deepest = entry;
|
|
69
|
+
}
|
|
70
|
+
if (deepest) {
|
|
71
|
+
byKind[deepest.span.kind] += to - from;
|
|
72
|
+
coveredMs += to - from;
|
|
73
|
+
gap = 0;
|
|
74
|
+
} else {
|
|
75
|
+
gap += to - from;
|
|
76
|
+
largestGapMs = Math.max(largestGapMs, gap);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const kind of TURN_SPAN_KINDS) byKind[kind] = round1(byKind[kind]);
|
|
80
|
+
return {
|
|
81
|
+
traceId,
|
|
82
|
+
wallMs: round1(wallMs),
|
|
83
|
+
coveredMs: round1(coveredMs),
|
|
84
|
+
coveragePct: wallMs > 0 ? round1(coveredMs / wallMs * 100) : 0,
|
|
85
|
+
uncoveredMs: round1(wallMs - coveredMs),
|
|
86
|
+
largestGapMs: round1(largestGapMs),
|
|
87
|
+
byKind,
|
|
88
|
+
spans: spans.length,
|
|
89
|
+
modelCalls: spans.filter((span) => span.name === "model.call").length,
|
|
90
|
+
toolCalls: spans.filter((span) => span.name === "tool.call").length,
|
|
91
|
+
dropped
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function createTurnTrace(options = {}) {
|
|
95
|
+
const now = options.now ?? Date.now;
|
|
96
|
+
const startedAt = options.startedAt ?? now();
|
|
97
|
+
const maxSpans = options.maxSpans ?? 1e3;
|
|
98
|
+
const rootId = randomHex(8);
|
|
99
|
+
let executionId = options.executionId ?? `pending_${randomHex(6)}`;
|
|
100
|
+
let traceId = turnTraceId(executionId);
|
|
101
|
+
const records = /* @__PURE__ */ new Map();
|
|
102
|
+
const open = /* @__PURE__ */ new Set();
|
|
103
|
+
let dropped = 0;
|
|
104
|
+
let finished;
|
|
105
|
+
const inert = (id) => {
|
|
106
|
+
const handle = {
|
|
107
|
+
id,
|
|
108
|
+
set: () => handle,
|
|
109
|
+
end: () => {
|
|
110
|
+
},
|
|
111
|
+
child: () => inert(rootId),
|
|
112
|
+
record: () => inert(rootId)
|
|
113
|
+
};
|
|
114
|
+
return handle;
|
|
115
|
+
};
|
|
116
|
+
const handleFor = (id) => {
|
|
117
|
+
const handle = {
|
|
118
|
+
id,
|
|
119
|
+
set(attrs) {
|
|
120
|
+
const record = records.get(id);
|
|
121
|
+
if (record && !finished) record.attrs = boundedAttrs({ ...record.attrs, ...attrs });
|
|
122
|
+
return handle;
|
|
123
|
+
},
|
|
124
|
+
end(attrs, error) {
|
|
125
|
+
const record = records.get(id);
|
|
126
|
+
if (!record || !open.has(id) || finished) return;
|
|
127
|
+
open.delete(id);
|
|
128
|
+
record.endMs = Math.max(record.startMs, now());
|
|
129
|
+
if (attrs) record.attrs = boundedAttrs({ ...record.attrs, ...attrs });
|
|
130
|
+
if (error !== void 0) record.error = errorText(error);
|
|
131
|
+
},
|
|
132
|
+
child: (name, kind, attrs) => trace.start(name, kind, attrs, handle),
|
|
133
|
+
record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs, handle)
|
|
134
|
+
};
|
|
135
|
+
return handle;
|
|
136
|
+
};
|
|
137
|
+
const add = (name, kind, startMs, endMs, attrs, parent) => {
|
|
138
|
+
if (finished) return inert(rootId);
|
|
139
|
+
if (records.size >= maxSpans) {
|
|
140
|
+
dropped++;
|
|
141
|
+
return inert(rootId);
|
|
142
|
+
}
|
|
143
|
+
const spanId = randomHex(8);
|
|
144
|
+
records.set(spanId, {
|
|
145
|
+
spanId,
|
|
146
|
+
parentSpanId: parent && parent.id !== spanId ? parent.id : rootId,
|
|
147
|
+
name: name.slice(0, 80),
|
|
148
|
+
kind,
|
|
149
|
+
startMs,
|
|
150
|
+
endMs: endMs ?? startMs,
|
|
151
|
+
attrs: attrs ? boundedAttrs(attrs) : {}
|
|
152
|
+
});
|
|
153
|
+
if (endMs === void 0) open.add(spanId);
|
|
154
|
+
return handleFor(spanId);
|
|
155
|
+
};
|
|
156
|
+
const rootHandle = {
|
|
157
|
+
id: rootId,
|
|
158
|
+
set: () => rootHandle,
|
|
159
|
+
end: () => {
|
|
160
|
+
},
|
|
161
|
+
child: (name, kind, attrs) => trace.start(name, kind, attrs),
|
|
162
|
+
record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs)
|
|
163
|
+
};
|
|
164
|
+
const trace = {
|
|
165
|
+
get traceId() {
|
|
166
|
+
return traceId;
|
|
167
|
+
},
|
|
168
|
+
get executionId() {
|
|
169
|
+
return executionId;
|
|
170
|
+
},
|
|
171
|
+
startedAt,
|
|
172
|
+
root: rootHandle,
|
|
173
|
+
bind(id) {
|
|
174
|
+
executionId = id;
|
|
175
|
+
traceId = turnTraceId(id);
|
|
176
|
+
},
|
|
177
|
+
start: (name, kind, attrs, parent) => add(name, kind, now(), void 0, attrs, parent),
|
|
178
|
+
record: (name, kind, startMs, endMs, attrs, parent) => add(name, kind, startMs, Math.max(startMs, endMs), attrs, parent),
|
|
179
|
+
async measure(name, kind, work, attrs, parent) {
|
|
180
|
+
const span = trace.start(name, kind, attrs, parent);
|
|
181
|
+
try {
|
|
182
|
+
const result = await work(span);
|
|
183
|
+
span.end();
|
|
184
|
+
return result;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
span.end(void 0, error);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
summary(at) {
|
|
191
|
+
const end = at ?? finished?.endedAt ?? now();
|
|
192
|
+
const spans = [...records.values()].map((record) => open.has(record.spanId) ? { ...record, endMs: end } : record);
|
|
193
|
+
return summarizeTurnSpans(spans, rootId, startedAt, end, traceId, dropped);
|
|
194
|
+
},
|
|
195
|
+
finish(outcome) {
|
|
196
|
+
if (finished) return finished;
|
|
197
|
+
const endedAt = Math.max(startedAt, outcome?.endedAt ?? now());
|
|
198
|
+
for (const id of open) {
|
|
199
|
+
const record = records.get(id);
|
|
200
|
+
record.endMs = Math.max(record.startMs, endedAt);
|
|
201
|
+
record.unfinished = true;
|
|
202
|
+
}
|
|
203
|
+
open.clear();
|
|
204
|
+
const spans = [...records.values()].sort((a, b) => a.startMs - b.startMs);
|
|
205
|
+
finished = {
|
|
206
|
+
traceId,
|
|
207
|
+
rootSpanId: rootId,
|
|
208
|
+
executionId,
|
|
209
|
+
startedAt,
|
|
210
|
+
endedAt,
|
|
211
|
+
spans,
|
|
212
|
+
summary: summarizeTurnSpans(spans, rootId, startedAt, endedAt, traceId, dropped),
|
|
213
|
+
status: outcome?.error === void 0 ? "ok" : "error",
|
|
214
|
+
...outcome?.error === void 0 ? {} : { error: errorText(outcome.error) }
|
|
215
|
+
};
|
|
216
|
+
return finished;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
return trace;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export {
|
|
223
|
+
TURN_SPAN_KINDS,
|
|
224
|
+
isTraceId,
|
|
225
|
+
turnTraceId,
|
|
226
|
+
summarizeTurnSpans,
|
|
227
|
+
createTurnTrace
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=chunk-BKPJFES3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/turn-trace/recorder.ts"],"sourcesContent":["/**\n * One trace per agent turn: a root span for the turn's wall time and named\n * child spans for where that time went (admission, each model call, each tool\n * call, the box claim and prompt, persistence).\n *\n * This module only records. It has no imports, so the Router engine, the\n * sandbox helpers and the chat route share it from any runtime, a Worker\n * included. `./otel` turns a finished trace into OTLP spans for Tangle\n * Intelligence.\n *\n * The turn's trace id is its execution id in the charset every hop accepts\n * ({@link turnTraceId}), sent as `x-tangle-trace-id` on every Router and\n * Sandbox call, so a Router dispatch row and a sandbox record join the turn's\n * spans by one id.\n */\n\n/** What a span's time is spent on. Each instant of the turn is credited to the deepest open span's kind. */\nexport type TurnSpanKind =\n | 'admission'\n | 'router'\n | 'model'\n | 'network'\n | 'tool'\n | 'sandbox'\n | 'persist'\n | 'other'\n\nexport const TURN_SPAN_KINDS: readonly TurnSpanKind[] = ['admission', 'router', 'model', 'network', 'tool', 'sandbox', 'persist', 'other']\n\n/** Bounded primitive metadata. Never prompts, answers, tool arguments or credentials. */\nexport type TurnSpanAttrs = Record<string, string | number | boolean>\n\nexport interface TurnSpanRecord {\n spanId: string\n /** The root span's id for a top-level child. */\n parentSpanId: string\n name: string\n kind: TurnSpanKind\n startMs: number\n endMs: number\n attrs: TurnSpanAttrs\n /** Set when the span ended in failure; a short code or message, never content. */\n error?: string\n /** True when the turn finished while the span was still open. */\n unfinished?: boolean\n}\n\nexport interface TurnSpan {\n readonly id: string\n /** Merge attributes. A later value replaces an earlier one. */\n set(attrs: TurnSpanAttrs): TurnSpan\n /** End now. A second call changes nothing. `error` marks the span failed. */\n end(attrs?: TurnSpanAttrs, error?: unknown): void\n /** A child that starts now. */\n child(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs): TurnSpan\n /** A child whose times are already known. */\n record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs): TurnSpan\n}\n\n/** How much of the turn's wall time is inside named spans, and what the time was spent on. */\nexport interface TurnTraceSummary {\n traceId: string\n wallMs: number\n /** Wall time inside at least one child span. */\n coveredMs: number\n /** `coveredMs / wallMs` as a percentage, one decimal. */\n coveragePct: number\n uncoveredMs: number\n /** The longest stretch with no span open: where an unnamed wait hides. */\n largestGapMs: number\n /** Wall time credited to the deepest open span's kind. Sums to `coveredMs`. */\n byKind: Record<TurnSpanKind, number>\n spans: number\n modelCalls: number\n toolCalls: number\n /** Spans refused after the per-turn cap. */\n dropped: number\n}\n\nexport interface TurnTraceSnapshot {\n traceId: string\n /** The id the root span is exported under. */\n rootSpanId: string\n executionId: string\n startedAt: number\n endedAt: number\n spans: TurnSpanRecord[]\n summary: TurnTraceSummary\n /** `ok`, or the failure the turn ended with. */\n status: 'ok' | 'error'\n error?: string\n}\n\n/** Where a finished turn's trace goes. `labels` are the host's metadata for the root span (session, user, workspace). */\nexport type TurnTraceSink = (snapshot: TurnTraceSnapshot, labels: TurnSpanAttrs) => void | Promise<void>\n\nexport interface TurnTrace {\n /** The turn's execution id in the charset the Router and Sandbox accept. Send it as `x-tangle-trace-id`. */\n readonly traceId: string\n readonly executionId: string\n readonly startedAt: number\n /** The span a top-level child hangs from. */\n readonly root: TurnSpan\n /** Name the turn's execution id once it is known. Spans recorded earlier keep their times. */\n bind(executionId: string): void\n start(name: string, kind: TurnSpanKind, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan\n record(name: string, kind: TurnSpanKind, startMs: number, endMs: number, attrs?: TurnSpanAttrs, parent?: TurnSpan): TurnSpan\n /** Run `work` inside a span; the span ends when it settles, failed if it throws. */\n measure<T>(name: string, kind: TurnSpanKind, work: (span: TurnSpan) => Promise<T> | T, attrs?: TurnSpanAttrs, parent?: TurnSpan): Promise<T>\n /** Read the current numbers without ending the trace. */\n summary(now?: number): TurnTraceSummary\n /** End the trace: open spans close at `endedAt`, and later spans are refused. A second call returns the first result. */\n finish(outcome?: { error?: unknown; endedAt?: number }): TurnTraceSnapshot\n}\n\nexport interface CreateTurnTraceOptions {\n /** Epoch ms the turn began (default now). A route passes its request time so admission is inside the trace. */\n startedAt?: number\n executionId?: string\n now?: () => number\n /** Most spans one turn keeps (default 1000); the rest are counted as dropped. */\n maxSpans?: number\n}\n\nconst TRACE_ID_PATTERN = /^[\\w-]{8,200}$/\nconst MAX_ATTRS = 48\nconst MAX_ATTR_STRING = 200\n\n/** True when the Router and the Sandbox API keep this id as a `x-tangle-trace-id`. */\nexport function isTraceId(value: string): boolean {\n return TRACE_ID_PATTERN.test(value)\n}\n\n/**\n * The turn's execution id as a trace id. Execution ids carry `:` (`app:session:turn`),\n * which the Router drops from `x-tangle-trace-id`, so every character outside\n * `[A-Za-z0-9_-]` becomes `_`. An id already in the charset is returned as it is.\n */\nexport function turnTraceId(executionId: string): string {\n if (TRACE_ID_PATTERN.test(executionId)) return executionId\n const id = executionId.replace(/[^\\w-]/g, '_').slice(0, 200)\n return id.length >= 8 ? id : id.padEnd(8, '_')\n}\n\nfunction randomHex(bytes: number): string {\n const out = new Uint8Array(bytes)\n if (typeof globalThis.crypto?.getRandomValues === 'function') globalThis.crypto.getRandomValues(out)\n else for (let i = 0; i < bytes; i++) out[i] = Math.floor(Math.random() * 256)\n return Array.from(out, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction boundedAttrs(attrs: TurnSpanAttrs): TurnSpanAttrs {\n const out: TurnSpanAttrs = {}\n let count = 0\n for (const [key, value] of Object.entries(attrs)) {\n if (count >= MAX_ATTRS) break\n if (typeof value === 'number' && !Number.isFinite(value)) continue\n out[key.slice(0, 80)] = typeof value === 'string' ? value.slice(0, MAX_ATTR_STRING) : value\n count++\n }\n return out\n}\n\nfunction errorText(error: unknown): string {\n if (error instanceof Error) return (error.name === 'Error' ? error.message : `${error.name}: ${error.message}`).slice(0, MAX_ATTR_STRING)\n return String(error).slice(0, MAX_ATTR_STRING)\n}\n\nconst zeroKinds = (): Record<TurnSpanKind, number> => ({\n admission: 0, router: 0, model: 0, network: 0, tool: 0, sandbox: 0, persist: 0, other: 0,\n})\n\nconst round1 = (value: number) => Math.round(value * 10) / 10\n\n/**\n * Coverage and the per-kind split for a set of spans over `[startMs, endMs]`.\n * Exported for a reader that holds the spans of a finished trace.\n */\nexport function summarizeTurnSpans(\n spans: readonly Pick<TurnSpanRecord, 'spanId' | 'parentSpanId' | 'kind' | 'name' | 'startMs' | 'endMs'>[],\n rootSpanId: string,\n startMs: number,\n endMs: number,\n traceId: string,\n dropped = 0,\n): TurnTraceSummary {\n const wallMs = Math.max(0, endMs - startMs)\n const parent = new Map(spans.map((span) => [span.spanId, span.parentSpanId]))\n const depthOf = (id: string): number => {\n let depth = 0\n for (let at = parent.get(id); at !== undefined && at !== rootSpanId; at = parent.get(at)) {\n depth++\n if (depth > 64) break\n }\n return depth\n }\n const clipped = spans\n .map((span) => ({ span, start: Math.max(startMs, span.startMs), end: Math.min(endMs, span.endMs), depth: depthOf(span.spanId) }))\n .filter((entry) => entry.end > entry.start)\n const points = [...new Set([startMs, endMs, ...clipped.flatMap((entry) => [entry.start, entry.end])])].sort((a, b) => a - b)\n const byKind = zeroKinds()\n let coveredMs = 0\n let largestGapMs = 0\n let gap = 0\n for (let i = 0; i + 1 < points.length; i++) {\n const from = points[i]!\n const to = points[i + 1]!\n let deepest: (typeof clipped)[number] | undefined\n for (const entry of clipped) {\n if (entry.start <= from && entry.end >= to && (!deepest || entry.depth > deepest.depth || (entry.depth === deepest.depth && entry.start > deepest.start))) deepest = entry\n }\n if (deepest) {\n byKind[deepest.span.kind] += to - from\n coveredMs += to - from\n gap = 0\n } else {\n gap += to - from\n largestGapMs = Math.max(largestGapMs, gap)\n }\n }\n for (const kind of TURN_SPAN_KINDS) byKind[kind] = round1(byKind[kind])\n return {\n traceId,\n wallMs: round1(wallMs),\n coveredMs: round1(coveredMs),\n coveragePct: wallMs > 0 ? round1((coveredMs / wallMs) * 100) : 0,\n uncoveredMs: round1(wallMs - coveredMs),\n largestGapMs: round1(largestGapMs),\n byKind,\n spans: spans.length,\n modelCalls: spans.filter((span) => span.name === 'model.call').length,\n toolCalls: spans.filter((span) => span.name === 'tool.call').length,\n dropped,\n }\n}\n\nexport function createTurnTrace(options: CreateTurnTraceOptions = {}): TurnTrace {\n const now = options.now ?? Date.now\n const startedAt = options.startedAt ?? now()\n const maxSpans = options.maxSpans ?? 1000\n const rootId = randomHex(8)\n let executionId = options.executionId ?? `pending_${randomHex(6)}`\n let traceId = turnTraceId(executionId)\n const records = new Map<string, TurnSpanRecord>()\n const open = new Set<string>()\n let dropped = 0\n let finished: TurnTraceSnapshot | undefined\n\n // A refused or late span still returns a handle, so instrumentation never branches on the trace's state.\n const inert = (id: string): TurnSpan => {\n const handle: TurnSpan = {\n id,\n set: () => handle,\n end: () => {},\n child: () => inert(rootId),\n record: () => inert(rootId),\n }\n return handle\n }\n\n const handleFor = (id: string): TurnSpan => {\n const handle: TurnSpan = {\n id,\n set(attrs) {\n const record = records.get(id)\n if (record && !finished) record.attrs = boundedAttrs({ ...record.attrs, ...attrs })\n return handle\n },\n end(attrs, error) {\n const record = records.get(id)\n if (!record || !open.has(id) || finished) return\n open.delete(id)\n record.endMs = Math.max(record.startMs, now())\n if (attrs) record.attrs = boundedAttrs({ ...record.attrs, ...attrs })\n if (error !== undefined) record.error = errorText(error)\n },\n child: (name, kind, attrs) => trace.start(name, kind, attrs, handle),\n record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs, handle),\n }\n return handle\n }\n\n const add = (name: string, kind: TurnSpanKind, startMs: number, endMs: number | undefined, attrs: TurnSpanAttrs | undefined, parent: TurnSpan | undefined): TurnSpan => {\n if (finished) return inert(rootId)\n if (records.size >= maxSpans) {\n dropped++\n return inert(rootId)\n }\n const spanId = randomHex(8)\n records.set(spanId, {\n spanId,\n parentSpanId: parent && parent.id !== spanId ? parent.id : rootId,\n name: name.slice(0, 80),\n kind,\n startMs,\n endMs: endMs ?? startMs,\n attrs: attrs ? boundedAttrs(attrs) : {},\n })\n if (endMs === undefined) open.add(spanId)\n return handleFor(spanId)\n }\n\n const rootHandle: TurnSpan = {\n id: rootId,\n set: () => rootHandle,\n end: () => {},\n child: (name, kind, attrs) => trace.start(name, kind, attrs),\n record: (name, kind, start, end, attrs) => trace.record(name, kind, start, end, attrs),\n }\n\n const trace: TurnTrace = {\n get traceId() { return traceId },\n get executionId() { return executionId },\n startedAt,\n root: rootHandle,\n bind(id) {\n executionId = id\n traceId = turnTraceId(id)\n },\n start: (name, kind, attrs, parent) => add(name, kind, now(), undefined, attrs, parent),\n record: (name, kind, startMs, endMs, attrs, parent) => add(name, kind, startMs, Math.max(startMs, endMs), attrs, parent),\n async measure(name, kind, work, attrs, parent) {\n const span = trace.start(name, kind, attrs, parent)\n try {\n const result = await work(span)\n span.end()\n return result\n } catch (error) {\n span.end(undefined, error)\n throw error\n }\n },\n summary(at) {\n const end = at ?? finished?.endedAt ?? now()\n const spans = [...records.values()].map((record) => open.has(record.spanId) ? { ...record, endMs: end } : record)\n return summarizeTurnSpans(spans, rootId, startedAt, end, traceId, dropped)\n },\n finish(outcome) {\n if (finished) return finished\n const endedAt = Math.max(startedAt, outcome?.endedAt ?? now())\n for (const id of open) {\n const record = records.get(id)!\n record.endMs = Math.max(record.startMs, endedAt)\n record.unfinished = true\n }\n open.clear()\n const spans = [...records.values()].sort((a, b) => a.startMs - b.startMs)\n finished = {\n traceId,\n rootSpanId: rootId,\n executionId,\n startedAt,\n endedAt,\n spans,\n summary: summarizeTurnSpans(spans, rootId, startedAt, endedAt, traceId, dropped),\n status: outcome?.error === undefined ? 'ok' : 'error',\n ...(outcome?.error === undefined ? {} : { error: errorText(outcome.error) }),\n }\n return finished\n },\n }\n return trace\n}\n"],"mappings":"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","names":[]}
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
buildAppToolMcpServer
|
|
23
23
|
} from "./chunk-6A7MYOUI.js";
|
|
24
|
+
import {
|
|
25
|
+
nextCallIndex
|
|
26
|
+
} from "./chunk-7XQIY2LU.js";
|
|
24
27
|
import {
|
|
25
28
|
resolveTangleExecutionEnvironment,
|
|
26
29
|
trimOrNull
|
|
@@ -40,6 +43,113 @@ var fail = (error) => ({
|
|
|
40
43
|
error: error instanceof Error ? error : new Error(String(error))
|
|
41
44
|
});
|
|
42
45
|
|
|
46
|
+
// src/sandbox/prompt-trace.ts
|
|
47
|
+
var asRec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
48
|
+
var asNum = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
49
|
+
var PHASES = ["agent_run_start", "harness_spawn", "harness_ready", "first_model_request", "first_upstream_byte", "first_text"];
|
|
50
|
+
function createPromptTrace(trace, parent, meta) {
|
|
51
|
+
const startedAt = Date.now();
|
|
52
|
+
const prompt = trace.start("sandbox.prompt", "sandbox", meta, parent);
|
|
53
|
+
let firstEventAt;
|
|
54
|
+
let firstTextAt;
|
|
55
|
+
let generation;
|
|
56
|
+
let phases;
|
|
57
|
+
let events = 0;
|
|
58
|
+
const tools = /* @__PURE__ */ new Map();
|
|
59
|
+
const startTool = (id, name) => {
|
|
60
|
+
if (tools.has(id)) return;
|
|
61
|
+
tools.set(id, trace.start("tool.call", "tool", {
|
|
62
|
+
"gen_ai.tool.name": name,
|
|
63
|
+
"tool.call_id": id,
|
|
64
|
+
"call.index": nextCallIndex(trace, "tool.call"),
|
|
65
|
+
where: "sandbox",
|
|
66
|
+
approx: true
|
|
67
|
+
}, generation ?? prompt));
|
|
68
|
+
};
|
|
69
|
+
const observe = (raw) => {
|
|
70
|
+
try {
|
|
71
|
+
const event = asRec(raw);
|
|
72
|
+
if (!event) return;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
events++;
|
|
75
|
+
firstEventAt ??= now;
|
|
76
|
+
const type = typeof event.type === "string" ? event.type : "";
|
|
77
|
+
const data = asRec(event.data);
|
|
78
|
+
if (type === "result") {
|
|
79
|
+
const report = asRec(data?.firstTextPhases);
|
|
80
|
+
const offsets = asRec(report?.phasesMs);
|
|
81
|
+
if (offsets) {
|
|
82
|
+
phases = {
|
|
83
|
+
offsets,
|
|
84
|
+
warm: typeof report?.harnessWarm === "boolean" ? report.harnessWarm : null,
|
|
85
|
+
...typeof report?.model === "string" ? { model: report.model } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (type === "tool_call" || type === "tool.call") {
|
|
91
|
+
const id = String(data?.id ?? data?.callId ?? data?.callID ?? data?.name ?? "");
|
|
92
|
+
if (id) startTool(id, String(data?.name ?? data?.tool ?? "tool"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (type === "tool_result" || type === "tool.result") {
|
|
96
|
+
const id = String(data?.id ?? data?.callId ?? data?.callID ?? data?.name ?? "");
|
|
97
|
+
tools.get(id)?.end(void 0, data?.error ?? (data?.status === "error" ? "tool_error" : void 0));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (type !== "message.part.updated") return;
|
|
101
|
+
const part = asRec(data?.part);
|
|
102
|
+
const partType = String(part?.type ?? "");
|
|
103
|
+
if (partType === "text" && firstTextAt === void 0 && (typeof data?.delta === "string" && data.delta || typeof part?.text === "string" && part.text)) {
|
|
104
|
+
firstTextAt = now;
|
|
105
|
+
generation = prompt.child("sandbox.generation", "model");
|
|
106
|
+
} else if (partType === "tool") {
|
|
107
|
+
const id = String(part?.id ?? part?.callID ?? "");
|
|
108
|
+
if (!id) return;
|
|
109
|
+
const state = asRec(part?.state);
|
|
110
|
+
const status = String(state?.status ?? part?.status ?? "");
|
|
111
|
+
startTool(id, String(part?.tool ?? "tool"));
|
|
112
|
+
if (status === "completed" || status === "error") tools.get(id)?.end(void 0, status === "error" ? "tool_error" : void 0);
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const end = (error) => {
|
|
118
|
+
try {
|
|
119
|
+
for (const span of tools.values()) span.end({ "tool.unclosed": true });
|
|
120
|
+
generation?.end();
|
|
121
|
+
if (phases && firstTextAt !== void 0) {
|
|
122
|
+
const firstText = asNum(phases.offsets.first_text);
|
|
123
|
+
if (firstText !== void 0 && firstTextAt - firstText >= startedAt) {
|
|
124
|
+
const origin = firstTextAt - firstText;
|
|
125
|
+
prompt.record("sandbox.transit", "sandbox", startedAt, origin, { note: "sdk, api, orchestrator and host hops, both ways" });
|
|
126
|
+
let previous = 0;
|
|
127
|
+
for (const phase of PHASES) {
|
|
128
|
+
const offset = asNum(phases.offsets[phase]);
|
|
129
|
+
if (offset === void 0 || offset < previous) continue;
|
|
130
|
+
prompt.record(`sidecar.${phase}`, phase === "first_upstream_byte" || phase === "first_text" ? "model" : "sandbox", origin + previous, origin + offset);
|
|
131
|
+
previous = offset;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
prompt.set({
|
|
136
|
+
events,
|
|
137
|
+
...firstEventAt !== void 0 ? { first_event_ms: firstEventAt - startedAt } : {},
|
|
138
|
+
...firstTextAt !== void 0 ? { first_text_ms: firstTextAt - startedAt } : {},
|
|
139
|
+
...phases?.warm !== null && phases?.warm !== void 0 ? { "sidecar.harness_warm": phases.warm } : {},
|
|
140
|
+
...phases?.model ? { "sidecar.model": phases.model } : {},
|
|
141
|
+
...Object.fromEntries(PHASES.flatMap((phase) => {
|
|
142
|
+
const offset = asNum(phases?.offsets[phase]);
|
|
143
|
+
return offset === void 0 ? [] : [[`sidecar.${phase}_ms`, Math.round(offset)]];
|
|
144
|
+
}))
|
|
145
|
+
});
|
|
146
|
+
prompt.end(void 0, error);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
return { observe, end };
|
|
151
|
+
}
|
|
152
|
+
|
|
43
153
|
// src/sandbox/model.ts
|
|
44
154
|
function backendModelFields(model) {
|
|
45
155
|
return {
|
|
@@ -427,7 +537,14 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
427
537
|
var TASK_LIMIT = 2e4;
|
|
428
538
|
var SANDBOX_RUN_DESCRIPTION = "Run work that needs a computer in this workspace's sandbox: executing code or scripts, reading or converting PDFs, spreadsheets and archives, filling forms with the installed toolkits, or editing many files. Describe the whole task and the result you need; the sandbox agent does it and returns its answer. Takes seconds to minutes, so use it only when the answer needs the computer.";
|
|
429
539
|
function sandboxPromptRunner(shell, prompt = {}) {
|
|
430
|
-
return (box, task, { signal, timeoutMs }) => runSandboxPrompt(shell, box, task, {
|
|
540
|
+
return (box, task, { signal, timeoutMs, trace, span }) => runSandboxPrompt(shell, box, task, {
|
|
541
|
+
...prompt,
|
|
542
|
+
signal,
|
|
543
|
+
timeoutMs,
|
|
544
|
+
disallowQuestions: true,
|
|
545
|
+
...trace ? { trace } : {},
|
|
546
|
+
...span ? { traceParent: span } : {}
|
|
547
|
+
});
|
|
431
548
|
}
|
|
432
549
|
function createSandboxRunTool(options) {
|
|
433
550
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -466,16 +583,25 @@ function createSandboxRunTool(options) {
|
|
|
466
583
|
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
467
584
|
})]);
|
|
468
585
|
let box;
|
|
586
|
+
const claim = context?.span?.child("sandbox.claim", "sandbox");
|
|
469
587
|
try {
|
|
470
588
|
box = await capped(options.box(signal));
|
|
589
|
+
claim?.end();
|
|
471
590
|
} catch (error) {
|
|
591
|
+
claim?.end(void 0, error instanceof SandboxRunError ? error.code : "sandbox_unavailable");
|
|
472
592
|
if (error instanceof SandboxRunError) return fail2(error.code, error.message, error.cause);
|
|
473
593
|
return fail2("sandbox_unavailable", "The sandbox for this workspace could not start, so this work was not done.", error);
|
|
474
594
|
}
|
|
475
595
|
emit("box_ready", { ms: Date.now() - started });
|
|
476
596
|
let answer;
|
|
477
597
|
try {
|
|
478
|
-
answer = await capped(runTask(box, task, {
|
|
598
|
+
answer = await capped(runTask(box, task, {
|
|
599
|
+
signal,
|
|
600
|
+
timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)),
|
|
601
|
+
emit,
|
|
602
|
+
...context?.trace ? { trace: context.trace } : {},
|
|
603
|
+
...context?.span ? { span: context.span } : {}
|
|
604
|
+
}));
|
|
479
605
|
} catch (error) {
|
|
480
606
|
const timedOut = signal.aborted && !options.signal?.aborted;
|
|
481
607
|
return fail2("sandbox_failed", timedOut ? `The sandbox did not finish within ${Math.round(timeoutMs / 1e3)} s, so this work was not done.` : "The sandbox run failed, so this work was not done.", error);
|
|
@@ -559,10 +685,13 @@ function createSandboxExecTool(options) {
|
|
|
559
685
|
if (options.warm) await options.warm.leases.turn(options.warm.key).catch(() => {
|
|
560
686
|
});
|
|
561
687
|
let box;
|
|
688
|
+
const reused = ready !== null;
|
|
689
|
+
const claim = context?.span?.child("sandbox.claim", "sandbox");
|
|
562
690
|
const starting = place(signal);
|
|
563
691
|
try {
|
|
564
692
|
box = await raceAbort(starting, signal);
|
|
565
693
|
} catch (error) {
|
|
694
|
+
claim?.end(void 0, error instanceof SandboxRunError ? error.code : "sandbox_unavailable");
|
|
566
695
|
if (ready === starting) ready = null;
|
|
567
696
|
if (error instanceof SandboxRunError) {
|
|
568
697
|
options.onFailure?.(error);
|
|
@@ -570,15 +699,22 @@ function createSandboxExecTool(options) {
|
|
|
570
699
|
}
|
|
571
700
|
return fail2("sandbox_unavailable", "The sandbox did not start in time, so the command did not run.", error);
|
|
572
701
|
}
|
|
702
|
+
claim?.end({ "claim.reused": reused });
|
|
573
703
|
emit("box_ready", { ms: Date.now() - started });
|
|
574
704
|
let result;
|
|
705
|
+
const execSpan = context?.span?.child("sandbox.exec", "sandbox");
|
|
575
706
|
try {
|
|
576
707
|
result = await raceAbort(box.exec(command, {
|
|
577
708
|
...cwd ? { cwd } : {},
|
|
578
709
|
...options.env ? { env: options.env } : {},
|
|
579
|
-
timeoutMs: Math.max(1, timeoutMs - (Date.now() - started))
|
|
710
|
+
timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)),
|
|
711
|
+
// The same id the Router turn sends, so this command's Sandbox API and host records join the turn.
|
|
712
|
+
// SDK 0.62.2 added `traceId` to ExecOptions; an older SDK ignores the field.
|
|
713
|
+
...context?.trace ? { traceId: context.trace.traceId } : {}
|
|
580
714
|
}), signal);
|
|
715
|
+
execSpan?.end({ "exec.exit_code": result.exitCode });
|
|
581
716
|
} catch (error) {
|
|
717
|
+
execSpan?.end(void 0, "exec_failed");
|
|
582
718
|
const timedOut = signal.aborted && !options.signal?.aborted;
|
|
583
719
|
return fail2("sandbox_failed", timedOut ? `The command did not finish within ${Math.round(timeoutMs / 1e3)} s.` : "The command could not run in the sandbox.", error);
|
|
584
720
|
}
|
|
@@ -2384,9 +2520,24 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
2384
2520
|
workspaceId: options.workspaceId,
|
|
2385
2521
|
...options.userId ? { userId: options.userId } : {}
|
|
2386
2522
|
});
|
|
2387
|
-
const
|
|
2388
|
-
|
|
2389
|
-
|
|
2523
|
+
const { trace } = options;
|
|
2524
|
+
const claim = trace?.start("sandbox.claim", "sandbox", { "workspace.harness": options.harness }, options.traceParent);
|
|
2525
|
+
const traced = trace && claim ? {
|
|
2526
|
+
...options,
|
|
2527
|
+
measure: (stage, operation) => {
|
|
2528
|
+
const run = () => trace.measure(stage, "sandbox", () => operation(), void 0, claim);
|
|
2529
|
+
return options.measure ? options.measure(stage, run) : run();
|
|
2530
|
+
}
|
|
2531
|
+
} : options;
|
|
2532
|
+
try {
|
|
2533
|
+
const box = await bringUpWorkspaceSandbox(shell, traced);
|
|
2534
|
+
claim?.end({ "sandbox.id": box.id });
|
|
2535
|
+
await observeProvisionedBox(shell, options, box);
|
|
2536
|
+
return box;
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
claim?.end(void 0, error);
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2390
2541
|
}
|
|
2391
2542
|
async function observeProvisionedBox(shell, options, box) {
|
|
2392
2543
|
if (!options.spend?.onProvisioned) return;
|
|
@@ -2521,11 +2672,12 @@ async function provisionWorkspaceSandbox(shell, options) {
|
|
|
2521
2672
|
}
|
|
2522
2673
|
}
|
|
2523
2674
|
if (!replacementRequested && !forceNew && shell.resumeStopped !== false) {
|
|
2524
|
-
const stopped = await listStopped(client, name);
|
|
2675
|
+
const stopped = await measure("list_stopped", () => listStopped(client, name));
|
|
2525
2676
|
if (!stopped.succeeded) throw stopped.error;
|
|
2526
2677
|
if (stopped.value) {
|
|
2527
|
-
const
|
|
2528
|
-
const
|
|
2678
|
+
const stoppedBox = stopped.value;
|
|
2679
|
+
const runtimeEnv2 = await measure("runtime_env", () => resolveWorkspaceRuntimeEnv(shell, scope));
|
|
2680
|
+
const resumed = await measure("resume", () => resumeStoppedBox(stoppedBox, resumeTimeout, runtimeEnv2, onProgress));
|
|
2529
2681
|
if (!resumed.succeeded) {
|
|
2530
2682
|
const recovery = await requestSandboxReplacement(
|
|
2531
2683
|
shell.recoverStoppedSandbox,
|
|
@@ -2635,12 +2787,12 @@ async function provisionWorkspaceSandbox(shell, options) {
|
|
|
2635
2787
|
SHELL_PROMPT_BUDGET_HINT
|
|
2636
2788
|
);
|
|
2637
2789
|
assertProvisionPayloadWithinCap(payload ?? {});
|
|
2638
|
-
let box = await client.create(payload);
|
|
2639
|
-
await box.waitFor("running", {
|
|
2790
|
+
let box = await measure("create", () => client.create(payload));
|
|
2791
|
+
await measure("wait_running", () => box.waitFor("running", {
|
|
2640
2792
|
timeoutMs: shell.provisionTimeoutMs ?? DEFAULT_PROVISION_TIMEOUT_MS,
|
|
2641
2793
|
...onProgress ? { onProgress } : {}
|
|
2642
|
-
});
|
|
2643
|
-
box = await refreshRuntimeConnection(client, box);
|
|
2794
|
+
}));
|
|
2795
|
+
box = await measure("refresh_connection", () => refreshRuntimeConnection(client, box));
|
|
2644
2796
|
if (deferredFiles.length > 0) {
|
|
2645
2797
|
const written = await writeProfileFilesToBox(box, deferredFiles);
|
|
2646
2798
|
if (!written.succeeded) {
|
|
@@ -2648,7 +2800,8 @@ async function provisionWorkspaceSandbox(shell, options) {
|
|
|
2648
2800
|
}
|
|
2649
2801
|
}
|
|
2650
2802
|
if (shell.bootstrap) {
|
|
2651
|
-
const
|
|
2803
|
+
const bootstrap = shell.bootstrap.bind(shell);
|
|
2804
|
+
const boot = await measure("bootstrap", () => bootstrap(box, scope));
|
|
2652
2805
|
if (!boot.succeeded) {
|
|
2653
2806
|
throw new Error(`bootstrap failed on new box ${name}`, { cause: boot.error });
|
|
2654
2807
|
}
|
|
@@ -2793,7 +2946,8 @@ async function* detachedSandboxPromptEvents(box, prompt, options, backend) {
|
|
|
2793
2946
|
...options.expectedSecretProfileReceipt ? { expectedSecretProfileReceipt: options.expectedSecretProfileReceipt } : {},
|
|
2794
2947
|
...options.requireVisibleAssistantOutput !== void 0 ? { requireVisibleAssistantOutput: options.requireVisibleAssistantOutput } : {},
|
|
2795
2948
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
2796
|
-
...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {}
|
|
2949
|
+
...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {},
|
|
2950
|
+
...options.trace ? { traceId: options.trace.traceId } : {}
|
|
2797
2951
|
});
|
|
2798
2952
|
const admittedExecutionId = admission?.executionId ?? executionId;
|
|
2799
2953
|
if (!admittedExecutionId) {
|
|
@@ -2810,6 +2964,7 @@ async function* detachedSandboxPromptEvents(box, prompt, options, backend) {
|
|
|
2810
2964
|
sessionId: admission?.sessionId ?? sessionId,
|
|
2811
2965
|
executionId: admittedExecutionId,
|
|
2812
2966
|
lastEventId: lastEventId || "0",
|
|
2967
|
+
...options.trace ? { traceId: options.trace.traceId } : {},
|
|
2813
2968
|
...options.signal ? { signal: options.signal } : {}
|
|
2814
2969
|
});
|
|
2815
2970
|
}
|
|
@@ -3031,13 +3186,22 @@ async function* streamSandboxPrompt(shell, box, message, options) {
|
|
|
3031
3186
|
...options?.requireVisibleAssistantOutput !== void 0 ? { requireVisibleAssistantOutput: options.requireVisibleAssistantOutput } : {},
|
|
3032
3187
|
...options?.expectedPublicProfileRequestDigest ? { expectedPublicProfileRequestDigest: options.expectedPublicProfileRequestDigest } : {},
|
|
3033
3188
|
...options?.expectedSecretProfileReceipt ? { expectedSecretProfileReceipt: options.expectedSecretProfileReceipt } : {},
|
|
3189
|
+
...options?.trace ? { traceId: options.trace.traceId } : {},
|
|
3034
3190
|
backend
|
|
3035
3191
|
});
|
|
3036
3192
|
const stream = options?.lastEventId ? dispatch() : resendRefusedDispatch(dispatch, options?.signal);
|
|
3037
3193
|
emitSandboxActivity(options?.spend, box);
|
|
3038
3194
|
let severedFinishReason = null;
|
|
3195
|
+
const promptTrace = options?.trace ? createPromptTrace(options.trace, options.traceParent, {
|
|
3196
|
+
"sandbox.id": box.id,
|
|
3197
|
+
...options.model ? { "gen_ai.request.model": options.model } : {},
|
|
3198
|
+
...options.harness ? { "sandbox.harness": options.harness } : {},
|
|
3199
|
+
...options.detach ? { "sandbox.detached": true } : {}
|
|
3200
|
+
}) : void 0;
|
|
3201
|
+
let promptError;
|
|
3039
3202
|
try {
|
|
3040
3203
|
for await (const event of stream) {
|
|
3204
|
+
promptTrace?.observe(event);
|
|
3041
3205
|
const step = classifySeveredStream(event);
|
|
3042
3206
|
if (step) severedFinishReason = step.kind === "step-finish" && step.severed ? step.reason : null;
|
|
3043
3207
|
if (severedFinishReason && isTerminalPromptEvent(event)) {
|
|
@@ -3051,8 +3215,12 @@ async function* streamSandboxPrompt(shell, box, message, options) {
|
|
|
3051
3215
|
}
|
|
3052
3216
|
yield event;
|
|
3053
3217
|
}
|
|
3218
|
+
} catch (error) {
|
|
3219
|
+
promptError = error;
|
|
3220
|
+
throw error;
|
|
3054
3221
|
} finally {
|
|
3055
3222
|
emitSandboxActivity(options?.spend, box);
|
|
3223
|
+
promptTrace?.end(promptError);
|
|
3056
3224
|
}
|
|
3057
3225
|
if (severedFinishReason) {
|
|
3058
3226
|
throw new Error(`sandbox model stream severed mid-turn (reason="${severedFinishReason}")`);
|
|
@@ -3345,4 +3513,4 @@ export {
|
|
|
3345
3513
|
isTerminalPromptEvent,
|
|
3346
3514
|
detectInteractiveQuestion
|
|
3347
3515
|
};
|
|
3348
|
-
//# sourceMappingURL=chunk-
|
|
3516
|
+
//# sourceMappingURL=chunk-RJJRRR5Z.js.map
|