acpx 0.9.0 → 0.11.0

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 (33) hide show
  1. package/README.md +23 -19
  2. package/dist/{cli-Bf3yjqzE.js → cli-CC2w0U-A.js} +4 -4
  3. package/dist/{cli-Bf3yjqzE.js.map → cli-CC2w0U-A.js.map} +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +685 -67
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-BssohYqM.d.ts → client-j3sLnpcM.d.ts} +27 -4
  9. package/dist/client-j3sLnpcM.d.ts.map +1 -0
  10. package/dist/{flags-C-rwARqg.js → flags-BKjjl3tF.js} +4 -4
  11. package/dist/flags-BKjjl3tF.js.map +1 -0
  12. package/dist/{flows-WLs26_5Y.js → flows-BabqiU0u.js} +5 -4
  13. package/dist/flows-BabqiU0u.js.map +1 -0
  14. package/dist/flows.d.ts +1 -1
  15. package/dist/flows.d.ts.map +1 -1
  16. package/dist/flows.js +1 -1
  17. package/dist/{live-checkpoint-D5d-K9s1.js → live-checkpoint-BZrk9Mjz.js} +894 -384
  18. package/dist/live-checkpoint-BZrk9Mjz.js.map +1 -0
  19. package/dist/{output-DPg20dvn.js → output-D_BGt1YI.js} +180 -98
  20. package/dist/output-D_BGt1YI.js.map +1 -0
  21. package/dist/runtime.d.ts +71 -5
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +188 -32
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/{session-options-CFudjdkU.d.ts → session-options-Co1oGEK8.d.ts} +22 -2
  26. package/dist/{session-options-CFudjdkU.d.ts.map → session-options-Co1oGEK8.d.ts.map} +1 -1
  27. package/package.json +23 -17
  28. package/skills/acpx/SKILL.md +66 -5
  29. package/dist/client-BssohYqM.d.ts.map +0 -1
  30. package/dist/flags-C-rwARqg.js.map +0 -1
  31. package/dist/flows-WLs26_5Y.js.map +0 -1
  32. package/dist/live-checkpoint-D5d-K9s1.js.map +0 -1
  33. package/dist/output-DPg20dvn.js.map +0 -1
@@ -1,14 +1,14 @@
1
- import fs, { statSync } from "node:fs";
1
+ import fs, { readFileSync, statSync } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import path from "node:path";
4
4
  import fs$1 from "node:fs/promises";
5
5
  import os from "node:os";
6
+ import { randomUUID } from "node:crypto";
6
7
  import { execFile, spawn } from "node:child_process";
7
8
  import { Readable, Writable } from "node:stream";
