@oh-my-pi/pi-coding-agent 16.0.9 → 16.0.10

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 (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cli.js +2822 -2872
  3. package/dist/types/collab/host.d.ts +2 -2
  4. package/dist/types/collab/protocol.d.ts +4 -5
  5. package/dist/types/config/model-resolver.d.ts +11 -2
  6. package/dist/types/config/settings-schema.d.ts +12 -2
  7. package/dist/types/session/agent-session.d.ts +13 -0
  8. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  9. package/dist/types/slash-commands/helpers/collab-qrcode.d.ts +13 -0
  10. package/dist/types/tools/index.d.ts +9 -1
  11. package/dist/types/utils/image-loading.d.ts +12 -0
  12. package/dist/types/utils/qrcode.d.ts +48 -0
  13. package/package.json +12 -12
  14. package/src/cli/args.ts +7 -1
  15. package/src/collab/host.ts +4 -4
  16. package/src/collab/protocol.ts +48 -15
  17. package/src/config/config-file.ts +1 -1
  18. package/src/config/keybindings.ts +2 -2
  19. package/src/config/model-registry.ts +16 -4
  20. package/src/config/model-resolver.ts +193 -35
  21. package/src/config/settings-schema.ts +14 -2
  22. package/src/config/settings.ts +3 -3
  23. package/src/internal-urls/docs-index.generated.txt +1 -1
  24. package/src/main.ts +2 -2
  25. package/src/modes/components/oauth-selector.ts +31 -2
  26. package/src/prompts/tools/inspect-image.md +1 -1
  27. package/src/sdk.ts +26 -7
  28. package/src/session/agent-session.ts +93 -14
  29. package/src/slash-commands/builtin-registry.ts +29 -11
  30. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  31. package/src/thinking.ts +25 -5
  32. package/src/tools/index.ts +10 -1
  33. package/src/tools/inspect-image.ts +72 -9
  34. package/src/utils/file-mentions.ts +5 -2
  35. package/src/utils/image-loading.ts +58 -0
  36. package/src/utils/qrcode.ts +535 -0
@@ -72,6 +72,21 @@ export interface ScopedModel {
72
72
  explicitThinkingLevel: boolean;
73
73
  }
74
74
 
75
+ interface ThinkingSuffixOptions {
76
+ allowMaxAlias?: boolean;
77
+ }
78
+
79
+ interface ModelStringParseOptions extends ThinkingSuffixOptions {
80
+ isLiteralModelId?: (provider: string, id: string) => boolean;
81
+ }
82
+ const MAX_THINKING_SUFFIX_OPTIONS: ThinkingSuffixOptions = { allowMaxAlias: true };
83
+
84
+ function parseThinkingSuffix(value: string, options?: ThinkingSuffixOptions): ThinkingLevel | undefined {
85
+ const level = parseThinkingLevel(value);
86
+ if (level !== undefined) return level;
87
+ return options?.allowMaxAlias === true && value === "max" ? ThinkingLevel.XHigh : undefined;
88
+ }
89
+
75
90
  /**
76
91
  * Split a trailing `:<level>` thinking selector off a model pattern.
77
92
  *
@@ -81,27 +96,89 @@ export interface ScopedModel {
81
96
  * role-alias callers pass `PREFIX_MODEL_ROLE.length` so the base is at least
82
97
  * as long as the `pi/` prefix.
83
98
  */
84
- function splitThinkingSuffix(pattern: string, minColonIndex = -1): { base: string; level?: ThinkingLevel } {
99
+ function splitThinkingSuffix(
100
+ pattern: string,
101
+ minColonIndex = -1,
102
+ options?: ThinkingSuffixOptions,
103
+ ): { base: string; level?: ThinkingLevel } {
85
104
  const colonIdx = pattern.lastIndexOf(":");
86
105
  if (colonIdx <= minColonIndex) return { base: pattern };
87
- const level = parseThinkingLevel(pattern.slice(colonIdx + 1));
106
+ const level = parseThinkingSuffix(pattern.slice(colonIdx + 1), options);
88
107
  return level ? { base: pattern.slice(0, colonIdx), level } : { base: pattern };
89
108
  }
90
109
 
110
+ function hasExactModelPattern(pattern: string, availableModels: readonly Model<Api>[]): boolean {
111
+ const normalized = pattern.toLowerCase();
112
+ return availableModels.some(
113
+ model => model.id.toLowerCase() === normalized || `${model.provider}/${model.id}`.toLowerCase() === normalized,
114
+ );
115
+ }
116
+
117
+ function matchingGlobModels(pattern: string, availableModels: readonly Model<Api>[]): Model<Api>[] {
118
+ const glob = new Bun.Glob(pattern.toLowerCase());
119
+ return availableModels.filter(model => {
120
+ const fullId = `${model.provider}/${model.id}`;
121
+ return glob.match(fullId.toLowerCase()) || glob.match(model.id.toLowerCase());
122
+ });
123
+ }
124
+
125
+ function resolveGlobScopePattern(
126
+ pattern: string,
127
+ availableModels: readonly Model<Api>[],
128
+ ): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
129
+ const strictSuffix = splitThinkingSuffix(pattern);
130
+ if (strictSuffix.level !== undefined) {
131
+ return {
132
+ models: matchingGlobModels(strictSuffix.base, availableModels),
133
+ thinkingLevel: strictSuffix.level,
134
+ explicitThinkingLevel: true,
135
+ };
136
+ }
137
+
138
+ const maxSuffix = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
139
+ if (maxSuffix.level !== undefined) {
140
+ const literalMatches = matchingGlobModels(pattern, availableModels);
141
+ if (literalMatches.length > 0) {
142
+ return { models: literalMatches, thinkingLevel: undefined, explicitThinkingLevel: false };
143
+ }
144
+ return {
145
+ models: matchingGlobModels(maxSuffix.base, availableModels),
146
+ thinkingLevel: maxSuffix.level,
147
+ explicitThinkingLevel: true,
148
+ };
149
+ }
150
+
151
+ return {
152
+ models: matchingGlobModels(pattern, availableModels),
153
+ thinkingLevel: undefined,
154
+ explicitThinkingLevel: false,
155
+ };
156
+ }
157
+
91
158
  /**
92
159
  * Parse a model string in "provider/modelId" format.
93
160
  * Returns undefined if the format is invalid.
94
161
  */
95
162
  export function parseModelString(
96
163
  modelStr: string,
164
+ options?: ModelStringParseOptions,
97
165
  ): { provider: string; id: string; thinkingLevel?: ThinkingLevel } | undefined {
98
166
  const slashIdx = modelStr.indexOf("/");
99
167
  if (slashIdx <= 0) return undefined;
100
168
  const id = modelStr.slice(slashIdx + 1);
101
169
  const provider = modelStr.slice(0, slashIdx);
102
- // Strip valid thinking level suffix (e.g., "claude-sonnet-4-6:high" -> id "claude-sonnet-4-6", thinkingLevel "high")
103
- const { base, level } = splitThinkingSuffix(id);
104
- return level ? { provider, id: base, thinkingLevel: level } : { provider, id };
170
+ // Strip strict thinking level suffixes first (e.g. "claude-sonnet-4-6:high" -> id "claude-sonnet-4-6", thinkingLevel "high").
171
+ const strict = splitThinkingSuffix(id);
172
+ if (strict.level) return { provider, id: strict.base, thinkingLevel: strict.level };
173
+ // `max` is a provider-facing alias for xhigh, but real model IDs can end in
174
+ // `:max`. Context-aware callers pass a literal lookup so those models win.
175
+ const maxAlias = splitThinkingSuffix(id, -1, options);
176
+ if (maxAlias.level) {
177
+ return options?.isLiteralModelId?.(provider, id) === true
178
+ ? { provider, id }
179
+ : { provider, id: maxAlias.base, thinkingLevel: maxAlias.level };
180
+ }
181
+ return { provider, id };
105
182
  }
106
183
 
107
184
  /**
@@ -149,7 +226,10 @@ function getOpenRouterRouteSuffix(modelId: string): { baseId: string; suffix: st
149
226
  }
150
227
 
151
228
  const suffix = modelId.slice(colonIdx + 1).trim();
152
- if (!suffix || parseThinkingLevel(suffix)) {
229
+ // `max` is a thinking-level alias (xhigh), never an OpenRouter route suffix, so
230
+ // `openrouter/<id>:max` falls through to the max-aware selector split instead of
231
+ // being cloned into a literal `<id>:max` model id with the reasoning level lost.
232
+ if (!suffix || parseThinkingSuffix(suffix, MAX_THINKING_SUFFIX_OPTIONS)) {
153
233
  return undefined;
154
234
  }
155
235
 
@@ -196,6 +276,50 @@ function cloneModelWithRequestedId(model: Model<Api>, requestedId: string): Mode
196
276
  };
197
277
  }
198
278
 
279
+ const AMAZON_BEDROCK_PROVIDER = "amazon-bedrock";
280
+ const BEDROCK_INFERENCE_PROFILE_ARN =
281
+ /^arn:aws(?:-[a-z]+)*:bedrock:[a-z0-9-]+:[0-9]*:(?:application-inference-profile|inference-profile)\/[a-z0-9][a-z0-9._:-]*$/i;
282
+
283
+ function hasBedrockInferenceProfileThinkingSuffix(modelId: string): boolean {
284
+ const { base, level } = splitThinkingSuffix(modelId);
285
+ return level !== undefined && BEDROCK_INFERENCE_PROFILE_ARN.test(base.trim());
286
+ }
287
+
288
+ function resolveBedrockInferenceProfileModelId(
289
+ modelId: string,
290
+ availableModels: readonly Model<Api>[],
291
+ ): Model<Api> | undefined {
292
+ const requestedId = modelId.trim();
293
+ if (hasBedrockInferenceProfileThinkingSuffix(requestedId) || !BEDROCK_INFERENCE_PROFILE_ARN.test(requestedId)) {
294
+ return undefined;
295
+ }
296
+
297
+ const template = availableModels.find(model => model.provider.toLowerCase() === AMAZON_BEDROCK_PROVIDER);
298
+ if (!template) return undefined;
299
+
300
+ return buildModel({
301
+ id: requestedId,
302
+ name: "Bedrock inference profile",
303
+ api: "bedrock-converse-stream",
304
+ provider: AMAZON_BEDROCK_PROVIDER,
305
+ baseUrl: template.baseUrl,
306
+ reasoning: false,
307
+ input: ["text"],
308
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
309
+ contextWindow: null,
310
+ maxTokens: null,
311
+ });
312
+ }
313
+
314
+ function resolveBedrockInferenceProfileReference(
315
+ provider: string,
316
+ modelId: string,
317
+ availableModels: readonly Model<Api>[],
318
+ ): Model<Api> | undefined {
319
+ if (provider.toLowerCase() !== AMAZON_BEDROCK_PROVIDER) return undefined;
320
+ return resolveBedrockInferenceProfileModelId(modelId, availableModels);
321
+ }
322
+
199
323
  const UPSTREAM_ROUTING_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
200
324
 
201
325
  /**
@@ -289,6 +413,11 @@ export function resolveProviderModelReference(
289
413
  }
290
414
  }
291
415
 
416
+ const bedrockInferenceProfile = resolveBedrockInferenceProfileReference(provider, modelId, availableModels);
417
+ if (bedrockInferenceProfile) {
418
+ return bedrockInferenceProfile;
419
+ }
420
+
292
421
  if (normalizedProvider !== "openrouter") {
293
422
  return undefined;
294
423
  }
@@ -470,6 +599,7 @@ function findExactCanonicalModelMatch(
470
599
  * The single model-matching engine. Tries, in order:
471
600
  * 1. exact `provider/id` reference (variant-alias and OpenRouter routed/date
472
601
  * fallbacks included),
602
+
473
603
  * 2. exact canonical id (coalesces provider variants),
474
604
  * 3. exact bare id (preference-ranked),
475
605
  * 4. retired effort-tier variant alias (collapsed catalog entries),
@@ -502,6 +632,11 @@ function matchModel(
502
632
  return pickPreferredModel(exactMatches, context);
503
633
  }
504
634
 
635
+ const bedrockInferenceProfile = resolveBedrockInferenceProfileModelId(modelPattern, availableModels);
636
+ if (bedrockInferenceProfile) {
637
+ return bedrockInferenceProfile;
638
+ }
639
+
505
640
  // Retired effort-tier variant ids (bare, no provider prefix) resolve to
506
641
  // their collapsed logical model; models from the providers whose table
507
642
  // declared the alias win ties. Auto-derived `X-thinking` pairs resolve
@@ -625,8 +760,10 @@ function parseModelPatternWithContext(
625
760
  return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
626
761
  }
627
762
 
628
- // No match - try stripping a valid thinking suffix and recursing
629
- const { base, level } = splitThinkingSuffix(pattern);
763
+ // No match - try stripping a valid thinking suffix and recursing.
764
+ // `max` is accepted only after the full pattern failed, so literal model IDs
765
+ // ending in `:max` keep winning over the alias.
766
+ const { base, level } = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
630
767
  if (level) {
631
768
  const result = parseModelPatternWithContext(base, availableModels, context, options);
632
769
  if (result.model) {
@@ -730,7 +867,11 @@ function resolveDefaultInheritedPatterns(
730
867
 
731
868
  const resolved: string[] = [];
732
869
  for (const pattern of normalizeModelPatternList(configuredDefault)) {
733
- const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(pattern, PREFIX_MODEL_ROLE.length);
870
+ const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
871
+ pattern,
872
+ PREFIX_MODEL_ROLE.length,
873
+ MAX_THINKING_SUFFIX_OPTIONS,
874
+ );
734
875
  const aliasRole = getModelRoleAlias(aliasCandidate);
735
876
  if (aliasRole === role) {
736
877
  // Self-alias (e.g. modelRoles.default = "pi/smol") would loop back to the
@@ -765,7 +906,11 @@ function resolveConfiguredRolePattern(
765
906
  const normalized = value.trim();
766
907
  if (!normalized) return undefined;
767
908
 
768
- const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(normalized, PREFIX_MODEL_ROLE.length);
909
+ const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
910
+ normalized,
911
+ PREFIX_MODEL_ROLE.length,
912
+ MAX_THINKING_SUFFIX_OPTIONS,
913
+ );
769
914
  const role = getModelRoleAlias(aliasCandidate);
770
915
  if (!role) return [normalized];
771
916
  if (visited.has(role)) return undefined;
@@ -888,9 +1033,19 @@ export function resolveModelRoleValue(
888
1033
  return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning };
889
1034
  }
890
1035
 
1036
+ interface ExplicitThinkingSelectorOptions {
1037
+ isLiteralModelId?: (provider: string, id: string) => boolean;
1038
+ }
1039
+
1040
+ function isLiteralModelSelector(value: string, options?: ExplicitThinkingSelectorOptions): boolean {
1041
+ const parsed = parseModelString(value);
1042
+ return parsed !== undefined && options?.isLiteralModelId?.(parsed.provider, parsed.id) === true;
1043
+ }
1044
+
891
1045
  export function extractExplicitThinkingSelector(
892
1046
  value: string | undefined,
893
1047
  settings?: Settings,
1048
+ options?: ExplicitThinkingSelectorOptions,
894
1049
  ): ThinkingLevel | undefined {
895
1050
  if (!value) return undefined;
896
1051
  const normalized = value.trim();
@@ -900,9 +1055,13 @@ export function extractExplicitThinkingSelector(
900
1055
  let current = normalized;
901
1056
  while (!visited.has(current)) {
902
1057
  visited.add(current);
903
- const thinkingSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length).level;
904
- if (thinkingSelector) {
905
- return thinkingSelector;
1058
+ const strictSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length).level;
1059
+ if (strictSelector) {
1060
+ return strictSelector;
1061
+ }
1062
+ const maxSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length, MAX_THINKING_SUFFIX_OPTIONS).level;
1063
+ if (maxSelector && (current.startsWith(PREFIX_MODEL_ROLE) || !isLiteralModelSelector(current, options))) {
1064
+ return maxSelector;
906
1065
  }
907
1066
  const expanded = expandRoleAlias(current, settings).trim();
908
1067
  if (!expanded || expanded === current) break;
@@ -922,10 +1081,15 @@ export function resolveModelFromString(
922
1081
  matchPreferences?: ModelMatchPreferences,
923
1082
  modelRegistry?: CanonicalModelRegistry,
924
1083
  ): Model<Api> | undefined {
925
- const parsed = parseModelString(value);
1084
+ const exact = available.find(model => `${model.provider}/${model.id}` === value);
1085
+ if (exact) return exact;
1086
+ const parsed = parseModelString(value, {
1087
+ ...MAX_THINKING_SUFFIX_OPTIONS,
1088
+ isLiteralModelId: (provider, id) => available.some(model => model.provider === provider && model.id === id),
1089
+ });
926
1090
  if (parsed) {
927
- const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
928
- if (exact) return exact;
1091
+ const parsedExact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
1092
+ if (parsedExact) return parsedExact;
929
1093
  }
930
1094
  return parseModelPattern(value, available, matchPreferences, { modelRegistry }).model;
931
1095
  }
@@ -1065,7 +1229,10 @@ function resolveExactCanonicalScopePattern(
1065
1229
  modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
1066
1230
  availableModels: Model<Api>[],
1067
1231
  ): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } | undefined {
1068
- const { base: canonicalId, level: thinkingLevel } = splitThinkingSuffix(pattern);
1232
+ if (pattern.endsWith(":max") && hasExactModelPattern(pattern, availableModels)) {
1233
+ return undefined;
1234
+ }
1235
+ const { base: canonicalId, level: thinkingLevel } = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
1069
1236
  const explicitThinkingLevel = thinkingLevel !== undefined;
1070
1237
 
1071
1238
  const variants = modelRegistry
@@ -1111,17 +1278,13 @@ export async function resolveModelScope(
1111
1278
  for (const pattern of patterns) {
1112
1279
  // Check if pattern contains glob characters
1113
1280
  if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
1114
- // Extract optional thinking level suffix (e.g., "provider/*:high")
1115
- const { base: globPattern, level: thinkingLevel } = splitThinkingSuffix(pattern);
1116
- const explicitThinkingLevel = thinkingLevel !== undefined;
1117
-
1118
- // Match against "provider/modelId" format OR just model ID
1119
- // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
1120
- const matchingModels = availableModels.filter(m => {
1121
- const fullId = `${m.provider}/${m.id}`;
1122
- const glob = new Bun.Glob(globPattern.toLowerCase());
1123
- return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
1124
- });
1281
+ // Extract optional thinking level suffix (e.g., "provider/*:high") only
1282
+ // after literal `:max` globs had a chance to match real model IDs.
1283
+ const {
1284
+ models: matchingModels,
1285
+ thinkingLevel,
1286
+ explicitThinkingLevel,
1287
+ } = resolveGlobScopePattern(pattern, availableModels);
1125
1288
 
1126
1289
  if (matchingModels.length === 0) {
1127
1290
  logger.warn(`No models match pattern "${pattern}"`);
@@ -1226,13 +1389,8 @@ export function filterAvailableModelsByEnabledPatterns(
1226
1389
 
1227
1390
  for (const pattern of patterns) {
1228
1391
  if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
1229
- const { base: globPattern } = splitThinkingSuffix(pattern);
1230
- const glob = new Bun.Glob(globPattern.toLowerCase());
1231
- for (const model of available) {
1232
- const fullId = `${model.provider}/${model.id}`.toLowerCase();
1233
- if (glob.match(fullId) || glob.match(model.id.toLowerCase())) {
1234
- addAllowed(model);
1235
- }
1392
+ for (const model of resolveGlobScopePattern(pattern, available).models) {
1393
+ addAllowed(model);
1236
1394
  }
1237
1395
  continue;
1238
1396
  }
@@ -872,7 +872,7 @@ export const SETTINGS_SCHEMA = {
872
872
  // Reasoning and prompts
873
873
  defaultThinkingLevel: {
874
874
  type: "enum",
875
- values: [...THINKING_EFFORTS, AUTO_THINKING],
875
+ values: [...THINKING_EFFORTS, AUTO_THINKING, "max"],
876
876
  default: "high",
877
877
  ui: {
878
878
  tab: "model",
@@ -1519,6 +1519,18 @@ export const SETTINGS_SCHEMA = {
1519
1519
  },
1520
1520
  },
1521
1521
 
1522
+ "collab.webUrl": {
1523
+ type: "string",
1524
+ default: "",
1525
+ ui: {
1526
+ tab: "interaction",
1527
+ group: "Collab",
1528
+ label: "Web UI URL",
1529
+ description:
1530
+ "Browser UI used by /collab links; empty derives from collab.relayUrl; explicit http:// is localhost-only",
1531
+ },
1532
+ },
1533
+
1522
1534
  "collab.displayName": {
1523
1535
  type: "string",
1524
1536
  default: "",
@@ -1878,7 +1890,7 @@ export const SETTINGS_SCHEMA = {
1878
1890
  { value: "anthropic", label: "Anthropic", description: "Use Anthropic-style in-band tool calls." },
1879
1891
  { value: "deepseek", label: "DeepSeek", description: "Use DeepSeek-style in-band tool calls." },
1880
1892
  { value: "harmony", label: "Harmony", description: "Use Harmony-style in-band tool calls." },
1881
- { value: "pi", label: "Pi", description: "Use the Pi owned dialect." },
1893
+ { value: "pi", label: "Pi", description: "Use the Pi owned dialect (compact sigil-delimited tool calls)." },
1882
1894
  { value: "qwen3", label: "Qwen3", description: "Use the Qwen3 owned dialect." },
1883
1895
  { value: "gemini", label: "Gemini", description: "Use the Gemini owned dialect." },
1884
1896
  { value: "gemma", label: "Gemma", description: "Use the Gemma owned dialect." },
@@ -24,7 +24,7 @@ import {
24
24
  procmgr,
25
25
  setDefaultTabWidth,
26
26
  } from "@oh-my-pi/pi-utils";
27
- import { YAML } from "bun";
27
+ import { JSONC, YAML } from "bun";
28
28
  import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
29
29
  import type { ModelRole } from "../config/model-roles";
30
30
  import { loadCapability } from "../discovery";
@@ -668,9 +668,9 @@ export class Settings {
668
668
  // 1. Migrate from settings.json
669
669
  const settingsJsonPath = path.join(this.#agentDir, "settings.json");
670
670
  try {
671
- const parsed = JSON.parse(await Bun.file(settingsJsonPath).text());
671
+ const parsed: unknown = JSONC.parse(await Bun.file(settingsJsonPath).text());
672
672
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
673
- settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed));
673
+ settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed as RawSettings));
674
674
  migrated = true;
675
675
  try {
676
676
  fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);