@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.
Files changed (41) hide show
  1. package/dist/chat-routes/index.js +6 -4
  2. package/dist/chat-routes/index.js.map +1 -1
  3. package/dist/chat-routes/turn-routes.d.ts +28 -0
  4. package/dist/{chunk-XJEIGVMQ.js → chunk-2OWT3TNV.js} +2 -2
  5. package/dist/{chunk-XP77DJEF.js → chunk-6XXKFYIM.js} +72 -30
  6. package/dist/chunk-6XXKFYIM.js.map +1 -0
  7. package/dist/chunk-7XQIY2LU.js +151 -0
  8. package/dist/chunk-7XQIY2LU.js.map +1 -0
  9. package/dist/chunk-BKPJFES3.js +229 -0
  10. package/dist/chunk-BKPJFES3.js.map +1 -0
  11. package/dist/{chunk-SUIH5B55.js → chunk-RJJRRR5Z.js} +184 -16
  12. package/dist/chunk-RJJRRR5Z.js.map +1 -0
  13. package/dist/{chunk-FPOOBGRG.js → chunk-T4BLISYH.js} +40 -5
  14. package/dist/chunk-T4BLISYH.js.map +1 -0
  15. package/dist/public-consultation/index.d.ts +1 -1
  16. package/dist/public-consultation/index.js +2 -1
  17. package/dist/public-consultation/index.js.map +1 -1
  18. package/dist/runtime/index.js +4 -2
  19. package/dist/runtime/index.js.map +1 -1
  20. package/dist/runtime/router-turn.d.ts +14 -1
  21. package/dist/runtime/router-turn.js +3 -1
  22. package/dist/sandbox/index.d.ts +19 -0
  23. package/dist/sandbox/index.js +2 -1
  24. package/dist/sandbox/prompt-trace.d.ts +20 -0
  25. package/dist/sandbox/sandbox-run-tool.d.ts +8 -0
  26. package/dist/trace/index.d.ts +0 -1
  27. package/dist/trace/index.js +0 -240
  28. package/dist/trace/index.js.map +1 -1
  29. package/dist/turn-trace/index.d.ts +11 -0
  30. package/dist/turn-trace/index.js +121 -0
  31. package/dist/turn-trace/index.js.map +1 -0
  32. package/dist/turn-trace/otel.d.ts +36 -0
  33. package/dist/turn-trace/recorder.d.ts +122 -0
  34. package/dist/turn-trace/server-timing.d.ts +15 -0
  35. package/dist/turn-trace/traced-fetch.d.ts +31 -0
  36. package/package.json +6 -1
  37. package/dist/chunk-FPOOBGRG.js.map +0 -1
  38. package/dist/chunk-SUIH5B55.js.map +0 -1
  39. package/dist/chunk-XP77DJEF.js.map +0 -1
  40. package/dist/trace/stage-timing.d.ts +0 -61
  41. /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":";AA2BO,IAAM,kBAA2C,CAAC,aAAa,UAAU,SAAS,WAAW,QAAQ,WAAW,WAAW,OAAO;AAiGzI,IAAM,mBAAmB;AACzB,IAAM,YAAY;AAClB,IAAM,kBAAkB;AAGjB,SAAS,UAAU,OAAwB;AAChD,SAAO,iBAAiB,KAAK,KAAK;AACpC;AAOO,SAAS,YAAY,aAA6B;AACvD,MAAI,iBAAiB,KAAK,WAAW,EAAG,QAAO;AAC/C,QAAM,KAAK,YAAY,QAAQ,WAAW,GAAG,EAAE,MAAM,GAAG,GAAG;AAC3D,SAAO,GAAG,UAAU,IAAI,KAAK,GAAG,OAAO,GAAG,GAAG;AAC/C;AAEA,SAAS,UAAU,OAAuB;AACxC,QAAM,MAAM,IAAI,WAAW,KAAK;AAChC,MAAI,OAAO,WAAW,QAAQ,oBAAoB,WAAY,YAAW,OAAO,gBAAgB,GAAG;AAAA,MAC9F,UAAS,IAAI,GAAG,IAAI,OAAO,IAAK,KAAI,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAC5E,SAAO,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACxE;AAEA,SAAS,aAAa,OAAqC;AACzD,QAAM,MAAqB,CAAC;AAC5B,MAAI,QAAQ;AACZ,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,SAAS,UAAW;AACxB,QAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG;AAC1D,QAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,OAAO,UAAU,WAAW,MAAM,MAAM,GAAG,eAAe,IAAI;AACtF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAAwB;AACzC,MAAI,iBAAiB,MAAO,SAAQ,MAAM,SAAS,UAAU,MAAM,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,IAAI,MAAM,GAAG,eAAe;AACxI,SAAO,OAAO,KAAK,EAAE,MAAM,GAAG,eAAe;AAC/C;AAEA,IAAM,YAAY,OAAqC;AAAA,EACrD,WAAW;AAAA,EAAG,QAAQ;AAAA,EAAG,OAAO;AAAA,EAAG,SAAS;AAAA,EAAG,MAAM;AAAA,EAAG,SAAS;AAAA,EAAG,SAAS;AAAA,EAAG,OAAO;AACzF;AAEA,IAAM,SAAS,CAAC,UAAkB,KAAK,MAAM,QAAQ,EAAE,IAAI;AAMpD,SAAS,mBACd,OACA,YACA,SACA,OACA,SACA,UAAU,GACQ;AAClB,QAAM,SAAS,KAAK,IAAI,GAAG,QAAQ,OAAO;AAC1C,QAAM,SAAS,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,KAAK,YAAY,CAAC,CAAC;AAC5E,QAAM,UAAU,CAAC,OAAuB;AACtC,QAAI,QAAQ;AACZ,aAAS,KAAK,OAAO,IAAI,EAAE,GAAG,OAAO,UAAa,OAAO,YAAY,KAAK,OAAO,IAAI,EAAE,GAAG;AACxF;AACA,UAAI,QAAQ,GAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AACA,QAAM,UAAU,MACb,IAAI,CAAC,UAAU,EAAE,MAAM,OAAO,KAAK,IAAI,SAAS,KAAK,OAAO,GAAG,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,GAAG,OAAO,QAAQ,KAAK,MAAM,EAAE,EAAE,EAC/H,OAAO,CAAC,UAAU,MAAM,MAAM,MAAM,KAAK;AAC5C,QAAM,SAAS,CAAC,GAAG,oBAAI,IAAI,CAAC,SAAS,OAAO,GAAG,QAAQ,QAAQ,CAAC,UAAU,CAAC,MAAM,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC3H,QAAM,SAAS,UAAU;AACzB,MAAI,YAAY;AAChB,MAAI,eAAe;AACnB,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,OAAO,QAAQ,KAAK;AAC1C,UAAM,OAAO,OAAO,CAAC;AACrB,UAAM,KAAK,OAAO,IAAI,CAAC;AACvB,QAAI;AACJ,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,SAAS,QAAQ,MAAM,OAAO,OAAO,CAAC,WAAW,MAAM,QAAQ,QAAQ,SAAU,MAAM,UAAU,QAAQ,SAAS,MAAM,QAAQ,QAAQ,OAAS,WAAU;AAAA,IACvK;AACA,QAAI,SAAS;AACX,aAAO,QAAQ,KAAK,IAAI,KAAK,KAAK;AAClC,mBAAa,KAAK;AAClB,YAAM;AAAA,IACR,OAAO;AACL,aAAO,KAAK;AACZ,qBAAe,KAAK,IAAI,cAAc,GAAG;AAAA,IAC3C;AAAA,EACF;AACA,aAAW,QAAQ,gBAAiB,QAAO,IAAI,IAAI,OAAO,OAAO,IAAI,CAAC;AACtE,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO,MAAM;AAAA,IACrB,WAAW,OAAO,SAAS;AAAA,IAC3B,aAAa,SAAS,IAAI,OAAQ,YAAY,SAAU,GAAG,IAAI;AAAA,IAC/D,aAAa,OAAO,SAAS,SAAS;AAAA,IACtC,cAAc,OAAO,YAAY;AAAA,IACjC;AAAA,IACA,OAAO,MAAM;AAAA,IACb,YAAY,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,YAAY,EAAE;AAAA,IAC/D,WAAW,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW,EAAE;AAAA,IAC7D;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,UAAkC,CAAC,GAAc;AAC/E,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,YAAY,QAAQ,aAAa,IAAI;AAC3C,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,SAAS,UAAU,CAAC;AAC1B,MAAI,cAAc,QAAQ,eAAe,WAAW,UAAU,CAAC,CAAC;AAChE,MAAI,UAAU,YAAY,WAAW;AACrC,QAAM,UAAU,oBAAI,IAA4B;AAChD,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,UAAU;AACd,MAAI;AAGJ,QAAM,QAAQ,CAAC,OAAyB;AACtC,UAAM,SAAmB;AAAA,MACvB;AAAA,MACA,KAAK,MAAM;AAAA,MACX,KAAK,MAAM;AAAA,MAAC;AAAA,MACZ,OAAO,MAAM,MAAM,MAAM;AAAA,MACzB,QAAQ,MAAM,MAAM,MAAM;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,CAAC,OAAyB;AAC1C,UAAM,SAAmB;AAAA,MACvB;AAAA,MACA,IAAI,OAAO;AACT,cAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,YAAI,UAAU,CAAC,SAAU,QAAO,QAAQ,aAAa,EAAE,GAAG,OAAO,OAAO,GAAG,MAAM,CAAC;AAClF,eAAO;AAAA,MACT;AAAA,MACA,IAAI,OAAO,OAAO;AAChB,cAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,YAAI,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE,KAAK,SAAU;AAC1C,aAAK,OAAO,EAAE;AACd,eAAO,QAAQ,KAAK,IAAI,OAAO,SAAS,IAAI,CAAC;AAC7C,YAAI,MAAO,QAAO,QAAQ,aAAa,EAAE,GAAG,OAAO,OAAO,GAAG,MAAM,CAAC;AACpE,YAAI,UAAU,OAAW,QAAO,QAAQ,UAAU,KAAK;AAAA,MACzD;AAAA,MACA,OAAO,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,MAAM,MAAM,OAAO,MAAM;AAAA,MACnE,QAAQ,CAAC,MAAM,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK,OAAO,MAAM;AAAA,IAC/F;AACA,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,CAAC,MAAc,MAAoB,SAAiB,OAA2B,OAAkC,WAA2C;AACtK,QAAI,SAAU,QAAO,MAAM,MAAM;AACjC,QAAI,QAAQ,QAAQ,UAAU;AAC5B;AACA,aAAO,MAAM,MAAM;AAAA,IACrB;AACA,UAAM,SAAS,UAAU,CAAC;AAC1B,YAAQ,IAAI,QAAQ;AAAA,MAClB;AAAA,MACA,cAAc,UAAU,OAAO,OAAO,SAAS,OAAO,KAAK;AAAA,MAC3D,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,MACtB;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,OAAO,QAAQ,aAAa,KAAK,IAAI,CAAC;AAAA,IACxC,CAAC;AACD,QAAI,UAAU,OAAW,MAAK,IAAI,MAAM;AACxC,WAAO,UAAU,MAAM;AAAA,EACzB;AAEA,QAAM,aAAuB;AAAA,IAC3B,IAAI;AAAA,IACJ,KAAK,MAAM;AAAA,IACX,KAAK,MAAM;AAAA,IAAC;AAAA,IACZ,OAAO,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3D,QAAQ,CAAC,MAAM,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK,KAAK;AAAA,EACvF;AAEA,QAAM,QAAmB;AAAA,IACvB,IAAI,UAAU;AAAE,aAAO;AAAA,IAAQ;AAAA,IAC/B,IAAI,cAAc;AAAE,aAAO;AAAA,IAAY;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,IACN,KAAK,IAAI;AACP,oBAAc;AACd,gBAAU,YAAY,EAAE;AAAA,IAC1B;AAAA,IACA,OAAO,CAAC,MAAM,MAAM,OAAO,WAAW,IAAI,MAAM,MAAM,IAAI,GAAG,QAAW,OAAO,MAAM;AAAA,IACrF,QAAQ,CAAC,MAAM,MAAM,SAAS,OAAO,OAAO,WAAW,IAAI,MAAM,MAAM,SAAS,KAAK,IAAI,SAAS,KAAK,GAAG,OAAO,MAAM;AAAA,IACvH,MAAM,QAAQ,MAAM,MAAM,MAAM,OAAO,QAAQ;AAC7C,YAAM,OAAO,MAAM,MAAM,MAAM,MAAM,OAAO,MAAM;AAClD,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,IAAI;AAC9B,aAAK,IAAI;AACT,eAAO;AAAA,MACT,SAAS,OAAO;AACd,aAAK,IAAI,QAAW,KAAK;AACzB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,QAAQ,IAAI;AACV,YAAM,MAAM,MAAM,UAAU,WAAW,IAAI;AAC3C,YAAM,QAAQ,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,KAAK,IAAI,OAAO,MAAM,IAAI,EAAE,GAAG,QAAQ,OAAO,IAAI,IAAI,MAAM;AAChH,aAAO,mBAAmB,OAAO,QAAQ,WAAW,KAAK,SAAS,OAAO;AAAA,IAC3E;AAAA,IACA,OAAO,SAAS;AACd,UAAI,SAAU,QAAO;AACrB,YAAM,UAAU,KAAK,IAAI,WAAW,SAAS,WAAW,IAAI,CAAC;AAC7D,iBAAW,MAAM,MAAM;AACrB,cAAM,SAAS,QAAQ,IAAI,EAAE;AAC7B,eAAO,QAAQ,KAAK,IAAI,OAAO,SAAS,OAAO;AAC/C,eAAO,aAAa;AAAA,MACtB;AACA,WAAK,MAAM;AACX,YAAM,QAAQ,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACxE,iBAAW;AAAA,QACT;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,mBAAmB,OAAO,QAAQ,WAAW,SAAS,SAAS,OAAO;AAAA,QAC/E,QAAQ,SAAS,UAAU,SAAY,OAAO;AAAA,QAC9C,GAAI,SAAS,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,UAAU,QAAQ,KAAK,EAAE;AAAA,MAC5E;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","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, { ...prompt, signal, timeoutMs, disallowQuestions: true });
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, { signal, timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), emit }));
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 box = await bringUpWorkspaceSandbox(shell, options);
2388
- await observeProvisionedBox(shell, options, box);
2389
- return box;
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 runtimeEnv2 = await resolveWorkspaceRuntimeEnv(shell, scope);
2528
- const resumed = await resumeStoppedBox(stopped.value, resumeTimeout, runtimeEnv2, onProgress);
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 boot = await shell.bootstrap(box, scope);
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-SUIH5B55.js.map
3516
+ //# sourceMappingURL=chunk-RJJRRR5Z.js.map