8
- import { ClientSideConnection, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
9
+ import { ClientSideConnection, PROTOCOL_VERSION, RequestError } from "@agentclientprotocol/sdk";
9
10
  import readline from "node:readline/promises";
10
11
  import { promisify } from "node:util";
11
- import { randomUUID } from "node:crypto";
12
12
  //#region src/errors.ts
13
13
  var AcpxOperationalError = class extends Error {
14
14
  outputCode;
@@ -223,12 +223,12 @@ const SESSION_RECORD_SCHEMA = "acpx.session.v1";
223
223
  //#endregion
224
224
  //#region src/acp/error-shapes.ts
225
225
  const RESOURCE_NOT_FOUND_ACP_CODES = new Set([-32001, -32002]);
226
- function asRecord$7(value) {
226
+ function asRecord$8(value) {
227
227
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
228
228
  return value;
229
229
  }
230
230
  function toAcpErrorPayload(value) {
231
- const record = asRecord$7(value);
231
+ const record = asRecord$8(value);
232
232
  if (!record) return;
233
233
  if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
234
234
  if (typeof record.message !== "string" || record.message.length === 0) return;
@@ -242,7 +242,7 @@ function extractAcpErrorInternal(value, depth) {
242
242
  if (depth > 5) return;
243
243
  const direct = toAcpErrorPayload(value);
244
244
  if (direct) return direct;
245
- const record = asRecord$7(value);
245
+ const record = asRecord$8(value);
246
246
  if (!record) return;
247
247
  return extractNestedAcpError(record, depth);
248
248
  }
@@ -277,7 +277,7 @@ function hasSessionNotFoundHint(value, depth = 0) {
277
277
  if (depth > 4) return false;
278
278
  if (isSessionNotFoundText(value)) return true;
279
279
  if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
280
- const record = asRecord$7(value);
280
+ const record = asRecord$8(value);
281
281
  if (!record) return false;
282
282
  return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
283
283
  }
@@ -297,7 +297,7 @@ function isAcpResourceNotFoundError(error) {
297
297
  //#region src/acp/error-normalization.ts
298
298
  const AUTH_REQUIRED_ACP_CODES = new Set([-32e3]);
299
299
  const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
300
- function asRecord$6(value) {
300
+ function asRecord$7(value) {
301
301
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
302
302
  return value;
303
303
  }
@@ -318,7 +318,7 @@ function isAcpAuthRequiredPayload(acp) {
318
318
  if (!acp) return false;
319
319
  if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
320
320
  if (isAuthRequiredMessage(acp.message)) return true;
321
- const data = asRecord$6(acp.data);
321
+ const data = asRecord$7(acp.data);
322
322
  if (!data) return false;
323
323
  return hasAuthRequiredData(data);
324
324
  }
@@ -338,7 +338,7 @@ function isOutputErrorOrigin(value) {
338
338
  return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
339
339
  }
340
340
  function readOutputErrorMeta(error) {
341
- const record = asRecord$6(error);
341
+ const record = asRecord$7(error);
342
342
  if (!record) return {};
343
343
  return {
344
344
  outputCode: isOutputErrorCode(record.outputCode) ? record.outputCode : void 0,
@@ -356,7 +356,7 @@ function isNoSessionLike(error) {
356
356
  }
357
357
  function isUsageLike(error) {
358
358
  if (!(error instanceof Error)) return false;
359
- return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$6(error)?.code === "commander.invalidArgument";
359
+ return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$7(error)?.code === "commander.invalidArgument";
360
360
  }
361
361
  function formatErrorMessage(error) {
362
362
  return formatUnknownErrorMessage(error);
@@ -364,7 +364,7 @@ function formatErrorMessage(error) {
364
364
  function isAcpQueryClosedBeforeResponseError(error) {
365
365
  const acp = extractAcpError(error);
366
366
  if (!acp || acp.code !== -32603) return false;
367
- const details = asRecord$6(acp.data)?.details;
367
+ const details = asRecord$7(acp.data)?.details;
368
368
  if (typeof details !== "string") return false;
369
369
  return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
370
370
  }
@@ -432,7 +432,8 @@ function exitCodeForOutputErrorCode(code) {
432
432
  const ACP_ADAPTER_PACKAGE_RANGES = {
433
433
  pi: "^0.0.26",
434
434
  codex: "^0.0.44",
435
- claude: "^0.36.1"
435
+ claude: "^0.37.0",
436
+ mux: "^0.27.0"
436
437
  };
437
438
  const AGENT_REGISTRY = {
438
439
  pi: `npx pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`,
@@ -443,10 +444,12 @@ const AGENT_REGISTRY = {
443
444
  cursor: "cursor-agent acp",
444
445
  copilot: "copilot --acp --stdio",
445
446
  droid: "droid exec --output-format acp",
447
+ "fast-agent": "uvx fast-agent-mcp acp",
446
448
  iflow: "iflow --experimental-acp",
447
449
  kilocode: "npx -y @kilocode/cli acp",
448
450
  kimi: "kimi acp",
449
451
  kiro: "kiro-cli-chat acp",
452
+ mux: `npx -y mux@${ACP_ADAPTER_PACKAGE_RANGES.mux} acp`,
450
453
  opencode: "npx -y opencode-ai acp",
451
454
  qoder: "qodercli --acp",
452
455
  qwen: "qwen --acp",
@@ -658,7 +661,7 @@ var PromptInputValidationError = class extends Error {
658
661
  this.name = "PromptInputValidationError";
659
662
  }
660
663
  };
661
- function asRecord$5(value) {
664
+ function asRecord$6(value) {
662
665
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
663
666
  return value;
664
667
  }
@@ -676,28 +679,28 @@ function isAudioMimeType(value) {
676
679
  return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
677
680
  }
678
681
  function isTextBlock(value) {
679
- const record = asRecord$5(value);
682
+ const record = asRecord$6(value);
680
683
  return record?.type === "text" && typeof record.text === "string";
681
684
  }
682
685
  function isImageBlock(value) {
683
- const record = asRecord$5(value);
686
+ const record = asRecord$6(value);
684
687
  return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
685
688
  }
686
689
  function isAudioBlock(value) {
687
- const record = asRecord$5(value);
690
+ const record = asRecord$6(value);
688
691
  return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
689
692
  }
690
693
  function isResourceLinkBlock(value) {
691
- const record = asRecord$5(value);
694
+ const record = asRecord$6(value);
692
695
  return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
693
696
  }
694
697
  function isResourcePayload(value) {
695
- const record = asRecord$5(value);
698
+ const record = asRecord$6(value);
696
699
  if (!record || !isNonEmptyString(record.uri)) return false;
697
700
  return record.text === void 0 || typeof record.text === "string";
698
701
  }
699
702
  function isResourceBlock(value) {
700
- const record = asRecord$5(value);
703
+ const record = asRecord$6(value);
701
704
  return record?.type === "resource" && isResourcePayload(record.resource);
702
705
  }
703
706
  const CONTENT_BLOCK_VALIDATORS = [
@@ -741,11 +744,11 @@ function validateResourceLinkContentBlock(record, index) {
741
744
  if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
742
745
  }
743
746
  function validateResourceContentBlock(record, index) {
744
- if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
747
+ if (!asRecord$6(record.resource)) return `prompt[${index}] resource block must include a resource object`;
745
748
  return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
746
749
  }
747
750
  function getContentBlockValidationError(value, index) {
748
- const record = asRecord$5(value);
751
+ const record = asRecord$6(value);
749
752
  if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
750
753
  const validator = contentBlockErrorValidator(record.type);
751
754
  return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
@@ -827,6 +830,104 @@ function resourceBlockDisplayText(block) {
827
830
  return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
828
831
  }
829
832
  //#endregion
833
+ //#region src/acp/jsonrpc.ts
834
+ function asRecord$5(value) {
835
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
836
+ return value;
837
+ }
838
+ function hasValidId(value) {
839
+ return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
840
+ }
841
+ function isErrorObject(value) {
842
+ const record = asRecord$5(value);
843
+ return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
844
+ }
845
+ function hasResultOrError(value) {
846
+ const hasResult = Object.hasOwn(value, "result");
847
+ const hasError = Object.hasOwn(value, "error");
848
+ if (hasResult && hasError) return false;
849
+ if (!hasResult && !hasError) return false;
850
+ if (hasError && !isErrorObject(value.error)) return false;
851
+ return true;
852
+ }
853
+ function hasMethod(value) {
854
+ return typeof value.method === "string" && value.method.length > 0;
855
+ }
856
+ function isJsonRpcRequest(value) {
857
+ return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
858
+ }
859
+ function isJsonRpcNotificationRecord(value) {
860
+ return hasMethod(value) && !Object.hasOwn(value, "id");
861
+ }
862
+ function isJsonRpcResponse(value) {
863
+ if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
864
+ return hasResultOrError(value);
865
+ }
866
+ function isAcpJsonRpcMessage(value) {
867
+ const record = asRecord$5(value);
868
+ if (!record || record.jsonrpc !== "2.0") return false;
869
+ return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
870
+ }
871
+ function isJsonRpcNotification(message) {
872
+ return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
873
+ }
874
+ function isSessionUpdateNotification(message) {
875
+ return isJsonRpcNotification(message) && message.method === "session/update";
876
+ }
877
+ function extractSessionUpdateNotification(message) {
878
+ if (!isSessionUpdateNotification(message)) return;
879
+ const params = asRecord$5(message.params);
880
+ if (!params) return;
881
+ const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
882
+ if (!sessionId) return;
883
+ const update = asRecord$5(params.update);
884
+ if (!update || typeof update.sessionUpdate !== "string") return;
885
+ return {
886
+ sessionId,
887
+ update
888
+ };
889
+ }
890
+ function parsePromptStopReason(message) {
891
+ if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
892
+ const record = asRecord$5(message.result);
893
+ if (!record) return;
894
+ return typeof record.stopReason === "string" ? record.stopReason : void 0;
895
+ }
896
+ function parseJsonRpcErrorMessage(message) {
897
+ if (!Object.hasOwn(message, "error")) return;
898
+ const errorRecord = asRecord$5(message.error);
899
+ if (!errorRecord || typeof errorRecord.message !== "string") return;
900
+ return errorRecord.message;
901
+ }
902
+ //#endregion
903
+ //#region src/session/event-log.ts
904
+ const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
905
+ function sessionBaseDir$1() {
906
+ return path.join(os.homedir(), ".acpx", "sessions");
907
+ }
908
+ function safeSessionId(sessionId) {
909
+ return encodeURIComponent(sessionId);
910
+ }
911
+ function sessionEventActivePath(sessionId) {
912
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
913
+ }
914
+ function sessionEventSegmentPath(sessionId, segment) {
915
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
916
+ }
917
+ function sessionEventLockPath(sessionId) {
918
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
919
+ }
920
+ function defaultSessionEventLog(sessionId) {
921
+ return {
922
+ active_path: sessionEventActivePath(sessionId),
923
+ segment_count: 5,
924
+ max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
925
+ max_segments: 5,
926
+ last_write_at: void 0,
927
+ last_write_error: null
928
+ };
929
+ }
930
+ //#endregion
830
931
  //#region src/acp/agent-session-id.ts
831
932
  const AGENT_SESSION_ID_META_KEYS = ["agentSessionId", "sessionId"];
832
933
  function normalizeAgentSessionId(value) {
@@ -889,173 +990,15 @@ function serializeSessionRecordForDisk(record) {
889
990
  messages: canonical.messages,
890
991
  updated_at: canonical.updated_at,
891
992
  cumulative_token_usage: canonical.cumulative_token_usage,
993
+ cumulative_cost: canonical.cumulative_cost,
892
994
  request_token_usage: canonical.request_token_usage,
893
- acpx: canonical.acpx
894
- };
895
- }
896
- //#endregion
897
- //#region src/perf-metrics.ts
898
- const counters = /* @__PURE__ */ new Map();
899
- const gauges = /* @__PURE__ */ new Map();
900
- const timings = /* @__PURE__ */ new Map();
901
- function hrNow() {
902
- return process.hrtime.bigint();
903
- }
904
- function durationMs(start) {
905
- return Number(process.hrtime.bigint() - start) / 1e6;
906
- }
907
- function roundMetric(value) {
908
- return Number(value.toFixed(3));
909
- }
910
- function incrementPerfCounter(name, delta = 1) {
911
- counters.set(name, (counters.get(name) ?? 0) + delta);
912
- }
913
- function setPerfGauge(name, value) {
914
- gauges.set(name, value);
915
- }
916
- function recordPerfDuration(name, durationMsValue) {
917
- const next = timings.get(name) ?? {
918
- count: 0,
919
- totalMs: 0,
920
- maxMs: 0
921
- };
922
- next.count += 1;
923
- next.totalMs += durationMsValue;
924
- next.maxMs = Math.max(next.maxMs, durationMsValue);
925
- timings.set(name, next);
926
- }
927
- async function measurePerf(name, run) {
928
- const startedAt = hrNow();
929
- try {
930
- return await run();
931
- } finally {
932
- recordPerfDuration(name, durationMs(startedAt));
933
- }
934
- }
935
- function startPerfTimer(name) {
936
- const startedAt = hrNow();
937
- return () => {
938
- const elapsedMs = durationMs(startedAt);
939
- recordPerfDuration(name, elapsedMs);
940
- return elapsedMs;
941
- };
942
- }
943
- function getPerfMetricsSnapshot() {
944
- return {
945
- counters: Object.fromEntries(counters.entries()),
946
- gauges: Object.fromEntries(gauges.entries()),
947
- timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
948
- count: bucket.count,
949
- totalMs: roundMetric(bucket.totalMs),
950
- maxMs: roundMetric(bucket.maxMs)
951
- }]))
952
- };
953
- }
954
- function resetPerfMetrics() {
955
- counters.clear();
956
- gauges.clear();
957
- timings.clear();
958
- }
959
- function formatPerfMetric(name, durationMsValue) {
960
- return `${name}=${roundMetric(durationMsValue)}ms`;
961
- }
962
- //#endregion
963
- //#region src/persisted-key-policy.ts
964
- const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
965
- const ZED_TAG_KEYS = new Set([
966
- "User",
967
- "Agent",
968
- "Resume",
969
- "Text",
970
- "Mention",
971
- "Image",
972
- "Audio",
973
- "Thinking",
974
- "RedactedThinking",
975
- "ToolUse"
976
- ]);
977
- const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
978
- const OPAQUE_VALUE_PATHS = new Set([
979
- "agent_capabilities",
980
- "messages.Agent.content.ToolUse.input",
981
- "acpx.desired_config_options",
982
- "acpx.config_options"
983
- ]);
984
- function isRecord(value) {
985
- return !!value && typeof value === "object" && !Array.isArray(value);
986
- }
987
- function joinPath(path) {
988
- return path.join(".");
989
- }
990
- function isAllowedKey(path, key) {
991
- if (ZED_TAG_KEYS.has(key)) return true;
992
- return false;
993
- }
994
- function shouldSkipKeyRule(path) {
995
- return MAP_OBJECT_PATHS.has(joinPath(path));
996
- }
997
- function shouldSkipDescend(path) {
998
- return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
999
- }
1000
- function isToolResultOutputTail(path, toolResultsIndex) {
1001
- return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
1002
- }
1003
- function isToolResultOutputPath(path) {
1004
- if (path.length < 5 || path[path.length - 1] !== "output") return false;
1005
- const toolResultsIndex = path.lastIndexOf("tool_results");
1006
- if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
1007
- return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
1008
- }
1009
- function collectViolations(value, path, violations) {
1010
- if (Array.isArray(value)) {
1011
- for (const entry of value) collectViolations(entry, path, violations);
1012
- return;
1013
- }
1014
- if (!isRecord(value)) return;
1015
- const skipKeyRule = shouldSkipKeyRule(path);
1016
- for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
1017
- }
1018
- function collectKeyViolation(child, key, path, skipKeyRule, violations) {
1019
- if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1020
- const childPath = [...path, key];
1021
- if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
1022
- }
1023
- function findPersistedKeyPolicyViolations(value) {
1024
- const violations = [];
1025
- collectViolations(value, [], violations);
1026
- return violations;
1027
- }
1028
- function assertPersistedKeyPolicy(value) {
1029
- const violations = findPersistedKeyPolicyViolations(value);
1030
- if (violations.length === 0) return;
1031
- throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
1032
- }
1033
- //#endregion
1034
- //#region src/session/event-log.ts
1035
- const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
1036
- function sessionBaseDir$1() {
1037
- return path.join(os.homedir(), ".acpx", "sessions");
1038
- }
1039
- function safeSessionId(sessionId) {
1040
- return encodeURIComponent(sessionId);
1041
- }
1042
- function sessionEventActivePath(sessionId) {
1043
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
1044
- }
1045
- function sessionEventSegmentPath(sessionId, segment) {
1046
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
1047
- }
1048
- function sessionEventLockPath(sessionId) {
1049
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
1050
- }
1051
- function defaultSessionEventLog(sessionId) {
1052
- return {
1053
- active_path: sessionEventActivePath(sessionId),
1054
- segment_count: 5,
1055
- max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
1056
- max_segments: 5,
1057
- last_write_at: void 0,
1058
- last_write_error: null
995
+ acpx: canonical.acpx,
996
+ imported_from: canonical.importedFrom ? {
997
+ record_id: canonical.importedFrom.recordId,
998
+ cwd_original: canonical.importedFrom.cwdOriginal,
999
+ exported_by: canonical.importedFrom.exportedBy,
1000
+ exported_at: canonical.importedFrom.exportedAt
1001
+ } : void 0
1059
1002
  };
1060
1003
  }
1061
1004
  //#endregion
@@ -1070,6 +1013,43 @@ function hasOwn$1(source, key) {
1070
1013
  function isStringArray(value) {
1071
1014
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
1072
1015
  }
1016
+ function hasModelConfigOption(options) {
1017
+ if (!Array.isArray(options)) return false;
1018
+ return options.some((entry) => {
1019
+ const option = asRecord$4(entry);
1020
+ return option?.category === "model" || option?.id === "model";
1021
+ });
1022
+ }
1023
+ function parseConfigOptions(raw) {
1024
+ if (!Array.isArray(raw) || !raw.every((entry) => asRecord$4(entry) !== void 0)) return;
1025
+ return raw;
1026
+ }
1027
+ function parseAvailableCommand(raw) {
1028
+ if (typeof raw === "string") {
1029
+ const name = raw.trim();
1030
+ return name ? { name } : void 0;
1031
+ }
1032
+ const record = asRecord$4(raw);
1033
+ if (!record) return;
1034
+ const name = parseNonEmptyString(record.name);
1035
+ if (!name) return;
1036
+ const description = parseNonEmptyString(record.description);
1037
+ return {
1038
+ name,
1039
+ ...description ? { description } : {},
1040
+ ...typeof record.has_input === "boolean" ? { has_input: record.has_input } : {}
1041
+ };
1042
+ }
1043
+ function parseAvailableCommands(raw) {
1044
+ if (!Array.isArray(raw)) return;
1045
+ const commands = raw.map((entry) => parseAvailableCommand(entry)).filter((entry) => entry !== void 0);
1046
+ return commands.length > 0 ? commands : void 0;
1047
+ }
1048
+ function parseNonEmptyString(value) {
1049
+ if (typeof value !== "string") return;
1050
+ const trimmed = value.trim();
1051
+ return trimmed ? trimmed : void 0;
1052
+ }
1073
1053
  function parseTokenUsage(raw) {
1074
1054
  if (raw === void 0 || raw === null) return;
1075
1055
  const record = asRecord$4(raw);
@@ -1079,7 +1059,9 @@ function parseTokenUsage(raw) {
1079
1059
  "input_tokens",
1080
1060
  "output_tokens",
1081
1061
  "cache_creation_input_tokens",
1082
- "cache_read_input_tokens"
1062
+ "cache_read_input_tokens",
1063
+ "thought_tokens",
1064
+ "total_tokens"
1083
1065
  ]) {
1084
1066
  const value = record[field];
1085
1067
  if (value === void 0) continue;
@@ -1091,6 +1073,32 @@ function parseTokenUsage(raw) {
1091
1073
  function isNonNegativeFiniteNumber(value) {
1092
1074
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
1093
1075
  }
1076
+ function parseUsageCost(raw) {
1077
+ if (raw === void 0 || raw === null) return;
1078
+ const record = asRecord$4(raw);
1079
+ if (!record) return null;
1080
+ return parseUsageCostRecord(record);
1081
+ }
1082
+ function parseUsageCostRecord(record) {
1083
+ const amount = parseCostAmount(record.amount);
1084
+ const currency = parseCostCurrency(record.currency);
1085
+ if (amount === null || currency === null) return null;
1086
+ const cost = {
1087
+ ...amount !== void 0 ? { amount } : {},
1088
+ ...currency !== void 0 ? { currency } : {}
1089
+ };
1090
+ return Object.keys(cost).length > 0 ? cost : void 0;
1091
+ }
1092
+ function parseCostAmount(value) {
1093
+ if (value === void 0) return;
1094
+ return isNonNegativeFiniteNumber(value) ? value : null;
1095
+ }
1096
+ function parseCostCurrency(value) {
1097
+ if (value === void 0) return;
1098
+ if (typeof value !== "string") return null;
1099
+ const currency = value.trim();
1100
+ return currency.length > 0 ? currency : void 0;
1101
+ }
1094
1102
  function parseRequestTokenUsage(raw) {
1095
1103
  if (raw === void 0 || raw === null) return;
1096
1104
  const record = asRecord$4(raw);
@@ -1190,13 +1198,15 @@ function parseConversationRecord(record) {
1190
1198
  const title = parseConversationTitle(record.title);
1191
1199
  if (title === INVALID_VALUE) return;
1192
1200
  const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
1201
+ const cumulativeCost = parseUsageCost(record.cumulative_cost);
1193
1202
  const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
1194
- if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
1203
+ if (cumulativeTokenUsage === null || cumulativeCost === null || requestTokenUsage === null) return;
1195
1204
  return {
1196
1205
  title,
1197
1206
  messages: record.messages,
1198
1207
  updated_at: record.updated_at,
1199
1208
  cumulative_token_usage: cumulativeTokenUsage ?? {},
1209
+ cumulative_cost: cumulativeCost,
1200
1210
  request_token_usage: requestTokenUsage ?? {}
1201
1211
  };
1202
1212
  }
@@ -1216,13 +1226,20 @@ function parseAcpxState(raw) {
1216
1226
  assignStringState(state, "current_mode_id", record.current_mode_id);
1217
1227
  assignStringState(state, "desired_mode_id", record.desired_mode_id);
1218
1228
  assignDesiredConfigOptions(state, record.desired_config_options);
1219
- assignStringState(state, "current_model_id", record.current_model_id);
1220
- if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
1221
- if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
1222
- if (Array.isArray(record.config_options)) state.config_options = record.config_options;
1229
+ assignParsedModelState(state, record);
1230
+ const availableCommands = parseAvailableCommands(record.available_commands);
1231
+ if (availableCommands) state.available_commands = availableCommands;
1223
1232
  assignParsedSessionOptions(state, record.session_options);
1224
1233
  return state;
1225
1234
  }
1235
+ function assignParsedModelState(state, record) {
1236
+ assignStringState(state, "current_model_id", record.current_model_id);
1237
+ if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
1238
+ if (record.model_control === "config_option" || record.model_control === "legacy_set_model") state.model_control = record.model_control;
1239
+ const configOptions = parseConfigOptions(record.config_options);
1240
+ if (configOptions) state.config_options = configOptions;
1241
+ if (state.model_control === void 0 && state.available_models !== void 0) state.model_control = hasModelConfigOption(state.config_options) ? "config_option" : "legacy_set_model";
1242
+ }
1226
1243
  function assignBooleanTrue(state, key, value) {
1227
1244
  if (value === true) state[key] = true;
1228
1245
  }
@@ -1283,6 +1300,27 @@ function hasValidEventLogCore(record) {
1283
1300
  function isPositiveInteger(value) {
1284
1301
  return typeof value === "number" && Number.isInteger(value) && value > 0;
1285
1302
  }
1303
+ function parseImportedFrom(raw) {
1304
+ if (raw == null) return;
1305
+ const record = asRecord$4(raw);
1306
+ if (!record || typeof record.record_id !== "string" || typeof record.cwd_original !== "string" || typeof record.exported_by !== "string" || typeof record.exported_at !== "string") return null;
1307
+ return {
1308
+ recordId: record.record_id,
1309
+ cwdOriginal: record.cwd_original,
1310
+ exportedBy: record.exported_by,
1311
+ exportedAt: record.exported_at
1312
+ };
1313
+ }
1314
+ function parseSessionRecordMetadata(record) {
1315
+ const lastRequestId = normalizeOptionalString(record.last_request_id);
1316
+ if (lastRequestId === null) return null;
1317
+ const importedFrom = parseImportedFrom(record.imported_from);
1318
+ if (importedFrom === null) return null;
1319
+ return {
1320
+ lastRequestId,
1321
+ importedFrom
1322
+ };
1323
+ }
1286
1324
  function normalizeOptionalName(value) {
1287
1325
  if (value == null) return;
1288
1326
  if (typeof value !== "string") return null;
@@ -1334,8 +1372,8 @@ function parseSessionRecord(raw) {
1334
1372
  const conversation = parseConversationRecord(record);
1335
1373
  if (!conversation) return null;
1336
1374
  const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
1337
- const lastRequestId = normalizeOptionalString(record.last_request_id);
1338
- if (lastRequestId === null) return null;
1375
+ const metadata = parseSessionRecordMetadata(record);
1376
+ if (!metadata) return null;
1339
1377
  return {
1340
1378
  schema: SESSION_RECORD_SCHEMA,
1341
1379
  acpxRecordId: record.acpx_record_id,
@@ -1347,7 +1385,7 @@ function parseSessionRecord(raw) {
1347
1385
  createdAt: record.created_at,
1348
1386
  lastUsedAt: record.last_used_at,
1349
1387
  lastSeq: record.last_seq,
1350
- lastRequestId,
1388
+ lastRequestId: metadata.lastRequestId,
1351
1389
  eventLog,
1352
1390
  closed: optionals.closed,
1353
1391
  closedAt: optionals.closedAt,
@@ -1364,38 +1402,177 @@ function parseSessionRecord(raw) {
1364
1402
  messages: conversation.messages,
1365
1403
  updated_at: conversation.updated_at,
1366
1404
  cumulative_token_usage: conversation.cumulative_token_usage,
1405
+ cumulative_cost: conversation.cumulative_cost,
1367
1406
  request_token_usage: conversation.request_token_usage,
1368
- acpx: parseAcpxState(record.acpx)
1407
+ acpx: parseAcpxState(record.acpx),
1408
+ importedFrom: metadata.importedFrom
1369
1409
  };
1370
1410
  }
1371
- function hasValidSessionRecordCore(record) {
1372
- return hasStringFields(record, [
1373
- "acpx_record_id",
1374
- "acp_session_id",
1375
- "agent_command",
1376
- "cwd",
1377
- "created_at",
1378
- "last_used_at"
1379
- ]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
1411
+ function hasValidSessionRecordCore(record) {
1412
+ return hasStringFields(record, [
1413
+ "acpx_record_id",
1414
+ "acp_session_id",
1415
+ "agent_command",
1416
+ "cwd",
1417
+ "created_at",
1418
+ "last_used_at"
1419
+ ]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
1420
+ }
1421
+ function validSessionOptionals(options) {
1422
+ if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
1423
+ return options;
1424
+ }
1425
+ function hasNullOptionalSessionFields(options) {
1426
+ return [
1427
+ options.name,
1428
+ options.pid,
1429
+ options.closed,
1430
+ options.closedAt,
1431
+ options.agentStartedAt,
1432
+ options.lastPromptAt,
1433
+ options.lastAgentExitAt,
1434
+ options.lastAgentDisconnectReason
1435
+ ].some((value) => value === null);
1436
+ }
1437
+ function hasInvalidExitStatus(options) {
1438
+ return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
1439
+ }
1440
+ //#endregion
1441
+ //#region src/perf-metrics.ts
1442
+ const counters = /* @__PURE__ */ new Map();
1443
+ const gauges = /* @__PURE__ */ new Map();
1444
+ const timings = /* @__PURE__ */ new Map();
1445
+ function hrNow() {
1446
+ return process.hrtime.bigint();
1447
+ }
1448
+ function durationMs(start) {
1449
+ return Number(process.hrtime.bigint() - start) / 1e6;
1450
+ }
1451
+ function roundMetric(value) {
1452
+ return Number(value.toFixed(3));
1453
+ }
1454
+ function incrementPerfCounter(name, delta = 1) {
1455
+ counters.set(name, (counters.get(name) ?? 0) + delta);
1456
+ }
1457
+ function setPerfGauge(name, value) {
1458
+ gauges.set(name, value);
1459
+ }
1460
+ function recordPerfDuration(name, durationMsValue) {
1461
+ const next = timings.get(name) ?? {
1462
+ count: 0,
1463
+ totalMs: 0,
1464
+ maxMs: 0
1465
+ };
1466
+ next.count += 1;
1467
+ next.totalMs += durationMsValue;
1468
+ next.maxMs = Math.max(next.maxMs, durationMsValue);
1469
+ timings.set(name, next);
1470
+ }
1471
+ async function measurePerf(name, run) {
1472
+ const startedAt = hrNow();
1473
+ try {
1474
+ return await run();
1475
+ } finally {
1476
+ recordPerfDuration(name, durationMs(startedAt));
1477
+ }
1478
+ }
1479
+ function startPerfTimer(name) {
1480
+ const startedAt = hrNow();
1481
+ return () => {
1482
+ const elapsedMs = durationMs(startedAt);
1483
+ recordPerfDuration(name, elapsedMs);
1484
+ return elapsedMs;
1485
+ };
1486
+ }
1487
+ function getPerfMetricsSnapshot() {
1488
+ return {
1489
+ counters: Object.fromEntries(counters.entries()),
1490
+ gauges: Object.fromEntries(gauges.entries()),
1491
+ timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
1492
+ count: bucket.count,
1493
+ totalMs: roundMetric(bucket.totalMs),
1494
+ maxMs: roundMetric(bucket.maxMs)
1495
+ }]))
1496
+ };
1497
+ }
1498
+ function resetPerfMetrics() {
1499
+ counters.clear();
1500
+ gauges.clear();
1501
+ timings.clear();
1502
+ }
1503
+ function formatPerfMetric(name, durationMsValue) {
1504
+ return `${name}=${roundMetric(durationMsValue)}ms`;
1505
+ }
1506
+ //#endregion
1507
+ //#region src/persisted-key-policy.ts
1508
+ const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
1509
+ const ZED_TAG_KEYS = new Set([
1510
+ "User",
1511
+ "Agent",
1512
+ "Resume",
1513
+ "Text",
1514
+ "Mention",
1515
+ "Image",
1516
+ "Audio",
1517
+ "Thinking",
1518
+ "RedactedThinking",
1519
+ "ToolUse"
1520
+ ]);
1521
+ const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
1522
+ const OPAQUE_VALUE_PATHS = new Set([
1523
+ "agent_capabilities",
1524
+ "messages.Agent.content.ToolUse.input",
1525
+ "acpx.desired_config_options",
1526
+ "acpx.config_options"
1527
+ ]);
1528
+ function isRecord(value) {
1529
+ return !!value && typeof value === "object" && !Array.isArray(value);
1530
+ }
1531
+ function joinPath(path) {
1532
+ return path.join(".");
1533
+ }
1534
+ function isAllowedKey(path, key) {
1535
+ if (ZED_TAG_KEYS.has(key)) return true;
1536
+ return false;
1537
+ }
1538
+ function shouldSkipKeyRule(path) {
1539
+ return MAP_OBJECT_PATHS.has(joinPath(path));
1540
+ }
1541
+ function shouldSkipDescend(path) {
1542
+ return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
1543
+ }
1544
+ function isToolResultOutputTail(path, toolResultsIndex) {
1545
+ return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
1380
1546
  }
1381
- function validSessionOptionals(options) {
1382
- if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
1383
- return options;
1547
+ function isToolResultOutputPath(path) {
1548
+ if (path.length < 5 || path[path.length - 1] !== "output") return false;
1549
+ const toolResultsIndex = path.lastIndexOf("tool_results");
1550
+ if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
1551
+ return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
1384
1552
  }
1385
- function hasNullOptionalSessionFields(options) {
1386
- return [
1387
- options.name,
1388
- options.pid,
1389
- options.closed,
1390
- options.closedAt,
1391
- options.agentStartedAt,
1392
- options.lastPromptAt,
1393
- options.lastAgentExitAt,
1394
- options.lastAgentDisconnectReason
1395
- ].some((value) => value === null);
1553
+ function collectViolations(value, path, violations) {
1554
+ if (Array.isArray(value)) {
1555
+ for (const entry of value) collectViolations(entry, path, violations);
1556
+ return;
1557
+ }
1558
+ if (!isRecord(value)) return;
1559
+ const skipKeyRule = shouldSkipKeyRule(path);
1560
+ for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
1396
1561
  }
1397
- function hasInvalidExitStatus(options) {
1398
- return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
1562
+ function collectKeyViolation(child, key, path, skipKeyRule, violations) {
1563
+ if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1564
+ const childPath = [...path, key];
1565
+ if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
1566
+ }
1567
+ function findPersistedKeyPolicyViolations(value) {
1568
+ const violations = [];
1569
+ collectViolations(value, [], violations);
1570
+ return violations;
1571
+ }
1572
+ function assertPersistedKeyPolicy(value) {
1573
+ const violations = findPersistedKeyPolicyViolations(value);
1574
+ if (violations.length === 0) return;
1575
+ throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
1399
1576
  }
1400
1577
  //#endregion
1401
1578
  //#region src/session/persistence/index.ts
@@ -2204,6 +2381,49 @@ function buildTerminalShellSpawnCommand(command, platform = process.platform) {
2204
2381
  };
2205
2382
  }
2206
2383
  //#endregion
2384
+ //#region src/version.ts
2385
+ const UNKNOWN_VERSION = "0.0.0-unknown";
2386
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
2387
+ let cachedVersion = null;
2388
+ function parseVersion(value) {
2389
+ if (typeof value !== "string") return null;
2390
+ const trimmed = value.trim();
2391
+ return trimmed.length > 0 ? trimmed : null;
2392
+ }
2393
+ function readPackageVersion(packageJsonPath) {
2394
+ try {
2395
+ return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
2396
+ } catch {
2397
+ return null;
2398
+ }
2399
+ }
2400
+ function resolveVersionFromAncestors(startDir) {
2401
+ let current = startDir;
2402
+ while (true) {
2403
+ const packageVersion = readPackageVersion(path.join(current, "package.json"));
2404
+ if (packageVersion) return packageVersion;
2405
+ const parent = path.dirname(current);
2406
+ if (parent === current) return null;
2407
+ current = parent;
2408
+ }
2409
+ }
2410
+ function resolveAcpxVersion(params) {
2411
+ const envVersion = resolvePackageEnvVersion(params?.env ?? process.env);
2412
+ if (envVersion) return envVersion;
2413
+ if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
2414
+ return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
2415
+ }
2416
+ function resolvePackageEnvVersion(env) {
2417
+ const envPackageName = parseVersion(env.npm_package_name);
2418
+ const envVersion = parseVersion(env.npm_package_version);
2419
+ return envPackageName === "acpx" ? envVersion : null;
2420
+ }
2421
+ function getAcpxVersion() {
2422
+ if (cachedVersion) return cachedVersion;
2423
+ cachedVersion = resolveAcpxVersion();
2424
+ return cachedVersion;
2425
+ }
2426
+ //#endregion
2207
2427
  //#region src/acp/client-process.ts
2208
2428
  const execFileAsync = promisify(execFile);
2209
2429
  function isoNow$1() {
@@ -2379,6 +2599,7 @@ const GEMINI_ACP_FLAG_VERSION = [
2379
2599
  0
2380
2600
  ];
2381
2601
  const COPILOT_HELP_TIMEOUT_MS = 2e3;
2602
+ const CLAUDE_CODE_DEFAULT_SETTING_SOURCES = ["project", "local"];
2382
2603
  const QODER_BENIGN_STDOUT_LINES = new Set(["Received interrupt signal. Cleaning up resources...", "Cleanup completed. Exiting..."]);
2383
2604
  function resolveAgentCloseAfterStdinEndMs(agentCommand) {
2384
2605
  const { command } = splitCommandLine(agentCommand);
@@ -2401,6 +2622,9 @@ function isCopilotAcpCommand(command, args) {
2401
2622
  function isQoderAcpCommand(command, args) {
2402
2623
  return basenameToken(command) === "qodercli" && args.includes("--acp");
2403
2624
  }
2625
+ function isDevinAcpCommand(command, args) {
2626
+ return basenameToken(command) === "devin" && (args.includes("acp") || args.includes("--acp") || args.includes("--experimental-acp"));
2627
+ }
2404
2628
  function hasCommandFlag(args, flagName) {
2405
2629
  return args.some((arg) => arg === flagName || arg.startsWith(`${flagName}=`));
2406
2630
  }
@@ -2541,16 +2765,20 @@ async function ensureCopilotAcpSupport(command) {
2541
2765
  const helpOutput = await readCommandOutput(command, ["--help"], COPILOT_HELP_TIMEOUT_MS);
2542
2766
  if (typeof helpOutput === "string" && !helpOutput.includes("--acp")) throw new CopilotAcpUnsupportedError(await buildCopilotAcpUnsupportedMessage(command), { retryable: false });
2543
2767
  }
2544
- function buildClaudeCodeOptionsMeta(options) {
2545
- if (!options) return;
2768
+ function buildClaudeCodeOptionsMeta(options, isolateUserSettings = false) {
2546
2769
  const claudeCodeOptions = {};
2547
- assignClaudeCodeOptions(claudeCodeOptions, options);
2770
+ if (isolateUserSettings) claudeCodeOptions.settingSources = resolveClaudeCodeSettingSources();
2771
+ if (options) assignClaudeCodeOptions(claudeCodeOptions, options);
2548
2772
  const meta = {};
2549
2773
  if (Object.keys(claudeCodeOptions).length > 0) meta.claudeCode = { options: claudeCodeOptions };
2550
- assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
2774
+ assignClaudeCodeSystemPrompt(meta, options?.systemPrompt);
2551
2775
  if (Object.keys(meta).length === 0) return;
2552
2776
  return meta;
2553
2777
  }
2778
+ function resolveClaudeCodeSettingSources(env = process.env) {
2779
+ if (env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS?.trim() === "1") return ["user", ...CLAUDE_CODE_DEFAULT_SETTING_SOURCES];
2780
+ return [...CLAUDE_CODE_DEFAULT_SETTING_SOURCES];
2781
+ }
2554
2782
  function assignClaudeCodeOptions(target, options) {
2555
2783
  if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
2556
2784
  if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
@@ -2642,74 +2870,96 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
2642
2870
  };
2643
2871
  }
2644
2872
  //#endregion
2645
- //#region src/acp/jsonrpc.ts
2873
+ //#region src/acp/model-support.ts
2874
+ var RequestedModelUnsupportedError = class extends Error {
2875
+ constructor(message) {
2876
+ super(message);
2877
+ this.name = "RequestedModelUnsupportedError";
2878
+ }
2879
+ };
2880
+ function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
2881
+ if (!agentCommand) return false;
2882
+ const { command, args } = splitCommandLine(agentCommand);
2883
+ return isClaudeAcpCommand(command, args);
2884
+ }
2646
2885
  function asRecord$2(value) {
2647
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2886
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2648
2887
  return value;
2649
2888
  }
2650
- function hasValidId(value) {
2651
- return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
2652
- }
2653
- function isErrorObject(value) {
2654
- const record = asRecord$2(value);
2655
- return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
2656
- }
2657
- function hasResultOrError(value) {
2658
- const hasResult = Object.hasOwn(value, "result");
2659
- const hasError = Object.hasOwn(value, "error");
2660
- if (hasResult && hasError) return false;
2661
- if (!hasResult && !hasError) return false;
2662
- if (hasError && !isErrorObject(value.error)) return false;
2663
- return true;
2664
- }
2665
- function hasMethod(value) {
2666
- return typeof value.method === "string" && value.method.length > 0;
2667
- }
2668
- function isJsonRpcRequest(value) {
2669
- return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
2670
- }
2671
- function isJsonRpcNotificationRecord(value) {
2672
- return hasMethod(value) && !Object.hasOwn(value, "id");
2673
- }
2674
- function isJsonRpcResponse(value) {
2675
- if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
2676
- return hasResultOrError(value);
2677
- }
2678
- function isAcpJsonRpcMessage(value) {
2679
- const record = asRecord$2(value);
2680
- if (!record || record.jsonrpc !== "2.0") return false;
2681
- return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
2682
- }
2683
- function isJsonRpcNotification(message) {
2684
- return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
2889
+ function parseAvailableModel(value) {
2890
+ const option = asRecord$2(value);
2891
+ if (!option || typeof option.value !== "string" || typeof option.name !== "string") return;
2892
+ return {
2893
+ modelId: option.value,
2894
+ name: option.name
2895
+ };
2685
2896
  }
2686
- function isSessionUpdateNotification(message) {
2687
- return isJsonRpcNotification(message) && message.method === "session/update";
2897
+ function parseAvailableModelGroup(value) {
2898
+ const group = asRecord$2(value);
2899
+ if (!group || typeof group.group !== "string" || typeof group.name !== "string" || !Array.isArray(group.options)) return;
2900
+ const models = group.options.map((option) => parseAvailableModel(option));
2901
+ return models.every((model) => model !== void 0) ? models : void 0;
2902
+ }
2903
+ function parseAvailableModels(value) {
2904
+ if (!Array.isArray(value)) return;
2905
+ const directModels = value.map((option) => parseAvailableModel(option));
2906
+ if (directModels.every((model) => model !== void 0)) return directModels;
2907
+ const groupedModels = value.map((group) => parseAvailableModelGroup(group));
2908
+ return groupedModels.every((models) => models !== void 0) ? groupedModels.flat() : void 0;
2909
+ }
2910
+ function isModelSelectOption(option) {
2911
+ return option.type === "select" && (option.category === "model" || option.id === "model");
2912
+ }
2913
+ function parseModelConfigOption(value) {
2914
+ const option = asRecord$2(value);
2915
+ if (!option || !isModelSelectOption(option) || typeof option.id !== "string" || typeof option.currentValue !== "string") return;
2916
+ const availableModels = parseAvailableModels(option.options);
2917
+ return availableModels ? {
2918
+ configId: option.id,
2919
+ currentModelId: option.currentValue,
2920
+ availableModels
2921
+ } : void 0;
2688
2922
  }
2689
- function extractSessionUpdateNotification(message) {
2690
- if (!isSessionUpdateNotification(message)) return;
2691
- const params = asRecord$2(message.params);
2692
- if (!params) return;
2693
- const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
2694
- if (!sessionId) return;
2695
- const update = asRecord$2(params.update);
2696
- if (!update || typeof update.sessionUpdate !== "string") return;
2923
+ function modelStateFromConfigOptions(configOptions) {
2924
+ if (!Array.isArray(configOptions)) return;
2925
+ for (const value of configOptions) {
2926
+ const models = parseModelConfigOption(value);
2927
+ if (models) return models;
2928
+ }
2929
+ }
2930
+ function modelStateFromLegacyResponse(response) {
2931
+ if (!response || typeof response !== "object") return;
2932
+ const models = response.models;
2933
+ if (!models || typeof models.currentModelId !== "string" || !Array.isArray(models.availableModels)) return;
2934
+ const availableModels = models.availableModels.flatMap((entry) => {
2935
+ if (!entry || typeof entry !== "object") return [];
2936
+ const candidate = entry;
2937
+ return typeof candidate.modelId === "string" && typeof candidate.name === "string" ? [{
2938
+ modelId: candidate.modelId,
2939
+ name: candidate.name
2940
+ }] : [];
2941
+ });
2697
2942
  return {
2698
- sessionId,
2699
- update
2943
+ currentModelId: models.currentModelId,
2944
+ availableModels
2700
2945
  };
2701
2946
  }
2702
- function parsePromptStopReason(message) {
2703
- if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
2704
- const record = asRecord$2(message.result);
2705
- if (!record) return;
2706
- return typeof record.stopReason === "string" ? record.stopReason : void 0;
2947
+ function modelStateFromSessionResponse(params) {
2948
+ return modelStateFromConfigOptions(params.configOptions) ?? modelStateFromLegacyResponse(params.response);
2707
2949
  }
2708
- function parseJsonRpcErrorMessage(message) {
2709
- if (!Object.hasOwn(message, "error")) return;
2710
- const errorRecord = asRecord$2(message.error);
2711
- if (!errorRecord || typeof errorRecord.message !== "string") return;
2712
- return errorRecord.message;
2950
+ function formatAvailableModelIds(models) {
2951
+ const ids = models?.availableModels.map((model) => model.modelId.trim()).filter((modelId) => modelId.length > 0) ?? [];
2952
+ return ids.length > 0 ? ids.join(", ") : "none advertised";
2953
+ }
2954
+ function assertRequestedModelSupported(params) {
2955
+ if (!params.models) {
2956
+ if (supportsLegacyClaudeCodeModelMetadata(params.agentCommand)) return;
2957
+ throw new RequestedModelUnsupportedError(`Cannot ${params.context === "replay" ? "replay saved model" : "apply --model"} "${params.requestedModel}": the ACP agent did not advertise model support through a session config option or legacy models metadata, and the adapter does not support a startup model flag.`);
2958
+ }
2959
+ if (!new Set(params.models.availableModels.map((model) => model.modelId)).has(params.requestedModel)) {
2960
+ if (supportsLegacyClaudeCodeModelMetadata(params.agentCommand)) return `requested model "${params.requestedModel}" was not in the Claude ACP advertised model list (${formatAvailableModelIds(params.models)}); forwarding it to Claude Code so the adapter can accept or reject it.`;
2961
+ throw new RequestedModelUnsupportedError(`Cannot ${params.context === "replay" ? "replay saved model" : "apply --model"} "${params.requestedModel}": the ACP agent did not advertise that model. Available models: ${formatAvailableModelIds(params.models)}.`);
2962
+ }
2713
2963
  }
2714
2964
  //#endregion
2715
2965
  //#region src/acp/session-control-errors.ts
@@ -3328,11 +3578,54 @@ const DRAIN_POLL_INTERVAL_MS = 20;
3328
3578
  const AGENT_CLOSE_TERM_GRACE_MS = 1500;
3329
3579
  const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
3330
3580
  const STARTUP_STDERR_MAX_CHARS = 8192;
3581
+ const DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META = Object.freeze({ "cognition.ai/requestDiagnostics": true });
3582
+ const DEVIN_COMPATIBILITY_CLIENT_NAME = "windsurf";
3583
+ const DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION = "1.110.1";
3584
+ function resolveClientInfo(devinAcp) {
3585
+ if (!devinAcp) return {
3586
+ name: "acpx",
3587
+ version: getAcpxVersion()
3588
+ };
3589
+ return {
3590
+ name: DEVIN_COMPATIBILITY_CLIENT_NAME,
3591
+ version: process.env.ACPX_DEVIN_WINDSURF_VERSION ?? DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION
3592
+ };
3593
+ }
3594
+ function resolveClientCapabilities(params) {
3595
+ const baseCapabilities = {
3596
+ fs: {
3597
+ readTextFile: true,
3598
+ writeTextFile: true
3599
+ },
3600
+ terminal: params.terminal
3601
+ };
3602
+ if (!params.devinAcp) return baseCapabilities;
3603
+ return {
3604
+ ...baseCapabilities,
3605
+ _meta: DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META
3606
+ };
3607
+ }
3608
+ function isDevinRequestDiagnosticsMethod(method) {
3609
+ return method === "_cognition.ai/request_diagnostics";
3610
+ }
3611
+ function hasResponseField(response, field) {
3612
+ return !!response && typeof response === "object" && field in response;
3613
+ }
3614
+ function normalizeResponseConfigOptions(response) {
3615
+ if (!response || !("configOptions" in response)) return;
3616
+ return response.configOptions ?? [];
3617
+ }
3331
3618
  function toReconnectedSessionResult(response) {
3619
+ const configOptions = normalizeResponseConfigOptions(response);
3332
3620
  return {
3333
3621
  agentSessionId: extractRuntimeSessionId(response?._meta),
3334
- configOptions: response?.configOptions ?? void 0,
3335
- models: response?.models ?? void 0
3622
+ configOptions,
3623
+ models: modelStateFromSessionResponse({
3624
+ configOptions,
3625
+ response
3626
+ }),
3627
+ configOptionsPresent: hasResponseField(response, "configOptions"),
3628
+ legacyModelMetadataPresent: hasResponseField(response, "models")
3336
3629
  };
3337
3630
  }
3338
3631
  function childProcessIsRunning(agent) {
@@ -3431,6 +3724,8 @@ var AcpClient = class {
3431
3724
  lastKnownPid;
3432
3725
  promptPermissionFailures = /* @__PURE__ */ new Map();
3433
3726
  pendingConnectionRequests = /* @__PURE__ */ new Set();
3727
+ modelConfigIds = /* @__PURE__ */ new Map();
3728
+ legacyModelSessionIds = /* @__PURE__ */ new Set();
3434
3729
  constructor(options) {
3435
3730
  this.options = {
3436
3731
  ...options,
@@ -3542,7 +3837,7 @@ var AcpClient = class {
3542
3837
  const input = Writable.toWeb(child.stdin);
3543
3838
  const output = Readable.toWeb(child.stdout);
3544
3839
  const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
3545
- const connection = this.createConnection(stream);
3840
+ const connection = this.createConnection(stream, launch);
3546
3841
  connection.signal.addEventListener("abort", () => {
3547
3842
  this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
3548
3843
  }, { once: true });
@@ -3566,6 +3861,7 @@ var AcpClient = class {
3566
3861
  spawnCommand,
3567
3862
  args,
3568
3863
  resolvedBuiltInLaunch,
3864
+ devinAcp: isDevinAcpCommand(spawnCommand, args),
3569
3865
  geminiAcp: isGeminiAcpCommand(spawnCommand, args),
3570
3866
  copilotAcp: isCopilotAcpCommand(spawnCommand, args),
3571
3867
  claudeAcp: isClaudeAcpCommand(spawnCommand, args),
@@ -3602,7 +3898,7 @@ var AcpClient = class {
3602
3898
  }
3603
3899
  return requireAgentStdio(spawnedChild);
3604
3900
  }
3605
- createConnection(stream) {
3901
+ createConnection(stream, launch) {
3606
3902
  return new ClientSideConnection(() => ({
3607
3903
  sessionUpdate: async (params) => {
3608
3904
  await this.handleSessionUpdate(params);
@@ -3610,6 +3906,10 @@ var AcpClient = class {
3610
3906
  requestPermission: async (params) => {
3611
3907
  return this.handlePermissionRequest(params);
3612
3908
  },
3909
+ extMethod: async (method) => {
3910
+ if (launch.devinAcp && isDevinRequestDiagnosticsMethod(method)) return {};
3911
+ throw RequestError.methodNotFound(method);
3912
+ },
3613
3913
  readTextFile: async (params) => {
3614
3914
  return this.handleReadTextFile(params);
3615
3915
  },
@@ -3630,12 +3930,13 @@ var AcpClient = class {
3630
3930
  },
3631
3931
  releaseTerminal: async (params) => {
3632
3932
  return this.handleReleaseTerminal(params);
3633
- }
3933
+ },
3934
+ extNotification: async () => {}
3634
3935
  }), stream);
3635
3936
  }
3636
3937
  async initializeAgentConnection(params) {
3637
3938
  try {
3638
- const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
3939
+ const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch), params.startupFailure.promise]);
3639
3940
  params.startupFailure.dispose();
3640
3941
  this.connection = params.connection;
3641
3942
  this.agent = params.child;
@@ -3645,22 +3946,16 @@ var AcpClient = class {
3645
3946
  await this.handleInitializeFailure(params, error);
3646
3947
  }
3647
3948
  }
3648
- async initializeProtocolConnection(connection, geminiAcp) {
3949
+ async initializeProtocolConnection(connection, launch) {
3649
3950
  const initializePromise = connection.initialize({
3650
3951
  protocolVersion: PROTOCOL_VERSION,
3651
- clientCapabilities: {
3652
- fs: {
3653
- readTextFile: true,
3654
- writeTextFile: true
3655
- },
3952
+ clientCapabilities: resolveClientCapabilities({
3953
+ devinAcp: launch.devinAcp,
3656
3954
  terminal: this.options.terminal !== false
3657
- },
3658
- clientInfo: {
3659
- name: "acpx",
3660
- version: "0.1.0"
3661
- }
3955
+ }),
3956
+ clientInfo: resolveClientInfo(launch.devinAcp)
3662
3957
  });
3663
- const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
3958
+ const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
3664
3959
  await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
3665
3960
  return initialized;
3666
3961
  }
@@ -3723,7 +4018,7 @@ var AcpClient = class {
3723
4018
  const createPromise = this.runConnectionRequest(() => connection.newSession({
3724
4019
  cwd: sessionCwd,
3725
4020
  mcpServers: this.options.mcpServers ?? [],
3726
- _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions)
4021
+ _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
3727
4022
  }));
3728
4023
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
3729
4024
  } catch (error) {
@@ -3734,11 +4029,19 @@ var AcpClient = class {
3734
4029
  throw error;
3735
4030
  }
3736
4031
  this.loadedSessionId = result.sessionId;
4032
+ const configOptions = normalizeResponseConfigOptions(result);
4033
+ const models = modelStateFromSessionResponse({
4034
+ configOptions,
4035
+ response: result
4036
+ });
4037
+ this.rememberSessionModels(result.sessionId, models);
3737
4038
  return {
3738
4039
  sessionId: result.sessionId,
3739
4040
  agentSessionId: extractRuntimeSessionId(result._meta),
3740
- configOptions: result.configOptions ?? void 0,
3741
- models: result.models ?? void 0
4041
+ configOptions,
4042
+ models,
4043
+ configOptionsPresent: hasResponseField(result, "configOptions"),
4044
+ legacyModelMetadataPresent: hasResponseField(result, "models")
3742
4045
  };
3743
4046
  }
3744
4047
  async loadSession(sessionId, cwd = this.options.cwd) {
@@ -3761,7 +4064,9 @@ var AcpClient = class {
3761
4064
  this.restoreSessionUpdateSuppression(previousSuppression);
3762
4065
  }
3763
4066
  this.loadedSessionId = sessionId;
3764
- return toReconnectedSessionResult(response);
4067
+ const result = toReconnectedSessionResult(response);
4068
+ this.updateRememberedSessionModels(sessionId, result);
4069
+ return result;
3765
4070
  }
3766
4071
  async resumeSession(sessionId, cwd = this.options.cwd) {
3767
4072
  const connection = this.getConnection();
@@ -3772,7 +4077,9 @@ var AcpClient = class {
3772
4077
  mcpServers: this.options.mcpServers ?? []
3773
4078
  }));
3774
4079
  this.loadedSessionId = sessionId;
3775
- return toReconnectedSessionResult(response);
4080
+ const result = toReconnectedSessionResult(response);
4081
+ this.updateRememberedSessionModels(sessionId, result);
4082
+ return result;
3776
4083
  }
3777
4084
  applySessionUpdateSuppression(enabled) {
3778
4085
  const previous = {
@@ -3855,21 +4162,73 @@ var AcpClient = class {
3855
4162
  throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`);
3856
4163
  }
3857
4164
  }
3858
- async setSessionModel(sessionId, modelId) {
4165
+ async setSessionModel(sessionId, modelId, controlOverride) {
4166
+ const control = this.resolveModelControl(sessionId, controlOverride);
4167
+ if (!control) throw new RequestedModelUnsupportedError(`Cannot set model "${modelId}": the ACP session did not advertise a model config option or legacy session/set_model support.`);
4168
+ return control.kind === "config_option" ? await this.setSessionModelThroughConfig(sessionId, modelId, control.configId) : await this.setSessionModelThroughLegacyMethod(sessionId, modelId);
4169
+ }
4170
+ async setSessionModelThroughConfig(sessionId, modelId, configId) {
4171
+ const connection = this.getConnection();
4172
+ try {
4173
+ const response = await this.runConnectionRequest(() => connection.setSessionConfigOption({
4174
+ sessionId,
4175
+ configId,
4176
+ value: modelId
4177
+ }));
4178
+ this.rememberSessionModels(sessionId, modelStateFromConfigOptions(response.configOptions));
4179
+ return response;
4180
+ } catch (error) {
4181
+ return this.throwSessionModelError("session/set_config_option", modelId, error);
4182
+ }
4183
+ }
4184
+ async setSessionModelThroughLegacyMethod(sessionId, modelId) {
3859
4185
  const connection = this.getConnection();
3860
4186
  try {
3861
- await this.runConnectionRequest(() => connection.unstable_setSessionModel({
4187
+ await this.runConnectionRequest(() => connection.extMethod("session/set_model", {
3862
4188
  sessionId,
3863
4189
  modelId
3864
4190
  }));
4191
+ return;
3865
4192
  } catch (error) {
3866
- const wrapped = maybeWrapSessionControlError("session/set_model", error, `for model "${modelId}"`);
3867
- if (wrapped !== error) throw wrapped;
3868
- const acp = extractAcpError(error);
3869
- const summary = acp ? formatSessionControlAcpSummary(acp) : error instanceof Error ? error.message : String(error);
3870
- if (error instanceof Error) throw new Error(`Failed session/set_model for model "${modelId}": ${summary}`, { cause: error });
3871
- throw new Error(`Failed session/set_model for model "${modelId}": ${summary}`, { cause: error });
4193
+ return this.throwSessionModelError("session/set_model", modelId, error);
4194
+ }
4195
+ }
4196
+ throwSessionModelError(method, modelId, error) {
4197
+ const wrapped = maybeWrapSessionControlError(method, error, `for model "${modelId}"`);
4198
+ if (wrapped !== error) throw wrapped;
4199
+ const acp = extractAcpError(error);
4200
+ const summary = acp ? formatSessionControlAcpSummary(acp) : error instanceof Error ? error.message : String(error);
4201
+ throw new Error(`Failed ${method} for model "${modelId}": ${summary}`, { cause: error });
4202
+ }
4203
+ resolveModelControl(sessionId, controlOverride) {
4204
+ if (controlOverride) return controlOverride.configId ? {
4205
+ kind: "config_option",
4206
+ configId: controlOverride.configId
4207
+ } : { kind: "legacy_set_model" };
4208
+ const configId = this.modelConfigIds.get(sessionId);
4209
+ if (configId) return {
4210
+ kind: "config_option",
4211
+ configId
4212
+ };
4213
+ return this.legacyModelSessionIds.has(sessionId) ? { kind: "legacy_set_model" } : void 0;
4214
+ }
4215
+ rememberSessionModels(sessionId, models) {
4216
+ if (!models) {
4217
+ this.modelConfigIds.delete(sessionId);
4218
+ this.legacyModelSessionIds.delete(sessionId);
4219
+ return;
4220
+ }
4221
+ if (models.configId) {
4222
+ this.modelConfigIds.set(sessionId, models.configId);
4223
+ this.legacyModelSessionIds.delete(sessionId);
4224
+ return;
3872
4225
  }
4226
+ this.modelConfigIds.delete(sessionId);
4227
+ this.legacyModelSessionIds.add(sessionId);
4228
+ }
4229
+ updateRememberedSessionModels(sessionId, result) {
4230
+ const explicitConfigRemoval = result.configOptionsPresent && this.modelConfigIds.has(sessionId);
4231
+ if (result.models || result.legacyModelMetadataPresent || explicitConfigRemoval) this.rememberSessionModels(sessionId, result.models);
3873
4232
  }
3874
4233
  async cancel(sessionId) {
3875
4234
  const connection = this.getConnection();
@@ -3881,6 +4240,8 @@ var AcpClient = class {
3881
4240
  const connection = this.getConnection();
3882
4241
  await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
3883
4242
  if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
4243
+ this.modelConfigIds.delete(sessionId);
4244
+ this.legacyModelSessionIds.delete(sessionId);
3884
4245
  }
3885
4246
  async listSessions(params = {}) {
3886
4247
  const connection = this.getConnection();
@@ -3929,6 +4290,8 @@ var AcpClient = class {
3929
4290
  this.permissionAbortControllers.clear();
3930
4291
  this.promptPermissionFailures.clear();
3931
4292
  this.loadedSessionId = void 0;
4293
+ this.modelConfigIds.clear();
4294
+ this.legacyModelSessionIds.clear();
3932
4295
  this.initResult = void 0;
3933
4296
  this.connection = void 0;
3934
4297
  this.agent = void 0;
@@ -3982,7 +4345,7 @@ var AcpClient = class {
3982
4345
  target.push(text);
3983
4346
  if (target.join("").length - STARTUP_STDERR_MAX_CHARS <= 0) return;
3984
4347
  const joined = target.join("");
3985
- target.splice(0, target.length, joined.slice(-STARTUP_STDERR_MAX_CHARS));
4348
+ target.splice(0, target.length, joined.slice(-8192));
3986
4349
  }
3987
4350
  summarizeStartupStderr(target) {
3988
4351
  const joined = target.join("").trim();
@@ -4341,6 +4704,7 @@ function applyConversation(record, conversation) {
4341
4704
  record.updated_at = conversation.updated_at;
4342
4705
  record.messages = conversation.messages;
4343
4706
  record.cumulative_token_usage = conversation.cumulative_token_usage;
4707
+ record.cumulative_cost = conversation.cumulative_cost;
4344
4708
  record.request_token_usage = conversation.request_token_usage;
4345
4709
  }
4346
4710
  //#endregion
@@ -4416,6 +4780,51 @@ function nonEmptyString(value) {
4416
4780
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
4417
4781
  }
4418
4782
  //#endregion
4783
+ //#region src/session/model-state.ts
4784
+ function configOptionsAreAuthoritative(state) {
4785
+ return state.model_control === "config_option";
4786
+ }
4787
+ function legacyModelState(state) {
4788
+ if (!Array.isArray(state.available_models)) return;
4789
+ return {
4790
+ currentModelId: state.current_model_id ?? "",
4791
+ availableModels: state.available_models.map((modelId) => ({
4792
+ modelId,
4793
+ name: modelId
4794
+ }))
4795
+ };
4796
+ }
4797
+ function advertisedModelState(state) {
4798
+ if (!state) return;
4799
+ const configModels = modelStateFromConfigOptions(state?.config_options);
4800
+ if (configModels) return configModels;
4801
+ if (configOptionsAreAuthoritative(state)) return;
4802
+ return legacyModelState(state);
4803
+ }
4804
+ function applyAdvertisedModelState(state, models) {
4805
+ state.current_model_id = models.currentModelId;
4806
+ state.available_models = models.availableModels.map((model) => model.modelId);
4807
+ state.model_control = models.configId ? "config_option" : "legacy_set_model";
4808
+ }
4809
+ function clearAdvertisedModelState(state) {
4810
+ delete state.current_model_id;
4811
+ delete state.available_models;
4812
+ delete state.model_control;
4813
+ }
4814
+ function removeModelConfigOptions(state) {
4815
+ if (!state.config_options) return;
4816
+ state.config_options = state.config_options.filter((option) => option.category !== "model" && option.id !== "model");
4817
+ }
4818
+ function applyConfigOptionsModelState(state, configOptions) {
4819
+ const previousConfigModels = modelStateFromConfigOptions(state.config_options);
4820
+ const preservesLegacyControl = state.model_control === "legacy_set_model" || state.model_control === void 0 && previousConfigModels === void 0 && legacyModelState(state) !== void 0;
4821
+ state.config_options = structuredClone(configOptions);
4822
+ const models = modelStateFromConfigOptions(configOptions);
4823
+ if (models) applyAdvertisedModelState(state, models);
4824
+ else if (preservesLegacyControl) state.model_control = "legacy_set_model";
4825
+ else clearAdvertisedModelState(state);
4826
+ }
4827
+ //#endregion
4419
4828
  //#region src/session/conversation-model.ts
4420
4829
  const MAX_RUNTIME_MESSAGES = 200;
4421
4830
  const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
@@ -4436,10 +4845,25 @@ function hasOwn(source, key) {
4436
4845
  return Object.prototype.hasOwnProperty.call(source, key);
4437
4846
  }
4438
4847
  function normalizeAgentName(value) {
4848
+ return trimmedString(value);
4849
+ }
4850
+ function trimmedString(value) {
4439
4851
  if (typeof value !== "string") return;
4440
4852
  const trimmed = value.trim();
4441
4853
  return trimmed.length > 0 ? trimmed : void 0;
4442
4854
  }
4855
+ function normalizeAvailableCommand(value) {
4856
+ const record = asRecord(value);
4857
+ if (!record) return;
4858
+ const name = trimmedString(record.name);
4859
+ if (!name) return;
4860
+ const description = trimmedString(record.description);
4861
+ return {
4862
+ name,
4863
+ ...description ? { description } : {},
4864
+ has_input: record.input != null
4865
+ };
4866
+ }
4443
4867
  function extractText(content) {
4444
4868
  switch (content.type) {
4445
4869
  case "text": return content.text;
@@ -4667,7 +5091,9 @@ function usageToTokenUsage(update) {
4667
5091
  "cache_read_input_tokens",
4668
5092
  "cacheReadInputTokens",
4669
5093
  "cachedReadTokens"
4670
- ])
5094
+ ]),
5095
+ thought_tokens: numberField(source, ["thought_tokens", "thoughtTokens"]),
5096
+ total_tokens: numberField(source, ["total_tokens", "totalTokens"])
4671
5097
  };
4672
5098
  if (!hasTokenUsageValue(normalized)) return;
4673
5099
  return normalized;
@@ -4675,6 +5101,21 @@ function usageToTokenUsage(update) {
4675
5101
  function hasTokenUsageValue(usage) {
4676
5102
  return Object.values(usage).some((value) => value !== void 0);
4677
5103
  }
5104
+ function usageCost(update) {
5105
+ const cost = asRecord(asRecord(update)?.cost);
5106
+ if (!cost) return;
5107
+ return buildUsageCost(numberField(cost, ["amount"]), stringField(cost.currency));
5108
+ }
5109
+ function stringField(value) {
5110
+ return typeof value === "string" && value.trim() ? value : void 0;
5111
+ }
5112
+ function buildUsageCost(amount, currency) {
5113
+ const cost = {
5114
+ ...amount !== void 0 ? { amount } : {},
5115
+ ...currency !== void 0 ? { currency } : {}
5116
+ };
5117
+ return Object.keys(cost).length > 0 ? cost : void 0;
5118
+ }
4678
5119
  function ensureAcpxState$1(state) {
4679
5120
  return state ?? {};
4680
5121
  }
@@ -4690,6 +5131,7 @@ function createSessionConversation(timestamp = isoNow()) {
4690
5131
  messages: [],
4691
5132
  updated_at: timestamp,
4692
5133
  cumulative_token_usage: {},
5134
+ cumulative_cost: void 0,
4693
5135
  request_token_usage: {}
4694
5136
  };
4695
5137
  }
@@ -4700,9 +5142,13 @@ function cloneSessionConversation(conversation) {
4700
5142
  messages: deepClone(conversation.messages ?? []),
4701
5143
  updated_at: conversation.updated_at,
4702
5144
  cumulative_token_usage: deepClone(conversation.cumulative_token_usage ?? {}),
5145
+ cumulative_cost: cloneUsageCost(conversation.cumulative_cost),
4703
5146
  request_token_usage: deepClone(conversation.request_token_usage ?? {})
4704
5147
  };
4705
5148
  }
5149
+ function cloneUsageCost(cost) {
5150
+ return cost ? { ...cost } : void 0;
5151
+ }
4706
5152
  function cloneSessionAcpxState(state) {
4707
5153
  if (!state) return;
4708
5154
  return {
@@ -4711,7 +5157,8 @@ function cloneSessionAcpxState(state) {
4711
5157
  desired_config_options: state.desired_config_options ? { ...state.desired_config_options } : void 0,
4712
5158
  current_model_id: state.current_model_id,
4713
5159
  available_models: state.available_models ? [...state.available_models] : void 0,
4714
- available_commands: state.available_commands ? [...state.available_commands] : void 0,
5160
+ model_control: state.model_control,
5161
+ available_commands: state.available_commands ? state.available_commands.map((command) => ({ ...command })) : void 0,
4715
5162
  config_options: state.config_options ? deepClone(state.config_options) : void 0,
4716
5163
  session_options: cloneSessionOptions(state.session_options)
4717
5164
  };
@@ -4792,13 +5239,13 @@ const SESSION_UPDATE_HANDLERS = {
4792
5239
  if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
4793
5240
  },
4794
5241
  available_commands_update: (_conversation, acpx, update) => {
4795
- if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
5242
+ if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => normalizeAvailableCommand(entry)).filter((entry) => entry !== void 0);
4796
5243
  },
4797
5244
  current_mode_update: (_conversation, acpx, update) => {
4798
5245
  if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
4799
5246
  },
4800
5247
  config_option_update: (_conversation, acpx, update) => {
4801
- if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
5248
+ if (update.sessionUpdate === "config_option_update") applyConfigOptionsModelState(acpx, deepClone(update.configOptions));
4802
5249
  }
4803
5250
  };
4804
5251
  function appendUserMessageChunk(conversation, content) {
@@ -4815,10 +5262,14 @@ function appendAgentMessageChunk(conversation, content, append) {
4815
5262
  }
4816
5263
  function applyUsageUpdate(conversation, update) {
4817
5264
  const usage = usageToTokenUsage(update);
4818
- if (!usage) return;
4819
- conversation.cumulative_token_usage = usage;
4820
- const userId = lastUserMessageId(conversation);
4821
- if (userId) conversation.request_token_usage[userId] = usage;
5265
+ const cost = usageCost(update);
5266
+ if (!usage && !cost) return;
5267
+ if (usage) {
5268
+ conversation.cumulative_token_usage = usage;
5269
+ const userId = lastUserMessageId(conversation);
5270
+ if (userId) conversation.request_token_usage[userId] = usage;
5271
+ }
5272
+ if (cost) conversation.cumulative_cost = cost;
4822
5273
  }
4823
5274
  function applySessionInfoUpdate(conversation, update) {
4824
5275
  if (hasOwn(update, "title")) conversation.title = update.title ?? null;
@@ -4831,10 +5282,10 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
4831
5282
  return acpx;
4832
5283
  }
4833
5284
  function trimConversationForRuntime(conversation) {
4834
- if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-MAX_RUNTIME_MESSAGES);
5285
+ if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-200);
4835
5286
  for (const message of conversation.messages) trimRuntimeMessage(message);
4836
5287
  const requestUsageEntries = Object.entries(conversation.request_token_usage);
4837
- if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
5288
+ if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-100));
4838
5289
  }
4839
5290
  function trimRuntimeMessage(message) {
4840
5291
  if (isUserMessage(message)) {
@@ -4864,12 +5315,15 @@ function trimRuntimeToolResult(result) {
4864
5315
  }
4865
5316
  //#endregion
4866
5317
  //#region src/session/config-options.ts
5318
+ function applyConfigOptionsToState(state, configOptions) {
5319
+ const acpxState = cloneSessionAcpxState(state) ?? {};
5320
+ applyConfigOptionsModelState(acpxState, configOptions);
5321
+ return acpxState;
5322
+ }
4867
5323
  function applyConfigOptionsToRecord(record, result) {
4868
5324
  const configOptions = result?.configOptions;
4869
5325
  if (!configOptions) return;
4870
- const acpxState = cloneSessionAcpxState(record.acpx) ?? {};
4871
- acpxState.config_options = structuredClone(configOptions);
4872
- record.acpx = acpxState;
5326
+ record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
4873
5327
  }
4874
5328
  //#endregion
4875
5329
  //#region src/session/mode-preference.ts
@@ -4915,10 +5369,18 @@ function setDesiredConfigOption(record, configId, value) {
4915
5369
  else delete acpx.desired_config_options;
4916
5370
  record.acpx = acpx;
4917
5371
  }
5372
+ function clearDesiredConfigOption(state, configId) {
5373
+ const normalizedConfigId = normalizeModeId(configId);
5374
+ if (!normalizedConfigId || !state.desired_config_options) return;
5375
+ const desired = { ...state.desired_config_options };
5376
+ delete desired[normalizedConfigId];
5377
+ if (Object.keys(desired).length > 0) state.desired_config_options = desired;
5378
+ else delete state.desired_config_options;
5379
+ }
4918
5380
  function getDesiredModelId(state) {
4919
5381
  return normalizeModelId(state?.session_options?.model);
4920
5382
  }
4921
- function setDesiredModelId(record, modelId) {
5383
+ function setDesiredModelId(record, modelId, modelConfigId) {
4922
5384
  const acpx = ensureAcpxState(record.acpx);
4923
5385
  const normalized = normalizeModelId(modelId);
4924
5386
  const sessionOptions = { ...acpx.session_options };
@@ -4926,6 +5388,7 @@ function setDesiredModelId(record, modelId) {
4926
5388
  else delete sessionOptions.model;
4927
5389
  if (typeof sessionOptions.model === "string" || Array.isArray(sessionOptions.allowed_tools) || typeof sessionOptions.max_turns === "number" || sessionOptions.system_prompt !== void 0) acpx.session_options = sessionOptions;
4928
5390
  else delete acpx.session_options;
5391
+ clearDesiredConfigOption(acpx, modelConfigId ?? modelStateFromConfigOptions(acpx.config_options)?.configId);
4929
5392
  record.acpx = acpx;
4930
5393
  }
4931
5394
  function setCurrentModelId(record, modelId) {
@@ -4938,49 +5401,30 @@ function setCurrentModelId(record, modelId) {
4938
5401
  function syncAdvertisedModelState(record, models) {
4939
5402
  if (!models) return;
4940
5403
  const acpx = ensureAcpxState(record.acpx);
4941
- acpx.current_model_id = models.currentModelId;
4942
- acpx.available_models = models.availableModels.map((model) => model.modelId);
5404
+ applyAdvertisedModelState(acpx, models);
4943
5405
  record.acpx = acpx;
4944
5406
  }
4945
5407
  //#endregion
4946
- //#region src/acp/model-support.ts
4947
- var RequestedModelUnsupportedError = class extends Error {
4948
- constructor(message) {
4949
- super(message);
4950
- this.name = "RequestedModelUnsupportedError";
4951
- }
4952
- };
4953
- function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
4954
- if (!agentCommand) return false;
4955
- const { command, args } = splitCommandLine(agentCommand);
4956
- return isClaudeAcpCommand(command, args);
4957
- }
4958
- function formatAvailableModelIds(models) {
4959
- const ids = models?.availableModels.map((model) => model.modelId.trim()).filter((modelId) => modelId.length > 0) ?? [];
4960
- return ids.length > 0 ? ids.join(", ") : "none advertised";
4961
- }
4962
- function assertRequestedModelSupported(params) {
4963
- if (!params.models) {
4964
- if (supportsLegacyClaudeCodeModelMetadata(params.agentCommand)) return;
4965
- throw new RequestedModelUnsupportedError(`Cannot ${params.context === "replay" ? "replay saved model" : "apply --model"} "${params.requestedModel}": the ACP agent did not advertise model support. Generic model selection requires ACP models plus session/set_model support, or an adapter-specific startup model flag.`);
4966
- }
4967
- if (!new Set(params.models.availableModels.map((model) => model.modelId)).has(params.requestedModel)) throw new RequestedModelUnsupportedError(`Cannot ${params.context === "replay" ? "replay saved model" : "apply --model"} "${params.requestedModel}": the ACP agent did not advertise that model. Available models: ${formatAvailableModelIds(params.models)}.`);
4968
- }
4969
- //#endregion
4970
5408
  //#region src/session/model-application.ts
5409
+ function currentModelIdFromSetModelResponse(response, fallbackModelId) {
5410
+ return modelStateFromConfigOptions(response?.configOptions)?.currentModelId ?? fallbackModelId;
5411
+ }
4971
5412
  async function applyRequestedModelIfAdvertised(params) {
4972
5413
  const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
4973
- if (!requestedModel) return false;
4974
- assertRequestedModelSupported({
5414
+ if (!requestedModel) return { applied: false };
5415
+ const warning = assertRequestedModelSupported({
4975
5416
  requestedModel,
4976
5417
  models: params.models,
4977
5418
  agentCommand: params.agentCommand,
4978
5419
  context: "apply"
4979
5420
  });
4980
- if (!params.models) return false;
4981
- if (params.models.currentModelId === requestedModel) return true;
4982
- await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
4983
- return true;
5421
+ if (warning) params.onWarning?.(warning);
5422
+ if (!params.models) return { applied: false };
5423
+ if (params.models.currentModelId === requestedModel) return { applied: true };
5424
+ return {
5425
+ applied: true,
5426
+ response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models), params.timeoutMs)
5427
+ };
4984
5428
  }
4985
5429
  //#endregion
4986
5430
  //#region src/runtime/engine/reconnect.ts
@@ -5028,17 +5472,27 @@ async function replayDesiredMode(params) {
5028
5472
  }
5029
5473
  }
5030
5474
  async function replayDesiredModel(params) {
5031
- if (!params.desiredModelId) return;
5475
+ if (!params.desiredModelId) return { replayed: false };
5032
5476
  try {
5033
- assertRequestedModelSupported({
5477
+ emitModelSupportWarning(assertRequestedModelSupported({
5034
5478
  requestedModel: params.desiredModelId,
5035
5479
  models: params.models,
5036
5480
  agentCommand: params.record.agentCommand,
5037
5481
  context: "replay"
5038
- });
5039
- if (!params.models || params.models.currentModelId === params.desiredModelId) return;
5040
- await withTimeout(params.client.setSessionModel(params.sessionId, params.desiredModelId), params.timeoutMs);
5482
+ }), params.suppressWarnings);
5483
+ if (!params.models || params.models.currentModelId === params.desiredModelId) return { replayed: false };
5484
+ const response = await withTimeout(params.client.setSessionModel(params.sessionId, params.desiredModelId, params.models), params.timeoutMs);
5485
+ applyConfigOptionsToRecord(params.record, response);
5486
+ const models = response ? modelStateFromConfigOptions(response.configOptions) : {
5487
+ ...params.models,
5488
+ currentModelId: params.desiredModelId
5489
+ };
5041
5490
  if (params.verbose) process.stderr.write(`[acpx] replayed desired model ${params.desiredModelId} on fresh ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5491
+ return {
5492
+ replayed: true,
5493
+ models,
5494
+ configOptionsPresent: response !== void 0
5495
+ };
5042
5496
  } catch (error) {
5043
5497
  throw new SessionModelReplayError(`Failed to replay saved session model ${params.desiredModelId} on fresh ACP session ${params.sessionId}: ${formatErrorMessage(error)}`, {
5044
5498
  cause: error instanceof Error ? error : void 0,
@@ -5046,9 +5500,18 @@ async function replayDesiredModel(params) {
5046
5500
  });
5047
5501
  }
5048
5502
  }
5503
+ function emitModelSupportWarning(warning, suppressWarnings) {
5504
+ if (warning && !suppressWarnings) process.stderr.write(`[acpx] warning: ${warning}\n`);
5505
+ }
5049
5506
  async function replayDesiredConfigOptions(params) {
5507
+ let result = { replayed: false };
5050
5508
  for (const [configId, value] of Object.entries(params.desiredConfigOptions)) try {
5051
- await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value), params.timeoutMs);
5509
+ const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value), params.timeoutMs);
5510
+ applyConfigOptionsToRecord(params.record, response);
5511
+ result = {
5512
+ replayed: true,
5513
+ models: modelStateFromConfigOptions(response.configOptions)
5514
+ };
5052
5515
  if (params.verbose) process.stderr.write(`[acpx] replayed desired config option ${configId} on fresh ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5053
5516
  } catch (error) {
5054
5517
  throw new SessionConfigOptionReplayError(`Failed to replay saved session config option ${configId} on fresh ACP session ${params.sessionId}: ${formatErrorMessage(error)}`, {
@@ -5056,6 +5519,7 @@ async function replayDesiredConfigOptions(params) {
5056
5519
  retryable: true
5057
5520
  });
5058
5521
  }
5522
+ return result;
5059
5523
  }
5060
5524
  function restoreOriginalSessionState(params) {
5061
5525
  params.record.acpSessionId = params.sessionId;
@@ -5064,9 +5528,10 @@ function restoreOriginalSessionState(params) {
5064
5528
  async function connectAndLoadSession(options) {
5065
5529
  const record = options.record;
5066
5530
  const client = options.client;
5067
- const sameSessionOnly = requiresSameSession(options.resumePolicy);
5531
+ const sameSessionOnly = requiresSameSession(options.resumePolicy) || Boolean(record.importedFrom);
5068
5532
  const originalSessionId = record.acpSessionId;
5069
5533
  const originalAgentSessionId = record.agentSessionId;
5534
+ const originalAcpx = cloneSessionAcpxState(record.acpx);
5070
5535
  const desiredModeId = getDesiredModeId(record.acpx);
5071
5536
  const desiredModelId = getDesiredModelId(record.acpx);
5072
5537
  const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
@@ -5099,7 +5564,7 @@ async function connectAndLoadSession(options) {
5099
5564
  createdFreshSession = loadState.createdFreshSession;
5100
5565
  pendingAgentSessionId = loadState.pendingAgentSessionId;
5101
5566
  sessionModels = loadState.sessionModels;
5102
- await replayFreshSessionPreferences({
5567
+ const preferenceReplay = await replayFreshSessionPreferences({
5103
5568
  client,
5104
5569
  record,
5105
5570
  createdFreshSession,
@@ -5107,14 +5572,16 @@ async function connectAndLoadSession(options) {
5107
5572
  pendingAgentSessionId,
5108
5573
  originalSessionId,
5109
5574
  originalAgentSessionId,
5575
+ originalAcpx,
5110
5576
  desiredModeId,
5111
5577
  desiredModelId,
5112
5578
  desiredConfigOptions,
5113
5579
  sessionModels,
5114
5580
  timeoutMs: options.timeoutMs,
5115
- verbose: options.verbose
5581
+ verbose: options.verbose,
5582
+ suppressWarnings: options.suppressWarnings
5116
5583
  });
5117
- applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
5584
+ applyReconnectedModelState(record, resolveModelsAfterReplay(preferenceReplay, sessionModels), resolveConfigOptionsPresenceAfterReplay(preferenceReplay, loadState.configOptionsPresent), loadState.legacyModelMetadataPresent, createdFreshSession);
5118
5585
  options.onSessionIdResolved?.(sessionId);
5119
5586
  return {
5120
5587
  sessionId,
@@ -5123,9 +5590,28 @@ async function connectAndLoadSession(options) {
5123
5590
  loadError
5124
5591
  };
5125
5592
  }
5126
- function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
5127
- syncAdvertisedModelState(record, sessionModels);
5128
- if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
5593
+ function resolveModelsAfterReplay(replay, initialModels) {
5594
+ if (replay.configReplay.replayed) return replay.configReplay.models ?? preserveLegacyModels(replay.modelReplay.replayed ? replay.modelReplay.models : initialModels);
5595
+ return replay.modelReplay.replayed ? replay.modelReplay.models : initialModels;
5596
+ }
5597
+ function preserveLegacyModels(models) {
5598
+ return models && !models.configId ? models : void 0;
5599
+ }
5600
+ function resolveConfigOptionsPresenceAfterReplay(replay, initiallyPresent) {
5601
+ return initiallyPresent || replay.configReplay.replayed || replay.modelReplay.replayed && replay.modelReplay.configOptionsPresent;
5602
+ }
5603
+ function applyReconnectedModelState(record, sessionModels, configOptionsPresent, legacyModelMetadataPresent, createdFreshSession) {
5604
+ clearOmittedFreshSessionConfigOptions(record, createdFreshSession, configOptionsPresent);
5605
+ if (sessionModels) {
5606
+ if (legacyModelMetadataPresent && !sessionModels.configId && record.acpx) removeModelConfigOptions(record.acpx);
5607
+ syncAdvertisedModelState(record, sessionModels);
5608
+ } else clearRemovedModelState(record, legacyModelMetadataPresent || createdFreshSession);
5609
+ }
5610
+ function clearOmittedFreshSessionConfigOptions(record, createdFreshSession, configOptionsPresent) {
5611
+ if (createdFreshSession && !configOptionsPresent && record.acpx) delete record.acpx.config_options;
5612
+ }
5613
+ function clearRemovedModelState(record, shouldClear) {
5614
+ if (shouldClear && record.acpx) clearAdvertisedModelState(record.acpx);
5129
5615
  }
5130
5616
  function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
5131
5617
  if (!verbose) return;
@@ -5136,7 +5622,12 @@ function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbos
5136
5622
  if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
5137
5623
  }
5138
5624
  async function replayFreshSessionPreferences(params) {
5139
- if (!params.createdFreshSession) return;
5625
+ if (!params.createdFreshSession) return {
5626
+ modelReplay: { replayed: false },
5627
+ configReplay: { replayed: false }
5628
+ };
5629
+ let modelReplay = { replayed: false };
5630
+ let configReplay = { replayed: false };
5140
5631
  try {
5141
5632
  await replayDesiredMode({
5142
5633
  client: params.client,
@@ -5146,7 +5637,7 @@ async function replayFreshSessionPreferences(params) {
5146
5637
  timeoutMs: params.timeoutMs,
5147
5638
  verbose: params.verbose
5148
5639
  });
5149
- await replayDesiredModel({
5640
+ modelReplay = await replayDesiredModel({
5150
5641
  client: params.client,
5151
5642
  sessionId: params.sessionId,
5152
5643
  desiredModelId: params.desiredModelId,
@@ -5154,10 +5645,12 @@ async function replayFreshSessionPreferences(params) {
5154
5645
  record: params.record,
5155
5646
  models: params.sessionModels,
5156
5647
  timeoutMs: params.timeoutMs,
5157
- verbose: params.verbose
5648
+ verbose: params.verbose,
5649
+ suppressWarnings: params.suppressWarnings
5158
5650
  });
5159
- await replayDesiredConfigOptions({
5651
+ configReplay = await replayDesiredConfigOptions({
5160
5652
  client: params.client,
5653
+ record: params.record,
5161
5654
  sessionId: params.sessionId,
5162
5655
  desiredConfigOptions: params.desiredConfigOptions,
5163
5656
  previousSessionId: params.originalSessionId,
@@ -5170,17 +5663,24 @@ async function replayFreshSessionPreferences(params) {
5170
5663
  sessionId: params.originalSessionId,
5171
5664
  agentSessionId: params.originalAgentSessionId
5172
5665
  });
5666
+ params.record.acpx = cloneSessionAcpxState(params.originalAcpx);
5173
5667
  if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
5174
5668
  throw error;
5175
5669
  }
5176
5670
  params.record.acpSessionId = params.sessionId;
5177
5671
  reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
5672
+ return {
5673
+ modelReplay,
5674
+ configReplay
5675
+ };
5178
5676
  }
5179
5677
  async function loadOrCreateRuntimeSession(params) {
5180
5678
  if (params.reusingLoadedSession) return {
5181
5679
  sessionId: params.record.acpSessionId,
5182
5680
  pendingAgentSessionId: params.record.agentSessionId,
5183
5681
  sessionModels: void 0,
5682
+ configOptionsPresent: false,
5683
+ legacyModelMetadataPresent: false,
5184
5684
  resumed: true,
5185
5685
  createdFreshSession: false
5186
5686
  };
@@ -5201,6 +5701,8 @@ async function resumeRuntimeSession(params) {
5201
5701
  sessionId: params.record.acpSessionId,
5202
5702
  pendingAgentSessionId: params.record.agentSessionId,
5203
5703
  sessionModels: resumeResult.models,
5704
+ configOptionsPresent: resumeResult.configOptionsPresent,
5705
+ legacyModelMetadataPresent: resumeResult.legacyModelMetadataPresent,
5204
5706
  resumed: true,
5205
5707
  createdFreshSession: false
5206
5708
  };
@@ -5217,6 +5719,8 @@ async function loadRuntimeSession(params) {
5217
5719
  sessionId: params.record.acpSessionId,
5218
5720
  pendingAgentSessionId: params.record.agentSessionId,
5219
5721
  sessionModels: loadResult.models,
5722
+ configOptionsPresent: loadResult.configOptionsPresent,
5723
+ legacyModelMetadataPresent: loadResult.legacyModelMetadataPresent,
5220
5724
  resumed: true,
5221
5725
  createdFreshSession: false
5222
5726
  };
@@ -5244,6 +5748,8 @@ async function createFreshRuntimeSession(client, record, timeoutMs) {
5244
5748
  sessionId: createdSession.sessionId,
5245
5749
  pendingAgentSessionId: createdSession.agentSessionId,
5246
5750
  sessionModels: createdSession.models,
5751
+ configOptionsPresent: createdSession.configOptionsPresent,
5752
+ legacyModelMetadataPresent: createdSession.legacyModelMetadataPresent,
5247
5753
  resumed: false,
5248
5754
  createdFreshSession: true
5249
5755
  };
@@ -5259,7 +5765,10 @@ function createActiveSessionController(params) {
5259
5765
  await params.client.setSessionMode(getActiveSessionId(), modeId);
5260
5766
  },
5261
5767
  setSessionModel: async (modelId) => {
5262
- await params.client.setSessionModel(getActiveSessionId(), modelId);
5768
+ const models = advertisedModelState(params.record.acpx);
5769
+ const response = await params.client.setSessionModel(getActiveSessionId(), modelId, models);
5770
+ applyConfigOptionsToRecord(params.record, response);
5771
+ return response;
5263
5772
  },
5264
5773
  setSessionConfigOption: async (configId, value) => {
5265
5774
  return await params.client.setSessionConfigOption(getActiveSessionId(), configId, value);
@@ -5297,6 +5806,7 @@ async function withConnectedSession(options) {
5297
5806
  let notifiedClientAvailable = false;
5298
5807
  const activeController = createActiveSessionController({
5299
5808
  client,
5809
+ record,
5300
5810
  getActiveSessionId: () => activeSessionIdForControl
5301
5811
  });
5302
5812
  try {
@@ -5440,6 +5950,6 @@ var LiveSessionCheckpoint = class {
5440
5950
  }
5441
5951
  };
5442
5952
  //#endregion
5443
- export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, QueueProtocolError as Bt, applyConversation as C, formatErrorMessage as Ct, extractSessionUpdateNotification as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, findSession as F, PERMISSION_MODES as Ft, DEFAULT_EVENT_SEGMENT_MAX_BYTES as G, resolveSessionRecord as H, findSessionByDirectoryWalk as I, PERMISSION_POLICY_ACTIONS as It, sessionEventActivePath as J, defaultSessionEventLog as K, isoNow$2 as L, SESSION_RECORD_SCHEMA as Lt, DEFAULT_HISTORY_LIMIT as M, OUTPUT_ERROR_CODES as Mt, absolutePath as N, OUTPUT_ERROR_ORIGINS as Nt, isAcpJsonRpcMessage as O, toAcpErrorPayload as Ot, findGitRepositoryRoot as P, OUTPUT_FORMATS as Pt, formatPerfMetric as Q, listSessions as R, AgentSpawnError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, startPerfTimer as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, incrementPerfCounter as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation as h, TimeoutError as ht, connectAndLoadSession as i, setPerfGauge as it, permissionModeSatisfies as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, parseJsonRpcErrorMessage as k, AUTH_POLICIES as kt, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, assertRequestedModelSupported as o, serializeSessionRecordForDisk as ot, cloneSessionAcpxState as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, setCurrentModelId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf as tt, setDesiredModelId as u, mergePromptSourceWithText as ut, recordSessionUpdate as v, DEFAULT_AGENT_NAME as vt, applyLifecycleSnapshotToRecord as w, isRetryablePromptError as wt, persistSessionOptions as x, resolveAgentCommand as xt, trimConversationForRuntime as y, listBuiltInAgents as yt, listSessionsForAgent as z, QueueConnectionError as zt };
5953
+ export { setPerfGauge as $, assertRequestedModelSupported as A, isRetryablePromptError as At, listSessions as B, OUTPUT_FORMATS as Bt, mergeSessionOptions as C, withTimeout as Ct, applyLifecycleSnapshotToRecord as D, resolveAgentCommand as Dt, applyConversation as E, normalizeAgentName$1 as Et, absolutePath as F, AUTH_POLICIES as Ft, writeSessionRecord as G, AgentSpawnError as Gt, normalizeName as H, PERMISSION_POLICY_ACTIONS as Ht, findGitRepositoryRoot as I, EXIT_CODES as It, getPerfMetricsSnapshot as J, assertPersistedKeyPolicy as K, QueueConnectionError as Kt, findSession as L, NON_INTERACTIVE_PERMISSION_POLICIES as Lt, getAcpxVersion as M, extractAcpError as Mt, permissionModeSatisfies as N, isAcpResourceNotFoundError as Nt, reconcileAgentSessionId as O, exitCodeForOutputErrorCode as Ot, DEFAULT_HISTORY_LIMIT as P, toAcpErrorPayload as Pt, resetPerfMetrics as Q, findSessionByDirectoryWalk as R, OUTPUT_ERROR_CODES as Rt, advertisedModelState as S, withInterrupt as St, sessionOptionsFromRecord as T, listBuiltInAgents as Tt, pruneSessions as U, SESSION_RECORD_SCHEMA as Ut, listSessionsForAgent as V, PERMISSION_MODES as Vt, resolveSessionRecord as W, AcpxOperationalError as Wt, measurePerf as X, incrementPerfCounter as Y, recordPerfDuration as Z, createSessionConversation as _, parsePromptSource as _t, applyRequestedModelIfAdvertised as a, defaultSessionEventLog as at, recordSessionUpdate as b, InterruptedError as bt, setCurrentModelId as c, sessionEventLockPath as ct, setDesiredModelId as d, isAcpJsonRpcMessage as dt, startPerfTimer as et, syncAdvertisedModelState as f, parseJsonRpcErrorMessage as ft, cloneSessionConversation as g, mergePromptSourceWithText as gt, cloneSessionAcpxState as h, isPromptInput as ht, connectAndLoadSession as i, DEFAULT_EVENT_SEGMENT_MAX_BYTES as it, modelStateFromConfigOptions as j, normalizeOutputError as jt, AcpClient as k, formatErrorMessage as kt, setDesiredConfigOption as l, sessionEventSegmentPath as lt, applyConfigOptionsToState as m, PromptInputValidationError as mt, runPromptTurn as n, serializeSessionRecordForDisk as nt, currentModelIdFromSetModelResponse as o, sessionBaseDir$1 as ot, applyConfigOptionsToRecord as p, parsePromptStopReason as pt, formatPerfMetric as q, QueueProtocolError as qt, withConnectedSession as r, normalizeRuntimeSessionId as rt, clearDesiredConfigOption as s, sessionEventActivePath as st, LiveSessionCheckpoint as t, parseSessionRecord as tt, setDesiredModeId as u, extractSessionUpdateNotification as ut, recordClientOperation as v, promptToDisplayText as vt, persistSessionOptions as w, DEFAULT_AGENT_NAME as wt, trimConversationForRuntime as x, TimeoutError as xt, recordPromptSubmission as y, textPrompt as yt, isoNow$2 as z, OUTPUT_ERROR_ORIGINS as zt };
5444
5954
 
5445
- //# sourceMappingURL=live-checkpoint-D5d-K9s1.js.map
5955
+ //# sourceMappingURL=live-checkpoint-BZrk9Mjz.js.map