atlarix 15.8.2 → 15.9.1

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.
@@ -9587,6 +9587,10 @@ var init_schema = __esm({
9587
9587
  name: text("name").notNull(),
9588
9588
  api_base_url: text("api_base_url").notNull(),
9589
9589
  env_var_name: text("env_var_name"),
9590
+ /** Wire format: null / "openai" = OpenAI-compatible, "anthropic" = Messages API. */
9591
+ api_format: text("api_format"),
9592
+ /** How the key is sent: null = the format's usual header, or "x-api-key" / "bearer". */
9593
+ auth_header: text("auth_header"),
9590
9594
  createdAt: integer("created_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`),
9591
9595
  updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`)
9592
9596
  }
@@ -10911,6 +10915,14 @@ var init_usage_stats = __esm({
10911
10915
  }
10912
10916
  });
10913
10917
 
10918
+ // raw-file:/home/runner/work/Atlarix/Atlarix/drizzle/0080_custom_provider_api_format.sql
10919
+ var custom_provider_api_format_default;
10920
+ var init_custom_provider_api_format = __esm({
10921
+ "raw-file:/home/runner/work/Atlarix/Atlarix/drizzle/0080_custom_provider_api_format.sql"() {
10922
+ custom_provider_api_format_default = "-- The wire format a provider the user added speaks, and how it takes the key.\n--\n-- A custom provider could only be OpenAI-compatible. Many endpoints serve the\n-- Anthropic Messages API instead (Bedrock's /anthropic, gateways, proxies), and\n-- those take the key either as `x-api-key` or as `Authorization: Bearer`.\n--\n-- Additive only (two nullable columns, null = OpenAI-compatible with its usual\n-- Bearer key): an older reader ignores them, so min_reader stays where it is.\nALTER TABLE `language_model_providers` ADD `api_format` text;--> statement-breakpoint\nALTER TABLE `language_model_providers` ADD `auth_header` text;\n";
10923
+ }
10924
+ });
10925
+
10914
10926
  // src/db/migrations/bundled.generated.ts
10915
10927
  var BUNDLED_MIGRATIONS, DRIZZLE_HASH_TO_NAME;
10916
10928
  var init_bundled_generated = __esm({
@@ -10995,6 +11007,7 @@ var init_bundled_generated = __esm({
10995
11007
  init_automation_leases();
10996
11008
  init_mcp_headers();
10997
11009
  init_usage_stats();
11010
+ init_custom_provider_api_format();
10998
11011
  BUNDLED_MIGRATIONS = [
10999
11012
  { name: "0000_nebulous_proemial_gods", sql: nebulous_proemial_gods_default, drizzleHash: "2fd0348e72ac1429b9246311e8db17a34078771470c622a13ab09afdee0a0d31", when: 1744692127560 },
11000
11013
  { name: "0001_hesitant_roland_deschain", sql: hesitant_roland_deschain_default, drizzleHash: "b9b37284db688cf3aaaa2719dda788238fd216933d3756c0482f26b5343d0059", when: 1744999922420 },
@@ -11075,7 +11088,8 @@ var init_bundled_generated = __esm({
11075
11088
  { name: "0076_drop_installed_skills", sql: drop_installed_skills_default, drizzleHash: "b361a2b664ce74a3efeaa2f73cdf6de582509c6569a1aab54febbef507b98b3c", when: 17877024e5 },
11076
11089
  { name: "0077_automation_leases", sql: automation_leases_default, drizzleHash: "22f09427b49eb0fcdd2b62cd472735a88e5dd60974af0d0c008f6e6fddfdf545", when: 17877888e5 },
11077
11090
  { name: "0078_mcp_headers", sql: mcp_headers_default, drizzleHash: "2e2b040eac31e336e8e7662555fd8526e8cdfd59929671e9ca6e063a3f2d4004", when: 17878752e5 },
11078
- { name: "0079_usage_stats", sql: usage_stats_default, drizzleHash: "8ea9653e8339a4f4c4f0fb140766daace073443ed9e6efae3d4597b2fc6bd60d", when: 17879616e5 }
11091
+ { name: "0079_usage_stats", sql: usage_stats_default, drizzleHash: "8ea9653e8339a4f4c4f0fb140766daace073443ed9e6efae3d4597b2fc6bd60d", when: 17879616e5 },
11092
+ { name: "0080_custom_provider_api_format", sql: custom_provider_api_format_default, drizzleHash: "0c66aed1a57dea6a1e9925a9a74dc1ca4f181db3de0c8e446a860a9f3ef7b950", when: 1788048e6 }
11079
11093
  ];
11080
11094
  DRIZZLE_HASH_TO_NAME = {
11081
11095
  "2fd0348e72ac1429b9246311e8db17a34078771470c622a13ab09afdee0a0d31": "0000_nebulous_proemial_gods",
@@ -11157,7 +11171,8 @@ var init_bundled_generated = __esm({
11157
11171
  "b361a2b664ce74a3efeaa2f73cdf6de582509c6569a1aab54febbef507b98b3c": "0076_drop_installed_skills",
11158
11172
  "22f09427b49eb0fcdd2b62cd472735a88e5dd60974af0d0c008f6e6fddfdf545": "0077_automation_leases",
11159
11173
  "2e2b040eac31e336e8e7662555fd8526e8cdfd59929671e9ca6e063a3f2d4004": "0078_mcp_headers",
11160
- "8ea9653e8339a4f4c4f0fb140766daace073443ed9e6efae3d4597b2fc6bd60d": "0079_usage_stats"
11174
+ "8ea9653e8339a4f4c4f0fb140766daace073443ed9e6efae3d4597b2fc6bd60d": "0079_usage_stats",
11175
+ "0c66aed1a57dea6a1e9925a9a74dc1ca4f181db3de0c8e446a860a9f3ef7b950": "0080_custom_provider_api_format"
11161
11176
  };
11162
11177
  }
11163
11178
  });
@@ -11682,7 +11697,8 @@ var init_compass_product_copy = __esm({
11682
11697
  { id: "core-1", label: "Core 1", description: "" },
11683
11698
  { id: "core-2", label: "Core 2", description: "" },
11684
11699
  { id: "core-3", label: "Core 3", description: "" },
11685
- { id: "core-4", label: "Core 4", description: "" }
11700
+ { id: "core-4", label: "Core 4", description: "" },
11701
+ { id: "core-5", label: "Core 5", description: "" }
11686
11702
  ];
11687
11703
  }
11688
11704
  });
@@ -11828,6 +11844,7 @@ __export(compass_constants_exports, {
11828
11844
  CORE_TIER_2: () => CORE_TIER_2,
11829
11845
  CORE_TIER_3: () => CORE_TIER_3,
11830
11846
  CORE_TIER_4: () => CORE_TIER_4,
11847
+ CORE_TIER_5: () => CORE_TIER_5,
11831
11848
  CORE_TIER_AUTO: () => CORE_TIER_AUTO,
11832
11849
  CORE_TIER_ORDER: () => CORE_TIER_ORDER,
11833
11850
  DEFAULT_NEW_USER_CORE_TIER: () => DEFAULT_NEW_USER_CORE_TIER,
@@ -11849,6 +11866,7 @@ __export(compass_constants_exports, {
11849
11866
  getOpenRouterModelIdForCoreTier: () => getOpenRouterModelIdForCoreTier,
11850
11867
  isCompassBuildConfigured: () => isCompassBuildConfigured,
11851
11868
  isCoreAutoTier: () => isCoreAutoTier,
11869
+ isCoreTierFromNewerBuild: () => isCoreTierFromNewerBuild,
11852
11870
  isCoreTierId: () => isCoreTierId,
11853
11871
  normalizeCoreTierId: () => normalizeCoreTierId,
11854
11872
  resolveConfiguredCoreTier: () => resolveConfiguredCoreTier
@@ -11870,6 +11888,10 @@ function isCoreTierId(internalTierId) {
11870
11888
  const norm = normalizeCoreTierId(internalTierId);
11871
11889
  return CORE_TIER_ORDER.includes(norm) || norm === CORE_TIER_AUTO;
11872
11890
  }
11891
+ function isCoreTierFromNewerBuild(internalTierId) {
11892
+ const norm = normalizeCoreTierId(internalTierId);
11893
+ return /^core-\d+$/.test(norm) && !CORE_TIER_ORDER.includes(norm);
11894
+ }
11873
11895
  function isCoreAutoTier(internalTierId) {
11874
11896
  return !!internalTierId && normalizeCoreTierId(internalTierId) === CORE_TIER_AUTO;
11875
11897
  }
@@ -11947,6 +11969,8 @@ function getCoreModelIdForCoreTier(internalTierId) {
11947
11969
  return trimEnv(process.env.ATLARIX_CORE_MODEL_3);
11948
11970
  case CORE_TIER_4:
11949
11971
  return trimEnv(process.env.ATLARIX_CORE_MODEL_4);
11972
+ case CORE_TIER_5:
11973
+ return trimEnv(process.env.ATLARIX_CORE_MODEL_5);
11950
11974
  default:
11951
11975
  return void 0;
11952
11976
  }
@@ -11960,6 +11984,7 @@ function getFirstConfiguredCoreTier() {
11960
11984
  }
11961
11985
  function resolveConfiguredCoreTier(internalTierId) {
11962
11986
  const norm = normalizeCoreTierId(internalTierId);
11987
+ if (isCoreTierFromNewerBuild(norm)) return void 0;
11963
11988
  if (getCoreModelIdForCoreTier(norm)?.trim())
11964
11989
  return { tier: norm, substituted: false };
11965
11990
  if (norm === CORE_TIER_AUTO) return void 0;
@@ -11990,7 +12015,7 @@ function isCompassBuildConfigured() {
11990
12015
  function compassExhaustedUserMessage(_tier) {
11991
12016
  return coreExhaustedUserMessage();
11992
12017
  }
11993
- var COMPASS_PROVIDER_ID, CORE_TIER_1, CORE_TIER_2, CORE_TIER_3, CORE_TIER_4, CORE_TIER_AUTO, ATLARIX_AUTO_CONSENT_VERSION, CORE_TIER_ORDER, LEGACY_COMPASS_TIER_ALIASES, EMPTY_SLOT_SENTINELS, devCoreKeyProvider, getOpenRouterModelIdForCoreTier, DEFAULT_CORE_TIER_ORDER, DEFAULT_PAID_CORE_TIER, DEFAULT_NEW_USER_CORE_TIER;
12018
+ var COMPASS_PROVIDER_ID, CORE_TIER_1, CORE_TIER_2, CORE_TIER_3, CORE_TIER_4, CORE_TIER_5, CORE_TIER_AUTO, ATLARIX_AUTO_CONSENT_VERSION, CORE_TIER_ORDER, LEGACY_COMPASS_TIER_ALIASES, EMPTY_SLOT_SENTINELS, devCoreKeyProvider, getOpenRouterModelIdForCoreTier, DEFAULT_CORE_TIER_ORDER, DEFAULT_PAID_CORE_TIER, DEFAULT_NEW_USER_CORE_TIER;
11994
12019
  var init_compass_constants = __esm({
11995
12020
  "src/ipc/shared/compass_constants.ts"() {
11996
12021
  init_format_openrouter_model_label();
@@ -12004,13 +12029,15 @@ var init_compass_constants = __esm({
12004
12029
  CORE_TIER_2 = "core-2";
12005
12030
  CORE_TIER_3 = "core-3";
12006
12031
  CORE_TIER_4 = "core-4";
12032
+ CORE_TIER_5 = "core-5";
12007
12033
  CORE_TIER_AUTO = "core-auto";
12008
12034
  ATLARIX_AUTO_CONSENT_VERSION = 2;
12009
12035
  CORE_TIER_ORDER = [
12010
12036
  CORE_TIER_1,
12011
12037
  CORE_TIER_2,
12012
12038
  CORE_TIER_3,
12013
- CORE_TIER_4
12039
+ CORE_TIER_4,
12040
+ CORE_TIER_5
12014
12041
  ];
12015
12042
  LEGACY_COMPASS_TIER_ALIASES = {
12016
12043
  "compass-fast": CORE_TIER_1,
@@ -31811,7 +31838,26 @@ function renderModeSystemBlock(activeMode) {
31811
31838
  lines.push("</mode_system>");
31812
31839
  return lines.join("\n");
31813
31840
  }
31814
- async function buildSystemPromptWithProjectInstructions(params) {
31841
+ function joinSystemPromptParts({ stableHeader, dynamicTail }) {
31842
+ return `${stableHeader}
31843
+
31844
+ ${dynamicTail}
31845
+ `;
31846
+ }
31847
+ function splitDynamicTail(systemPrompt, dynamicTail) {
31848
+ if (!dynamicTail?.trim()) return systemPrompt;
31849
+ const joined = `
31850
+
31851
+ ${dynamicTail}
31852
+ `;
31853
+ const at2 = systemPrompt.indexOf(joined);
31854
+ if (at2 < 0) return systemPrompt;
31855
+ return {
31856
+ systemPromptOverride: systemPrompt.slice(0, at2) + systemPrompt.slice(at2 + joined.length),
31857
+ dynamicSystemTail: dynamicTail
31858
+ };
31859
+ }
31860
+ async function buildSystemPromptPartsWithProjectInstructions(params) {
31815
31861
  const {
31816
31862
  workspacePath,
31817
31863
  workspacePromptContent,
@@ -31840,7 +31886,7 @@ async function buildSystemPromptWithProjectInstructions(params) {
31840
31886
  buildInstructionsFromLayers(layers),
31841
31887
  projectDocsHint
31842
31888
  ].filter((s3) => s3.trim().length > 0).join("\n\n");
31843
- return constructSystemPrompt({
31889
+ return constructSystemPromptParts({
31844
31890
  contextSize,
31845
31891
  chatMode,
31846
31892
  effectiveWorkMode,
@@ -31866,7 +31912,7 @@ async function buildSystemPromptWithProjectInstructions(params) {
31866
31912
  registeredToolNames
31867
31913
  });
31868
31914
  }
31869
- var import_electron_log10, logger8, cachedPromptShell, AGENTS_MD, loadAgentsFile, FALLBACK_AGENTS, getAgentsMdForContextSize, getAgentsMdForWorkMode, MODE_SYSTEM_DESCRIPTIONS, constructSystemPromptParts, constructSystemPrompt;
31915
+ var import_electron_log10, logger8, cachedPromptShell, AGENTS_MD, loadAgentsFile, FALLBACK_AGENTS, getAgentsMdForContextSize, getAgentsMdForWorkMode, MODE_SYSTEM_DESCRIPTIONS, constructSystemPromptParts;
31870
31916
  var init_system_prompt = __esm({
31871
31917
  "src/prompts/system_prompt.ts"() {
31872
31918
  import_electron_log10 = __toESM(require_src());
@@ -32047,13 +32093,6 @@ When you need clarification or a decision from the user (architecture choices, d
32047
32093
  }
32048
32094
  return { stableHeader, dynamicTail: tail };
32049
32095
  };
32050
- constructSystemPrompt = (params) => {
32051
- const { stableHeader, dynamicTail } = constructSystemPromptParts(params);
32052
- return `${stableHeader}
32053
-
32054
- ${dynamicTail}
32055
- `;
32056
- };
32057
32096
  }
32058
32097
  });
32059
32098
 
@@ -67768,7 +67807,11 @@ var init_provider_sdk_factories = __esm({
67768
67807
  const provider = createOpenAI({ apiKey, ...baseURL ? { baseURL } : {} });
67769
67808
  return baseURL ? provider(modelId) : provider.responses(modelId);
67770
67809
  },
67771
- "@ai-sdk/anthropic": ({ modelId, apiKey, baseURL }) => createAnthropic({ apiKey, ...baseURL ? { baseURL } : {} })(modelId),
67810
+ // Exactly one of apiKey / authToken: the SDK refuses both.
67811
+ "@ai-sdk/anthropic": ({ modelId, apiKey, baseURL, authHeader }) => createAnthropic({
67812
+ ...authHeader === "bearer" ? { authToken: apiKey } : { apiKey },
67813
+ ...baseURL ? { baseURL } : {}
67814
+ })(modelId),
67772
67815
  "@ai-sdk/google": ({ modelId, apiKey, baseURL }) => createGoogle({ apiKey, ...baseURL ? { baseURL } : {} })(modelId),
67773
67816
  "@ai-sdk/xai": ({ modelId, apiKey, baseURL }) => createXai({ apiKey, ...baseURL ? { baseURL } : {} })(modelId)
67774
67817
  };
@@ -68427,7 +68470,7 @@ var init_model_reasoning_profile = __esm({
68427
68470
  temperatureById = /* @__PURE__ */ new Map();
68428
68471
  effortOverrides = /* @__PURE__ */ new Map();
68429
68472
  BUDGET_LEVELS = ["low", "medium", "high"];
68430
- BUDGET_HINT = { low: "medium", medium: "high", high: "max" };
68473
+ BUDGET_HINT = { low: "low", medium: "medium", high: "max" };
68431
68474
  NO_REASONING = /* @__PURE__ */ new Set(["none", "minimal"]);
68432
68475
  AUTO_EFFORT_LEVELS = ["low", "medium", "max"];
68433
68476
  AUTO_EFFORT_CHOICES = { kind: "effort", levels: [...AUTO_EFFORT_LEVELS], defaultLevel: "medium" };
@@ -68963,6 +69006,18 @@ var init_models_dev = __esm({
68963
69006
  }
68964
69007
  });
68965
69008
 
69009
+ // src/lib/provider-key-check.ts
69010
+ function anthropicHeaders(apiKey, authHeader) {
69011
+ return {
69012
+ ...authHeader === "bearer" ? { Authorization: `Bearer ${apiKey}` } : { "x-api-key": apiKey },
69013
+ "anthropic-version": "2023-06-01"
69014
+ };
69015
+ }
69016
+ var init_provider_key_check = __esm({
69017
+ "src/lib/provider-key-check.ts"() {
69018
+ }
69019
+ });
69020
+
68966
69021
  // node_modules/isexe/windows.js
68967
69022
  var require_windows = __commonJS({
68968
69023
  "node_modules/isexe/windows.js"(exports2, module2) {
@@ -71123,21 +71178,22 @@ async function fetchModelsFromOpenAI(apiKey) {
71123
71178
  return [];
71124
71179
  }
71125
71180
  }
71126
- async function fetchModelsFromAnthropic(apiKey) {
71127
- const cached4 = fromCache("anthropic");
71181
+ async function fetchModelsFromAnthropic(apiKey, endpoint2) {
71182
+ const cacheId = endpoint2?.providerId ?? "anthropic";
71183
+ const label = endpoint2 ? endpoint2.providerId : "Anthropic";
71184
+ const cached4 = fromCache(cacheId);
71128
71185
  if (cached4) return cached4;
71129
71186
  try {
71130
71187
  const items = [];
71131
71188
  let after2;
71132
71189
  for (let page = 0; page < MAX_PAGES; page++) {
71133
- const url2 = new URL("https://api.anthropic.com/v1/models");
71190
+ const url2 = new URL(
71191
+ endpoint2 ? `${endpoint2.baseUrl.replace(/\/+$/, "")}/models` : "https://api.anthropic.com/v1/models"
71192
+ );
71134
71193
  url2.searchParams.set("limit", "1000");
71135
71194
  if (after2) url2.searchParams.set("after_id", after2);
71136
71195
  const res = await fetch(url2, {
71137
- headers: {
71138
- "x-api-key": apiKey,
71139
- "anthropic-version": "2023-06-01"
71140
- },
71196
+ headers: anthropicHeaders(apiKey, endpoint2?.authHeader),
71141
71197
  signal: AbortSignal.timeout(15e3)
71142
71198
  });
71143
71199
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -71149,18 +71205,18 @@ async function fetchModelsFromAnthropic(apiKey) {
71149
71205
  const models = items.map((m3) => ({
71150
71206
  apiName: m3.id,
71151
71207
  displayName: m3.display_name ?? m3.id,
71152
- description: "Claude model with tool support",
71208
+ description: endpoint2 ? "Chat model with tool support" : "Claude model with tool support",
71153
71209
  ...m3.max_input_tokens ? { contextWindow: m3.max_input_tokens } : {},
71154
71210
  ...m3.max_tokens ? { maxOutputTokens: m3.max_tokens } : {},
71155
71211
  type: "cloud",
71156
71212
  supportsToolChoice: true
71157
71213
  }));
71158
71214
  sortModels(models);
71159
- toCache("anthropic", models);
71160
- logger10.info(`[Anthropic] Fetched ${models.length} models`);
71215
+ toCache(cacheId, models);
71216
+ logger10.info(`[${label}] Fetched ${models.length} models`);
71161
71217
  return models;
71162
71218
  } catch (e3) {
71163
- logger10.warn("[Anthropic] Fetch failed", e3);
71219
+ logger10.warn(`[${label}] Fetch failed`, e3);
71164
71220
  return [];
71165
71221
  }
71166
71222
  }
@@ -71363,7 +71419,14 @@ async function fetchModelsFromOpenRouter(apiKey) {
71363
71419
  return [];
71364
71420
  }
71365
71421
  }
71366
- async function fetchCustomProviderModels(providerId, baseUrl, apiKey) {
71422
+ async function fetchCustomProviderModels(providerId, baseUrl, apiKey, wire = {}) {
71423
+ if (wire.apiFormat === "anthropic") {
71424
+ return fetchModelsFromAnthropic(apiKey?.trim() || "none", {
71425
+ providerId,
71426
+ baseUrl,
71427
+ authHeader: wire.authHeader
71428
+ });
71429
+ }
71367
71430
  return fetchOpenAICompatibleModels({
71368
71431
  providerId,
71369
71432
  url: `${baseUrl.replace(/\/+$/, "")}/models`,
@@ -71434,6 +71497,7 @@ var init_provider_model_fetchers = __esm({
71434
71497
  import_electron_log12 = __toESM(require_src());
71435
71498
  init_openrouter_app_headers();
71436
71499
  init_language_model_constants();
71500
+ init_provider_key_check();
71437
71501
  logger10 = import_electron_log12.default.scope("provider_fetchers");
71438
71502
  CACHE_MS = 5 * 60 * 1e3;
71439
71503
  cache = /* @__PURE__ */ new Map();
@@ -71560,6 +71624,29 @@ var init_provider_api_keys = __esm({
71560
71624
  }
71561
71625
  });
71562
71626
 
71627
+ // src/lib/routing/custom-provider-format.ts
71628
+ function rememberCustomProviderFormats(providers) {
71629
+ anthropicFormat.clear();
71630
+ for (const p2 of providers) {
71631
+ if (p2.apiFormat === "anthropic") anthropicFormat.add(p2.id);
71632
+ }
71633
+ }
71634
+ function isCustomAnthropicFormat(providerId) {
71635
+ return Boolean(providerId && anthropicFormat.has(providerId));
71636
+ }
71637
+ function anthropicFormatCapabilityId(providerId) {
71638
+ return isCustomAnthropicFormat(providerId) ? "anthropic" : providerId;
71639
+ }
71640
+ function anthropicCatalogModelId(modelId) {
71641
+ return modelId.trim().replace(/^(?:[a-z]{2,6}\.)?anthropic\./i, "").replace(/-v\d+(?::\d+)?$/i, "");
71642
+ }
71643
+ var anthropicFormat;
71644
+ var init_custom_provider_format = __esm({
71645
+ "src/lib/routing/custom-provider-format.ts"() {
71646
+ anthropicFormat = /* @__PURE__ */ new Set();
71647
+ }
71648
+ });
71649
+
71563
71650
  // src/ipc/utils/thinking_utils.ts
71564
71651
  function reasoningCeiling(providerId) {
71565
71652
  return providerId && PROVIDERS_THAT_SUPPORT_THINKING.includes(providerId) ? "max" : "high";
@@ -71583,7 +71670,7 @@ function reasoningBudgetTokens(effort, maxOutputTokens) {
71583
71670
  const max4 = Math.max(1, Math.floor(maxOutputTokens));
71584
71671
  const reserve = Math.max(Math.round(max4 * 0.15), 2048);
71585
71672
  const ceil = Math.max(1024, max4 - reserve);
71586
- const frac = effort === "max" ? 0.85 : effort === "xhigh" ? 0.7 : effort === "high" ? 0.5 : 0.33;
71673
+ const frac = effort === "max" ? 0.85 : effort === "xhigh" ? 0.7 : effort === "high" ? 0.5 : effort === "low" ? 0.15 : 0.33;
71587
71674
  return Math.min(ceil, Math.max(1024, Math.round(max4 * frac)));
71588
71675
  }
71589
71676
  function getExtraProviderOptions(providerId, settings, includeExtendedThinking = false, maxOutputTokens, modelId) {
@@ -71667,6 +71754,9 @@ function resolveReasoningCatalogModelId(effectiveModel) {
71667
71754
  if (effectiveModel.provider === "compass") {
71668
71755
  return getCoreModelsDevTargetForTier(name29)?.modelId ?? getCoreModelIdForCoreTier(name29) ?? name29;
71669
71756
  }
71757
+ if (isCustomAnthropicFormat(effectiveModel.provider)) {
71758
+ return anthropicCatalogModelId(name29);
71759
+ }
71670
71760
  return name29;
71671
71761
  }
71672
71762
  function resolveMediaModelId(effectiveModel) {
@@ -71677,13 +71767,13 @@ function resolveMediaModelId(effectiveModel) {
71677
71767
  }
71678
71768
  function resolveCapabilityProviderId(effectiveModel) {
71679
71769
  const provider = (effectiveModel.provider ?? "").trim();
71680
- if (provider !== "compass") return provider;
71770
+ if (provider !== "compass") return anthropicFormatCapabilityId(provider);
71681
71771
  return getCoreModelsDevTargetForTier(effectiveModel.name ?? "")?.providerId ?? provider;
71682
71772
  }
71683
71773
  function detectReasoningCapability(modelApiId, builtinProviderId) {
71684
71774
  const id = modelApiId.trim();
71685
71775
  const lower2 = id.toLowerCase();
71686
- const pid = builtinProviderId ?? "";
71776
+ const pid = anthropicFormatCapabilityId(builtinProviderId) ?? "";
71687
71777
  let useOpenRouterReasoning = false;
71688
71778
  if (pid === "openrouter" || pid === "compass") {
71689
71779
  const row = id.includes("/") ? getCachedOpenRouterModelRow(id) : void 0;
@@ -71705,11 +71795,13 @@ function detectReasoningCapability(modelApiId, builtinProviderId) {
71705
71795
  }
71706
71796
  const useOpenAiReasoningSummary = pid === "openai";
71707
71797
  const useAnthropicThinking = pid === "anthropic" && catalogSaysReasons(pid, id) === true;
71798
+ const anthropicAdaptive = useAnthropicThinking && rawEffortValues(reasoningProfileFor(pid, id)?.options ?? []).length > 0;
71708
71799
  return {
71709
71800
  useOpenRouterReasoning,
71710
71801
  useGoogleThoughts,
71711
71802
  useOpenAiReasoningSummary,
71712
- useAnthropicThinking
71803
+ useAnthropicThinking,
71804
+ anthropicAdaptive
71713
71805
  };
71714
71806
  }
71715
71807
  function effortChoicesForModel(model) {
@@ -71737,7 +71829,7 @@ function isReasoningCapableModel(model) {
71737
71829
  return offersAChannel(capabilityProvider) || offersAChannel(model.provider ?? "");
71738
71830
  }
71739
71831
  function resolveDispatchCapabilityProviderId(effectiveModel, builtinProviderId) {
71740
- if (builtinProviderId !== "compass") return builtinProviderId;
71832
+ if (builtinProviderId !== "compass") return anthropicFormatCapabilityId(builtinProviderId);
71741
71833
  if (effectiveModel.provider !== "compass") return builtinProviderId;
71742
71834
  return resolveCapabilityProviderId(effectiveModel);
71743
71835
  }
@@ -71815,7 +71907,13 @@ function buildReasoningProviderOptions(cap, thinkingTier, effort = "medium", max
71815
71907
  reasoningEffort: exactEffort ?? effort
71816
71908
  };
71817
71909
  }
71818
- if (cap.useAnthropicThinking) {
71910
+ if (cap.useAnthropicThinking && cap.anthropicAdaptive) {
71911
+ const level = exactEffort?.toLowerCase();
71912
+ patch.anthropic = {
71913
+ thinking: { type: "adaptive", display: "summarized" },
71914
+ ...level && ANTHROPIC_EFFORTS.has(level) ? { effort: level } : {}
71915
+ };
71916
+ } else if (cap.useAnthropicThinking) {
71819
71917
  patch.anthropic = {
71820
71918
  thinking: {
71821
71919
  type: "enabled",
@@ -71825,7 +71923,7 @@ function buildReasoningProviderOptions(cap, thinkingTier, effort = "medium", max
71825
71923
  }
71826
71924
  return patch;
71827
71925
  }
71828
- var GEMINI_LEVELS;
71926
+ var GEMINI_LEVELS, ANTHROPIC_EFFORTS;
71829
71927
  var init_reasoning_detector = __esm({
71830
71928
  "src/lib/routing/reasoning-detector.ts"() {
71831
71929
  init_tier_context_limits();
@@ -71833,8 +71931,10 @@ var init_reasoning_detector = __esm({
71833
71931
  init_model_capability_profile();
71834
71932
  init_language_model_constants();
71835
71933
  init_model_reasoning_profile();
71934
+ init_custom_provider_format();
71836
71935
  init_thinking_utils();
71837
71936
  GEMINI_LEVELS = /* @__PURE__ */ new Set(["minimal", "low", "medium", "high"]);
71937
+ ANTHROPIC_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
71838
71938
  }
71839
71939
  });
71840
71940
 
@@ -72009,11 +72109,16 @@ async function getLanguageModelProviders() {
72009
72109
  name: cp.name,
72010
72110
  apiBaseUrl: cp.api_base_url,
72011
72111
  envVarName: cp.env_var_name ?? void 0,
72112
+ // The Anthropic format dispatches through the catalog's own Anthropic SDK
72113
+ // factory (provider-sdk-factories), like any models.dev provider on it.
72114
+ ...cp.api_format === "anthropic" ? { apiFormat: "anthropic", npm: ANTHROPIC_NPM } : {},
72115
+ ...cp.auth_header === "x-api-key" || cp.auth_header === "bearer" ? { authHeader: cp.auth_header } : {},
72012
72116
  type: "custom"
72013
72117
  // hasFreeTier, websiteUrl, gatewayPrefix are not in the custom DB schema
72014
72118
  // They will be undefined unless overridden by hardcoded values if IDs match
72015
72119
  });
72016
72120
  }
72121
+ rememberCustomProviderFormats([...customProvidersMap.values()]);
72017
72122
  const catalogById = /* @__PURE__ */ new Map();
72018
72123
  try {
72019
72124
  for (const c2 of await getModelsDevProviderCatalog())
@@ -72100,15 +72205,15 @@ function providerDisplayName(providerId) {
72100
72205
  if (!providerId) return void 0;
72101
72206
  return providerNames.get(providerId) ?? providerId.replace(/^custom::/, "");
72102
72207
  }
72103
- async function liveCustomModels(providerId, baseUrl, apiKey, now2 = Date.now()) {
72208
+ async function liveCustomModels(providerId, baseUrl, apiKey, now2 = Date.now(), wire = {}) {
72104
72209
  const { createHash: createHash10 } = await import("node:crypto");
72105
72210
  const keyTag = apiKey?.trim() ? createHash10("sha256").update(apiKey.trim()).digest("hex").slice(0, 12) : "none";
72106
- const cacheKey = `${providerId}|${baseUrl}|${keyTag}`;
72211
+ const cacheKey = `${providerId}|${baseUrl}|${keyTag}|${wire.apiFormat ?? "openai"}|${wire.authHeader ?? ""}`;
72107
72212
  const hit = liveCache.get(cacheKey);
72108
72213
  if (hit && now2 - hit.at < LIVE_TTL_MS) return hit.models;
72109
72214
  const { fetchCustomProviderModels: fetchCustomProviderModels2 } = await Promise.resolve().then(() => (init_provider_model_fetchers(), provider_model_fetchers_exports));
72110
72215
  const models = Promise.race([
72111
- fetchCustomProviderModels2(providerId, baseUrl, apiKey).catch(
72216
+ fetchCustomProviderModels2(providerId, baseUrl, apiKey, wire).catch(
72112
72217
  () => []
72113
72218
  ),
72114
72219
  new Promise(
@@ -72141,7 +72246,10 @@ async function getLanguageModels({
72141
72246
  maxOutputTokens: language_models.max_output_tokens,
72142
72247
  contextWindow: language_models.context_window
72143
72248
  }).from(language_models).where(
72144
- isCustomProvider({ providerId }) ? eq(language_models.customProviderId, providerId) : eq(language_models.builtinProviderId, providerId)
72249
+ // A provider from the providers table, whatever its id: the CLI's dev
72250
+ // endpoint is `atlarix-cli-dev`, with no `custom::`, and its model row
72251
+ // was looked up as a built-in's and never found (QA B3).
72252
+ provider.type === "custom" || isCustomProvider({ providerId }) ? eq(language_models.customProviderId, providerId) : eq(language_models.builtinProviderId, providerId)
72145
72253
  );
72146
72254
  customModels = customModelsDb.map((model) => ({
72147
72255
  ...model,
@@ -72202,7 +72310,10 @@ async function getLanguageModels({
72202
72310
  key = readSettings2().providerSettings?.[providerId]?.apiKey?.value;
72203
72311
  } catch {
72204
72312
  }
72205
- const listed = await liveCustomModels(providerId, provider.apiBaseUrl, key);
72313
+ const listed = await liveCustomModels(providerId, provider.apiBaseUrl, key, Date.now(), {
72314
+ apiFormat: provider.apiFormat,
72315
+ authHeader: provider.authHeader
72316
+ });
72206
72317
  const typed = new Set(customModels.map((m3) => m3.apiName));
72207
72318
  return [...customModels, ...listed.filter((m3) => !typed.has(m3.apiName))];
72208
72319
  }
@@ -72313,7 +72424,7 @@ function stampDeepThinkingSupport(modelsByProvider) {
72313
72424
  function isCustomProvider({ providerId }) {
72314
72425
  return providerId.startsWith(CUSTOM_PROVIDER_PREFIX);
72315
72426
  }
72316
- var providerNames, LIVE_TTL_MS, liveCache, CUSTOM_PROVIDER_PREFIX;
72427
+ var ANTHROPIC_NPM, providerNames, LIVE_TTL_MS, liveCache, CUSTOM_PROVIDER_PREFIX;
72317
72428
  var init_language_model_helpers = __esm({
72318
72429
  "src/ipc/shared/language_model_helpers.ts"() {
72319
72430
  init_db2();
@@ -72328,6 +72439,8 @@ var init_language_model_helpers = __esm({
72328
72439
  init_compass_constants();
72329
72440
  init_reasoning_detector();
72330
72441
  init_model_capability_profile();
72442
+ init_custom_provider_format();
72443
+ ANTHROPIC_NPM = "@ai-sdk/anthropic";
72331
72444
  providerNames = /* @__PURE__ */ new Map();
72332
72445
  LIVE_TTL_MS = 6e4;
72333
72446
  liveCache = /* @__PURE__ */ new Map();
@@ -95745,7 +95858,8 @@ async function resolveModelContextLimit(model, settings) {
95745
95858
  if (pid.startsWith("custom::")) {
95746
95859
  try {
95747
95860
  const { getLanguageModelProviders: getLanguageModelProviders2 } = await Promise.resolve().then(() => (init_language_model_helpers(), language_model_helpers_exports));
95748
- const base = (await getLanguageModelProviders2()).find((p2) => p2.id.toLowerCase() === pid)?.apiBaseUrl;
95861
+ const custom2 = (await getLanguageModelProviders2()).find((p2) => p2.id.toLowerCase() === pid);
95862
+ const base = custom2?.apiFormat === "anthropic" ? void 0 : custom2?.apiBaseUrl;
95749
95863
  if (base) {
95750
95864
  const { isOllamaEndpoint: isOllamaEndpoint2, ollamaOrigin: ollamaOrigin2, ollamaNumCtx: ollamaNumCtx2 } = await Promise.resolve().then(() => (init_ollama_native_fetch(), ollama_native_fetch_exports));
95751
95865
  if (await isOllamaEndpoint2(base)) {
@@ -95768,10 +95882,7 @@ async function resolveModelContextLimit(model, settings) {
95768
95882
  }
95769
95883
  try {
95770
95884
  const { getModelsDevLimits: getModelsDevLimits2 } = await Promise.resolve().then(() => (init_models_dev(), models_dev_exports));
95771
- const limits = await getModelsDevLimits2(
95772
- model.provider ?? "",
95773
- model.name ?? ""
95774
- );
95885
+ const limits = isCustomAnthropicFormat(model.provider) ? await getModelsDevLimits2("anthropic", anthropicCatalogModelId(model.name ?? "")) : await getModelsDevLimits2(model.provider ?? "", model.name ?? "");
95775
95886
  if (limits?.context && limits.context > 0) {
95776
95887
  return {
95777
95888
  tierLimit: floorSafe(limits.context * clamp),
@@ -95793,6 +95904,7 @@ var init_resolve_model_context_limit = __esm({
95793
95904
  init_compass_constants();
95794
95905
  init_findLanguageModel();
95795
95906
  import_electron_log16 = __toESM(require_src());
95907
+ init_custom_provider_format();
95796
95908
  logger14 = import_electron_log16.default.scope("context-limit");
95797
95909
  SAFETY_FACTOR = 0.8;
95798
95910
  localContextCache = /* @__PURE__ */ new Map();
@@ -95913,8 +96025,9 @@ async function getMaxTokensWithFloor(model, opts) {
95913
96025
  return opts?.reasoningCapable ? MAX_OUTPUT_TOKENS_FLOOR_REASONING : MAX_OUTPUT_TOKENS_FLOOR;
95914
96026
  }
95915
96027
  async function getTemperature(model) {
95916
- const providerId = model.provider === "compass" ? getCoreModelsDevTargetForTier(model.name)?.providerId ?? model.provider : model.provider;
95917
- const modelId = model.provider === "compass" ? getCoreModelsDevTargetForTier(model.name)?.modelId ?? model.name : model.name;
96028
+ const anthropicFormat2 = isCustomAnthropicFormat(model.provider);
96029
+ const providerId = model.provider === "compass" ? getCoreModelsDevTargetForTier(model.name)?.providerId ?? model.provider : anthropicFormat2 ? "anthropic" : model.provider;
96030
+ const modelId = model.provider === "compass" ? getCoreModelsDevTargetForTier(model.name)?.modelId ?? model.name : anthropicFormat2 ? anthropicCatalogModelId(model.name ?? "") : model.name;
95918
96031
  const allowed = model.provider === "compass" ? catalogAllowsTemperature(providerId, modelId ?? "") : supportsTemperatureParam(providerId, modelId ?? "");
95919
96032
  if (!allowed) return void 0;
95920
96033
  const modelOption = await findLanguageModel(model);
@@ -95929,6 +96042,7 @@ var init_token_utils = __esm({
95929
96042
  init_compass_constants();
95930
96043
  init_models_dev();
95931
96044
  init_model_reasoning_profile();
96045
+ init_custom_provider_format();
95932
96046
  estimateTokens = (text3) => {
95933
96047
  return exactTokenCountText(text3);
95934
96048
  };
@@ -214617,6 +214731,61 @@ var init_analytics = __esm({
214617
214731
  }
214618
214732
  });
214619
214733
 
214734
+ // src/shared/texts.ts
214735
+ function providerSaid(reason) {
214736
+ const m3 = /"message"\s*:\s*"([^"]{1,300})"/.exec(reason);
214737
+ const text3 = (m3?.[1] ?? reason).replace(/\s+/g, " ").replace(/(\S{0,12}?)\*{4,}(\S{0,8})/g, (_m, head2, tail) => `${head2}\u2026${tail}`).trim();
214738
+ if (text3.length <= 200) return text3;
214739
+ const cut = text3.slice(0, 200);
214740
+ const sentence = cut.lastIndexOf(". ");
214741
+ if (sentence > 80) return cut.slice(0, sentence + 1);
214742
+ const word = cut.lastIndexOf(" ");
214743
+ return `${cut.slice(0, word > 80 ? word : 200)}\u2026`;
214744
+ }
214745
+ function byokLimitUserMessage(reason, providerId) {
214746
+ const who = providerId ? `Your ${providerId} account` : "Your provider account";
214747
+ const said = providerSaid(reason);
214748
+ if (CREDIT_RE.test(reason)) {
214749
+ return `${who} ${BYOK_CREDIT_MARKER}, so it refused this request. Add credit on the provider's billing page, or switch model: Atlarix Auto is free. (It said: "${said}")`;
214750
+ }
214751
+ if (KEY_RE.test(reason)) {
214752
+ return `${providerId ?? "The provider"} ${BYOK_KEY_MARKER}. Check the key (it may have been revoked or mistyped), or switch model. (It said: "${said}")`;
214753
+ }
214754
+ if (RATE_RE.test(reason)) {
214755
+ return `${providerId ?? "The provider"} ${BYOK_RATE_MARKER} right now. Wait a moment and resend, or switch model. (It said: "${said}")`;
214756
+ }
214757
+ return null;
214758
+ }
214759
+ function modelFailureUserMessage(reason) {
214760
+ if (reason.includes(CORE_TIER_NEWER_BUILD)) return CORE_TIER_NEWER_BUILD;
214761
+ const said = providerSaid(reason.replace(/\bDetails:\s*/g, "")) || "the request failed";
214762
+ if (/model ['"`]?[^'"`\s]+['"`]? not found|model_not_found|no such model|unknown model/i.test(reason)) {
214763
+ return `The provider doesn't have that model. Check the model's id, or switch model. (It said: "${said}")`;
214764
+ }
214765
+ if (/\b404\b|not found/i.test(reason)) {
214766
+ return `The provider's address answered "not found". Check its base URL (most end in /v1), or switch model. (It said: "${said}")`;
214767
+ }
214768
+ if (/cannot connect|unable to connect|econnrefused|fetch failed|connection refused|enotfound/i.test(reason)) {
214769
+ return `Couldn't reach the provider. Is its server running, and is the address right? Or switch model. (It said: "${said}")`;
214770
+ }
214771
+ return `This model failed: "${said}". Try again, or switch model.`;
214772
+ }
214773
+ var AI_STREAMING_ERROR_MESSAGE_PREFIX, AI_STREAM_EMPTY_RESPONSE_FALLBACK, SAFETY_FILTER_BLOCKED_MESSAGE, BYOK_CREDIT_MARKER, BYOK_RATE_MARKER, BYOK_KEY_MARKER, CREDIT_RE, RATE_RE, KEY_RE, CORE_TIER_NEWER_BUILD;
214774
+ var init_texts = __esm({
214775
+ "src/shared/texts.ts"() {
214776
+ AI_STREAMING_ERROR_MESSAGE_PREFIX = "Sorry, there was an error from the AI: ";
214777
+ AI_STREAM_EMPTY_RESPONSE_FALLBACK = "Something went wrong \u2014 please try again.";
214778
+ SAFETY_FILTER_BLOCKED_MESSAGE = "Message blocked by safety filter.";
214779
+ BYOK_CREDIT_MARKER = "is out of credit or over its quota";
214780
+ BYOK_RATE_MARKER = "is rate-limiting your key";
214781
+ BYOK_KEY_MARKER = "rejected your API key";
214782
+ CREDIT_RE = /insufficient[_ ]?(quota|balance|credits?|funds)|credit balance is too low|exceeded your current quota|out of credits?|payment required|billing (hard )?limit|\b402\b|quota[_ ]exceeded|no credits/i;
214783
+ RATE_RE = /rate[_ -]?limit|too many requests|\b429\b|overloaded|resource[_ ]exhausted/i;
214784
+ KEY_RE = /invalid[_ ]?(api[_ ]?)?key|incorrect api key|api key not valid|unauthori[sz]ed|authentication[_ ]?(error|failed)|\b401\b|permission[_ ]denied/i;
214785
+ CORE_TIER_NEWER_BUILD = "That Atlarix Core model comes from a newer version of Atlarix. Update this one, or pick another model.";
214786
+ }
214787
+ });
214788
+
214620
214789
  // src/ipc/utils/get_model_client.ts
214621
214790
  var get_model_client_exports = {};
214622
214791
  __export(get_model_client_exports, {
@@ -214811,6 +214980,9 @@ async function getRegularModelClient(model, settings, providerConfig, options) {
214811
214980
  "Atlarix Auto isn't offered right now. Pick a Core model above, or add your own API key in Settings."
214812
214981
  );
214813
214982
  }
214983
+ if (!resolvedTier && isCoreTierFromNewerBuild(model.name)) {
214984
+ throw new Error(CORE_TIER_NEWER_BUILD);
214985
+ }
214814
214986
  if (!resolvedTier) {
214815
214987
  throw new Error(
214816
214988
  `No Atlarix Core model is configured for this build. Add your own API keys in Settings, or use a release build.`
@@ -215041,7 +215213,7 @@ async function getRegularModelClient(model, settings, providerConfig, options) {
215041
215213
  };
215042
215214
  }
215043
215215
  default: {
215044
- if (providerConfig.type === "custom" && providerConfig.apiBaseUrl) {
215216
+ if (providerConfig.type === "custom" && providerConfig.apiBaseUrl && providerConfig.apiFormat !== "anthropic") {
215045
215217
  const { isOllamaEndpoint: isOllamaEndpoint2, ollamaOrigin: ollamaOrigin2 } = await Promise.resolve().then(() => (init_ollama_native_fetch(), ollama_native_fetch_exports));
215046
215218
  if (await isOllamaEndpoint2(providerConfig.apiBaseUrl)) {
215047
215219
  return {
@@ -215061,7 +215233,8 @@ async function getRegularModelClient(model, settings, providerConfig, options) {
215061
215233
  modelId: model.name,
215062
215234
  apiKey,
215063
215235
  baseURL: providerConfig.apiBaseUrl,
215064
- npm: providerConfig.npm
215236
+ npm: providerConfig.npm,
215237
+ authHeader: providerConfig.authHeader
215065
215238
  }),
215066
215239
  builtinProviderId: providerConfig.id
215067
215240
  },
@@ -215110,6 +215283,7 @@ var init_get_model_client = __esm({
215110
215283
  init_analytics();
215111
215284
  init_local_model_ollama_handler();
215112
215285
  init_model_reasoning_profile();
215286
+ init_texts();
215113
215287
  logger19 = import_electron_log21.default.scope("getModelClient");
215114
215288
  }
215115
215289
  });
@@ -241873,6 +242047,7 @@ var init_dist21 = __esm({
241873
242047
  function resolveCacheMarkerPlacement(builtinProviderId, modelName) {
241874
242048
  if (builtinProviderId === "anthropic") return "message";
241875
242049
  if (!/claude|anthropic/i.test(modelName ?? "")) return "none";
242050
+ if (isCustomAnthropicFormat(builtinProviderId)) return "message";
241876
242051
  if (builtinProviderId === "bedrock" || builtinProviderId === "vertex") {
241877
242052
  return "message";
241878
242053
  }
@@ -241984,6 +242159,7 @@ function advanceCacheBreakpointsForStep(messages, placement) {
241984
242159
  var CACHE_MARKER, CACHE_MARKER_HEADER_1H, CACHE_KEY_BY_PROVIDER;
241985
242160
  var init_anthropic_cache_breakpoints = __esm({
241986
242161
  "src/lib/ai/anthropic-cache-breakpoints.ts"() {
242162
+ init_custom_provider_format();
241987
242163
  CACHE_MARKER = {
241988
242164
  anthropic: { cacheControl: { type: "ephemeral" } },
241989
242165
  openrouter: { cacheControl: { type: "ephemeral" } },
@@ -250323,59 +250499,6 @@ var init_delivered_results = __esm({
250323
250499
  }
250324
250500
  });
250325
250501
 
250326
- // src/shared/texts.ts
250327
- function providerSaid(reason) {
250328
- const m3 = /"message"\s*:\s*"([^"]{1,300})"/.exec(reason);
250329
- const text3 = (m3?.[1] ?? reason).replace(/\s+/g, " ").replace(/(\S{0,12}?)\*{4,}(\S{0,8})/g, (_m, head2, tail) => `${head2}\u2026${tail}`).trim();
250330
- if (text3.length <= 200) return text3;
250331
- const cut = text3.slice(0, 200);
250332
- const sentence = cut.lastIndexOf(". ");
250333
- if (sentence > 80) return cut.slice(0, sentence + 1);
250334
- const word = cut.lastIndexOf(" ");
250335
- return `${cut.slice(0, word > 80 ? word : 200)}\u2026`;
250336
- }
250337
- function byokLimitUserMessage(reason, providerId) {
250338
- const who = providerId ? `Your ${providerId} account` : "Your provider account";
250339
- const said = providerSaid(reason);
250340
- if (CREDIT_RE.test(reason)) {
250341
- return `${who} ${BYOK_CREDIT_MARKER}, so it refused this request. Add credit on the provider's billing page, or switch model: Atlarix Auto is free. (It said: "${said}")`;
250342
- }
250343
- if (KEY_RE.test(reason)) {
250344
- return `${providerId ?? "The provider"} ${BYOK_KEY_MARKER}. Check the key (it may have been revoked or mistyped), or switch model. (It said: "${said}")`;
250345
- }
250346
- if (RATE_RE.test(reason)) {
250347
- return `${providerId ?? "The provider"} ${BYOK_RATE_MARKER} right now. Wait a moment and resend, or switch model. (It said: "${said}")`;
250348
- }
250349
- return null;
250350
- }
250351
- function modelFailureUserMessage(reason) {
250352
- const said = providerSaid(reason.replace(/\bDetails:\s*/g, "")) || "the request failed";
250353
- if (/model ['"`]?[^'"`\s]+['"`]? not found|model_not_found|no such model|unknown model/i.test(reason)) {
250354
- return `The provider doesn't have that model. Check the model's id, or switch model. (It said: "${said}")`;
250355
- }
250356
- if (/\b404\b|not found/i.test(reason)) {
250357
- return `The provider's address answered "not found". Check its base URL (most end in /v1), or switch model. (It said: "${said}")`;
250358
- }
250359
- if (/cannot connect|unable to connect|econnrefused|fetch failed|connection refused|enotfound/i.test(reason)) {
250360
- return `Couldn't reach the provider. Is its server running, and is the address right? Or switch model. (It said: "${said}")`;
250361
- }
250362
- return `This model failed: "${said}". Try again, or switch model.`;
250363
- }
250364
- var AI_STREAMING_ERROR_MESSAGE_PREFIX, AI_STREAM_EMPTY_RESPONSE_FALLBACK, SAFETY_FILTER_BLOCKED_MESSAGE, BYOK_CREDIT_MARKER, BYOK_RATE_MARKER, BYOK_KEY_MARKER, CREDIT_RE, RATE_RE, KEY_RE;
250365
- var init_texts = __esm({
250366
- "src/shared/texts.ts"() {
250367
- AI_STREAMING_ERROR_MESSAGE_PREFIX = "Sorry, there was an error from the AI: ";
250368
- AI_STREAM_EMPTY_RESPONSE_FALLBACK = "Something went wrong \u2014 please try again.";
250369
- SAFETY_FILTER_BLOCKED_MESSAGE = "Message blocked by safety filter.";
250370
- BYOK_CREDIT_MARKER = "is out of credit or over its quota";
250371
- BYOK_RATE_MARKER = "is rate-limiting your key";
250372
- BYOK_KEY_MARKER = "rejected your API key";
250373
- CREDIT_RE = /insufficient[_ ]?(quota|balance|credits?|funds)|credit balance is too low|exceeded your current quota|out of credits?|payment required|billing (hard )?limit|\b402\b|quota[_ ]exceeded|no credits/i;
250374
- RATE_RE = /rate[_ -]?limit|too many requests|\b429\b|overloaded|resource[_ ]exhausted/i;
250375
- KEY_RE = /invalid[_ ]?(api[_ ]?)?key|incorrect api key|api key not valid|unauthori[sz]ed|authentication[_ ]?(error|failed)|\b401\b|permission[_ ]denied/i;
250376
- }
250377
- });
250378
-
250379
250502
  // src/lib/security/prompt-hygiene.ts
250380
250503
  function sanitizePromptInput(raw) {
250381
250504
  if (typeof raw !== "string") {
@@ -284883,6 +285006,8 @@ __export(paid_search_exports, {
284883
285006
  paidWebSearch: () => paidWebSearch
284884
285007
  });
284885
285008
  async function paidWebSearch(query, limit, chatId) {
285009
+ const { isBrowserSearchAvailable: isBrowserSearchAvailable2 } = await Promise.resolve().then(() => (init_browser_search(), browser_search_exports));
285010
+ if (isBrowserSearchAvailable2()) return null;
284886
285011
  const { paidCoreForChat: paidCoreForChat2, coreProxyPost: coreProxyPost2 } = await Promise.resolve().then(() => (init_paid_core(), paid_core_exports));
284887
285012
  if (!await paidCoreForChat2(chatId).catch(() => false)) return null;
284888
285013
  try {
@@ -284891,6 +285016,7 @@ async function paidWebSearch(query, limit, chatId) {
284891
285016
  { json: { query, num_results: limit } },
284892
285017
  { signal: AbortSignal.timeout(6e4) }
284893
285018
  );
285019
+ if (res?.status === 402) return { blocked: "balance" };
284894
285020
  if (!res?.ok) return null;
284895
285021
  const body = await res.json();
284896
285022
  const rows = (body.results ?? []).filter(
@@ -285227,7 +285353,7 @@ function optimizeSearchQueryKeywords(rawQuery) {
285227
285353
  const optimized = [...quoted, ...keywords.slice(0, 6)].join(" ").trim();
285228
285354
  return optimized || rawQuery;
285229
285355
  }
285230
- var import_turndown, WEB_SEARCH_SETUP_HINT, CLI_WEB_SEARCH_UNAVAILABLE, READ_URL_DEFAULT_MAX, READ_URL_MAX_CAP, FETCH_TIMEOUT_MS, FETCH_TIMEOUT_MAX_MS, BROWSER_USER_AGENT, _turndown, READER_PARTITION, BROWSER_ESCALATION_STATUSES, JS_SHELL_MAX_TEXT_CHARS, JS_SHELL_MIN_HTML_BYTES, EXTERNAL_SCRIPT_RE, BROWSER_EXTRACT_TIMEOUT_MS, POST_RENDER_MIN_CHARS, READER_HTML_SCRIPT, RELEVANCE_STOP_WORDS, WEB_SEARCH_GLOBAL_CACHE, WEB_SEARCH_GLOBAL_IN_FLIGHT, WEB_SEARCH_CACHE_TTL, READ_URL_CACHE, READ_URL_IN_FLIGHT, READ_URL_CACHE_TTL, READ_URL_CACHE_MAX_CHARS, WEB_SEARCH_DEFAULT_RESULTS, WEB_SEARCH_MAX_RESULTS, SITE_ROOT_FETCH_MAX, WebSearchAggregator;
285356
+ var import_turndown, WEB_SEARCH_SETUP_HINT, CLI_WEB_SEARCH_UNAVAILABLE, WEB_SEARCH_NO_BALANCE, READ_URL_DEFAULT_MAX, READ_URL_MAX_CAP, FETCH_TIMEOUT_MS, FETCH_TIMEOUT_MAX_MS, BROWSER_USER_AGENT, _turndown, READER_PARTITION, BROWSER_ESCALATION_STATUSES, JS_SHELL_MAX_TEXT_CHARS, JS_SHELL_MIN_HTML_BYTES, EXTERNAL_SCRIPT_RE, BROWSER_EXTRACT_TIMEOUT_MS, POST_RENDER_MIN_CHARS, READER_HTML_SCRIPT, RELEVANCE_STOP_WORDS, WEB_SEARCH_GLOBAL_CACHE, WEB_SEARCH_GLOBAL_IN_FLIGHT, WEB_SEARCH_CACHE_TTL, READ_URL_CACHE, READ_URL_IN_FLIGHT, READ_URL_CACHE_TTL, READ_URL_CACHE_MAX_CHARS, WEB_SEARCH_DEFAULT_RESULTS, WEB_SEARCH_MAX_RESULTS, SITE_ROOT_FETCH_MAX, WebSearchAggregator;
285231
285357
  var init_web = __esm({
285232
285358
  "src/lib/tools/web.ts"() {
285233
285359
  init_registry();
@@ -285238,6 +285364,7 @@ var init_web = __esm({
285238
285364
  import_turndown = __toESM(require_turndown_cjs());
285239
285365
  WEB_SEARCH_SETUP_HINT = "\n\n**If this keeps coming up empty:** search already runs through Atlarix's own browser, so there is nothing to install \u2014 try rephrasing as keywords rather than a question. If one site or domain is consistently thin, the user can connect a **search/web MCP** in Settings \u2192 Tools & MCP.";
285240
285366
  CLI_WEB_SEARCH_UNAVAILABLE = (query) => `**No web results for "${query}". In the Atlarix CLI, web search runs through the Atlarix Chrome extension** (install it and open its side panel once). Without it, add a search MCP server (any search API works that way). Tell the user so. \`read_url\` still works for a page whose address you know.`;
285367
+ WEB_SEARCH_NO_BALANCE = (query) => `**No web results for "${query}". Web search here is billed to the Atlarix account, and the balance can't cover it.** Tell the user to add credit (\`/billing\` in the terminal), or to connect a search MCP server. \`read_url\` still works for a page whose address you know.`;
285241
285368
  READ_URL_DEFAULT_MAX = 2e4;
285242
285369
  READ_URL_MAX_CAP = 5e4;
285243
285370
  FETCH_TIMEOUT_MS = 2e4;
@@ -285374,7 +285501,9 @@ ${fetched.content}`
285374
285501
  };
285375
285502
  }
285376
285503
  const { paidWebSearch: paidWebSearch2 } = await Promise.resolve().then(() => (init_paid_search(), paid_search_exports));
285377
- const paid = await paidWebSearch2(query, maxResults, args2._chatSessionId);
285504
+ const paidReply = await paidWebSearch2(query, maxResults, args2._chatSessionId);
285505
+ const noBalance = paidReply !== null && "blocked" in paidReply;
285506
+ const paid = noBalance ? null : paidReply;
285378
285507
  if (paid && paid.results.length > 0) {
285379
285508
  const lines = [`**Search results for: "${query}"**
285380
285509
  `];
@@ -285405,6 +285534,9 @@ ${fetched.content}`
285405
285534
  maxResults
285406
285535
  );
285407
285536
  console.log(`[web_search] Found ${searchResults.sources.length} results`);
285537
+ if (noBalance && searchResults.sources.length === 0) {
285538
+ return { text: WEB_SEARCH_NO_BALANCE(query), sources: [] };
285539
+ }
285408
285540
  return {
285409
285541
  // Remote search content is untrusted — data, not instructions.
285410
285542
  text: searchResults.sources.length > 0 ? wrapUntrustedData("web_search", searchResults.formattedText) : searchResults.formattedText,
@@ -338396,7 +338528,8 @@ async function simpleStreamText(ctx, deps, {
338396
338528
  ...reasoningPatch.anthropic
338397
338529
  };
338398
338530
  }
338399
- const handledNatively = offByBudget || Boolean(reasoningPatch.openrouter) || Boolean(reasoningPatch.google) || Boolean(reasoningPatch.openai) || Boolean(reasoningPatch.anthropic);
338531
+ const handledNatively = offByBudget || // The Anthropic SDK reads only its own bucket, in its own shape.
338532
+ isCustomAnthropicFormat(modelClient.builtinProviderId) || Boolean(reasoningPatch.openrouter) || Boolean(reasoningPatch.google) || Boolean(reasoningPatch.openai) || Boolean(reasoningPatch.anthropic);
338400
338533
  if (modelClient.builtinProviderId && !handledNatively) {
338401
338534
  const compatibleReasoning = getExtraProviderOptions(
338402
338535
  capabilityProviderId,
@@ -338415,6 +338548,7 @@ async function simpleStreamText(ctx, deps, {
338415
338548
  }
338416
338549
  }
338417
338550
  const isAnthropic = modelClient.builtinProviderId === "anthropic";
338551
+ const speaksAnthropic = isAnthropic || isCustomAnthropicFormat(modelClient.builtinProviderId);
338418
338552
  const cacheMarkerPlacement = resolveCacheMarkerPlacement(
338419
338553
  modelClient.builtinProviderId,
338420
338554
  effectiveModel?.name
@@ -338486,7 +338620,7 @@ async function simpleStreamText(ctx, deps, {
338486
338620
  history,
338487
338621
  placement: cacheMarkerPlacement,
338488
338622
  // v13.3: header prefix survives >5min between-turn gaps.
338489
- headerTtl1h: isAnthropic
338623
+ headerTtl1h: speaksAnthropic
338490
338624
  });
338491
338625
  const completedStepMessages = [];
338492
338626
  const recordStepMessages = (msgs) => {
@@ -338935,6 +339069,7 @@ var init_simple_stream_text = __esm({
338935
339069
  init_perf_marks();
338936
339070
  init_byok_pricing();
338937
339071
  init_reasoning_detector();
339072
+ init_custom_provider_format();
338938
339073
  init_system_reminder();
338939
339074
  init_step_messages();
338940
339075
  init_texts();
@@ -343033,6 +343168,7 @@ async function runBuildBranch(ctx, deps) {
343033
343168
  modelContextResolved,
343034
343169
  placeholderAssistantMessage,
343035
343170
  systemPrompt,
343171
+ dynamicTail,
343036
343172
  taskLedgerBlock,
343037
343173
  simpleStreamTextDeps,
343038
343174
  streamTurnId,
@@ -344280,7 +344416,9 @@ async function runBuildBranch(ctx, deps) {
344280
344416
  escalationMode: effectiveWorkMode === "plan" ? "plan" : effectiveWorkMode === "build" ? "build" : "agent",
344281
344417
  tools,
344282
344418
  repetition: agenticRepetition,
344283
- buildRoundSystemPrompt: () => systemPrompt,
344419
+ // The per-turn tail in its own system message, after the cached
344420
+ // header, so the next turn reads the cache instead of rewriting it.
344421
+ buildRoundSystemPrompt: () => splitDynamicTail(systemPrompt, dynamicTail),
344284
344422
  detectTextToolCalls: (fr2) => useTextToolFallback && containsTextToolCall2(fr2) ? parseTextToolCalls2(fr2) : null,
344285
344423
  executeTextToolCalls: (calls) => execBuildTextToolCalls(
344286
344424
  calls
@@ -344423,6 +344561,7 @@ var init_build_branch = __esm({
344423
344561
  init_dist21();
344424
344562
  import_electron_log102 = __toESM(require_src());
344425
344563
  init_edit_stats2();
344564
+ init_system_prompt();
344426
344565
  logger99 = import_electron_log102.default.scope("chat_stream_handlers");
344427
344566
  }
344428
344567
  });
@@ -346470,11 +346609,11 @@ function registerChatStreamHandlers() {
346470
346609
  providerBrand: modelIdentity.providerBrand,
346471
346610
  labelWithProvider: modelIdentity.labelWithProvider
346472
346611
  };
346473
- let baseSystemPrompt;
346612
+ let baseParts;
346474
346613
  if (hasWorkspace && appPath && effectiveAppId != null) {
346475
- baseSystemPrompt = await preflight.time(
346614
+ baseParts = await preflight.time(
346476
346615
  "system-prompt",
346477
- () => buildSystemPromptWithProjectInstructions({
346616
+ () => buildSystemPromptPartsWithProjectInstructions({
346478
346617
  workspacePath: appPath,
346479
346618
  workspacePromptContent: null,
346480
346619
  contextSize,
@@ -346487,7 +346626,7 @@ function registerChatStreamHandlers() {
346487
346626
  })
346488
346627
  );
346489
346628
  } else {
346490
- baseSystemPrompt = constructSystemPrompt({
346629
+ baseParts = constructSystemPromptParts({
346491
346630
  chatMode,
346492
346631
  effectiveWorkMode: modeBlockKey,
346493
346632
  contextSize,
@@ -346497,6 +346636,8 @@ function registerChatStreamHandlers() {
346497
346636
  ...promptIdentityOpts
346498
346637
  });
346499
346638
  }
346639
+ let baseSystemPrompt = joinSystemPromptParts(baseParts);
346640
+ const baseDynamicTail = baseParts.dynamicTail;
346500
346641
  if (activeUserAgent) {
346501
346642
  const { renderUserAgentSystemBlock: renderUserAgentSystemBlock2 } = await Promise.resolve().then(() => (init_user_agents(), user_agents_exports));
346502
346643
  baseSystemPrompt = `${renderUserAgentSystemBlock2(activeUserAgent)}
@@ -347006,6 +347147,7 @@ ${baseSystemPrompt}`;
347006
347147
  modelContextResolved,
347007
347148
  placeholderAssistantMessage,
347008
347149
  systemPrompt,
347150
+ dynamicTail: baseDynamicTail,
347009
347151
  taskLedgerBlock,
347010
347152
  simpleStreamTextDeps,
347011
347153
  streamTurnId,
@@ -347939,7 +348081,7 @@ ${err2.stack}` : "";
347939
348081
  }
347940
348082
 
347941
348083
  // src/headless/atlarix-headless.mts
347942
- var VERSION15 = false ? "dev" : "15.8.2";
348084
+ var VERSION15 = false ? "dev" : "15.9.1";
347943
348085
  if (process.argv[2] === "run") process.argv.splice(2, 1);
347944
348086
  var RUN_HELP = `atlarix run: one turn without the UI, for scripts and CI
347945
348087
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlarix",
3
- "version": "15.8.2",
3
+ "version": "15.9.1",
4
4
  "description": "Atlarix CLI: an AI coding agent in your terminal. Works with any model (Atlarix Auto, your own API key, or a local model) and runs unattended with `atlarix run` for CI.",
5
5
  "license": "PROPRIETARY",
6
6
  "author": {
@@ -65,12 +65,12 @@
65
65
  "provenance": false
66
66
  },
67
67
  "optionalDependencies": {
68
- "atlarix-darwin-arm64": "15.8.2",
69
- "atlarix-darwin-x64": "15.8.2",
70
- "atlarix-linux-x64": "15.8.2",
71
- "atlarix-linux-arm64": "15.8.2",
72
- "atlarix-linux-x64-musl": "15.8.2",
73
- "atlarix-linux-arm64-musl": "15.8.2",
74
- "atlarix-windows-x64": "15.8.2"
68
+ "atlarix-darwin-arm64": "15.9.1",
69
+ "atlarix-darwin-x64": "15.9.1",
70
+ "atlarix-linux-x64": "15.9.1",
71
+ "atlarix-linux-arm64": "15.9.1",
72
+ "atlarix-linux-x64-musl": "15.9.1",
73
+ "atlarix-linux-arm64-musl": "15.9.1",
74
+ "atlarix-windows-x64": "15.9.1"
75
75
  }
76
76
  }