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/vscode-worker.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/vscode-worker.ts
|
|
4
|
-
import { mkdir as
|
|
5
|
-
import
|
|
4
|
+
import { mkdir as mkdir15, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
|
|
5
|
+
import path35 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/permissions/presets.ts
|
|
8
8
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -971,7 +971,10 @@ var TuiStore = class {
|
|
|
971
971
|
this.#state.promptInput = "";
|
|
972
972
|
this.#state.promptError = void 0;
|
|
973
973
|
this.#state.activity = "starting session";
|
|
974
|
-
if (!isCompactCommand(prompt))
|
|
974
|
+
if (!isCompactCommand(prompt)) {
|
|
975
|
+
this.#clearTurnDiff();
|
|
976
|
+
this.#clearResolvedWorkPlan();
|
|
977
|
+
}
|
|
975
978
|
this.#resetPromptHistoryCursor();
|
|
976
979
|
this.#notify();
|
|
977
980
|
return Promise.resolve(prompt);
|
|
@@ -983,7 +986,10 @@ var TuiStore = class {
|
|
|
983
986
|
this.#state.promptInput = "";
|
|
984
987
|
this.#state.promptError = void 0;
|
|
985
988
|
this.#state.activity = "starting session";
|
|
986
|
-
if (!isCompactCommand(queued))
|
|
989
|
+
if (!isCompactCommand(queued)) {
|
|
990
|
+
this.#clearTurnDiff();
|
|
991
|
+
this.#clearResolvedWorkPlan();
|
|
992
|
+
}
|
|
987
993
|
this.#resetPromptHistoryCursor();
|
|
988
994
|
this.#notify();
|
|
989
995
|
return Promise.resolve(queued);
|
|
@@ -1992,7 +1998,7 @@ var TuiStore = class {
|
|
|
1992
1998
|
this.#state.activity = prompt ? "starting session" : "cancelled";
|
|
1993
1999
|
if (prompt && !isCompactCommand(prompt)) {
|
|
1994
2000
|
this.#clearTurnDiff();
|
|
1995
|
-
this.#
|
|
2001
|
+
this.#clearResolvedWorkPlan();
|
|
1996
2002
|
}
|
|
1997
2003
|
this.#resetPromptHistoryCursor();
|
|
1998
2004
|
resolve(prompt);
|
|
@@ -2029,6 +2035,10 @@ var TuiStore = class {
|
|
|
2029
2035
|
this.#state.progressNotes = [];
|
|
2030
2036
|
this.#state.workPlanExpanded = false;
|
|
2031
2037
|
}
|
|
2038
|
+
#clearResolvedWorkPlan() {
|
|
2039
|
+
if (!isResolvedWorkPlan(this.#state.workPlan)) return;
|
|
2040
|
+
this.#clearWorkPlan();
|
|
2041
|
+
}
|
|
2032
2042
|
#markActiveWorkPlanBlocked(message) {
|
|
2033
2043
|
const plan = this.#state.workPlan;
|
|
2034
2044
|
if (!plan) return;
|
|
@@ -2151,6 +2161,10 @@ function isFinalAnswerWorkStep(step) {
|
|
|
2151
2161
|
function isTerminalWorkStep(step) {
|
|
2152
2162
|
return step.status === "completed" || step.status === "skipped" || step.status === "failed" || step.status === "blocked";
|
|
2153
2163
|
}
|
|
2164
|
+
function isResolvedWorkPlan(plan) {
|
|
2165
|
+
const steps = plan?.steps ?? [];
|
|
2166
|
+
return steps.length > 0 && steps.every((step) => step.status === "completed" || step.status === "skipped");
|
|
2167
|
+
}
|
|
2154
2168
|
function appendSentence(existing, sentence) {
|
|
2155
2169
|
const cleanSentence = sentence.trim();
|
|
2156
2170
|
if (!cleanSentence) return existing ?? "";
|
|
@@ -2313,7 +2327,7 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2313
2327
|
}
|
|
2314
2328
|
|
|
2315
2329
|
// src/ui/tui/controller.ts
|
|
2316
|
-
import
|
|
2330
|
+
import path32 from "node:path";
|
|
2317
2331
|
|
|
2318
2332
|
// src/agents/types.ts
|
|
2319
2333
|
function normalizeAgent(input2) {
|
|
@@ -2407,7 +2421,7 @@ var claudeCodeAgent = {
|
|
|
2407
2421
|
name: "claudecode",
|
|
2408
2422
|
description: "Claude Code external coding agent for focused delegated workspace tasks.",
|
|
2409
2423
|
mode: "subagent",
|
|
2410
|
-
provider: { profile: "claudecode
|
|
2424
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
2411
2425
|
tools: [],
|
|
2412
2426
|
systemPrompt: [
|
|
2413
2427
|
"You are demian claudecode, a Claude Code external runtime working as a sub agent for Demian.",
|
|
@@ -2452,7 +2466,7 @@ var claudeCodeExplorerAgent = {
|
|
|
2452
2466
|
name: "claudecode-explorer",
|
|
2453
2467
|
description: "Claude Code external read-only explorer for cowork repository inspection.",
|
|
2454
2468
|
mode: "subagent",
|
|
2455
|
-
provider: { profile: "claudecode
|
|
2469
|
+
provider: { profile: "claudecode", permissionProfile: "explore" },
|
|
2456
2470
|
tools: [],
|
|
2457
2471
|
systemPrompt: [
|
|
2458
2472
|
"You are demian claudecode-explorer, a Claude Code external runtime working as a read-only cowork sub agent for Demian.",
|
|
@@ -2487,7 +2501,7 @@ var claudeCodeBuilderAgent = {
|
|
|
2487
2501
|
name: "claudecode-builder",
|
|
2488
2502
|
description: "Claude Code-backed builder for bounded cowork implementation tasks.",
|
|
2489
2503
|
mode: "subagent",
|
|
2490
|
-
provider: { profile: "claudecode
|
|
2504
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
2491
2505
|
tools: [],
|
|
2492
2506
|
systemPrompt: [
|
|
2493
2507
|
"You are demian claudecode-builder, a Claude Code external runtime working as a writer cowork sub agent for Demian.",
|
|
@@ -2792,7 +2806,7 @@ import { readFile as readFile6 } from "node:fs/promises";
|
|
|
2792
2806
|
import crypto4 from "node:crypto";
|
|
2793
2807
|
import fs7 from "node:fs";
|
|
2794
2808
|
import os7 from "node:os";
|
|
2795
|
-
import
|
|
2809
|
+
import path13 from "node:path";
|
|
2796
2810
|
|
|
2797
2811
|
// src/providers/retry.ts
|
|
2798
2812
|
var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -3185,12 +3199,14 @@ var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
|
3185
3199
|
var AnthropicProvider = class {
|
|
3186
3200
|
id = "anthropic";
|
|
3187
3201
|
#apiKey;
|
|
3202
|
+
#baseURL;
|
|
3188
3203
|
#defaultModel;
|
|
3189
3204
|
#defaultMaxTokens;
|
|
3190
3205
|
#client;
|
|
3191
3206
|
#onRetry;
|
|
3192
3207
|
constructor(config) {
|
|
3193
3208
|
this.#apiKey = config.apiKey;
|
|
3209
|
+
this.#baseURL = config.baseURL;
|
|
3194
3210
|
this.#defaultModel = config.defaultModel;
|
|
3195
3211
|
this.#defaultMaxTokens = config.defaultMaxTokens ?? 4096;
|
|
3196
3212
|
this.#client = config.client;
|
|
@@ -3235,7 +3251,7 @@ var AnthropicProvider = class {
|
|
|
3235
3251
|
if (this.#client) return this.#client;
|
|
3236
3252
|
if (!this.#apiKey) throw new Error("AnthropicProvider requires apiKey");
|
|
3237
3253
|
const Anthropic = await loadAnthropicConstructor();
|
|
3238
|
-
this.#client = new Anthropic({ apiKey: this.#apiKey });
|
|
3254
|
+
this.#client = new Anthropic({ apiKey: this.#apiKey, baseURL: this.#baseURL });
|
|
3239
3255
|
return this.#client;
|
|
3240
3256
|
}
|
|
3241
3257
|
};
|
|
@@ -3468,19 +3484,56 @@ import { execFile } from "node:child_process";
|
|
|
3468
3484
|
import { promisify } from "node:util";
|
|
3469
3485
|
import fs2 from "node:fs";
|
|
3470
3486
|
import { chmod, mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
3471
|
-
import
|
|
3487
|
+
import path3 from "node:path";
|
|
3472
3488
|
|
|
3473
3489
|
// src/providers/codex-state.ts
|
|
3474
3490
|
import crypto from "node:crypto";
|
|
3475
3491
|
import fs from "node:fs";
|
|
3476
3492
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3493
|
+
import os2 from "node:os";
|
|
3494
|
+
import path2 from "node:path";
|
|
3495
|
+
|
|
3496
|
+
// src/path-expansion.ts
|
|
3477
3497
|
import os from "node:os";
|
|
3478
3498
|
import path from "node:path";
|
|
3499
|
+
function expandPathReferences(value) {
|
|
3500
|
+
let expanded = expandTilde(value);
|
|
3501
|
+
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);
|
|
3502
|
+
expanded = expanded.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (match, name) => envPathValue(name) ?? match);
|
|
3503
|
+
return expanded;
|
|
3504
|
+
}
|
|
3505
|
+
function resolveExpandedPath(value) {
|
|
3506
|
+
return path.resolve(expandPathReferences(value));
|
|
3507
|
+
}
|
|
3508
|
+
function expandTilde(value) {
|
|
3509
|
+
if (value === "~") return os.homedir();
|
|
3510
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
|
|
3511
|
+
return value;
|
|
3512
|
+
}
|
|
3513
|
+
function envPathValue(name) {
|
|
3514
|
+
if (!name) return void 0;
|
|
3515
|
+
const value = processEnvValue(name);
|
|
3516
|
+
if (value !== void 0) return value;
|
|
3517
|
+
const upper = name.toUpperCase();
|
|
3518
|
+
if (upper === "HOME" || upper === "USERPROFILE") return os.homedir();
|
|
3519
|
+
if (process.platform === "win32" && upper === "HOMEDRIVE") return path.win32.parse(os.homedir()).root.replace(/[\\/]$/, "");
|
|
3520
|
+
if (process.platform === "win32" && upper === "HOMEPATH") return os.homedir().replace(/^[A-Za-z]:/, "");
|
|
3521
|
+
return void 0;
|
|
3522
|
+
}
|
|
3523
|
+
function processEnvValue(name) {
|
|
3524
|
+
if (process.env[name] !== void 0) return process.env[name];
|
|
3525
|
+
if (process.platform !== "win32") return void 0;
|
|
3526
|
+
const upper = name.toUpperCase();
|
|
3527
|
+
const key = Object.keys(process.env).find((item) => item.toUpperCase() === upper);
|
|
3528
|
+
return key ? process.env[key] : void 0;
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
// src/providers/codex-state.ts
|
|
3479
3532
|
function resolveCodexHome(configured) {
|
|
3480
|
-
return
|
|
3533
|
+
return resolveExpandedPath(configured ?? process.env.CODEX_HOME ?? path2.join(os2.homedir(), ".codex"));
|
|
3481
3534
|
}
|
|
3482
3535
|
async function loadOrCreateInstallationId(codexHome) {
|
|
3483
|
-
const filePath =
|
|
3536
|
+
const filePath = path2.join(codexHome, "installation_id");
|
|
3484
3537
|
try {
|
|
3485
3538
|
const existing = (await readFile(filePath, "utf8")).trim();
|
|
3486
3539
|
if (isUuid(existing)) return existing;
|
|
@@ -3502,15 +3555,6 @@ function isNodeError(error, code) {
|
|
|
3502
3555
|
function fileExists(filePath) {
|
|
3503
3556
|
return fs.existsSync(filePath);
|
|
3504
3557
|
}
|
|
3505
|
-
function expandCodexPath(value) {
|
|
3506
|
-
let expanded = value;
|
|
3507
|
-
if (expanded === "~") return os.homedir();
|
|
3508
|
-
if (expanded.startsWith("~/")) expanded = path.join(os.homedir(), expanded.slice(2));
|
|
3509
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
3510
|
-
const name = braced ?? bare;
|
|
3511
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
3512
|
-
});
|
|
3513
|
-
}
|
|
3514
3558
|
|
|
3515
3559
|
// src/providers/codex-auth.ts
|
|
3516
3560
|
var execFileAsync = promisify(execFile);
|
|
@@ -3733,10 +3777,10 @@ var CodexAuthStore = class {
|
|
|
3733
3777
|
}
|
|
3734
3778
|
};
|
|
3735
3779
|
function authFilePath(codexHome) {
|
|
3736
|
-
return
|
|
3780
|
+
return path3.join(codexHome, "auth.json");
|
|
3737
3781
|
}
|
|
3738
3782
|
function codexKeyringAccount(codexHome) {
|
|
3739
|
-
const resolved =
|
|
3783
|
+
const resolved = path3.resolve(codexHome);
|
|
3740
3784
|
const canonical = fs2.existsSync(resolved) ? fs2.realpathSync.native(resolved) : resolved;
|
|
3741
3785
|
const hash = crypto2.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
3742
3786
|
return `cli|${hash}`;
|
|
@@ -4165,8 +4209,8 @@ import { randomUUID } from "node:crypto";
|
|
|
4165
4209
|
import { execFile as execFile2 } from "node:child_process";
|
|
4166
4210
|
import fs3 from "node:fs";
|
|
4167
4211
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
4168
|
-
import
|
|
4169
|
-
import
|
|
4212
|
+
import os3 from "node:os";
|
|
4213
|
+
import path4 from "node:path";
|
|
4170
4214
|
import { promisify as promisify2 } from "node:util";
|
|
4171
4215
|
var execFileAsync2 = promisify2(execFile2);
|
|
4172
4216
|
var CLAUDE_CODE_KEYRING_SERVICE = "Claude Code-credentials";
|
|
@@ -4199,7 +4243,7 @@ function getSharedClaudeCodeAuthStore(options2 = {}) {
|
|
|
4199
4243
|
proactiveRefreshMinutes: options2.proactiveRefreshMinutes ?? 30,
|
|
4200
4244
|
refreshCache: options2.refreshCache ?? "claude-store",
|
|
4201
4245
|
refreshTokenURL: options2.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL,
|
|
4202
|
-
keychainAccount: options2.keychainAccount ??
|
|
4246
|
+
keychainAccount: options2.keychainAccount ?? os3.userInfo().username
|
|
4203
4247
|
});
|
|
4204
4248
|
const existing = sharedAuthStores2.get(key);
|
|
4205
4249
|
if (existing) return existing;
|
|
@@ -4228,7 +4272,7 @@ var ClaudeCodeAuthStore = class {
|
|
|
4228
4272
|
this.#proactiveRefreshMinutes = options2.proactiveRefreshMinutes ?? 30;
|
|
4229
4273
|
this.#refreshCache = options2.refreshCache ?? "claude-store";
|
|
4230
4274
|
this.#refreshTokenURL = options2.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL;
|
|
4231
|
-
this.#keychainAccount = options2.keychainAccount ??
|
|
4275
|
+
this.#keychainAccount = options2.keychainAccount ?? os3.userInfo().username;
|
|
4232
4276
|
this.#fetch = options2.fetch ?? fetch;
|
|
4233
4277
|
this.#keyring = options2.keyring ?? new MacOSSecurityKeyring2();
|
|
4234
4278
|
}
|
|
@@ -4429,10 +4473,10 @@ var ClaudeCodeAuthStore = class {
|
|
|
4429
4473
|
}
|
|
4430
4474
|
};
|
|
4431
4475
|
function resolveClaudeConfigDir(configured) {
|
|
4432
|
-
return
|
|
4476
|
+
return resolveExpandedPath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path4.join(os3.homedir(), ".claude"));
|
|
4433
4477
|
}
|
|
4434
4478
|
function claudeCodeAuthFilePath(claudeConfigDir) {
|
|
4435
|
-
return
|
|
4479
|
+
return path4.join(claudeConfigDir, ".credentials.json");
|
|
4436
4480
|
}
|
|
4437
4481
|
function oauthPayload(auth) {
|
|
4438
4482
|
const raw = auth.claudeAiOauth;
|
|
@@ -4491,15 +4535,6 @@ async function writeClaudeCodeAuthFileAtomic(claudeConfigDir, auth) {
|
|
|
4491
4535
|
await chmod2(tempPath, 384);
|
|
4492
4536
|
await rename2(tempPath, filePath);
|
|
4493
4537
|
}
|
|
4494
|
-
function expandClaudePath(value) {
|
|
4495
|
-
let expanded = value;
|
|
4496
|
-
if (expanded === "~") return os2.homedir();
|
|
4497
|
-
if (expanded.startsWith("~/")) expanded = path3.join(os2.homedir(), expanded.slice(2));
|
|
4498
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
4499
|
-
const name = braced ?? bare;
|
|
4500
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
4501
|
-
});
|
|
4502
|
-
}
|
|
4503
4538
|
var MacOSSecurityKeyring2 = class {
|
|
4504
4539
|
async load(service, account) {
|
|
4505
4540
|
if (process.platform !== "darwin") return void 0;
|
|
@@ -4736,6 +4771,111 @@ function parseClaudeCodeErrorBody(body) {
|
|
|
4736
4771
|
}
|
|
4737
4772
|
}
|
|
4738
4773
|
|
|
4774
|
+
// src/providers/ollama.ts
|
|
4775
|
+
var OllamaProvider = class {
|
|
4776
|
+
id = "ollama";
|
|
4777
|
+
#baseURL;
|
|
4778
|
+
#apiKey;
|
|
4779
|
+
#defaultModel;
|
|
4780
|
+
#fetch;
|
|
4781
|
+
#onRetry;
|
|
4782
|
+
constructor(config) {
|
|
4783
|
+
this.#baseURL = config.baseURL.replace(/\/+$/, "");
|
|
4784
|
+
this.#apiKey = config.apiKey;
|
|
4785
|
+
this.#defaultModel = config.defaultModel;
|
|
4786
|
+
this.#fetch = config.fetch ?? fetch;
|
|
4787
|
+
this.#onRetry = config.onRetry;
|
|
4788
|
+
}
|
|
4789
|
+
async chat(req) {
|
|
4790
|
+
return chatWithRetry(() => this.#rawChat(req), {
|
|
4791
|
+
signal: req.signal,
|
|
4792
|
+
onRetry: this.#onRetry
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
async #rawChat(req) {
|
|
4796
|
+
const response = await this.#fetch(`${this.#baseURL}/chat`, {
|
|
4797
|
+
method: "POST",
|
|
4798
|
+
headers: {
|
|
4799
|
+
"content-type": "application/json",
|
|
4800
|
+
...this.#authHeaders()
|
|
4801
|
+
},
|
|
4802
|
+
body: JSON.stringify({
|
|
4803
|
+
model: req.model || this.#defaultModel,
|
|
4804
|
+
messages: req.messages.map(toOllamaMessage),
|
|
4805
|
+
...req.tools.length > 0 ? { tools: req.tools.map(toOpenAITool) } : {},
|
|
4806
|
+
stream: false,
|
|
4807
|
+
...req.temperature !== void 0 ? { options: { temperature: req.temperature } } : {}
|
|
4808
|
+
}),
|
|
4809
|
+
signal: req.signal
|
|
4810
|
+
});
|
|
4811
|
+
const text = await response.text();
|
|
4812
|
+
if (!response.ok) throw new ProviderHttpError(response.status, text, parseRetryAfter(response.headers.get("retry-after")));
|
|
4813
|
+
return normalizeOllamaResponse(text ? JSON.parse(text) : {});
|
|
4814
|
+
}
|
|
4815
|
+
#authHeaders() {
|
|
4816
|
+
return this.#apiKey ? { authorization: `Bearer ${this.#apiKey}` } : {};
|
|
4817
|
+
}
|
|
4818
|
+
};
|
|
4819
|
+
function toOllamaMessage(message) {
|
|
4820
|
+
if (message.role === "tool") {
|
|
4821
|
+
return {
|
|
4822
|
+
role: "tool",
|
|
4823
|
+
content: message.content,
|
|
4824
|
+
tool_name: message.name
|
|
4825
|
+
};
|
|
4826
|
+
}
|
|
4827
|
+
const out = {
|
|
4828
|
+
role: message.role,
|
|
4829
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "")
|
|
4830
|
+
};
|
|
4831
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
4832
|
+
out.tool_calls = message.toolCalls.map((call) => ({
|
|
4833
|
+
function: {
|
|
4834
|
+
name: call.name,
|
|
4835
|
+
arguments: call.input ?? {}
|
|
4836
|
+
}
|
|
4837
|
+
}));
|
|
4838
|
+
}
|
|
4839
|
+
return out;
|
|
4840
|
+
}
|
|
4841
|
+
function normalizeOllamaResponse(raw) {
|
|
4842
|
+
const data = raw;
|
|
4843
|
+
const toolCalls = [];
|
|
4844
|
+
for (const call of data.message?.tool_calls ?? []) {
|
|
4845
|
+
const name = call.function?.name;
|
|
4846
|
+
if (!name) continue;
|
|
4847
|
+
toolCalls.push({
|
|
4848
|
+
id: call.id ?? `call_${toolCalls.length + 1}`,
|
|
4849
|
+
name,
|
|
4850
|
+
input: normalizeToolArguments(call.function?.arguments)
|
|
4851
|
+
});
|
|
4852
|
+
}
|
|
4853
|
+
const message = {
|
|
4854
|
+
role: "assistant",
|
|
4855
|
+
content: data.message?.content ?? null,
|
|
4856
|
+
...toolCalls.length ? { toolCalls } : {}
|
|
4857
|
+
};
|
|
4858
|
+
return {
|
|
4859
|
+
message,
|
|
4860
|
+
toolCalls,
|
|
4861
|
+
stopReason: toolCalls.length ? "tool_use" : data.done_reason === "length" ? "max_tokens" : "end_turn",
|
|
4862
|
+
usage: data.prompt_eval_count !== void 0 || data.eval_count !== void 0 ? {
|
|
4863
|
+
inputTokens: data.prompt_eval_count,
|
|
4864
|
+
outputTokens: data.eval_count,
|
|
4865
|
+
totalTokens: (data.prompt_eval_count ?? 0) + (data.eval_count ?? 0)
|
|
4866
|
+
} : void 0,
|
|
4867
|
+
raw
|
|
4868
|
+
};
|
|
4869
|
+
}
|
|
4870
|
+
function normalizeToolArguments(value) {
|
|
4871
|
+
if (typeof value !== "string") return value ?? {};
|
|
4872
|
+
try {
|
|
4873
|
+
return JSON.parse(value);
|
|
4874
|
+
} catch {
|
|
4875
|
+
return {};
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4739
4879
|
// src/external-runtime/claudecode-cli.ts
|
|
4740
4880
|
import { spawn as spawn3 } from "node:child_process";
|
|
4741
4881
|
import readline from "node:readline";
|
|
@@ -4743,7 +4883,7 @@ import readline from "node:readline";
|
|
|
4743
4883
|
// src/external-runtime/claudecode-attachments.ts
|
|
4744
4884
|
import crypto3 from "node:crypto";
|
|
4745
4885
|
import fs4 from "node:fs";
|
|
4746
|
-
import
|
|
4886
|
+
import path5 from "node:path";
|
|
4747
4887
|
async function resolveClaudeCodeAttachmentPrompt(runtimeLabel, config, req) {
|
|
4748
4888
|
const attachments = req.attachments ?? [];
|
|
4749
4889
|
if (attachments.length === 0) return req.prompt;
|
|
@@ -4785,7 +4925,7 @@ function unsupportedAttachmentError(runtimeLabel, count, detail) {
|
|
|
4785
4925
|
}
|
|
4786
4926
|
function formatAttachmentReference(value, cwd) {
|
|
4787
4927
|
if (isUrl(value)) return imageMimeFromPath(value) ? `[image: ${value} (${imageMimeFromPath(value)}, remote)]` : `[attachment: ${value}]`;
|
|
4788
|
-
const target =
|
|
4928
|
+
const target = path5.isAbsolute(value) ? value : path5.resolve(cwd, value);
|
|
4789
4929
|
try {
|
|
4790
4930
|
const stats = fs4.statSync(target);
|
|
4791
4931
|
if (!stats.isFile()) return `[attachment: ${value} (${stats.size} bytes)]`;
|
|
@@ -4802,7 +4942,7 @@ function isUrl(value) {
|
|
|
4802
4942
|
}
|
|
4803
4943
|
function imageMimeFromPath(value) {
|
|
4804
4944
|
const pathname = isUrl(value) ? new URL(value).pathname : value;
|
|
4805
|
-
const ext =
|
|
4945
|
+
const ext = path5.extname(pathname).toLowerCase();
|
|
4806
4946
|
if (ext === ".png") return "image/png";
|
|
4807
4947
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
4808
4948
|
if (ext === ".gif") return "image/gif";
|
|
@@ -4834,15 +4974,14 @@ function hasAnthropicApiKeyEnv(env = process.env) {
|
|
|
4834
4974
|
|
|
4835
4975
|
// src/external-runtime/claudecode-paths.ts
|
|
4836
4976
|
import fs5 from "node:fs";
|
|
4837
|
-
import
|
|
4838
|
-
import path5 from "node:path";
|
|
4977
|
+
import path6 from "node:path";
|
|
4839
4978
|
function resolveClaudeCodeCliPath(configured) {
|
|
4840
4979
|
if (configured) return expandHome(configured);
|
|
4841
4980
|
if (process.env.CLAUDE_CODE_CLI) return expandHome(process.env.CLAUDE_CODE_CLI);
|
|
4842
4981
|
const candidates = ["~/.local/bin/claude", "claude"];
|
|
4843
4982
|
for (const candidate of candidates) {
|
|
4844
4983
|
const expanded = expandHome(candidate);
|
|
4845
|
-
if (
|
|
4984
|
+
if (path6.isAbsolute(expanded)) {
|
|
4846
4985
|
if (isExecutableFile(expanded)) return expanded;
|
|
4847
4986
|
continue;
|
|
4848
4987
|
}
|
|
@@ -4851,9 +4990,7 @@ function resolveClaudeCodeCliPath(configured) {
|
|
|
4851
4990
|
return void 0;
|
|
4852
4991
|
}
|
|
4853
4992
|
function expandHome(value) {
|
|
4854
|
-
|
|
4855
|
-
if (value.startsWith("~/")) return path5.join(os3.homedir(), value.slice(2));
|
|
4856
|
-
return value;
|
|
4993
|
+
return expandPathReferences(value);
|
|
4857
4994
|
}
|
|
4858
4995
|
function isExecutableFile(filePath) {
|
|
4859
4996
|
try {
|
|
@@ -5119,14 +5256,14 @@ function numberValue2(value) {
|
|
|
5119
5256
|
// src/external-runtime/session-lock.ts
|
|
5120
5257
|
import { mkdir as mkdir4, open, readFile as readFile4, unlink } from "node:fs/promises";
|
|
5121
5258
|
import os4 from "node:os";
|
|
5122
|
-
import
|
|
5259
|
+
import path7 from "node:path";
|
|
5123
5260
|
var DEFAULT_STALE_MS = 30 * 60 * 1e3;
|
|
5124
5261
|
async function acquireClaudeCodeSessionLock(sessionId, options2 = {}) {
|
|
5125
|
-
const dir = options2.dir ??
|
|
5262
|
+
const dir = options2.dir ?? path7.join(os4.homedir(), ".demian", "claude-sessions");
|
|
5126
5263
|
const staleMs = options2.staleMs ?? DEFAULT_STALE_MS;
|
|
5127
5264
|
const now2 = options2.now ?? (() => Date.now());
|
|
5128
5265
|
await mkdir4(dir, { recursive: true });
|
|
5129
|
-
const lockPath =
|
|
5266
|
+
const lockPath = path7.join(dir, `${encodeURIComponent(sessionId)}.lock`);
|
|
5130
5267
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5131
5268
|
try {
|
|
5132
5269
|
const handle = await open(lockPath, "wx");
|
|
@@ -5190,14 +5327,14 @@ function isAlreadyExists(error) {
|
|
|
5190
5327
|
// src/external-runtime/usage-ledger.ts
|
|
5191
5328
|
import { mkdir as mkdir5, readFile as readFile5, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
|
|
5192
5329
|
import os5 from "node:os";
|
|
5193
|
-
import
|
|
5330
|
+
import path8 from "node:path";
|
|
5194
5331
|
var processLedger = /* @__PURE__ */ new Map();
|
|
5195
5332
|
var ClaudeCodeUsageLedger = class {
|
|
5196
5333
|
#scope;
|
|
5197
5334
|
#filePath;
|
|
5198
5335
|
constructor(options2 = {}) {
|
|
5199
5336
|
this.#scope = options2.scope ?? "process";
|
|
5200
|
-
this.#filePath = options2.filePath ??
|
|
5337
|
+
this.#filePath = options2.filePath ?? path8.join(os5.homedir(), ".demian", "usage-ledger.json");
|
|
5201
5338
|
}
|
|
5202
5339
|
async spentUsd(key, now2 = /* @__PURE__ */ new Date()) {
|
|
5203
5340
|
if (this.#scope === "process") return processLedger.get(processKey(key)) ?? 0;
|
|
@@ -5228,7 +5365,7 @@ var ClaudeCodeUsageLedger = class {
|
|
|
5228
5365
|
return { version: 1, buckets: {} };
|
|
5229
5366
|
}
|
|
5230
5367
|
async #write(file) {
|
|
5231
|
-
await mkdir5(
|
|
5368
|
+
await mkdir5(path8.dirname(this.#filePath), { recursive: true });
|
|
5232
5369
|
const temp = `${this.#filePath}.${process.pid}.tmp`;
|
|
5233
5370
|
await writeFile4(temp, `${JSON.stringify(file, null, 2)}
|
|
5234
5371
|
`, "utf8");
|
|
@@ -24889,10 +25026,10 @@ function now() {
|
|
|
24889
25026
|
}
|
|
24890
25027
|
|
|
24891
25028
|
// src/permissions/engine.ts
|
|
24892
|
-
import
|
|
25029
|
+
import path11 from "node:path";
|
|
24893
25030
|
|
|
24894
25031
|
// src/permissions/grants.ts
|
|
24895
|
-
import
|
|
25032
|
+
import path9 from "node:path";
|
|
24896
25033
|
|
|
24897
25034
|
// src/util.ts
|
|
24898
25035
|
function stableStringify(value) {
|
|
@@ -24947,8 +25084,8 @@ function grantKeyFor(tool, input2, cwd) {
|
|
|
24947
25084
|
}
|
|
24948
25085
|
if (tool === "write_file" || tool === "edit_file") {
|
|
24949
25086
|
const filePath = typeof object2.path === "string" ? object2.path : "";
|
|
24950
|
-
const parent = filePath ?
|
|
24951
|
-
return `${tool}:${
|
|
25087
|
+
const parent = filePath ? path9.dirname(path9.resolve(cwd, filePath)) : cwd;
|
|
25088
|
+
return `${tool}:${path9.relative(cwd, parent) || "."}`;
|
|
24952
25089
|
}
|
|
24953
25090
|
return `${tool}:${stableStringify(input2)}`;
|
|
24954
25091
|
}
|
|
@@ -24975,30 +25112,30 @@ function firstCommandTokens(command, count) {
|
|
|
24975
25112
|
}
|
|
24976
25113
|
|
|
24977
25114
|
// src/workspace/paths.ts
|
|
24978
|
-
import
|
|
25115
|
+
import path10 from "node:path";
|
|
24979
25116
|
function normalizePathForMatch(value) {
|
|
24980
|
-
return value.split(
|
|
25117
|
+
return value.split(path10.sep).join("/");
|
|
24981
25118
|
}
|
|
24982
25119
|
function isInsidePath(root, candidate) {
|
|
24983
|
-
const relative =
|
|
24984
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
25120
|
+
const relative = path10.relative(path10.resolve(root), path10.resolve(candidate));
|
|
25121
|
+
return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
|
|
24985
25122
|
}
|
|
24986
25123
|
function resolveInsideCwd(cwd, inputPath) {
|
|
24987
25124
|
if (typeof inputPath !== "string" || inputPath.length === 0) {
|
|
24988
25125
|
throw new Error("path must be a non-empty string");
|
|
24989
25126
|
}
|
|
24990
|
-
const resolved =
|
|
25127
|
+
const resolved = path10.resolve(cwd, inputPath);
|
|
24991
25128
|
if (!isInsidePath(cwd, resolved)) {
|
|
24992
25129
|
throw new Error(`Path is outside the workspace: ${inputPath}`);
|
|
24993
25130
|
}
|
|
24994
25131
|
return resolved;
|
|
24995
25132
|
}
|
|
24996
25133
|
function relativeToCwd(cwd, absolutePath) {
|
|
24997
|
-
const relative =
|
|
25134
|
+
const relative = path10.relative(cwd, absolutePath);
|
|
24998
25135
|
return normalizePathForMatch(relative || ".");
|
|
24999
25136
|
}
|
|
25000
25137
|
function isEnvFile(filePath) {
|
|
25001
|
-
const base =
|
|
25138
|
+
const base = path10.basename(filePath);
|
|
25002
25139
|
if (base === ".env.example") return false;
|
|
25003
25140
|
return base === ".env" || base.startsWith(".env.");
|
|
25004
25141
|
}
|
|
@@ -25105,7 +25242,7 @@ var PermissionEngine = class {
|
|
|
25105
25242
|
#hardDeny(tool, input2, grantKey) {
|
|
25106
25243
|
const paths = inputPaths(tool, input2);
|
|
25107
25244
|
for (const item of paths) {
|
|
25108
|
-
const resolved =
|
|
25245
|
+
const resolved = path11.resolve(this.#cwd, item);
|
|
25109
25246
|
if (!isInsidePath(this.#cwd, resolved)) {
|
|
25110
25247
|
return {
|
|
25111
25248
|
decision: "deny",
|
|
@@ -25159,7 +25296,7 @@ function matchesRule(rule, tool, input2, cwd) {
|
|
|
25159
25296
|
if (rule.match.pathGlob) {
|
|
25160
25297
|
const paths = inputPaths(tool, input2);
|
|
25161
25298
|
if (paths.length === 0) return false;
|
|
25162
|
-
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd,
|
|
25299
|
+
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path11.resolve(cwd, item))))) {
|
|
25163
25300
|
return false;
|
|
25164
25301
|
}
|
|
25165
25302
|
}
|
|
@@ -25206,7 +25343,7 @@ function rulePriority(rule, ref) {
|
|
|
25206
25343
|
}
|
|
25207
25344
|
function matchesPathRule(pattern, relativePath) {
|
|
25208
25345
|
if (!pattern) return true;
|
|
25209
|
-
if (
|
|
25346
|
+
if (path11.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
|
|
25210
25347
|
return matchGlob(pattern, relativePath);
|
|
25211
25348
|
}
|
|
25212
25349
|
function mostSpecificRule(rules, input2, cwd, ref) {
|
|
@@ -25284,13 +25421,13 @@ function permissionLabel(req) {
|
|
|
25284
25421
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
25285
25422
|
return `${req.tool}: ${inputObject.path}`;
|
|
25286
25423
|
}
|
|
25287
|
-
const
|
|
25288
|
-
if (
|
|
25424
|
+
const path36 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
25425
|
+
if (path36) return `${tool}: ${path36}`;
|
|
25289
25426
|
return tool;
|
|
25290
25427
|
}
|
|
25291
25428
|
|
|
25292
25429
|
// src/workspace/write-scope.ts
|
|
25293
|
-
import
|
|
25430
|
+
import path12 from "node:path";
|
|
25294
25431
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
25295
25432
|
var WORKSPACE_SCOPE = "**";
|
|
25296
25433
|
function normalizeWriteScope(cwd, scope) {
|
|
@@ -25312,7 +25449,7 @@ function enforceToolWriteScope(input2) {
|
|
|
25312
25449
|
if (paths.length === 0) return { ok: true, paths: [] };
|
|
25313
25450
|
const checked = [];
|
|
25314
25451
|
for (const target of paths) {
|
|
25315
|
-
const resolved =
|
|
25452
|
+
const resolved = path12.resolve(input2.cwd, target);
|
|
25316
25453
|
const relative = relativeToCwd(input2.cwd, resolved);
|
|
25317
25454
|
checked.push(relative);
|
|
25318
25455
|
const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
|
|
@@ -25336,7 +25473,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
|
|
|
25336
25473
|
return out;
|
|
25337
25474
|
}
|
|
25338
25475
|
function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
|
|
25339
|
-
const resolved =
|
|
25476
|
+
const resolved = path12.resolve(cwd, inputPath);
|
|
25340
25477
|
if (!isInsidePath(cwd, resolved)) {
|
|
25341
25478
|
return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
|
|
25342
25479
|
}
|
|
@@ -25355,9 +25492,9 @@ function normalizeScopePattern(cwd, raw) {
|
|
|
25355
25492
|
if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
|
|
25356
25493
|
if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
|
|
25357
25494
|
const hasGlob2 = /[*?[\]{}]/.test(value);
|
|
25358
|
-
if (
|
|
25495
|
+
if (path12.isAbsolute(value)) {
|
|
25359
25496
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
25360
|
-
const resolved =
|
|
25497
|
+
const resolved = path12.resolve(value);
|
|
25361
25498
|
if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
|
|
25362
25499
|
value = relativeToCwd(cwd, resolved);
|
|
25363
25500
|
}
|
|
@@ -25734,6 +25871,7 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
|
|
|
25734
25871
|
}
|
|
25735
25872
|
|
|
25736
25873
|
// src/config.ts
|
|
25874
|
+
var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
|
|
25737
25875
|
var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
|
|
25738
25876
|
var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
|
|
25739
25877
|
var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
|
|
@@ -25771,7 +25909,7 @@ var defaultConfig = {
|
|
|
25771
25909
|
maxConcurrentExpensive: 1,
|
|
25772
25910
|
maxConcurrentWriters: 1,
|
|
25773
25911
|
maxConcurrentPerProvider: {
|
|
25774
|
-
|
|
25912
|
+
claudecode: 1
|
|
25775
25913
|
},
|
|
25776
25914
|
tokenBudget: null,
|
|
25777
25915
|
defaultMergeStrategy: "synthesize",
|
|
@@ -25893,73 +26031,102 @@ var defaultConfig = {
|
|
|
25893
26031
|
providers: {
|
|
25894
26032
|
openai: {
|
|
25895
26033
|
type: "openai-compatible",
|
|
25896
|
-
model: "openai-model-name",
|
|
25897
26034
|
baseURL: "https://api.openai.com/v1",
|
|
25898
|
-
apiKeyEnv: "OPENAI_API_KEY"
|
|
26035
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
26036
|
+
catalog: {
|
|
26037
|
+
type: "openai-models",
|
|
26038
|
+
endpoint: "https://api.openai.com/v1/models"
|
|
26039
|
+
}
|
|
26040
|
+
},
|
|
26041
|
+
anthropic: {
|
|
26042
|
+
type: "anthropic",
|
|
26043
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
26044
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
26045
|
+
catalog: {
|
|
26046
|
+
type: "anthropic-models",
|
|
26047
|
+
endpoint: "https://api.anthropic.com/v1/models"
|
|
26048
|
+
}
|
|
25899
26049
|
},
|
|
25900
26050
|
gemini: {
|
|
25901
26051
|
type: "openai-compatible",
|
|
25902
|
-
model: "gemini-model-name",
|
|
25903
26052
|
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
25904
|
-
apiKeyEnv: "
|
|
26053
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
26054
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
26055
|
+
catalog: {
|
|
26056
|
+
type: "gemini-openai-models",
|
|
26057
|
+
endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models"
|
|
26058
|
+
}
|
|
25905
26059
|
},
|
|
25906
|
-
|
|
26060
|
+
groq: {
|
|
25907
26061
|
type: "openai-compatible",
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
26062
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
26063
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
26064
|
+
catalog: {
|
|
26065
|
+
type: "groq-models",
|
|
26066
|
+
endpoint: "https://api.groq.com/openai/v1/models"
|
|
26067
|
+
}
|
|
26068
|
+
},
|
|
26069
|
+
azure: {
|
|
26070
|
+
type: "openai-compatible",
|
|
26071
|
+
auth: { type: "api-key", header: "api-key" },
|
|
26072
|
+
modelProfiles: [
|
|
26073
|
+
{
|
|
26074
|
+
name: "azure-example",
|
|
26075
|
+
displayName: "Azure example",
|
|
26076
|
+
model: "azure-deployment-name",
|
|
26077
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
26078
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
26079
|
+
}
|
|
26080
|
+
]
|
|
25912
26081
|
},
|
|
25913
26082
|
lmstudio: {
|
|
25914
26083
|
type: "openai-compatible",
|
|
25915
|
-
model: "local-coder-model",
|
|
25916
26084
|
baseURL: "http://localhost:1234/v1",
|
|
25917
|
-
apiKey: "lm-studio"
|
|
26085
|
+
apiKey: "lm-studio",
|
|
26086
|
+
catalog: {
|
|
26087
|
+
type: "openai-compatible-models",
|
|
26088
|
+
endpoint: "http://localhost:1234/v1/models"
|
|
26089
|
+
}
|
|
25918
26090
|
},
|
|
25919
|
-
|
|
26091
|
+
"ollama-local": {
|
|
25920
26092
|
type: "openai-compatible",
|
|
25921
|
-
|
|
25922
|
-
|
|
25923
|
-
|
|
26093
|
+
baseURL: "http://localhost:11434/v1",
|
|
26094
|
+
apiKey: "ollama",
|
|
26095
|
+
quirks: { omitTemperature: true },
|
|
26096
|
+
catalog: {
|
|
26097
|
+
type: "openai-compatible-models",
|
|
26098
|
+
endpoint: "http://localhost:11434/v1/models"
|
|
26099
|
+
}
|
|
26100
|
+
},
|
|
26101
|
+
"ollama-cloud": {
|
|
26102
|
+
type: "ollama",
|
|
26103
|
+
baseURL: "https://ollama.com/api",
|
|
26104
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
26105
|
+
catalog: {
|
|
26106
|
+
type: "ollama-tags",
|
|
26107
|
+
endpoint: "https://ollama.com/api/tags"
|
|
26108
|
+
}
|
|
25924
26109
|
},
|
|
25925
26110
|
llamacpp: {
|
|
25926
26111
|
type: "openai-compatible",
|
|
25927
|
-
model: "local-coder-model",
|
|
25928
26112
|
baseURL: "http://localhost:8080/v1",
|
|
25929
|
-
apiKey: "llama.cpp"
|
|
25930
|
-
|
|
25931
|
-
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
baseURL: "https://openrouter.ai/api/v1",
|
|
25935
|
-
apiKeyEnv: "OPENROUTER_API_KEY"
|
|
25936
|
-
},
|
|
25937
|
-
together: {
|
|
25938
|
-
type: "openai-compatible",
|
|
25939
|
-
model: "provider/model-name",
|
|
25940
|
-
baseURL: "https://api.together.xyz/v1",
|
|
25941
|
-
apiKeyEnv: "TOGETHER_API_KEY"
|
|
25942
|
-
},
|
|
25943
|
-
groq: {
|
|
25944
|
-
type: "openai-compatible",
|
|
25945
|
-
model: "provider/model-name",
|
|
25946
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
25947
|
-
apiKeyEnv: "GROQ_API_KEY"
|
|
26113
|
+
apiKey: "llama.cpp",
|
|
26114
|
+
catalog: {
|
|
26115
|
+
type: "openai-compatible-models",
|
|
26116
|
+
endpoint: "http://localhost:8080/v1/models"
|
|
26117
|
+
}
|
|
25948
26118
|
},
|
|
25949
|
-
|
|
26119
|
+
vllm: {
|
|
25950
26120
|
type: "openai-compatible",
|
|
25951
|
-
|
|
25952
|
-
|
|
25953
|
-
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
|
|
25957
|
-
model: "anthropic-model-name",
|
|
25958
|
-
apiKeyEnv: "ANTHROPIC_API_KEY"
|
|
26121
|
+
baseURL: "http://localhost:8000/v1",
|
|
26122
|
+
apiKey: "vllm",
|
|
26123
|
+
catalog: {
|
|
26124
|
+
type: "openai-compatible-models",
|
|
26125
|
+
endpoint: "http://localhost:8000/v1/models"
|
|
26126
|
+
}
|
|
25959
26127
|
},
|
|
25960
26128
|
codex: {
|
|
25961
26129
|
type: "codex",
|
|
25962
|
-
model: "gpt-5.1-codex",
|
|
25963
26130
|
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
25964
26131
|
authStore: "auto",
|
|
25965
26132
|
allowApiKeyFallback: false,
|
|
@@ -25971,6 +26138,9 @@ var defaultConfig = {
|
|
|
25971
26138
|
effort: "medium",
|
|
25972
26139
|
summary: "auto"
|
|
25973
26140
|
}
|
|
26141
|
+
},
|
|
26142
|
+
catalog: {
|
|
26143
|
+
type: "codex-oauth-models"
|
|
25974
26144
|
}
|
|
25975
26145
|
},
|
|
25976
26146
|
claudecode: {
|
|
@@ -25989,67 +26159,37 @@ var defaultConfig = {
|
|
|
25989
26159
|
useBareMode: false,
|
|
25990
26160
|
usageLedgerScope: "process",
|
|
25991
26161
|
sessionLock: true,
|
|
25992
|
-
abortPolicy: "record-only"
|
|
25993
|
-
},
|
|
25994
|
-
"claudecode-plan": {
|
|
25995
|
-
type: "claudecode",
|
|
25996
|
-
runtime: "agent-sdk",
|
|
25997
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
25998
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
25999
|
-
permissionProfile: "plan",
|
|
26000
|
-
cliPath: "~/.local/bin/claude",
|
|
26001
|
-
cwdMode: "session",
|
|
26002
|
-
historyPolicy: "passthrough-resume",
|
|
26003
|
-
onInvalidResume: "fresh",
|
|
26004
|
-
attachmentFallback: "block",
|
|
26005
|
-
allowSubagents: false,
|
|
26006
|
-
sanitizeApiKeyEnv: true,
|
|
26007
|
-
authPreflight: true,
|
|
26008
|
-
useBareMode: false,
|
|
26009
|
-
usageLedgerScope: "process",
|
|
26010
|
-
sessionLock: true,
|
|
26011
|
-
abortPolicy: "record-only",
|
|
26012
|
-
hidden: true
|
|
26013
|
-
},
|
|
26014
|
-
"claudecode-subagent": {
|
|
26015
|
-
type: "claudecode",
|
|
26016
|
-
runtime: "agent-sdk",
|
|
26017
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
26018
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
26019
|
-
permissionProfile: "build",
|
|
26020
|
-
cliPath: "~/.local/bin/claude",
|
|
26021
|
-
cwdMode: "session",
|
|
26022
|
-
historyPolicy: "passthrough-resume",
|
|
26023
|
-
onInvalidResume: "fresh",
|
|
26024
|
-
attachmentFallback: "block",
|
|
26025
|
-
allowSubagents: false,
|
|
26026
|
-
sanitizeApiKeyEnv: true,
|
|
26027
|
-
authPreflight: true,
|
|
26028
|
-
useBareMode: false,
|
|
26029
|
-
usageLedgerScope: "process",
|
|
26030
|
-
sessionLock: true,
|
|
26031
26162
|
abortPolicy: "record-only",
|
|
26032
|
-
|
|
26163
|
+
catalog: {
|
|
26164
|
+
type: "claudecode-supported-models"
|
|
26165
|
+
}
|
|
26033
26166
|
}
|
|
26034
26167
|
}
|
|
26035
26168
|
};
|
|
26036
26169
|
async function loadConfig(options2) {
|
|
26037
26170
|
const configs = [];
|
|
26038
|
-
const userConfigDir =
|
|
26039
|
-
const
|
|
26040
|
-
const configPaths = [
|
|
26041
|
-
path12.join(userConfigDir, "config.json"),
|
|
26042
|
-
path12.join(userConfigDir, "config.jsond"),
|
|
26043
|
-
path12.join(projectConfigDir, "config.json"),
|
|
26044
|
-
path12.join(projectConfigDir, "config.jsond"),
|
|
26045
|
-
options2.configPath
|
|
26046
|
-
].filter(Boolean);
|
|
26171
|
+
const userConfigDir = path13.join(os7.homedir(), ".demian");
|
|
26172
|
+
const configPaths = [path13.join(userConfigDir, "config.json"), path13.join(userConfigDir, "config.jsond"), options2.configPath].filter(Boolean);
|
|
26047
26173
|
for (const filePath of configPaths) {
|
|
26048
26174
|
if (!fs7.existsSync(filePath)) continue;
|
|
26049
|
-
|
|
26175
|
+
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
26176
|
+
warnIfV1Config(parsed, filePath);
|
|
26177
|
+
configs.push(parsed);
|
|
26050
26178
|
}
|
|
26051
26179
|
return configs.reduce((acc, item) => mergeConfig(acc, item), structuredClone(defaultConfig));
|
|
26052
26180
|
}
|
|
26181
|
+
function warnIfV1Config(parsed, filePath) {
|
|
26182
|
+
if (parsed.version === 2) return;
|
|
26183
|
+
const providers = parsed.providers ?? {};
|
|
26184
|
+
const hasLegacyShape = Object.values(providers).some((provider) => {
|
|
26185
|
+
const candidate = provider;
|
|
26186
|
+
return (typeof candidate.model === "string" || Array.isArray(candidate.models)) && !Array.isArray(candidate.modelProfiles);
|
|
26187
|
+
});
|
|
26188
|
+
if (!hasLegacyShape && parsed.version === void 0 && Object.keys(providers).length === 0) return;
|
|
26189
|
+
if (!hasLegacyShape) return;
|
|
26190
|
+
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.
|
|
26191
|
+
`);
|
|
26192
|
+
}
|
|
26053
26193
|
function parseConfigText(text, filePath) {
|
|
26054
26194
|
try {
|
|
26055
26195
|
return JSON.parse(filePath.endsWith(".jsond") ? stripJsonD(text) : text);
|
|
@@ -26256,7 +26396,127 @@ function mergeConfig(base, overlay) {
|
|
|
26256
26396
|
};
|
|
26257
26397
|
}
|
|
26258
26398
|
function normalizeProviderAlias(providerName) {
|
|
26259
|
-
|
|
26399
|
+
if (providerName === "claudecode-plan" || providerName === "claudecode-subagent") return "claudecode";
|
|
26400
|
+
if (providerName === "ollama") return "ollama-local";
|
|
26401
|
+
return providerName;
|
|
26402
|
+
}
|
|
26403
|
+
function sortProviderNames(names) {
|
|
26404
|
+
const order = new Map(BUILTIN_PROVIDER_ORDER.map((name, index) => [name, index]));
|
|
26405
|
+
return [...names].sort((left, right) => {
|
|
26406
|
+
const leftIndex = order.get(left);
|
|
26407
|
+
const rightIndex = order.get(right);
|
|
26408
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) return leftIndex - rightIndex;
|
|
26409
|
+
if (leftIndex !== void 0) return -1;
|
|
26410
|
+
if (rightIndex !== void 0) return 1;
|
|
26411
|
+
return left.localeCompare(right);
|
|
26412
|
+
});
|
|
26413
|
+
}
|
|
26414
|
+
function providerModelProfiles(providerName, providerConfig) {
|
|
26415
|
+
const explicit = Array.isArray(providerConfig.modelProfiles) ? providerConfig.modelProfiles.filter((profile) => typeof profile?.model === "string" && profile.model.trim()).map((profile) => ({
|
|
26416
|
+
...profile,
|
|
26417
|
+
name: profile.name?.trim() || profile.displayName?.trim() || profile.model.trim(),
|
|
26418
|
+
displayName: profile.displayName?.trim() || profile.name?.trim() || profile.model.trim(),
|
|
26419
|
+
model: profile.model.trim()
|
|
26420
|
+
})) : [];
|
|
26421
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
26422
|
+
const seenDisplayNames = /* @__PURE__ */ new Set();
|
|
26423
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
26424
|
+
const out = [];
|
|
26425
|
+
for (const profile of explicit) {
|
|
26426
|
+
if (seenNames.has(profile.name)) {
|
|
26427
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile name "${profile.name}". Later entry ignored.
|
|
26428
|
+
`);
|
|
26429
|
+
continue;
|
|
26430
|
+
}
|
|
26431
|
+
if (profile.displayName && seenDisplayNames.has(profile.displayName)) {
|
|
26432
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile displayName "${profile.displayName}". Later entry ignored.
|
|
26433
|
+
`);
|
|
26434
|
+
continue;
|
|
26435
|
+
}
|
|
26436
|
+
seenNames.add(profile.name);
|
|
26437
|
+
if (profile.displayName) seenDisplayNames.add(profile.displayName);
|
|
26438
|
+
seenModels.add(profile.model);
|
|
26439
|
+
out.push(profile);
|
|
26440
|
+
}
|
|
26441
|
+
const legacy = [...typeof providerConfig.model === "string" && providerConfig.model.trim() ? [providerConfig.model.trim()] : [], ...(providerConfig.models ?? []).filter((model) => typeof model === "string" && model.trim())];
|
|
26442
|
+
for (const model of legacy) {
|
|
26443
|
+
const trimmed = model.trim();
|
|
26444
|
+
if (!trimmed || seenModels.has(trimmed)) continue;
|
|
26445
|
+
seenModels.add(trimmed);
|
|
26446
|
+
const name = trimmed;
|
|
26447
|
+
if (!seenNames.has(name)) seenNames.add(name);
|
|
26448
|
+
out.push({ name, displayName: name, model: trimmed });
|
|
26449
|
+
}
|
|
26450
|
+
const fallback = fallbackModelForProvider(providerName, providerConfig);
|
|
26451
|
+
if (out.length === 0 && fallback) out.push({ name: fallback, displayName: fallback, model: fallback });
|
|
26452
|
+
return out;
|
|
26453
|
+
}
|
|
26454
|
+
function providerModelOptions(providerName, providerConfig) {
|
|
26455
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
|
|
26456
|
+
}
|
|
26457
|
+
function defaultModelForProvider(providerName, providerConfig) {
|
|
26458
|
+
return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
|
|
26459
|
+
}
|
|
26460
|
+
function resolveConfiguredApiKey(providerConfig) {
|
|
26461
|
+
if (providerConfig.apiKey) return { value: providerConfig.apiKey };
|
|
26462
|
+
for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
|
|
26463
|
+
if (!envName) continue;
|
|
26464
|
+
const value = process.env[envName];
|
|
26465
|
+
if (value) return { value, envName };
|
|
26466
|
+
}
|
|
26467
|
+
return { envName: providerConfig.apiKeyEnv ?? providerConfig.apiKeyEnvAliases?.[0] };
|
|
26468
|
+
}
|
|
26469
|
+
function resolveProviderRuntimeConfig(config, selection) {
|
|
26470
|
+
const providerName = normalizeProviderAlias(selection.providerName);
|
|
26471
|
+
const providerConfig = resolveProviderConfig(config, providerName);
|
|
26472
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
26473
|
+
let profile;
|
|
26474
|
+
if (selection.modelProfileName) profile = profiles.find((item) => item.name === selection.modelProfileName);
|
|
26475
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.displayName === selection.model);
|
|
26476
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.model === selection.model);
|
|
26477
|
+
if (!profile) profile = profiles[0];
|
|
26478
|
+
if (!profile) {
|
|
26479
|
+
const model = selection.model ?? defaultModelForProvider(providerName, providerConfig);
|
|
26480
|
+
if (!model) throw new Error(`Provider ${providerName} has no model. Run \`demian config models ${providerName} --refresh\` or add a model profile.`);
|
|
26481
|
+
return { providerName, providerConfig, model };
|
|
26482
|
+
}
|
|
26483
|
+
const merged = mergeModelProfile(providerConfig, profile);
|
|
26484
|
+
return {
|
|
26485
|
+
providerName,
|
|
26486
|
+
providerConfig: merged,
|
|
26487
|
+
model: profile.model,
|
|
26488
|
+
modelProfileName: profile.name
|
|
26489
|
+
};
|
|
26490
|
+
}
|
|
26491
|
+
function mergeModelProfile(providerConfig, profile) {
|
|
26492
|
+
const merged = {
|
|
26493
|
+
...providerConfig,
|
|
26494
|
+
...definedOnly({
|
|
26495
|
+
baseURL: profile.baseURL,
|
|
26496
|
+
apiKey: profile.apiKey,
|
|
26497
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
26498
|
+
apiKeyEnvAliases: profile.apiKeyEnvAliases,
|
|
26499
|
+
auth: profile.auth,
|
|
26500
|
+
headers: profile.headers,
|
|
26501
|
+
quirks: profile.quirks,
|
|
26502
|
+
maxTokens: profile.maxTokens
|
|
26503
|
+
}),
|
|
26504
|
+
model: profile.model
|
|
26505
|
+
};
|
|
26506
|
+
return merged;
|
|
26507
|
+
}
|
|
26508
|
+
function definedOnly(input2) {
|
|
26509
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
|
|
26510
|
+
}
|
|
26511
|
+
function fallbackModelForProvider(providerName, providerConfig) {
|
|
26512
|
+
if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
|
|
26513
|
+
if (providerName === "openai") return "gpt-5.5";
|
|
26514
|
+
if (providerName === "anthropic") return CLAUDE_CODE_SONNET_MODEL;
|
|
26515
|
+
if (providerName === "codex" || providerConfig.type === "codex") return "gpt-5.5";
|
|
26516
|
+
if (providerName === "claudecode") return CLAUDE_CODE_SONNET_MODEL;
|
|
26517
|
+
if (providerName === "gemini") return "gemini-2.5-pro";
|
|
26518
|
+
if (providerName === "groq") return "openai/gpt-oss-120b";
|
|
26519
|
+
return void 0;
|
|
26260
26520
|
}
|
|
26261
26521
|
function migrateProviderConfig(provider, baseProvider) {
|
|
26262
26522
|
if (isLegacyClaudeCodeShape(provider)) {
|
|
@@ -26338,12 +26598,14 @@ function resolveAgentMode(config, flagMode) {
|
|
|
26338
26598
|
return "single-agent";
|
|
26339
26599
|
}
|
|
26340
26600
|
function resolveProviderConfig(config, providerName) {
|
|
26341
|
-
const
|
|
26601
|
+
const normalized = normalizeProviderAlias(providerName);
|
|
26602
|
+
const provider = config.providers[normalized];
|
|
26342
26603
|
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
26343
26604
|
return provider;
|
|
26344
26605
|
}
|
|
26345
26606
|
function resolveProvider(providerConfig, options2 = {}) {
|
|
26346
26607
|
const model = options2.model ?? providerConfig.model;
|
|
26608
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
26347
26609
|
if (providerConfig.type === "claudecode") {
|
|
26348
26610
|
throw new Error("claudecode is an external agent runtime. Use resolveExecutionBackend().");
|
|
26349
26611
|
}
|
|
@@ -26390,12 +26652,28 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26390
26652
|
};
|
|
26391
26653
|
}
|
|
26392
26654
|
if (providerConfig.type === "anthropic") {
|
|
26393
|
-
const apiKey2 =
|
|
26655
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
26394
26656
|
if (!apiKey2) {
|
|
26395
26657
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26396
26658
|
}
|
|
26397
26659
|
return {
|
|
26398
26660
|
provider: new AnthropicProvider({
|
|
26661
|
+
apiKey: apiKey2,
|
|
26662
|
+
baseURL: providerConfig.baseURL,
|
|
26663
|
+
defaultModel: model,
|
|
26664
|
+
onRetry: options2.onRetry
|
|
26665
|
+
}),
|
|
26666
|
+
model
|
|
26667
|
+
};
|
|
26668
|
+
}
|
|
26669
|
+
if (providerConfig.type === "ollama") {
|
|
26670
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
26671
|
+
if (!apiKey2) {
|
|
26672
|
+
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26673
|
+
}
|
|
26674
|
+
return {
|
|
26675
|
+
provider: new OllamaProvider({
|
|
26676
|
+
baseURL: providerConfig.baseURL,
|
|
26399
26677
|
apiKey: apiKey2,
|
|
26400
26678
|
defaultModel: model,
|
|
26401
26679
|
onRetry: options2.onRetry
|
|
@@ -26403,7 +26681,7 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26403
26681
|
model
|
|
26404
26682
|
};
|
|
26405
26683
|
}
|
|
26406
|
-
const apiKey =
|
|
26684
|
+
const apiKey = resolveConfiguredApiKey(providerConfig).value;
|
|
26407
26685
|
if (!apiKey) {
|
|
26408
26686
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26409
26687
|
}
|
|
@@ -26422,6 +26700,7 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26422
26700
|
}
|
|
26423
26701
|
function resolveExecutionBackend(providerConfig, options2 = {}) {
|
|
26424
26702
|
const model = options2.model ?? providerConfig.model;
|
|
26703
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
26425
26704
|
if (providerConfig.type === "claudecode") {
|
|
26426
26705
|
const runtimeConfig = options2.config || providerConfig.permissionProfile ? resolveClaudeCodeRuntimeConfig(providerConfig, options2.config ?? defaultConfig, options2.agent) : providerConfig;
|
|
26427
26706
|
return {
|
|
@@ -26465,7 +26744,8 @@ var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision",
|
|
|
26465
26744
|
|
|
26466
26745
|
// src/transcript.ts
|
|
26467
26746
|
import { mkdir as mkdir6, appendFile, writeFile as writeFile5 } from "node:fs/promises";
|
|
26468
|
-
import
|
|
26747
|
+
import os8 from "node:os";
|
|
26748
|
+
import path14 from "node:path";
|
|
26469
26749
|
var TranscriptWriter = class {
|
|
26470
26750
|
filePath;
|
|
26471
26751
|
#enabled;
|
|
@@ -26473,8 +26753,8 @@ var TranscriptWriter = class {
|
|
|
26473
26753
|
#queue = Promise.resolve();
|
|
26474
26754
|
constructor(options2) {
|
|
26475
26755
|
this.#enabled = options2.enabled !== false;
|
|
26476
|
-
const dir =
|
|
26477
|
-
this.filePath =
|
|
26756
|
+
const dir = path14.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
|
|
26757
|
+
this.filePath = path14.join(dir, "session.jsonl");
|
|
26478
26758
|
this.#ready = this.#enabled ? mkdir6(dir, { recursive: true }).then(() => writeFile5(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
26479
26759
|
}
|
|
26480
26760
|
write(event) {
|
|
@@ -26490,12 +26770,15 @@ var TranscriptWriter = class {
|
|
|
26490
26770
|
await this.#queue;
|
|
26491
26771
|
}
|
|
26492
26772
|
};
|
|
26773
|
+
function defaultDemianStorageDir() {
|
|
26774
|
+
return path14.join(os8.homedir(), ".demian");
|
|
26775
|
+
}
|
|
26493
26776
|
|
|
26494
26777
|
// src/external-runtime/snapshot-diff.ts
|
|
26495
26778
|
import { createHash as createHash2 } from "node:crypto";
|
|
26496
26779
|
import { createReadStream } from "node:fs";
|
|
26497
26780
|
import { readdir, readFile as readFile7, stat as stat3 } from "node:fs/promises";
|
|
26498
|
-
import
|
|
26781
|
+
import path15 from "node:path";
|
|
26499
26782
|
|
|
26500
26783
|
// src/workspace/diff.ts
|
|
26501
26784
|
var CONTEXT_LINES = 3;
|
|
@@ -26705,7 +26988,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
26705
26988
|
}
|
|
26706
26989
|
async function walk(root, relativeDir, entries, options2) {
|
|
26707
26990
|
if (entries.size >= options2.maxFiles) return;
|
|
26708
|
-
const absoluteDir =
|
|
26991
|
+
const absoluteDir = path15.join(root, relativeDir);
|
|
26709
26992
|
let items;
|
|
26710
26993
|
try {
|
|
26711
26994
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -26716,8 +26999,8 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
26716
26999
|
if (entries.size >= options2.maxFiles) return;
|
|
26717
27000
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
26718
27001
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
26719
|
-
const relativePath = normalizeRelativePath(
|
|
26720
|
-
const absolutePath =
|
|
27002
|
+
const relativePath = normalizeRelativePath(path15.join(relativeDir, item.name));
|
|
27003
|
+
const absolutePath = path15.join(root, relativePath);
|
|
26721
27004
|
if (item.isDirectory()) {
|
|
26722
27005
|
await walk(root, relativePath, entries, options2);
|
|
26723
27006
|
continue;
|
|
@@ -26753,7 +27036,7 @@ function sha256File(filePath) {
|
|
|
26753
27036
|
});
|
|
26754
27037
|
}
|
|
26755
27038
|
function normalizeRelativePath(value) {
|
|
26756
|
-
return value.split(
|
|
27039
|
+
return value.split(path15.sep).join("/");
|
|
26757
27040
|
}
|
|
26758
27041
|
|
|
26759
27042
|
// src/external-runtime/session-runner.ts
|
|
@@ -27188,7 +27471,7 @@ function safeJson(value) {
|
|
|
27188
27471
|
}
|
|
27189
27472
|
|
|
27190
27473
|
// src/hooks/dispatcher.ts
|
|
27191
|
-
import
|
|
27474
|
+
import path17 from "node:path";
|
|
27192
27475
|
|
|
27193
27476
|
// src/hooks/command.ts
|
|
27194
27477
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -27281,7 +27564,7 @@ var blockDangerousBashHook = {
|
|
|
27281
27564
|
};
|
|
27282
27565
|
|
|
27283
27566
|
// src/hooks/builtin/protect-env-files.ts
|
|
27284
|
-
import
|
|
27567
|
+
import path16 from "node:path";
|
|
27285
27568
|
var protectEnvFilesHook = {
|
|
27286
27569
|
name: "protect-env-files",
|
|
27287
27570
|
event: "PreToolUse",
|
|
@@ -27289,7 +27572,7 @@ var protectEnvFilesHook = {
|
|
|
27289
27572
|
run(ctx) {
|
|
27290
27573
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
27291
27574
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27292
|
-
const filePath = typeof input2.path === "string" ?
|
|
27575
|
+
const filePath = typeof input2.path === "string" ? path16.resolve(ctx.cwd, input2.path) : "";
|
|
27293
27576
|
if (filePath && isEnvFile(filePath)) {
|
|
27294
27577
|
return {
|
|
27295
27578
|
decision: "block",
|
|
@@ -27326,7 +27609,7 @@ var maskSecretsHook = {
|
|
|
27326
27609
|
};
|
|
27327
27610
|
|
|
27328
27611
|
// src/hooks/builtin/inject-env-info.ts
|
|
27329
|
-
import
|
|
27612
|
+
import os9 from "node:os";
|
|
27330
27613
|
var injectEnvInfoHook = {
|
|
27331
27614
|
name: "inject-env-info",
|
|
27332
27615
|
event: "SessionStart",
|
|
@@ -27335,7 +27618,7 @@ var injectEnvInfoHook = {
|
|
|
27335
27618
|
decision: "allow",
|
|
27336
27619
|
patch: {
|
|
27337
27620
|
systemNote: [
|
|
27338
|
-
`Environment: ${
|
|
27621
|
+
`Environment: ${os9.type()} ${os9.release()} (${os9.platform()}/${os9.arch()})`,
|
|
27339
27622
|
`cwd: ${ctx.cwd}`,
|
|
27340
27623
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
27341
27624
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -27404,14 +27687,14 @@ function matchesHook(match, ctx) {
|
|
|
27404
27687
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27405
27688
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
27406
27689
|
if (!filePath) return false;
|
|
27407
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
27690
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path17.resolve(ctx.cwd, filePath)))) return false;
|
|
27408
27691
|
}
|
|
27409
27692
|
return true;
|
|
27410
27693
|
}
|
|
27411
27694
|
|
|
27412
27695
|
// src/multimodal.ts
|
|
27413
27696
|
import { readFile as readFile8, stat as stat4 } from "node:fs/promises";
|
|
27414
|
-
import
|
|
27697
|
+
import path18 from "node:path";
|
|
27415
27698
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27416
27699
|
async function buildUserContent(prompt, images = [], options2) {
|
|
27417
27700
|
if (images.length === 0) return prompt;
|
|
@@ -27439,7 +27722,7 @@ async function imageToUrl(input2, options2) {
|
|
|
27439
27722
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
27440
27723
|
}
|
|
27441
27724
|
function mimeFromPath(filePath) {
|
|
27442
|
-
switch (
|
|
27725
|
+
switch (path18.extname(filePath).toLowerCase()) {
|
|
27443
27726
|
case ".jpg":
|
|
27444
27727
|
case ".jpeg":
|
|
27445
27728
|
return "image/jpeg";
|
|
@@ -27450,15 +27733,15 @@ function mimeFromPath(filePath) {
|
|
|
27450
27733
|
case ".webp":
|
|
27451
27734
|
return "image/webp";
|
|
27452
27735
|
default:
|
|
27453
|
-
throw new Error(`Unsupported image extension: ${
|
|
27736
|
+
throw new Error(`Unsupported image extension: ${path18.extname(filePath) || "(none)"}`);
|
|
27454
27737
|
}
|
|
27455
27738
|
}
|
|
27456
27739
|
|
|
27457
27740
|
// src/permissions/persistent-grants.ts
|
|
27458
27741
|
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
27459
27742
|
import fs8 from "node:fs";
|
|
27460
|
-
import
|
|
27461
|
-
import
|
|
27743
|
+
import os10 from "node:os";
|
|
27744
|
+
import path19 from "node:path";
|
|
27462
27745
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
27463
27746
|
var PersistentGrantStore = class {
|
|
27464
27747
|
filePath;
|
|
@@ -27496,15 +27779,15 @@ var PersistentGrantStore = class {
|
|
|
27496
27779
|
};
|
|
27497
27780
|
}
|
|
27498
27781
|
async #write(file) {
|
|
27499
|
-
await mkdir7(
|
|
27782
|
+
await mkdir7(path19.dirname(this.filePath), { recursive: true });
|
|
27500
27783
|
await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
27501
27784
|
`, { mode: 384 });
|
|
27502
27785
|
}
|
|
27503
27786
|
};
|
|
27504
27787
|
function resolveGrantPath(cwd, config) {
|
|
27505
|
-
if (config.path) return
|
|
27506
|
-
if ((config.scope ?? "project") === "user") return
|
|
27507
|
-
return
|
|
27788
|
+
if (config.path) return path19.resolve(cwd, config.path);
|
|
27789
|
+
if ((config.scope ?? "project") === "user") return path19.join(os10.homedir(), ".demian", "grants.json");
|
|
27790
|
+
return path19.join(cwd, ".demian", "grants.json");
|
|
27508
27791
|
}
|
|
27509
27792
|
function isGrantRecord(value) {
|
|
27510
27793
|
if (!value || typeof value !== "object") return false;
|
|
@@ -27938,16 +28221,16 @@ function fail(content, metadata) {
|
|
|
27938
28221
|
|
|
27939
28222
|
// src/tools/output.ts
|
|
27940
28223
|
import { mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
|
|
27941
|
-
import
|
|
28224
|
+
import path20 from "node:path";
|
|
27942
28225
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
27943
28226
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
27944
28227
|
async function capToolOutput(result, options2) {
|
|
27945
28228
|
const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
|
|
27946
28229
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
27947
28230
|
if (bytes <= capBytes) return result;
|
|
27948
|
-
const dir =
|
|
28231
|
+
const dir = path20.join(options2.cwd, ".demian", "tmp");
|
|
27949
28232
|
await mkdir8(dir, { recursive: true });
|
|
27950
|
-
const outputPath =
|
|
28233
|
+
const outputPath = path20.join(dir, `output-${safeCallId(options2.callId)}.txt`);
|
|
27951
28234
|
await writeFile7(outputPath, result.content, "utf8");
|
|
27952
28235
|
return {
|
|
27953
28236
|
...result,
|
|
@@ -27955,7 +28238,7 @@ async function capToolOutput(result, options2) {
|
|
|
27955
28238
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
27956
28239
|
`
|
|
27957
28240
|
|
|
27958
|
-
[Full output saved to ${
|
|
28241
|
+
[Full output saved to ${path20.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
27959
28242
|
|
|
27960
28243
|
`,
|
|
27961
28244
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -27981,7 +28264,7 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
27981
28264
|
|
|
27982
28265
|
// src/tools/read-file.ts
|
|
27983
28266
|
import { open as open2, stat as stat5 } from "node:fs/promises";
|
|
27984
|
-
import
|
|
28267
|
+
import path21 from "node:path";
|
|
27985
28268
|
|
|
27986
28269
|
// src/tools/validation.ts
|
|
27987
28270
|
function assertObject(input2, toolName) {
|
|
@@ -28065,7 +28348,7 @@ var readFileTool = {
|
|
|
28065
28348
|
const truncated = start + sliced.length < lines.length;
|
|
28066
28349
|
return ok(content + (truncated ? `
|
|
28067
28350
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
28068
|
-
path:
|
|
28351
|
+
path: path21.relative(ctx.cwd, filePath),
|
|
28069
28352
|
lines: sliced.length,
|
|
28070
28353
|
totalLines: lines.length,
|
|
28071
28354
|
truncated
|
|
@@ -28104,7 +28387,7 @@ function looksBinary(buffer) {
|
|
|
28104
28387
|
|
|
28105
28388
|
// src/tools/write-file.ts
|
|
28106
28389
|
import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
28107
|
-
import
|
|
28390
|
+
import path22 from "node:path";
|
|
28108
28391
|
var writeFileTool = {
|
|
28109
28392
|
name: "write_file",
|
|
28110
28393
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -28122,7 +28405,7 @@ var writeFileTool = {
|
|
|
28122
28405
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28123
28406
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
28124
28407
|
const before = await readOptionalTextFile(filePath);
|
|
28125
|
-
await mkdir9(
|
|
28408
|
+
await mkdir9(path22.dirname(filePath), { recursive: true });
|
|
28126
28409
|
await writeFile8(filePath, content, "utf8");
|
|
28127
28410
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28128
28411
|
const diff = createTextDiff(before, content, relative);
|
|
@@ -28198,7 +28481,7 @@ function countMatches(text, needle) {
|
|
|
28198
28481
|
|
|
28199
28482
|
// src/tools/bash.ts
|
|
28200
28483
|
import { spawn as spawn5 } from "node:child_process";
|
|
28201
|
-
import
|
|
28484
|
+
import path24 from "node:path";
|
|
28202
28485
|
|
|
28203
28486
|
// src/sandbox/env-only.ts
|
|
28204
28487
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -28258,7 +28541,7 @@ function bwrapPath() {
|
|
|
28258
28541
|
|
|
28259
28542
|
// src/sandbox/macos.ts
|
|
28260
28543
|
import { existsSync as existsSync3 } from "node:fs";
|
|
28261
|
-
import
|
|
28544
|
+
import path23 from "node:path";
|
|
28262
28545
|
function canUseMacOSSandbox() {
|
|
28263
28546
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
28264
28547
|
}
|
|
@@ -28284,7 +28567,7 @@ function macosProfile(cwd, config) {
|
|
|
28284
28567
|
}
|
|
28285
28568
|
if (mode === "workspace-write") {
|
|
28286
28569
|
lines.push("(deny file-write*)");
|
|
28287
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
28570
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path23.resolve(cwd))}"))`);
|
|
28288
28571
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
28289
28572
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
28290
28573
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -28346,7 +28629,7 @@ ${result.stderr}` : ""
|
|
|
28346
28629
|
].filter(Boolean).join("\n");
|
|
28347
28630
|
return ok(content, {
|
|
28348
28631
|
command,
|
|
28349
|
-
workdir:
|
|
28632
|
+
workdir: path24.relative(ctx.cwd, workdir) || ".",
|
|
28350
28633
|
exitCode: result.exitCode,
|
|
28351
28634
|
signal: result.signal,
|
|
28352
28635
|
timedOut: result.timedOut,
|
|
@@ -28399,7 +28682,7 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
28399
28682
|
// src/tools/grep.ts
|
|
28400
28683
|
import { spawn as spawn6 } from "node:child_process";
|
|
28401
28684
|
import { readdir as readdir2, readFile as readFile12, stat as stat6 } from "node:fs/promises";
|
|
28402
|
-
import
|
|
28685
|
+
import path25 from "node:path";
|
|
28403
28686
|
var MAX_MATCHES = 200;
|
|
28404
28687
|
var grepTool = {
|
|
28405
28688
|
name: "grep",
|
|
@@ -28474,7 +28757,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
28474
28757
|
}
|
|
28475
28758
|
function rewriteRgPath(cwd, line) {
|
|
28476
28759
|
const [file, rest] = splitFirst(line, ":");
|
|
28477
|
-
if (!
|
|
28760
|
+
if (!path25.isAbsolute(file)) return line;
|
|
28478
28761
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
28479
28762
|
}
|
|
28480
28763
|
function splitFirst(value, delimiter) {
|
|
@@ -28491,7 +28774,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28491
28774
|
if (isIgnoredPath(relative)) return;
|
|
28492
28775
|
const info = await stat6(item);
|
|
28493
28776
|
if (info.isDirectory()) {
|
|
28494
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
28777
|
+
for (const entry of await readdir2(item)) await walk2(path25.join(item, entry));
|
|
28495
28778
|
return;
|
|
28496
28779
|
}
|
|
28497
28780
|
if (glob && !matchGlob(glob, relative)) return;
|
|
@@ -28510,7 +28793,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28510
28793
|
|
|
28511
28794
|
// src/tools/glob.ts
|
|
28512
28795
|
import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
|
|
28513
|
-
import
|
|
28796
|
+
import path26 from "node:path";
|
|
28514
28797
|
var MAX_PATHS = 1e3;
|
|
28515
28798
|
var globTool = {
|
|
28516
28799
|
name: "glob",
|
|
@@ -28542,7 +28825,7 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
28542
28825
|
async function walk2(dir) {
|
|
28543
28826
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
28544
28827
|
for (const entry of entries) {
|
|
28545
|
-
const absolute =
|
|
28828
|
+
const absolute = path26.join(dir, entry.name);
|
|
28546
28829
|
const relative = relativeToCwd(cwd, absolute);
|
|
28547
28830
|
if (isIgnoredPath(relative)) continue;
|
|
28548
28831
|
const info = await stat7(absolute);
|
|
@@ -29545,13 +29828,13 @@ async function runExecutionSession(options2) {
|
|
|
29545
29828
|
|
|
29546
29829
|
// src/goals/lock.ts
|
|
29547
29830
|
import { mkdir as mkdir11, open as open3, rm as rm2 } from "node:fs/promises";
|
|
29548
|
-
import
|
|
29831
|
+
import path28 from "node:path";
|
|
29549
29832
|
|
|
29550
29833
|
// src/goals/storage.ts
|
|
29551
29834
|
import { createHash as createHash3 } from "node:crypto";
|
|
29552
29835
|
import { mkdir as mkdir10, readFile as readFile13, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
|
|
29553
29836
|
import fs9 from "node:fs";
|
|
29554
|
-
import
|
|
29837
|
+
import path27 from "node:path";
|
|
29555
29838
|
var GoalStore = class {
|
|
29556
29839
|
cwd;
|
|
29557
29840
|
dir;
|
|
@@ -29562,8 +29845,8 @@ var GoalStore = class {
|
|
|
29562
29845
|
this.cwd = cwd;
|
|
29563
29846
|
this.scope = normalizeGoalStoreScope(options2.scope);
|
|
29564
29847
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
29565
|
-
this.activePath =
|
|
29566
|
-
this.archiveDir =
|
|
29848
|
+
this.activePath = path27.join(this.dir, "active.json");
|
|
29849
|
+
this.archiveDir = path27.join(this.dir, "archive");
|
|
29567
29850
|
}
|
|
29568
29851
|
async loadActive() {
|
|
29569
29852
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
@@ -29577,7 +29860,7 @@ var GoalStore = class {
|
|
|
29577
29860
|
}
|
|
29578
29861
|
async saveActive(state) {
|
|
29579
29862
|
await mkdir10(this.dir, { recursive: true });
|
|
29580
|
-
const tmp =
|
|
29863
|
+
const tmp = path27.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
29581
29864
|
await writeFile10(tmp, `${JSON.stringify(state, null, 2)}
|
|
29582
29865
|
`, "utf8");
|
|
29583
29866
|
await rename4(tmp, this.activePath);
|
|
@@ -29591,21 +29874,21 @@ var GoalStore = class {
|
|
|
29591
29874
|
status,
|
|
29592
29875
|
updatedAt: Date.now()
|
|
29593
29876
|
};
|
|
29594
|
-
await writeFile10(
|
|
29877
|
+
await writeFile10(path27.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
29595
29878
|
`, "utf8");
|
|
29596
29879
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29597
29880
|
return archived;
|
|
29598
29881
|
}
|
|
29599
29882
|
async backupCorrupted(text) {
|
|
29600
29883
|
await mkdir10(this.dir, { recursive: true });
|
|
29601
|
-
const backup =
|
|
29884
|
+
const backup = path27.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
29602
29885
|
await writeFile10(backup, text, "utf8");
|
|
29603
29886
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29604
29887
|
}
|
|
29605
29888
|
};
|
|
29606
29889
|
function goalStoreDir(cwd, scope) {
|
|
29607
29890
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
29608
|
-
return safeScope ?
|
|
29891
|
+
return safeScope ? path27.join(cwd, ".demian", "goals", "sessions", safeScope) : path27.join(cwd, ".demian", "goals");
|
|
29609
29892
|
}
|
|
29610
29893
|
function normalizeGoalStoreScope(scope) {
|
|
29611
29894
|
const trimmed = scope?.trim();
|
|
@@ -29619,10 +29902,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
29619
29902
|
var GoalLock = class {
|
|
29620
29903
|
path;
|
|
29621
29904
|
constructor(cwd, scope) {
|
|
29622
|
-
this.path =
|
|
29905
|
+
this.path = path28.join(goalStoreDir(cwd, scope), "active.lock");
|
|
29623
29906
|
}
|
|
29624
29907
|
async acquire() {
|
|
29625
|
-
await mkdir11(
|
|
29908
|
+
await mkdir11(path28.dirname(this.path), { recursive: true });
|
|
29626
29909
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
29627
29910
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
29628
29911
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -30210,8 +30493,8 @@ function sha256(value) {
|
|
|
30210
30493
|
|
|
30211
30494
|
// src/root-session.ts
|
|
30212
30495
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30213
|
-
import
|
|
30214
|
-
import
|
|
30496
|
+
import os11 from "node:os";
|
|
30497
|
+
import path29 from "node:path";
|
|
30215
30498
|
|
|
30216
30499
|
// src/tools/cowork.ts
|
|
30217
30500
|
function createCoworkTool(options2) {
|
|
@@ -31034,8 +31317,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31034
31317
|
}
|
|
31035
31318
|
}
|
|
31036
31319
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
31037
|
-
const root = await mkdtemp(
|
|
31038
|
-
const cwd =
|
|
31320
|
+
const root = await mkdtemp(path29.join(os11.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
31321
|
+
const cwd = path29.join(root, "workspace");
|
|
31039
31322
|
await cp(this.#options.cwd, cwd, {
|
|
31040
31323
|
recursive: true,
|
|
31041
31324
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -31183,7 +31466,7 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31183
31466
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
31184
31467
|
return this.withPermissionPreset({
|
|
31185
31468
|
...agent,
|
|
31186
|
-
provider: agent.provider?.profile === "claudecode-
|
|
31469
|
+
provider: agent.provider?.profile === "claudecode" && agent.name === "claudecode-explorer" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
|
|
31187
31470
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
31188
31471
|
permissions: [
|
|
31189
31472
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
@@ -31230,13 +31513,14 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31230
31513
|
return lines.join("\n");
|
|
31231
31514
|
}
|
|
31232
31515
|
resolveInvocationBackend(profileName, modelOverride, agent) {
|
|
31233
|
-
const
|
|
31516
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride });
|
|
31517
|
+
const providerConfig = runtime.providerConfig;
|
|
31234
31518
|
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, modelOverride);
|
|
31235
31519
|
if (this.#options.resolveProvider) {
|
|
31236
|
-
const resolved = this.#options.resolveProvider(profileName, providerConfig,
|
|
31520
|
+
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
31237
31521
|
return { kind: "provider", ...resolved };
|
|
31238
31522
|
}
|
|
31239
|
-
return resolveExecutionBackend(providerConfig, { model:
|
|
31523
|
+
return resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config, agent });
|
|
31240
31524
|
}
|
|
31241
31525
|
loadAgentSession(agent, providerProfile, model, sessionScope, cwd = this.#options.cwd) {
|
|
31242
31526
|
const key = `${this.id}:${sessionScope ?? "delegate"}:${agent.name}:${providerProfile}:${model}:${cwd}`;
|
|
@@ -31338,17 +31622,17 @@ function findDependencyCycle(group) {
|
|
|
31338
31622
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
31339
31623
|
const visiting = /* @__PURE__ */ new Set();
|
|
31340
31624
|
const visited = /* @__PURE__ */ new Set();
|
|
31341
|
-
const
|
|
31625
|
+
const path36 = [];
|
|
31342
31626
|
const visit = (id) => {
|
|
31343
|
-
if (visiting.has(id)) return [...
|
|
31627
|
+
if (visiting.has(id)) return [...path36.slice(path36.indexOf(id)), id];
|
|
31344
31628
|
if (visited.has(id)) return void 0;
|
|
31345
31629
|
visiting.add(id);
|
|
31346
|
-
|
|
31630
|
+
path36.push(id);
|
|
31347
31631
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
31348
31632
|
const cycle = visit(dep);
|
|
31349
31633
|
if (cycle) return cycle;
|
|
31350
31634
|
}
|
|
31351
|
-
|
|
31635
|
+
path36.pop();
|
|
31352
31636
|
visiting.delete(id);
|
|
31353
31637
|
visited.add(id);
|
|
31354
31638
|
return void 0;
|
|
@@ -31389,7 +31673,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
31389
31673
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
31390
31674
|
}
|
|
31391
31675
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
31392
|
-
const relative =
|
|
31676
|
+
const relative = path29.relative(root, source).split(path29.sep).join("/");
|
|
31393
31677
|
if (!relative) return true;
|
|
31394
31678
|
const parts = relative.split("/");
|
|
31395
31679
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -31603,11 +31887,11 @@ function finitePositiveInteger(value) {
|
|
|
31603
31887
|
// src/ui/preferences.ts
|
|
31604
31888
|
import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
31605
31889
|
import fs10 from "node:fs";
|
|
31606
|
-
import
|
|
31890
|
+
import path30 from "node:path";
|
|
31607
31891
|
var UiPreferenceStore = class {
|
|
31608
31892
|
filePath;
|
|
31609
31893
|
constructor(cwd, filePath) {
|
|
31610
|
-
this.filePath = filePath ?
|
|
31894
|
+
this.filePath = filePath ? path30.resolve(cwd, filePath) : path30.join(cwd, ".demian", "preferences.json");
|
|
31611
31895
|
}
|
|
31612
31896
|
async load() {
|
|
31613
31897
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -31625,7 +31909,7 @@ var UiPreferenceStore = class {
|
|
|
31625
31909
|
model: selection.model,
|
|
31626
31910
|
updatedAt: Date.now()
|
|
31627
31911
|
};
|
|
31628
|
-
await mkdir12(
|
|
31912
|
+
await mkdir12(path30.dirname(this.filePath), { recursive: true });
|
|
31629
31913
|
await writeFile11(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
31630
31914
|
`, { mode: 384 });
|
|
31631
31915
|
}
|
|
@@ -31645,37 +31929,528 @@ function preferenceKey(selection) {
|
|
|
31645
31929
|
return `${selection.providerName}\0${selection.model}`;
|
|
31646
31930
|
}
|
|
31647
31931
|
|
|
31648
|
-
// src/
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31932
|
+
// src/models/catalog.ts
|
|
31933
|
+
import crypto6 from "node:crypto";
|
|
31934
|
+
import { mkdir as mkdir13, readFile as readFile15, rename as rename5, writeFile as writeFile12 } from "node:fs/promises";
|
|
31935
|
+
import os12 from "node:os";
|
|
31936
|
+
import path31 from "node:path";
|
|
31937
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
31938
|
+
var PING_TIMEOUT_MS = 1500;
|
|
31939
|
+
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
31940
|
+
var dynamicImport3 = new Function("specifier", "return import(specifier)");
|
|
31941
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
31942
|
+
var pingCache = /* @__PURE__ */ new Map();
|
|
31943
|
+
var PING_CACHE_TTL_MS = 30 * 1e3;
|
|
31944
|
+
var packageVersionPromise;
|
|
31945
|
+
async function listProviderModelCatalog(providerName, providerConfig, options2 = {}) {
|
|
31946
|
+
const key = `${catalogCacheKey(providerName, providerConfig)}:${options2.refresh ? "refresh" : "default"}`;
|
|
31947
|
+
const existing = inflight.get(key);
|
|
31948
|
+
if (existing) return existing;
|
|
31949
|
+
const promise = doListProviderModelCatalog(providerName, providerConfig, options2).finally(() => inflight.delete(key));
|
|
31950
|
+
inflight.set(key, promise);
|
|
31951
|
+
return promise;
|
|
31952
|
+
}
|
|
31953
|
+
async function doListProviderModelCatalog(providerName, providerConfig, options2) {
|
|
31954
|
+
const staticModels = staticModelEntries(providerName, providerConfig);
|
|
31955
|
+
const catalog = providerConfig.catalog;
|
|
31956
|
+
if (!catalog || catalog.type === "static") {
|
|
31957
|
+
return { status: staticModels.length ? "ready" : "unavailable", providerName, models: staticModels, source: "config" };
|
|
31958
|
+
}
|
|
31959
|
+
const missingAuthEnv = missingCatalogAuth(providerConfig);
|
|
31960
|
+
if (missingAuthEnv) {
|
|
31961
|
+
return {
|
|
31962
|
+
status: "missing-auth",
|
|
31963
|
+
providerName,
|
|
31964
|
+
models: staticModels,
|
|
31965
|
+
source: staticModels.length ? "config" : "fallback",
|
|
31966
|
+
message: `missing-auth:${missingAuthEnv}`
|
|
31967
|
+
};
|
|
31968
|
+
}
|
|
31969
|
+
const cacheKey = catalogCacheKey(providerName, providerConfig);
|
|
31970
|
+
const ttlMs = catalog.refreshTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
31971
|
+
const cached = await readCatalogCache(cacheKey, ttlMs, options2.refresh !== true);
|
|
31972
|
+
if (cached && !options2.refresh) return { ...cached, models: mergeCatalogWithStatic(staticModels, cached.models) };
|
|
31973
|
+
if (catalog.endpoint) {
|
|
31974
|
+
const reachable = await pingEndpoint(catalog.endpoint, options2.fetch);
|
|
31975
|
+
if (!reachable) {
|
|
31976
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
31977
|
+
if (stale) return { ...stale, models: mergeCatalogWithStatic(staticModels, stale.models), message: `endpoint ${catalog.endpoint} unreachable; using cached models` };
|
|
31978
|
+
if (staticModels.length) return { status: "ready", providerName, models: staticModels, source: "config", message: `endpoint ${catalog.endpoint} unreachable; using configured profiles` };
|
|
31979
|
+
return { status: "unavailable", providerName, models: [], source: "fallback", message: `endpoint ${catalog.endpoint} unreachable` };
|
|
31980
|
+
}
|
|
31981
|
+
}
|
|
31982
|
+
try {
|
|
31983
|
+
const remote = await fetchCatalog(providerName, providerConfig, options2);
|
|
31984
|
+
const merged = mergeCatalogWithStatic(staticModels, remote.models);
|
|
31985
|
+
const result = { ...remote, models: merged };
|
|
31986
|
+
if (result.status === "ready") await writeCatalogCache(cacheKey, result);
|
|
31987
|
+
return result;
|
|
31988
|
+
} catch (error) {
|
|
31989
|
+
const authMissing = isMissingAuthError(error);
|
|
31990
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
31991
|
+
if (stale) return { ...stale, status: authMissing ? "missing-auth" : stale.status, models: mergeCatalogWithStatic(staticModels, stale.models), message: errorMessage2(error) };
|
|
31992
|
+
if (staticModels.length) return { status: authMissing ? "missing-auth" : "ready", providerName, models: staticModels, source: "config", message: errorMessage2(error) };
|
|
31993
|
+
return { status: authMissing ? "missing-auth" : "unavailable", providerName, models: [], source: "fallback", message: errorMessage2(error) };
|
|
31994
|
+
}
|
|
31995
|
+
}
|
|
31996
|
+
async function pingEndpoint(endpoint, fetcher) {
|
|
31997
|
+
const now2 = Date.now();
|
|
31998
|
+
const cached = pingCache.get(endpoint);
|
|
31999
|
+
if (cached && cached.expiresAt > now2) return cached.ok;
|
|
32000
|
+
const ok2 = await tryPing(endpoint, fetcher);
|
|
32001
|
+
pingCache.set(endpoint, { ok: ok2, expiresAt: now2 + PING_CACHE_TTL_MS });
|
|
32002
|
+
return ok2;
|
|
32003
|
+
}
|
|
32004
|
+
async function tryPing(endpoint, fetcher) {
|
|
32005
|
+
const fn = fetcher ?? fetch;
|
|
32006
|
+
try {
|
|
32007
|
+
const response = await fn(endpoint, { method: "HEAD", signal: AbortSignal.timeout(PING_TIMEOUT_MS) });
|
|
32008
|
+
if (response.status < 500) return true;
|
|
32009
|
+
} catch {
|
|
32010
|
+
}
|
|
32011
|
+
try {
|
|
32012
|
+
const response = await fn(endpoint, { method: "GET", signal: AbortSignal.timeout(PING_TIMEOUT_MS), headers: { range: "bytes=0-0" } });
|
|
32013
|
+
return response.status < 500;
|
|
32014
|
+
} catch {
|
|
32015
|
+
return false;
|
|
32016
|
+
}
|
|
32017
|
+
}
|
|
32018
|
+
function invalidateCatalogPingCache(endpoint) {
|
|
32019
|
+
if (endpoint) pingCache.delete(endpoint);
|
|
32020
|
+
else pingCache.clear();
|
|
32021
|
+
}
|
|
32022
|
+
async function fetchCatalog(providerName, providerConfig, options2) {
|
|
32023
|
+
if (providerConfig.type === "codex" || providerConfig.catalog?.type === "codex-oauth-models") return fetchCodexCatalog(providerName, providerConfig, options2);
|
|
32024
|
+
if (providerConfig.type === "claudecode" || providerConfig.catalog?.type === "claudecode-supported-models") return fetchClaudeCodeCatalog(providerName, providerConfig, options2);
|
|
32025
|
+
if (providerConfig.catalog?.type === "anthropic-models") return fetchAnthropicCatalog(providerName, providerConfig, options2);
|
|
32026
|
+
if (providerConfig.catalog?.type === "ollama-tags") return fetchOllamaTagsCatalog(providerName, providerConfig, options2);
|
|
32027
|
+
return fetchOpenAIStyleCatalog(providerName, providerConfig, options2);
|
|
32028
|
+
}
|
|
32029
|
+
async function fetchOpenAIStyleCatalog(providerName, providerConfig, options2) {
|
|
32030
|
+
if (providerConfig.type !== "openai-compatible") throw new Error(`Provider ${providerName} is not OpenAI-compatible.`);
|
|
32031
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32032
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32033
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/models`;
|
|
32034
|
+
const response = await fetchJson2(endpoint, {
|
|
32035
|
+
fetcher: options2.fetch,
|
|
32036
|
+
timeoutMs: options2.timeoutMs,
|
|
32037
|
+
headers: {
|
|
32038
|
+
...authHeadersForOpenAICompatible(providerConfig, apiKey.value),
|
|
32039
|
+
...providerConfig.headers ?? {}
|
|
32040
|
+
}
|
|
32041
|
+
});
|
|
32042
|
+
return {
|
|
32043
|
+
status: "ready",
|
|
32044
|
+
providerName,
|
|
32045
|
+
models: normalizeOpenAIModels(response).map((entry) => ({ ...entry, source: "api" })),
|
|
32046
|
+
source: "api"
|
|
32047
|
+
};
|
|
32048
|
+
}
|
|
32049
|
+
async function fetchAnthropicCatalog(providerName, providerConfig, options2) {
|
|
32050
|
+
if (providerConfig.type !== "anthropic") throw new Error(`Provider ${providerName} is not Anthropic.`);
|
|
32051
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32052
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32053
|
+
const base = providerConfig.baseURL ?? "https://api.anthropic.com/v1";
|
|
32054
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${base.replace(/\/+$/, "")}/models`;
|
|
32055
|
+
const models = [];
|
|
32056
|
+
let afterId;
|
|
32057
|
+
for (; ; ) {
|
|
32058
|
+
const url = new URL(endpoint);
|
|
32059
|
+
if (afterId) url.searchParams.set("after_id", afterId);
|
|
32060
|
+
url.searchParams.set("limit", "100");
|
|
32061
|
+
const response = await fetchJson2(url.toString(), {
|
|
32062
|
+
fetcher: options2.fetch,
|
|
32063
|
+
timeoutMs: options2.timeoutMs,
|
|
32064
|
+
headers: {
|
|
32065
|
+
"x-api-key": apiKey.value,
|
|
32066
|
+
"anthropic-version": "2023-06-01"
|
|
32067
|
+
}
|
|
32068
|
+
});
|
|
32069
|
+
const page = normalizeAnthropicModels(response);
|
|
32070
|
+
models.push(...page);
|
|
32071
|
+
if (!response.has_more || !response.last_id) break;
|
|
32072
|
+
afterId = response.last_id;
|
|
32073
|
+
}
|
|
32074
|
+
return { status: "ready", providerName, models, source: "api" };
|
|
32075
|
+
}
|
|
32076
|
+
async function fetchOllamaTagsCatalog(providerName, providerConfig, options2) {
|
|
32077
|
+
if (providerConfig.type !== "ollama") throw new Error(`Provider ${providerName} is not Ollama native.`);
|
|
32078
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32079
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32080
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/tags`;
|
|
32081
|
+
const response = await fetchJson2(endpoint, {
|
|
32082
|
+
fetcher: options2.fetch,
|
|
32083
|
+
timeoutMs: options2.timeoutMs,
|
|
32084
|
+
headers: { authorization: `Bearer ${apiKey.value}` }
|
|
32085
|
+
});
|
|
32086
|
+
return {
|
|
32087
|
+
status: "ready",
|
|
32088
|
+
providerName,
|
|
32089
|
+
models: normalizeOllamaTags(response).map((entry) => ({ ...entry, source: "api" })),
|
|
32090
|
+
source: "api"
|
|
32091
|
+
};
|
|
32092
|
+
}
|
|
32093
|
+
async function fetchCodexCatalog(providerName, providerConfig, options2) {
|
|
32094
|
+
if (providerConfig.type !== "codex") throw new Error(`Provider ${providerName} is not Codex.`);
|
|
32095
|
+
const fetcher = options2.fetch ?? fetch;
|
|
32096
|
+
const baseURL = (providerConfig.baseURL ?? "https://chatgpt.com/backend-api/codex").replace(/\/+$/, "");
|
|
32097
|
+
const installationId = await loadOrCreateInstallationId(resolveCodexHome(providerConfig.codexHome));
|
|
32098
|
+
const auth = getSharedCodexAuthStore({
|
|
32099
|
+
codexHome: providerConfig.codexHome,
|
|
32100
|
+
authStore: providerConfig.authStore,
|
|
32101
|
+
allowApiKeyFallback: providerConfig.allowApiKeyFallback,
|
|
32102
|
+
proactiveRefreshMinutes: providerConfig.refresh?.proactiveRefreshMinutes,
|
|
32103
|
+
refreshCache: providerConfig.refresh?.cache,
|
|
32104
|
+
fetch: fetcher
|
|
32105
|
+
});
|
|
32106
|
+
const headers = await auth.requestHeaders(installationId);
|
|
32107
|
+
const url = new URL(`${baseURL}/models`);
|
|
32108
|
+
url.searchParams.set("client_version", await codexClientVersion(options2.clientVersion));
|
|
32109
|
+
const response = await fetchJson2(url.toString(), {
|
|
32110
|
+
fetcher,
|
|
32111
|
+
timeoutMs: options2.timeoutMs,
|
|
32112
|
+
headers
|
|
32113
|
+
});
|
|
32114
|
+
return {
|
|
32115
|
+
status: "ready",
|
|
32116
|
+
providerName,
|
|
32117
|
+
models: normalizeCodexModels(response).map((entry) => ({ ...entry, source: "oauth" })),
|
|
32118
|
+
source: "oauth"
|
|
32119
|
+
};
|
|
32120
|
+
}
|
|
32121
|
+
async function fetchClaudeCodeCatalog(providerName, providerConfig, options2) {
|
|
32122
|
+
if (providerConfig.type !== "claudecode") throw new Error(`Provider ${providerName} is not Claude Code.`);
|
|
32123
|
+
const fallback = staticModelEntries(providerName, providerConfig);
|
|
32124
|
+
try {
|
|
32125
|
+
const module = await dynamicImport3("@anthropic-ai/claude-agent-sdk");
|
|
32126
|
+
if (!module.query) throw new Error("Claude Agent SDK query export is unavailable.");
|
|
32127
|
+
const query = module.query({
|
|
32128
|
+
prompt: "",
|
|
32129
|
+
options: {
|
|
32130
|
+
model: defaultModelForProvider(providerName, providerConfig),
|
|
32131
|
+
maxTurns: 0,
|
|
32132
|
+
pathToClaudeCodeExecutable: resolveClaudeCodeCliPath(providerConfig.cliPath),
|
|
32133
|
+
env: sanitizedClaudeCodeEnv(providerConfig.env, providerConfig.sanitizeApiKeyEnv ?? true)
|
|
32134
|
+
}
|
|
32135
|
+
});
|
|
32136
|
+
try {
|
|
32137
|
+
if (typeof query.supportedModels !== "function") throw new Error("Claude Agent SDK supportedModels is unavailable.");
|
|
32138
|
+
const models = await withTimeout(query.supportedModels(), options2.timeoutMs ?? 5e3);
|
|
32139
|
+
return {
|
|
32140
|
+
status: "ready",
|
|
32141
|
+
providerName,
|
|
32142
|
+
models: models.map((model, index) => ({
|
|
32143
|
+
name: model.displayName || model.value,
|
|
32144
|
+
displayName: model.displayName || model.value,
|
|
32145
|
+
model: model.value,
|
|
32146
|
+
description: model.description,
|
|
32147
|
+
isDefault: index === 0,
|
|
32148
|
+
source: "oauth"
|
|
32149
|
+
})),
|
|
32150
|
+
source: "oauth"
|
|
32151
|
+
};
|
|
32152
|
+
} finally {
|
|
32153
|
+
query.close?.();
|
|
32154
|
+
}
|
|
32155
|
+
} catch (error) {
|
|
32156
|
+
if (fallback.length) return { status: "ready", providerName, models: fallback, source: "fallback", message: errorMessage2(error) };
|
|
32157
|
+
throw error;
|
|
32158
|
+
}
|
|
32159
|
+
}
|
|
32160
|
+
function staticModelEntries(providerName, providerConfig) {
|
|
32161
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile, index) => ({
|
|
32162
|
+
name: profile.displayName ?? profile.name,
|
|
32163
|
+
displayName: profile.displayName ?? profile.name,
|
|
32164
|
+
model: profile.model,
|
|
32165
|
+
description: profile.description,
|
|
32166
|
+
isDefault: index === 0,
|
|
32167
|
+
source: "config"
|
|
31658
32168
|
}));
|
|
31659
32169
|
}
|
|
32170
|
+
function mergeCatalogWithStatic(staticModels, remoteModels) {
|
|
32171
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
32172
|
+
for (const model of remoteModels) byModel.set(model.model, model);
|
|
32173
|
+
for (const model of staticModels) byModel.set(model.model, { ...byModel.get(model.model), ...model, source: model.source });
|
|
32174
|
+
return [...byModel.values()];
|
|
32175
|
+
}
|
|
32176
|
+
function normalizeOpenAIModels(raw) {
|
|
32177
|
+
const items = Array.isArray(raw.data) ? raw.data : Array.isArray(raw.models) ? raw.models : [];
|
|
32178
|
+
return items.map((item) => {
|
|
32179
|
+
const object2 = item;
|
|
32180
|
+
const id = stringValue5(object2.id) ?? stringValue5(object2.name) ?? stringValue5(object2.model);
|
|
32181
|
+
if (!id) return void 0;
|
|
32182
|
+
return {
|
|
32183
|
+
name: id,
|
|
32184
|
+
displayName: stringValue5(object2.display_name) ?? stringValue5(object2.displayName) ?? id,
|
|
32185
|
+
model: id,
|
|
32186
|
+
description: stringValue5(object2.description)
|
|
32187
|
+
};
|
|
32188
|
+
}).filter(Boolean);
|
|
32189
|
+
}
|
|
32190
|
+
function normalizeAnthropicModels(raw) {
|
|
32191
|
+
return (raw.data ?? []).map((item) => {
|
|
32192
|
+
const object2 = item;
|
|
32193
|
+
const id = stringValue5(object2.id);
|
|
32194
|
+
if (!id) return void 0;
|
|
32195
|
+
return {
|
|
32196
|
+
name: stringValue5(object2.display_name) ?? id,
|
|
32197
|
+
displayName: stringValue5(object2.display_name) ?? id,
|
|
32198
|
+
model: id,
|
|
32199
|
+
description: stringValue5(object2.description),
|
|
32200
|
+
source: "api"
|
|
32201
|
+
};
|
|
32202
|
+
}).filter(Boolean);
|
|
32203
|
+
}
|
|
32204
|
+
function normalizeOllamaTags(raw) {
|
|
32205
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
32206
|
+
return items.map((item) => {
|
|
32207
|
+
const object2 = item;
|
|
32208
|
+
const id = stringValue5(object2.name) ?? stringValue5(object2.model);
|
|
32209
|
+
if (!id) return void 0;
|
|
32210
|
+
return { name: id, displayName: id, model: id };
|
|
32211
|
+
}).filter(Boolean);
|
|
32212
|
+
}
|
|
32213
|
+
function normalizeCodexModels(raw) {
|
|
32214
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
32215
|
+
return items.map((item, index) => {
|
|
32216
|
+
const object2 = item;
|
|
32217
|
+
const id = stringValue5(object2.slug) ?? stringValue5(object2.id) ?? stringValue5(object2.model);
|
|
32218
|
+
if (!id) return void 0;
|
|
32219
|
+
return {
|
|
32220
|
+
name: stringValue5(object2.display_name) ?? id,
|
|
32221
|
+
displayName: stringValue5(object2.display_name) ?? id,
|
|
32222
|
+
model: id,
|
|
32223
|
+
description: stringValue5(object2.description),
|
|
32224
|
+
isDefault: index === 0
|
|
32225
|
+
};
|
|
32226
|
+
}).filter(Boolean);
|
|
32227
|
+
}
|
|
32228
|
+
function authHeadersForOpenAICompatible(providerConfig, apiKey) {
|
|
32229
|
+
const auth = inferOpenAICompatibleAuth(providerConfig.baseURL, providerConfig.auth);
|
|
32230
|
+
const type = auth.type ?? "bearer";
|
|
32231
|
+
const header = auth.header ?? (type === "api-key" ? "api-key" : "authorization");
|
|
32232
|
+
return { [header]: type === "bearer" ? `Bearer ${apiKey}` : apiKey };
|
|
32233
|
+
}
|
|
32234
|
+
async function fetchJson2(url, options2) {
|
|
32235
|
+
const response = await (options2.fetcher ?? fetch)(url, {
|
|
32236
|
+
method: "GET",
|
|
32237
|
+
headers: {
|
|
32238
|
+
accept: "application/json",
|
|
32239
|
+
...options2.headers ?? {}
|
|
32240
|
+
},
|
|
32241
|
+
signal: AbortSignal.timeout(options2.timeoutMs ?? 5e3)
|
|
32242
|
+
});
|
|
32243
|
+
const text = await response.text();
|
|
32244
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
|
|
32245
|
+
return text ? JSON.parse(text) : {};
|
|
32246
|
+
}
|
|
32247
|
+
function cachePath(cacheKey) {
|
|
32248
|
+
return path31.join(os12.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
32249
|
+
}
|
|
32250
|
+
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
32251
|
+
if (!allow) return void 0;
|
|
32252
|
+
try {
|
|
32253
|
+
const raw = JSON.parse(await readFile15(cachePath(cacheKey), "utf8"));
|
|
32254
|
+
if (!raw.result) return void 0;
|
|
32255
|
+
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
32256
|
+
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
32257
|
+
} catch {
|
|
32258
|
+
return void 0;
|
|
32259
|
+
}
|
|
32260
|
+
}
|
|
32261
|
+
async function writeCatalogCache(cacheKey, result) {
|
|
32262
|
+
const filePath = cachePath(cacheKey);
|
|
32263
|
+
await mkdir13(path31.dirname(filePath), { recursive: true, mode: 448 });
|
|
32264
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32265
|
+
await writeFile12(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
32266
|
+
await rename5(temp, filePath);
|
|
32267
|
+
}
|
|
32268
|
+
function safeCacheName(providerName) {
|
|
32269
|
+
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
32270
|
+
}
|
|
32271
|
+
function catalogCacheKey(providerName, providerConfig) {
|
|
32272
|
+
const identity = {
|
|
32273
|
+
providerName,
|
|
32274
|
+
type: providerConfig.type,
|
|
32275
|
+
catalogType: providerConfig.catalog?.type,
|
|
32276
|
+
endpoint: providerConfig.catalog?.endpoint,
|
|
32277
|
+
baseURL: "baseURL" in providerConfig ? providerConfig.baseURL : void 0,
|
|
32278
|
+
apiKeyEnv: "apiKeyEnv" in providerConfig ? providerConfig.apiKeyEnv : void 0,
|
|
32279
|
+
apiKeyEnvAliases: "apiKeyEnvAliases" in providerConfig ? providerConfig.apiKeyEnvAliases : void 0,
|
|
32280
|
+
auth: "auth" in providerConfig ? providerConfig.auth : void 0
|
|
32281
|
+
};
|
|
32282
|
+
const digest = crypto6.createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16);
|
|
32283
|
+
return `${providerName}-${digest}`;
|
|
32284
|
+
}
|
|
32285
|
+
function missingCatalogAuth(providerConfig) {
|
|
32286
|
+
if (providerConfig.type !== "openai-compatible" && providerConfig.type !== "anthropic" && providerConfig.type !== "ollama") return void 0;
|
|
32287
|
+
if (providerConfig.catalog?.requiresAuth === false) return void 0;
|
|
32288
|
+
const expectsAuth = providerConfig.catalog?.requiresAuth === true || !!providerConfig.apiKey || !!providerConfig.apiKeyEnv || !!providerConfig.apiKeyEnvAliases?.length;
|
|
32289
|
+
if (!expectsAuth) return void 0;
|
|
32290
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32291
|
+
return apiKey.value ? void 0 : apiKey.envName ?? "apiKey";
|
|
32292
|
+
}
|
|
32293
|
+
function stringValue5(value) {
|
|
32294
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
32295
|
+
}
|
|
32296
|
+
function missingAuth(envName) {
|
|
32297
|
+
const error = new Error(`missing-auth:${envName}`);
|
|
32298
|
+
return error;
|
|
32299
|
+
}
|
|
32300
|
+
function isMissingAuthError(error) {
|
|
32301
|
+
if (error instanceof CodexAuthError) {
|
|
32302
|
+
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";
|
|
32303
|
+
}
|
|
32304
|
+
return error instanceof Error && error.message.startsWith("missing-auth:");
|
|
32305
|
+
}
|
|
32306
|
+
function errorMessage2(error) {
|
|
32307
|
+
return error instanceof Error ? error.message : String(error);
|
|
32308
|
+
}
|
|
32309
|
+
async function codexClientVersion(override) {
|
|
32310
|
+
const normalized = semverLike(override);
|
|
32311
|
+
if (normalized) return normalized;
|
|
32312
|
+
packageVersionPromise ??= readPackageVersion();
|
|
32313
|
+
return packageVersionPromise;
|
|
32314
|
+
}
|
|
32315
|
+
async function readPackageVersion() {
|
|
32316
|
+
try {
|
|
32317
|
+
const raw = JSON.parse(await readFile15(new URL("../package.json", import.meta.url), "utf8"));
|
|
32318
|
+
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
32319
|
+
} catch {
|
|
32320
|
+
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
32321
|
+
}
|
|
32322
|
+
}
|
|
32323
|
+
function semverLike(value) {
|
|
32324
|
+
if (typeof value !== "string") return void 0;
|
|
32325
|
+
const trimmed = value.trim();
|
|
32326
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed) ? trimmed : void 0;
|
|
32327
|
+
}
|
|
32328
|
+
function withTimeout(promise, timeoutMs) {
|
|
32329
|
+
return new Promise((resolve, reject) => {
|
|
32330
|
+
const timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
32331
|
+
promise.then(
|
|
32332
|
+
(value) => {
|
|
32333
|
+
clearTimeout(timer);
|
|
32334
|
+
resolve(value);
|
|
32335
|
+
},
|
|
32336
|
+
(error) => {
|
|
32337
|
+
clearTimeout(timer);
|
|
32338
|
+
reject(error);
|
|
32339
|
+
}
|
|
32340
|
+
);
|
|
32341
|
+
});
|
|
32342
|
+
}
|
|
32343
|
+
|
|
32344
|
+
// src/ui/settings.ts
|
|
32345
|
+
function providerOptions(config, catalogs = {}) {
|
|
32346
|
+
const orderedNames = sortProviderNames(Object.keys(config.providers));
|
|
32347
|
+
const order = new Map(orderedNames.map((name, index) => [name, index]));
|
|
32348
|
+
return orderedNames.map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli")).map(([name, provider]) => {
|
|
32349
|
+
const catalog = catalogs[name];
|
|
32350
|
+
const available = providerCatalogAvailable(catalog);
|
|
32351
|
+
const profiles = catalog?.models.length ? catalog.models.map((model) => ({
|
|
32352
|
+
name: model.name,
|
|
32353
|
+
displayName: model.displayName ?? model.name,
|
|
32354
|
+
model: model.model,
|
|
32355
|
+
description: model.description,
|
|
32356
|
+
source: model.source,
|
|
32357
|
+
isDefault: model.isDefault
|
|
32358
|
+
})) : providerModelProfiles(name, provider);
|
|
32359
|
+
const modelProfiles = profiles.map((profile) => {
|
|
32360
|
+
const displayName = profile.displayName ?? profile.name ?? profile.model;
|
|
32361
|
+
return {
|
|
32362
|
+
...profile,
|
|
32363
|
+
label: available ? displayName : unavailableLabel(displayName),
|
|
32364
|
+
available
|
|
32365
|
+
};
|
|
32366
|
+
});
|
|
32367
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
32368
|
+
const defaultModel = defaultModelForProvider(name, provider);
|
|
32369
|
+
return {
|
|
32370
|
+
name,
|
|
32371
|
+
label: available ? name : unavailableLabel(name),
|
|
32372
|
+
model: defaultModel,
|
|
32373
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel(defaultModel),
|
|
32374
|
+
models,
|
|
32375
|
+
modelProfiles,
|
|
32376
|
+
type: provider.type,
|
|
32377
|
+
runtime: provider.type === "claudecode" ? provider.runtime : void 0,
|
|
32378
|
+
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
32379
|
+
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
32380
|
+
available,
|
|
32381
|
+
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
32382
|
+
};
|
|
32383
|
+
}).sort((left, right) => {
|
|
32384
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
32385
|
+
return (order.get(left.name) ?? 0) - (order.get(right.name) ?? 0);
|
|
32386
|
+
});
|
|
32387
|
+
}
|
|
32388
|
+
async function providerOptionsWithCatalog(config, options2 = {}) {
|
|
32389
|
+
const entries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli"));
|
|
32390
|
+
const catalogs = Object.fromEntries(
|
|
32391
|
+
await Promise.all(
|
|
32392
|
+
entries.map(async ([name, provider]) => {
|
|
32393
|
+
const catalog = await listProviderModelCatalog(name, provider, { timeoutMs: options2.timeoutMs }).catch((error) => ({
|
|
32394
|
+
status: "unavailable",
|
|
32395
|
+
providerName: name,
|
|
32396
|
+
models: [],
|
|
32397
|
+
source: "fallback",
|
|
32398
|
+
message: errorMessage3(error)
|
|
32399
|
+
}));
|
|
32400
|
+
return [name, catalog];
|
|
32401
|
+
})
|
|
32402
|
+
)
|
|
32403
|
+
);
|
|
32404
|
+
return providerOptions(config, catalogs);
|
|
32405
|
+
}
|
|
32406
|
+
var MISSING_SELECTION_PLACEHOLDER = "-";
|
|
31660
32407
|
function createInteractiveModelSelection(config, flags = {}, saved) {
|
|
31661
32408
|
const savedProviderName = saved?.providerName === "claudecode-plan" ? "claudecode" : saved?.providerName;
|
|
31662
|
-
const
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
|
|
31666
|
-
|
|
32409
|
+
const savedProviderExists = savedProviderName && config.providers[savedProviderName] && !config.providers[savedProviderName]?.hidden;
|
|
32410
|
+
if (saved?.providerName && !flags.provider && !savedProviderExists) {
|
|
32411
|
+
return {
|
|
32412
|
+
providerName: saved.providerName,
|
|
32413
|
+
providerSource: "saved",
|
|
32414
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
32415
|
+
modelSource: "saved"
|
|
32416
|
+
};
|
|
32417
|
+
}
|
|
32418
|
+
const providerName = flags.provider ?? (savedProviderExists ? savedProviderName : config.defaultProvider);
|
|
32419
|
+
let providerConfig;
|
|
32420
|
+
try {
|
|
32421
|
+
providerConfig = resolveProviderConfig(config, providerName);
|
|
32422
|
+
} catch {
|
|
32423
|
+
return {
|
|
32424
|
+
providerName,
|
|
32425
|
+
providerSource: flags.provider ? "flag" : "config",
|
|
32426
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
32427
|
+
modelSource: "config"
|
|
32428
|
+
};
|
|
32429
|
+
}
|
|
32430
|
+
const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
|
|
31667
32431
|
const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
|
|
32432
|
+
const model = flags.model ?? savedModel ?? defaultModelForProvider(providerName, providerConfig);
|
|
31668
32433
|
return {
|
|
31669
32434
|
providerName,
|
|
31670
32435
|
providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
|
|
31671
|
-
model
|
|
31672
|
-
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config"
|
|
32436
|
+
model,
|
|
32437
|
+
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
|
|
32438
|
+
modelProfileName: providerModelProfiles(providerName, providerConfig).find((profile) => profile.model === model || profile.displayName === model || profile.name === model)?.name
|
|
31673
32439
|
};
|
|
31674
32440
|
}
|
|
32441
|
+
function providerCatalogAvailable(catalog) {
|
|
32442
|
+
return catalog ? catalog.status === "ready" && catalog.models.length > 0 : true;
|
|
32443
|
+
}
|
|
32444
|
+
function unavailableLabel(value) {
|
|
32445
|
+
return `(n/a) ${value}`;
|
|
32446
|
+
}
|
|
32447
|
+
function errorMessage3(error) {
|
|
32448
|
+
return error instanceof Error ? error.message : String(error);
|
|
32449
|
+
}
|
|
31675
32450
|
|
|
31676
32451
|
// src/ui/tui/controller.ts
|
|
31677
32452
|
async function runTuiSession(flags, store2) {
|
|
31678
|
-
const cwd =
|
|
32453
|
+
const cwd = path32.resolve(flags.cwd ?? process.cwd());
|
|
31679
32454
|
const eventBus = new EventBus();
|
|
31680
32455
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
31681
32456
|
const loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
@@ -31696,7 +32471,7 @@ async function runTuiSession(flags, store2) {
|
|
|
31696
32471
|
const current = agentRegistry.get(agentName);
|
|
31697
32472
|
agentOptions.unshift({ name: current.name, description: current.description });
|
|
31698
32473
|
}
|
|
31699
|
-
store2.configureSettings(initialSelection,
|
|
32474
|
+
store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
31700
32475
|
agent: agentName,
|
|
31701
32476
|
cwd,
|
|
31702
32477
|
agentOptions,
|
|
@@ -31713,9 +32488,9 @@ async function runTuiSession(flags, store2) {
|
|
|
31713
32488
|
const rootSessionId = flags.sessionId ?? createRootSessionId();
|
|
31714
32489
|
const goalTitleGenerator = async (input2) => {
|
|
31715
32490
|
const selection = store2.currentSelection();
|
|
31716
|
-
const
|
|
31717
|
-
const resolved = resolveExecutionBackend(providerConfig, {
|
|
31718
|
-
model:
|
|
32491
|
+
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
32492
|
+
const resolved = resolveExecutionBackend(runtime.providerConfig, {
|
|
32493
|
+
model: runtime.model,
|
|
31719
32494
|
onRetry: (event) => eventBus.emit({
|
|
31720
32495
|
type: "provider.retry",
|
|
31721
32496
|
sessionId: "goal-title",
|
|
@@ -31773,6 +32548,7 @@ async function runTuiSession(flags, store2) {
|
|
|
31773
32548
|
if (goalCommand.kind === "message") {
|
|
31774
32549
|
store2.showSystemMessage("Goal", goalCommand.message);
|
|
31775
32550
|
if (parsedGoalCommand?.action === "clear") store2.clearGoalState();
|
|
32551
|
+
else if (!goalCommand.state && isGoalStateClearingAction(parsedGoalCommand?.action)) store2.clearGoalState();
|
|
31776
32552
|
else if (goalCommand.state) store2.showGoalState(goalCommand.state);
|
|
31777
32553
|
store2.prepareForPrompt("waiting for next message");
|
|
31778
32554
|
continue;
|
|
@@ -31824,10 +32600,10 @@ async function runTuiSession(flags, store2) {
|
|
|
31824
32600
|
if (goalState) await goalRuntime.finishIteration({ state: goalState, finalAnswer: result.finalAnswer, reason: result.endReason, usage: result.usage });
|
|
31825
32601
|
history = interactiveHistoryFromRunMessages(result.messages);
|
|
31826
32602
|
} else {
|
|
31827
|
-
const providerConfig = resolveProviderConfig(runConfig, selection.providerName);
|
|
31828
32603
|
const runtimeAgent = goalState ? withGoalTools(agent) : agent;
|
|
31829
|
-
const
|
|
31830
|
-
|
|
32604
|
+
const runtime = resolveProviderRuntimeConfig(runConfig, selection);
|
|
32605
|
+
const backend = resolveExecutionBackend(runtime.providerConfig, {
|
|
32606
|
+
model: runtime.model,
|
|
31831
32607
|
config: runConfig,
|
|
31832
32608
|
agent: runtimeAgent,
|
|
31833
32609
|
onRetry: (event) => eventBus.emit({
|
|
@@ -31919,8 +32695,446 @@ async function runTuiSession(flags, store2) {
|
|
|
31919
32695
|
savedSelectionKey = key;
|
|
31920
32696
|
}
|
|
31921
32697
|
}
|
|
32698
|
+
function isGoalStateClearingAction(action) {
|
|
32699
|
+
return action === "status" || action === "pause" || action === "resume";
|
|
32700
|
+
}
|
|
32701
|
+
|
|
32702
|
+
// src/config-watcher.ts
|
|
32703
|
+
import { EventEmitter } from "node:events";
|
|
32704
|
+
import fs11 from "node:fs";
|
|
32705
|
+
import path34 from "node:path";
|
|
32706
|
+
|
|
32707
|
+
// src/config-scaffold.ts
|
|
32708
|
+
import { chmod as chmod3, mkdir as mkdir14, readFile as readFile16, rename as rename6, stat as stat8, writeFile as writeFile13 } from "node:fs/promises";
|
|
32709
|
+
import os13 from "node:os";
|
|
32710
|
+
import path33 from "node:path";
|
|
32711
|
+
function defaultUserConfigPath() {
|
|
32712
|
+
return path33.join(os13.homedir(), ".demian", "config.json");
|
|
32713
|
+
}
|
|
32714
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
32715
|
+
return {
|
|
32716
|
+
version: 2,
|
|
32717
|
+
defaultProvider,
|
|
32718
|
+
providers: {
|
|
32719
|
+
openai: {
|
|
32720
|
+
type: "openai-compatible",
|
|
32721
|
+
baseURL: "https://api.openai.com/v1",
|
|
32722
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
32723
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
32724
|
+
},
|
|
32725
|
+
anthropic: {
|
|
32726
|
+
type: "anthropic",
|
|
32727
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
32728
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
32729
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
32730
|
+
},
|
|
32731
|
+
gemini: {
|
|
32732
|
+
type: "openai-compatible",
|
|
32733
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
32734
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
32735
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
32736
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
32737
|
+
},
|
|
32738
|
+
groq: {
|
|
32739
|
+
type: "openai-compatible",
|
|
32740
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
32741
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
32742
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
32743
|
+
},
|
|
32744
|
+
azure: {
|
|
32745
|
+
type: "openai-compatible",
|
|
32746
|
+
auth: { type: "api-key", header: "api-key" },
|
|
32747
|
+
modelProfiles: [
|
|
32748
|
+
{
|
|
32749
|
+
name: "azure-example",
|
|
32750
|
+
displayName: "Azure example",
|
|
32751
|
+
model: "azure-deployment-name",
|
|
32752
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
32753
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
32754
|
+
}
|
|
32755
|
+
]
|
|
32756
|
+
},
|
|
32757
|
+
lmstudio: {
|
|
32758
|
+
type: "openai-compatible",
|
|
32759
|
+
baseURL: "http://localhost:1234/v1",
|
|
32760
|
+
apiKey: "lm-studio",
|
|
32761
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
32762
|
+
},
|
|
32763
|
+
"ollama-local": {
|
|
32764
|
+
type: "openai-compatible",
|
|
32765
|
+
baseURL: "http://localhost:11434/v1",
|
|
32766
|
+
apiKey: "ollama",
|
|
32767
|
+
quirks: { omitTemperature: true },
|
|
32768
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
32769
|
+
},
|
|
32770
|
+
"ollama-cloud": {
|
|
32771
|
+
type: "ollama",
|
|
32772
|
+
baseURL: "https://ollama.com/api",
|
|
32773
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
32774
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
32775
|
+
},
|
|
32776
|
+
llamacpp: {
|
|
32777
|
+
type: "openai-compatible",
|
|
32778
|
+
baseURL: "http://localhost:8080/v1",
|
|
32779
|
+
apiKey: "llama.cpp",
|
|
32780
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
32781
|
+
},
|
|
32782
|
+
vllm: {
|
|
32783
|
+
type: "openai-compatible",
|
|
32784
|
+
baseURL: "http://localhost:8000/v1",
|
|
32785
|
+
apiKey: "vllm",
|
|
32786
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
32787
|
+
},
|
|
32788
|
+
codex: {
|
|
32789
|
+
type: "codex",
|
|
32790
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
32791
|
+
authStore: "auto",
|
|
32792
|
+
allowApiKeyFallback: false,
|
|
32793
|
+
promptCacheKey: "root-session",
|
|
32794
|
+
catalog: { type: "codex-oauth-models" }
|
|
32795
|
+
},
|
|
32796
|
+
claudecode: {
|
|
32797
|
+
type: "claudecode",
|
|
32798
|
+
runtime: "agent-sdk",
|
|
32799
|
+
cliPath: "~/.local/bin/claude",
|
|
32800
|
+
cwdMode: "session",
|
|
32801
|
+
historyPolicy: "passthrough-resume",
|
|
32802
|
+
onInvalidResume: "fresh",
|
|
32803
|
+
attachmentFallback: "block",
|
|
32804
|
+
allowSubagents: false,
|
|
32805
|
+
sanitizeApiKeyEnv: true,
|
|
32806
|
+
authPreflight: true,
|
|
32807
|
+
useBareMode: false,
|
|
32808
|
+
usageLedgerScope: "process",
|
|
32809
|
+
sessionLock: true,
|
|
32810
|
+
abortPolicy: "record-only",
|
|
32811
|
+
catalog: { type: "claudecode-supported-models" }
|
|
32812
|
+
}
|
|
32813
|
+
}
|
|
32814
|
+
};
|
|
32815
|
+
}
|
|
32816
|
+
async function createUserConfig(options2 = {}) {
|
|
32817
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32818
|
+
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
32819
|
+
`;
|
|
32820
|
+
if (options2.print) return { path: filePath, created: false, content };
|
|
32821
|
+
const existed = await exists(filePath);
|
|
32822
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile16(filePath, "utf8"), existed: true };
|
|
32823
|
+
await writeJsonAtomic(filePath, content);
|
|
32824
|
+
return { path: filePath, created: true, content, existed };
|
|
32825
|
+
}
|
|
32826
|
+
async function addProvider(options2) {
|
|
32827
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32828
|
+
const config = await readConfigObject(filePath);
|
|
32829
|
+
const providers = objectValue(config.providers);
|
|
32830
|
+
const name = options2.name;
|
|
32831
|
+
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
32832
|
+
providers[name] = providerPreset(options2);
|
|
32833
|
+
config.providers = providers;
|
|
32834
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32835
|
+
`;
|
|
32836
|
+
await writeJsonAtomic(filePath, content);
|
|
32837
|
+
return { path: filePath, created: true, content };
|
|
32838
|
+
}
|
|
32839
|
+
async function addModelProfile(options2) {
|
|
32840
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32841
|
+
const config = await readConfigObject(filePath);
|
|
32842
|
+
const providers = objectValue(config.providers);
|
|
32843
|
+
const provider = objectValue(providers[options2.provider]);
|
|
32844
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
32845
|
+
const displayName = options2.displayName ?? options2.name;
|
|
32846
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
32847
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
32848
|
+
if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
32849
|
+
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
|
|
32850
|
+
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
32851
|
+
}
|
|
32852
|
+
const next = {
|
|
32853
|
+
name: options2.name,
|
|
32854
|
+
displayName,
|
|
32855
|
+
model: options2.model,
|
|
32856
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
32857
|
+
...options2.apiKey ? { apiKey: options2.apiKey } : {},
|
|
32858
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
32859
|
+
};
|
|
32860
|
+
if (existingByName >= 0) profiles[existingByName] = next;
|
|
32861
|
+
else profiles.push(next);
|
|
32862
|
+
provider.modelProfiles = profiles;
|
|
32863
|
+
providers[options2.provider] = provider;
|
|
32864
|
+
config.providers = providers;
|
|
32865
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32866
|
+
`;
|
|
32867
|
+
await writeJsonAtomic(filePath, content);
|
|
32868
|
+
return { path: filePath, created: true, content };
|
|
32869
|
+
}
|
|
32870
|
+
async function updateConfigDefaults(options2) {
|
|
32871
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32872
|
+
const config = await readConfigObject(filePath);
|
|
32873
|
+
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
32874
|
+
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
32875
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
32876
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
32877
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
32878
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32879
|
+
`;
|
|
32880
|
+
await writeJsonAtomic(filePath, content);
|
|
32881
|
+
return { path: filePath, created: true, content };
|
|
32882
|
+
}
|
|
32883
|
+
async function readConfigObject(filePath) {
|
|
32884
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
32885
|
+
const raw = JSON.parse(await readFile16(filePath, "utf8"));
|
|
32886
|
+
raw.version ??= 2;
|
|
32887
|
+
raw.providers = objectValue(raw.providers);
|
|
32888
|
+
return raw;
|
|
32889
|
+
}
|
|
32890
|
+
function providerPreset(options2) {
|
|
32891
|
+
const preset = options2.preset ?? options2.name;
|
|
32892
|
+
const auth = {
|
|
32893
|
+
...options2.apiKey ? { apiKey: options2.apiKey } : {},
|
|
32894
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
32895
|
+
};
|
|
32896
|
+
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
32897
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", ...auth, ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32898
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", apiKeyEnv: "ANTHROPIC_API_KEY", ...auth, catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32899
|
+
if (preset === "gemini") {
|
|
32900
|
+
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
32901
|
+
return { type: "openai-compatible", baseURL: geminiBase, apiKeyEnv: "GEMINI_API_KEY", apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...auth, ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
32902
|
+
}
|
|
32903
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY", ...auth, ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
32904
|
+
if (preset === "azure") {
|
|
32905
|
+
return {
|
|
32906
|
+
type: "openai-compatible",
|
|
32907
|
+
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
32908
|
+
...auth,
|
|
32909
|
+
modelProfiles: [
|
|
32910
|
+
{
|
|
32911
|
+
name: "azure-example",
|
|
32912
|
+
displayName: "Azure example",
|
|
32913
|
+
model: "azure-deployment-name",
|
|
32914
|
+
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
32915
|
+
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
32916
|
+
}
|
|
32917
|
+
]
|
|
32918
|
+
};
|
|
32919
|
+
}
|
|
32920
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
32921
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
32922
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...auth, catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
32923
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
32924
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
32925
|
+
if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
32926
|
+
if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
|
|
32927
|
+
if ((options2.type ?? preset) === "openai-compatible") {
|
|
32928
|
+
const baseURL = options2.baseURL;
|
|
32929
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
32930
|
+
return {
|
|
32931
|
+
type: "openai-compatible",
|
|
32932
|
+
baseURL,
|
|
32933
|
+
...options2.apiKey ? { apiKey: options2.apiKey } : {},
|
|
32934
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {},
|
|
32935
|
+
...openAIAuth,
|
|
32936
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
32937
|
+
};
|
|
32938
|
+
}
|
|
32939
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
32940
|
+
}
|
|
32941
|
+
async function writeJsonAtomic(filePath, content) {
|
|
32942
|
+
await mkdir14(path33.dirname(filePath), { recursive: true, mode: 448 });
|
|
32943
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32944
|
+
await writeFile13(temp, content, { mode: 384 });
|
|
32945
|
+
await rename6(temp, filePath);
|
|
32946
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
32947
|
+
}
|
|
32948
|
+
function detectDefaultProvider() {
|
|
32949
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
32950
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
32951
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
32952
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
32953
|
+
return "openai";
|
|
32954
|
+
}
|
|
32955
|
+
function objectValue(value) {
|
|
32956
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
32957
|
+
}
|
|
32958
|
+
function normalizeOptionalName(value) {
|
|
32959
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
32960
|
+
}
|
|
32961
|
+
async function exists(filePath) {
|
|
32962
|
+
try {
|
|
32963
|
+
await stat8(filePath);
|
|
32964
|
+
return true;
|
|
32965
|
+
} catch {
|
|
32966
|
+
return false;
|
|
32967
|
+
}
|
|
32968
|
+
}
|
|
32969
|
+
function expandHome2(value) {
|
|
32970
|
+
return resolveExpandedPath(value);
|
|
32971
|
+
}
|
|
32972
|
+
|
|
32973
|
+
// src/config-watcher.ts
|
|
32974
|
+
var ConfigWatcher = class extends EventEmitter {
|
|
32975
|
+
#filePath;
|
|
32976
|
+
#debounceMs;
|
|
32977
|
+
#watcher;
|
|
32978
|
+
#pollIntervalMs = 1e3;
|
|
32979
|
+
#lastSeen;
|
|
32980
|
+
#pending;
|
|
32981
|
+
#started = false;
|
|
32982
|
+
constructor(options2 = {}) {
|
|
32983
|
+
super();
|
|
32984
|
+
this.#filePath = options2.path ?? defaultUserConfigPath();
|
|
32985
|
+
this.#debounceMs = options2.debounceMs ?? 300;
|
|
32986
|
+
}
|
|
32987
|
+
get filePath() {
|
|
32988
|
+
return this.#filePath;
|
|
32989
|
+
}
|
|
32990
|
+
start() {
|
|
32991
|
+
if (this.#started) return;
|
|
32992
|
+
this.#started = true;
|
|
32993
|
+
this.#lastSeen = this.#snapshot();
|
|
32994
|
+
try {
|
|
32995
|
+
fs11.mkdirSync(path34.dirname(this.#filePath), { recursive: true, mode: 448 });
|
|
32996
|
+
this.#watcher = fs11.watch(path34.dirname(this.#filePath), { persistent: false }, (_eventType, filename) => {
|
|
32997
|
+
if (filename && filename !== path34.basename(this.#filePath)) return;
|
|
32998
|
+
this.#schedule();
|
|
32999
|
+
});
|
|
33000
|
+
this.#watcher.on("error", () => void 0);
|
|
33001
|
+
} catch {
|
|
33002
|
+
this.#watcher = void 0;
|
|
33003
|
+
}
|
|
33004
|
+
fs11.watchFile(this.#filePath, { persistent: false, interval: this.#pollIntervalMs }, () => this.#schedule());
|
|
33005
|
+
}
|
|
33006
|
+
stop() {
|
|
33007
|
+
this.#started = false;
|
|
33008
|
+
if (this.#pending) clearTimeout(this.#pending);
|
|
33009
|
+
this.#pending = void 0;
|
|
33010
|
+
this.#watcher?.close();
|
|
33011
|
+
this.#watcher = void 0;
|
|
33012
|
+
try {
|
|
33013
|
+
fs11.unwatchFile(this.#filePath);
|
|
33014
|
+
} catch {
|
|
33015
|
+
}
|
|
33016
|
+
}
|
|
33017
|
+
#schedule() {
|
|
33018
|
+
if (this.#pending) clearTimeout(this.#pending);
|
|
33019
|
+
this.#pending = setTimeout(() => this.#emit(), this.#debounceMs);
|
|
33020
|
+
}
|
|
33021
|
+
#emit() {
|
|
33022
|
+
this.#pending = void 0;
|
|
33023
|
+
const snapshot = this.#snapshot();
|
|
33024
|
+
const previous = this.#lastSeen ?? { size: 0, mtimeMs: 0, exists: false };
|
|
33025
|
+
this.#lastSeen = snapshot;
|
|
33026
|
+
if (previous.exists && !snapshot.exists) {
|
|
33027
|
+
this.emit("unlink", this.#filePath);
|
|
33028
|
+
this.emit("changed", this.#filePath, "unlink");
|
|
33029
|
+
return;
|
|
33030
|
+
}
|
|
33031
|
+
if (!previous.exists && snapshot.exists) {
|
|
33032
|
+
this.emit("create", this.#filePath);
|
|
33033
|
+
this.emit("changed", this.#filePath, "create");
|
|
33034
|
+
return;
|
|
33035
|
+
}
|
|
33036
|
+
if (snapshot.exists && (snapshot.mtimeMs !== previous.mtimeMs || snapshot.size !== previous.size)) {
|
|
33037
|
+
this.emit("change", this.#filePath);
|
|
33038
|
+
this.emit("changed", this.#filePath, "change");
|
|
33039
|
+
}
|
|
33040
|
+
}
|
|
33041
|
+
#snapshot() {
|
|
33042
|
+
try {
|
|
33043
|
+
const stat9 = fs11.statSync(this.#filePath);
|
|
33044
|
+
return { size: stat9.size, mtimeMs: stat9.mtimeMs, exists: true };
|
|
33045
|
+
} catch {
|
|
33046
|
+
return { size: 0, mtimeMs: 0, exists: false };
|
|
33047
|
+
}
|
|
33048
|
+
}
|
|
33049
|
+
};
|
|
33050
|
+
function createConfigWatcher(options2) {
|
|
33051
|
+
const watcher = new ConfigWatcher(options2);
|
|
33052
|
+
watcher.start();
|
|
33053
|
+
return watcher;
|
|
33054
|
+
}
|
|
31922
33055
|
|
|
31923
33056
|
// src/vscode-worker.ts
|
|
33057
|
+
if (process.argv.includes("--config-template")) {
|
|
33058
|
+
process.stdout.write(`${JSON.stringify(defaultUserConfig(), null, 2)}
|
|
33059
|
+
`);
|
|
33060
|
+
process.exit(0);
|
|
33061
|
+
}
|
|
33062
|
+
if (process.argv.includes("--config-init")) {
|
|
33063
|
+
const force = process.argv.includes("--force");
|
|
33064
|
+
await createUserConfig({ force }).then(
|
|
33065
|
+
(result) => {
|
|
33066
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path, created: result.created, existed: result.existed ?? false })}
|
|
33067
|
+
`);
|
|
33068
|
+
process.exit(0);
|
|
33069
|
+
},
|
|
33070
|
+
(error) => {
|
|
33071
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33072
|
+
`);
|
|
33073
|
+
process.exit(1);
|
|
33074
|
+
}
|
|
33075
|
+
);
|
|
33076
|
+
}
|
|
33077
|
+
if (process.argv.includes("--config-add-provider")) {
|
|
33078
|
+
await addProvider(jsonArg("--config-add-provider")).then(
|
|
33079
|
+
(result) => {
|
|
33080
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33081
|
+
`);
|
|
33082
|
+
process.exit(0);
|
|
33083
|
+
},
|
|
33084
|
+
(error) => {
|
|
33085
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33086
|
+
`);
|
|
33087
|
+
process.exit(1);
|
|
33088
|
+
}
|
|
33089
|
+
);
|
|
33090
|
+
}
|
|
33091
|
+
if (process.argv.includes("--config-add-model")) {
|
|
33092
|
+
await addModelProfile(jsonArg("--config-add-model")).then(
|
|
33093
|
+
(result) => {
|
|
33094
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33095
|
+
`);
|
|
33096
|
+
process.exit(0);
|
|
33097
|
+
},
|
|
33098
|
+
(error) => {
|
|
33099
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33100
|
+
`);
|
|
33101
|
+
process.exit(1);
|
|
33102
|
+
}
|
|
33103
|
+
);
|
|
33104
|
+
}
|
|
33105
|
+
if (process.argv.includes("--config-set-defaults")) {
|
|
33106
|
+
await updateConfigDefaults(jsonArg("--config-set-defaults")).then(
|
|
33107
|
+
(result) => {
|
|
33108
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33109
|
+
`);
|
|
33110
|
+
process.exit(0);
|
|
33111
|
+
},
|
|
33112
|
+
(error) => {
|
|
33113
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33114
|
+
`);
|
|
33115
|
+
process.exit(1);
|
|
33116
|
+
}
|
|
33117
|
+
);
|
|
33118
|
+
}
|
|
33119
|
+
if (process.argv.includes("--provider-auth-check")) {
|
|
33120
|
+
await checkProviderAuth(jsonArg("--provider-auth-check")).then(
|
|
33121
|
+
(result) => {
|
|
33122
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
|
|
33123
|
+
`);
|
|
33124
|
+
process.exit(0);
|
|
33125
|
+
},
|
|
33126
|
+
(error) => {
|
|
33127
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33128
|
+
`);
|
|
33129
|
+
process.exit(1);
|
|
33130
|
+
}
|
|
33131
|
+
);
|
|
33132
|
+
}
|
|
33133
|
+
if (process.argv.includes("--config-path")) {
|
|
33134
|
+
process.stdout.write(`${defaultUserConfigPath()}
|
|
33135
|
+
`);
|
|
33136
|
+
process.exit(0);
|
|
33137
|
+
}
|
|
31924
33138
|
var options = parseOptions();
|
|
31925
33139
|
var store = new TuiStore();
|
|
31926
33140
|
var diffState = normalizeDiffState(options.initialSnapshot?.diff);
|
|
@@ -31942,7 +33156,7 @@ store.handleEvent = (event) => {
|
|
|
31942
33156
|
};
|
|
31943
33157
|
store.subscribe(scheduleSnapshot);
|
|
31944
33158
|
process.on("message", (message) => {
|
|
31945
|
-
void handleMessage(message).catch((error) => send({ type: "error", message:
|
|
33159
|
+
void handleMessage(message).catch((error) => send({ type: "error", message: errorMessage4(error) }));
|
|
31946
33160
|
});
|
|
31947
33161
|
process.on("disconnect", () => {
|
|
31948
33162
|
closed = true;
|
|
@@ -31984,7 +33198,7 @@ async function main() {
|
|
|
31984
33198
|
},
|
|
31985
33199
|
(error) => {
|
|
31986
33200
|
sendSnapshotNow();
|
|
31987
|
-
send({ type: "error", message:
|
|
33201
|
+
send({ type: "error", message: errorMessage4(error) });
|
|
31988
33202
|
process.exit(1);
|
|
31989
33203
|
}
|
|
31990
33204
|
);
|
|
@@ -32047,21 +33261,22 @@ async function undoDiffAction(actionId) {
|
|
|
32047
33261
|
if (!action) throw new Error("No diff action is available to undo.");
|
|
32048
33262
|
if (!canUndoDiffAction(diffState, action.id)) throw new Error("Only the latest change for a file can be undone.");
|
|
32049
33263
|
if (!hasBeforeSnapshot(action)) throw new Error("This diff was not recorded with enough content to undo safely.");
|
|
32050
|
-
const target =
|
|
32051
|
-
const relative =
|
|
32052
|
-
if (relative.startsWith("..") ||
|
|
33264
|
+
const target = path35.resolve(options.cwd, action.path);
|
|
33265
|
+
const relative = path35.relative(options.cwd, target);
|
|
33266
|
+
if (relative.startsWith("..") || path35.isAbsolute(relative)) throw new Error("Refusing to undo a file outside the workspace.");
|
|
32053
33267
|
if (!action.beforeExists) {
|
|
32054
33268
|
await rm4(target, { force: true });
|
|
32055
33269
|
} else {
|
|
32056
|
-
await
|
|
32057
|
-
await
|
|
33270
|
+
await mkdir15(path35.dirname(target), { recursive: true });
|
|
33271
|
+
await writeFile14(target, action.beforeContent ?? "", "utf8");
|
|
32058
33272
|
}
|
|
32059
33273
|
markDiffActionUndone(action.id);
|
|
32060
33274
|
send({ type: "diffUndoCompleted", actionId: action.id, path: action.path });
|
|
32061
33275
|
sendSnapshotNow();
|
|
32062
33276
|
}
|
|
32063
33277
|
function submitPrompt(prompt, displayPrompt, images = []) {
|
|
32064
|
-
if (
|
|
33278
|
+
if (isGoalCommandPrompt(prompt)) clearRestoredGoal();
|
|
33279
|
+
if (!isCompactCommand(prompt)) clearResolvedRestoredWorkPlan();
|
|
32065
33280
|
if (displayPrompt && displayPrompt !== prompt) displayPromptOverrides.push({ prompt, displayPrompt });
|
|
32066
33281
|
pendingImages = images;
|
|
32067
33282
|
store.clearPromptInput();
|
|
@@ -32074,6 +33289,32 @@ function submitGoalCommand(command) {
|
|
|
32074
33289
|
if (store.snapshot().inputMode !== "prompt") return;
|
|
32075
33290
|
submitPrompt(`/goal ${normalized}`);
|
|
32076
33291
|
}
|
|
33292
|
+
function jsonArg(flag) {
|
|
33293
|
+
const index = process.argv.indexOf(flag);
|
|
33294
|
+
const raw = index >= 0 ? process.argv[index + 1] : void 0;
|
|
33295
|
+
if (!raw) throw new Error(`${flag} requires a JSON payload.`);
|
|
33296
|
+
const parsed = JSON.parse(raw);
|
|
33297
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${flag} payload must be an object.`);
|
|
33298
|
+
return parsed;
|
|
33299
|
+
}
|
|
33300
|
+
async function checkProviderAuth(options2) {
|
|
33301
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: options2.configPath });
|
|
33302
|
+
const providerName = typeof options2.provider === "string" && options2.provider.trim() ? options2.provider.trim() : config.defaultProvider;
|
|
33303
|
+
const provider = config.providers[providerName];
|
|
33304
|
+
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
33305
|
+
const catalog = await listProviderModelCatalog(providerName, provider, {
|
|
33306
|
+
refresh: options2.refresh !== false,
|
|
33307
|
+
timeoutMs: typeof options2.timeoutMs === "number" && options2.timeoutMs > 0 ? options2.timeoutMs : 5e3
|
|
33308
|
+
});
|
|
33309
|
+
return {
|
|
33310
|
+
providerName,
|
|
33311
|
+
providerType: provider.type,
|
|
33312
|
+
status: catalog.status,
|
|
33313
|
+
source: catalog.source,
|
|
33314
|
+
message: catalog.message,
|
|
33315
|
+
models: catalog.models.map((model) => ({ name: model.name, displayName: model.displayName, model: model.model, source: model.source, isDefault: model.isDefault }))
|
|
33316
|
+
};
|
|
33317
|
+
}
|
|
32077
33318
|
function displayUserMessageEvent(event) {
|
|
32078
33319
|
if (isNestedInvocationEvent2(event)) return event;
|
|
32079
33320
|
const index = displayPromptOverrides.findIndex((item) => item.prompt === event.text);
|
|
@@ -32159,8 +33400,17 @@ function clearRestoredGoal() {
|
|
|
32159
33400
|
restoredGoalCleared = true;
|
|
32160
33401
|
if (restoredSnapshot) restoredSnapshot = { ...restoredSnapshot, goal: void 0 };
|
|
32161
33402
|
}
|
|
32162
|
-
function
|
|
32163
|
-
|
|
33403
|
+
function clearResolvedRestoredWorkPlan() {
|
|
33404
|
+
if (!restoredSnapshot || !isResolvedWorkPlan2(restoredSnapshot.workPlan)) return;
|
|
33405
|
+
restoredSnapshot = {
|
|
33406
|
+
...restoredSnapshot,
|
|
33407
|
+
workPlan: void 0,
|
|
33408
|
+
workPlanExpanded: false,
|
|
33409
|
+
progressNotes: []
|
|
33410
|
+
};
|
|
33411
|
+
}
|
|
33412
|
+
function isGoalCommandPrompt(prompt) {
|
|
33413
|
+
return /^\/(?:goal|ralph-loop)\b/i.test(String(prompt || "").trim());
|
|
32164
33414
|
}
|
|
32165
33415
|
function normalizeGoalCommand(command) {
|
|
32166
33416
|
return command === "pause" || command === "resume" || command === "clear" ? command : void 0;
|
|
@@ -32195,6 +33445,10 @@ function isInternalPromptBlock(block) {
|
|
|
32195
33445
|
const text = Array.isArray(block.lines) ? block.lines.join("\n").trim() : "";
|
|
32196
33446
|
return /^You are running as sub agent:/i.test(text);
|
|
32197
33447
|
}
|
|
33448
|
+
function isResolvedWorkPlan2(plan) {
|
|
33449
|
+
const steps = Array.isArray(plan?.steps) ? plan.steps : [];
|
|
33450
|
+
return steps.length > 0 && steps.every((step) => step?.status === "completed" || step?.status === "skipped");
|
|
33451
|
+
}
|
|
32198
33452
|
function updateDiffState(event) {
|
|
32199
33453
|
if (event?.type === "user.message" && !isNestedInvocationEvent2(event)) {
|
|
32200
33454
|
diffState = void 0;
|
|
@@ -32373,7 +33627,7 @@ function numberOrZero(value) {
|
|
|
32373
33627
|
}
|
|
32374
33628
|
function compactEvent(event) {
|
|
32375
33629
|
if (!event || typeof event !== "object") return event;
|
|
32376
|
-
if (event.type === "model.text.delta") return { ...event,
|
|
33630
|
+
if (event.type === "model.text.delta") return { ...event, textLength: event.text?.length ?? 0 };
|
|
32377
33631
|
return event;
|
|
32378
33632
|
}
|
|
32379
33633
|
function send(message) {
|
|
@@ -32412,11 +33666,16 @@ async function loadAndSendConfigMeta() {
|
|
|
32412
33666
|
}
|
|
32413
33667
|
}) : config;
|
|
32414
33668
|
applyInitialApiKeys();
|
|
32415
|
-
sendConfigMeta();
|
|
33669
|
+
void sendConfigMeta();
|
|
32416
33670
|
} catch (error) {
|
|
32417
|
-
send({ type: "error", message:
|
|
33671
|
+
send({ type: "error", message: errorMessage4(error) });
|
|
32418
33672
|
}
|
|
32419
33673
|
}
|
|
33674
|
+
var configWatcher = createConfigWatcher();
|
|
33675
|
+
configWatcher.on("changed", () => {
|
|
33676
|
+
invalidateCatalogPingCache();
|
|
33677
|
+
void loadAndSendConfigMeta();
|
|
33678
|
+
});
|
|
32420
33679
|
function applyInitialApiKeys() {
|
|
32421
33680
|
for (const [provider, apiKey] of Object.entries(options.apiKeys ?? {})) {
|
|
32422
33681
|
if (typeof apiKey === "string") setApiKey(provider, apiKey, { quiet: true });
|
|
@@ -32426,12 +33685,18 @@ function setApiKey(providerName, apiKey, opts = {}) {
|
|
|
32426
33685
|
const provider = loadedConfig?.providers?.[providerName];
|
|
32427
33686
|
const envName = provider?.apiKeyEnv;
|
|
32428
33687
|
if (envName && apiKey.trim()) process.env[envName] = apiKey.trim();
|
|
32429
|
-
if (!opts.quiet) sendConfigMeta();
|
|
33688
|
+
if (!opts.quiet) void sendConfigMeta();
|
|
32430
33689
|
}
|
|
32431
|
-
function sendConfigMeta() {
|
|
33690
|
+
async function sendConfigMeta() {
|
|
32432
33691
|
if (!loadedConfig) return;
|
|
32433
33692
|
const registry = new AgentRegistry();
|
|
32434
33693
|
for (const agentDefinition of Object.values(loadedConfig.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
|
|
33694
|
+
const providerEntries = sortProviderNames(Object.keys(loadedConfig.providers)).map((name) => [name, loadedConfig.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
|
|
33695
|
+
const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
|
|
33696
|
+
const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
|
|
33697
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
33698
|
+
return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
|
|
33699
|
+
});
|
|
32435
33700
|
send({
|
|
32436
33701
|
type: "configMeta",
|
|
32437
33702
|
config: {
|
|
@@ -32440,30 +33705,61 @@ function sendConfigMeta() {
|
|
|
32440
33705
|
context: {
|
|
32441
33706
|
main: loadedConfig.context?.main
|
|
32442
33707
|
},
|
|
32443
|
-
|
|
33708
|
+
providerOrder: providers.map((provider) => provider.name),
|
|
33709
|
+
providers,
|
|
32444
33710
|
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
32445
33711
|
}
|
|
32446
33712
|
});
|
|
32447
33713
|
}
|
|
32448
|
-
function providerMeta(name, provider) {
|
|
33714
|
+
async function providerMeta(name, provider) {
|
|
32449
33715
|
const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
|
|
32450
|
-
const
|
|
32451
|
-
const
|
|
33716
|
+
const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
|
|
33717
|
+
const available = providerCatalogAvailable2(catalog);
|
|
33718
|
+
const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
|
|
33719
|
+
name: model.name,
|
|
33720
|
+
displayName: model.displayName ?? model.name,
|
|
33721
|
+
model: model.model,
|
|
33722
|
+
description: model.description,
|
|
33723
|
+
source: model.source,
|
|
33724
|
+
isDefault: model.isDefault
|
|
33725
|
+
})) : providerModelProfiles(name, provider);
|
|
33726
|
+
const labeledProfiles = modelProfiles.map((profile) => {
|
|
33727
|
+
const displayName = profile.displayName ?? profile.name ?? profile.model;
|
|
33728
|
+
return {
|
|
33729
|
+
...profile,
|
|
33730
|
+
label: available ? displayName : unavailableLabel2(displayName),
|
|
33731
|
+
available
|
|
33732
|
+
};
|
|
33733
|
+
});
|
|
33734
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
33735
|
+
const defaultModel = defaultModelForProvider(name, provider);
|
|
32452
33736
|
return {
|
|
32453
33737
|
name,
|
|
33738
|
+
label: available ? name : unavailableLabel2(name),
|
|
33739
|
+
available,
|
|
32454
33740
|
type: provider.type,
|
|
32455
33741
|
runtime: provider.runtime,
|
|
32456
33742
|
permissionMode: provider.permissionMode,
|
|
32457
33743
|
ga: provider.ga === true || process.env.DEMIAN_CLAUDECODE_GA === "1",
|
|
32458
|
-
model:
|
|
33744
|
+
model: defaultModel,
|
|
33745
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
|
|
32459
33746
|
models,
|
|
33747
|
+
modelProfiles: labeledProfiles,
|
|
32460
33748
|
baseURL: provider.baseURL,
|
|
32461
33749
|
apiKeyEnv,
|
|
33750
|
+
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
33751
|
+
catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
|
|
32462
33752
|
hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0,
|
|
32463
|
-
hasEnvApiKey: !!
|
|
32464
|
-
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic"
|
|
33753
|
+
hasEnvApiKey: !![apiKeyEnv, ...asStringArray(provider.apiKeyEnvAliases)].find((envName) => envName && process.env[envName]),
|
|
33754
|
+
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
|
|
32465
33755
|
};
|
|
32466
33756
|
}
|
|
33757
|
+
function providerCatalogAvailable2(catalog) {
|
|
33758
|
+
return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
33759
|
+
}
|
|
33760
|
+
function unavailableLabel2(value) {
|
|
33761
|
+
return `(n/a) ${value}`;
|
|
33762
|
+
}
|
|
32467
33763
|
function asStringArray(value) {
|
|
32468
33764
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
32469
33765
|
}
|
|
@@ -32474,6 +33770,6 @@ function contextOptions(value) {
|
|
|
32474
33770
|
if (typeof value.compactAtRatio === "number" && Number.isFinite(value.compactAtRatio) && value.compactAtRatio > 0 && value.compactAtRatio <= 1) context.compactAtRatio = value.compactAtRatio;
|
|
32475
33771
|
return Object.keys(context).length ? context : void 0;
|
|
32476
33772
|
}
|
|
32477
|
-
function
|
|
33773
|
+
function errorMessage4(error) {
|
|
32478
33774
|
return error instanceof Error ? error.message : String(error);
|
|
32479
33775
|
}
|