demian-cli 1.0.6 → 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/cli.mjs +1799 -364
- package/dist/index.mjs +995 -259
- package/dist/tui.mjs +21951 -20437
- package/dist/vscode-worker.mjs +1586 -290
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1204
|
+
return resolveExpandedPath(configured ?? process.env.CODEX_HOME ?? path2.join(os2.homedir(), ".codex"));
|
|
1166
1205
|
}
|
|
1167
1206
|
async function loadOrCreateInstallationId(codexHome) {
|
|
1168
|
-
const filePath =
|
|
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
|
|
1451
|
+
return path3.join(codexHome, "auth.json");
|
|
1422
1452
|
}
|
|
1423
1453
|
function codexKeyringAccount(codexHome) {
|
|
1424
|
-
const resolved =
|
|
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
|
|
1860
|
-
import
|
|
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 ??
|
|
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 ??
|
|
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
|
|
2153
|
+
return resolveExpandedPath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path4.join(os3.homedir(), ".claude"));
|
|
2124
2154
|
}
|
|
2125
2155
|
function claudeCodeAuthFilePath(claudeConfigDir) {
|
|
2126
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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 =
|
|
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
|
|
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 ??
|
|
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(
|
|
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
|
|
22706
|
+
import path11 from "node:path";
|
|
22584
22707
|
|
|
22585
22708
|
// src/permissions/grants.ts
|
|
22586
|
-
import
|
|
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 ?
|
|
22642
|
-
return `${tool}:${
|
|
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
|
|
22792
|
+
import path10 from "node:path";
|
|
22670
22793
|
function normalizePathForMatch(value) {
|
|
22671
|
-
return value.split(
|
|
22794
|
+
return value.split(path10.sep).join("/");
|
|
22672
22795
|
}
|
|
22673
22796
|
function isInsidePath(root, candidate) {
|
|
22674
|
-
const relative =
|
|
22675
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
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 =
|
|
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 =
|
|
22811
|
+
const relative = path10.relative(cwd, absolutePath);
|
|
22689
22812
|
return normalizePathForMatch(relative || ".");
|
|
22690
22813
|
}
|
|
22691
22814
|
function isEnvFile(filePath) {
|
|
22692
|
-
const base =
|
|
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 =
|
|
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,
|
|
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 (
|
|
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
|
|
22979
|
-
if (
|
|
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
|
|
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 =
|
|
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 =
|
|
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 (
|
|
23172
|
+
if (path12.isAbsolute(value)) {
|
|
23050
23173
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
23051
|
-
const resolved =
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
23737
|
+
groq: {
|
|
23598
23738
|
type: "openai-compatible",
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
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
|
-
|
|
23768
|
+
"ollama-local": {
|
|
23611
23769
|
type: "openai-compatible",
|
|
23612
|
-
|
|
23613
|
-
|
|
23614
|
-
|
|
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
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
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
|
-
|
|
23796
|
+
vllm: {
|
|
23641
23797
|
type: "openai-compatible",
|
|
23642
|
-
|
|
23643
|
-
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
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
|
-
|
|
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 =
|
|
23730
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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" ?
|
|
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,
|
|
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
|
|
24547
|
-
import
|
|
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
|
|
25289
|
+
const bytes = await readFile8(filePath);
|
|
24572
25290
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
24573
25291
|
}
|
|
24574
25292
|
function mimeFromPath(filePath) {
|
|
24575
|
-
switch (
|
|
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: ${
|
|
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
|
|
25309
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
24592
25310
|
import fs8 from "node:fs";
|
|
24593
|
-
import
|
|
24594
|
-
import
|
|
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
|
|
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
|
|
24633
|
-
await
|
|
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
|
|
24639
|
-
if ((config.scope ?? "project") === "user") return
|
|
24640
|
-
return
|
|
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
|
|
24700
|
-
import
|
|
25417
|
+
import os12 from "node:os";
|
|
25418
|
+
import path28 from "node:path";
|
|
24701
25419
|
|
|
24702
25420
|
// src/transcript.ts
|
|
24703
|
-
import { mkdir as
|
|
24704
|
-
import
|
|
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 =
|
|
24713
|
-
this.filePath =
|
|
24714
|
-
this.#ready = this.#enabled ?
|
|
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
|
|
24734
|
-
import
|
|
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 =
|
|
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(
|
|
24956
|
-
const absolutePath =
|
|
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
|
|
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(
|
|
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
|
|
25686
|
-
import
|
|
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 =
|
|
25694
|
-
await
|
|
25695
|
-
const outputPath =
|
|
25696
|
-
await
|
|
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 ${
|
|
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
|
|
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:
|
|
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
|
|
25852
|
-
import
|
|
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
|
|
25871
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
26147
|
-
import
|
|
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 (!
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
26524
|
-
url:
|
|
26525
|
-
snippet: cleanSnippet(
|
|
26526
|
-
publishedDate:
|
|
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:
|
|
26536
|
-
url:
|
|
26537
|
-
snippet: cleanSnippet(
|
|
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:
|
|
26548
|
-
url:
|
|
27269
|
+
title: stringValue5(result.title) ?? "Untitled",
|
|
27270
|
+
url: stringValue5(result.url) ?? "",
|
|
26549
27271
|
snippet: exaSnippet(result),
|
|
26550
|
-
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(
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
28158
|
-
const cwd =
|
|
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-
|
|
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
|
|
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,
|
|
29079
|
+
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
28357
29080
|
return { kind: "provider", ...resolved };
|
|
28358
29081
|
}
|
|
28359
|
-
return resolveExecutionBackend(providerConfig, { model:
|
|
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
|
|
29184
|
+
const path29 = [];
|
|
28462
29185
|
const visit = (id) => {
|
|
28463
|
-
if (visiting.has(id)) return [...
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|