demian-cli 1.0.7 → 1.0.8

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.
package/dist/index.mjs CHANGED
@@ -92,7 +92,7 @@ var claudeCodeAgent = {
92
92
  name: "claudecode",
93
93
  description: "Claude Code external coding agent for focused delegated workspace tasks.",
94
94
  mode: "subagent",
95
- provider: { profile: "claudecode-subagent", permissionProfile: "build" },
95
+ provider: { profile: "claudecode", permissionProfile: "build" },
96
96
  tools: [],
97
97
  systemPrompt: [
98
98
  "You are demian claudecode, a Claude Code external runtime working as a sub agent for Demian.",
@@ -137,7 +137,7 @@ var claudeCodeExplorerAgent = {
137
137
  name: "claudecode-explorer",
138
138
  description: "Claude Code external read-only explorer for cowork repository inspection.",
139
139
  mode: "subagent",
140
- provider: { profile: "claudecode-subagent", permissionProfile: "explore" },
140
+ provider: { profile: "claudecode", permissionProfile: "explore" },
141
141
  tools: [],
142
142
  systemPrompt: [
143
143
  "You are demian claudecode-explorer, a Claude Code external runtime working as a read-only cowork sub agent for Demian.",
@@ -172,7 +172,7 @@ var claudeCodeBuilderAgent = {
172
172
  name: "claudecode-builder",
173
173
  description: "Claude Code-backed builder for bounded cowork implementation tasks.",
174
174
  mode: "subagent",
175
- provider: { profile: "claudecode-subagent", permissionProfile: "build" },
175
+ provider: { profile: "claudecode", permissionProfile: "build" },
176
176
  tools: [],
177
177
  systemPrompt: [
178
178
  "You are demian claudecode-builder, a Claude Code external runtime working as a writer cowork sub agent for Demian.",
@@ -477,7 +477,7 @@ import { readFile as readFile6 } from "node:fs/promises";
477
477
  import crypto4 from "node:crypto";
478
478
  import fs7 from "node:fs";
479
479
  import os7 from "node:os";
480
- import path12 from "node:path";
480
+ import path13 from "node:path";
481
481
 
482
482
  // src/providers/retry.ts
483
483
  var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
@@ -870,12 +870,14 @@ var dynamicImport = new Function("specifier", "return import(specifier)");
870
870
  var AnthropicProvider = class {
871
871
  id = "anthropic";
872
872
  #apiKey;
873
+ #baseURL;
873
874
  #defaultModel;
874
875
  #defaultMaxTokens;
875
876
  #client;
876
877
  #onRetry;
877
878
  constructor(config) {
878
879
  this.#apiKey = config.apiKey;
880
+ this.#baseURL = config.baseURL;
879
881
  this.#defaultModel = config.defaultModel;
880
882
  this.#defaultMaxTokens = config.defaultMaxTokens ?? 4096;
881
883
  this.#client = config.client;
@@ -920,7 +922,7 @@ var AnthropicProvider = class {
920
922
  if (this.#client) return this.#client;
921
923
  if (!this.#apiKey) throw new Error("AnthropicProvider requires apiKey");
922
924
  const Anthropic = await loadAnthropicConstructor();
923
- this.#client = new Anthropic({ apiKey: this.#apiKey });
925
+ this.#client = new Anthropic({ apiKey: this.#apiKey, baseURL: this.#baseURL });
924
926
  return this.#client;
925
927
  }
926
928
  };
@@ -1153,19 +1155,56 @@ import { execFile } from "node:child_process";
1153
1155
  import { promisify } from "node:util";
1154
1156
  import fs2 from "node:fs";
1155
1157
  import { chmod, mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
1156
- import path2 from "node:path";
1158
+ import path3 from "node:path";
1157
1159
 
1158
1160
  // src/providers/codex-state.ts
1159
1161
  import crypto from "node:crypto";
1160
1162
  import fs from "node:fs";
1161
1163
  import { mkdir, readFile, writeFile } from "node:fs/promises";
1164
+ import os2 from "node:os";
1165
+ import path2 from "node:path";
1166
+
1167
+ // src/path-expansion.ts
1162
1168
  import os from "node:os";
1163
1169
  import path from "node:path";
1170
+ function expandPathReferences(value) {
1171
+ let expanded = expandTilde(value);
1172
+ expanded = expanded.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, bare) => envPathValue(braced ?? bare) ?? match);
1173
+ expanded = expanded.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (match, name) => envPathValue(name) ?? match);
1174
+ return expanded;
1175
+ }
1176
+ function resolveExpandedPath(value) {
1177
+ return path.resolve(expandPathReferences(value));
1178
+ }
1179
+ function expandTilde(value) {
1180
+ if (value === "~") return os.homedir();
1181
+ if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
1182
+ return value;
1183
+ }
1184
+ function envPathValue(name) {
1185
+ if (!name) return void 0;
1186
+ const value = processEnvValue(name);
1187
+ if (value !== void 0) return value;
1188
+ const upper = name.toUpperCase();
1189
+ if (upper === "HOME" || upper === "USERPROFILE") return os.homedir();
1190
+ if (process.platform === "win32" && upper === "HOMEDRIVE") return path.win32.parse(os.homedir()).root.replace(/[\\/]$/, "");
1191
+ if (process.platform === "win32" && upper === "HOMEPATH") return os.homedir().replace(/^[A-Za-z]:/, "");
1192
+ return void 0;
1193
+ }
1194
+ function processEnvValue(name) {
1195
+ if (process.env[name] !== void 0) return process.env[name];
1196
+ if (process.platform !== "win32") return void 0;
1197
+ const upper = name.toUpperCase();
1198
+ const key = Object.keys(process.env).find((item) => item.toUpperCase() === upper);
1199
+ return key ? process.env[key] : void 0;
1200
+ }
1201
+
1202
+ // src/providers/codex-state.ts
1164
1203
  function resolveCodexHome(configured) {
1165
- return path.resolve(expandCodexPath(configured ?? process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex")));
1204
+ return resolveExpandedPath(configured ?? process.env.CODEX_HOME ?? path2.join(os2.homedir(), ".codex"));
1166
1205
  }
1167
1206
  async function loadOrCreateInstallationId(codexHome) {
1168
- const filePath = path.join(codexHome, "installation_id");
1207
+ const filePath = path2.join(codexHome, "installation_id");
1169
1208
  try {
1170
1209
  const existing = (await readFile(filePath, "utf8")).trim();
1171
1210
  if (isUuid(existing)) return existing;
@@ -1187,15 +1226,6 @@ function isNodeError(error, code) {
1187
1226
  function fileExists(filePath) {
1188
1227
  return fs.existsSync(filePath);
1189
1228
  }
1190
- function expandCodexPath(value) {
1191
- let expanded = value;
1192
- if (expanded === "~") return os.homedir();
1193
- if (expanded.startsWith("~/")) expanded = path.join(os.homedir(), expanded.slice(2));
1194
- return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
1195
- const name = braced ?? bare;
1196
- return name && process.env[name] !== void 0 ? process.env[name] : match;
1197
- });
1198
- }
1199
1229
 
1200
1230
  // src/providers/codex-auth.ts
1201
1231
  var execFileAsync = promisify(execFile);
@@ -1418,10 +1448,10 @@ var CodexAuthStore = class {
1418
1448
  }
1419
1449
  };
1420
1450
  function authFilePath(codexHome) {
1421
- return path2.join(codexHome, "auth.json");
1451
+ return path3.join(codexHome, "auth.json");
1422
1452
  }
1423
1453
  function codexKeyringAccount(codexHome) {
1424
- const resolved = path2.resolve(codexHome);
1454
+ const resolved = path3.resolve(codexHome);
1425
1455
  const canonical = fs2.existsSync(resolved) ? fs2.realpathSync.native(resolved) : resolved;
1426
1456
  const hash = crypto2.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
1427
1457
  return `cli|${hash}`;
@@ -1856,8 +1886,8 @@ import { randomUUID } from "node:crypto";
1856
1886
  import { execFile as execFile2 } from "node:child_process";
1857
1887
  import fs3 from "node:fs";
1858
1888
  import { chmod as chmod2, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
1859
- import os2 from "node:os";
1860
- import path3 from "node:path";
1889
+ import os3 from "node:os";
1890
+ import path4 from "node:path";
1861
1891
  import { promisify as promisify2 } from "node:util";
1862
1892
  var execFileAsync2 = promisify2(execFile2);
1863
1893
  var CLAUDE_CODE_KEYRING_SERVICE = "Claude Code-credentials";
@@ -1890,7 +1920,7 @@ function getSharedClaudeCodeAuthStore(options = {}) {
1890
1920
  proactiveRefreshMinutes: options.proactiveRefreshMinutes ?? 30,
1891
1921
  refreshCache: options.refreshCache ?? "claude-store",
1892
1922
  refreshTokenURL: options.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL,
1893
- keychainAccount: options.keychainAccount ?? os2.userInfo().username
1923
+ keychainAccount: options.keychainAccount ?? os3.userInfo().username
1894
1924
  });
1895
1925
  const existing = sharedAuthStores2.get(key);
1896
1926
  if (existing) return existing;
@@ -1919,7 +1949,7 @@ var ClaudeCodeAuthStore = class {
1919
1949
  this.#proactiveRefreshMinutes = options.proactiveRefreshMinutes ?? 30;
1920
1950
  this.#refreshCache = options.refreshCache ?? "claude-store";
1921
1951
  this.#refreshTokenURL = options.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL;
1922
- this.#keychainAccount = options.keychainAccount ?? os2.userInfo().username;
1952
+ this.#keychainAccount = options.keychainAccount ?? os3.userInfo().username;
1923
1953
  this.#fetch = options.fetch ?? fetch;
1924
1954
  this.#keyring = options.keyring ?? new MacOSSecurityKeyring2();
1925
1955
  }
@@ -2120,10 +2150,10 @@ var ClaudeCodeAuthStore = class {
2120
2150
  }
2121
2151
  };
2122
2152
  function resolveClaudeConfigDir(configured) {
2123
- return path3.resolve(expandClaudePath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path3.join(os2.homedir(), ".claude")));
2153
+ return resolveExpandedPath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path4.join(os3.homedir(), ".claude"));
2124
2154
  }
2125
2155
  function claudeCodeAuthFilePath(claudeConfigDir) {
2126
- return path3.join(claudeConfigDir, ".credentials.json");
2156
+ return path4.join(claudeConfigDir, ".credentials.json");
2127
2157
  }
2128
2158
  function oauthPayload(auth) {
2129
2159
  const raw = auth.claudeAiOauth;
@@ -2182,15 +2212,6 @@ async function writeClaudeCodeAuthFileAtomic(claudeConfigDir, auth) {
2182
2212
  await chmod2(tempPath, 384);
2183
2213
  await rename2(tempPath, filePath);
2184
2214
  }
2185
- function expandClaudePath(value) {
2186
- let expanded = value;
2187
- if (expanded === "~") return os2.homedir();
2188
- if (expanded.startsWith("~/")) expanded = path3.join(os2.homedir(), expanded.slice(2));
2189
- return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
2190
- const name = braced ?? bare;
2191
- return name && process.env[name] !== void 0 ? process.env[name] : match;
2192
- });
2193
- }
2194
2215
  var MacOSSecurityKeyring2 = class {
2195
2216
  async load(service, account) {
2196
2217
  if (process.platform !== "darwin") return void 0;
@@ -2427,6 +2448,111 @@ function parseClaudeCodeErrorBody(body) {
2427
2448
  }
2428
2449
  }
2429
2450
 
2451
+ // src/providers/ollama.ts
2452
+ var OllamaProvider = class {
2453
+ id = "ollama";
2454
+ #baseURL;
2455
+ #apiKey;
2456
+ #defaultModel;
2457
+ #fetch;
2458
+ #onRetry;
2459
+ constructor(config) {
2460
+ this.#baseURL = config.baseURL.replace(/\/+$/, "");
2461
+ this.#apiKey = config.apiKey;
2462
+ this.#defaultModel = config.defaultModel;
2463
+ this.#fetch = config.fetch ?? fetch;
2464
+ this.#onRetry = config.onRetry;
2465
+ }
2466
+ async chat(req) {
2467
+ return chatWithRetry(() => this.#rawChat(req), {
2468
+ signal: req.signal,
2469
+ onRetry: this.#onRetry
2470
+ });
2471
+ }
2472
+ async #rawChat(req) {
2473
+ const response = await this.#fetch(`${this.#baseURL}/chat`, {
2474
+ method: "POST",
2475
+ headers: {
2476
+ "content-type": "application/json",
2477
+ ...this.#authHeaders()
2478
+ },
2479
+ body: JSON.stringify({
2480
+ model: req.model || this.#defaultModel,
2481
+ messages: req.messages.map(toOllamaMessage),
2482
+ ...req.tools.length > 0 ? { tools: req.tools.map(toOpenAITool) } : {},
2483
+ stream: false,
2484
+ ...req.temperature !== void 0 ? { options: { temperature: req.temperature } } : {}
2485
+ }),
2486
+ signal: req.signal
2487
+ });
2488
+ const text = await response.text();
2489
+ if (!response.ok) throw new ProviderHttpError(response.status, text, parseRetryAfter(response.headers.get("retry-after")));
2490
+ return normalizeOllamaResponse(text ? JSON.parse(text) : {});
2491
+ }
2492
+ #authHeaders() {
2493
+ return this.#apiKey ? { authorization: `Bearer ${this.#apiKey}` } : {};
2494
+ }
2495
+ };
2496
+ function toOllamaMessage(message) {
2497
+ if (message.role === "tool") {
2498
+ return {
2499
+ role: "tool",
2500
+ content: message.content,
2501
+ tool_name: message.name
2502
+ };
2503
+ }
2504
+ const out = {
2505
+ role: message.role,
2506
+ content: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "")
2507
+ };
2508
+ if (message.role === "assistant" && message.toolCalls?.length) {
2509
+ out.tool_calls = message.toolCalls.map((call) => ({
2510
+ function: {
2511
+ name: call.name,
2512
+ arguments: call.input ?? {}
2513
+ }
2514
+ }));
2515
+ }
2516
+ return out;
2517
+ }
2518
+ function normalizeOllamaResponse(raw) {
2519
+ const data = raw;
2520
+ const toolCalls = [];
2521
+ for (const call of data.message?.tool_calls ?? []) {
2522
+ const name = call.function?.name;
2523
+ if (!name) continue;
2524
+ toolCalls.push({
2525
+ id: call.id ?? `call_${toolCalls.length + 1}`,
2526
+ name,
2527
+ input: normalizeToolArguments(call.function?.arguments)
2528
+ });
2529
+ }
2530
+ const message = {
2531
+ role: "assistant",
2532
+ content: data.message?.content ?? null,
2533
+ ...toolCalls.length ? { toolCalls } : {}
2534
+ };
2535
+ return {
2536
+ message,
2537
+ toolCalls,
2538
+ stopReason: toolCalls.length ? "tool_use" : data.done_reason === "length" ? "max_tokens" : "end_turn",
2539
+ usage: data.prompt_eval_count !== void 0 || data.eval_count !== void 0 ? {
2540
+ inputTokens: data.prompt_eval_count,
2541
+ outputTokens: data.eval_count,
2542
+ totalTokens: (data.prompt_eval_count ?? 0) + (data.eval_count ?? 0)
2543
+ } : void 0,
2544
+ raw
2545
+ };
2546
+ }
2547
+ function normalizeToolArguments(value) {
2548
+ if (typeof value !== "string") return value ?? {};
2549
+ try {
2550
+ return JSON.parse(value);
2551
+ } catch {
2552
+ return {};
2553
+ }
2554
+ }
2555
+
2430
2556
  // src/external-runtime/claudecode-cli.ts
2431
2557
  import { spawn as spawn3 } from "node:child_process";
2432
2558
  import readline from "node:readline";
@@ -2434,7 +2560,7 @@ import readline from "node:readline";
2434
2560
  // src/external-runtime/claudecode-attachments.ts
2435
2561
  import crypto3 from "node:crypto";
2436
2562
  import fs4 from "node:fs";
2437
- import path4 from "node:path";
2563
+ import path5 from "node:path";
2438
2564
  async function resolveClaudeCodeAttachmentPrompt(runtimeLabel, config, req) {
2439
2565
  const attachments = req.attachments ?? [];
2440
2566
  if (attachments.length === 0) return req.prompt;
@@ -2476,7 +2602,7 @@ function unsupportedAttachmentError(runtimeLabel, count, detail) {
2476
2602
  }
2477
2603
  function formatAttachmentReference(value, cwd) {
2478
2604
  if (isUrl(value)) return imageMimeFromPath(value) ? `[image: ${value} (${imageMimeFromPath(value)}, remote)]` : `[attachment: ${value}]`;
2479
- const target = path4.isAbsolute(value) ? value : path4.resolve(cwd, value);
2605
+ const target = path5.isAbsolute(value) ? value : path5.resolve(cwd, value);
2480
2606
  try {
2481
2607
  const stats = fs4.statSync(target);
2482
2608
  if (!stats.isFile()) return `[attachment: ${value} (${stats.size} bytes)]`;
@@ -2493,7 +2619,7 @@ function isUrl(value) {
2493
2619
  }
2494
2620
  function imageMimeFromPath(value) {
2495
2621
  const pathname = isUrl(value) ? new URL(value).pathname : value;
2496
- const ext = path4.extname(pathname).toLowerCase();
2622
+ const ext = path5.extname(pathname).toLowerCase();
2497
2623
  if (ext === ".png") return "image/png";
2498
2624
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
2499
2625
  if (ext === ".gif") return "image/gif";
@@ -2525,15 +2651,14 @@ function hasAnthropicApiKeyEnv(env = process.env) {
2525
2651
 
2526
2652
  // src/external-runtime/claudecode-paths.ts
2527
2653
  import fs5 from "node:fs";
2528
- import os3 from "node:os";
2529
- import path5 from "node:path";
2654
+ import path6 from "node:path";
2530
2655
  function resolveClaudeCodeCliPath(configured) {
2531
2656
  if (configured) return expandHome(configured);
2532
2657
  if (process.env.CLAUDE_CODE_CLI) return expandHome(process.env.CLAUDE_CODE_CLI);
2533
2658
  const candidates = ["~/.local/bin/claude", "claude"];
2534
2659
  for (const candidate of candidates) {
2535
2660
  const expanded = expandHome(candidate);
2536
- if (path5.isAbsolute(expanded)) {
2661
+ if (path6.isAbsolute(expanded)) {
2537
2662
  if (isExecutableFile(expanded)) return expanded;
2538
2663
  continue;
2539
2664
  }
@@ -2542,9 +2667,7 @@ function resolveClaudeCodeCliPath(configured) {
2542
2667
  return void 0;
2543
2668
  }
2544
2669
  function expandHome(value) {
2545
- if (value === "~") return os3.homedir();
2546
- if (value.startsWith("~/")) return path5.join(os3.homedir(), value.slice(2));
2547
- return value;
2670
+ return expandPathReferences(value);
2548
2671
  }
2549
2672
  function isExecutableFile(filePath) {
2550
2673
  try {
@@ -2810,14 +2933,14 @@ function numberValue2(value) {
2810
2933
  // src/external-runtime/session-lock.ts
2811
2934
  import { mkdir as mkdir4, open, readFile as readFile4, unlink } from "node:fs/promises";
2812
2935
  import os4 from "node:os";
2813
- import path6 from "node:path";
2936
+ import path7 from "node:path";
2814
2937
  var DEFAULT_STALE_MS = 30 * 60 * 1e3;
2815
2938
  async function acquireClaudeCodeSessionLock(sessionId, options = {}) {
2816
- const dir = options.dir ?? path6.join(os4.homedir(), ".demian", "claude-sessions");
2939
+ const dir = options.dir ?? path7.join(os4.homedir(), ".demian", "claude-sessions");
2817
2940
  const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
2818
2941
  const now2 = options.now ?? (() => Date.now());
2819
2942
  await mkdir4(dir, { recursive: true });
2820
- const lockPath = path6.join(dir, `${encodeURIComponent(sessionId)}.lock`);
2943
+ const lockPath = path7.join(dir, `${encodeURIComponent(sessionId)}.lock`);
2821
2944
  for (let attempt = 0; attempt < 2; attempt++) {
2822
2945
  try {
2823
2946
  const handle = await open(lockPath, "wx");
@@ -2881,14 +3004,14 @@ function isAlreadyExists(error) {
2881
3004
  // src/external-runtime/usage-ledger.ts
2882
3005
  import { mkdir as mkdir5, readFile as readFile5, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
2883
3006
  import os5 from "node:os";
2884
- import path7 from "node:path";
3007
+ import path8 from "node:path";
2885
3008
  var processLedger = /* @__PURE__ */ new Map();
2886
3009
  var ClaudeCodeUsageLedger = class {
2887
3010
  #scope;
2888
3011
  #filePath;
2889
3012
  constructor(options = {}) {
2890
3013
  this.#scope = options.scope ?? "process";
2891
- this.#filePath = options.filePath ?? path7.join(os5.homedir(), ".demian", "usage-ledger.json");
3014
+ this.#filePath = options.filePath ?? path8.join(os5.homedir(), ".demian", "usage-ledger.json");
2892
3015
  }
2893
3016
  async spentUsd(key, now2 = /* @__PURE__ */ new Date()) {
2894
3017
  if (this.#scope === "process") return processLedger.get(processKey(key)) ?? 0;
@@ -2919,7 +3042,7 @@ var ClaudeCodeUsageLedger = class {
2919
3042
  return { version: 1, buckets: {} };
2920
3043
  }
2921
3044
  async #write(file) {
2922
- await mkdir5(path7.dirname(this.#filePath), { recursive: true });
3045
+ await mkdir5(path8.dirname(this.#filePath), { recursive: true });
2923
3046
  const temp = `${this.#filePath}.${process.pid}.tmp`;
2924
3047
  await writeFile4(temp, `${JSON.stringify(file, null, 2)}
2925
3048
  `, "utf8");
@@ -22580,10 +22703,10 @@ function now() {
22580
22703
  }
22581
22704
 
22582
22705
  // src/permissions/engine.ts
22583
- import path10 from "node:path";
22706
+ import path11 from "node:path";
22584
22707
 
22585
22708
  // src/permissions/grants.ts
22586
- import path8 from "node:path";
22709
+ import path9 from "node:path";
22587
22710
 
22588
22711
  // src/util.ts
22589
22712
  function stableStringify(value) {
@@ -22638,8 +22761,8 @@ function grantKeyFor(tool, input2, cwd) {
22638
22761
  }
22639
22762
  if (tool === "write_file" || tool === "edit_file") {
22640
22763
  const filePath = typeof object2.path === "string" ? object2.path : "";
22641
- const parent = filePath ? path8.dirname(path8.resolve(cwd, filePath)) : cwd;
22642
- return `${tool}:${path8.relative(cwd, parent) || "."}`;
22764
+ const parent = filePath ? path9.dirname(path9.resolve(cwd, filePath)) : cwd;
22765
+ return `${tool}:${path9.relative(cwd, parent) || "."}`;
22643
22766
  }
22644
22767
  return `${tool}:${stableStringify(input2)}`;
22645
22768
  }
@@ -22666,30 +22789,30 @@ function firstCommandTokens(command, count) {
22666
22789
  }
22667
22790
 
22668
22791
  // src/workspace/paths.ts
22669
- import path9 from "node:path";
22792
+ import path10 from "node:path";
22670
22793
  function normalizePathForMatch(value) {
22671
- return value.split(path9.sep).join("/");
22794
+ return value.split(path10.sep).join("/");
22672
22795
  }
22673
22796
  function isInsidePath(root, candidate) {
22674
- const relative = path9.relative(path9.resolve(root), path9.resolve(candidate));
22675
- return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
22797
+ const relative = path10.relative(path10.resolve(root), path10.resolve(candidate));
22798
+ return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
22676
22799
  }
22677
22800
  function resolveInsideCwd(cwd, inputPath) {
22678
22801
  if (typeof inputPath !== "string" || inputPath.length === 0) {
22679
22802
  throw new Error("path must be a non-empty string");
22680
22803
  }
22681
- const resolved = path9.resolve(cwd, inputPath);
22804
+ const resolved = path10.resolve(cwd, inputPath);
22682
22805
  if (!isInsidePath(cwd, resolved)) {
22683
22806
  throw new Error(`Path is outside the workspace: ${inputPath}`);
22684
22807
  }
22685
22808
  return resolved;
22686
22809
  }
22687
22810
  function relativeToCwd(cwd, absolutePath) {
22688
- const relative = path9.relative(cwd, absolutePath);
22811
+ const relative = path10.relative(cwd, absolutePath);
22689
22812
  return normalizePathForMatch(relative || ".");
22690
22813
  }
22691
22814
  function isEnvFile(filePath) {
22692
- const base = path9.basename(filePath);
22815
+ const base = path10.basename(filePath);
22693
22816
  if (base === ".env.example") return false;
22694
22817
  return base === ".env" || base.startsWith(".env.");
22695
22818
  }
@@ -22796,7 +22919,7 @@ var PermissionEngine = class {
22796
22919
  #hardDeny(tool, input2, grantKey) {
22797
22920
  const paths = inputPaths(tool, input2);
22798
22921
  for (const item of paths) {
22799
- const resolved = path10.resolve(this.#cwd, item);
22922
+ const resolved = path11.resolve(this.#cwd, item);
22800
22923
  if (!isInsidePath(this.#cwd, resolved)) {
22801
22924
  return {
22802
22925
  decision: "deny",
@@ -22850,7 +22973,7 @@ function matchesRule(rule, tool, input2, cwd) {
22850
22973
  if (rule.match.pathGlob) {
22851
22974
  const paths = inputPaths(tool, input2);
22852
22975
  if (paths.length === 0) return false;
22853
- if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path10.resolve(cwd, item))))) {
22976
+ if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path11.resolve(cwd, item))))) {
22854
22977
  return false;
22855
22978
  }
22856
22979
  }
@@ -22897,7 +23020,7 @@ function rulePriority(rule, ref) {
22897
23020
  }
22898
23021
  function matchesPathRule(pattern, relativePath) {
22899
23022
  if (!pattern) return true;
22900
- if (path10.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
23023
+ if (path11.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
22901
23024
  return matchGlob(pattern, relativePath);
22902
23025
  }
22903
23026
  function mostSpecificRule(rules, input2, cwd, ref) {
@@ -22975,13 +23098,13 @@ function permissionLabel(req) {
22975
23098
  if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
22976
23099
  return `${req.tool}: ${inputObject.path}`;
22977
23100
  }
22978
- const path27 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
22979
- if (path27) return `${tool}: ${path27}`;
23101
+ const path29 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
23102
+ if (path29) return `${tool}: ${path29}`;
22980
23103
  return tool;
22981
23104
  }
22982
23105
 
22983
23106
  // src/workspace/write-scope.ts
22984
- import path11 from "node:path";
23107
+ import path12 from "node:path";
22985
23108
  var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
22986
23109
  var WORKSPACE_SCOPE = "**";
22987
23110
  function normalizeWriteScope(cwd, scope) {
@@ -23003,7 +23126,7 @@ function enforceToolWriteScope(input2) {
23003
23126
  if (paths.length === 0) return { ok: true, paths: [] };
23004
23127
  const checked = [];
23005
23128
  for (const target of paths) {
23006
- const resolved = path11.resolve(input2.cwd, target);
23129
+ const resolved = path12.resolve(input2.cwd, target);
23007
23130
  const relative = relativeToCwd(input2.cwd, resolved);
23008
23131
  checked.push(relative);
23009
23132
  const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
@@ -23027,7 +23150,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
23027
23150
  return out;
23028
23151
  }
23029
23152
  function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
23030
- const resolved = path11.resolve(cwd, inputPath);
23153
+ const resolved = path12.resolve(cwd, inputPath);
23031
23154
  if (!isInsidePath(cwd, resolved)) {
23032
23155
  return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
23033
23156
  }
@@ -23046,9 +23169,9 @@ function normalizeScopePattern(cwd, raw) {
23046
23169
  if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
23047
23170
  if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
23048
23171
  const hasGlob2 = /[*?[\]{}]/.test(value);
23049
- if (path11.isAbsolute(value)) {
23172
+ if (path12.isAbsolute(value)) {
23050
23173
  if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
23051
- const resolved = path11.resolve(value);
23174
+ const resolved = path12.resolve(value);
23052
23175
  if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
23053
23176
  value = relativeToCwd(cwd, resolved);
23054
23177
  }
@@ -23425,6 +23548,7 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
23425
23548
  }
23426
23549
 
23427
23550
  // src/config.ts
23551
+ var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
23428
23552
  var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
23429
23553
  var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
23430
23554
  var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
@@ -23462,7 +23586,7 @@ var defaultConfig = {
23462
23586
  maxConcurrentExpensive: 1,
23463
23587
  maxConcurrentWriters: 1,
23464
23588
  maxConcurrentPerProvider: {
23465
- "claudecode-subagent": 1
23589
+ claudecode: 1
23466
23590
  },
23467
23591
  tokenBudget: null,
23468
23592
  defaultMergeStrategy: "synthesize",
@@ -23584,73 +23708,102 @@ var defaultConfig = {
23584
23708
  providers: {
23585
23709
  openai: {
23586
23710
  type: "openai-compatible",
23587
- model: "openai-model-name",
23588
23711
  baseURL: "https://api.openai.com/v1",
23589
- apiKeyEnv: "OPENAI_API_KEY"
23712
+ apiKeyEnv: "OPENAI_API_KEY",
23713
+ catalog: {
23714
+ type: "openai-models",
23715
+ endpoint: "https://api.openai.com/v1/models"
23716
+ }
23717
+ },
23718
+ anthropic: {
23719
+ type: "anthropic",
23720
+ baseURL: "https://api.anthropic.com/v1",
23721
+ apiKeyEnv: "ANTHROPIC_API_KEY",
23722
+ catalog: {
23723
+ type: "anthropic-models",
23724
+ endpoint: "https://api.anthropic.com/v1/models"
23725
+ }
23590
23726
  },
23591
23727
  gemini: {
23592
23728
  type: "openai-compatible",
23593
- model: "gemini-model-name",
23594
23729
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
23595
- apiKeyEnv: "GOOGLE_API_KEY"
23730
+ apiKeyEnv: "GEMINI_API_KEY",
23731
+ apiKeyEnvAliases: ["GOOGLE_API_KEY"],
23732
+ catalog: {
23733
+ type: "gemini-openai-models",
23734
+ endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models"
23735
+ }
23596
23736
  },
23597
- ollama: {
23737
+ groq: {
23598
23738
  type: "openai-compatible",
23599
- model: "local-coder-model",
23600
- baseURL: "http://localhost:11434/v1",
23601
- apiKey: "ollama",
23602
- quirks: { omitTemperature: true }
23739
+ baseURL: "https://api.groq.com/openai/v1",
23740
+ apiKeyEnv: "GROQ_API_KEY",
23741
+ catalog: {
23742
+ type: "groq-models",
23743
+ endpoint: "https://api.groq.com/openai/v1/models"
23744
+ }
23745
+ },
23746
+ azure: {
23747
+ type: "openai-compatible",
23748
+ auth: { type: "api-key", header: "api-key" },
23749
+ modelProfiles: [
23750
+ {
23751
+ name: "azure-example",
23752
+ displayName: "Azure example",
23753
+ model: "azure-deployment-name",
23754
+ baseURL: "https://example.openai.azure.com/openai/v1",
23755
+ apiKeyEnv: "AZURE_OPENAI_API_KEY"
23756
+ }
23757
+ ]
23603
23758
  },
23604
23759
  lmstudio: {
23605
23760
  type: "openai-compatible",
23606
- model: "local-coder-model",
23607
23761
  baseURL: "http://localhost:1234/v1",
23608
- apiKey: "lm-studio"
23762
+ apiKey: "lm-studio",
23763
+ catalog: {
23764
+ type: "openai-compatible-models",
23765
+ endpoint: "http://localhost:1234/v1/models"
23766
+ }
23609
23767
  },
23610
- vllm: {
23768
+ "ollama-local": {
23611
23769
  type: "openai-compatible",
23612
- model: "local-coder-model",
23613
- baseURL: "http://localhost:8000/v1",
23614
- apiKey: "vllm"
23770
+ baseURL: "http://localhost:11434/v1",
23771
+ apiKey: "ollama",
23772
+ quirks: { omitTemperature: true },
23773
+ catalog: {
23774
+ type: "openai-compatible-models",
23775
+ endpoint: "http://localhost:11434/v1/models"
23776
+ }
23777
+ },
23778
+ "ollama-cloud": {
23779
+ type: "ollama",
23780
+ baseURL: "https://ollama.com/api",
23781
+ apiKeyEnv: "OLLAMA_API_KEY",
23782
+ catalog: {
23783
+ type: "ollama-tags",
23784
+ endpoint: "https://ollama.com/api/tags"
23785
+ }
23615
23786
  },
23616
23787
  llamacpp: {
23617
23788
  type: "openai-compatible",
23618
- model: "local-coder-model",
23619
23789
  baseURL: "http://localhost:8080/v1",
23620
- apiKey: "llama.cpp"
23621
- },
23622
- openrouter: {
23623
- type: "openai-compatible",
23624
- model: "provider/model-name",
23625
- baseURL: "https://openrouter.ai/api/v1",
23626
- apiKeyEnv: "OPENROUTER_API_KEY"
23627
- },
23628
- together: {
23629
- type: "openai-compatible",
23630
- model: "provider/model-name",
23631
- baseURL: "https://api.together.xyz/v1",
23632
- apiKeyEnv: "TOGETHER_API_KEY"
23633
- },
23634
- groq: {
23635
- type: "openai-compatible",
23636
- model: "provider/model-name",
23637
- baseURL: "https://api.groq.com/openai/v1",
23638
- apiKeyEnv: "GROQ_API_KEY"
23790
+ apiKey: "llama.cpp",
23791
+ catalog: {
23792
+ type: "openai-compatible-models",
23793
+ endpoint: "http://localhost:8080/v1/models"
23794
+ }
23639
23795
  },
23640
- azure: {
23796
+ vllm: {
23641
23797
  type: "openai-compatible",
23642
- model: "azure-deployment-name",
23643
- baseURL: "https://example.openai.azure.com/openai/v1",
23644
- apiKeyEnv: "AZURE_OPENAI_API_KEY"
23645
- },
23646
- anthropic: {
23647
- type: "anthropic",
23648
- model: "anthropic-model-name",
23649
- apiKeyEnv: "ANTHROPIC_API_KEY"
23798
+ baseURL: "http://localhost:8000/v1",
23799
+ apiKey: "vllm",
23800
+ catalog: {
23801
+ type: "openai-compatible-models",
23802
+ endpoint: "http://localhost:8000/v1/models"
23803
+ }
23650
23804
  },
23651
23805
  codex: {
23652
23806
  type: "codex",
23653
- model: "gpt-5.1-codex",
23654
23807
  baseURL: "https://chatgpt.com/backend-api/codex",
23655
23808
  authStore: "auto",
23656
23809
  allowApiKeyFallback: false,
@@ -23662,6 +23815,9 @@ var defaultConfig = {
23662
23815
  effort: "medium",
23663
23816
  summary: "auto"
23664
23817
  }
23818
+ },
23819
+ catalog: {
23820
+ type: "codex-oauth-models"
23665
23821
  }
23666
23822
  },
23667
23823
  claudecode: {
@@ -23680,67 +23836,37 @@ var defaultConfig = {
23680
23836
  useBareMode: false,
23681
23837
  usageLedgerScope: "process",
23682
23838
  sessionLock: true,
23683
- abortPolicy: "record-only"
23684
- },
23685
- "claudecode-plan": {
23686
- type: "claudecode",
23687
- runtime: "agent-sdk",
23688
- model: CLAUDE_CODE_SONNET_MODEL,
23689
- models: [...CLAUDE_CODE_MODELS],
23690
- permissionProfile: "plan",
23691
- cliPath: "~/.local/bin/claude",
23692
- cwdMode: "session",
23693
- historyPolicy: "passthrough-resume",
23694
- onInvalidResume: "fresh",
23695
- attachmentFallback: "block",
23696
- allowSubagents: false,
23697
- sanitizeApiKeyEnv: true,
23698
- authPreflight: true,
23699
- useBareMode: false,
23700
- usageLedgerScope: "process",
23701
- sessionLock: true,
23702
- abortPolicy: "record-only",
23703
- hidden: true
23704
- },
23705
- "claudecode-subagent": {
23706
- type: "claudecode",
23707
- runtime: "agent-sdk",
23708
- model: CLAUDE_CODE_SONNET_MODEL,
23709
- models: [...CLAUDE_CODE_MODELS],
23710
- permissionProfile: "build",
23711
- cliPath: "~/.local/bin/claude",
23712
- cwdMode: "session",
23713
- historyPolicy: "passthrough-resume",
23714
- onInvalidResume: "fresh",
23715
- attachmentFallback: "block",
23716
- allowSubagents: false,
23717
- sanitizeApiKeyEnv: true,
23718
- authPreflight: true,
23719
- useBareMode: false,
23720
- usageLedgerScope: "process",
23721
- sessionLock: true,
23722
23839
  abortPolicy: "record-only",
23723
- hidden: true
23840
+ catalog: {
23841
+ type: "claudecode-supported-models"
23842
+ }
23724
23843
  }
23725
23844
  }
23726
23845
  };
23727
23846
  async function loadConfig(options) {
23728
23847
  const configs = [];
23729
- const userConfigDir = path12.join(os7.homedir(), ".demian");
23730
- const projectConfigDir = path12.join(options.cwd, ".demian");
23731
- const configPaths = [
23732
- path12.join(userConfigDir, "config.json"),
23733
- path12.join(userConfigDir, "config.jsond"),
23734
- path12.join(projectConfigDir, "config.json"),
23735
- path12.join(projectConfigDir, "config.jsond"),
23736
- options.configPath
23737
- ].filter(Boolean);
23848
+ const userConfigDir = path13.join(os7.homedir(), ".demian");
23849
+ const configPaths = [path13.join(userConfigDir, "config.json"), path13.join(userConfigDir, "config.jsond"), options.configPath].filter(Boolean);
23738
23850
  for (const filePath of configPaths) {
23739
23851
  if (!fs7.existsSync(filePath)) continue;
23740
- configs.push(parseConfigText(await readFile6(filePath, "utf8"), filePath));
23852
+ const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
23853
+ warnIfV1Config(parsed, filePath);
23854
+ configs.push(parsed);
23741
23855
  }
23742
23856
  return configs.reduce((acc, item) => mergeConfig(acc, item), structuredClone(defaultConfig));
23743
23857
  }
23858
+ function warnIfV1Config(parsed, filePath) {
23859
+ if (parsed.version === 2) return;
23860
+ const providers = parsed.providers ?? {};
23861
+ const hasLegacyShape = Object.values(providers).some((provider) => {
23862
+ const candidate = provider;
23863
+ return (typeof candidate.model === "string" || Array.isArray(candidate.models)) && !Array.isArray(candidate.modelProfiles);
23864
+ });
23865
+ if (!hasLegacyShape && parsed.version === void 0 && Object.keys(providers).length === 0) return;
23866
+ if (!hasLegacyShape) return;
23867
+ process.stderr.write(`[demian] warning: ${filePath} uses the v1 schema (provider.model/models). v2 expects modelProfiles. Run \`demian config init\` to scaffold a v2 template.
23868
+ `);
23869
+ }
23744
23870
  function parseConfigText(text, filePath) {
23745
23871
  try {
23746
23872
  return JSON.parse(filePath.endsWith(".jsond") ? stripJsonD(text) : text);
@@ -23947,7 +24073,168 @@ function mergeConfig(base, overlay) {
23947
24073
  };
23948
24074
  }
23949
24075
  function normalizeProviderAlias(providerName) {
23950
- return providerName === "claudecode-plan" ? "claudecode" : providerName;
24076
+ if (providerName === "claudecode-plan" || providerName === "claudecode-subagent") return "claudecode";
24077
+ if (providerName === "ollama") return "ollama-local";
24078
+ return providerName;
24079
+ }
24080
+ function sortProviderNames(names) {
24081
+ const order = new Map(BUILTIN_PROVIDER_ORDER.map((name, index) => [name, index]));
24082
+ return [...names].sort((left, right) => {
24083
+ const leftIndex = order.get(left);
24084
+ const rightIndex = order.get(right);
24085
+ if (leftIndex !== void 0 && rightIndex !== void 0) return leftIndex - rightIndex;
24086
+ if (leftIndex !== void 0) return -1;
24087
+ if (rightIndex !== void 0) return 1;
24088
+ return left.localeCompare(right);
24089
+ });
24090
+ }
24091
+ function providerModelProfiles(providerName, providerConfig) {
24092
+ const explicit = Array.isArray(providerConfig.modelProfiles) ? providerConfig.modelProfiles.filter((profile) => typeof profile?.model === "string" && profile.model.trim()).map((profile) => ({
24093
+ ...profile,
24094
+ name: profile.name?.trim() || profile.displayName?.trim() || profile.model.trim(),
24095
+ displayName: profile.displayName?.trim() || profile.name?.trim() || profile.model.trim(),
24096
+ model: profile.model.trim()
24097
+ })) : [];
24098
+ const seenNames = /* @__PURE__ */ new Set();
24099
+ const seenDisplayNames = /* @__PURE__ */ new Set();
24100
+ const seenModels = /* @__PURE__ */ new Set();
24101
+ const out = [];
24102
+ for (const profile of explicit) {
24103
+ if (seenNames.has(profile.name)) {
24104
+ process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile name "${profile.name}". Later entry ignored.
24105
+ `);
24106
+ continue;
24107
+ }
24108
+ if (profile.displayName && seenDisplayNames.has(profile.displayName)) {
24109
+ process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile displayName "${profile.displayName}". Later entry ignored.
24110
+ `);
24111
+ continue;
24112
+ }
24113
+ seenNames.add(profile.name);
24114
+ if (profile.displayName) seenDisplayNames.add(profile.displayName);
24115
+ seenModels.add(profile.model);
24116
+ out.push(profile);
24117
+ }
24118
+ const legacy = [...typeof providerConfig.model === "string" && providerConfig.model.trim() ? [providerConfig.model.trim()] : [], ...(providerConfig.models ?? []).filter((model) => typeof model === "string" && model.trim())];
24119
+ for (const model of legacy) {
24120
+ const trimmed = model.trim();
24121
+ if (!trimmed || seenModels.has(trimmed)) continue;
24122
+ seenModels.add(trimmed);
24123
+ const name = trimmed;
24124
+ if (!seenNames.has(name)) seenNames.add(name);
24125
+ out.push({ name, displayName: name, model: trimmed });
24126
+ }
24127
+ const fallback = fallbackModelForProvider(providerName, providerConfig);
24128
+ if (out.length === 0 && fallback) out.push({ name: fallback, displayName: fallback, model: fallback });
24129
+ return out;
24130
+ }
24131
+ function providerModelOptions(providerName, providerConfig) {
24132
+ return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
24133
+ }
24134
+ function defaultModelForProvider(providerName, providerConfig) {
24135
+ return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
24136
+ }
24137
+ function resolveConfiguredApiKey(providerConfig) {
24138
+ if (providerConfig.apiKey) return { value: providerConfig.apiKey };
24139
+ for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
24140
+ if (!envName) continue;
24141
+ const value = process.env[envName];
24142
+ if (value) return { value, envName };
24143
+ }
24144
+ return { envName: providerConfig.apiKeyEnv ?? providerConfig.apiKeyEnvAliases?.[0] };
24145
+ }
24146
+ function resolveProviderRuntimeConfig(config, selection) {
24147
+ const providerName = normalizeProviderAlias(selection.providerName);
24148
+ const providerConfig = resolveProviderConfig(config, providerName);
24149
+ const profiles = providerModelProfiles(providerName, providerConfig);
24150
+ let profile;
24151
+ if (selection.modelProfileName) profile = profiles.find((item) => item.name === selection.modelProfileName);
24152
+ if (!profile && selection.model) profile = profiles.find((item) => item.displayName === selection.model);
24153
+ if (!profile && selection.model) profile = profiles.find((item) => item.model === selection.model);
24154
+ if (!profile) profile = profiles[0];
24155
+ if (!profile) {
24156
+ const model = selection.model ?? defaultModelForProvider(providerName, providerConfig);
24157
+ if (!model) throw new Error(`Provider ${providerName} has no model. Run \`demian config models ${providerName} --refresh\` or add a model profile.`);
24158
+ return { providerName, providerConfig, model };
24159
+ }
24160
+ const merged = mergeModelProfile(providerConfig, profile);
24161
+ return {
24162
+ providerName,
24163
+ providerConfig: merged,
24164
+ model: profile.model,
24165
+ modelProfileName: profile.name
24166
+ };
24167
+ }
24168
+ function resolveInternalAgentBackend(config, options = {}) {
24169
+ const tried = [];
24170
+ const candidates = uniqueProviderCandidates([options.preferredProvider, config.defaultProvider, ...Object.keys(config.providers)]);
24171
+ let lastError;
24172
+ for (const candidate of candidates) {
24173
+ tried.push(candidate);
24174
+ const provider = config.providers[normalizeProviderAlias(candidate)];
24175
+ if (!provider || provider.hidden) continue;
24176
+ try {
24177
+ const runtime = resolveProviderRuntimeConfig(config, {
24178
+ providerName: candidate,
24179
+ modelProfileName: candidate === options.preferredProvider ? options.modelProfileName : void 0,
24180
+ model: candidate === options.preferredProvider ? options.model : void 0
24181
+ });
24182
+ const source = candidate === options.preferredProvider ? "preferred" : candidate === config.defaultProvider ? "default" : "first-available";
24183
+ return {
24184
+ providerName: runtime.providerName,
24185
+ providerConfig: runtime.providerConfig,
24186
+ model: runtime.model,
24187
+ modelProfileName: runtime.modelProfileName,
24188
+ source,
24189
+ fallbackReason: source !== "preferred" && options.preferredProvider ? `preferred provider ${options.preferredProvider} unavailable` : void 0
24190
+ };
24191
+ } catch (error) {
24192
+ lastError = error instanceof Error ? error : new Error(String(error));
24193
+ }
24194
+ }
24195
+ throw new Error(`No usable provider for internal agent. Tried: ${tried.join(", ") || "(none)"}. Last error: ${lastError?.message ?? "no providers configured"}.`);
24196
+ }
24197
+ function uniqueProviderCandidates(values) {
24198
+ const seen = /* @__PURE__ */ new Set();
24199
+ const out = [];
24200
+ for (const value of values) {
24201
+ if (!value) continue;
24202
+ const normalized = normalizeProviderAlias(value);
24203
+ if (seen.has(normalized)) continue;
24204
+ seen.add(normalized);
24205
+ out.push(normalized);
24206
+ }
24207
+ return out;
24208
+ }
24209
+ function mergeModelProfile(providerConfig, profile) {
24210
+ const merged = {
24211
+ ...providerConfig,
24212
+ ...definedOnly({
24213
+ baseURL: profile.baseURL,
24214
+ apiKey: profile.apiKey,
24215
+ apiKeyEnv: profile.apiKeyEnv,
24216
+ apiKeyEnvAliases: profile.apiKeyEnvAliases,
24217
+ auth: profile.auth,
24218
+ headers: profile.headers,
24219
+ quirks: profile.quirks,
24220
+ maxTokens: profile.maxTokens
24221
+ }),
24222
+ model: profile.model
24223
+ };
24224
+ return merged;
24225
+ }
24226
+ function definedOnly(input2) {
24227
+ return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
24228
+ }
24229
+ function fallbackModelForProvider(providerName, providerConfig) {
24230
+ if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
24231
+ if (providerName === "openai") return "gpt-5.5";
24232
+ if (providerName === "anthropic") return CLAUDE_CODE_SONNET_MODEL;
24233
+ if (providerName === "codex" || providerConfig.type === "codex") return "gpt-5.5";
24234
+ if (providerName === "claudecode") return CLAUDE_CODE_SONNET_MODEL;
24235
+ if (providerName === "gemini") return "gemini-2.5-pro";
24236
+ if (providerName === "groq") return "openai/gpt-oss-120b";
24237
+ return void 0;
23951
24238
  }
23952
24239
  function migrateProviderConfig(provider, baseProvider) {
23953
24240
  if (isLegacyClaudeCodeShape(provider)) {
@@ -24029,12 +24316,14 @@ function resolveAgentMode(config, flagMode) {
24029
24316
  return "single-agent";
24030
24317
  }
24031
24318
  function resolveProviderConfig(config, providerName) {
24032
- const provider = config.providers[providerName];
24319
+ const normalized = normalizeProviderAlias(providerName);
24320
+ const provider = config.providers[normalized];
24033
24321
  if (!provider) throw new Error(`Unknown provider: ${providerName}`);
24034
24322
  return provider;
24035
24323
  }
24036
24324
  function resolveProvider(providerConfig, options = {}) {
24037
24325
  const model = options.model ?? providerConfig.model;
24326
+ if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
24038
24327
  if (providerConfig.type === "claudecode") {
24039
24328
  throw new Error("claudecode is an external agent runtime. Use resolveExecutionBackend().");
24040
24329
  }
@@ -24081,20 +24370,36 @@ function resolveProvider(providerConfig, options = {}) {
24081
24370
  };
24082
24371
  }
24083
24372
  if (providerConfig.type === "anthropic") {
24084
- const apiKey2 = providerConfig.apiKey ?? (providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0);
24373
+ const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
24085
24374
  if (!apiKey2) {
24086
24375
  throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
24087
24376
  }
24088
24377
  return {
24089
24378
  provider: new AnthropicProvider({
24090
24379
  apiKey: apiKey2,
24380
+ baseURL: providerConfig.baseURL,
24091
24381
  defaultModel: model,
24092
24382
  onRetry: options.onRetry
24093
24383
  }),
24094
24384
  model
24095
24385
  };
24096
24386
  }
24097
- const apiKey = providerConfig.apiKey ?? (providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0);
24387
+ if (providerConfig.type === "ollama") {
24388
+ const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
24389
+ if (!apiKey2) {
24390
+ throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
24391
+ }
24392
+ return {
24393
+ provider: new OllamaProvider({
24394
+ baseURL: providerConfig.baseURL,
24395
+ apiKey: apiKey2,
24396
+ defaultModel: model,
24397
+ onRetry: options.onRetry
24398
+ }),
24399
+ model
24400
+ };
24401
+ }
24402
+ const apiKey = resolveConfiguredApiKey(providerConfig).value;
24098
24403
  if (!apiKey) {
24099
24404
  throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
24100
24405
  }
@@ -24113,6 +24418,7 @@ function resolveProvider(providerConfig, options = {}) {
24113
24418
  }
24114
24419
  function resolveExecutionBackend(providerConfig, options = {}) {
24115
24420
  const model = options.model ?? providerConfig.model;
24421
+ if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
24116
24422
  if (providerConfig.type === "claudecode") {
24117
24423
  const runtimeConfig = options.config || providerConfig.permissionProfile ? resolveClaudeCodeRuntimeConfig(providerConfig, options.config ?? defaultConfig, options.agent) : providerConfig;
24118
24424
  return {
@@ -24321,7 +24627,7 @@ function safeJson(value) {
24321
24627
  }
24322
24628
 
24323
24629
  // src/hooks/dispatcher.ts
24324
- import path14 from "node:path";
24630
+ import path15 from "node:path";
24325
24631
 
24326
24632
  // src/hooks/command.ts
24327
24633
  import { spawn as spawn4 } from "node:child_process";
@@ -24414,7 +24720,7 @@ var blockDangerousBashHook = {
24414
24720
  };
24415
24721
 
24416
24722
  // src/hooks/builtin/protect-env-files.ts
24417
- import path13 from "node:path";
24723
+ import path14 from "node:path";
24418
24724
  var protectEnvFilesHook = {
24419
24725
  name: "protect-env-files",
24420
24726
  event: "PreToolUse",
@@ -24422,7 +24728,7 @@ var protectEnvFilesHook = {
24422
24728
  run(ctx) {
24423
24729
  if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
24424
24730
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
24425
- const filePath = typeof input2.path === "string" ? path13.resolve(ctx.cwd, input2.path) : "";
24731
+ const filePath = typeof input2.path === "string" ? path14.resolve(ctx.cwd, input2.path) : "";
24426
24732
  if (filePath && isEnvFile(filePath)) {
24427
24733
  return {
24428
24734
  decision: "block",
@@ -24537,14 +24843,426 @@ function matchesHook(match, ctx) {
24537
24843
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
24538
24844
  const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
24539
24845
  if (!filePath) return false;
24540
- if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path14.resolve(ctx.cwd, filePath)))) return false;
24846
+ if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path15.resolve(ctx.cwd, filePath)))) return false;
24541
24847
  }
24542
24848
  return true;
24543
24849
  }
24544
24850
 
24851
+ // src/models/catalog.ts
24852
+ import crypto5 from "node:crypto";
24853
+ import { mkdir as mkdir6, readFile as readFile7, rename as rename4, writeFile as writeFile5 } from "node:fs/promises";
24854
+ import os9 from "node:os";
24855
+ import path16 from "node:path";
24856
+ var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
24857
+ var PING_TIMEOUT_MS = 1500;
24858
+ var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
24859
+ var dynamicImport2 = new Function("specifier", "return import(specifier)");
24860
+ var inflight = /* @__PURE__ */ new Map();
24861
+ var pingCache = /* @__PURE__ */ new Map();
24862
+ var PING_CACHE_TTL_MS = 30 * 1e3;
24863
+ var packageVersionPromise;
24864
+ async function listProviderModelCatalog(providerName, providerConfig, options = {}) {
24865
+ const key = `${catalogCacheKey(providerName, providerConfig)}:${options.refresh ? "refresh" : "default"}`;
24866
+ const existing = inflight.get(key);
24867
+ if (existing) return existing;
24868
+ const promise = doListProviderModelCatalog(providerName, providerConfig, options).finally(() => inflight.delete(key));
24869
+ inflight.set(key, promise);
24870
+ return promise;
24871
+ }
24872
+ async function doListProviderModelCatalog(providerName, providerConfig, options) {
24873
+ const staticModels = staticModelEntries(providerName, providerConfig);
24874
+ const catalog = providerConfig.catalog;
24875
+ if (!catalog || catalog.type === "static") {
24876
+ return { status: staticModels.length ? "ready" : "unavailable", providerName, models: staticModels, source: "config" };
24877
+ }
24878
+ const missingAuthEnv = missingCatalogAuth(providerConfig);
24879
+ if (missingAuthEnv) {
24880
+ return {
24881
+ status: "missing-auth",
24882
+ providerName,
24883
+ models: staticModels,
24884
+ source: staticModels.length ? "config" : "fallback",
24885
+ message: `missing-auth:${missingAuthEnv}`
24886
+ };
24887
+ }
24888
+ const cacheKey = catalogCacheKey(providerName, providerConfig);
24889
+ const ttlMs = catalog.refreshTtlMs ?? DEFAULT_CACHE_TTL_MS;
24890
+ const cached = await readCatalogCache(cacheKey, ttlMs, options.refresh !== true);
24891
+ if (cached && !options.refresh) return { ...cached, models: mergeCatalogWithStatic(staticModels, cached.models) };
24892
+ if (catalog.endpoint) {
24893
+ const reachable = await pingEndpoint(catalog.endpoint, options.fetch);
24894
+ if (!reachable) {
24895
+ const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
24896
+ if (stale) return { ...stale, models: mergeCatalogWithStatic(staticModels, stale.models), message: `endpoint ${catalog.endpoint} unreachable; using cached models` };
24897
+ if (staticModels.length) return { status: "ready", providerName, models: staticModels, source: "config", message: `endpoint ${catalog.endpoint} unreachable; using configured profiles` };
24898
+ return { status: "unavailable", providerName, models: [], source: "fallback", message: `endpoint ${catalog.endpoint} unreachable` };
24899
+ }
24900
+ }
24901
+ try {
24902
+ const remote = await fetchCatalog(providerName, providerConfig, options);
24903
+ const merged = mergeCatalogWithStatic(staticModels, remote.models);
24904
+ const result = { ...remote, models: merged };
24905
+ if (result.status === "ready") await writeCatalogCache(cacheKey, result);
24906
+ return result;
24907
+ } catch (error) {
24908
+ const authMissing = isMissingAuthError(error);
24909
+ const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
24910
+ if (stale) return { ...stale, status: authMissing ? "missing-auth" : stale.status, models: mergeCatalogWithStatic(staticModels, stale.models), message: errorMessage2(error) };
24911
+ if (staticModels.length) return { status: authMissing ? "missing-auth" : "ready", providerName, models: staticModels, source: "config", message: errorMessage2(error) };
24912
+ return { status: authMissing ? "missing-auth" : "unavailable", providerName, models: [], source: "fallback", message: errorMessage2(error) };
24913
+ }
24914
+ }
24915
+ async function pingEndpoint(endpoint, fetcher) {
24916
+ const now2 = Date.now();
24917
+ const cached = pingCache.get(endpoint);
24918
+ if (cached && cached.expiresAt > now2) return cached.ok;
24919
+ const ok2 = await tryPing(endpoint, fetcher);
24920
+ pingCache.set(endpoint, { ok: ok2, expiresAt: now2 + PING_CACHE_TTL_MS });
24921
+ return ok2;
24922
+ }
24923
+ async function tryPing(endpoint, fetcher) {
24924
+ const fn = fetcher ?? fetch;
24925
+ try {
24926
+ const response = await fn(endpoint, { method: "HEAD", signal: AbortSignal.timeout(PING_TIMEOUT_MS) });
24927
+ if (response.status < 500) return true;
24928
+ } catch {
24929
+ }
24930
+ try {
24931
+ const response = await fn(endpoint, { method: "GET", signal: AbortSignal.timeout(PING_TIMEOUT_MS), headers: { range: "bytes=0-0" } });
24932
+ return response.status < 500;
24933
+ } catch {
24934
+ return false;
24935
+ }
24936
+ }
24937
+ function invalidateCatalogPingCache(endpoint) {
24938
+ if (endpoint) pingCache.delete(endpoint);
24939
+ else pingCache.clear();
24940
+ }
24941
+ async function fetchCatalog(providerName, providerConfig, options) {
24942
+ if (providerConfig.type === "codex" || providerConfig.catalog?.type === "codex-oauth-models") return fetchCodexCatalog(providerName, providerConfig, options);
24943
+ if (providerConfig.type === "claudecode" || providerConfig.catalog?.type === "claudecode-supported-models") return fetchClaudeCodeCatalog(providerName, providerConfig, options);
24944
+ if (providerConfig.catalog?.type === "anthropic-models") return fetchAnthropicCatalog(providerName, providerConfig, options);
24945
+ if (providerConfig.catalog?.type === "ollama-tags") return fetchOllamaTagsCatalog(providerName, providerConfig, options);
24946
+ return fetchOpenAIStyleCatalog(providerName, providerConfig, options);
24947
+ }
24948
+ async function fetchOpenAIStyleCatalog(providerName, providerConfig, options) {
24949
+ if (providerConfig.type !== "openai-compatible") throw new Error(`Provider ${providerName} is not OpenAI-compatible.`);
24950
+ const apiKey = resolveConfiguredApiKey(providerConfig);
24951
+ if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
24952
+ const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/models`;
24953
+ const response = await fetchJson(endpoint, {
24954
+ fetcher: options.fetch,
24955
+ timeoutMs: options.timeoutMs,
24956
+ headers: {
24957
+ ...authHeadersForOpenAICompatible(providerConfig, apiKey.value),
24958
+ ...providerConfig.headers ?? {}
24959
+ }
24960
+ });
24961
+ return {
24962
+ status: "ready",
24963
+ providerName,
24964
+ models: normalizeOpenAIModels(response).map((entry) => ({ ...entry, source: "api" })),
24965
+ source: "api"
24966
+ };
24967
+ }
24968
+ async function fetchAnthropicCatalog(providerName, providerConfig, options) {
24969
+ if (providerConfig.type !== "anthropic") throw new Error(`Provider ${providerName} is not Anthropic.`);
24970
+ const apiKey = resolveConfiguredApiKey(providerConfig);
24971
+ if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
24972
+ const base = providerConfig.baseURL ?? "https://api.anthropic.com/v1";
24973
+ const endpoint = providerConfig.catalog?.endpoint ?? `${base.replace(/\/+$/, "")}/models`;
24974
+ const models = [];
24975
+ let afterId;
24976
+ for (; ; ) {
24977
+ const url = new URL(endpoint);
24978
+ if (afterId) url.searchParams.set("after_id", afterId);
24979
+ url.searchParams.set("limit", "100");
24980
+ const response = await fetchJson(url.toString(), {
24981
+ fetcher: options.fetch,
24982
+ timeoutMs: options.timeoutMs,
24983
+ headers: {
24984
+ "x-api-key": apiKey.value,
24985
+ "anthropic-version": "2023-06-01"
24986
+ }
24987
+ });
24988
+ const page = normalizeAnthropicModels(response);
24989
+ models.push(...page);
24990
+ if (!response.has_more || !response.last_id) break;
24991
+ afterId = response.last_id;
24992
+ }
24993
+ return { status: "ready", providerName, models, source: "api" };
24994
+ }
24995
+ async function fetchOllamaTagsCatalog(providerName, providerConfig, options) {
24996
+ if (providerConfig.type !== "ollama") throw new Error(`Provider ${providerName} is not Ollama native.`);
24997
+ const apiKey = resolveConfiguredApiKey(providerConfig);
24998
+ if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
24999
+ const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/tags`;
25000
+ const response = await fetchJson(endpoint, {
25001
+ fetcher: options.fetch,
25002
+ timeoutMs: options.timeoutMs,
25003
+ headers: { authorization: `Bearer ${apiKey.value}` }
25004
+ });
25005
+ return {
25006
+ status: "ready",
25007
+ providerName,
25008
+ models: normalizeOllamaTags(response).map((entry) => ({ ...entry, source: "api" })),
25009
+ source: "api"
25010
+ };
25011
+ }
25012
+ async function fetchCodexCatalog(providerName, providerConfig, options) {
25013
+ if (providerConfig.type !== "codex") throw new Error(`Provider ${providerName} is not Codex.`);
25014
+ const fetcher = options.fetch ?? fetch;
25015
+ const baseURL = (providerConfig.baseURL ?? "https://chatgpt.com/backend-api/codex").replace(/\/+$/, "");
25016
+ const installationId = await loadOrCreateInstallationId(resolveCodexHome(providerConfig.codexHome));
25017
+ const auth = getSharedCodexAuthStore({
25018
+ codexHome: providerConfig.codexHome,
25019
+ authStore: providerConfig.authStore,
25020
+ allowApiKeyFallback: providerConfig.allowApiKeyFallback,
25021
+ proactiveRefreshMinutes: providerConfig.refresh?.proactiveRefreshMinutes,
25022
+ refreshCache: providerConfig.refresh?.cache,
25023
+ fetch: fetcher
25024
+ });
25025
+ const headers = await auth.requestHeaders(installationId);
25026
+ const url = new URL(`${baseURL}/models`);
25027
+ url.searchParams.set("client_version", await codexClientVersion(options.clientVersion));
25028
+ const response = await fetchJson(url.toString(), {
25029
+ fetcher,
25030
+ timeoutMs: options.timeoutMs,
25031
+ headers
25032
+ });
25033
+ return {
25034
+ status: "ready",
25035
+ providerName,
25036
+ models: normalizeCodexModels(response).map((entry) => ({ ...entry, source: "oauth" })),
25037
+ source: "oauth"
25038
+ };
25039
+ }
25040
+ async function fetchClaudeCodeCatalog(providerName, providerConfig, options) {
25041
+ if (providerConfig.type !== "claudecode") throw new Error(`Provider ${providerName} is not Claude Code.`);
25042
+ const fallback = staticModelEntries(providerName, providerConfig);
25043
+ try {
25044
+ const module = await dynamicImport2("@anthropic-ai/claude-agent-sdk");
25045
+ if (!module.query) throw new Error("Claude Agent SDK query export is unavailable.");
25046
+ const query = module.query({
25047
+ prompt: "",
25048
+ options: {
25049
+ model: defaultModelForProvider(providerName, providerConfig),
25050
+ maxTurns: 0,
25051
+ pathToClaudeCodeExecutable: resolveClaudeCodeCliPath(providerConfig.cliPath),
25052
+ env: sanitizedClaudeCodeEnv(providerConfig.env, providerConfig.sanitizeApiKeyEnv ?? true)
25053
+ }
25054
+ });
25055
+ try {
25056
+ if (typeof query.supportedModels !== "function") throw new Error("Claude Agent SDK supportedModels is unavailable.");
25057
+ const models = await withTimeout(query.supportedModels(), options.timeoutMs ?? 5e3);
25058
+ return {
25059
+ status: "ready",
25060
+ providerName,
25061
+ models: models.map((model, index) => ({
25062
+ name: model.displayName || model.value,
25063
+ displayName: model.displayName || model.value,
25064
+ model: model.value,
25065
+ description: model.description,
25066
+ isDefault: index === 0,
25067
+ source: "oauth"
25068
+ })),
25069
+ source: "oauth"
25070
+ };
25071
+ } finally {
25072
+ query.close?.();
25073
+ }
25074
+ } catch (error) {
25075
+ if (fallback.length) return { status: "ready", providerName, models: fallback, source: "fallback", message: errorMessage2(error) };
25076
+ throw error;
25077
+ }
25078
+ }
25079
+ function staticModelEntries(providerName, providerConfig) {
25080
+ return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile, index) => ({
25081
+ name: profile.displayName ?? profile.name,
25082
+ displayName: profile.displayName ?? profile.name,
25083
+ model: profile.model,
25084
+ description: profile.description,
25085
+ isDefault: index === 0,
25086
+ source: "config"
25087
+ }));
25088
+ }
25089
+ function mergeCatalogWithStatic(staticModels, remoteModels) {
25090
+ const byModel = /* @__PURE__ */ new Map();
25091
+ for (const model of remoteModels) byModel.set(model.model, model);
25092
+ for (const model of staticModels) byModel.set(model.model, { ...byModel.get(model.model), ...model, source: model.source });
25093
+ return [...byModel.values()];
25094
+ }
25095
+ function normalizeOpenAIModels(raw) {
25096
+ const items = Array.isArray(raw.data) ? raw.data : Array.isArray(raw.models) ? raw.models : [];
25097
+ return items.map((item) => {
25098
+ const object2 = item;
25099
+ const id = stringValue4(object2.id) ?? stringValue4(object2.name) ?? stringValue4(object2.model);
25100
+ if (!id) return void 0;
25101
+ return {
25102
+ name: id,
25103
+ displayName: stringValue4(object2.display_name) ?? stringValue4(object2.displayName) ?? id,
25104
+ model: id,
25105
+ description: stringValue4(object2.description)
25106
+ };
25107
+ }).filter(Boolean);
25108
+ }
25109
+ function normalizeAnthropicModels(raw) {
25110
+ return (raw.data ?? []).map((item) => {
25111
+ const object2 = item;
25112
+ const id = stringValue4(object2.id);
25113
+ if (!id) return void 0;
25114
+ return {
25115
+ name: stringValue4(object2.display_name) ?? id,
25116
+ displayName: stringValue4(object2.display_name) ?? id,
25117
+ model: id,
25118
+ description: stringValue4(object2.description),
25119
+ source: "api"
25120
+ };
25121
+ }).filter(Boolean);
25122
+ }
25123
+ function normalizeOllamaTags(raw) {
25124
+ const items = Array.isArray(raw.models) ? raw.models : [];
25125
+ return items.map((item) => {
25126
+ const object2 = item;
25127
+ const id = stringValue4(object2.name) ?? stringValue4(object2.model);
25128
+ if (!id) return void 0;
25129
+ return { name: id, displayName: id, model: id };
25130
+ }).filter(Boolean);
25131
+ }
25132
+ function normalizeCodexModels(raw) {
25133
+ const items = Array.isArray(raw.models) ? raw.models : [];
25134
+ return items.map((item, index) => {
25135
+ const object2 = item;
25136
+ const id = stringValue4(object2.slug) ?? stringValue4(object2.id) ?? stringValue4(object2.model);
25137
+ if (!id) return void 0;
25138
+ return {
25139
+ name: stringValue4(object2.display_name) ?? id,
25140
+ displayName: stringValue4(object2.display_name) ?? id,
25141
+ model: id,
25142
+ description: stringValue4(object2.description),
25143
+ isDefault: index === 0
25144
+ };
25145
+ }).filter(Boolean);
25146
+ }
25147
+ function authHeadersForOpenAICompatible(providerConfig, apiKey) {
25148
+ const auth = inferOpenAICompatibleAuth(providerConfig.baseURL, providerConfig.auth);
25149
+ const type = auth.type ?? "bearer";
25150
+ const header = auth.header ?? (type === "api-key" ? "api-key" : "authorization");
25151
+ return { [header]: type === "bearer" ? `Bearer ${apiKey}` : apiKey };
25152
+ }
25153
+ async function fetchJson(url, options) {
25154
+ const response = await (options.fetcher ?? fetch)(url, {
25155
+ method: "GET",
25156
+ headers: {
25157
+ accept: "application/json",
25158
+ ...options.headers ?? {}
25159
+ },
25160
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
25161
+ });
25162
+ const text = await response.text();
25163
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
25164
+ return text ? JSON.parse(text) : {};
25165
+ }
25166
+ function cachePath(cacheKey) {
25167
+ return path16.join(os9.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
25168
+ }
25169
+ async function readCatalogCache(cacheKey, ttlMs, allow) {
25170
+ if (!allow) return void 0;
25171
+ try {
25172
+ const raw = JSON.parse(await readFile7(cachePath(cacheKey), "utf8"));
25173
+ if (!raw.result) return void 0;
25174
+ if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
25175
+ return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
25176
+ } catch {
25177
+ return void 0;
25178
+ }
25179
+ }
25180
+ async function writeCatalogCache(cacheKey, result) {
25181
+ const filePath = cachePath(cacheKey);
25182
+ await mkdir6(path16.dirname(filePath), { recursive: true, mode: 448 });
25183
+ const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
25184
+ await writeFile5(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
25185
+ await rename4(temp, filePath);
25186
+ }
25187
+ function safeCacheName(providerName) {
25188
+ return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
25189
+ }
25190
+ function catalogCacheKey(providerName, providerConfig) {
25191
+ const identity = {
25192
+ providerName,
25193
+ type: providerConfig.type,
25194
+ catalogType: providerConfig.catalog?.type,
25195
+ endpoint: providerConfig.catalog?.endpoint,
25196
+ baseURL: "baseURL" in providerConfig ? providerConfig.baseURL : void 0,
25197
+ apiKeyEnv: "apiKeyEnv" in providerConfig ? providerConfig.apiKeyEnv : void 0,
25198
+ apiKeyEnvAliases: "apiKeyEnvAliases" in providerConfig ? providerConfig.apiKeyEnvAliases : void 0,
25199
+ auth: "auth" in providerConfig ? providerConfig.auth : void 0
25200
+ };
25201
+ const digest = crypto5.createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16);
25202
+ return `${providerName}-${digest}`;
25203
+ }
25204
+ function missingCatalogAuth(providerConfig) {
25205
+ if (providerConfig.type !== "openai-compatible" && providerConfig.type !== "anthropic" && providerConfig.type !== "ollama") return void 0;
25206
+ if (providerConfig.catalog?.requiresAuth === false) return void 0;
25207
+ const expectsAuth = providerConfig.catalog?.requiresAuth === true || !!providerConfig.apiKey || !!providerConfig.apiKeyEnv || !!providerConfig.apiKeyEnvAliases?.length;
25208
+ if (!expectsAuth) return void 0;
25209
+ const apiKey = resolveConfiguredApiKey(providerConfig);
25210
+ return apiKey.value ? void 0 : apiKey.envName ?? "apiKey";
25211
+ }
25212
+ function stringValue4(value) {
25213
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
25214
+ }
25215
+ function missingAuth(envName) {
25216
+ const error = new Error(`missing-auth:${envName}`);
25217
+ return error;
25218
+ }
25219
+ function isMissingAuthError(error) {
25220
+ if (error instanceof CodexAuthError) {
25221
+ return error.code === "missing_auth" || error.code === "missing_chatgpt_tokens" || error.code === "refresh_unavailable" || error.code === "keyring_write_unsupported" || error.code === "refresh_expired" || error.code === "refresh_invalidated" || error.code === "refresh_reused" || error.code === "refresh_other";
25222
+ }
25223
+ return error instanceof Error && error.message.startsWith("missing-auth:");
25224
+ }
25225
+ function errorMessage2(error) {
25226
+ return error instanceof Error ? error.message : String(error);
25227
+ }
25228
+ async function codexClientVersion(override) {
25229
+ const normalized = semverLike(override);
25230
+ if (normalized) return normalized;
25231
+ packageVersionPromise ??= readPackageVersion();
25232
+ return packageVersionPromise;
25233
+ }
25234
+ async function readPackageVersion() {
25235
+ try {
25236
+ const raw = JSON.parse(await readFile7(new URL("../package.json", import.meta.url), "utf8"));
25237
+ return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
25238
+ } catch {
25239
+ return DEFAULT_CODEX_CLIENT_VERSION;
25240
+ }
25241
+ }
25242
+ function semverLike(value) {
25243
+ if (typeof value !== "string") return void 0;
25244
+ const trimmed = value.trim();
25245
+ return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed) ? trimmed : void 0;
25246
+ }
25247
+ function withTimeout(promise, timeoutMs) {
25248
+ return new Promise((resolve, reject) => {
25249
+ const timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
25250
+ promise.then(
25251
+ (value) => {
25252
+ clearTimeout(timer);
25253
+ resolve(value);
25254
+ },
25255
+ (error) => {
25256
+ clearTimeout(timer);
25257
+ reject(error);
25258
+ }
25259
+ );
25260
+ });
25261
+ }
25262
+
24545
25263
  // src/multimodal.ts
24546
- import { readFile as readFile7, stat as stat3 } from "node:fs/promises";
24547
- import path15 from "node:path";
25264
+ import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
25265
+ import path17 from "node:path";
24548
25266
  var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
24549
25267
  async function buildUserContent(prompt, images = [], options) {
24550
25268
  if (images.length === 0) return prompt;
@@ -24568,11 +25286,11 @@ async function imageToUrl(input2, options) {
24568
25286
  const maxImageBytes = options.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
24569
25287
  if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
24570
25288
  const mime = mimeFromPath(filePath);
24571
- const bytes = await readFile7(filePath);
25289
+ const bytes = await readFile8(filePath);
24572
25290
  return `data:${mime};base64,${bytes.toString("base64")}`;
24573
25291
  }
24574
25292
  function mimeFromPath(filePath) {
24575
- switch (path15.extname(filePath).toLowerCase()) {
25293
+ switch (path17.extname(filePath).toLowerCase()) {
24576
25294
  case ".jpg":
24577
25295
  case ".jpeg":
24578
25296
  return "image/jpeg";
@@ -24583,15 +25301,15 @@ function mimeFromPath(filePath) {
24583
25301
  case ".webp":
24584
25302
  return "image/webp";
24585
25303
  default:
24586
- throw new Error(`Unsupported image extension: ${path15.extname(filePath) || "(none)"}`);
25304
+ throw new Error(`Unsupported image extension: ${path17.extname(filePath) || "(none)"}`);
24587
25305
  }
24588
25306
  }
24589
25307
 
24590
25308
  // src/permissions/persistent-grants.ts
24591
- import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
25309
+ import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
24592
25310
  import fs8 from "node:fs";
24593
- import os9 from "node:os";
24594
- import path16 from "node:path";
25311
+ import os10 from "node:os";
25312
+ import path18 from "node:path";
24595
25313
  var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
24596
25314
  var PersistentGrantStore = class {
24597
25315
  filePath;
@@ -24622,22 +25340,22 @@ var PersistentGrantStore = class {
24622
25340
  }
24623
25341
  async #read() {
24624
25342
  if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
24625
- const data = JSON.parse(await readFile8(this.filePath, "utf8"));
25343
+ const data = JSON.parse(await readFile9(this.filePath, "utf8"));
24626
25344
  return {
24627
25345
  version: 1,
24628
25346
  grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
24629
25347
  };
24630
25348
  }
24631
25349
  async #write(file) {
24632
- await mkdir6(path16.dirname(this.filePath), { recursive: true });
24633
- await writeFile5(this.filePath, `${JSON.stringify(file, null, 2)}
25350
+ await mkdir7(path18.dirname(this.filePath), { recursive: true });
25351
+ await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
24634
25352
  `, { mode: 384 });
24635
25353
  }
24636
25354
  };
24637
25355
  function resolveGrantPath(cwd, config) {
24638
- if (config.path) return path16.resolve(cwd, config.path);
24639
- if ((config.scope ?? "project") === "user") return path16.join(os9.homedir(), ".demian", "grants.json");
24640
- return path16.join(cwd, ".demian", "grants.json");
25356
+ if (config.path) return path18.resolve(cwd, config.path);
25357
+ if ((config.scope ?? "project") === "user") return path18.join(os10.homedir(), ".demian", "grants.json");
25358
+ return path18.join(cwd, ".demian", "grants.json");
24641
25359
  }
24642
25360
  function isGrantRecord(value) {
24643
25361
  if (!value || typeof value !== "object") return false;
@@ -24696,12 +25414,13 @@ function applyPermissionPresetToConfig(config, preset) {
24696
25414
 
24697
25415
  // src/root-session.ts
24698
25416
  import { cp, mkdtemp, rm as rm2 } from "node:fs/promises";
24699
- import os10 from "node:os";
24700
- import path26 from "node:path";
25417
+ import os12 from "node:os";
25418
+ import path28 from "node:path";
24701
25419
 
24702
25420
  // src/transcript.ts
24703
- import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
24704
- import path17 from "node:path";
25421
+ import { mkdir as mkdir8, appendFile, writeFile as writeFile7 } from "node:fs/promises";
25422
+ import os11 from "node:os";
25423
+ import path19 from "node:path";
24705
25424
  var TranscriptWriter = class {
24706
25425
  filePath;
24707
25426
  #enabled;
@@ -24709,9 +25428,9 @@ var TranscriptWriter = class {
24709
25428
  #queue = Promise.resolve();
24710
25429
  constructor(options) {
24711
25430
  this.#enabled = options.enabled !== false;
24712
- const dir = path17.join(options.cwd, ".demian", "transcripts", options.sessionId);
24713
- this.filePath = path17.join(dir, "session.jsonl");
24714
- this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
25431
+ const dir = path19.join(options.storageDir ?? defaultDemianStorageDir(), "transcripts", options.sessionId);
25432
+ this.filePath = path19.join(dir, "session.jsonl");
25433
+ this.#ready = this.#enabled ? mkdir8(dir, { recursive: true }).then(() => writeFile7(this.filePath, "", { flag: "a" })) : Promise.resolve();
24715
25434
  }
24716
25435
  write(event) {
24717
25436
  if (!this.#enabled) return Promise.resolve();
@@ -24726,12 +25445,15 @@ var TranscriptWriter = class {
24726
25445
  await this.#queue;
24727
25446
  }
24728
25447
  };
25448
+ function defaultDemianStorageDir() {
25449
+ return path19.join(os11.homedir(), ".demian");
25450
+ }
24729
25451
 
24730
25452
  // src/external-runtime/snapshot-diff.ts
24731
25453
  import { createHash as createHash2 } from "node:crypto";
24732
25454
  import { createReadStream } from "node:fs";
24733
- import { readdir, readFile as readFile9, stat as stat4 } from "node:fs/promises";
24734
- import path18 from "node:path";
25455
+ import { readdir, readFile as readFile10, stat as stat4 } from "node:fs/promises";
25456
+ import path20 from "node:path";
24735
25457
 
24736
25458
  // src/workspace/diff.ts
24737
25459
  var CONTEXT_LINES = 3;
@@ -24941,7 +25663,7 @@ async function diffWorkspaceSnapshot(before) {
24941
25663
  }
24942
25664
  async function walk(root, relativeDir, entries, options) {
24943
25665
  if (entries.size >= options.maxFiles) return;
24944
- const absoluteDir = path18.join(root, relativeDir);
25666
+ const absoluteDir = path20.join(root, relativeDir);
24945
25667
  let items;
24946
25668
  try {
24947
25669
  items = await readdir(absoluteDir, { withFileTypes: true });
@@ -24952,8 +25674,8 @@ async function walk(root, relativeDir, entries, options) {
24952
25674
  if (entries.size >= options.maxFiles) return;
24953
25675
  if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
24954
25676
  if (SKIP_DIRS.has(item.name)) continue;
24955
- const relativePath = normalizeRelativePath(path18.join(relativeDir, item.name));
24956
- const absolutePath = path18.join(root, relativePath);
25677
+ const relativePath = normalizeRelativePath(path20.join(relativeDir, item.name));
25678
+ const absolutePath = path20.join(root, relativePath);
24957
25679
  if (item.isDirectory()) {
24958
25680
  await walk(root, relativePath, entries, options);
24959
25681
  continue;
@@ -24975,7 +25697,7 @@ function snapshotDiffText(entry) {
24975
25697
  `;
24976
25698
  }
24977
25699
  async function readTextContent(filePath) {
24978
- const buffer = await readFile9(filePath);
25700
+ const buffer = await readFile10(filePath);
24979
25701
  if (buffer.includes(0)) return void 0;
24980
25702
  return buffer.toString("utf8");
24981
25703
  }
@@ -24989,7 +25711,7 @@ function sha256File(filePath) {
24989
25711
  });
24990
25712
  }
24991
25713
  function normalizeRelativePath(value) {
24992
- return value.split(path18.sep).join("/");
25714
+ return value.split(path20.sep).join("/");
24993
25715
  }
24994
25716
 
24995
25717
  // src/external-runtime/session-runner.ts
@@ -25682,25 +26404,25 @@ function fail(content, metadata) {
25682
26404
  }
25683
26405
 
25684
26406
  // src/tools/output.ts
25685
- import { mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
25686
- import path19 from "node:path";
26407
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
26408
+ import path21 from "node:path";
25687
26409
  var DEFAULT_CAP_BYTES = 32 * 1024;
25688
26410
  var HALF_PREVIEW_BYTES = 16 * 1024;
25689
26411
  async function capToolOutput(result, options) {
25690
26412
  const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
25691
26413
  const bytes = Buffer.byteLength(result.content, "utf8");
25692
26414
  if (bytes <= capBytes) return result;
25693
- const dir = path19.join(options.cwd, ".demian", "tmp");
25694
- await mkdir8(dir, { recursive: true });
25695
- const outputPath = path19.join(dir, `output-${safeCallId(options.callId)}.txt`);
25696
- await writeFile7(outputPath, result.content, "utf8");
26415
+ const dir = path21.join(options.cwd, ".demian", "tmp");
26416
+ await mkdir9(dir, { recursive: true });
26417
+ const outputPath = path21.join(dir, `output-${safeCallId(options.callId)}.txt`);
26418
+ await writeFile8(outputPath, result.content, "utf8");
25697
26419
  return {
25698
26420
  ...result,
25699
26421
  content: [
25700
26422
  sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
25701
26423
  `
25702
26424
 
25703
- [Full output saved to ${path19.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
26425
+ [Full output saved to ${path21.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
25704
26426
 
25705
26427
  `,
25706
26428
  sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
@@ -25726,7 +26448,7 @@ function sliceUtf8(text, startByte, endByte) {
25726
26448
 
25727
26449
  // src/tools/read-file.ts
25728
26450
  import { open as open2, stat as stat5 } from "node:fs/promises";
25729
- import path20 from "node:path";
26451
+ import path22 from "node:path";
25730
26452
 
25731
26453
  // src/tools/validation.ts
25732
26454
  function assertObject(input2, toolName) {
@@ -25810,7 +26532,7 @@ var readFileTool = {
25810
26532
  const truncated = start + sliced.length < lines.length;
25811
26533
  return ok(content + (truncated ? `
25812
26534
  ... (${lines.length - start - sliced.length} more lines)` : ""), {
25813
- path: path20.relative(ctx.cwd, filePath),
26535
+ path: path22.relative(ctx.cwd, filePath),
25814
26536
  lines: sliced.length,
25815
26537
  totalLines: lines.length,
25816
26538
  truncated
@@ -25848,8 +26570,8 @@ function looksBinary(buffer) {
25848
26570
  }
25849
26571
 
25850
26572
  // src/tools/write-file.ts
25851
- import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
25852
- import path21 from "node:path";
26573
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
26574
+ import path23 from "node:path";
25853
26575
  var writeFileTool = {
25854
26576
  name: "write_file",
25855
26577
  description: "Create or replace a text file inside the workspace.",
@@ -25867,8 +26589,8 @@ var writeFileTool = {
25867
26589
  const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
25868
26590
  const content = stringField(object2, "content", { allowEmpty: true });
25869
26591
  const before = await readOptionalTextFile(filePath);
25870
- await mkdir9(path21.dirname(filePath), { recursive: true });
25871
- await writeFile8(filePath, content, "utf8");
26592
+ await mkdir10(path23.dirname(filePath), { recursive: true });
26593
+ await writeFile9(filePath, content, "utf8");
25872
26594
  const relative = relativeToCwd(ctx.cwd, filePath);
25873
26595
  const diff = createTextDiff(before, content, relative);
25874
26596
  return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
@@ -25881,7 +26603,7 @@ var writeFileTool = {
25881
26603
  };
25882
26604
  async function readOptionalTextFile(filePath) {
25883
26605
  try {
25884
- return await readFile10(filePath, "utf8");
26606
+ return await readFile11(filePath, "utf8");
25885
26607
  } catch (error) {
25886
26608
  if (isNotFound(error)) return void 0;
25887
26609
  throw error;
@@ -25892,7 +26614,7 @@ function isNotFound(error) {
25892
26614
  }
25893
26615
 
25894
26616
  // src/tools/edit-file.ts
25895
- import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
26617
+ import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
25896
26618
  var editFileTool = {
25897
26619
  name: "edit_file",
25898
26620
  description: "Replace an exact string in a text file inside the workspace.",
@@ -25914,12 +26636,12 @@ var editFileTool = {
25914
26636
  const newString = stringField(object2, "newString", { allowEmpty: true });
25915
26637
  const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
25916
26638
  if (oldString === newString) throw new Error("oldString and newString must be different");
25917
- const before = await readFile11(filePath, "utf8");
26639
+ const before = await readFile12(filePath, "utf8");
25918
26640
  const matches = countMatches(before, oldString);
25919
26641
  if (matches === 0) throw new Error("oldString was not found");
25920
26642
  if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
25921
26643
  const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
25922
- await writeFile9(filePath, after, "utf8");
26644
+ await writeFile10(filePath, after, "utf8");
25923
26645
  const relative = relativeToCwd(ctx.cwd, filePath);
25924
26646
  const diff = createTextDiff(before, after, relative);
25925
26647
  return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
@@ -25943,7 +26665,7 @@ function countMatches(text, needle) {
25943
26665
 
25944
26666
  // src/tools/bash.ts
25945
26667
  import { spawn as spawn5 } from "node:child_process";
25946
- import path23 from "node:path";
26668
+ import path25 from "node:path";
25947
26669
 
25948
26670
  // src/sandbox/env-only.ts
25949
26671
  function buildEnvOnlyLaunch(command, config) {
@@ -26003,7 +26725,7 @@ function bwrapPath() {
26003
26725
 
26004
26726
  // src/sandbox/macos.ts
26005
26727
  import { existsSync as existsSync3 } from "node:fs";
26006
- import path22 from "node:path";
26728
+ import path24 from "node:path";
26007
26729
  function canUseMacOSSandbox() {
26008
26730
  return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
26009
26731
  }
@@ -26029,7 +26751,7 @@ function macosProfile(cwd, config) {
26029
26751
  }
26030
26752
  if (mode === "workspace-write") {
26031
26753
  lines.push("(deny file-write*)");
26032
- lines.push(`(allow file-write* (subpath "${escapeProfilePath(path22.resolve(cwd))}"))`);
26754
+ lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
26033
26755
  lines.push('(allow file-write* (subpath "/tmp"))');
26034
26756
  lines.push('(allow file-write* (subpath "/private/tmp"))');
26035
26757
  lines.push('(allow file-write* (subpath "/private/var/folders"))');
@@ -26091,7 +26813,7 @@ ${result.stderr}` : ""
26091
26813
  ].filter(Boolean).join("\n");
26092
26814
  return ok(content, {
26093
26815
  command,
26094
- workdir: path23.relative(ctx.cwd, workdir) || ".",
26816
+ workdir: path25.relative(ctx.cwd, workdir) || ".",
26095
26817
  exitCode: result.exitCode,
26096
26818
  signal: result.signal,
26097
26819
  timedOut: result.timedOut,
@@ -26143,8 +26865,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
26143
26865
 
26144
26866
  // src/tools/grep.ts
26145
26867
  import { spawn as spawn6 } from "node:child_process";
26146
- import { readdir as readdir2, readFile as readFile12, stat as stat6 } from "node:fs/promises";
26147
- import path24 from "node:path";
26868
+ import { readdir as readdir2, readFile as readFile13, stat as stat6 } from "node:fs/promises";
26869
+ import path26 from "node:path";
26148
26870
  var MAX_MATCHES = 200;
26149
26871
  var grepTool = {
26150
26872
  name: "grep",
@@ -26219,7 +26941,7 @@ function runRg(cwd, base, pattern, glob, signal) {
26219
26941
  }
26220
26942
  function rewriteRgPath(cwd, line) {
26221
26943
  const [file, rest] = splitFirst(line, ":");
26222
- if (!path24.isAbsolute(file)) return line;
26944
+ if (!path26.isAbsolute(file)) return line;
26223
26945
  return `${relativeToCwd(cwd, file)}:${rest}`;
26224
26946
  }
26225
26947
  function splitFirst(value, delimiter) {
@@ -26236,11 +26958,11 @@ async function fallbackSearch(cwd, base, pattern, glob) {
26236
26958
  if (isIgnoredPath(relative)) return;
26237
26959
  const info = await stat6(item);
26238
26960
  if (info.isDirectory()) {
26239
- for (const entry of await readdir2(item)) await walk2(path24.join(item, entry));
26961
+ for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
26240
26962
  return;
26241
26963
  }
26242
26964
  if (glob && !matchGlob(glob, relative)) return;
26243
- const buffer = await readFile12(item);
26965
+ const buffer = await readFile13(item);
26244
26966
  if (buffer.includes(0)) return;
26245
26967
  const lines = buffer.toString("utf8").split(/\r?\n/);
26246
26968
  for (let i = 0; i < lines.length; i++) {
@@ -26255,7 +26977,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
26255
26977
 
26256
26978
  // src/tools/glob.ts
26257
26979
  import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
26258
- import path25 from "node:path";
26980
+ import path27 from "node:path";
26259
26981
  var MAX_PATHS = 1e3;
26260
26982
  var globTool = {
26261
26983
  name: "glob",
@@ -26287,7 +27009,7 @@ async function collectPaths(cwd, root, pattern) {
26287
27009
  async function walk2(dir) {
26288
27010
  const entries = await readdir3(dir, { withFileTypes: true });
26289
27011
  for (const entry of entries) {
26290
- const absolute = path25.join(dir, entry.name);
27012
+ const absolute = path27.join(dir, entry.name);
26291
27013
  const relative = relativeToCwd(cwd, absolute);
26292
27014
  if (isIgnoredPath(relative)) continue;
26293
27015
  const info = await stat7(absolute);
@@ -26428,7 +27150,7 @@ async function runBraveSearch(config, options) {
26428
27150
  if (config.country) url.searchParams.set("country", config.country);
26429
27151
  if (config.searchLang) url.searchParams.set("search_lang", config.searchLang);
26430
27152
  if (config.safeSearch) url.searchParams.set("safesearch", config.safeSearch);
26431
- const json = await fetchJson(url, {
27153
+ const json = await fetchJson2(url, {
26432
27154
  method: "GET",
26433
27155
  headers: {
26434
27156
  accept: "application/json",
@@ -26447,7 +27169,7 @@ async function runTavilySearch(config, options) {
26447
27169
  if (!apiKey.ok) return apiKey;
26448
27170
  const maxResults = Math.min(positiveInteger(options.maxResults, config.maxResults ?? 5, "maxResults"), TAVILY_MAX_RESULTS);
26449
27171
  const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.tavily.endpoint ?? "https://api.tavily.com/search";
26450
- const json = await fetchJson(endpoint, {
27172
+ const json = await fetchJson2(endpoint, {
26451
27173
  method: "POST",
26452
27174
  headers: {
26453
27175
  "content-type": "application/json",
@@ -26473,7 +27195,7 @@ async function runExaSearch(config, options) {
26473
27195
  if (!apiKey.ok) return apiKey;
26474
27196
  const numResults = Math.min(positiveInteger(options.maxResults, config.numResults ?? 5, "maxResults"), EXA_MAX_RESULTS);
26475
27197
  const endpoint = config.endpoint ?? defaultConfig.webSearch.providers.exa.endpoint ?? "https://api.exa.ai/search";
26476
- const json = await fetchJson(endpoint, {
27198
+ const json = await fetchJson2(endpoint, {
26477
27199
  method: "POST",
26478
27200
  headers: {
26479
27201
  "content-type": "application/json",
@@ -26502,7 +27224,7 @@ function resolveApiKey(provider, config) {
26502
27224
  if (value) return { ok: true, value };
26503
27225
  return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
26504
27226
  }
26505
- async function fetchJson(url, options) {
27227
+ async function fetchJson2(url, options) {
26506
27228
  const { fetcher, provider, ...init } = options;
26507
27229
  const response = await fetcher(url, init);
26508
27230
  const text = await response.text();
@@ -26520,10 +27242,10 @@ function normalizeBraveResults(raw, limit) {
26520
27242
  return (data.web?.results ?? []).slice(0, limit).map((item) => {
26521
27243
  const result = item;
26522
27244
  return {
26523
- title: stringValue4(result.title) ?? "Untitled",
26524
- url: stringValue4(result.url) ?? "",
26525
- snippet: cleanSnippet(stringValue4(result.description) ?? stringValue4(result.snippet)),
26526
- publishedDate: stringValue4(result.age)
27245
+ title: stringValue5(result.title) ?? "Untitled",
27246
+ url: stringValue5(result.url) ?? "",
27247
+ snippet: cleanSnippet(stringValue5(result.description) ?? stringValue5(result.snippet)),
27248
+ publishedDate: stringValue5(result.age)
26527
27249
  };
26528
27250
  }).filter((item) => item.url.length > 0);
26529
27251
  }
@@ -26532,9 +27254,9 @@ function normalizeTavilyResults(raw, limit) {
26532
27254
  return (data.results ?? []).slice(0, limit).map((item) => {
26533
27255
  const result = item;
26534
27256
  return {
26535
- title: stringValue4(result.title) ?? "Untitled",
26536
- url: stringValue4(result.url) ?? "",
26537
- snippet: cleanSnippet(stringValue4(result.content) ?? stringValue4(result.raw_content)),
27257
+ title: stringValue5(result.title) ?? "Untitled",
27258
+ url: stringValue5(result.url) ?? "",
27259
+ snippet: cleanSnippet(stringValue5(result.content) ?? stringValue5(result.raw_content)),
26538
27260
  score: numberValue3(result.score)
26539
27261
  };
26540
27262
  }).filter((item) => item.url.length > 0);
@@ -26544,10 +27266,10 @@ function normalizeExaResults(raw, limit) {
26544
27266
  return (data.results ?? []).slice(0, limit).map((item) => {
26545
27267
  const result = item;
26546
27268
  return {
26547
- title: stringValue4(result.title) ?? "Untitled",
26548
- url: stringValue4(result.url) ?? "",
27269
+ title: stringValue5(result.title) ?? "Untitled",
27270
+ url: stringValue5(result.url) ?? "",
26549
27271
  snippet: exaSnippet(result),
26550
- publishedDate: stringValue4(result.publishedDate),
27272
+ publishedDate: stringValue5(result.publishedDate),
26551
27273
  score: numberValue3(result.score)
26552
27274
  };
26553
27275
  }).filter((item) => item.url.length > 0);
@@ -26558,7 +27280,7 @@ function exaSnippet(result) {
26558
27280
  const joined = highlights.map((item) => typeof item === "string" ? item : "").filter(Boolean).join(" ");
26559
27281
  if (joined) return cleanSnippet(joined);
26560
27282
  }
26561
- return cleanSnippet(stringValue4(result.text) ?? stringValue4(result.summary));
27283
+ return cleanSnippet(stringValue5(result.text) ?? stringValue5(result.summary));
26562
27284
  }
26563
27285
  function searchResult(provider, query, results, raw) {
26564
27286
  const content = [
@@ -26582,7 +27304,7 @@ function formatResult(index, result) {
26582
27304
  if (result.snippet) lines.push(` Snippet: ${result.snippet}`);
26583
27305
  return lines.join("\n");
26584
27306
  }
26585
- function stringValue4(value) {
27307
+ function stringValue5(value) {
26586
27308
  return typeof value === "string" && value.length > 0 ? value : void 0;
26587
27309
  }
26588
27310
  function numberValue3(value) {
@@ -27462,7 +28184,7 @@ function stringArray2(value) {
27462
28184
  }
27463
28185
 
27464
28186
  // src/external-runtime/session-map.ts
27465
- import crypto5 from "node:crypto";
28187
+ import crypto6 from "node:crypto";
27466
28188
  var ClaudeCodeSessionMap = class {
27467
28189
  #sessions = /* @__PURE__ */ new Map();
27468
28190
  get(key) {
@@ -27515,7 +28237,7 @@ function normalizeInstruction(value) {
27515
28237
  return value.replace(/\r\n/g, "\n").trim().split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n");
27516
28238
  }
27517
28239
  function sha256(value) {
27518
- return crypto5.createHash("sha256").update(value).digest("hex").slice(0, 16);
28240
+ return crypto6.createHash("sha256").update(value).digest("hex").slice(0, 16);
27519
28241
  }
27520
28242
 
27521
28243
  // src/root-session.ts
@@ -28154,8 +28876,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
28154
28876
  }
28155
28877
  }
28156
28878
  async createCoworkIsolatedWorkspace(groupId, memberId) {
28157
- const root = await mkdtemp(path26.join(os10.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
28158
- const cwd = path26.join(root, "workspace");
28879
+ const root = await mkdtemp(path28.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
28880
+ const cwd = path28.join(root, "workspace");
28159
28881
  await cp(this.#options.cwd, cwd, {
28160
28882
  recursive: true,
28161
28883
  filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
@@ -28303,7 +29025,7 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
28303
29025
  const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
28304
29026
  return this.withPermissionPreset({
28305
29027
  ...agent,
28306
- provider: agent.provider?.profile === "claudecode-subagent" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
29028
+ provider: agent.provider?.profile === "claudecode" && agent.name === "claudecode-explorer" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
28307
29029
  tools: agent.tools.filter((tool) => readTools.has(tool)),
28308
29030
  permissions: [
28309
29031
  ...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
@@ -28350,13 +29072,14 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
28350
29072
  return lines.join("\n");
28351
29073
  }
28352
29074
  resolveInvocationBackend(profileName, modelOverride, agent) {
28353
- const providerConfig = resolveProviderConfig(this.#options.config, profileName);
29075
+ const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride });
29076
+ const providerConfig = runtime.providerConfig;
28354
29077
  if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, modelOverride);
28355
29078
  if (this.#options.resolveProvider) {
28356
- const resolved = this.#options.resolveProvider(profileName, providerConfig, modelOverride);
29079
+ const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
28357
29080
  return { kind: "provider", ...resolved };
28358
29081
  }
28359
- return resolveExecutionBackend(providerConfig, { model: modelOverride, config: this.#options.config, agent });
29082
+ return resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config, agent });
28360
29083
  }
28361
29084
  loadAgentSession(agent, providerProfile, model, sessionScope, cwd = this.#options.cwd) {
28362
29085
  const key = `${this.id}:${sessionScope ?? "delegate"}:${agent.name}:${providerProfile}:${model}:${cwd}`;
@@ -28458,17 +29181,17 @@ function findDependencyCycle(group) {
28458
29181
  const byId = new Map(group.map((member) => [member.memberId, member]));
28459
29182
  const visiting = /* @__PURE__ */ new Set();
28460
29183
  const visited = /* @__PURE__ */ new Set();
28461
- const path27 = [];
29184
+ const path29 = [];
28462
29185
  const visit = (id) => {
28463
- if (visiting.has(id)) return [...path27.slice(path27.indexOf(id)), id];
29186
+ if (visiting.has(id)) return [...path29.slice(path29.indexOf(id)), id];
28464
29187
  if (visited.has(id)) return void 0;
28465
29188
  visiting.add(id);
28466
- path27.push(id);
29189
+ path29.push(id);
28467
29190
  for (const dep of byId.get(id)?.dependsOn ?? []) {
28468
29191
  const cycle = visit(dep);
28469
29192
  if (cycle) return cycle;
28470
29193
  }
28471
- path27.pop();
29194
+ path29.pop();
28472
29195
  visiting.delete(id);
28473
29196
  visited.add(id);
28474
29197
  return void 0;
@@ -28509,7 +29232,7 @@ function scopeStaticPrefix(pattern) {
28509
29232
  return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
28510
29233
  }
28511
29234
  function shouldCopyIntoCoworkWorkspace(root, source) {
28512
- const relative = path26.relative(root, source).split(path26.sep).join("/");
29235
+ const relative = path28.relative(root, source).split(path28.sep).join("/");
28513
29236
  if (!relative) return true;
28514
29237
  const parts = relative.split("/");
28515
29238
  return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
@@ -28703,6 +29426,7 @@ function sumUsage(outcomes) {
28703
29426
  export {
28704
29427
  AgentRegistry,
28705
29428
  AnthropicProvider,
29429
+ BUILTIN_PROVIDER_ORDER,
28706
29430
  CLAUDE_CODE_KEYRING_SERVICE,
28707
29431
  CLAUDE_CODE_MODELS,
28708
29432
  CLAUDE_CODE_OAUTH_BETA_HEADER,
@@ -28730,6 +29454,7 @@ export {
28730
29454
  DEFAULT_CODEX_BASE_URL,
28731
29455
  EventBus,
28732
29456
  HookDispatcher,
29457
+ OllamaProvider,
28733
29458
  OpenAIProvider,
28734
29459
  PERMISSION_PRESETS,
28735
29460
  PermissionEngine,
@@ -28764,6 +29489,8 @@ export {
28764
29489
  decodeJwtPayload,
28765
29490
  defaultConfig,
28766
29491
  defaultDecisionForPermissionPreset,
29492
+ defaultDemianStorageDir,
29493
+ defaultModelForProvider,
28767
29494
  estimateMessageTokens,
28768
29495
  estimateMessagesTokens,
28769
29496
  estimateToolSchemaTokens,
@@ -28780,6 +29507,7 @@ export {
28780
29507
  imageToUrl,
28781
29508
  inferOpenAICompatibleAuth,
28782
29509
  injectClaudeCodeSystemPrefix,
29510
+ invalidateCatalogPingCache,
28783
29511
  isAzureOpenAIEndpoint,
28784
29512
  isCallableAgent,
28785
29513
  isCoworkableAgent,
@@ -28788,6 +29516,7 @@ export {
28788
29516
  isInsidePath,
28789
29517
  isPermissionPreset,
28790
29518
  isPrimaryAgent,
29519
+ listProviderModelCatalog,
28791
29520
  loadConfig,
28792
29521
  loadOrCreateInstallationId,
28793
29522
  matchGlob,
@@ -28796,6 +29525,7 @@ export {
28796
29525
  normalizeAgent,
28797
29526
  normalizeAnthropicResponse,
28798
29527
  normalizeCodexResponse,
29528
+ normalizeOllamaResponse,
28799
29529
  normalizeOpenAIResponse,
28800
29530
  normalizePathForMatch,
28801
29531
  normalizePermissionPreset,
@@ -28807,16 +29537,22 @@ export {
28807
29537
  parseOpenAIStream,
28808
29538
  parseRetryAfter,
28809
29539
  permissionDecisionLine,
29540
+ providerModelOptions,
29541
+ providerModelProfiles,
28810
29542
  relativeToCwd,
28811
29543
  resolveAgentMode,
28812
29544
  resolveClaudeCodeRuntimeConfig,
28813
29545
  resolveClaudeConfigDir,
28814
29546
  resolveCodexHome,
29547
+ resolveConfiguredApiKey,
28815
29548
  resolveExecutionBackend,
28816
29549
  resolveGrantPath,
28817
29550
  resolveInsideCwd,
29551
+ resolveInternalAgentBackend,
28818
29552
  resolveProvider,
28819
29553
  resolveProviderConfig,
29554
+ resolveProviderRuntimeConfig,
29555
+ sortProviderNames,
28820
29556
  toClaudeCodeMessagesPayload,
28821
29557
  toCodexResponsesPayload,
28822
29558
  toCodexResponsesTool,