codex-copilot-dx 0.6.5 → 0.6.7

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.
@@ -396,6 +396,19 @@ function backupName(recipe) {
396
396
  return `${recipe.id}-${recipe.sourceAsarSha256.slice(0, 12)}`;
397
397
  }
398
398
 
399
+ export function pmStudioBackupRoot(home = os.homedir()) {
400
+ return path.join(home, ".local", "share", "codex-copilot-dx", "pm-studio-backups");
401
+ }
402
+
403
+ export function pmStudioPatchManifestPath({
404
+ home = os.homedir(),
405
+ backupRoot = pmStudioBackupRoot(home),
406
+ recipe,
407
+ } = {}) {
408
+ if (!recipe) throw new TypeError("PM Studio patch recipe is required");
409
+ return path.join(backupRoot, backupName(recipe), "manifest.json");
410
+ }
411
+
399
412
  function backupManifest(recipe, createdAt) {
400
413
  return {
401
414
  schema_version: 1,
@@ -512,13 +525,24 @@ function writePatchedBinaryRecord({ backup, inspection, operations }) {
512
525
  backup.manifest = manifest;
513
526
  }
514
527
 
515
- function assertPatchedBinaryRecord({ inspection, manifestPath }) {
528
+ export function assertPatchedBinaryRecord({ inspection, manifestPath, recipe }) {
516
529
  let manifest;
517
530
  try {
518
531
  manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
519
532
  } catch {
520
533
  throw setupError("PM_STUDIO_PATCH_RECORD_INVALID", "PM Studio patched-binary record is missing or invalid");
521
534
  }
535
+ if (recipe && (manifest?.schema_version !== 1
536
+ || manifest?.kind !== "ccdx-pm-studio-backup"
537
+ || manifest?.recipe_id !== recipe.id
538
+ || manifest?.app?.bundle_identifier !== recipe.bundleIdentifier
539
+ || manifest?.app?.version !== recipe.version
540
+ || manifest?.app?.build !== recipe.build
541
+ || manifest?.patched?.asar_sha256 !== recipe.patchedAsarSha256
542
+ || manifest?.patched?.asar_header_sha256 !== recipe.patchedHeaderSha256
543
+ || !integrityMatches(manifest?.patched?.electron_asar_integrity, "SHA256", recipe.patchedHeaderSha256))) {
544
+ throw setupError("PM_STUDIO_PATCH_RECORD_INVALID", "PM Studio patched-binary record does not match the installed patch recipe");
545
+ }
522
546
  const binaries = manifest?.patched?.binaries;
523
547
  if (!binaries
524
548
  || binaries.main_executable_sha256 !== inspection.executableIntegrity.executableSha256
@@ -593,7 +617,7 @@ export async function runPmStudioSetup({
593
617
  commandName = "ccdx",
594
618
  appPath = DEFAULT_PM_STUDIO_APP_PATH,
595
619
  home = os.homedir(),
596
- backupRoot = path.join(home, ".local", "share", "codex-copilot-dx", "pm-studio-backups"),
620
+ backupRoot = pmStudioBackupRoot(home),
597
621
  recipes = PM_STUDIO_RECIPES,
598
622
  operations: operationOverrides = {},
599
623
  logger = (line) => console.log(line),
@@ -629,7 +653,7 @@ export async function runPmStudioSetup({
629
653
  throw setupError("PM_STUDIO_BACKUP_INVALID", "Patched PM Studio has no matching verified backup; no files were changed");
630
654
  }
631
655
  const backup = validateBackup({ backupDir: finalBackupDir, recipe, operations });
632
- assertPatchedBinaryRecord({ inspection: source, manifestPath: backup.manifestPath });
656
+ assertPatchedBinaryRecord({ inspection: source, manifestPath: backup.manifestPath, recipe });
633
657
  emit(`[OK] PM Studio ${recipe.version} build ${recipe.build} is already patched; no backup or signing was repeated.`);
634
658
  emit(`[OK] Verified backup: ${backup.backupAppPath}`);
635
659
  emit(`[OK] Backup manifest: ${backup.manifestPath}`);
@@ -700,7 +724,7 @@ export async function runPmStudioSetup({
700
724
  installed.issues);
701
725
  }
702
726
  try {
703
- assertPatchedBinaryRecord({ inspection: installed, manifestPath: backup.manifestPath });
727
+ assertPatchedBinaryRecord({ inspection: installed, manifestPath: backup.manifestPath, recipe });
704
728
  } catch (error) {
705
729
  throw setupError("PM_STUDIO_INSTALL_VERIFY_FAILED",
706
730
  `Installed PM Studio differs from the verified staging record; do not launch it. ${recoveryMessage(backup)}`,
@@ -7,16 +7,33 @@ import {
7
7
  } from "./auth-profile.mjs";
8
8
  import { PM_STUDIO_RECIPES } from "./pm-studio-asar.mjs";
9
9
  import {
10
+ assertPatchedBinaryRecord,
10
11
  createPmStudioSetupOperations,
11
12
  DEFAULT_PM_STUDIO_APP_PATH,
12
13
  inspectPmStudioApp,
13
14
  PM_STUDIO_CLAUDE_AUTH_COMMAND,
15
+ pmStudioBackupRoot,
16
+ pmStudioPatchManifestPath,
14
17
  } from "./pm-studio-setup.mjs";
18
+ import { readAdapterStatus } from "./cli-status.mjs";
15
19
  import { checkRunningAdapter } from "./running-adapter.mjs";
16
20
  import { status } from "./status.mjs";
21
+ import {
22
+ cliOutputFormat,
23
+ cliOutputWidth,
24
+ formatResponsiveCliTable,
25
+ terminalCell,
26
+ } from "./cli-table.mjs";
17
27
 
18
28
  const PM_RELAY_HOST = "127.0.0.1";
19
29
  const PM_RELAY_PORT = 2026;
30
+ const PM_RELAY_CAPABILITY = "pm_studio_relay_v1";
31
+ const PM_RELAY_ROUTE_NAMES = Object.freeze([
32
+ "pm_models",
33
+ "pm_chat_completions",
34
+ "pm_responses",
35
+ "pm_embeddings",
36
+ ]);
20
37
 
21
38
  function selectRecipe(metadata, recipes) {
22
39
  return recipes.find((recipe) => recipe.version === String(metadata.version)
@@ -42,15 +59,52 @@ function inspectClaudeProfile(home, readCredentials) {
42
59
  }
43
60
  }
44
61
 
62
+ function inspectAdapterRuntime(adapter, runtimeStatus) {
63
+ const data = runtimeStatus?.data;
64
+ const health = adapter?.data;
65
+ const issues = [];
66
+ if (!data || typeof data !== "object") {
67
+ return { ok: false, baseUrl: runtimeStatus?.baseUrl || adapter?.baseUrl, data: null, issues: ["runtime status payload is missing"] };
68
+ }
69
+ if (data.version !== health?.version
70
+ || data.protocol_version !== health?.protocol_version
71
+ || data.instance_id !== health?.instance_id
72
+ || data.pid !== health?.pid) {
73
+ issues.push("health and runtime status do not describe the same adapter instance");
74
+ }
75
+ const capabilities = new Set(Array.isArray(data.capabilities) ? data.capabilities : []);
76
+ if (!capabilities.has(PM_RELAY_CAPABILITY)) issues.push("PM Studio relay capability is missing");
77
+ if (data.profiles?.claude?.mode !== "isolated") issues.push("Claude profile mode is not isolated");
78
+ if (data.profiles?.claude?.profile_current !== true) {
79
+ issues.push("running Claude credentials do not match the current isolated profile");
80
+ }
81
+ if (data.routing?.responses !== "codex") issues.push("/v1/responses is not routed to Codex");
82
+ if (data.routing?.messages !== "claude") issues.push("/v1/messages is not routed to Claude");
83
+ const routes = data.requests?.by_route;
84
+ for (const routeName of PM_RELAY_ROUTE_NAMES) {
85
+ if (!routes || typeof routes !== "object" || !Object.hasOwn(routes, routeName)) {
86
+ issues.push(`runtime status does not expose ${routeName}`);
87
+ }
88
+ }
89
+ return {
90
+ ...runtimeStatus,
91
+ ok: issues.length === 0,
92
+ issues,
93
+ };
94
+ }
95
+
45
96
  export async function inspectPmStudioStatus({
46
97
  appPath = DEFAULT_PM_STUDIO_APP_PATH,
47
98
  home = os.homedir(),
99
+ backupRoot = pmStudioBackupRoot(home),
48
100
  recipes = PM_STUDIO_RECIPES,
49
101
  operationOverrides = {},
50
102
  existsSync = fs.existsSync,
51
103
  inspectApp = inspectPmStudioApp,
104
+ verifyPatchRecord = assertPatchedBinaryRecord,
52
105
  readClaudeCredentials = readAuthProfileCredentials,
53
106
  checkRunningAdapterFn = checkRunningAdapter,
107
+ readAdapterStatusFn = readAdapterStatus,
54
108
  } = {}) {
55
109
  const operations = createPmStudioSetupOperations(operationOverrides);
56
110
  let app;
@@ -69,9 +123,24 @@ export async function inspectPmStudioStatus({
69
123
  bundleIdentifier: String(metadata.bundleIdentifier),
70
124
  };
71
125
  const recipe = selectRecipe(normalized, recipes);
72
- app = recipe
73
- ? { ...inspectApp({ appPath, recipe, operations }), recipe: recipe.id }
74
- : { state: "unsupported", metadata: normalized, issues: [], recipe: "" };
126
+ if (recipe) {
127
+ app = { ...inspectApp({ appPath, recipe, operations }), recipe: recipe.id };
128
+ if (app.state === "patched") {
129
+ const manifestPath = pmStudioPatchManifestPath({ home, backupRoot, recipe });
130
+ try {
131
+ verifyPatchRecord({ inspection: app, manifestPath, recipe });
132
+ app.patchRecord = { valid: true, manifestPath, reason: "" };
133
+ } catch (error) {
134
+ const reason = safeMessage(error);
135
+ app.patchRecipeMatched = true;
136
+ app.patchRecord = { valid: false, manifestPath, reason };
137
+ app.state = "drift";
138
+ app.issues = [...(Array.isArray(app.issues) ? app.issues : []), reason];
139
+ }
140
+ }
141
+ } else {
142
+ app = { state: "unsupported", metadata: normalized, issues: [], recipe: "" };
143
+ }
75
144
  } catch (error) {
76
145
  app = { state: "error", metadata: null, issues: [safeMessage(error)], recipe: "" };
77
146
  }
@@ -84,42 +153,125 @@ export async function inspectPmStudioStatus({
84
153
  } catch (error) {
85
154
  adapter = { ok: false, error };
86
155
  }
87
- const operational = app.state === "patched" && claude.valid && adapter?.ok === true;
88
- return { ok: operational, appPath, app, claude, adapter };
156
+ let runtime = { ok: false, baseUrl: adapter?.baseUrl, data: null, issues: ["adapter health check failed"] };
157
+ if (adapter?.ok === true) {
158
+ try {
159
+ runtime = inspectAdapterRuntime(adapter, await readAdapterStatusFn({
160
+ host: PM_RELAY_HOST,
161
+ port: PM_RELAY_PORT,
162
+ }));
163
+ } catch (error) {
164
+ runtime = { ok: false, baseUrl: adapter.baseUrl, data: null, issues: [safeMessage(error)] };
165
+ }
166
+ }
167
+ const operational = app.state === "patched"
168
+ && app.patchRecord?.valid === true
169
+ && claude.valid
170
+ && adapter?.ok === true
171
+ && runtime.ok === true;
172
+ return { ok: operational, appPath, app, claude, adapter, runtime };
89
173
  }
90
174
 
91
- function appStatusLine(result, commandName) {
175
+ function appStatusItem(result, commandName) {
92
176
  const { app } = result;
93
177
  const version = app.metadata ? `${app.metadata.version} build ${app.metadata.build}` : "";
94
- if (app.state === "patched") return status("ok", `PM Studio ${version} is patched and verified`);
95
- if (app.state === "clean") return status("warn", `PM Studio ${version} is supported but not patched; run ${commandName} pms setup`);
96
- if (app.state === "unsupported") return status("err", `PM Studio ${version} has no exact patch recipe; no files will be changed`);
97
- if (app.state === "drift") return status("err", `PM Studio ${version} does not match the clean or patched recipe: ${app.issues.join("; ")}`);
98
- if (app.state === "not_installed") return status("warn", `PM Studio is not installed at ${result.appPath}`);
99
- return status("err", `PM Studio could not be inspected: ${app.issues.join("; ")}`);
178
+ if (app.state === "patched" && app.patchRecord?.valid === true) {
179
+ return { kind: "ok", component: "App patch", detail: `${version}; patched and verified`, message: `PM Studio ${version} is patched and verified against its installed patch record` };
180
+ }
181
+ if (app.state === "patched") {
182
+ const reason = app.patchRecord?.reason || "installed patch record is missing";
183
+ return { kind: "err", component: "App patch", detail: `${version}; patch record not verified`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${reason}` };
184
+ }
185
+ if (app.state === "clean") return { kind: "warn", component: "App patch", detail: `${version}; supported, not patched`, message: `PM Studio ${version} is supported but not patched; run ${commandName} pms setup` };
186
+ if (app.state === "unsupported") return { kind: "err", component: "App patch", detail: `${version}; unsupported version`, message: `PM Studio ${version} has no exact patch recipe; no files will be changed` };
187
+ if (app.state === "drift" && app.patchRecipeMatched) {
188
+ return { kind: "err", component: "App patch", detail: `${version}; patch record drift`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${app.issues.join("; ")}` };
189
+ }
190
+ if (app.state === "drift") return { kind: "err", component: "App patch", detail: `${version}; integrity drift`, message: `PM Studio ${version} does not match the clean or patched recipe: ${app.issues.join("; ")}` };
191
+ if (app.state === "not_installed") return { kind: "warn", component: "App patch", detail: "not installed", message: `PM Studio is not installed at ${result.appPath}` };
192
+ return { kind: "err", component: "App patch", detail: "inspection failed", message: `PM Studio could not be inspected: ${app.issues.join("; ")}` };
100
193
  }
101
194
 
102
- export function formatPmStudioStatus(result, { commandName = "ccdx" } = {}) {
103
- const lines = [
104
- `${commandName} pms status`,
105
- appStatusLine(result, commandName),
106
- ];
195
+ function pmStudioStatusItems(result, commandName) {
196
+ const items = [appStatusItem(result, commandName)];
107
197
  if (result.claude.valid) {
108
198
  const account = result.claude.login ? ` for ${result.claude.login}` : "";
109
- lines.push(status("ok", `Isolated Claude profile is valid${account}`));
199
+ const detail = result.claude.login ? `${result.claude.login}; isolated profile valid` : "isolated profile valid";
200
+ items.push({ kind: "ok", component: "Claude profile", detail, message: `Isolated Claude profile is valid${account}` });
110
201
  } else {
111
202
  const reason = result.claude.configured && result.claude.reason ? ` (${result.claude.reason})` : "";
112
- lines.push(status("err", `Isolated Claude profile is not ready${reason}; run ${PM_STUDIO_CLAUDE_AUTH_COMMAND}`));
203
+ items.push({ kind: "err", component: "Claude profile", detail: "not ready", message: `Isolated Claude profile is not ready${reason}; run ${PM_STUDIO_CLAUDE_AUTH_COMMAND}` });
113
204
  }
114
- if (result.adapter?.ok) {
115
- lines.push(status("ok", `PM relay is available at ${result.adapter.baseUrl}`));
205
+ if (result.adapter?.ok && result.runtime?.ok) {
206
+ items.push({ kind: "ok", component: "PM relay", detail: `verified at ${result.adapter.baseUrl}`, message: `PM relay and isolated Claude routing are verified at ${result.adapter.baseUrl}` });
207
+ } else if (result.adapter?.ok) {
208
+ const reason = result.runtime?.issues?.join("; ") || "runtime status is unavailable";
209
+ items.push({ kind: "err", component: "PM relay", detail: "running but routing not ready", message: `The adapter is running at ${result.adapter.baseUrl}, but PM relay routing is not ready: ${reason}; stop it and run ${commandName} start` });
116
210
  } else if (result.adapter?.incompatible) {
117
- lines.push(status("err", `The adapter at ${result.adapter.baseUrl} is incompatible; stop it and run ${commandName} start`));
211
+ items.push({ kind: "err", component: "PM relay", detail: `incompatible at ${result.adapter.baseUrl}`, message: `The adapter at ${result.adapter.baseUrl} is incompatible; stop it and run ${commandName} start` });
212
+ } else {
213
+ items.push({ kind: "warn", component: "PM relay", detail: `not running at ${PM_RELAY_HOST}:${PM_RELAY_PORT}`, message: `PM relay is not running at http://${PM_RELAY_HOST}:${PM_RELAY_PORT}; run ${commandName} start` });
214
+ }
215
+ if (result.runtime?.ok) {
216
+ items.push({ kind: "info", component: "Routing", detail: "GPT -> PM bearer; Claude -> isolated profile", message: "Routing: PM GPT uses the PM Studio bearer; eligible Claude chat uses the isolated Claude profile" });
118
217
  } else {
119
- lines.push(status("warn", `PM relay is not running at http://${PM_RELAY_HOST}:${PM_RELAY_PORT}; run ${commandName} start`));
218
+ items.push({ kind: "info", component: "Routing", detail: "expected: GPT -> PM bearer; Claude -> isolated profile", message: "Expected routing: PM GPT uses the PM Studio bearer; eligible Claude chat uses the isolated Claude profile" });
219
+ }
220
+ return items;
221
+ }
222
+
223
+ function formatPmStudioStatusPlain(result, commandName, { sanitize = false } = {}) {
224
+ const lines = [
225
+ `${commandName} pms status`,
226
+ ...pmStudioStatusItems(result, commandName).map((item) => status(item.kind, item.message)),
227
+ ];
228
+ return (sanitize ? lines.map((line) => terminalCell(line, { fallback: "" })) : lines).join("\n");
229
+ }
230
+
231
+ function tableState(kind) {
232
+ return `[${String(kind).toUpperCase()}]`;
233
+ }
234
+
235
+ function formatPmStudioStatusTable(result, { commandName, output }) {
236
+ const items = pmStudioStatusItems(result, commandName);
237
+ const rows = items.map((item) => ({
238
+ component: item.component,
239
+ state: tableState(item.kind),
240
+ detail: item.detail,
241
+ }));
242
+ const table = formatResponsiveCliTable({
243
+ columns: [
244
+ { key: "component", label: "COMPONENT" },
245
+ { key: "state", label: "STATE" },
246
+ { key: "detail", label: "DETAIL" },
247
+ ],
248
+ compactColumns: [
249
+ { key: "component", label: "COMPONENT" },
250
+ { key: "state", label: "STATE" },
251
+ ],
252
+ rows,
253
+ width: cliOutputWidth(output),
254
+ });
255
+ const lines = [`${commandName} pms status`, table.output];
256
+ const details = table.compact ? items : items.filter((item) => item.kind === "warn" || item.kind === "err");
257
+ if (details.length) {
258
+ lines.push("", "Details:", ...details.map((item) => terminalCell(status(item.kind, item.message), { fallback: "" })));
259
+ }
260
+ return { output: lines.join("\n"), overflow: table.overflow };
261
+ }
262
+
263
+ export function formatPmStudioStatus(result, {
264
+ commandName = "ccdx",
265
+ format = "plain",
266
+ output = process.stdout,
267
+ } = {}) {
268
+ if (cliOutputFormat(format, output) === "plain") {
269
+ return formatPmStudioStatusPlain(result, commandName);
120
270
  }
121
- lines.push(status("info", "Routing: PM GPT uses the PM Studio bearer; eligible Claude chat uses the isolated Claude profile"));
122
- return lines.join("\n");
271
+ const table = formatPmStudioStatusTable(result, { commandName, output });
272
+ return format === "auto" && table.overflow
273
+ ? formatPmStudioStatusPlain(result, commandName, { sanitize: true })
274
+ : table.output;
123
275
  }
124
276
 
125
277
  export async function runPmStudioStatus(options = {}) {
@@ -1,22 +1,13 @@
1
+ import {
2
+ isClaudeCopilotCatalogEntry,
3
+ isClaudeCopilotModel,
4
+ } from "./models.mjs";
5
+
1
6
  function catalogData(catalog) {
2
7
  if (Array.isArray(catalog)) return catalog;
3
8
  return Array.isArray(catalog?.data) ? catalog.data : [];
4
9
  }
5
10
 
6
- function isClaudeCatalogEntry(model) {
7
- const id = String(model?.id || "").trim();
8
- const vendor = String(model?.vendor || "").trim().toLowerCase();
9
- return Boolean(id) && (vendor === "anthropic" || id.toLowerCase().startsWith("claude-"));
10
- }
11
-
12
- function isAllowedClaudeModel(model) {
13
- return isClaudeCatalogEntry(model)
14
- && String(model?.vendor || "").trim().toLowerCase() === "anthropic"
15
- && model?.model_picker_enabled !== false
16
- && Array.isArray(model?.supported_endpoints)
17
- && model.supported_endpoints.includes("/chat/completions");
18
- }
19
-
20
11
  export function profileRouting({ claudeMode = "inherited", claudeConfigured = false } = {}) {
21
12
  const isolated = claudeMode === "isolated" || claudeConfigured === true;
22
13
  return Object.freeze({ responses: "codex", messages: isolated ? "claude" : "codex" });
@@ -44,9 +35,9 @@ export function createPmStudioModelRouter({ getCatalog, isClaudeEnabled } = {})
44
35
  const known = new Set();
45
36
  for (const model of catalogData(catalog)) {
46
37
  const id = String(model?.id || "").trim();
47
- if (!id || !isClaudeCatalogEntry(model)) continue;
38
+ if (!id || !isClaudeCopilotCatalogEntry(model)) continue;
48
39
  known.add(id);
49
- if (enabled && isAllowedClaudeModel(model) && !allowed.has(id)) allowed.set(id, model);
40
+ if (enabled && isClaudeCopilotModel(model) && !allowed.has(id)) allowed.set(id, model);
50
41
  }
51
42
  cachedCatalog = catalog;
52
43
  cachedEnabled = enabled;
@@ -46,6 +46,15 @@ export function createProfileRuntime({
46
46
  };
47
47
  const credentials = readCredentials();
48
48
  const claudeProfile = safeProfileStatus(credentials);
49
+ const claudeCredentialFingerprint = credentials.valid
50
+ ? githubTokenFingerprint(credentials.token)
51
+ : "";
52
+ const isClaudeProfileCurrent = () => {
53
+ if (!claudeCredentialFingerprint) return false;
54
+ const current = readCredentials();
55
+ return current.valid === true
56
+ && githubTokenFingerprint(current.token) === claudeCredentialFingerprint;
57
+ };
49
58
  let codexCredentialFingerprint = "";
50
59
  try {
51
60
  const codexCredentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
@@ -65,6 +74,7 @@ export function createProfileRuntime({
65
74
  claudeProfile,
66
75
  codexCredentialFingerprint,
67
76
  claudeCredentialFingerprint: "",
77
+ isClaudeProfileCurrent,
68
78
  });
69
79
  }
70
80
 
@@ -86,8 +96,7 @@ export function createProfileRuntime({
86
96
  claudeMode: "isolated",
87
97
  claudeProfile,
88
98
  codexCredentialFingerprint,
89
- claudeCredentialFingerprint: credentials.valid
90
- ? githubTokenFingerprint(credentials.token)
91
- : "",
99
+ claudeCredentialFingerprint,
100
+ isClaudeProfileCurrent,
92
101
  });
93
102
  }
@@ -1,4 +1,5 @@
1
1
  import { httpError } from "./http-transport.mjs";
2
+ import { clearResponsesToolOutputPartsCache } from "./responses-content.mjs";
2
3
  import { loadRuntimeConfig, parsePositiveInteger, RUNTIME_DEFAULTS } from "./runtime-config.mjs";
3
4
 
4
5
  const DEFAULT_MAX_BYTES = RUNTIME_DEFAULTS.responseHistoryMaxBytes;
@@ -201,17 +202,26 @@ export function responseHistoryMaterializedBytes(responseId) {
201
202
  return chain.reduce((bytes, entry) => bytes + entry.bytes, 0);
202
203
  }
203
204
 
204
- export function materializeResponseHistory(responseId) {
205
+ export function materializeResponseHistory(responseId, { assertActive } = {}) {
206
+ assertActive?.();
205
207
  const { chain, rootId } = responseHistoryChain(responseId);
206
208
  const items = [];
207
- for (const entry of chain.reverse()) items.push(...entry.inputItems, ...entry.outputItems);
209
+ for (let index = 0; index < chain.length; index += 1) {
210
+ if ((index & 63) === 0) assertActive?.();
211
+ const entry = chain[chain.length - index - 1];
212
+ items.push(...entry.inputItems, ...entry.outputItems);
213
+ }
214
+ assertActive?.();
208
215
  const materialized = cloneJson(items);
216
+ assertActive?.();
209
217
  touchTree(rootId);
210
218
  return materialized;
211
219
  }
212
220
 
213
221
  export function rememberResponseHistoryNode({ id, parentId, inputItems, outputItems, takeOwnership = false }) {
214
222
  if (!id || !Array.isArray(inputItems)) return;
223
+ clearResponsesToolOutputPartsCache(inputItems);
224
+ clearResponsesToolOutputPartsCache(outputItems);
215
225
  const bytes = jsonByteLength([inputItems, outputItems]);
216
226
  if (bytes > maxBytes) {
217
227
  if (histories.has(id)) removeSubtree(id);