@agentclientprotocol/claude-agent-acp 0.75.2-preview.1 → 0.75.2-preview.2

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.
@@ -1,6 +1,7 @@
1
1
  export declare const AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY = "nativeSubagentSessions";
2
2
  export declare const AIR_ASYNC_TASKS_CAPABILITY = "asyncTasks";
3
3
  export declare const AIR_SESSION_FAILURE_CAPABILITY = "sessionFailure";
4
+ export declare const AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY = "recommendedValue";
4
5
  /** The capability list this side advertises, as its own `_meta` object. */
5
6
  export declare function airCapabilityMeta(...capabilities: string[]): Record<string, unknown>;
6
7
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"air-extension.d.ts","sourceRoot":"","sources":["../src/air-extension.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uCAAuC,2BAA2B,CAAC;AAChF,eAAO,MAAM,0BAA0B,eAAe,CAAC;AACvD,eAAO,MAAM,8BAA8B,mBAAmB,CAAC;AAQ/D,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,YAAY,EAAE,MAAM,EAAE,2BAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAChD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAY9F"}
1
+ {"version":3,"file":"air-extension.d.ts","sourceRoot":"","sources":["../src/air-extension.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uCAAuC,2BAA2B,CAAC;AAChF,eAAO,MAAM,0BAA0B,eAAe,CAAC;AACvD,eAAO,MAAM,8BAA8B,mBAAmB,CAAC;AAC/D,eAAO,MAAM,uCAAuC,qBAAqB,CAAC;AAQ1E,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,YAAY,EAAE,MAAM,EAAE,2BAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAChD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAY9F"}
@@ -1,6 +1,7 @@
1
1
  export const AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY = "nativeSubagentSessions";
2
2
  export const AIR_ASYNC_TASKS_CAPABILITY = "asyncTasks";
3
3
  export const AIR_SESSION_FAILURE_CAPABILITY = "sessionFailure";
4
+ export const AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY = "recommendedValue";
4
5
  const JETBRAINS_META_KEY = "jetbrains";
5
6
  const AIR_META_KEY = "air";
6
7
  const AIR_EXTENSION_VERSION_KEY = "version";
@@ -31,6 +31,7 @@ export type ClearContextSession<Turn extends ClearContextTurn = ClearContextTurn
31
31
  configOptions: SessionConfigOption[];
32
32
  currentAgent: string;
33
33
  fastModeEnabled: boolean;
34
+ effortPinnedLevel?: string;
34
35
  activeTurn?: Turn | null;
35
36
  turnQueue?: Turn[];
36
37
  pendingExitPlanContextReset?: ClearContextReset;
@@ -1 +1 @@
1
- {"version":3,"file":"clear-context-coordinator.d.ts","sourceRoot":"","sources":["../src/clear-context-coordinator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"clear-context-coordinator.d.ts","sourceRoot":"","sources":["../src/clear-context-coordinator.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,6 @@
1
- import { DEFAULT_AGENT_ID, DEFAULT_MODEL_ID, EFFORT_CONFIG_ID } from "./session-config-ids.js";
1
+ import { DEFAULT_AGENT_ID, DEFAULT_MODEL_ID } from "./session-config-ids.js";
2
2
  function restartParams(session) {
3
3
  const originalParams = session.creationParams ?? { cwd: session.cwd, mcpServers: [] };
4
- const currentEffort = session.configOptions.find((option) => option.id === EFFORT_CONFIG_ID)?.currentValue;
5
4
  const originalMeta = originalParams._meta;
6
5
  const originalOptions = originalMeta?.claudeCode?.options;
7
6
  const unmanagedOptions = { ...(originalOptions ?? {}) };
@@ -20,8 +19,8 @@ function restartParams(session) {
20
19
  ? { model: session.models.currentModelId }
21
20
  : {}),
22
21
  ...(session.currentAgent !== DEFAULT_AGENT_ID ? { agent: session.currentAgent } : {}),
23
- ...(typeof currentEffort === "string" && currentEffort !== "default"
24
- ? { effort: currentEffort }
22
+ ...(session.effortPinnedLevel !== undefined
23
+ ? { effort: session.effortPinnedLevel }
25
24
  : {}),
26
25
  },
27
26
  },
@@ -0,0 +1,14 @@
1
+ import type { SessionConfigOption } from "@agentclientprotocol/sdk";
2
+ import type { EffortLevel, ModelInfo, Settings } from "@anthropic-ai/claude-agent-sdk";
3
+ export { EFFORT_CONFIG_ID } from "./session-config-ids.js";
4
+ export declare function toSdkEffortLevel(value: string | undefined): EffortLevel | null;
5
+ /** Resolve the effort the CLI will use: per-model settings first, then the
6
+ * legacy top-level effort setting. Model settings are keyed by canonical
7
+ * model name, so try the resolved SDK model before picker and raw IDs. */
8
+ export declare function settingsEffortForModel(settings: Settings, modelInfo: ModelInfo | undefined, modelId?: string): string | undefined;
9
+ /** Programmatic query settings have higher priority than file-backed settings,
10
+ * matching the SDK. Keep unrelated per-model entries from lower tiers while
11
+ * replacing entries supplied at the programmatic tier. */
12
+ export declare function mergeEffortSettings(base: Settings, override: Settings | undefined): Settings;
13
+ export declare function buildEffortConfigOption(modelInfos: ModelInfo[], currentModelId: string, currentEffortLevel: string | undefined, useRecommendedValue: boolean): SessionConfigOption | undefined;
14
+ //# sourceMappingURL=session-effort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-effort.d.ts","sourceRoot":"","sources":["../src/session-effort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAIvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAK3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI,CAE9E;AASD;;2EAE2E;AAC3E,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAgBpB;AAED;;0DAE0D;AAC1D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAkB5F;AAED,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,EAAE,EACvB,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,GAAG,SAAS,CA2CjC"}
@@ -0,0 +1,93 @@
1
+ import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
2
+ import { EFFORT_CONFIG_ID } from "./session-config-ids.js";
3
+ export { EFFORT_CONFIG_ID } from "./session-config-ids.js";
4
+ // The SDK drops `undefined` during JSON transport and only clears a flag-layer
5
+ // setting when it receives an explicit `null`. Map both an absent picker and
6
+ // the legacy "default" row to null so a previously applied effort is cleared.
7
+ export function toSdkEffortLevel(value) {
8
+ return value === undefined || value === "default" ? null : value;
9
+ }
10
+ function canonicalizeModelSettingsKey(value) {
11
+ return value
12
+ .trim()
13
+ .toLowerCase()
14
+ .replace(/-(\d+m)$/i, "[$1]");
15
+ }
16
+ /** Resolve the effort the CLI will use: per-model settings first, then the
17
+ * legacy top-level effort setting. Model settings are keyed by canonical
18
+ * model name, so try the resolved SDK model before picker and raw IDs. */
19
+ export function settingsEffortForModel(settings, modelInfo, modelId) {
20
+ const modelSettings = settings.modelSettings;
21
+ if (modelSettings) {
22
+ for (const key of [modelInfo?.resolvedModel, modelInfo?.value, modelId]) {
23
+ if (key === undefined)
24
+ continue;
25
+ const exact = modelSettings[key]?.effortLevel;
26
+ if (typeof exact === "string")
27
+ return exact;
28
+ const canonicalKey = canonicalizeModelSettingsKey(key);
29
+ const matchingEntry = Object.entries(modelSettings).find(([candidate]) => canonicalizeModelSettingsKey(candidate) === canonicalKey);
30
+ const perModel = matchingEntry?.[1]?.effortLevel;
31
+ if (typeof perModel === "string")
32
+ return perModel;
33
+ }
34
+ }
35
+ return settings.effortLevel;
36
+ }
37
+ /** Programmatic query settings have higher priority than file-backed settings,
38
+ * matching the SDK. Keep unrelated per-model entries from lower tiers while
39
+ * replacing entries supplied at the programmatic tier. */
40
+ export function mergeEffortSettings(base, override) {
41
+ if (!override)
42
+ return base;
43
+ const overriddenModelKeys = new Set(Object.keys(override.modelSettings ?? {}).map(canonicalizeModelSettingsKey));
44
+ const unshadowedBaseModelSettings = Object.fromEntries(Object.entries(base.modelSettings ?? {}).filter(([key]) => !overriddenModelKeys.has(canonicalizeModelSettingsKey(key))));
45
+ return {
46
+ ...base,
47
+ ...override,
48
+ modelSettings: base.modelSettings || override.modelSettings
49
+ ? { ...unshadowedBaseModelSettings, ...override.modelSettings }
50
+ : undefined,
51
+ };
52
+ }
53
+ export function buildEffortConfigOption(modelInfos, currentModelId, currentEffortLevel, useRecommendedValue) {
54
+ const currentModelInfo = modelInfos.find((model) => model.value === currentModelId);
55
+ const supportedLevels = currentModelInfo?.supportsEffort
56
+ ? (currentModelInfo.supportedEffortLevels ?? [])
57
+ : [];
58
+ if (supportedLevels.length === 0)
59
+ return undefined;
60
+ const recommendedEffort = supportedLevels.includes("medium")
61
+ ? "medium"
62
+ : supportedLevels[0];
63
+ const options = [
64
+ ...(useRecommendedValue ? [] : [{ value: "default", name: "Default" }]),
65
+ ...supportedLevels.map((level) => ({
66
+ value: level,
67
+ name: level
68
+ .split(/[_-]/)
69
+ .map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
70
+ .join(" "),
71
+ })),
72
+ ];
73
+ const includes = (level) => (!useRecommendedValue && level === "default") || supportedLevels.includes(level);
74
+ const currentValue = currentEffortLevel && includes(currentEffortLevel)
75
+ ? currentEffortLevel
76
+ : useRecommendedValue
77
+ ? recommendedEffort
78
+ : "default";
79
+ return {
80
+ id: EFFORT_CONFIG_ID,
81
+ name: "Effort",
82
+ description: "Available effort levels for this model",
83
+ category: "thought_level",
84
+ type: "select",
85
+ currentValue,
86
+ options,
87
+ ...(useRecommendedValue
88
+ ? {
89
+ _meta: withAirMeta(undefined, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, recommendedEffort),
90
+ }
91
+ : {}),
92
+ };
93
+ }
@@ -0,0 +1,41 @@
1
+ import type { SessionConfigOption } from "@agentclientprotocol/sdk";
2
+ import type { ModelInfo, Query, Settings } from "@anthropic-ai/claude-agent-sdk";
3
+ export declare const MODEL_CONFIG_ID = "model";
4
+ export type SessionModelState = {
5
+ availableModels: Array<{
6
+ modelId: string;
7
+ name: string;
8
+ description?: string;
9
+ }>;
10
+ currentModelId: string;
11
+ };
12
+ type ModelLogger = {
13
+ log: (...args: unknown[]) => void;
14
+ error: (...args: unknown[]) => void;
15
+ };
16
+ type ModelSettingsSource = {
17
+ getSettings(): Settings;
18
+ };
19
+ export declare function resolveModelPreference(models: ModelInfo[], preference: string): ModelInfo | null;
20
+ export declare function matchResumedModel(models: ModelInfo[], liveModel: string): ModelInfo;
21
+ export declare function buildModelConfigOption(models: SessionModelState, modelInfos: ModelInfo[], useRecommendedValue: boolean): SessionConfigOption;
22
+ /**
23
+ * Restrict the SDK's model list to the user's `availableModels` allowlist
24
+ * (already merged-and-deduped across settings sources by `SettingsManager`).
25
+ * The user's exact entries become the model IDs surfaced via configOptions
26
+ * and passed to `setModel`, which prevents Claude Code from silently
27
+ * substituting a date-pinned variant (e.g. `haiku` →
28
+ * `claude-haiku-4-5-20251001`) that the user may not have access to.
29
+ *
30
+ * Display info and capability flags are copied from the closest SDK match so
31
+ * the UI still renders sensible names and effort levels.
32
+ *
33
+ * Semantics from https://code.claude.com/docs/en/model-config#restrict-model-selection:
34
+ * - `undefined` is handled by the caller (no allowlist applied).
35
+ * - The Default option is unaffected by `availableModels` — it always remains
36
+ * available, even when the allowlist is `[]`.
37
+ */
38
+ export declare function applyAvailableModelsAllowlist(sdkModels: ModelInfo[], allowlist: string[], settingsModelOverrides?: Record<string, string>): ModelInfo[];
39
+ export declare function getAvailableModels(query: Query, models: ModelInfo[], sdkModels: ModelInfo[], settingsManager: ModelSettingsSource, logger: ModelLogger, isResumedSession: boolean, sessionId: string, resumedModelHint?: string): Promise<SessionModelState>;
40
+ export {};
41
+ //# sourceMappingURL=session-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-model.d.ts","sourceRoot":"","sources":["../src/session-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAGjF,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AACF,KAAK,mBAAmB,GAAG;IAAE,WAAW,IAAI,QAAQ,CAAA;CAAE,CAAC;AAmGvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA2ChG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAyBnF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,SAAS,EAAE,EACvB,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAsDrB;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,MAAM,EAAE,EACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9C,SAAS,EAAE,CAsDb;AAWD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,eAAe,EAAE,mBAAmB,EACpC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,OAAO,EACzB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,iBAAiB,CAAC,CA6G5B"}
@@ -0,0 +1,395 @@
1
+ import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
2
+ export const MODEL_CONFIG_ID = "model";
3
+ const MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
4
+ const CONTEXT_HINT_SUFFIX_PATTERN = /-(\d+m)$/i;
5
+ const MODEL_FAMILY_VERSION_PATTERN = /\b(\d+)(?:[-.](\d+))?\b/;
6
+ const VERSIONABLE_MODEL_DISPLAY_PATTERN = /^(?:Claude\s+)?([a-z][a-z0-9-]*)(?:\s+\d+(?:\.\d+)?)?(?:\s+\(\d+[mk]\s+context\))?$/i;
7
+ const DISPLAY_CONTEXT_SUFFIX_PATTERN = /\s+\((\d+[mk])\s+context\)$/i;
8
+ function stripContextHints(value) {
9
+ return value.replace(/\[\d+m\]/gi, "").replace(CONTEXT_HINT_SUFFIX_PATTERN, "");
10
+ }
11
+ function canonicalizeModelId(value) {
12
+ return value.trim().toLowerCase().replace(CONTEXT_HINT_SUFFIX_PATTERN, "[$1]");
13
+ }
14
+ function contextHintOf(value) {
15
+ return canonicalizeModelId(value).match(MODEL_CONTEXT_HINT_PATTERN)?.[1] ?? null;
16
+ }
17
+ function extractModelFamilyVersion(value) {
18
+ const match = stripContextHints(value).match(MODEL_FAMILY_VERSION_PATTERN);
19
+ if (!match)
20
+ return null;
21
+ return match[2] ? `${match[1]}.${match[2]}` : match[1];
22
+ }
23
+ function modelVersionsCompatible(preference, candidate) {
24
+ const preferred = extractModelFamilyVersion(preference);
25
+ if (!preferred)
26
+ return true;
27
+ const candidateVersion = extractModelFamilyVersion(candidate.value) ??
28
+ extractModelFamilyVersion(candidate.displayName) ??
29
+ extractModelFamilyVersion(candidate.description);
30
+ return candidateVersion ? preferred === candidateVersion : true;
31
+ }
32
+ function displayModelVersion(model, family) {
33
+ const familyVersion = new RegExp(`\\b${family}[-\\s]+(\\d+)(?:[-.](\\d+))?`, "i");
34
+ // The display name is the value we are deciding whether to rewrite, so it
35
+ // cannot validate its own normalization. Require independent SDK metadata.
36
+ for (const source of [model.resolvedModel, model.description, model.value]) {
37
+ const match = source?.match(familyVersion);
38
+ if (match)
39
+ return match[2] ? `${match[1]}.${match[2]}` : match[1];
40
+ }
41
+ return undefined;
42
+ }
43
+ /** Add concrete versions when a terse SDK label names the same family as the
44
+ * underlying model metadata. Multi-word/custom labels remain untouched.
45
+ * Context size remains in the option description; if normalization would
46
+ * collide, callers fall back to the original labels. */
47
+ function versionedModelDisplayName(model) {
48
+ const versionable = model.displayName.match(VERSIONABLE_MODEL_DISPLAY_PATTERN);
49
+ if (!versionable)
50
+ return model.displayName;
51
+ const version = displayModelVersion(model, versionable[1]);
52
+ if (!version)
53
+ return model.displayName;
54
+ const contextSuffix = model.displayName.match(DISPLAY_CONTEXT_SUFFIX_PATTERN);
55
+ const descriptionKeepsContext = contextSuffix !== null &&
56
+ new RegExp(`\\b${contextSuffix[1]}\\s+context\\b`, "i").test(model.description);
57
+ const withoutContext = model.displayName.replace(DISPLAY_CONTEXT_SUFFIX_PATTERN, "");
58
+ if (/\b\d+(?:\.\d+)?\b/.test(withoutContext)) {
59
+ return descriptionKeepsContext ? withoutContext : model.displayName;
60
+ }
61
+ if (descriptionKeepsContext || contextSuffix === null)
62
+ return `${withoutContext} ${version}`;
63
+ // Keep the suffix in its original spelling and insert the version before it.
64
+ return `${withoutContext} ${version}${contextSuffix[0]}`;
65
+ }
66
+ function tokenizeModelPreference(model) {
67
+ const lower = model.trim().toLowerCase();
68
+ const contextHint = lower.match(MODEL_CONTEXT_HINT_PATTERN)?.[1]?.toLowerCase();
69
+ const tokens = lower
70
+ .replace(MODEL_CONTEXT_HINT_PATTERN, " $1 ")
71
+ .split(/[^a-z0-9]+/)
72
+ .filter(Boolean)
73
+ .map((token) => {
74
+ if (token === "opusplan")
75
+ return "opus";
76
+ if (token === "best" || token === "default")
77
+ return "";
78
+ return token;
79
+ })
80
+ .filter((token) => token && token !== "claude")
81
+ .filter((token) => /[a-z]/.test(token) || token.endsWith("m"));
82
+ return { tokens, contextHint };
83
+ }
84
+ function scoreModelMatch(model, tokens, contextHint) {
85
+ const haystack = `${model.value} ${model.displayName}`.toLowerCase();
86
+ let score = 0;
87
+ let nonHintMatched = false;
88
+ for (const token of tokens) {
89
+ if (!haystack.includes(token))
90
+ continue;
91
+ if (token !== contextHint)
92
+ nonHintMatched = true;
93
+ score += token === contextHint ? 3 : 1;
94
+ }
95
+ return contextHint && !nonHintMatched ? 0 : score;
96
+ }
97
+ export function resolveModelPreference(models, preference) {
98
+ const trimmed = preference.trim();
99
+ if (!trimmed)
100
+ return null;
101
+ const lower = trimmed.toLowerCase();
102
+ const canonicalPreference = canonicalizeModelId(trimmed);
103
+ const directMatch = models.find((model) => model.value === trimmed ||
104
+ canonicalizeModelId(model.value) === canonicalPreference ||
105
+ model.displayName.toLowerCase() === lower);
106
+ if (directMatch)
107
+ return directMatch;
108
+ const matchesResolved = (model) => model.resolvedModel != null && canonicalizeModelId(model.resolvedModel) === canonicalPreference;
109
+ const resolvedMatch = models.find((model) => model.value !== "default" && matchesResolved(model)) ??
110
+ models.find(matchesResolved);
111
+ if (resolvedMatch)
112
+ return resolvedMatch;
113
+ const preferenceHint = contextHintOf(trimmed);
114
+ const includesMatch = models.find((model) => {
115
+ if (!modelVersionsCompatible(trimmed, model))
116
+ return false;
117
+ if (contextHintOf(model.value) !== preferenceHint)
118
+ return false;
119
+ const value = model.value.toLowerCase();
120
+ const display = model.displayName.toLowerCase();
121
+ return value.includes(lower) || display.includes(lower) || lower.includes(value);
122
+ });
123
+ if (includesMatch)
124
+ return includesMatch;
125
+ const { tokens, contextHint } = tokenizeModelPreference(trimmed);
126
+ if (tokens.length === 0)
127
+ return null;
128
+ let bestMatch = null;
129
+ let bestScore = 0;
130
+ for (const model of models) {
131
+ if (!modelVersionsCompatible(trimmed, model))
132
+ continue;
133
+ const score = scoreModelMatch(model, tokens, contextHint);
134
+ if (score > 0 && (!bestMatch || score > bestScore)) {
135
+ bestMatch = model;
136
+ bestScore = score;
137
+ }
138
+ }
139
+ return bestMatch;
140
+ }
141
+ export function matchResumedModel(models, liveModel) {
142
+ const live = canonicalizeModelId(liveModel);
143
+ const defaultEntry = models.find((model) => model.value === "default");
144
+ const defaultResolved = defaultEntry?.resolvedModel
145
+ ? canonicalizeModelId(defaultEntry.resolvedModel)
146
+ : undefined;
147
+ if (defaultEntry && defaultResolved === live)
148
+ return defaultEntry;
149
+ const exactMatch = models.find((model) => model.resolvedModel && canonicalizeModelId(model.resolvedModel) === live);
150
+ if (exactMatch)
151
+ return exactMatch;
152
+ if (defaultEntry &&
153
+ defaultResolved &&
154
+ stripContextHints(defaultResolved) === stripContextHints(live)) {
155
+ return defaultEntry;
156
+ }
157
+ return (resolveModelPreference(models, liveModel) ?? {
158
+ value: liveModel,
159
+ displayName: liveModel,
160
+ description: "",
161
+ });
162
+ }
163
+ export function buildModelConfigOption(models, modelInfos, useRecommendedValue) {
164
+ const defaultInfo = modelInfos.find((model) => model.value === "default");
165
+ const selectableIds = new Set(models.availableModels.map((model) => model.modelId));
166
+ const concreteInfos = modelInfos.filter((model) => model.value !== "default" && selectableIds.has(model.value));
167
+ // Recommendations describe the running default, so fuzzy family matching
168
+ // must never substitute a different generation or context window.
169
+ const defaultResolved = defaultInfo?.resolvedModel;
170
+ const recommended = defaultResolved
171
+ ? (concreteInfos.find((model) => canonicalizeModelId(model.resolvedModel ?? model.value) ===
172
+ canonicalizeModelId(defaultResolved)) ?? null)
173
+ : null;
174
+ const concreteRecommendation = useRecommendedValue && recommended !== null;
175
+ const available = concreteRecommendation
176
+ ? models.availableModels.filter((model) => model.modelId !== "default")
177
+ : models.availableModels;
178
+ return {
179
+ id: MODEL_CONFIG_ID,
180
+ name: "Model",
181
+ description: "AI model to use",
182
+ category: "model",
183
+ type: "select",
184
+ currentValue: concreteRecommendation && models.currentModelId === "default"
185
+ ? recommended.value
186
+ : models.currentModelId,
187
+ ...(concreteRecommendation
188
+ ? {
189
+ _meta: withAirMeta(undefined, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, recommended.value),
190
+ }
191
+ : {}),
192
+ options: available.map((model) => {
193
+ if (model.modelId === "default" && defaultInfo?.resolvedModel) {
194
+ const named = modelInfos.find((info) => info.value !== "default" && info.resolvedModel === defaultInfo.resolvedModel);
195
+ return {
196
+ value: model.modelId,
197
+ name: model.name,
198
+ description: named?.displayName ?? defaultInfo.resolvedModel,
199
+ };
200
+ }
201
+ return {
202
+ value: model.modelId,
203
+ name: model.name,
204
+ description: model.description ?? undefined,
205
+ };
206
+ }),
207
+ };
208
+ }
209
+ function resolveSettingsModel(models, settingsModel, logger) {
210
+ if (settingsModel === undefined) {
211
+ return null;
212
+ }
213
+ if (typeof settingsModel !== "string") {
214
+ const typeLabel = settingsModel === null ? "null" : typeof settingsModel;
215
+ logger.error(`Ignoring model from settings: expected a string, got ${typeLabel}.`);
216
+ return null;
217
+ }
218
+ return resolveModelPreference(models, settingsModel);
219
+ }
220
+ /**
221
+ * Restrict the SDK's model list to the user's `availableModels` allowlist
222
+ * (already merged-and-deduped across settings sources by `SettingsManager`).
223
+ * The user's exact entries become the model IDs surfaced via configOptions
224
+ * and passed to `setModel`, which prevents Claude Code from silently
225
+ * substituting a date-pinned variant (e.g. `haiku` →
226
+ * `claude-haiku-4-5-20251001`) that the user may not have access to.
227
+ *
228
+ * Display info and capability flags are copied from the closest SDK match so
229
+ * the UI still renders sensible names and effort levels.
230
+ *
231
+ * Semantics from https://code.claude.com/docs/en/model-config#restrict-model-selection:
232
+ * - `undefined` is handled by the caller (no allowlist applied).
233
+ * - The Default option is unaffected by `availableModels` — it always remains
234
+ * available, even when the allowlist is `[]`.
235
+ */
236
+ export function applyAvailableModelsAllowlist(sdkModels, allowlist, settingsModelOverrides) {
237
+ // Default is always preserved per the docs. Synthesize one if the SDK
238
+ // didn't surface it so downstream code (e.g. `getAvailableModels` picking
239
+ // `models[0]` as a fallback) still has something to work with.
240
+ const defaultModel = sdkModels.find((m) => m.value === "default") ?? {
241
+ value: "default",
242
+ displayName: "Default",
243
+ description: "",
244
+ };
245
+ const result = [defaultModel];
246
+ const seen = new Set([defaultModel.value]);
247
+ const sdkModelsWithoutDefault = sdkModels.filter((m) => m.value !== "default");
248
+ // Bedrock/Vertex deployments enforce short aliases (e.g. "claude-opus-4-6")
249
+ // in availableModels but require provider-specific IDs at the API. We still
250
+ // resolve `sdkMatch` against the alias (`trimmed`) — that's what the
251
+ // matching heuristics above are built for, and override targets (ARNs,
252
+ // opaque provider IDs) often won't textually resemble anything in
253
+ // `sdkModelsWithoutDefault`. Only the entry's surfaced `value` becomes the
254
+ // override target, so it's what `setModel` ends up passing to the API.
255
+ for (const entry of allowlist) {
256
+ const trimmed = entry.trim();
257
+ if (!trimmed || seen.has(trimmed))
258
+ continue;
259
+ const overridden = settingsModelOverrides?.[trimmed];
260
+ const effective = overridden ?? trimmed;
261
+ if (seen.has(effective))
262
+ continue;
263
+ const sdkMatch = resolveModelPreference(sdkModelsWithoutDefault, trimmed);
264
+ if (sdkMatch) {
265
+ result.push({ ...sdkMatch, value: effective });
266
+ }
267
+ else {
268
+ result.push({ value: effective, displayName: trimmed, description: "" });
269
+ }
270
+ seen.add(effective);
271
+ }
272
+ // The custom model option (ANTHROPIC_CUSTOM_MODEL_OPTION) is exempt from the
273
+ // allowlist, the same way Default is. Per the model-config docs it adds an
274
+ // entry "without replacing the built-in aliases" and "appears at the bottom of
275
+ // the /model picker", so we append it last and skip the allowlist filter; this
276
+ // keeps a slim alias allowlist from hiding the custom model row.
277
+ // https://code.claude.com/docs/en/model-config#add-a-custom-model-option
278
+ const customModelOption = process.env.ANTHROPIC_CUSTOM_MODEL_OPTION?.trim();
279
+ if (customModelOption && !seen.has(customModelOption)) {
280
+ const customModel = sdkModels.find((m) => m.value === customModelOption);
281
+ if (customModel) {
282
+ result.push(customModel);
283
+ seen.add(customModel.value);
284
+ }
285
+ }
286
+ return result;
287
+ }
288
+ /** Whether a rejected `setModel` was vetoed by a user-configured
289
+ * PreModelSwitch hook. The CLI's rejection message is the stable,
290
+ * distinguishable marker ("Model switch blocked by a PreModelSwitch hook:
291
+ * <reason>"); a format change makes this stop matching and the failure
292
+ * falls back to the ordinary fail-loud path, no worse than before. */
293
+ function isPreModelSwitchHookBlock(error) {
294
+ return error instanceof Error && error.message.includes("blocked by a PreModelSwitch hook");
295
+ }
296
+ export async function getAvailableModels(query, models, sdkModels, settingsManager, logger, isResumedSession, sessionId, resumedModelHint) {
297
+ const settings = settingsManager.getSettings();
298
+ let currentModel = models[0];
299
+ let resolvedFromInput;
300
+ // Model priority (highest to lowest):
301
+ // 1. ANTHROPIC_MODEL environment variable
302
+ // 2. settings.model (user configuration)
303
+ // 3. the resumed session's live model (resumed sessions only)
304
+ // 4. models[0] (default first model)
305
+ if (process.env.ANTHROPIC_MODEL) {
306
+ const match = resolveModelPreference(models, process.env.ANTHROPIC_MODEL);
307
+ if (match) {
308
+ currentModel = match;
309
+ resolvedFromInput = process.env.ANTHROPIC_MODEL;
310
+ }
311
+ }
312
+ else if (typeof settings.model === "string") {
313
+ const match = resolveSettingsModel(models, settings.model, logger);
314
+ if (match) {
315
+ currentModel = match;
316
+ resolvedFromInput = settings.model;
317
+ }
318
+ }
319
+ // A resumed session restores the model from its last real assistant
320
+ // transcript record. Use that same local record instead of getContextUsage:
321
+ // the latter is a control request to the live CLI process and can add tens
322
+ // of seconds to session/load before its first turn. No `setModel` here: the
323
+ // SDK is already running this model, and pushing a picker alias back (e.g.
324
+ // "opus[1m]") could change the live model rather than describe it.
325
+ if (resolvedFromInput === undefined && isResumedSession) {
326
+ currentModel = resumedModelHint ? matchResumedModel(models, resumedModelHint) : currentModel;
327
+ }
328
+ // Skip the setModel round-trip when we can prove the SDK has already landed
329
+ // on the same model. Two cases qualify:
330
+ // (a) No override applied — currentModel is the SDK's own default (or, on
331
+ // resume, the live model read back from the SDK above); nothing to sync.
332
+ // (b) The resolver returned the user's input verbatim AND that value exists
333
+ // in the SDK's original model list — meaning no fuzzy match or
334
+ // allowlist rewrite was involved, and the SDK (which reads the same
335
+ // ANTHROPIC_MODEL / settings.json) will have arrived at the same entry.
336
+ // This only holds for fresh sessions: a resumed session lands on the
337
+ // transcript's model regardless of env/settings, so the override must
338
+ // be re-asserted to keep the reported model truthful.
339
+ // Anything else (fuzzy match, allowlist-synthesized value, alias) gets a
340
+ // setModel call so we don't drift from the user's intended pin.
341
+ const sdkSawSameValue = sdkModels.some((m) => m.value === currentModel.value);
342
+ const skipSetModel = resolvedFromInput === undefined ||
343
+ (!isResumedSession && currentModel.value === resolvedFromInput && sdkSawSameValue);
344
+ if (!skipSetModel) {
345
+ const setModelStartedAt = performance.now();
346
+ try {
347
+ await query.setModel(currentModel.value);
348
+ logger.log(`[session/models] sessionId=${sessionId} phase=set-model durationMs=${Math.round(performance.now() - setModelStartedAt)} model=${currentModel.value} outcome=success`);
349
+ }
350
+ catch (error) {
351
+ logger.log(`[session/models] sessionId=${sessionId} phase=set-model durationMs=${Math.round(performance.now() - setModelStartedAt)} model=${currentModel.value} outcome=error`);
352
+ // On a fresh session the pin is a defining option — fail loudly. A
353
+ // resumed session already runs fine on the transcript's model, so
354
+ // failing the whole session/load over the re-assert would be worse
355
+ // than loading with the pin unapplied (mirrors the setPermissionMode
356
+ // containment in createSession). The SDK then stayed on the
357
+ // transcript's model, so read that back rather than reporting the
358
+ // pin the session isn't running.
359
+ //
360
+ // One fresh-session failure is advisory, not defining: a
361
+ // user-configured PreModelSwitch hook can veto the pin (CLI 2.1.251+;
362
+ // 'deny', or 'ask' — which headless sessions refuse), and the SDK
363
+ // rejects setModel with "Model switch blocked by a PreModelSwitch
364
+ // hook: …". Terminal Claude Code never lets a hook veto its startup
365
+ // model (the spawn model isn't a switch), so failing session/new here
366
+ // would make the same hook config fatal only over ACP. The session
367
+ // stays on the SDK's own default — report that. We can't read the
368
+ // live model back on this path: getContextUsage isn't serviced on a
369
+ // fresh session until the first prompt turn (issues #886/#880).
370
+ if (!isResumedSession) {
371
+ if (!isPreModelSwitchHookBlock(error))
372
+ throw error;
373
+ logger.error(`Model pin "${currentModel.value}" was vetoed by a PreModelSwitch hook; staying on the default model:`, error);
374
+ currentModel = models[0];
375
+ }
376
+ else {
377
+ logger.error(`Failed to re-assert model "${currentModel.value}" on resume:`, error);
378
+ currentModel = resumedModelHint ? matchResumedModel(models, resumedModelHint) : models[0];
379
+ }
380
+ }
381
+ }
382
+ const displayNames = models.map(versionedModelDisplayName);
383
+ const displayNameCounts = new Map();
384
+ for (const name of displayNames) {
385
+ displayNameCounts.set(name, (displayNameCounts.get(name) ?? 0) + 1);
386
+ }
387
+ return {
388
+ availableModels: models.map((model, index) => ({
389
+ modelId: model.value,
390
+ name: displayNameCounts.get(displayNames[index]) === 1 ? displayNames[index] : model.displayName,
391
+ description: model.description,
392
+ })),
393
+ currentModelId: currentModel.value,
394
+ };
395
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.75.2-preview.1",
6
+ "version": "0.75.2-preview.2",
7
7
  "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",