@darkhunt-security/endpoint-codex 0.9.9 → 0.9.11

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.
@@ -18308,6 +18308,7 @@ function attachmentLabel(record) {
18308
18308
  return `[${record.mediaType ?? "attachment"}${size}]`;
18309
18309
  }
18310
18310
  var RESUMED_USER_TEXT_LIMIT = 4e3;
18311
+ var USAGE_WIRE_NAMES = { thinking_tokens: "reasoning" };
18311
18312
  var SessionEmitter = class {
18312
18313
  client;
18313
18314
  options;
@@ -18331,6 +18332,8 @@ var SessionEmitter = class {
18331
18332
  /** Attachments seen before the trace could be opened knowing the prompt. */
18332
18333
  deferred = [];
18333
18334
  pendingUsage;
18335
+ /** Set when the model call this generation stands for failed. Cleared with it. */
18336
+ generationError;
18334
18337
  outputText = [];
18335
18338
  /** The last thing the assistant said in this pass — the trace's result. */
18336
18339
  lastAssistantText;
@@ -18386,9 +18389,10 @@ var SessionEmitter = class {
18386
18389
  this.count++;
18387
18390
  if (record.kind === "attachment")
18388
18391
  this.pendingAttachments.push(attachmentLabel(record));
18389
- if (record.kind === "user_message")
18392
+ if (record.kind === "user_message" && !record.isError && !record.synthetic) {
18390
18393
  this.absorbUserTurn(record);
18391
- if (!this.trace && record.kind === "attachment") {
18394
+ }
18395
+ if (!this.trace && (record.kind === "attachment" || record.synthetic === true)) {
18392
18396
  this.deferred.push(record);
18393
18397
  return;
18394
18398
  }
@@ -18426,6 +18430,15 @@ var SessionEmitter = class {
18426
18430
  this.owners.set(record.uuid, owner);
18427
18431
  switch (record.kind) {
18428
18432
  case "user_message":
18433
+ if (record.synthetic) {
18434
+ this.seeded(["injected", record.uuid], () => owner.span("context_injection", {
18435
+ startTime: record.ts,
18436
+ ...record.text !== void 0 ? { output: summarize(record.text) } : {}
18437
+ })).end({ endTime: record.ts });
18438
+ break;
18439
+ }
18440
+ if (record.isError)
18441
+ this.generationError = record.errorKind ?? "error";
18429
18442
  this.endGeneration();
18430
18443
  break;
18431
18444
  case "attachment": {
@@ -18442,7 +18455,9 @@ var SessionEmitter = class {
18442
18455
  }
18443
18456
  case "assistant_message":
18444
18457
  this.openGeneration(record, owner);
18445
- if (record.text)
18458
+ if (record.isError)
18459
+ this.generationError = record.errorKind ?? "error";
18460
+ else if (record.text)
18446
18461
  this.outputText.push(record.text);
18447
18462
  break;
18448
18463
  case "thinking": {
@@ -18471,15 +18486,44 @@ var SessionEmitter = class {
18471
18486
  case "tool_result": {
18472
18487
  const span = record.toolCallId ? this.toolSpans.get(record.toolCallId) : void 0;
18473
18488
  if (span) {
18474
- span.end({ output: record.output, endTime: record.ts });
18489
+ span.end({
18490
+ output: record.output,
18491
+ endTime: record.ts,
18492
+ ...record.isError ? { level: "ERROR" } : {},
18493
+ ...record.errorKind !== void 0 ? { statusMessage: record.errorKind } : {}
18494
+ });
18475
18495
  this.toolSpans.delete(record.toolCallId);
18476
18496
  }
18477
18497
  this.pendingToolResults.push(summarize(record.output));
18478
18498
  break;
18479
18499
  }
18500
+ case "compaction":
18501
+ this.endGeneration();
18502
+ this.seeded(["compaction", record.uuid], () => owner.span("compaction", {
18503
+ startTime: record.ts,
18504
+ ...record.text ? { output: record.text } : {}
18505
+ })).end({ endTime: record.ts });
18506
+ break;
18507
+ case "permission_grant": {
18508
+ const snapshot = record.input && typeof record.input === "object" ? record.input : void 0;
18509
+ const steps = Array.isArray(snapshot?.steps) ? snapshot.steps : [];
18510
+ this.seeded(["permission", record.uuid], () => owner.span("permission_grant", {
18511
+ startTime: record.ts,
18512
+ // `input`, not `output`: the list is what this record IS, not a result it
18513
+ // produced.
18514
+ input: steps,
18515
+ metadata: {
18516
+ // The true count, which is larger than `steps.length` when the mapper
18517
+ // truncated the snapshot to its byte budget.
18518
+ count: String(snapshot?.count ?? steps.length),
18519
+ full: String(snapshot?.full === true)
18520
+ }
18521
+ })).end({ endTime: record.ts });
18522
+ break;
18523
+ }
18480
18524
  case "usage":
18481
18525
  if (record.usage) {
18482
- this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number"));
18526
+ this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number").map(([key, value]) => [USAGE_WIRE_NAMES[key] ?? key, value]));
18483
18527
  }
18484
18528
  if (record.model)
18485
18529
  this.model = record.model;
@@ -18557,6 +18601,9 @@ var SessionEmitter = class {
18557
18601
  return this.options.parentAgentId ?? this.meta["parentSessionId"];
18558
18602
  }
18559
18603
  defaultTraceName() {
18604
+ const label = this.options.workflow?.label;
18605
+ if (label)
18606
+ return label;
18560
18607
  const agentType = this.agentType();
18561
18608
  if (agentType)
18562
18609
  return `subagent: ${agentType}`;
@@ -18565,6 +18612,27 @@ var SessionEmitter = class {
18565
18612
  }
18566
18613
  return this.options.agentType ? `subagent: ${this.options.agentType}` : `subagent ${this.options.agentId}`;
18567
18614
  }
18615
+ /**
18616
+ * The run this agent belongs to, flattened onto the trace.
18617
+ *
18618
+ * Flat scalars rather than a nested object: metadata is lifted onto the span one
18619
+ * attribute at a time downstream, so a nested value would arrive as a JSON blob that
18620
+ * nothing can group or filter on. `workflow_id` is the one that matters — it is what
18621
+ * lets a consumer collect the agents of one run out of a session that ran several.
18622
+ */
18623
+ workflowMetadata() {
18624
+ const workflow = this.options.workflow;
18625
+ if (!workflow)
18626
+ return {};
18627
+ return {
18628
+ workflow_id: workflow.id,
18629
+ ...workflow.name !== void 0 ? { workflow_name: workflow.name } : {},
18630
+ ...workflow.label !== void 0 ? { workflow_label: workflow.label } : {},
18631
+ ...workflow.phase !== void 0 ? { workflow_phase: workflow.phase } : {},
18632
+ ...workflow.phaseIndex !== void 0 ? { workflow_phase_index: workflow.phaseIndex } : {},
18633
+ ...workflow.attempt !== void 0 ? { workflow_attempt: workflow.attempt } : {}
18634
+ };
18635
+ }
18568
18636
  /** The session traces group under. Set from the path, or from `session_meta`. */
18569
18637
  grouping;
18570
18638
  ensureTrace(ts) {
@@ -18589,7 +18657,10 @@ var SessionEmitter = class {
18589
18657
  ...this.agentId() !== void 0 ? { agent_id: this.agentId() } : {},
18590
18658
  ...this.agentType() !== void 0 ? { agent_type: this.agentType() } : {},
18591
18659
  ...this.parentAgentId() !== void 0 ? { parent_agent_id: this.parentAgentId() } : {},
18592
- ...this.options.spawnDepth !== void 0 ? { spawn_depth: this.options.spawnDepth } : {}
18660
+ ...this.options.spawnDepth !== void 0 ? { spawn_depth: this.options.spawnDepth } : {},
18661
+ // snake_case for the same reason as `agent_id` above: trace-hub lifts each
18662
+ // key through unchanged, and these are read alongside it.
18663
+ ...this.workflowMetadata()
18593
18664
  },
18594
18665
  ...this.options.userId !== void 0 ? { userId: this.options.userId } : {},
18595
18666
  ...this.options.userEmail !== void 0 ? { userEmail: this.options.userEmail } : {},
@@ -18609,7 +18680,18 @@ var SessionEmitter = class {
18609
18680
  this.model = record.model;
18610
18681
  this.generation = this.seeded(["generation", id], () => owner.generation("assistant", {
18611
18682
  startTime: record.ts,
18612
- ...this.model !== void 0 ? { model: this.model } : {}
18683
+ ...this.model !== void 0 ? { model: this.model } : {},
18684
+ // What drove this model call — an installed skill, an MCP server's output, a
18685
+ // plugin. A flat scalar like every other metadata value emitted here, because
18686
+ // trace-hub lifts each key onto the span unchanged and a nested value would
18687
+ // arrive as a blob nothing can group or filter on. One lowercase word, so unlike
18688
+ // `agent_id` there is no camelCase to convert.
18689
+ //
18690
+ // Set once, at open, and never through update(): this method early-returns when
18691
+ // the generation is already open, so the value has to be constant across a
18692
+ // message group. Measured over the claude-code corpus, 0 of 1,381 attributed
18693
+ // groups carry two different tokens.
18694
+ ...record.attribution !== void 0 ? { metadata: { attribution: record.attribution } } : {}
18613
18695
  }));
18614
18696
  const input = this.buildInputMessages();
18615
18697
  if (input.inputMessages)
@@ -18649,12 +18731,14 @@ var SessionEmitter = class {
18649
18731
  this.generation.end({
18650
18732
  ...output ? { outputMessages: [{ role: "assistant", content: output }] } : {},
18651
18733
  ...this.model !== void 0 ? { model: this.model } : {},
18652
- ...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {}
18734
+ ...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {},
18735
+ ...this.generationError !== void 0 ? { level: "ERROR", statusMessage: this.generationError } : {}
18653
18736
  });
18654
18737
  this.generation = void 0;
18655
18738
  this.generationId = void 0;
18656
18739
  this.outputText = [];
18657
18740
  this.pendingUsage = void 0;
18741
+ this.generationError = void 0;
18658
18742
  }
18659
18743
  };
18660
18744
 
@@ -21746,6 +21830,7 @@ async function runForwarder(mapper, options = {}) {
21746
21830
  ...sub?.agentType !== void 0 ? { agentType: sub.agentType } : {},
21747
21831
  ...sub?.parentAgentId !== void 0 ? { parentAgentId: sub.parentAgentId } : {},
21748
21832
  ...sub?.spawnDepth !== void 0 ? { spawnDepth: sub.spawnDepth } : {},
21833
+ ...sub?.workflow !== void 0 ? { workflow: sub.workflow } : {},
21749
21834
  ...ids !== void 0 ? { ids } : {},
21750
21835
  ...userId !== void 0 ? { userId } : {},
21751
21836
  ...carried !== void 0 ? { resumeTurn: carried } : {}
@@ -21861,6 +21946,7 @@ import { join as join9 } from "node:path";
21861
21946
  var CLI_CREDENTIALS_PATH = join9(homedir3(), ".darkhunt", "credentials.json");
21862
21947
 
21863
21948
  // adapters/codex/dist/transcript.js
21949
+ import { readFileSync as readFileSync6 } from "node:fs";
21864
21950
  import { basename, join as join10 } from "node:path";
21865
21951
  import { homedir as homedir4 } from "node:os";
21866
21952
  function str(value) {
@@ -21897,17 +21983,128 @@ function textOf(content) {
21897
21983
  const parts = content.map((b) => b && typeof b === "object" ? str(b["text"]) : void 0).filter((t) => !!t);
21898
21984
  return parts.length > 0 ? parts.join("\n") : void 0;
21899
21985
  }
21986
+ var CODEX_APPROVAL_MODES = {
21987
+ never: "auto",
21988
+ "on-request": "ask"
21989
+ };
21990
+ var CODEX_SANDBOX_MODES = {
21991
+ "workspace-write": "workspace-write",
21992
+ "read-only": "read-only",
21993
+ "danger-full-access": "danger-full-access"
21994
+ };
21995
+ var PERMISSION_SNAPSHOT_LIMIT = 32e3;
21996
+ function budgeted(steps) {
21997
+ const kept = [];
21998
+ let bytes = 0;
21999
+ for (const step of steps) {
22000
+ bytes += JSON.stringify(step ?? null).length + 1;
22001
+ if (bytes > PERMISSION_SNAPSHOT_LIMIT)
22002
+ break;
22003
+ kept.push(step);
22004
+ }
22005
+ return kept;
22006
+ }
22007
+ function unwrapExecOutput(value) {
22008
+ if (typeof value !== "string")
22009
+ return { output: value };
22010
+ let parsed;
22011
+ try {
22012
+ parsed = JSON.parse(value);
22013
+ } catch {
22014
+ return { output: value };
22015
+ }
22016
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
22017
+ return { output: value };
22018
+ const bag = parsed;
22019
+ if (!Object.prototype.hasOwnProperty.call(bag, "output"))
22020
+ return { output: value };
22021
+ const metadata = bag["metadata"];
22022
+ if (!metadata || typeof metadata !== "object")
22023
+ return { output: value };
22024
+ const exitCode = num(metadata["exit_code"]);
22025
+ if (exitCode === void 0)
22026
+ return { output: value };
22027
+ return { output: bag["output"], exitCode };
22028
+ }
22029
+ function jwtClaims(token) {
22030
+ const parts = token.split(".");
22031
+ if (parts.length !== 3 || !parts[1])
22032
+ return void 0;
22033
+ try {
22034
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
22035
+ const claims = JSON.parse(json);
22036
+ return claims && typeof claims === "object" ? claims : void 0;
22037
+ } catch {
22038
+ return void 0;
22039
+ }
22040
+ }
22041
+ function emailFromAuth(raw) {
22042
+ let parsed;
22043
+ try {
22044
+ parsed = JSON.parse(raw);
22045
+ } catch {
22046
+ return void 0;
22047
+ }
22048
+ const tokens = parsed?.tokens;
22049
+ const token = tokens?.id_token;
22050
+ if (typeof token !== "string" || token === "")
22051
+ return void 0;
22052
+ const email = jwtClaims(token)?.["email"];
22053
+ return typeof email === "string" && email !== "" ? email : void 0;
22054
+ }
22055
+ function repositoryIdentity(url) {
22056
+ if (url === void 0)
22057
+ return void 0;
22058
+ const trimmed = url.trim();
22059
+ if (trimmed === "")
22060
+ return void 0;
22061
+ const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(\S+)$/.exec(trimmed);
22062
+ if (scp)
22063
+ return `${scp[1]}/${stripSuffix(scp[2])}`;
22064
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `ssh://${trimmed}`;
22065
+ try {
22066
+ const parsed = new URL(withScheme);
22067
+ if (!parsed.hostname)
22068
+ return trimmed;
22069
+ const path = stripSuffix(parsed.pathname.replace(/^\/+/, ""));
22070
+ return path === "" ? parsed.hostname : `${parsed.hostname}/${path}`;
22071
+ } catch {
22072
+ return trimmed;
22073
+ }
22074
+ }
22075
+ function stripSuffix(path) {
22076
+ return path.replace(/\/+$/, "").replace(/\.git$/i, "");
22077
+ }
21900
22078
  var codexTranscript = {
21901
22079
  vendor: "codex",
21902
22080
  sessionRoots() {
21903
22081
  return [join10(homedir4(), ".codex", "sessions")];
21904
22082
  },
21905
- // No `resolveUserId` yet, so Codex traces fall back to the configured `userId`.
21906
- // The identity exists but not as a plain field: `~/.codex/auth.json` holds
21907
- // `tokens.account_id` (a UUID, not a person) and an `email` claim inside the
21908
- // `id_token` JWT. Reading it means decoding an OAuth token and honouring its expiry
21909
- // a stale token would attribute sessions to a *wrong* address, which is worse than
21910
- // leaving them unattributed. Tracked separately.
22083
+ /**
22084
+ * `~/.codex/auth.json` -> the `email` claim of `tokens.id_token`.
22085
+ *
22086
+ * Codex records no identity in the rollout itself and none in plain text anywhere
22087
+ * else: `tokens.account_id` is a UUID, not a person. The address is a claim inside the
22088
+ * OAuth id_token, so reading it means decoding that token — hence {@link jwtClaims}.
22089
+ *
22090
+ * The token's own `exp` is deliberately not enforced. It is an hour long and the file
22091
+ * is only rewritten on sign-in and on refresh, so gating on expiry would leave most
22092
+ * sessions unattributed while answering a question nobody asked: an expired id_token
22093
+ * is evidence that a token needs refreshing, never evidence that a different person is
22094
+ * now signed in. Signing in as someone else rewrites this file, which is what makes
22095
+ * the stale-address worry unfounded — the claim always names the last account to
22096
+ * authenticate on this machine, which is the account that wrote these rollouts.
22097
+ *
22098
+ * `undefined` for API-key auth (`OPENAI_API_KEY` with no tokens), for a signed-out
22099
+ * agent, and for anything unreadable; the caller then falls back to configuration.
22100
+ */
22101
+ resolveUserId() {
22102
+ try {
22103
+ return emailFromAuth(readFileSync6(join10(homedir4(), ".codex", "auth.json"), "utf8"));
22104
+ } catch {
22105
+ return void 0;
22106
+ }
22107
+ },
21911
22108
  sessionIdFor(path) {
21912
22109
  const name = basename(path, ".jsonl");
21913
22110
  const match = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(name);
@@ -21930,12 +22127,20 @@ var codexTranscript = {
21930
22127
  const uuid = `codex:${offset}`;
21931
22128
  const base = { vendor: "codex", ts, uuid };
21932
22129
  if (raw.type === "session_meta") {
22130
+ const rawGit = payload["git"];
22131
+ const git = rawGit && typeof rawGit === "object" ? rawGit : void 0;
22132
+ const gitBranch = str(git?.["branch"]);
22133
+ const gitRepositoryUrl = repositoryIdentity(str(git?.["repository_url"]));
21933
22134
  return [
21934
22135
  {
21935
22136
  ...base,
21936
22137
  kind: "session_meta",
21937
22138
  meta: {
21938
22139
  ...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
22140
+ // Conditional spread rather than a plain assignment: the 7 `{}` blocks and
22141
+ // the 92 absent ones must leave the key absent, not present-and-undefined.
22142
+ ...gitBranch !== void 0 ? { gitBranch } : {},
22143
+ ...gitRepositoryUrl !== void 0 ? { gitRepositoryUrl } : {},
21939
22144
  ...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
21940
22145
  ...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
21941
22146
  // A Codex subagent is a rollout of its own, a peer of its parent in the
@@ -21951,15 +22156,61 @@ var codexTranscript = {
21951
22156
  }
21952
22157
  if (raw.type === "turn_context") {
21953
22158
  const model = str(payload["model"]);
21954
- if (!model)
22159
+ const approval = str(payload["approval_policy"]);
22160
+ const sandbox = payload["sandbox_policy"];
22161
+ const sandboxMode = (() => {
22162
+ if (!sandbox || typeof sandbox !== "object")
22163
+ return void 0;
22164
+ const bag = sandbox;
22165
+ return str(bag["mode"]) ?? str(bag["type"]);
22166
+ })();
22167
+ const meta = {
22168
+ ...model !== void 0 ? { model } : {},
22169
+ ...approval !== void 0 ? { approvalMode: CODEX_APPROVAL_MODES[approval] ?? approval } : {},
22170
+ ...sandboxMode !== void 0 ? { sandboxMode: CODEX_SANDBOX_MODES[sandboxMode] ?? sandboxMode } : {}
22171
+ };
22172
+ if (Object.keys(meta).length === 0)
21955
22173
  return [];
21956
- return [{ ...base, kind: "session_meta", meta: { model } }];
22174
+ return [{ ...base, kind: "session_meta", meta }];
21957
22175
  }
21958
22176
  const kind = str(payload["type"]);
21959
22177
  if (raw.type === "event_msg" && kind === "token_count") {
21960
22178
  const usage = mapTokenCount(payload["info"]);
21961
22179
  return usage ? [{ ...base, kind: "usage", usage }] : [];
21962
22180
  }
22181
+ if (raw.type === "compacted") {
22182
+ const note = str(payload["message"]);
22183
+ return [{ ...base, kind: "compaction", ...note ? { text: note } : {} }];
22184
+ }
22185
+ if (raw.type === "world_state") {
22186
+ const state = payload["state"];
22187
+ const perms = state && typeof state === "object" ? state["permissions"] : void 0;
22188
+ if (!perms || typeof perms !== "object")
22189
+ return [];
22190
+ const list = perms["approved_command_prefixes"];
22191
+ const steps = Array.isArray(list) ? list : [];
22192
+ return [
22193
+ {
22194
+ ...base,
22195
+ kind: "permission_grant",
22196
+ input: {
22197
+ steps: budgeted(steps),
22198
+ count: steps.length,
22199
+ ...payload["full"] === true ? { full: true } : {}
22200
+ }
22201
+ }
22202
+ ];
22203
+ }
22204
+ if (raw.type === "event_msg" && kind === "turn_aborted") {
22205
+ return [
22206
+ {
22207
+ ...base,
22208
+ kind: "user_message",
22209
+ isError: true,
22210
+ errorKind: `aborted:${str(payload["reason"]) ?? "unknown"}`
22211
+ }
22212
+ ];
22213
+ }
21963
22214
  if (raw.type !== "response_item")
21964
22215
  return [];
21965
22216
  switch (kind) {
@@ -21973,18 +22224,74 @@ var codexTranscript = {
21973
22224
  ...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
21974
22225
  }
21975
22226
  ];
21976
- case "function_call_output":
22227
+ case "function_call_output": {
22228
+ const flattened = textOf(payload["output"]) ?? payload["output"];
22229
+ const { output, exitCode } = unwrapExecOutput(flattened);
21977
22230
  return [
21978
22231
  {
21979
22232
  ...base,
21980
22233
  kind: "tool_result",
21981
- output: payload["output"],
22234
+ output,
22235
+ // Only where an exit status was actually found. On the 993 string outputs
22236
+ // that are not envelopes and the 144 array ones the key stays ABSENT rather
22237
+ // than `false`: the emitter tests `record.isError`, and "this tool reported
22238
+ // success" is a different claim from "nothing here reported an outcome".
22239
+ ...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
21982
22240
  ...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
21983
22241
  }
21984
22242
  ];
22243
+ }
22244
+ // Codex's OTHER tool-call shape, and the one it actually uses: `exec` and
22245
+ // `apply_patch` arrive as `custom_tool_call`, never as `function_call`. Measured
22246
+ // across 251 rollouts, 2179 of 5382 tool calls — 40% — took this shape, so
22247
+ // handling only `function_call` left every shell command and every file write
22248
+ // this endpoint made invisible to both the trace and anything reading it.
22249
+ case "custom_tool_call":
22250
+ return [
22251
+ {
22252
+ ...base,
22253
+ kind: "tool_call",
22254
+ toolName: str(payload["name"]) ?? "tool",
22255
+ input: payload["input"],
22256
+ ...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
22257
+ }
22258
+ ];
22259
+ case "custom_tool_call_output": {
22260
+ const flattened = textOf(payload["output"]) ?? payload["output"];
22261
+ const { output, exitCode } = unwrapExecOutput(flattened);
22262
+ return [
22263
+ {
22264
+ ...base,
22265
+ kind: "tool_result",
22266
+ output,
22267
+ ...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
22268
+ ...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
22269
+ }
22270
+ ];
22271
+ }
22272
+ // The model's own web reads. 49 across the corpus in 9 rollouts — `search` 31,
22273
+ // `open_page` 16, `find_in_page` 2 — every one of which mapped to nothing, so a
22274
+ // session where the agent searched and opened pages rendered as reasoning
22275
+ // followed by an answer with no source anywhere in it. The queries and the URLs
22276
+ // are the whole point of the record: they are the evidence of what the agent
22277
+ // read from outside the machine.
22278
+ //
22279
+ // `action` is passed through whole. It is already a vendor-neutral argument bag
22280
+ // ({type, query?, queries?, url?, pattern?}) and it is small — 322 bytes at the
22281
+ // worst, 121 on average across all 49.
22282
+ //
22283
+ // Deliberately unpaired: no `toolCallId`. Only 1 of the 49 lines carries an
22284
+ // `id`, `call_id` is the pairing key everywhere else in this stream, and no
22285
+ // response_item output line ever pairs with a search — so setting one would
22286
+ // register a span in the emitter's pairing table that nothing ever closes, and
22287
+ // `finish()` would end it at pass end and stamp a multi-minute duration on a
22288
+ // search that took seconds. Left unpaired, the emitter closes it at the call
22289
+ // timestamp, which is the truthful reading.
22290
+ case "web_search_call":
22291
+ return [{ ...base, kind: "tool_call", toolName: "web_search", input: payload["action"] }];
21985
22292
  case "reasoning": {
21986
22293
  const text = textOf(payload["summary"]) ?? textOf(payload["content"]);
21987
- return text ? [{ ...base, kind: "thinking", text }] : [];
22294
+ return [{ ...base, kind: "thinking", ...text !== void 0 ? { text } : {} }];
21988
22295
  }
21989
22296
  case "message": {
21990
22297
  const text = textOf(payload["content"]);