demian-cli 1.0.7 → 1.0.9
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/README.md +13 -2
- package/dist/cli.mjs +1838 -359
- package/dist/index.mjs +1022 -260
- package/dist/tui.mjs +27004 -25460
- package/dist/vscode-worker.mjs +1607 -295
- package/docs/ko/README.md +6 -2
- 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"];
|
|
@@ -61,7 +61,7 @@ function isExitCommand(input2) {
|
|
|
61
61
|
function isStopCommand(input2) {
|
|
62
62
|
return input2.trim().toLowerCase() === "/stop";
|
|
63
63
|
}
|
|
64
|
-
function
|
|
64
|
+
function isCompactCommand(input2) {
|
|
65
65
|
return input2.trim().toLowerCase() === "/compact";
|
|
66
66
|
}
|
|
67
67
|
function isRetryCommand(input2) {
|
|
@@ -971,7 +971,7 @@ var TuiStore = class {
|
|
|
971
971
|
this.#state.promptInput = "";
|
|
972
972
|
this.#state.promptError = void 0;
|
|
973
973
|
this.#state.activity = "starting session";
|
|
974
|
-
if (!
|
|
974
|
+
if (!isCompactCommand(prompt)) {
|
|
975
975
|
this.#clearTurnDiff();
|
|
976
976
|
this.#clearResolvedWorkPlan();
|
|
977
977
|
}
|
|
@@ -986,7 +986,7 @@ var TuiStore = class {
|
|
|
986
986
|
this.#state.promptInput = "";
|
|
987
987
|
this.#state.promptError = void 0;
|
|
988
988
|
this.#state.activity = "starting session";
|
|
989
|
-
if (!
|
|
989
|
+
if (!isCompactCommand(queued)) {
|
|
990
990
|
this.#clearTurnDiff();
|
|
991
991
|
this.#clearResolvedWorkPlan();
|
|
992
992
|
}
|
|
@@ -1234,7 +1234,7 @@ var TuiStore = class {
|
|
|
1234
1234
|
this.stopActiveTask();
|
|
1235
1235
|
return;
|
|
1236
1236
|
}
|
|
1237
|
-
if (
|
|
1237
|
+
if (isCompactCommand(prompt)) {
|
|
1238
1238
|
this.#resolvePrompt(prompt);
|
|
1239
1239
|
return;
|
|
1240
1240
|
}
|
|
@@ -1996,7 +1996,7 @@ var TuiStore = class {
|
|
|
1996
1996
|
this.#state.promptInput = "";
|
|
1997
1997
|
this.#state.promptError = void 0;
|
|
1998
1998
|
this.#state.activity = prompt ? "starting session" : "cancelled";
|
|
1999
|
-
if (prompt && !
|
|
1999
|
+
if (prompt && !isCompactCommand(prompt)) {
|
|
2000
2000
|
this.#clearTurnDiff();
|
|
2001
2001
|
this.#clearResolvedWorkPlan();
|
|
2002
2002
|
}
|
|
@@ -2006,7 +2006,7 @@ var TuiStore = class {
|
|
|
2006
2006
|
}
|
|
2007
2007
|
#rememberPrompt(prompt) {
|
|
2008
2008
|
const value = prompt.trim();
|
|
2009
|
-
if (!value || isExitCommand(value) || isStopCommand(value) ||
|
|
2009
|
+
if (!value || isExitCommand(value) || isStopCommand(value) || isCompactCommand(value) || isRetryCommand(value)) return;
|
|
2010
2010
|
if (this.#promptHistory.at(-1) === value) {
|
|
2011
2011
|
this.#state.canRetryLastPrompt = true;
|
|
2012
2012
|
return;
|
|
@@ -2327,7 +2327,7 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
|
|
2329
2329
|
// src/ui/tui/controller.ts
|
|
2330
|
-
import
|
|
2330
|
+
import path32 from "node:path";
|
|
2331
2331
|
|
|
2332
2332
|
// src/agents/types.ts
|
|
2333
2333
|
function normalizeAgent(input2) {
|
|
@@ -2421,7 +2421,7 @@ var claudeCodeAgent = {
|
|
|
2421
2421
|
name: "claudecode",
|
|
2422
2422
|
description: "Claude Code external coding agent for focused delegated workspace tasks.",
|
|
2423
2423
|
mode: "subagent",
|
|
2424
|
-
provider: { profile: "claudecode
|
|
2424
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
2425
2425
|
tools: [],
|
|
2426
2426
|
systemPrompt: [
|
|
2427
2427
|
"You are demian claudecode, a Claude Code external runtime working as a sub agent for Demian.",
|
|
@@ -2466,7 +2466,7 @@ var claudeCodeExplorerAgent = {
|
|
|
2466
2466
|
name: "claudecode-explorer",
|
|
2467
2467
|
description: "Claude Code external read-only explorer for cowork repository inspection.",
|
|
2468
2468
|
mode: "subagent",
|
|
2469
|
-
provider: { profile: "claudecode
|
|
2469
|
+
provider: { profile: "claudecode", permissionProfile: "explore" },
|
|
2470
2470
|
tools: [],
|
|
2471
2471
|
systemPrompt: [
|
|
2472
2472
|
"You are demian claudecode-explorer, a Claude Code external runtime working as a read-only cowork sub agent for Demian.",
|
|
@@ -2501,7 +2501,7 @@ var claudeCodeBuilderAgent = {
|
|
|
2501
2501
|
name: "claudecode-builder",
|
|
2502
2502
|
description: "Claude Code-backed builder for bounded cowork implementation tasks.",
|
|
2503
2503
|
mode: "subagent",
|
|
2504
|
-
provider: { profile: "claudecode
|
|
2504
|
+
provider: { profile: "claudecode", permissionProfile: "build" },
|
|
2505
2505
|
tools: [],
|
|
2506
2506
|
systemPrompt: [
|
|
2507
2507
|
"You are demian claudecode-builder, a Claude Code external runtime working as a writer cowork sub agent for Demian.",
|
|
@@ -2806,7 +2806,7 @@ import { readFile as readFile6 } from "node:fs/promises";
|
|
|
2806
2806
|
import crypto4 from "node:crypto";
|
|
2807
2807
|
import fs7 from "node:fs";
|
|
2808
2808
|
import os7 from "node:os";
|
|
2809
|
-
import
|
|
2809
|
+
import path13 from "node:path";
|
|
2810
2810
|
|
|
2811
2811
|
// src/providers/retry.ts
|
|
2812
2812
|
var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -3199,12 +3199,14 @@ var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
|
3199
3199
|
var AnthropicProvider = class {
|
|
3200
3200
|
id = "anthropic";
|
|
3201
3201
|
#apiKey;
|
|
3202
|
+
#baseURL;
|
|
3202
3203
|
#defaultModel;
|
|
3203
3204
|
#defaultMaxTokens;
|
|
3204
3205
|
#client;
|
|
3205
3206
|
#onRetry;
|
|
3206
3207
|
constructor(config) {
|
|
3207
3208
|
this.#apiKey = config.apiKey;
|
|
3209
|
+
this.#baseURL = config.baseURL;
|
|
3208
3210
|
this.#defaultModel = config.defaultModel;
|
|
3209
3211
|
this.#defaultMaxTokens = config.defaultMaxTokens ?? 4096;
|
|
3210
3212
|
this.#client = config.client;
|
|
@@ -3249,7 +3251,7 @@ var AnthropicProvider = class {
|
|
|
3249
3251
|
if (this.#client) return this.#client;
|
|
3250
3252
|
if (!this.#apiKey) throw new Error("AnthropicProvider requires apiKey");
|
|
3251
3253
|
const Anthropic = await loadAnthropicConstructor();
|
|
3252
|
-
this.#client = new Anthropic({ apiKey: this.#apiKey });
|
|
3254
|
+
this.#client = new Anthropic({ apiKey: this.#apiKey, baseURL: this.#baseURL });
|
|
3253
3255
|
return this.#client;
|
|
3254
3256
|
}
|
|
3255
3257
|
};
|
|
@@ -3482,19 +3484,56 @@ import { execFile } from "node:child_process";
|
|
|
3482
3484
|
import { promisify } from "node:util";
|
|
3483
3485
|
import fs2 from "node:fs";
|
|
3484
3486
|
import { chmod, mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
3485
|
-
import
|
|
3487
|
+
import path3 from "node:path";
|
|
3486
3488
|
|
|
3487
3489
|
// src/providers/codex-state.ts
|
|
3488
3490
|
import crypto from "node:crypto";
|
|
3489
3491
|
import fs from "node:fs";
|
|
3490
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
|
|
3491
3497
|
import os from "node:os";
|
|
3492
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
|
|
3493
3532
|
function resolveCodexHome(configured) {
|
|
3494
|
-
return
|
|
3533
|
+
return resolveExpandedPath(configured ?? process.env.CODEX_HOME ?? path2.join(os2.homedir(), ".codex"));
|
|
3495
3534
|
}
|
|
3496
3535
|
async function loadOrCreateInstallationId(codexHome) {
|
|
3497
|
-
const filePath =
|
|
3536
|
+
const filePath = path2.join(codexHome, "installation_id");
|
|
3498
3537
|
try {
|
|
3499
3538
|
const existing = (await readFile(filePath, "utf8")).trim();
|
|
3500
3539
|
if (isUuid(existing)) return existing;
|
|
@@ -3516,15 +3555,6 @@ function isNodeError(error, code) {
|
|
|
3516
3555
|
function fileExists(filePath) {
|
|
3517
3556
|
return fs.existsSync(filePath);
|
|
3518
3557
|
}
|
|
3519
|
-
function expandCodexPath(value) {
|
|
3520
|
-
let expanded = value;
|
|
3521
|
-
if (expanded === "~") return os.homedir();
|
|
3522
|
-
if (expanded.startsWith("~/")) expanded = path.join(os.homedir(), expanded.slice(2));
|
|
3523
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
3524
|
-
const name = braced ?? bare;
|
|
3525
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
3526
|
-
});
|
|
3527
|
-
}
|
|
3528
3558
|
|
|
3529
3559
|
// src/providers/codex-auth.ts
|
|
3530
3560
|
var execFileAsync = promisify(execFile);
|
|
@@ -3747,10 +3777,10 @@ var CodexAuthStore = class {
|
|
|
3747
3777
|
}
|
|
3748
3778
|
};
|
|
3749
3779
|
function authFilePath(codexHome) {
|
|
3750
|
-
return
|
|
3780
|
+
return path3.join(codexHome, "auth.json");
|
|
3751
3781
|
}
|
|
3752
3782
|
function codexKeyringAccount(codexHome) {
|
|
3753
|
-
const resolved =
|
|
3783
|
+
const resolved = path3.resolve(codexHome);
|
|
3754
3784
|
const canonical = fs2.existsSync(resolved) ? fs2.realpathSync.native(resolved) : resolved;
|
|
3755
3785
|
const hash = crypto2.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
3756
3786
|
return `cli|${hash}`;
|
|
@@ -4179,8 +4209,8 @@ import { randomUUID } from "node:crypto";
|
|
|
4179
4209
|
import { execFile as execFile2 } from "node:child_process";
|
|
4180
4210
|
import fs3 from "node:fs";
|
|
4181
4211
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
4182
|
-
import
|
|
4183
|
-
import
|
|
4212
|
+
import os3 from "node:os";
|
|
4213
|
+
import path4 from "node:path";
|
|
4184
4214
|
import { promisify as promisify2 } from "node:util";
|
|
4185
4215
|
var execFileAsync2 = promisify2(execFile2);
|
|
4186
4216
|
var CLAUDE_CODE_KEYRING_SERVICE = "Claude Code-credentials";
|
|
@@ -4213,7 +4243,7 @@ function getSharedClaudeCodeAuthStore(options2 = {}) {
|
|
|
4213
4243
|
proactiveRefreshMinutes: options2.proactiveRefreshMinutes ?? 30,
|
|
4214
4244
|
refreshCache: options2.refreshCache ?? "claude-store",
|
|
4215
4245
|
refreshTokenURL: options2.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL,
|
|
4216
|
-
keychainAccount: options2.keychainAccount ??
|
|
4246
|
+
keychainAccount: options2.keychainAccount ?? os3.userInfo().username
|
|
4217
4247
|
});
|
|
4218
4248
|
const existing = sharedAuthStores2.get(key);
|
|
4219
4249
|
if (existing) return existing;
|
|
@@ -4242,7 +4272,7 @@ var ClaudeCodeAuthStore = class {
|
|
|
4242
4272
|
this.#proactiveRefreshMinutes = options2.proactiveRefreshMinutes ?? 30;
|
|
4243
4273
|
this.#refreshCache = options2.refreshCache ?? "claude-store";
|
|
4244
4274
|
this.#refreshTokenURL = options2.refreshTokenURL ?? DEFAULT_CLAUDE_CODE_REFRESH_TOKEN_URL;
|
|
4245
|
-
this.#keychainAccount = options2.keychainAccount ??
|
|
4275
|
+
this.#keychainAccount = options2.keychainAccount ?? os3.userInfo().username;
|
|
4246
4276
|
this.#fetch = options2.fetch ?? fetch;
|
|
4247
4277
|
this.#keyring = options2.keyring ?? new MacOSSecurityKeyring2();
|
|
4248
4278
|
}
|
|
@@ -4443,10 +4473,10 @@ var ClaudeCodeAuthStore = class {
|
|
|
4443
4473
|
}
|
|
4444
4474
|
};
|
|
4445
4475
|
function resolveClaudeConfigDir(configured) {
|
|
4446
|
-
return
|
|
4476
|
+
return resolveExpandedPath(configured ?? process.env.CLAUDE_CONFIG_DIR ?? path4.join(os3.homedir(), ".claude"));
|
|
4447
4477
|
}
|
|
4448
4478
|
function claudeCodeAuthFilePath(claudeConfigDir) {
|
|
4449
|
-
return
|
|
4479
|
+
return path4.join(claudeConfigDir, ".credentials.json");
|
|
4450
4480
|
}
|
|
4451
4481
|
function oauthPayload(auth) {
|
|
4452
4482
|
const raw = auth.claudeAiOauth;
|
|
@@ -4505,15 +4535,6 @@ async function writeClaudeCodeAuthFileAtomic(claudeConfigDir, auth) {
|
|
|
4505
4535
|
await chmod2(tempPath, 384);
|
|
4506
4536
|
await rename2(tempPath, filePath);
|
|
4507
4537
|
}
|
|
4508
|
-
function expandClaudePath(value) {
|
|
4509
|
-
let expanded = value;
|
|
4510
|
-
if (expanded === "~") return os2.homedir();
|
|
4511
|
-
if (expanded.startsWith("~/")) expanded = path3.join(os2.homedir(), expanded.slice(2));
|
|
4512
|
-
return expanded.replace(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/gi, (match, braced, bare) => {
|
|
4513
|
-
const name = braced ?? bare;
|
|
4514
|
-
return name && process.env[name] !== void 0 ? process.env[name] : match;
|
|
4515
|
-
});
|
|
4516
|
-
}
|
|
4517
4538
|
var MacOSSecurityKeyring2 = class {
|
|
4518
4539
|
async load(service, account) {
|
|
4519
4540
|
if (process.platform !== "darwin") return void 0;
|
|
@@ -4750,6 +4771,111 @@ function parseClaudeCodeErrorBody(body) {
|
|
|
4750
4771
|
}
|
|
4751
4772
|
}
|
|
4752
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
|
+
|
|
4753
4879
|
// src/external-runtime/claudecode-cli.ts
|
|
4754
4880
|
import { spawn as spawn3 } from "node:child_process";
|
|
4755
4881
|
import readline from "node:readline";
|
|
@@ -4757,7 +4883,7 @@ import readline from "node:readline";
|
|
|
4757
4883
|
// src/external-runtime/claudecode-attachments.ts
|
|
4758
4884
|
import crypto3 from "node:crypto";
|
|
4759
4885
|
import fs4 from "node:fs";
|
|
4760
|
-
import
|
|
4886
|
+
import path5 from "node:path";
|
|
4761
4887
|
async function resolveClaudeCodeAttachmentPrompt(runtimeLabel, config, req) {
|
|
4762
4888
|
const attachments = req.attachments ?? [];
|
|
4763
4889
|
if (attachments.length === 0) return req.prompt;
|
|
@@ -4799,7 +4925,7 @@ function unsupportedAttachmentError(runtimeLabel, count, detail) {
|
|
|
4799
4925
|
}
|
|
4800
4926
|
function formatAttachmentReference(value, cwd) {
|
|
4801
4927
|
if (isUrl(value)) return imageMimeFromPath(value) ? `[image: ${value} (${imageMimeFromPath(value)}, remote)]` : `[attachment: ${value}]`;
|
|
4802
|
-
const target =
|
|
4928
|
+
const target = path5.isAbsolute(value) ? value : path5.resolve(cwd, value);
|
|
4803
4929
|
try {
|
|
4804
4930
|
const stats = fs4.statSync(target);
|
|
4805
4931
|
if (!stats.isFile()) return `[attachment: ${value} (${stats.size} bytes)]`;
|
|
@@ -4816,7 +4942,7 @@ function isUrl(value) {
|
|
|
4816
4942
|
}
|
|
4817
4943
|
function imageMimeFromPath(value) {
|
|
4818
4944
|
const pathname = isUrl(value) ? new URL(value).pathname : value;
|
|
4819
|
-
const ext =
|
|
4945
|
+
const ext = path5.extname(pathname).toLowerCase();
|
|
4820
4946
|
if (ext === ".png") return "image/png";
|
|
4821
4947
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
4822
4948
|
if (ext === ".gif") return "image/gif";
|
|
@@ -4848,15 +4974,14 @@ function hasAnthropicApiKeyEnv(env = process.env) {
|
|
|
4848
4974
|
|
|
4849
4975
|
// src/external-runtime/claudecode-paths.ts
|
|
4850
4976
|
import fs5 from "node:fs";
|
|
4851
|
-
import
|
|
4852
|
-
import path5 from "node:path";
|
|
4977
|
+
import path6 from "node:path";
|
|
4853
4978
|
function resolveClaudeCodeCliPath(configured) {
|
|
4854
4979
|
if (configured) return expandHome(configured);
|
|
4855
4980
|
if (process.env.CLAUDE_CODE_CLI) return expandHome(process.env.CLAUDE_CODE_CLI);
|
|
4856
4981
|
const candidates = ["~/.local/bin/claude", "claude"];
|
|
4857
4982
|
for (const candidate of candidates) {
|
|
4858
4983
|
const expanded = expandHome(candidate);
|
|
4859
|
-
if (
|
|
4984
|
+
if (path6.isAbsolute(expanded)) {
|
|
4860
4985
|
if (isExecutableFile(expanded)) return expanded;
|
|
4861
4986
|
continue;
|
|
4862
4987
|
}
|
|
@@ -4865,9 +4990,7 @@ function resolveClaudeCodeCliPath(configured) {
|
|
|
4865
4990
|
return void 0;
|
|
4866
4991
|
}
|
|
4867
4992
|
function expandHome(value) {
|
|
4868
|
-
|
|
4869
|
-
if (value.startsWith("~/")) return path5.join(os3.homedir(), value.slice(2));
|
|
4870
|
-
return value;
|
|
4993
|
+
return expandPathReferences(value);
|
|
4871
4994
|
}
|
|
4872
4995
|
function isExecutableFile(filePath) {
|
|
4873
4996
|
try {
|
|
@@ -5133,14 +5256,14 @@ function numberValue2(value) {
|
|
|
5133
5256
|
// src/external-runtime/session-lock.ts
|
|
5134
5257
|
import { mkdir as mkdir4, open, readFile as readFile4, unlink } from "node:fs/promises";
|
|
5135
5258
|
import os4 from "node:os";
|
|
5136
|
-
import
|
|
5259
|
+
import path7 from "node:path";
|
|
5137
5260
|
var DEFAULT_STALE_MS = 30 * 60 * 1e3;
|
|
5138
5261
|
async function acquireClaudeCodeSessionLock(sessionId, options2 = {}) {
|
|
5139
|
-
const dir = options2.dir ??
|
|
5262
|
+
const dir = options2.dir ?? path7.join(os4.homedir(), ".demian", "claude-sessions");
|
|
5140
5263
|
const staleMs = options2.staleMs ?? DEFAULT_STALE_MS;
|
|
5141
5264
|
const now2 = options2.now ?? (() => Date.now());
|
|
5142
5265
|
await mkdir4(dir, { recursive: true });
|
|
5143
|
-
const lockPath =
|
|
5266
|
+
const lockPath = path7.join(dir, `${encodeURIComponent(sessionId)}.lock`);
|
|
5144
5267
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5145
5268
|
try {
|
|
5146
5269
|
const handle = await open(lockPath, "wx");
|
|
@@ -5204,14 +5327,14 @@ function isAlreadyExists(error) {
|
|
|
5204
5327
|
// src/external-runtime/usage-ledger.ts
|
|
5205
5328
|
import { mkdir as mkdir5, readFile as readFile5, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
|
|
5206
5329
|
import os5 from "node:os";
|
|
5207
|
-
import
|
|
5330
|
+
import path8 from "node:path";
|
|
5208
5331
|
var processLedger = /* @__PURE__ */ new Map();
|
|
5209
5332
|
var ClaudeCodeUsageLedger = class {
|
|
5210
5333
|
#scope;
|
|
5211
5334
|
#filePath;
|
|
5212
5335
|
constructor(options2 = {}) {
|
|
5213
5336
|
this.#scope = options2.scope ?? "process";
|
|
5214
|
-
this.#filePath = options2.filePath ??
|
|
5337
|
+
this.#filePath = options2.filePath ?? path8.join(os5.homedir(), ".demian", "usage-ledger.json");
|
|
5215
5338
|
}
|
|
5216
5339
|
async spentUsd(key, now2 = /* @__PURE__ */ new Date()) {
|
|
5217
5340
|
if (this.#scope === "process") return processLedger.get(processKey(key)) ?? 0;
|
|
@@ -5242,7 +5365,7 @@ var ClaudeCodeUsageLedger = class {
|
|
|
5242
5365
|
return { version: 1, buckets: {} };
|
|
5243
5366
|
}
|
|
5244
5367
|
async #write(file) {
|
|
5245
|
-
await mkdir5(
|
|
5368
|
+
await mkdir5(path8.dirname(this.#filePath), { recursive: true });
|
|
5246
5369
|
const temp = `${this.#filePath}.${process.pid}.tmp`;
|
|
5247
5370
|
await writeFile4(temp, `${JSON.stringify(file, null, 2)}
|
|
5248
5371
|
`, "utf8");
|
|
@@ -24903,10 +25026,10 @@ function now() {
|
|
|
24903
25026
|
}
|
|
24904
25027
|
|
|
24905
25028
|
// src/permissions/engine.ts
|
|
24906
|
-
import
|
|
25029
|
+
import path11 from "node:path";
|
|
24907
25030
|
|
|
24908
25031
|
// src/permissions/grants.ts
|
|
24909
|
-
import
|
|
25032
|
+
import path9 from "node:path";
|
|
24910
25033
|
|
|
24911
25034
|
// src/util.ts
|
|
24912
25035
|
function stableStringify(value) {
|
|
@@ -24961,8 +25084,8 @@ function grantKeyFor(tool, input2, cwd) {
|
|
|
24961
25084
|
}
|
|
24962
25085
|
if (tool === "write_file" || tool === "edit_file") {
|
|
24963
25086
|
const filePath = typeof object2.path === "string" ? object2.path : "";
|
|
24964
|
-
const parent = filePath ?
|
|
24965
|
-
return `${tool}:${
|
|
25087
|
+
const parent = filePath ? path9.dirname(path9.resolve(cwd, filePath)) : cwd;
|
|
25088
|
+
return `${tool}:${path9.relative(cwd, parent) || "."}`;
|
|
24966
25089
|
}
|
|
24967
25090
|
return `${tool}:${stableStringify(input2)}`;
|
|
24968
25091
|
}
|
|
@@ -24989,30 +25112,30 @@ function firstCommandTokens(command, count) {
|
|
|
24989
25112
|
}
|
|
24990
25113
|
|
|
24991
25114
|
// src/workspace/paths.ts
|
|
24992
|
-
import
|
|
25115
|
+
import path10 from "node:path";
|
|
24993
25116
|
function normalizePathForMatch(value) {
|
|
24994
|
-
return value.split(
|
|
25117
|
+
return value.split(path10.sep).join("/");
|
|
24995
25118
|
}
|
|
24996
25119
|
function isInsidePath(root, candidate) {
|
|
24997
|
-
const relative =
|
|
24998
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
25120
|
+
const relative = path10.relative(path10.resolve(root), path10.resolve(candidate));
|
|
25121
|
+
return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
|
|
24999
25122
|
}
|
|
25000
25123
|
function resolveInsideCwd(cwd, inputPath) {
|
|
25001
25124
|
if (typeof inputPath !== "string" || inputPath.length === 0) {
|
|
25002
25125
|
throw new Error("path must be a non-empty string");
|
|
25003
25126
|
}
|
|
25004
|
-
const resolved =
|
|
25127
|
+
const resolved = path10.resolve(cwd, inputPath);
|
|
25005
25128
|
if (!isInsidePath(cwd, resolved)) {
|
|
25006
25129
|
throw new Error(`Path is outside the workspace: ${inputPath}`);
|
|
25007
25130
|
}
|
|
25008
25131
|
return resolved;
|
|
25009
25132
|
}
|
|
25010
25133
|
function relativeToCwd(cwd, absolutePath) {
|
|
25011
|
-
const relative =
|
|
25134
|
+
const relative = path10.relative(cwd, absolutePath);
|
|
25012
25135
|
return normalizePathForMatch(relative || ".");
|
|
25013
25136
|
}
|
|
25014
25137
|
function isEnvFile(filePath) {
|
|
25015
|
-
const base =
|
|
25138
|
+
const base = path10.basename(filePath);
|
|
25016
25139
|
if (base === ".env.example") return false;
|
|
25017
25140
|
return base === ".env" || base.startsWith(".env.");
|
|
25018
25141
|
}
|
|
@@ -25119,7 +25242,7 @@ var PermissionEngine = class {
|
|
|
25119
25242
|
#hardDeny(tool, input2, grantKey) {
|
|
25120
25243
|
const paths = inputPaths(tool, input2);
|
|
25121
25244
|
for (const item of paths) {
|
|
25122
|
-
const resolved =
|
|
25245
|
+
const resolved = path11.resolve(this.#cwd, item);
|
|
25123
25246
|
if (!isInsidePath(this.#cwd, resolved)) {
|
|
25124
25247
|
return {
|
|
25125
25248
|
decision: "deny",
|
|
@@ -25173,7 +25296,7 @@ function matchesRule(rule, tool, input2, cwd) {
|
|
|
25173
25296
|
if (rule.match.pathGlob) {
|
|
25174
25297
|
const paths = inputPaths(tool, input2);
|
|
25175
25298
|
if (paths.length === 0) return false;
|
|
25176
|
-
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))))) {
|
|
25177
25300
|
return false;
|
|
25178
25301
|
}
|
|
25179
25302
|
}
|
|
@@ -25220,7 +25343,7 @@ function rulePriority(rule, ref) {
|
|
|
25220
25343
|
}
|
|
25221
25344
|
function matchesPathRule(pattern, relativePath) {
|
|
25222
25345
|
if (!pattern) return true;
|
|
25223
|
-
if (
|
|
25346
|
+
if (path11.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
|
|
25224
25347
|
return matchGlob(pattern, relativePath);
|
|
25225
25348
|
}
|
|
25226
25349
|
function mostSpecificRule(rules, input2, cwd, ref) {
|
|
@@ -25298,13 +25421,13 @@ function permissionLabel(req) {
|
|
|
25298
25421
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
25299
25422
|
return `${req.tool}: ${inputObject.path}`;
|
|
25300
25423
|
}
|
|
25301
|
-
const
|
|
25302
|
-
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}`;
|
|
25303
25426
|
return tool;
|
|
25304
25427
|
}
|
|
25305
25428
|
|
|
25306
25429
|
// src/workspace/write-scope.ts
|
|
25307
|
-
import
|
|
25430
|
+
import path12 from "node:path";
|
|
25308
25431
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
25309
25432
|
var WORKSPACE_SCOPE = "**";
|
|
25310
25433
|
function normalizeWriteScope(cwd, scope) {
|
|
@@ -25326,7 +25449,7 @@ function enforceToolWriteScope(input2) {
|
|
|
25326
25449
|
if (paths.length === 0) return { ok: true, paths: [] };
|
|
25327
25450
|
const checked = [];
|
|
25328
25451
|
for (const target of paths) {
|
|
25329
|
-
const resolved =
|
|
25452
|
+
const resolved = path12.resolve(input2.cwd, target);
|
|
25330
25453
|
const relative = relativeToCwd(input2.cwd, resolved);
|
|
25331
25454
|
checked.push(relative);
|
|
25332
25455
|
const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
|
|
@@ -25350,7 +25473,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
|
|
|
25350
25473
|
return out;
|
|
25351
25474
|
}
|
|
25352
25475
|
function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
|
|
25353
|
-
const resolved =
|
|
25476
|
+
const resolved = path12.resolve(cwd, inputPath);
|
|
25354
25477
|
if (!isInsidePath(cwd, resolved)) {
|
|
25355
25478
|
return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
|
|
25356
25479
|
}
|
|
@@ -25369,9 +25492,9 @@ function normalizeScopePattern(cwd, raw) {
|
|
|
25369
25492
|
if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
|
|
25370
25493
|
if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
|
|
25371
25494
|
const hasGlob2 = /[*?[\]{}]/.test(value);
|
|
25372
|
-
if (
|
|
25495
|
+
if (path12.isAbsolute(value)) {
|
|
25373
25496
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
25374
|
-
const resolved =
|
|
25497
|
+
const resolved = path12.resolve(value);
|
|
25375
25498
|
if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
|
|
25376
25499
|
value = relativeToCwd(cwd, resolved);
|
|
25377
25500
|
}
|
|
@@ -25748,6 +25871,7 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
|
|
|
25748
25871
|
}
|
|
25749
25872
|
|
|
25750
25873
|
// src/config.ts
|
|
25874
|
+
var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
|
|
25751
25875
|
var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
|
|
25752
25876
|
var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
|
|
25753
25877
|
var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
|
|
@@ -25785,7 +25909,7 @@ var defaultConfig = {
|
|
|
25785
25909
|
maxConcurrentExpensive: 1,
|
|
25786
25910
|
maxConcurrentWriters: 1,
|
|
25787
25911
|
maxConcurrentPerProvider: {
|
|
25788
|
-
|
|
25912
|
+
claudecode: 1
|
|
25789
25913
|
},
|
|
25790
25914
|
tokenBudget: null,
|
|
25791
25915
|
defaultMergeStrategy: "synthesize",
|
|
@@ -25840,6 +25964,7 @@ var defaultConfig = {
|
|
|
25840
25964
|
defaultProvider: "brave",
|
|
25841
25965
|
providers: {
|
|
25842
25966
|
brave: {
|
|
25967
|
+
apiKey: "",
|
|
25843
25968
|
apiKeyEnv: "BRAVE_SEARCH_API_KEY",
|
|
25844
25969
|
endpoint: "https://api.search.brave.com/res/v1/web/search",
|
|
25845
25970
|
count: 10,
|
|
@@ -25848,12 +25973,14 @@ var defaultConfig = {
|
|
|
25848
25973
|
safeSearch: "moderate"
|
|
25849
25974
|
},
|
|
25850
25975
|
tavily: {
|
|
25976
|
+
apiKey: "",
|
|
25851
25977
|
apiKeyEnv: "TAVILY_API_KEY",
|
|
25852
25978
|
endpoint: "https://api.tavily.com/search",
|
|
25853
25979
|
maxResults: 5,
|
|
25854
25980
|
searchDepth: "basic"
|
|
25855
25981
|
},
|
|
25856
25982
|
exa: {
|
|
25983
|
+
apiKey: "",
|
|
25857
25984
|
apiKeyEnv: "EXA_API_KEY",
|
|
25858
25985
|
endpoint: "https://api.exa.ai/search",
|
|
25859
25986
|
numResults: 5,
|
|
@@ -25907,73 +26034,113 @@ var defaultConfig = {
|
|
|
25907
26034
|
providers: {
|
|
25908
26035
|
openai: {
|
|
25909
26036
|
type: "openai-compatible",
|
|
25910
|
-
model: "openai-model-name",
|
|
25911
26037
|
baseURL: "https://api.openai.com/v1",
|
|
25912
|
-
|
|
26038
|
+
apiKey: "",
|
|
26039
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
26040
|
+
catalog: {
|
|
26041
|
+
type: "openai-models",
|
|
26042
|
+
endpoint: "https://api.openai.com/v1/models"
|
|
26043
|
+
}
|
|
26044
|
+
},
|
|
26045
|
+
anthropic: {
|
|
26046
|
+
type: "anthropic",
|
|
26047
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
26048
|
+
apiKey: "",
|
|
26049
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
26050
|
+
catalog: {
|
|
26051
|
+
type: "anthropic-models",
|
|
26052
|
+
endpoint: "https://api.anthropic.com/v1/models"
|
|
26053
|
+
}
|
|
25913
26054
|
},
|
|
25914
26055
|
gemini: {
|
|
25915
26056
|
type: "openai-compatible",
|
|
25916
|
-
model: "gemini-model-name",
|
|
25917
26057
|
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
25918
|
-
|
|
26058
|
+
apiKey: "",
|
|
26059
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
26060
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
26061
|
+
catalog: {
|
|
26062
|
+
type: "gemini-openai-models",
|
|
26063
|
+
endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models"
|
|
26064
|
+
}
|
|
26065
|
+
},
|
|
26066
|
+
groq: {
|
|
26067
|
+
type: "openai-compatible",
|
|
26068
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
26069
|
+
apiKey: "",
|
|
26070
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
26071
|
+
catalog: {
|
|
26072
|
+
type: "groq-models",
|
|
26073
|
+
endpoint: "https://api.groq.com/openai/v1/models"
|
|
26074
|
+
}
|
|
25919
26075
|
},
|
|
25920
|
-
|
|
26076
|
+
azure: {
|
|
25921
26077
|
type: "openai-compatible",
|
|
25922
|
-
|
|
25923
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
26078
|
+
auth: { type: "api-key", header: "api-key" },
|
|
26079
|
+
modelProfiles: [
|
|
26080
|
+
{
|
|
26081
|
+
name: "azure-example",
|
|
26082
|
+
displayName: "Azure example",
|
|
26083
|
+
model: "azure-deployment-name",
|
|
26084
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
26085
|
+
apiKey: "",
|
|
26086
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
26087
|
+
}
|
|
26088
|
+
]
|
|
25926
26089
|
},
|
|
25927
26090
|
lmstudio: {
|
|
25928
26091
|
type: "openai-compatible",
|
|
25929
|
-
model: "local-coder-model",
|
|
25930
26092
|
baseURL: "http://localhost:1234/v1",
|
|
25931
|
-
apiKey: "lm-studio"
|
|
26093
|
+
apiKey: "lm-studio",
|
|
26094
|
+
modelProfiles: [],
|
|
26095
|
+
catalog: {
|
|
26096
|
+
type: "openai-compatible-models",
|
|
26097
|
+
endpoint: "http://localhost:1234/v1/models"
|
|
26098
|
+
}
|
|
25932
26099
|
},
|
|
25933
|
-
|
|
26100
|
+
"ollama-local": {
|
|
25934
26101
|
type: "openai-compatible",
|
|
25935
|
-
|
|
25936
|
-
|
|
25937
|
-
|
|
26102
|
+
baseURL: "http://localhost:11434/v1",
|
|
26103
|
+
apiKey: "ollama",
|
|
26104
|
+
modelProfiles: [],
|
|
26105
|
+
quirks: { omitTemperature: true },
|
|
26106
|
+
catalog: {
|
|
26107
|
+
type: "openai-compatible-models",
|
|
26108
|
+
endpoint: "http://localhost:11434/v1/models"
|
|
26109
|
+
}
|
|
26110
|
+
},
|
|
26111
|
+
"ollama-cloud": {
|
|
26112
|
+
type: "ollama",
|
|
26113
|
+
baseURL: "https://ollama.com/api",
|
|
26114
|
+
apiKey: "",
|
|
26115
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
26116
|
+
modelProfiles: [],
|
|
26117
|
+
catalog: {
|
|
26118
|
+
type: "ollama-tags",
|
|
26119
|
+
endpoint: "https://ollama.com/api/tags"
|
|
26120
|
+
}
|
|
25938
26121
|
},
|
|
25939
26122
|
llamacpp: {
|
|
25940
26123
|
type: "openai-compatible",
|
|
25941
|
-
model: "local-coder-model",
|
|
25942
26124
|
baseURL: "http://localhost:8080/v1",
|
|
25943
|
-
apiKey: "llama.cpp"
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
apiKeyEnv: "OPENROUTER_API_KEY"
|
|
25950
|
-
},
|
|
25951
|
-
together: {
|
|
25952
|
-
type: "openai-compatible",
|
|
25953
|
-
model: "provider/model-name",
|
|
25954
|
-
baseURL: "https://api.together.xyz/v1",
|
|
25955
|
-
apiKeyEnv: "TOGETHER_API_KEY"
|
|
25956
|
-
},
|
|
25957
|
-
groq: {
|
|
25958
|
-
type: "openai-compatible",
|
|
25959
|
-
model: "provider/model-name",
|
|
25960
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
25961
|
-
apiKeyEnv: "GROQ_API_KEY"
|
|
26125
|
+
apiKey: "llama.cpp",
|
|
26126
|
+
modelProfiles: [],
|
|
26127
|
+
catalog: {
|
|
26128
|
+
type: "openai-compatible-models",
|
|
26129
|
+
endpoint: "http://localhost:8080/v1/models"
|
|
26130
|
+
}
|
|
25962
26131
|
},
|
|
25963
|
-
|
|
26132
|
+
vllm: {
|
|
25964
26133
|
type: "openai-compatible",
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
apiKeyEnv: "ANTHROPIC_API_KEY"
|
|
26134
|
+
baseURL: "http://localhost:8000/v1",
|
|
26135
|
+
apiKey: "vllm",
|
|
26136
|
+
modelProfiles: [],
|
|
26137
|
+
catalog: {
|
|
26138
|
+
type: "openai-compatible-models",
|
|
26139
|
+
endpoint: "http://localhost:8000/v1/models"
|
|
26140
|
+
}
|
|
25973
26141
|
},
|
|
25974
26142
|
codex: {
|
|
25975
26143
|
type: "codex",
|
|
25976
|
-
model: "gpt-5.1-codex",
|
|
25977
26144
|
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
25978
26145
|
authStore: "auto",
|
|
25979
26146
|
allowApiKeyFallback: false,
|
|
@@ -25985,6 +26152,9 @@ var defaultConfig = {
|
|
|
25985
26152
|
effort: "medium",
|
|
25986
26153
|
summary: "auto"
|
|
25987
26154
|
}
|
|
26155
|
+
},
|
|
26156
|
+
catalog: {
|
|
26157
|
+
type: "codex-oauth-models"
|
|
25988
26158
|
}
|
|
25989
26159
|
},
|
|
25990
26160
|
claudecode: {
|
|
@@ -26003,67 +26173,37 @@ var defaultConfig = {
|
|
|
26003
26173
|
useBareMode: false,
|
|
26004
26174
|
usageLedgerScope: "process",
|
|
26005
26175
|
sessionLock: true,
|
|
26006
|
-
abortPolicy: "record-only"
|
|
26007
|
-
},
|
|
26008
|
-
"claudecode-plan": {
|
|
26009
|
-
type: "claudecode",
|
|
26010
|
-
runtime: "agent-sdk",
|
|
26011
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
26012
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
26013
|
-
permissionProfile: "plan",
|
|
26014
|
-
cliPath: "~/.local/bin/claude",
|
|
26015
|
-
cwdMode: "session",
|
|
26016
|
-
historyPolicy: "passthrough-resume",
|
|
26017
|
-
onInvalidResume: "fresh",
|
|
26018
|
-
attachmentFallback: "block",
|
|
26019
|
-
allowSubagents: false,
|
|
26020
|
-
sanitizeApiKeyEnv: true,
|
|
26021
|
-
authPreflight: true,
|
|
26022
|
-
useBareMode: false,
|
|
26023
|
-
usageLedgerScope: "process",
|
|
26024
|
-
sessionLock: true,
|
|
26025
|
-
abortPolicy: "record-only",
|
|
26026
|
-
hidden: true
|
|
26027
|
-
},
|
|
26028
|
-
"claudecode-subagent": {
|
|
26029
|
-
type: "claudecode",
|
|
26030
|
-
runtime: "agent-sdk",
|
|
26031
|
-
model: CLAUDE_CODE_SONNET_MODEL,
|
|
26032
|
-
models: [...CLAUDE_CODE_MODELS],
|
|
26033
|
-
permissionProfile: "build",
|
|
26034
|
-
cliPath: "~/.local/bin/claude",
|
|
26035
|
-
cwdMode: "session",
|
|
26036
|
-
historyPolicy: "passthrough-resume",
|
|
26037
|
-
onInvalidResume: "fresh",
|
|
26038
|
-
attachmentFallback: "block",
|
|
26039
|
-
allowSubagents: false,
|
|
26040
|
-
sanitizeApiKeyEnv: true,
|
|
26041
|
-
authPreflight: true,
|
|
26042
|
-
useBareMode: false,
|
|
26043
|
-
usageLedgerScope: "process",
|
|
26044
|
-
sessionLock: true,
|
|
26045
26176
|
abortPolicy: "record-only",
|
|
26046
|
-
|
|
26177
|
+
catalog: {
|
|
26178
|
+
type: "claudecode-supported-models"
|
|
26179
|
+
}
|
|
26047
26180
|
}
|
|
26048
26181
|
}
|
|
26049
26182
|
};
|
|
26050
26183
|
async function loadConfig(options2) {
|
|
26051
26184
|
const configs = [];
|
|
26052
|
-
const userConfigDir =
|
|
26053
|
-
const
|
|
26054
|
-
const configPaths = [
|
|
26055
|
-
path12.join(userConfigDir, "config.json"),
|
|
26056
|
-
path12.join(userConfigDir, "config.jsond"),
|
|
26057
|
-
path12.join(projectConfigDir, "config.json"),
|
|
26058
|
-
path12.join(projectConfigDir, "config.jsond"),
|
|
26059
|
-
options2.configPath
|
|
26060
|
-
].filter(Boolean);
|
|
26185
|
+
const userConfigDir = path13.join(os7.homedir(), ".demian");
|
|
26186
|
+
const configPaths = [path13.join(userConfigDir, "config.json"), path13.join(userConfigDir, "config.jsond"), options2.configPath].filter(Boolean);
|
|
26061
26187
|
for (const filePath of configPaths) {
|
|
26062
26188
|
if (!fs7.existsSync(filePath)) continue;
|
|
26063
|
-
|
|
26189
|
+
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
26190
|
+
warnIfV1Config(parsed, filePath);
|
|
26191
|
+
configs.push(parsed);
|
|
26064
26192
|
}
|
|
26065
26193
|
return configs.reduce((acc, item) => mergeConfig(acc, item), structuredClone(defaultConfig));
|
|
26066
26194
|
}
|
|
26195
|
+
function warnIfV1Config(parsed, filePath) {
|
|
26196
|
+
if (parsed.version === 2) return;
|
|
26197
|
+
const providers = parsed.providers ?? {};
|
|
26198
|
+
const hasLegacyShape = Object.values(providers).some((provider) => {
|
|
26199
|
+
const candidate = provider;
|
|
26200
|
+
return (typeof candidate.model === "string" || Array.isArray(candidate.models)) && !Array.isArray(candidate.modelProfiles);
|
|
26201
|
+
});
|
|
26202
|
+
if (!hasLegacyShape && parsed.version === void 0 && Object.keys(providers).length === 0) return;
|
|
26203
|
+
if (!hasLegacyShape) return;
|
|
26204
|
+
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.
|
|
26205
|
+
`);
|
|
26206
|
+
}
|
|
26067
26207
|
function parseConfigText(text, filePath) {
|
|
26068
26208
|
try {
|
|
26069
26209
|
return JSON.parse(filePath.endsWith(".jsond") ? stripJsonD(text) : text);
|
|
@@ -26270,7 +26410,137 @@ function mergeConfig(base, overlay) {
|
|
|
26270
26410
|
};
|
|
26271
26411
|
}
|
|
26272
26412
|
function normalizeProviderAlias(providerName) {
|
|
26273
|
-
|
|
26413
|
+
if (providerName === "claudecode-plan" || providerName === "claudecode-subagent") return "claudecode";
|
|
26414
|
+
if (providerName === "ollama") return "ollama-local";
|
|
26415
|
+
return providerName;
|
|
26416
|
+
}
|
|
26417
|
+
function sortProviderNames(names) {
|
|
26418
|
+
const order = new Map(BUILTIN_PROVIDER_ORDER.map((name, index) => [name, index]));
|
|
26419
|
+
return [...names].sort((left, right) => {
|
|
26420
|
+
const leftIndex = order.get(left);
|
|
26421
|
+
const rightIndex = order.get(right);
|
|
26422
|
+
if (leftIndex !== void 0 && rightIndex !== void 0) return leftIndex - rightIndex;
|
|
26423
|
+
if (leftIndex !== void 0) return -1;
|
|
26424
|
+
if (rightIndex !== void 0) return 1;
|
|
26425
|
+
return left.localeCompare(right);
|
|
26426
|
+
});
|
|
26427
|
+
}
|
|
26428
|
+
function providerModelProfiles(providerName, providerConfig) {
|
|
26429
|
+
const explicit = Array.isArray(providerConfig.modelProfiles) ? providerConfig.modelProfiles.filter((profile) => typeof profile?.model === "string" && profile.model.trim()).map((profile) => ({
|
|
26430
|
+
...profile,
|
|
26431
|
+
name: profile.name?.trim() || profile.displayName?.trim() || profile.model.trim(),
|
|
26432
|
+
displayName: profile.displayName?.trim() || profile.name?.trim() || profile.model.trim(),
|
|
26433
|
+
model: profile.model.trim()
|
|
26434
|
+
})) : [];
|
|
26435
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
26436
|
+
const seenDisplayNames = /* @__PURE__ */ new Set();
|
|
26437
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
26438
|
+
const out = [];
|
|
26439
|
+
for (const profile of explicit) {
|
|
26440
|
+
if (seenNames.has(profile.name)) {
|
|
26441
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile name "${profile.name}". Later entry ignored.
|
|
26442
|
+
`);
|
|
26443
|
+
continue;
|
|
26444
|
+
}
|
|
26445
|
+
if (profile.displayName && seenDisplayNames.has(profile.displayName)) {
|
|
26446
|
+
process.stderr.write(`[demian] warning: provider ${providerName} has duplicate modelProfile displayName "${profile.displayName}". Later entry ignored.
|
|
26447
|
+
`);
|
|
26448
|
+
continue;
|
|
26449
|
+
}
|
|
26450
|
+
seenNames.add(profile.name);
|
|
26451
|
+
if (profile.displayName) seenDisplayNames.add(profile.displayName);
|
|
26452
|
+
seenModels.add(profile.model);
|
|
26453
|
+
out.push(profile);
|
|
26454
|
+
}
|
|
26455
|
+
const legacy = [...typeof providerConfig.model === "string" && providerConfig.model.trim() ? [providerConfig.model.trim()] : [], ...(providerConfig.models ?? []).filter((model) => typeof model === "string" && model.trim())];
|
|
26456
|
+
for (const model of legacy) {
|
|
26457
|
+
const trimmed = model.trim();
|
|
26458
|
+
if (!trimmed || seenModels.has(trimmed)) continue;
|
|
26459
|
+
seenModels.add(trimmed);
|
|
26460
|
+
const name = trimmed;
|
|
26461
|
+
if (!seenNames.has(name)) seenNames.add(name);
|
|
26462
|
+
out.push({ name, displayName: name, model: trimmed });
|
|
26463
|
+
}
|
|
26464
|
+
const fallback = fallbackModelForProvider(providerName, providerConfig);
|
|
26465
|
+
if (out.length === 0 && fallback) out.push({ name: fallback, displayName: fallback, model: fallback });
|
|
26466
|
+
return out;
|
|
26467
|
+
}
|
|
26468
|
+
function providerModelOptions(providerName, providerConfig) {
|
|
26469
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => displayModelForProfile(profile));
|
|
26470
|
+
}
|
|
26471
|
+
function defaultModelForProvider(providerName, providerConfig) {
|
|
26472
|
+
return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
|
|
26473
|
+
}
|
|
26474
|
+
function displayModelForProfile(profile) {
|
|
26475
|
+
return profile.displayName?.trim() || profile.name?.trim() || profile.model;
|
|
26476
|
+
}
|
|
26477
|
+
function defaultDisplayModelForProvider(providerName, providerConfig) {
|
|
26478
|
+
const profile = providerModelProfiles(providerName, providerConfig)[0];
|
|
26479
|
+
return profile ? displayModelForProfile(profile) : defaultModelForProvider(providerName, providerConfig);
|
|
26480
|
+
}
|
|
26481
|
+
function resolveConfiguredApiKey(providerConfig) {
|
|
26482
|
+
if (providerConfig.apiKey) return { value: providerConfig.apiKey };
|
|
26483
|
+
for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
|
|
26484
|
+
if (!envName) continue;
|
|
26485
|
+
const value = process.env[envName];
|
|
26486
|
+
if (value) return { value, envName };
|
|
26487
|
+
}
|
|
26488
|
+
return { envName: providerConfig.apiKeyEnv ?? providerConfig.apiKeyEnvAliases?.[0] };
|
|
26489
|
+
}
|
|
26490
|
+
function resolveProviderRuntimeConfig(config, selection) {
|
|
26491
|
+
const providerName = normalizeProviderAlias(selection.providerName);
|
|
26492
|
+
const providerConfig = resolveProviderConfig(config, providerName);
|
|
26493
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
26494
|
+
let profile;
|
|
26495
|
+
if (selection.modelProfileName) profile = profiles.find((item) => item.name === selection.modelProfileName);
|
|
26496
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.displayName === selection.model);
|
|
26497
|
+
if (!profile && selection.model) profile = profiles.find((item) => item.model === selection.model);
|
|
26498
|
+
if (!profile) profile = profiles[0];
|
|
26499
|
+
if (!profile) {
|
|
26500
|
+
const model = selection.model ?? defaultModelForProvider(providerName, providerConfig);
|
|
26501
|
+
if (!model) throw new Error(`Provider ${providerName} has no model. Run \`demian config models ${providerName} --refresh\` or add a model profile.`);
|
|
26502
|
+
return { providerName, providerConfig, model };
|
|
26503
|
+
}
|
|
26504
|
+
const merged = mergeModelProfile(providerConfig, profile);
|
|
26505
|
+
return {
|
|
26506
|
+
providerName,
|
|
26507
|
+
providerConfig: merged,
|
|
26508
|
+
model: profile.model,
|
|
26509
|
+
modelProfileName: profile.name
|
|
26510
|
+
};
|
|
26511
|
+
}
|
|
26512
|
+
function mergeModelProfile(providerConfig, profile) {
|
|
26513
|
+
const merged = {
|
|
26514
|
+
...providerConfig,
|
|
26515
|
+
...definedOnly({
|
|
26516
|
+
baseURL: profile.baseURL,
|
|
26517
|
+
apiKey: nonEmptyString(profile.apiKey),
|
|
26518
|
+
apiKeyEnv: profile.apiKeyEnv,
|
|
26519
|
+
apiKeyEnvAliases: profile.apiKeyEnvAliases,
|
|
26520
|
+
auth: profile.auth,
|
|
26521
|
+
headers: profile.headers,
|
|
26522
|
+
quirks: profile.quirks,
|
|
26523
|
+
maxTokens: profile.maxTokens
|
|
26524
|
+
}),
|
|
26525
|
+
model: profile.model
|
|
26526
|
+
};
|
|
26527
|
+
return merged;
|
|
26528
|
+
}
|
|
26529
|
+
function definedOnly(input2) {
|
|
26530
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
|
|
26531
|
+
}
|
|
26532
|
+
function nonEmptyString(value) {
|
|
26533
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
26534
|
+
}
|
|
26535
|
+
function fallbackModelForProvider(providerName, providerConfig) {
|
|
26536
|
+
if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
|
|
26537
|
+
if (providerName === "openai") return "gpt-5.5";
|
|
26538
|
+
if (providerName === "anthropic") return CLAUDE_CODE_SONNET_MODEL;
|
|
26539
|
+
if (providerName === "codex" || providerConfig.type === "codex") return "gpt-5.5";
|
|
26540
|
+
if (providerName === "claudecode") return CLAUDE_CODE_SONNET_MODEL;
|
|
26541
|
+
if (providerName === "gemini") return "gemini-2.5-pro";
|
|
26542
|
+
if (providerName === "groq") return "openai/gpt-oss-120b";
|
|
26543
|
+
return void 0;
|
|
26274
26544
|
}
|
|
26275
26545
|
function migrateProviderConfig(provider, baseProvider) {
|
|
26276
26546
|
if (isLegacyClaudeCodeShape(provider)) {
|
|
@@ -26352,12 +26622,14 @@ function resolveAgentMode(config, flagMode) {
|
|
|
26352
26622
|
return "single-agent";
|
|
26353
26623
|
}
|
|
26354
26624
|
function resolveProviderConfig(config, providerName) {
|
|
26355
|
-
const
|
|
26625
|
+
const normalized = normalizeProviderAlias(providerName);
|
|
26626
|
+
const provider = config.providers[normalized];
|
|
26356
26627
|
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
26357
26628
|
return provider;
|
|
26358
26629
|
}
|
|
26359
26630
|
function resolveProvider(providerConfig, options2 = {}) {
|
|
26360
26631
|
const model = options2.model ?? providerConfig.model;
|
|
26632
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
26361
26633
|
if (providerConfig.type === "claudecode") {
|
|
26362
26634
|
throw new Error("claudecode is an external agent runtime. Use resolveExecutionBackend().");
|
|
26363
26635
|
}
|
|
@@ -26404,12 +26676,28 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26404
26676
|
};
|
|
26405
26677
|
}
|
|
26406
26678
|
if (providerConfig.type === "anthropic") {
|
|
26407
|
-
const apiKey2 =
|
|
26679
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
26408
26680
|
if (!apiKey2) {
|
|
26409
26681
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26410
26682
|
}
|
|
26411
26683
|
return {
|
|
26412
26684
|
provider: new AnthropicProvider({
|
|
26685
|
+
apiKey: apiKey2,
|
|
26686
|
+
baseURL: providerConfig.baseURL,
|
|
26687
|
+
defaultModel: model,
|
|
26688
|
+
onRetry: options2.onRetry
|
|
26689
|
+
}),
|
|
26690
|
+
model
|
|
26691
|
+
};
|
|
26692
|
+
}
|
|
26693
|
+
if (providerConfig.type === "ollama") {
|
|
26694
|
+
const apiKey2 = resolveConfiguredApiKey(providerConfig).value;
|
|
26695
|
+
if (!apiKey2) {
|
|
26696
|
+
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26697
|
+
}
|
|
26698
|
+
return {
|
|
26699
|
+
provider: new OllamaProvider({
|
|
26700
|
+
baseURL: providerConfig.baseURL,
|
|
26413
26701
|
apiKey: apiKey2,
|
|
26414
26702
|
defaultModel: model,
|
|
26415
26703
|
onRetry: options2.onRetry
|
|
@@ -26417,7 +26705,7 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26417
26705
|
model
|
|
26418
26706
|
};
|
|
26419
26707
|
}
|
|
26420
|
-
const apiKey =
|
|
26708
|
+
const apiKey = resolveConfiguredApiKey(providerConfig).value;
|
|
26421
26709
|
if (!apiKey) {
|
|
26422
26710
|
throw new Error(`Missing API key for provider. Set ${providerConfig.apiKeyEnv ?? "apiKey"} or configure apiKey.`);
|
|
26423
26711
|
}
|
|
@@ -26436,6 +26724,7 @@ function resolveProvider(providerConfig, options2 = {}) {
|
|
|
26436
26724
|
}
|
|
26437
26725
|
function resolveExecutionBackend(providerConfig, options2 = {}) {
|
|
26438
26726
|
const model = options2.model ?? providerConfig.model;
|
|
26727
|
+
if (!model) throw new Error("Provider has no selected model. Choose a model or add a model profile.");
|
|
26439
26728
|
if (providerConfig.type === "claudecode") {
|
|
26440
26729
|
const runtimeConfig = options2.config || providerConfig.permissionProfile ? resolveClaudeCodeRuntimeConfig(providerConfig, options2.config ?? defaultConfig, options2.agent) : providerConfig;
|
|
26441
26730
|
return {
|
|
@@ -26479,7 +26768,8 @@ var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision",
|
|
|
26479
26768
|
|
|
26480
26769
|
// src/transcript.ts
|
|
26481
26770
|
import { mkdir as mkdir6, appendFile, writeFile as writeFile5 } from "node:fs/promises";
|
|
26482
|
-
import
|
|
26771
|
+
import os8 from "node:os";
|
|
26772
|
+
import path14 from "node:path";
|
|
26483
26773
|
var TranscriptWriter = class {
|
|
26484
26774
|
filePath;
|
|
26485
26775
|
#enabled;
|
|
@@ -26487,8 +26777,8 @@ var TranscriptWriter = class {
|
|
|
26487
26777
|
#queue = Promise.resolve();
|
|
26488
26778
|
constructor(options2) {
|
|
26489
26779
|
this.#enabled = options2.enabled !== false;
|
|
26490
|
-
const dir =
|
|
26491
|
-
this.filePath =
|
|
26780
|
+
const dir = path14.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
|
|
26781
|
+
this.filePath = path14.join(dir, "session.jsonl");
|
|
26492
26782
|
this.#ready = this.#enabled ? mkdir6(dir, { recursive: true }).then(() => writeFile5(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
26493
26783
|
}
|
|
26494
26784
|
write(event) {
|
|
@@ -26504,12 +26794,15 @@ var TranscriptWriter = class {
|
|
|
26504
26794
|
await this.#queue;
|
|
26505
26795
|
}
|
|
26506
26796
|
};
|
|
26797
|
+
function defaultDemianStorageDir() {
|
|
26798
|
+
return path14.join(os8.homedir(), ".demian");
|
|
26799
|
+
}
|
|
26507
26800
|
|
|
26508
26801
|
// src/external-runtime/snapshot-diff.ts
|
|
26509
26802
|
import { createHash as createHash2 } from "node:crypto";
|
|
26510
26803
|
import { createReadStream } from "node:fs";
|
|
26511
26804
|
import { readdir, readFile as readFile7, stat as stat3 } from "node:fs/promises";
|
|
26512
|
-
import
|
|
26805
|
+
import path15 from "node:path";
|
|
26513
26806
|
|
|
26514
26807
|
// src/workspace/diff.ts
|
|
26515
26808
|
var CONTEXT_LINES = 3;
|
|
@@ -26719,7 +27012,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
26719
27012
|
}
|
|
26720
27013
|
async function walk(root, relativeDir, entries, options2) {
|
|
26721
27014
|
if (entries.size >= options2.maxFiles) return;
|
|
26722
|
-
const absoluteDir =
|
|
27015
|
+
const absoluteDir = path15.join(root, relativeDir);
|
|
26723
27016
|
let items;
|
|
26724
27017
|
try {
|
|
26725
27018
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -26730,8 +27023,8 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
26730
27023
|
if (entries.size >= options2.maxFiles) return;
|
|
26731
27024
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
26732
27025
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
26733
|
-
const relativePath = normalizeRelativePath(
|
|
26734
|
-
const absolutePath =
|
|
27026
|
+
const relativePath = normalizeRelativePath(path15.join(relativeDir, item.name));
|
|
27027
|
+
const absolutePath = path15.join(root, relativePath);
|
|
26735
27028
|
if (item.isDirectory()) {
|
|
26736
27029
|
await walk(root, relativePath, entries, options2);
|
|
26737
27030
|
continue;
|
|
@@ -26767,7 +27060,7 @@ function sha256File(filePath) {
|
|
|
26767
27060
|
});
|
|
26768
27061
|
}
|
|
26769
27062
|
function normalizeRelativePath(value) {
|
|
26770
|
-
return value.split(
|
|
27063
|
+
return value.split(path15.sep).join("/");
|
|
26771
27064
|
}
|
|
26772
27065
|
|
|
26773
27066
|
// src/external-runtime/session-runner.ts
|
|
@@ -27202,7 +27495,7 @@ function safeJson(value) {
|
|
|
27202
27495
|
}
|
|
27203
27496
|
|
|
27204
27497
|
// src/hooks/dispatcher.ts
|
|
27205
|
-
import
|
|
27498
|
+
import path17 from "node:path";
|
|
27206
27499
|
|
|
27207
27500
|
// src/hooks/command.ts
|
|
27208
27501
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -27295,7 +27588,7 @@ var blockDangerousBashHook = {
|
|
|
27295
27588
|
};
|
|
27296
27589
|
|
|
27297
27590
|
// src/hooks/builtin/protect-env-files.ts
|
|
27298
|
-
import
|
|
27591
|
+
import path16 from "node:path";
|
|
27299
27592
|
var protectEnvFilesHook = {
|
|
27300
27593
|
name: "protect-env-files",
|
|
27301
27594
|
event: "PreToolUse",
|
|
@@ -27303,7 +27596,7 @@ var protectEnvFilesHook = {
|
|
|
27303
27596
|
run(ctx) {
|
|
27304
27597
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
27305
27598
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27306
|
-
const filePath = typeof input2.path === "string" ?
|
|
27599
|
+
const filePath = typeof input2.path === "string" ? path16.resolve(ctx.cwd, input2.path) : "";
|
|
27307
27600
|
if (filePath && isEnvFile(filePath)) {
|
|
27308
27601
|
return {
|
|
27309
27602
|
decision: "block",
|
|
@@ -27340,7 +27633,7 @@ var maskSecretsHook = {
|
|
|
27340
27633
|
};
|
|
27341
27634
|
|
|
27342
27635
|
// src/hooks/builtin/inject-env-info.ts
|
|
27343
|
-
import
|
|
27636
|
+
import os9 from "node:os";
|
|
27344
27637
|
var injectEnvInfoHook = {
|
|
27345
27638
|
name: "inject-env-info",
|
|
27346
27639
|
event: "SessionStart",
|
|
@@ -27349,7 +27642,7 @@ var injectEnvInfoHook = {
|
|
|
27349
27642
|
decision: "allow",
|
|
27350
27643
|
patch: {
|
|
27351
27644
|
systemNote: [
|
|
27352
|
-
`Environment: ${
|
|
27645
|
+
`Environment: ${os9.type()} ${os9.release()} (${os9.platform()}/${os9.arch()})`,
|
|
27353
27646
|
`cwd: ${ctx.cwd}`,
|
|
27354
27647
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
27355
27648
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -27418,14 +27711,14 @@ function matchesHook(match, ctx) {
|
|
|
27418
27711
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27419
27712
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
27420
27713
|
if (!filePath) return false;
|
|
27421
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
27714
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path17.resolve(ctx.cwd, filePath)))) return false;
|
|
27422
27715
|
}
|
|
27423
27716
|
return true;
|
|
27424
27717
|
}
|
|
27425
27718
|
|
|
27426
27719
|
// src/multimodal.ts
|
|
27427
27720
|
import { readFile as readFile8, stat as stat4 } from "node:fs/promises";
|
|
27428
|
-
import
|
|
27721
|
+
import path18 from "node:path";
|
|
27429
27722
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27430
27723
|
async function buildUserContent(prompt, images = [], options2) {
|
|
27431
27724
|
if (images.length === 0) return prompt;
|
|
@@ -27453,7 +27746,7 @@ async function imageToUrl(input2, options2) {
|
|
|
27453
27746
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
27454
27747
|
}
|
|
27455
27748
|
function mimeFromPath(filePath) {
|
|
27456
|
-
switch (
|
|
27749
|
+
switch (path18.extname(filePath).toLowerCase()) {
|
|
27457
27750
|
case ".jpg":
|
|
27458
27751
|
case ".jpeg":
|
|
27459
27752
|
return "image/jpeg";
|
|
@@ -27464,15 +27757,15 @@ function mimeFromPath(filePath) {
|
|
|
27464
27757
|
case ".webp":
|
|
27465
27758
|
return "image/webp";
|
|
27466
27759
|
default:
|
|
27467
|
-
throw new Error(`Unsupported image extension: ${
|
|
27760
|
+
throw new Error(`Unsupported image extension: ${path18.extname(filePath) || "(none)"}`);
|
|
27468
27761
|
}
|
|
27469
27762
|
}
|
|
27470
27763
|
|
|
27471
27764
|
// src/permissions/persistent-grants.ts
|
|
27472
27765
|
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
27473
27766
|
import fs8 from "node:fs";
|
|
27474
|
-
import
|
|
27475
|
-
import
|
|
27767
|
+
import os10 from "node:os";
|
|
27768
|
+
import path19 from "node:path";
|
|
27476
27769
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
27477
27770
|
var PersistentGrantStore = class {
|
|
27478
27771
|
filePath;
|
|
@@ -27510,15 +27803,15 @@ var PersistentGrantStore = class {
|
|
|
27510
27803
|
};
|
|
27511
27804
|
}
|
|
27512
27805
|
async #write(file) {
|
|
27513
|
-
await mkdir7(
|
|
27806
|
+
await mkdir7(path19.dirname(this.filePath), { recursive: true });
|
|
27514
27807
|
await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
27515
27808
|
`, { mode: 384 });
|
|
27516
27809
|
}
|
|
27517
27810
|
};
|
|
27518
27811
|
function resolveGrantPath(cwd, config) {
|
|
27519
|
-
if (config.path) return
|
|
27520
|
-
if ((config.scope ?? "project") === "user") return
|
|
27521
|
-
return
|
|
27812
|
+
if (config.path) return path19.resolve(cwd, config.path);
|
|
27813
|
+
if ((config.scope ?? "project") === "user") return path19.join(os10.homedir(), ".demian", "grants.json");
|
|
27814
|
+
return path19.join(cwd, ".demian", "grants.json");
|
|
27522
27815
|
}
|
|
27523
27816
|
function isGrantRecord(value) {
|
|
27524
27817
|
if (!value || typeof value !== "object") return false;
|
|
@@ -27952,16 +28245,16 @@ function fail(content, metadata) {
|
|
|
27952
28245
|
|
|
27953
28246
|
// src/tools/output.ts
|
|
27954
28247
|
import { mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
|
|
27955
|
-
import
|
|
28248
|
+
import path20 from "node:path";
|
|
27956
28249
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
27957
28250
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
27958
28251
|
async function capToolOutput(result, options2) {
|
|
27959
28252
|
const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
|
|
27960
28253
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
27961
28254
|
if (bytes <= capBytes) return result;
|
|
27962
|
-
const dir =
|
|
28255
|
+
const dir = path20.join(options2.cwd, ".demian", "tmp");
|
|
27963
28256
|
await mkdir8(dir, { recursive: true });
|
|
27964
|
-
const outputPath =
|
|
28257
|
+
const outputPath = path20.join(dir, `output-${safeCallId(options2.callId)}.txt`);
|
|
27965
28258
|
await writeFile7(outputPath, result.content, "utf8");
|
|
27966
28259
|
return {
|
|
27967
28260
|
...result,
|
|
@@ -27969,7 +28262,7 @@ async function capToolOutput(result, options2) {
|
|
|
27969
28262
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
27970
28263
|
`
|
|
27971
28264
|
|
|
27972
|
-
[Full output saved to ${
|
|
28265
|
+
[Full output saved to ${path20.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
27973
28266
|
|
|
27974
28267
|
`,
|
|
27975
28268
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -27995,7 +28288,7 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
27995
28288
|
|
|
27996
28289
|
// src/tools/read-file.ts
|
|
27997
28290
|
import { open as open2, stat as stat5 } from "node:fs/promises";
|
|
27998
|
-
import
|
|
28291
|
+
import path21 from "node:path";
|
|
27999
28292
|
|
|
28000
28293
|
// src/tools/validation.ts
|
|
28001
28294
|
function assertObject(input2, toolName) {
|
|
@@ -28079,7 +28372,7 @@ var readFileTool = {
|
|
|
28079
28372
|
const truncated = start + sliced.length < lines.length;
|
|
28080
28373
|
return ok(content + (truncated ? `
|
|
28081
28374
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
28082
|
-
path:
|
|
28375
|
+
path: path21.relative(ctx.cwd, filePath),
|
|
28083
28376
|
lines: sliced.length,
|
|
28084
28377
|
totalLines: lines.length,
|
|
28085
28378
|
truncated
|
|
@@ -28118,7 +28411,7 @@ function looksBinary(buffer) {
|
|
|
28118
28411
|
|
|
28119
28412
|
// src/tools/write-file.ts
|
|
28120
28413
|
import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
28121
|
-
import
|
|
28414
|
+
import path22 from "node:path";
|
|
28122
28415
|
var writeFileTool = {
|
|
28123
28416
|
name: "write_file",
|
|
28124
28417
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -28136,7 +28429,7 @@ var writeFileTool = {
|
|
|
28136
28429
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28137
28430
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
28138
28431
|
const before = await readOptionalTextFile(filePath);
|
|
28139
|
-
await mkdir9(
|
|
28432
|
+
await mkdir9(path22.dirname(filePath), { recursive: true });
|
|
28140
28433
|
await writeFile8(filePath, content, "utf8");
|
|
28141
28434
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28142
28435
|
const diff = createTextDiff(before, content, relative);
|
|
@@ -28212,7 +28505,7 @@ function countMatches(text, needle) {
|
|
|
28212
28505
|
|
|
28213
28506
|
// src/tools/bash.ts
|
|
28214
28507
|
import { spawn as spawn5 } from "node:child_process";
|
|
28215
|
-
import
|
|
28508
|
+
import path24 from "node:path";
|
|
28216
28509
|
|
|
28217
28510
|
// src/sandbox/env-only.ts
|
|
28218
28511
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -28272,7 +28565,7 @@ function bwrapPath() {
|
|
|
28272
28565
|
|
|
28273
28566
|
// src/sandbox/macos.ts
|
|
28274
28567
|
import { existsSync as existsSync3 } from "node:fs";
|
|
28275
|
-
import
|
|
28568
|
+
import path23 from "node:path";
|
|
28276
28569
|
function canUseMacOSSandbox() {
|
|
28277
28570
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
28278
28571
|
}
|
|
@@ -28298,7 +28591,7 @@ function macosProfile(cwd, config) {
|
|
|
28298
28591
|
}
|
|
28299
28592
|
if (mode === "workspace-write") {
|
|
28300
28593
|
lines.push("(deny file-write*)");
|
|
28301
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
28594
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path23.resolve(cwd))}"))`);
|
|
28302
28595
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
28303
28596
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
28304
28597
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -28360,7 +28653,7 @@ ${result.stderr}` : ""
|
|
|
28360
28653
|
].filter(Boolean).join("\n");
|
|
28361
28654
|
return ok(content, {
|
|
28362
28655
|
command,
|
|
28363
|
-
workdir:
|
|
28656
|
+
workdir: path24.relative(ctx.cwd, workdir) || ".",
|
|
28364
28657
|
exitCode: result.exitCode,
|
|
28365
28658
|
signal: result.signal,
|
|
28366
28659
|
timedOut: result.timedOut,
|
|
@@ -28413,7 +28706,7 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
28413
28706
|
// src/tools/grep.ts
|
|
28414
28707
|
import { spawn as spawn6 } from "node:child_process";
|
|
28415
28708
|
import { readdir as readdir2, readFile as readFile12, stat as stat6 } from "node:fs/promises";
|
|
28416
|
-
import
|
|
28709
|
+
import path25 from "node:path";
|
|
28417
28710
|
var MAX_MATCHES = 200;
|
|
28418
28711
|
var grepTool = {
|
|
28419
28712
|
name: "grep",
|
|
@@ -28488,7 +28781,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
28488
28781
|
}
|
|
28489
28782
|
function rewriteRgPath(cwd, line) {
|
|
28490
28783
|
const [file, rest] = splitFirst(line, ":");
|
|
28491
|
-
if (!
|
|
28784
|
+
if (!path25.isAbsolute(file)) return line;
|
|
28492
28785
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
28493
28786
|
}
|
|
28494
28787
|
function splitFirst(value, delimiter) {
|
|
@@ -28505,7 +28798,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28505
28798
|
if (isIgnoredPath(relative)) return;
|
|
28506
28799
|
const info = await stat6(item);
|
|
28507
28800
|
if (info.isDirectory()) {
|
|
28508
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
28801
|
+
for (const entry of await readdir2(item)) await walk2(path25.join(item, entry));
|
|
28509
28802
|
return;
|
|
28510
28803
|
}
|
|
28511
28804
|
if (glob && !matchGlob(glob, relative)) return;
|
|
@@ -28524,7 +28817,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28524
28817
|
|
|
28525
28818
|
// src/tools/glob.ts
|
|
28526
28819
|
import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
|
|
28527
|
-
import
|
|
28820
|
+
import path26 from "node:path";
|
|
28528
28821
|
var MAX_PATHS = 1e3;
|
|
28529
28822
|
var globTool = {
|
|
28530
28823
|
name: "glob",
|
|
@@ -28556,7 +28849,7 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
28556
28849
|
async function walk2(dir) {
|
|
28557
28850
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
28558
28851
|
for (const entry of entries) {
|
|
28559
|
-
const absolute =
|
|
28852
|
+
const absolute = path26.join(dir, entry.name);
|
|
28560
28853
|
const relative = relativeToCwd(cwd, absolute);
|
|
28561
28854
|
if (isIgnoredPath(relative)) continue;
|
|
28562
28855
|
const info = await stat7(absolute);
|
|
@@ -28767,7 +29060,7 @@ async function runExaSearch(config, options2) {
|
|
|
28767
29060
|
return searchResult("exa", options2.query, results, json.value);
|
|
28768
29061
|
}
|
|
28769
29062
|
function resolveApiKey(provider, config) {
|
|
28770
|
-
const value = config.apiKey
|
|
29063
|
+
const value = config.apiKey || (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
|
|
28771
29064
|
if (value) return { ok: true, value };
|
|
28772
29065
|
return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
|
|
28773
29066
|
}
|
|
@@ -29559,13 +29852,13 @@ async function runExecutionSession(options2) {
|
|
|
29559
29852
|
|
|
29560
29853
|
// src/goals/lock.ts
|
|
29561
29854
|
import { mkdir as mkdir11, open as open3, rm as rm2 } from "node:fs/promises";
|
|
29562
|
-
import
|
|
29855
|
+
import path28 from "node:path";
|
|
29563
29856
|
|
|
29564
29857
|
// src/goals/storage.ts
|
|
29565
29858
|
import { createHash as createHash3 } from "node:crypto";
|
|
29566
29859
|
import { mkdir as mkdir10, readFile as readFile13, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
|
|
29567
29860
|
import fs9 from "node:fs";
|
|
29568
|
-
import
|
|
29861
|
+
import path27 from "node:path";
|
|
29569
29862
|
var GoalStore = class {
|
|
29570
29863
|
cwd;
|
|
29571
29864
|
dir;
|
|
@@ -29576,8 +29869,8 @@ var GoalStore = class {
|
|
|
29576
29869
|
this.cwd = cwd;
|
|
29577
29870
|
this.scope = normalizeGoalStoreScope(options2.scope);
|
|
29578
29871
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
29579
|
-
this.activePath =
|
|
29580
|
-
this.archiveDir =
|
|
29872
|
+
this.activePath = path27.join(this.dir, "active.json");
|
|
29873
|
+
this.archiveDir = path27.join(this.dir, "archive");
|
|
29581
29874
|
}
|
|
29582
29875
|
async loadActive() {
|
|
29583
29876
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
@@ -29591,7 +29884,7 @@ var GoalStore = class {
|
|
|
29591
29884
|
}
|
|
29592
29885
|
async saveActive(state) {
|
|
29593
29886
|
await mkdir10(this.dir, { recursive: true });
|
|
29594
|
-
const tmp =
|
|
29887
|
+
const tmp = path27.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
29595
29888
|
await writeFile10(tmp, `${JSON.stringify(state, null, 2)}
|
|
29596
29889
|
`, "utf8");
|
|
29597
29890
|
await rename4(tmp, this.activePath);
|
|
@@ -29605,21 +29898,21 @@ var GoalStore = class {
|
|
|
29605
29898
|
status,
|
|
29606
29899
|
updatedAt: Date.now()
|
|
29607
29900
|
};
|
|
29608
|
-
await writeFile10(
|
|
29901
|
+
await writeFile10(path27.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
29609
29902
|
`, "utf8");
|
|
29610
29903
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29611
29904
|
return archived;
|
|
29612
29905
|
}
|
|
29613
29906
|
async backupCorrupted(text) {
|
|
29614
29907
|
await mkdir10(this.dir, { recursive: true });
|
|
29615
|
-
const backup =
|
|
29908
|
+
const backup = path27.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
29616
29909
|
await writeFile10(backup, text, "utf8");
|
|
29617
29910
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29618
29911
|
}
|
|
29619
29912
|
};
|
|
29620
29913
|
function goalStoreDir(cwd, scope) {
|
|
29621
29914
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
29622
|
-
return safeScope ?
|
|
29915
|
+
return safeScope ? path27.join(cwd, ".demian", "goals", "sessions", safeScope) : path27.join(cwd, ".demian", "goals");
|
|
29623
29916
|
}
|
|
29624
29917
|
function normalizeGoalStoreScope(scope) {
|
|
29625
29918
|
const trimmed = scope?.trim();
|
|
@@ -29633,10 +29926,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
29633
29926
|
var GoalLock = class {
|
|
29634
29927
|
path;
|
|
29635
29928
|
constructor(cwd, scope) {
|
|
29636
|
-
this.path =
|
|
29929
|
+
this.path = path28.join(goalStoreDir(cwd, scope), "active.lock");
|
|
29637
29930
|
}
|
|
29638
29931
|
async acquire() {
|
|
29639
|
-
await mkdir11(
|
|
29932
|
+
await mkdir11(path28.dirname(this.path), { recursive: true });
|
|
29640
29933
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
29641
29934
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
29642
29935
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -30224,8 +30517,8 @@ function sha256(value) {
|
|
|
30224
30517
|
|
|
30225
30518
|
// src/root-session.ts
|
|
30226
30519
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30227
|
-
import
|
|
30228
|
-
import
|
|
30520
|
+
import os11 from "node:os";
|
|
30521
|
+
import path29 from "node:path";
|
|
30229
30522
|
|
|
30230
30523
|
// src/tools/cowork.ts
|
|
30231
30524
|
function createCoworkTool(options2) {
|
|
@@ -31048,8 +31341,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31048
31341
|
}
|
|
31049
31342
|
}
|
|
31050
31343
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
31051
|
-
const root = await mkdtemp(
|
|
31052
|
-
const cwd =
|
|
31344
|
+
const root = await mkdtemp(path29.join(os11.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
31345
|
+
const cwd = path29.join(root, "workspace");
|
|
31053
31346
|
await cp(this.#options.cwd, cwd, {
|
|
31054
31347
|
recursive: true,
|
|
31055
31348
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -31197,7 +31490,7 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31197
31490
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
31198
31491
|
return this.withPermissionPreset({
|
|
31199
31492
|
...agent,
|
|
31200
|
-
provider: agent.provider?.profile === "claudecode-
|
|
31493
|
+
provider: agent.provider?.profile === "claudecode" && agent.name === "claudecode-explorer" ? { ...agent.provider, permissionProfile: "explore" } : agent.provider,
|
|
31201
31494
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
31202
31495
|
permissions: [
|
|
31203
31496
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
@@ -31244,13 +31537,14 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31244
31537
|
return lines.join("\n");
|
|
31245
31538
|
}
|
|
31246
31539
|
resolveInvocationBackend(profileName, modelOverride, agent) {
|
|
31247
|
-
const
|
|
31540
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride });
|
|
31541
|
+
const providerConfig = runtime.providerConfig;
|
|
31248
31542
|
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, modelOverride);
|
|
31249
31543
|
if (this.#options.resolveProvider) {
|
|
31250
|
-
const resolved = this.#options.resolveProvider(profileName, providerConfig,
|
|
31544
|
+
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
31251
31545
|
return { kind: "provider", ...resolved };
|
|
31252
31546
|
}
|
|
31253
|
-
return resolveExecutionBackend(providerConfig, { model:
|
|
31547
|
+
return resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config, agent });
|
|
31254
31548
|
}
|
|
31255
31549
|
loadAgentSession(agent, providerProfile, model, sessionScope, cwd = this.#options.cwd) {
|
|
31256
31550
|
const key = `${this.id}:${sessionScope ?? "delegate"}:${agent.name}:${providerProfile}:${model}:${cwd}`;
|
|
@@ -31352,17 +31646,17 @@ function findDependencyCycle(group) {
|
|
|
31352
31646
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
31353
31647
|
const visiting = /* @__PURE__ */ new Set();
|
|
31354
31648
|
const visited = /* @__PURE__ */ new Set();
|
|
31355
|
-
const
|
|
31649
|
+
const path36 = [];
|
|
31356
31650
|
const visit = (id) => {
|
|
31357
|
-
if (visiting.has(id)) return [...
|
|
31651
|
+
if (visiting.has(id)) return [...path36.slice(path36.indexOf(id)), id];
|
|
31358
31652
|
if (visited.has(id)) return void 0;
|
|
31359
31653
|
visiting.add(id);
|
|
31360
|
-
|
|
31654
|
+
path36.push(id);
|
|
31361
31655
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
31362
31656
|
const cycle = visit(dep);
|
|
31363
31657
|
if (cycle) return cycle;
|
|
31364
31658
|
}
|
|
31365
|
-
|
|
31659
|
+
path36.pop();
|
|
31366
31660
|
visiting.delete(id);
|
|
31367
31661
|
visited.add(id);
|
|
31368
31662
|
return void 0;
|
|
@@ -31403,7 +31697,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
31403
31697
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
31404
31698
|
}
|
|
31405
31699
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
31406
|
-
const relative =
|
|
31700
|
+
const relative = path29.relative(root, source).split(path29.sep).join("/");
|
|
31407
31701
|
if (!relative) return true;
|
|
31408
31702
|
const parts = relative.split("/");
|
|
31409
31703
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -31617,11 +31911,11 @@ function finitePositiveInteger(value) {
|
|
|
31617
31911
|
// src/ui/preferences.ts
|
|
31618
31912
|
import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
31619
31913
|
import fs10 from "node:fs";
|
|
31620
|
-
import
|
|
31914
|
+
import path30 from "node:path";
|
|
31621
31915
|
var UiPreferenceStore = class {
|
|
31622
31916
|
filePath;
|
|
31623
31917
|
constructor(cwd, filePath) {
|
|
31624
|
-
this.filePath = filePath ?
|
|
31918
|
+
this.filePath = filePath ? path30.resolve(cwd, filePath) : path30.join(cwd, ".demian", "preferences.json");
|
|
31625
31919
|
}
|
|
31626
31920
|
async load() {
|
|
31627
31921
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -31639,7 +31933,7 @@ var UiPreferenceStore = class {
|
|
|
31639
31933
|
model: selection.model,
|
|
31640
31934
|
updatedAt: Date.now()
|
|
31641
31935
|
};
|
|
31642
|
-
await mkdir12(
|
|
31936
|
+
await mkdir12(path30.dirname(this.filePath), { recursive: true });
|
|
31643
31937
|
await writeFile11(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
31644
31938
|
`, { mode: 384 });
|
|
31645
31939
|
}
|
|
@@ -31659,37 +31953,533 @@ function preferenceKey(selection) {
|
|
|
31659
31953
|
return `${selection.providerName}\0${selection.model}`;
|
|
31660
31954
|
}
|
|
31661
31955
|
|
|
31662
|
-
// src/
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
31671
|
-
|
|
31956
|
+
// src/models/catalog.ts
|
|
31957
|
+
import crypto6 from "node:crypto";
|
|
31958
|
+
import { mkdir as mkdir13, readFile as readFile15, rename as rename5, writeFile as writeFile12 } from "node:fs/promises";
|
|
31959
|
+
import os12 from "node:os";
|
|
31960
|
+
import path31 from "node:path";
|
|
31961
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
31962
|
+
var PING_TIMEOUT_MS = 1500;
|
|
31963
|
+
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
31964
|
+
var dynamicImport3 = new Function("specifier", "return import(specifier)");
|
|
31965
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
31966
|
+
var pingCache = /* @__PURE__ */ new Map();
|
|
31967
|
+
var PING_CACHE_TTL_MS = 30 * 1e3;
|
|
31968
|
+
var packageVersionPromise;
|
|
31969
|
+
async function listProviderModelCatalog(providerName, providerConfig, options2 = {}) {
|
|
31970
|
+
const key = `${catalogCacheKey(providerName, providerConfig)}:${options2.refresh ? "refresh" : "default"}`;
|
|
31971
|
+
const existing = inflight.get(key);
|
|
31972
|
+
if (existing) return existing;
|
|
31973
|
+
const promise = doListProviderModelCatalog(providerName, providerConfig, options2).finally(() => inflight.delete(key));
|
|
31974
|
+
inflight.set(key, promise);
|
|
31975
|
+
return promise;
|
|
31976
|
+
}
|
|
31977
|
+
async function doListProviderModelCatalog(providerName, providerConfig, options2) {
|
|
31978
|
+
const staticModels = staticModelEntries(providerName, providerConfig);
|
|
31979
|
+
const catalog = providerConfig.catalog;
|
|
31980
|
+
if (!catalog || catalog.type === "static") {
|
|
31981
|
+
return { status: staticModels.length ? "ready" : "unavailable", providerName, models: staticModels, source: "config" };
|
|
31982
|
+
}
|
|
31983
|
+
const missingAuthEnv = missingCatalogAuth(providerConfig);
|
|
31984
|
+
if (missingAuthEnv) {
|
|
31985
|
+
return {
|
|
31986
|
+
status: "missing-auth",
|
|
31987
|
+
providerName,
|
|
31988
|
+
models: staticModels,
|
|
31989
|
+
source: staticModels.length ? "config" : "fallback",
|
|
31990
|
+
message: `missing-auth:${missingAuthEnv}`
|
|
31991
|
+
};
|
|
31992
|
+
}
|
|
31993
|
+
const cacheKey = catalogCacheKey(providerName, providerConfig);
|
|
31994
|
+
const ttlMs = catalog.refreshTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
31995
|
+
const cached = await readCatalogCache(cacheKey, ttlMs, options2.refresh !== true);
|
|
31996
|
+
if (cached && !options2.refresh) return { ...cached, models: mergeCatalogWithStatic(staticModels, cached.models) };
|
|
31997
|
+
if (catalog.endpoint) {
|
|
31998
|
+
const reachable = await pingEndpoint(catalog.endpoint, options2.fetch);
|
|
31999
|
+
if (!reachable) {
|
|
32000
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
32001
|
+
if (stale) return { ...stale, models: mergeCatalogWithStatic(staticModels, stale.models), message: `endpoint ${catalog.endpoint} unreachable; using cached models` };
|
|
32002
|
+
if (staticModels.length) return { status: "ready", providerName, models: staticModels, source: "config", message: `endpoint ${catalog.endpoint} unreachable; using configured profiles` };
|
|
32003
|
+
return { status: "unavailable", providerName, models: [], source: "fallback", message: `endpoint ${catalog.endpoint} unreachable` };
|
|
32004
|
+
}
|
|
32005
|
+
}
|
|
32006
|
+
try {
|
|
32007
|
+
const remote = await fetchCatalog(providerName, providerConfig, options2);
|
|
32008
|
+
const merged = mergeCatalogWithStatic(staticModels, remote.models);
|
|
32009
|
+
const result = { ...remote, models: merged };
|
|
32010
|
+
if (result.status === "ready") await writeCatalogCache(cacheKey, result);
|
|
32011
|
+
return result;
|
|
32012
|
+
} catch (error) {
|
|
32013
|
+
const authMissing = isMissingAuthError(error);
|
|
32014
|
+
const stale = await readCatalogCache(cacheKey, Number.POSITIVE_INFINITY, true);
|
|
32015
|
+
if (stale) return { ...stale, status: authMissing ? "missing-auth" : stale.status, models: mergeCatalogWithStatic(staticModels, stale.models), message: errorMessage2(error) };
|
|
32016
|
+
if (staticModels.length) return { status: authMissing ? "missing-auth" : "ready", providerName, models: staticModels, source: "config", message: errorMessage2(error) };
|
|
32017
|
+
return { status: authMissing ? "missing-auth" : "unavailable", providerName, models: [], source: "fallback", message: errorMessage2(error) };
|
|
32018
|
+
}
|
|
32019
|
+
}
|
|
32020
|
+
async function pingEndpoint(endpoint, fetcher) {
|
|
32021
|
+
const now2 = Date.now();
|
|
32022
|
+
const cached = pingCache.get(endpoint);
|
|
32023
|
+
if (cached && cached.expiresAt > now2) return cached.ok;
|
|
32024
|
+
const ok2 = await tryPing(endpoint, fetcher);
|
|
32025
|
+
pingCache.set(endpoint, { ok: ok2, expiresAt: now2 + PING_CACHE_TTL_MS });
|
|
32026
|
+
return ok2;
|
|
32027
|
+
}
|
|
32028
|
+
async function tryPing(endpoint, fetcher) {
|
|
32029
|
+
const fn = fetcher ?? fetch;
|
|
32030
|
+
try {
|
|
32031
|
+
const response = await fn(endpoint, { method: "HEAD", signal: AbortSignal.timeout(PING_TIMEOUT_MS) });
|
|
32032
|
+
if (response.status < 500) return true;
|
|
32033
|
+
} catch {
|
|
32034
|
+
}
|
|
32035
|
+
try {
|
|
32036
|
+
const response = await fn(endpoint, { method: "GET", signal: AbortSignal.timeout(PING_TIMEOUT_MS), headers: { range: "bytes=0-0" } });
|
|
32037
|
+
return response.status < 500;
|
|
32038
|
+
} catch {
|
|
32039
|
+
return false;
|
|
32040
|
+
}
|
|
32041
|
+
}
|
|
32042
|
+
function invalidateCatalogPingCache(endpoint) {
|
|
32043
|
+
if (endpoint) pingCache.delete(endpoint);
|
|
32044
|
+
else pingCache.clear();
|
|
32045
|
+
}
|
|
32046
|
+
async function fetchCatalog(providerName, providerConfig, options2) {
|
|
32047
|
+
if (providerConfig.type === "codex" || providerConfig.catalog?.type === "codex-oauth-models") return fetchCodexCatalog(providerName, providerConfig, options2);
|
|
32048
|
+
if (providerConfig.type === "claudecode" || providerConfig.catalog?.type === "claudecode-supported-models") return fetchClaudeCodeCatalog(providerName, providerConfig, options2);
|
|
32049
|
+
if (providerConfig.catalog?.type === "anthropic-models") return fetchAnthropicCatalog(providerName, providerConfig, options2);
|
|
32050
|
+
if (providerConfig.catalog?.type === "ollama-tags") return fetchOllamaTagsCatalog(providerName, providerConfig, options2);
|
|
32051
|
+
return fetchOpenAIStyleCatalog(providerName, providerConfig, options2);
|
|
32052
|
+
}
|
|
32053
|
+
async function fetchOpenAIStyleCatalog(providerName, providerConfig, options2) {
|
|
32054
|
+
if (providerConfig.type !== "openai-compatible") throw new Error(`Provider ${providerName} is not OpenAI-compatible.`);
|
|
32055
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32056
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32057
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/models`;
|
|
32058
|
+
const response = await fetchJson2(endpoint, {
|
|
32059
|
+
fetcher: options2.fetch,
|
|
32060
|
+
timeoutMs: options2.timeoutMs,
|
|
32061
|
+
headers: {
|
|
32062
|
+
...authHeadersForOpenAICompatible(providerConfig, apiKey.value),
|
|
32063
|
+
...providerConfig.headers ?? {}
|
|
32064
|
+
}
|
|
32065
|
+
});
|
|
32066
|
+
return {
|
|
32067
|
+
status: "ready",
|
|
32068
|
+
providerName,
|
|
32069
|
+
models: normalizeOpenAIModels(response).map((entry) => ({ ...entry, source: "api" })),
|
|
32070
|
+
source: "api"
|
|
32071
|
+
};
|
|
32072
|
+
}
|
|
32073
|
+
async function fetchAnthropicCatalog(providerName, providerConfig, options2) {
|
|
32074
|
+
if (providerConfig.type !== "anthropic") throw new Error(`Provider ${providerName} is not Anthropic.`);
|
|
32075
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32076
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32077
|
+
const base = providerConfig.baseURL ?? "https://api.anthropic.com/v1";
|
|
32078
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${base.replace(/\/+$/, "")}/models`;
|
|
32079
|
+
const models = [];
|
|
32080
|
+
let afterId;
|
|
32081
|
+
for (; ; ) {
|
|
32082
|
+
const url = new URL(endpoint);
|
|
32083
|
+
if (afterId) url.searchParams.set("after_id", afterId);
|
|
32084
|
+
url.searchParams.set("limit", "100");
|
|
32085
|
+
const response = await fetchJson2(url.toString(), {
|
|
32086
|
+
fetcher: options2.fetch,
|
|
32087
|
+
timeoutMs: options2.timeoutMs,
|
|
32088
|
+
headers: {
|
|
32089
|
+
"x-api-key": apiKey.value,
|
|
32090
|
+
"anthropic-version": "2023-06-01"
|
|
32091
|
+
}
|
|
32092
|
+
});
|
|
32093
|
+
const page = normalizeAnthropicModels(response);
|
|
32094
|
+
models.push(...page);
|
|
32095
|
+
if (!response.has_more || !response.last_id) break;
|
|
32096
|
+
afterId = response.last_id;
|
|
32097
|
+
}
|
|
32098
|
+
return { status: "ready", providerName, models, source: "api" };
|
|
32099
|
+
}
|
|
32100
|
+
async function fetchOllamaTagsCatalog(providerName, providerConfig, options2) {
|
|
32101
|
+
if (providerConfig.type !== "ollama") throw new Error(`Provider ${providerName} is not Ollama native.`);
|
|
32102
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32103
|
+
if (!apiKey.value) throw missingAuth(providerConfig.apiKeyEnv ?? "apiKey");
|
|
32104
|
+
const endpoint = providerConfig.catalog?.endpoint ?? `${providerConfig.baseURL.replace(/\/+$/, "")}/tags`;
|
|
32105
|
+
const response = await fetchJson2(endpoint, {
|
|
32106
|
+
fetcher: options2.fetch,
|
|
32107
|
+
timeoutMs: options2.timeoutMs,
|
|
32108
|
+
headers: { authorization: `Bearer ${apiKey.value}` }
|
|
32109
|
+
});
|
|
32110
|
+
return {
|
|
32111
|
+
status: "ready",
|
|
32112
|
+
providerName,
|
|
32113
|
+
models: normalizeOllamaTags(response).map((entry) => ({ ...entry, source: "api" })),
|
|
32114
|
+
source: "api"
|
|
32115
|
+
};
|
|
32116
|
+
}
|
|
32117
|
+
async function fetchCodexCatalog(providerName, providerConfig, options2) {
|
|
32118
|
+
if (providerConfig.type !== "codex") throw new Error(`Provider ${providerName} is not Codex.`);
|
|
32119
|
+
const fetcher = options2.fetch ?? fetch;
|
|
32120
|
+
const baseURL = (providerConfig.baseURL ?? "https://chatgpt.com/backend-api/codex").replace(/\/+$/, "");
|
|
32121
|
+
const installationId = await loadOrCreateInstallationId(resolveCodexHome(providerConfig.codexHome));
|
|
32122
|
+
const auth = getSharedCodexAuthStore({
|
|
32123
|
+
codexHome: providerConfig.codexHome,
|
|
32124
|
+
authStore: providerConfig.authStore,
|
|
32125
|
+
allowApiKeyFallback: providerConfig.allowApiKeyFallback,
|
|
32126
|
+
proactiveRefreshMinutes: providerConfig.refresh?.proactiveRefreshMinutes,
|
|
32127
|
+
refreshCache: providerConfig.refresh?.cache,
|
|
32128
|
+
fetch: fetcher
|
|
32129
|
+
});
|
|
32130
|
+
const headers = await auth.requestHeaders(installationId);
|
|
32131
|
+
const url = new URL(`${baseURL}/models`);
|
|
32132
|
+
url.searchParams.set("client_version", await codexClientVersion(options2.clientVersion));
|
|
32133
|
+
const response = await fetchJson2(url.toString(), {
|
|
32134
|
+
fetcher,
|
|
32135
|
+
timeoutMs: options2.timeoutMs,
|
|
32136
|
+
headers
|
|
32137
|
+
});
|
|
32138
|
+
return {
|
|
32139
|
+
status: "ready",
|
|
32140
|
+
providerName,
|
|
32141
|
+
models: normalizeCodexModels(response).map((entry) => ({ ...entry, source: "oauth" })),
|
|
32142
|
+
source: "oauth"
|
|
32143
|
+
};
|
|
32144
|
+
}
|
|
32145
|
+
async function fetchClaudeCodeCatalog(providerName, providerConfig, options2) {
|
|
32146
|
+
if (providerConfig.type !== "claudecode") throw new Error(`Provider ${providerName} is not Claude Code.`);
|
|
32147
|
+
const fallback = staticModelEntries(providerName, providerConfig);
|
|
32148
|
+
try {
|
|
32149
|
+
const module = await dynamicImport3("@anthropic-ai/claude-agent-sdk");
|
|
32150
|
+
if (!module.query) throw new Error("Claude Agent SDK query export is unavailable.");
|
|
32151
|
+
const query = module.query({
|
|
32152
|
+
prompt: "",
|
|
32153
|
+
options: {
|
|
32154
|
+
model: defaultModelForProvider(providerName, providerConfig),
|
|
32155
|
+
maxTurns: 0,
|
|
32156
|
+
pathToClaudeCodeExecutable: resolveClaudeCodeCliPath(providerConfig.cliPath),
|
|
32157
|
+
env: sanitizedClaudeCodeEnv(providerConfig.env, providerConfig.sanitizeApiKeyEnv ?? true)
|
|
32158
|
+
}
|
|
32159
|
+
});
|
|
32160
|
+
try {
|
|
32161
|
+
if (typeof query.supportedModels !== "function") throw new Error("Claude Agent SDK supportedModels is unavailable.");
|
|
32162
|
+
const models = await withTimeout(query.supportedModels(), options2.timeoutMs ?? 5e3);
|
|
32163
|
+
return {
|
|
32164
|
+
status: "ready",
|
|
32165
|
+
providerName,
|
|
32166
|
+
models: models.map((model, index) => ({
|
|
32167
|
+
name: model.displayName || model.value,
|
|
32168
|
+
displayName: model.displayName || model.value,
|
|
32169
|
+
model: model.value,
|
|
32170
|
+
description: model.description,
|
|
32171
|
+
isDefault: index === 0,
|
|
32172
|
+
source: "oauth"
|
|
32173
|
+
})),
|
|
32174
|
+
source: "oauth"
|
|
32175
|
+
};
|
|
32176
|
+
} finally {
|
|
32177
|
+
query.close?.();
|
|
32178
|
+
}
|
|
32179
|
+
} catch (error) {
|
|
32180
|
+
if (fallback.length) return { status: "ready", providerName, models: fallback, source: "fallback", message: errorMessage2(error) };
|
|
32181
|
+
throw error;
|
|
32182
|
+
}
|
|
32183
|
+
}
|
|
32184
|
+
function staticModelEntries(providerName, providerConfig) {
|
|
32185
|
+
return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile, index) => ({
|
|
32186
|
+
name: profile.displayName ?? profile.name,
|
|
32187
|
+
displayName: profile.displayName ?? profile.name,
|
|
32188
|
+
model: profile.model,
|
|
32189
|
+
description: profile.description,
|
|
32190
|
+
isDefault: index === 0,
|
|
32191
|
+
source: "config"
|
|
31672
32192
|
}));
|
|
31673
32193
|
}
|
|
32194
|
+
function mergeCatalogWithStatic(staticModels, remoteModels) {
|
|
32195
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
32196
|
+
for (const model of remoteModels) byModel.set(model.model, model);
|
|
32197
|
+
for (const model of staticModels) byModel.set(model.model, { ...byModel.get(model.model), ...model, source: model.source });
|
|
32198
|
+
return [...byModel.values()];
|
|
32199
|
+
}
|
|
32200
|
+
function normalizeOpenAIModels(raw) {
|
|
32201
|
+
const items = Array.isArray(raw.data) ? raw.data : Array.isArray(raw.models) ? raw.models : [];
|
|
32202
|
+
return items.map((item) => {
|
|
32203
|
+
const object2 = item;
|
|
32204
|
+
const id = stringValue5(object2.id) ?? stringValue5(object2.name) ?? stringValue5(object2.model);
|
|
32205
|
+
if (!id) return void 0;
|
|
32206
|
+
return {
|
|
32207
|
+
name: id,
|
|
32208
|
+
displayName: stringValue5(object2.display_name) ?? stringValue5(object2.displayName) ?? id,
|
|
32209
|
+
model: id,
|
|
32210
|
+
description: stringValue5(object2.description)
|
|
32211
|
+
};
|
|
32212
|
+
}).filter(Boolean);
|
|
32213
|
+
}
|
|
32214
|
+
function normalizeAnthropicModels(raw) {
|
|
32215
|
+
return (raw.data ?? []).map((item) => {
|
|
32216
|
+
const object2 = item;
|
|
32217
|
+
const id = stringValue5(object2.id);
|
|
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
|
+
source: "api"
|
|
32225
|
+
};
|
|
32226
|
+
}).filter(Boolean);
|
|
32227
|
+
}
|
|
32228
|
+
function normalizeOllamaTags(raw) {
|
|
32229
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
32230
|
+
return items.map((item) => {
|
|
32231
|
+
const object2 = item;
|
|
32232
|
+
const id = stringValue5(object2.name) ?? stringValue5(object2.model);
|
|
32233
|
+
if (!id) return void 0;
|
|
32234
|
+
return { name: id, displayName: id, model: id };
|
|
32235
|
+
}).filter(Boolean);
|
|
32236
|
+
}
|
|
32237
|
+
function normalizeCodexModels(raw) {
|
|
32238
|
+
const items = Array.isArray(raw.models) ? raw.models : [];
|
|
32239
|
+
return items.map((item, index) => {
|
|
32240
|
+
const object2 = item;
|
|
32241
|
+
const id = stringValue5(object2.slug) ?? stringValue5(object2.id) ?? stringValue5(object2.model);
|
|
32242
|
+
if (!id) return void 0;
|
|
32243
|
+
return {
|
|
32244
|
+
name: stringValue5(object2.display_name) ?? id,
|
|
32245
|
+
displayName: stringValue5(object2.display_name) ?? id,
|
|
32246
|
+
model: id,
|
|
32247
|
+
description: stringValue5(object2.description),
|
|
32248
|
+
isDefault: index === 0
|
|
32249
|
+
};
|
|
32250
|
+
}).filter(Boolean);
|
|
32251
|
+
}
|
|
32252
|
+
function authHeadersForOpenAICompatible(providerConfig, apiKey) {
|
|
32253
|
+
const auth = inferOpenAICompatibleAuth(providerConfig.baseURL, providerConfig.auth);
|
|
32254
|
+
const type = auth.type ?? "bearer";
|
|
32255
|
+
const header = auth.header ?? (type === "api-key" ? "api-key" : "authorization");
|
|
32256
|
+
return { [header]: type === "bearer" ? `Bearer ${apiKey}` : apiKey };
|
|
32257
|
+
}
|
|
32258
|
+
async function fetchJson2(url, options2) {
|
|
32259
|
+
const response = await (options2.fetcher ?? fetch)(url, {
|
|
32260
|
+
method: "GET",
|
|
32261
|
+
headers: {
|
|
32262
|
+
accept: "application/json",
|
|
32263
|
+
...options2.headers ?? {}
|
|
32264
|
+
},
|
|
32265
|
+
signal: AbortSignal.timeout(options2.timeoutMs ?? 5e3)
|
|
32266
|
+
});
|
|
32267
|
+
const text = await response.text();
|
|
32268
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
|
|
32269
|
+
return text ? JSON.parse(text) : {};
|
|
32270
|
+
}
|
|
32271
|
+
function cachePath(cacheKey) {
|
|
32272
|
+
return path31.join(os12.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
32273
|
+
}
|
|
32274
|
+
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
32275
|
+
if (!allow) return void 0;
|
|
32276
|
+
try {
|
|
32277
|
+
const raw = JSON.parse(await readFile15(cachePath(cacheKey), "utf8"));
|
|
32278
|
+
if (!raw.result) return void 0;
|
|
32279
|
+
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
32280
|
+
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
32281
|
+
} catch {
|
|
32282
|
+
return void 0;
|
|
32283
|
+
}
|
|
32284
|
+
}
|
|
32285
|
+
async function writeCatalogCache(cacheKey, result) {
|
|
32286
|
+
const filePath = cachePath(cacheKey);
|
|
32287
|
+
await mkdir13(path31.dirname(filePath), { recursive: true, mode: 448 });
|
|
32288
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32289
|
+
await writeFile12(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
32290
|
+
await rename5(temp, filePath);
|
|
32291
|
+
}
|
|
32292
|
+
function safeCacheName(providerName) {
|
|
32293
|
+
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
32294
|
+
}
|
|
32295
|
+
function catalogCacheKey(providerName, providerConfig) {
|
|
32296
|
+
const identity = {
|
|
32297
|
+
providerName,
|
|
32298
|
+
type: providerConfig.type,
|
|
32299
|
+
catalogType: providerConfig.catalog?.type,
|
|
32300
|
+
endpoint: providerConfig.catalog?.endpoint,
|
|
32301
|
+
baseURL: "baseURL" in providerConfig ? providerConfig.baseURL : void 0,
|
|
32302
|
+
apiKeyEnv: "apiKeyEnv" in providerConfig ? providerConfig.apiKeyEnv : void 0,
|
|
32303
|
+
apiKeyEnvAliases: "apiKeyEnvAliases" in providerConfig ? providerConfig.apiKeyEnvAliases : void 0,
|
|
32304
|
+
auth: "auth" in providerConfig ? providerConfig.auth : void 0
|
|
32305
|
+
};
|
|
32306
|
+
const digest = crypto6.createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16);
|
|
32307
|
+
return `${providerName}-${digest}`;
|
|
32308
|
+
}
|
|
32309
|
+
function missingCatalogAuth(providerConfig) {
|
|
32310
|
+
if (providerConfig.type !== "openai-compatible" && providerConfig.type !== "anthropic" && providerConfig.type !== "ollama") return void 0;
|
|
32311
|
+
if (providerConfig.catalog?.requiresAuth === false) return void 0;
|
|
32312
|
+
const expectsAuth = providerConfig.catalog?.requiresAuth === true || !!providerConfig.apiKey || !!providerConfig.apiKeyEnv || !!providerConfig.apiKeyEnvAliases?.length;
|
|
32313
|
+
if (!expectsAuth) return void 0;
|
|
32314
|
+
const apiKey = resolveConfiguredApiKey(providerConfig);
|
|
32315
|
+
return apiKey.value ? void 0 : apiKey.envName ?? "apiKey";
|
|
32316
|
+
}
|
|
32317
|
+
function stringValue5(value) {
|
|
32318
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
32319
|
+
}
|
|
32320
|
+
function missingAuth(envName) {
|
|
32321
|
+
const error = new Error(`missing-auth:${envName}`);
|
|
32322
|
+
return error;
|
|
32323
|
+
}
|
|
32324
|
+
function isMissingAuthError(error) {
|
|
32325
|
+
if (error instanceof CodexAuthError) {
|
|
32326
|
+
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";
|
|
32327
|
+
}
|
|
32328
|
+
return error instanceof Error && error.message.startsWith("missing-auth:");
|
|
32329
|
+
}
|
|
32330
|
+
function errorMessage2(error) {
|
|
32331
|
+
return error instanceof Error ? error.message : String(error);
|
|
32332
|
+
}
|
|
32333
|
+
async function codexClientVersion(override) {
|
|
32334
|
+
const normalized = semverLike(override);
|
|
32335
|
+
if (normalized) return normalized;
|
|
32336
|
+
packageVersionPromise ??= readPackageVersion();
|
|
32337
|
+
return packageVersionPromise;
|
|
32338
|
+
}
|
|
32339
|
+
async function readPackageVersion() {
|
|
32340
|
+
try {
|
|
32341
|
+
const raw = JSON.parse(await readFile15(new URL("../package.json", import.meta.url), "utf8"));
|
|
32342
|
+
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
32343
|
+
} catch {
|
|
32344
|
+
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
32345
|
+
}
|
|
32346
|
+
}
|
|
32347
|
+
function semverLike(value) {
|
|
32348
|
+
if (typeof value !== "string") return void 0;
|
|
32349
|
+
const trimmed = value.trim();
|
|
32350
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed) ? trimmed : void 0;
|
|
32351
|
+
}
|
|
32352
|
+
function withTimeout(promise, timeoutMs) {
|
|
32353
|
+
return new Promise((resolve, reject) => {
|
|
32354
|
+
const timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
32355
|
+
promise.then(
|
|
32356
|
+
(value) => {
|
|
32357
|
+
clearTimeout(timer);
|
|
32358
|
+
resolve(value);
|
|
32359
|
+
},
|
|
32360
|
+
(error) => {
|
|
32361
|
+
clearTimeout(timer);
|
|
32362
|
+
reject(error);
|
|
32363
|
+
}
|
|
32364
|
+
);
|
|
32365
|
+
});
|
|
32366
|
+
}
|
|
32367
|
+
|
|
32368
|
+
// src/ui/settings.ts
|
|
32369
|
+
function providerOptions(config, catalogs = {}) {
|
|
32370
|
+
const orderedNames = sortProviderNames(Object.keys(config.providers));
|
|
32371
|
+
const order = new Map(orderedNames.map((name, index) => [name, index]));
|
|
32372
|
+
return orderedNames.map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli")).map(([name, provider]) => {
|
|
32373
|
+
const catalog = catalogs[name];
|
|
32374
|
+
const available = providerCatalogAvailable(catalog);
|
|
32375
|
+
const profiles = catalog?.models.length ? catalog.models.map((model) => ({
|
|
32376
|
+
name: model.name,
|
|
32377
|
+
displayName: model.displayName ?? model.name,
|
|
32378
|
+
model: model.model,
|
|
32379
|
+
description: model.description,
|
|
32380
|
+
source: model.source,
|
|
32381
|
+
isDefault: model.isDefault
|
|
32382
|
+
})) : providerModelProfiles(name, provider);
|
|
32383
|
+
const modelProfiles = profiles.map((profile) => {
|
|
32384
|
+
const displayName = displayModelForProfile(profile);
|
|
32385
|
+
return {
|
|
32386
|
+
...profile,
|
|
32387
|
+
label: available ? displayName : unavailableLabel(displayName),
|
|
32388
|
+
available
|
|
32389
|
+
};
|
|
32390
|
+
});
|
|
32391
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
32392
|
+
const defaultModel = defaultDisplayModelForProvider(name, provider);
|
|
32393
|
+
return {
|
|
32394
|
+
name,
|
|
32395
|
+
label: available ? name : unavailableLabel(name),
|
|
32396
|
+
model: defaultModel,
|
|
32397
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel(defaultModel),
|
|
32398
|
+
models,
|
|
32399
|
+
modelProfiles,
|
|
32400
|
+
type: provider.type,
|
|
32401
|
+
runtime: provider.type === "claudecode" ? provider.runtime : void 0,
|
|
32402
|
+
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
32403
|
+
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
32404
|
+
available,
|
|
32405
|
+
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
32406
|
+
};
|
|
32407
|
+
}).sort((left, right) => {
|
|
32408
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
32409
|
+
return (order.get(left.name) ?? 0) - (order.get(right.name) ?? 0);
|
|
32410
|
+
});
|
|
32411
|
+
}
|
|
32412
|
+
async function providerOptionsWithCatalog(config, options2 = {}) {
|
|
32413
|
+
const entries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden && !(provider.type === "claudecode" && provider.runtime === "cli"));
|
|
32414
|
+
const catalogs = Object.fromEntries(
|
|
32415
|
+
await Promise.all(
|
|
32416
|
+
entries.map(async ([name, provider]) => {
|
|
32417
|
+
const catalog = await listProviderModelCatalog(name, provider, { timeoutMs: options2.timeoutMs }).catch((error) => ({
|
|
32418
|
+
status: "unavailable",
|
|
32419
|
+
providerName: name,
|
|
32420
|
+
models: [],
|
|
32421
|
+
source: "fallback",
|
|
32422
|
+
message: errorMessage3(error)
|
|
32423
|
+
}));
|
|
32424
|
+
return [name, catalog];
|
|
32425
|
+
})
|
|
32426
|
+
)
|
|
32427
|
+
);
|
|
32428
|
+
return providerOptions(config, catalogs);
|
|
32429
|
+
}
|
|
32430
|
+
var MISSING_SELECTION_PLACEHOLDER = "-";
|
|
31674
32431
|
function createInteractiveModelSelection(config, flags = {}, saved) {
|
|
31675
32432
|
const savedProviderName = saved?.providerName === "claudecode-plan" ? "claudecode" : saved?.providerName;
|
|
31676
|
-
const
|
|
31677
|
-
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
|
|
32433
|
+
const savedProviderExists = savedProviderName && config.providers[savedProviderName] && !config.providers[savedProviderName]?.hidden;
|
|
32434
|
+
if (saved?.providerName && !flags.provider && !savedProviderExists) {
|
|
32435
|
+
return {
|
|
32436
|
+
providerName: saved.providerName,
|
|
32437
|
+
providerSource: "saved",
|
|
32438
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
32439
|
+
modelSource: "saved"
|
|
32440
|
+
};
|
|
32441
|
+
}
|
|
32442
|
+
const providerName = flags.provider ?? (savedProviderExists ? savedProviderName : config.defaultProvider);
|
|
32443
|
+
let providerConfig;
|
|
32444
|
+
try {
|
|
32445
|
+
providerConfig = resolveProviderConfig(config, providerName);
|
|
32446
|
+
} catch {
|
|
32447
|
+
return {
|
|
32448
|
+
providerName,
|
|
32449
|
+
providerSource: flags.provider ? "flag" : "config",
|
|
32450
|
+
model: MISSING_SELECTION_PLACEHOLDER,
|
|
32451
|
+
modelSource: "config"
|
|
32452
|
+
};
|
|
32453
|
+
}
|
|
32454
|
+
const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
|
|
31681
32455
|
const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
|
|
32456
|
+
const profiles = providerModelProfiles(providerName, providerConfig);
|
|
32457
|
+
const savedProfile = savedModel ? profiles.find((profile) => profile.model === savedModel || profile.displayName === savedModel || profile.name === savedModel) : void 0;
|
|
32458
|
+
const defaultModel = displayModelForProfile(profiles[0] ?? { name: "", displayName: "", model: "" }) || defaultDisplayModelForProvider(providerName, providerConfig);
|
|
32459
|
+
const flagProfile = flags.model ? profiles.find((profile) => profile.model === flags.model || profile.displayName === flags.model || profile.name === flags.model) : void 0;
|
|
32460
|
+
const selectedProfile = flags.model ? flagProfile : savedModel ? savedProfile : profiles[0];
|
|
32461
|
+
const model = flags.model ?? (savedProfile ? displayModelForProfile(savedProfile) : savedModel) ?? defaultModel;
|
|
31682
32462
|
return {
|
|
31683
32463
|
providerName,
|
|
31684
32464
|
providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
|
|
31685
|
-
model
|
|
31686
|
-
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config"
|
|
32465
|
+
model,
|
|
32466
|
+
modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
|
|
32467
|
+
modelProfileName: selectedProfile?.name
|
|
31687
32468
|
};
|
|
31688
32469
|
}
|
|
32470
|
+
function providerCatalogAvailable(catalog) {
|
|
32471
|
+
return catalog ? catalog.status === "ready" && catalog.models.length > 0 : true;
|
|
32472
|
+
}
|
|
32473
|
+
function unavailableLabel(value) {
|
|
32474
|
+
return `(n/a) ${value}`;
|
|
32475
|
+
}
|
|
32476
|
+
function errorMessage3(error) {
|
|
32477
|
+
return error instanceof Error ? error.message : String(error);
|
|
32478
|
+
}
|
|
31689
32479
|
|
|
31690
32480
|
// src/ui/tui/controller.ts
|
|
31691
32481
|
async function runTuiSession(flags, store2) {
|
|
31692
|
-
const cwd =
|
|
32482
|
+
const cwd = path32.resolve(flags.cwd ?? process.cwd());
|
|
31693
32483
|
const eventBus = new EventBus();
|
|
31694
32484
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
31695
32485
|
const loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
@@ -31710,7 +32500,7 @@ async function runTuiSession(flags, store2) {
|
|
|
31710
32500
|
const current = agentRegistry.get(agentName);
|
|
31711
32501
|
agentOptions.unshift({ name: current.name, description: current.description });
|
|
31712
32502
|
}
|
|
31713
|
-
store2.configureSettings(initialSelection,
|
|
32503
|
+
store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
31714
32504
|
agent: agentName,
|
|
31715
32505
|
cwd,
|
|
31716
32506
|
agentOptions,
|
|
@@ -31727,9 +32517,9 @@ async function runTuiSession(flags, store2) {
|
|
|
31727
32517
|
const rootSessionId = flags.sessionId ?? createRootSessionId();
|
|
31728
32518
|
const goalTitleGenerator = async (input2) => {
|
|
31729
32519
|
const selection = store2.currentSelection();
|
|
31730
|
-
const
|
|
31731
|
-
const resolved = resolveExecutionBackend(providerConfig, {
|
|
31732
|
-
model:
|
|
32520
|
+
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
32521
|
+
const resolved = resolveExecutionBackend(runtime.providerConfig, {
|
|
32522
|
+
model: runtime.model,
|
|
31733
32523
|
onRetry: (event) => eventBus.emit({
|
|
31734
32524
|
type: "provider.retry",
|
|
31735
32525
|
sessionId: "goal-title",
|
|
@@ -31750,7 +32540,7 @@ async function runTuiSession(flags, store2) {
|
|
|
31750
32540
|
return 0;
|
|
31751
32541
|
}
|
|
31752
32542
|
if (!prompt) return 0;
|
|
31753
|
-
if (
|
|
32543
|
+
if (isCompactCommand(prompt)) {
|
|
31754
32544
|
const result = compactInteractiveHistory(history, config.context.main);
|
|
31755
32545
|
history = result.messages;
|
|
31756
32546
|
store2.prepareForPrompt("waiting for next message");
|
|
@@ -31787,6 +32577,7 @@ async function runTuiSession(flags, store2) {
|
|
|
31787
32577
|
if (goalCommand.kind === "message") {
|
|
31788
32578
|
store2.showSystemMessage("Goal", goalCommand.message);
|
|
31789
32579
|
if (parsedGoalCommand?.action === "clear") store2.clearGoalState();
|
|
32580
|
+
else if (!goalCommand.state && isGoalStateClearingAction(parsedGoalCommand?.action)) store2.clearGoalState();
|
|
31790
32581
|
else if (goalCommand.state) store2.showGoalState(goalCommand.state);
|
|
31791
32582
|
store2.prepareForPrompt("waiting for next message");
|
|
31792
32583
|
continue;
|
|
@@ -31838,10 +32629,10 @@ async function runTuiSession(flags, store2) {
|
|
|
31838
32629
|
if (goalState) await goalRuntime.finishIteration({ state: goalState, finalAnswer: result.finalAnswer, reason: result.endReason, usage: result.usage });
|
|
31839
32630
|
history = interactiveHistoryFromRunMessages(result.messages);
|
|
31840
32631
|
} else {
|
|
31841
|
-
const providerConfig = resolveProviderConfig(runConfig, selection.providerName);
|
|
31842
32632
|
const runtimeAgent = goalState ? withGoalTools(agent) : agent;
|
|
31843
|
-
const
|
|
31844
|
-
|
|
32633
|
+
const runtime = resolveProviderRuntimeConfig(runConfig, selection);
|
|
32634
|
+
const backend = resolveExecutionBackend(runtime.providerConfig, {
|
|
32635
|
+
model: runtime.model,
|
|
31845
32636
|
config: runConfig,
|
|
31846
32637
|
agent: runtimeAgent,
|
|
31847
32638
|
onRetry: (event) => eventBus.emit({
|
|
@@ -31933,8 +32724,461 @@ async function runTuiSession(flags, store2) {
|
|
|
31933
32724
|
savedSelectionKey = key;
|
|
31934
32725
|
}
|
|
31935
32726
|
}
|
|
32727
|
+
function isGoalStateClearingAction(action) {
|
|
32728
|
+
return action === "status" || action === "pause" || action === "resume";
|
|
32729
|
+
}
|
|
32730
|
+
|
|
32731
|
+
// src/config-watcher.ts
|
|
32732
|
+
import { EventEmitter } from "node:events";
|
|
32733
|
+
import fs11 from "node:fs";
|
|
32734
|
+
import path34 from "node:path";
|
|
32735
|
+
|
|
32736
|
+
// src/config-scaffold.ts
|
|
32737
|
+
import { chmod as chmod3, mkdir as mkdir14, readFile as readFile16, rename as rename6, stat as stat8, writeFile as writeFile13 } from "node:fs/promises";
|
|
32738
|
+
import os13 from "node:os";
|
|
32739
|
+
import path33 from "node:path";
|
|
32740
|
+
function defaultUserConfigPath() {
|
|
32741
|
+
return path33.join(os13.homedir(), ".demian", "config.json");
|
|
32742
|
+
}
|
|
32743
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
32744
|
+
return {
|
|
32745
|
+
version: 2,
|
|
32746
|
+
defaultProvider,
|
|
32747
|
+
providers: {
|
|
32748
|
+
openai: {
|
|
32749
|
+
type: "openai-compatible",
|
|
32750
|
+
baseURL: "https://api.openai.com/v1",
|
|
32751
|
+
apiKey: "",
|
|
32752
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
32753
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
32754
|
+
},
|
|
32755
|
+
anthropic: {
|
|
32756
|
+
type: "anthropic",
|
|
32757
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
32758
|
+
apiKey: "",
|
|
32759
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
32760
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
32761
|
+
},
|
|
32762
|
+
gemini: {
|
|
32763
|
+
type: "openai-compatible",
|
|
32764
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
32765
|
+
apiKey: "",
|
|
32766
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
32767
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
32768
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
32769
|
+
},
|
|
32770
|
+
groq: {
|
|
32771
|
+
type: "openai-compatible",
|
|
32772
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
32773
|
+
apiKey: "",
|
|
32774
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
32775
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
32776
|
+
},
|
|
32777
|
+
azure: {
|
|
32778
|
+
type: "openai-compatible",
|
|
32779
|
+
auth: { type: "api-key", header: "api-key" },
|
|
32780
|
+
modelProfiles: [
|
|
32781
|
+
{
|
|
32782
|
+
name: "azure-example",
|
|
32783
|
+
displayName: "Azure example",
|
|
32784
|
+
model: "azure-deployment-name",
|
|
32785
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
32786
|
+
apiKey: "",
|
|
32787
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
32788
|
+
}
|
|
32789
|
+
]
|
|
32790
|
+
},
|
|
32791
|
+
lmstudio: {
|
|
32792
|
+
type: "openai-compatible",
|
|
32793
|
+
baseURL: "http://localhost:1234/v1",
|
|
32794
|
+
apiKey: "lm-studio",
|
|
32795
|
+
modelProfiles: [],
|
|
32796
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
32797
|
+
},
|
|
32798
|
+
"ollama-local": {
|
|
32799
|
+
type: "openai-compatible",
|
|
32800
|
+
baseURL: "http://localhost:11434/v1",
|
|
32801
|
+
apiKey: "ollama",
|
|
32802
|
+
modelProfiles: [],
|
|
32803
|
+
quirks: { omitTemperature: true },
|
|
32804
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
32805
|
+
},
|
|
32806
|
+
"ollama-cloud": {
|
|
32807
|
+
type: "ollama",
|
|
32808
|
+
baseURL: "https://ollama.com/api",
|
|
32809
|
+
apiKey: "",
|
|
32810
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
32811
|
+
modelProfiles: [],
|
|
32812
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
32813
|
+
},
|
|
32814
|
+
llamacpp: {
|
|
32815
|
+
type: "openai-compatible",
|
|
32816
|
+
baseURL: "http://localhost:8080/v1",
|
|
32817
|
+
apiKey: "llama.cpp",
|
|
32818
|
+
modelProfiles: [],
|
|
32819
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
32820
|
+
},
|
|
32821
|
+
vllm: {
|
|
32822
|
+
type: "openai-compatible",
|
|
32823
|
+
baseURL: "http://localhost:8000/v1",
|
|
32824
|
+
apiKey: "vllm",
|
|
32825
|
+
modelProfiles: [],
|
|
32826
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
32827
|
+
},
|
|
32828
|
+
codex: {
|
|
32829
|
+
type: "codex",
|
|
32830
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
32831
|
+
authStore: "auto",
|
|
32832
|
+
allowApiKeyFallback: false,
|
|
32833
|
+
promptCacheKey: "root-session",
|
|
32834
|
+
catalog: { type: "codex-oauth-models" }
|
|
32835
|
+
},
|
|
32836
|
+
claudecode: {
|
|
32837
|
+
type: "claudecode",
|
|
32838
|
+
runtime: "agent-sdk",
|
|
32839
|
+
cliPath: "~/.local/bin/claude",
|
|
32840
|
+
cwdMode: "session",
|
|
32841
|
+
historyPolicy: "passthrough-resume",
|
|
32842
|
+
onInvalidResume: "fresh",
|
|
32843
|
+
attachmentFallback: "block",
|
|
32844
|
+
allowSubagents: false,
|
|
32845
|
+
sanitizeApiKeyEnv: true,
|
|
32846
|
+
authPreflight: true,
|
|
32847
|
+
useBareMode: false,
|
|
32848
|
+
usageLedgerScope: "process",
|
|
32849
|
+
sessionLock: true,
|
|
32850
|
+
abortPolicy: "record-only",
|
|
32851
|
+
catalog: { type: "claudecode-supported-models" }
|
|
32852
|
+
}
|
|
32853
|
+
}
|
|
32854
|
+
};
|
|
32855
|
+
}
|
|
32856
|
+
async function createUserConfig(options2 = {}) {
|
|
32857
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32858
|
+
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
32859
|
+
`;
|
|
32860
|
+
if (options2.print) return { path: filePath, created: false, content };
|
|
32861
|
+
const existed = await exists(filePath);
|
|
32862
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile16(filePath, "utf8"), existed: true };
|
|
32863
|
+
await writeJsonAtomic(filePath, content);
|
|
32864
|
+
return { path: filePath, created: true, content, existed };
|
|
32865
|
+
}
|
|
32866
|
+
async function addProvider(options2) {
|
|
32867
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32868
|
+
const config = await readConfigObject(filePath);
|
|
32869
|
+
const providers = objectValue(config.providers);
|
|
32870
|
+
const name = options2.name;
|
|
32871
|
+
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
32872
|
+
providers[name] = providerPreset(options2);
|
|
32873
|
+
config.providers = providers;
|
|
32874
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32875
|
+
`;
|
|
32876
|
+
await writeJsonAtomic(filePath, content);
|
|
32877
|
+
return { path: filePath, created: true, content };
|
|
32878
|
+
}
|
|
32879
|
+
async function addModelProfile(options2) {
|
|
32880
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32881
|
+
const config = await readConfigObject(filePath);
|
|
32882
|
+
const providers = objectValue(config.providers);
|
|
32883
|
+
const provider = objectValue(providers[options2.provider]);
|
|
32884
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
32885
|
+
const displayName = options2.displayName ?? options2.name;
|
|
32886
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
32887
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
32888
|
+
if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
32889
|
+
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
|
|
32890
|
+
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.`);
|
|
32891
|
+
}
|
|
32892
|
+
const next = {
|
|
32893
|
+
name: options2.name,
|
|
32894
|
+
displayName,
|
|
32895
|
+
model: options2.model,
|
|
32896
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
32897
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
32898
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
32899
|
+
};
|
|
32900
|
+
if (existingByName >= 0) profiles[existingByName] = next;
|
|
32901
|
+
else profiles.push(next);
|
|
32902
|
+
provider.modelProfiles = profiles;
|
|
32903
|
+
providers[options2.provider] = provider;
|
|
32904
|
+
config.providers = providers;
|
|
32905
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32906
|
+
`;
|
|
32907
|
+
await writeJsonAtomic(filePath, content);
|
|
32908
|
+
return { path: filePath, created: true, content };
|
|
32909
|
+
}
|
|
32910
|
+
async function updateConfigDefaults(options2) {
|
|
32911
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
32912
|
+
const config = await readConfigObject(filePath);
|
|
32913
|
+
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
32914
|
+
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
32915
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
32916
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
32917
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
32918
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
32919
|
+
`;
|
|
32920
|
+
await writeJsonAtomic(filePath, content);
|
|
32921
|
+
return { path: filePath, created: true, content };
|
|
32922
|
+
}
|
|
32923
|
+
async function readConfigObject(filePath) {
|
|
32924
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
32925
|
+
const raw = JSON.parse(await readFile16(filePath, "utf8"));
|
|
32926
|
+
raw.version ??= 2;
|
|
32927
|
+
raw.providers = objectValue(raw.providers);
|
|
32928
|
+
return raw;
|
|
32929
|
+
}
|
|
32930
|
+
function providerPreset(options2) {
|
|
32931
|
+
const preset = options2.preset ?? options2.name;
|
|
32932
|
+
const auth = apiKeyAuthFields(options2);
|
|
32933
|
+
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
32934
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32935
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
32936
|
+
if (preset === "gemini") {
|
|
32937
|
+
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
32938
|
+
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
32939
|
+
}
|
|
32940
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
32941
|
+
if (preset === "azure") {
|
|
32942
|
+
return {
|
|
32943
|
+
type: "openai-compatible",
|
|
32944
|
+
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
32945
|
+
...auth,
|
|
32946
|
+
modelProfiles: [
|
|
32947
|
+
{
|
|
32948
|
+
name: "azure-example",
|
|
32949
|
+
displayName: "Azure example",
|
|
32950
|
+
model: "azure-deployment-name",
|
|
32951
|
+
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
32952
|
+
...options2.apiKey ? {} : { apiKey: "" },
|
|
32953
|
+
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
32954
|
+
}
|
|
32955
|
+
]
|
|
32956
|
+
};
|
|
32957
|
+
}
|
|
32958
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
32959
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
32960
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
32961
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
32962
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
32963
|
+
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" } };
|
|
32964
|
+
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" } };
|
|
32965
|
+
if ((options2.type ?? preset) === "openai-compatible") {
|
|
32966
|
+
const baseURL = options2.baseURL;
|
|
32967
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
32968
|
+
return {
|
|
32969
|
+
type: "openai-compatible",
|
|
32970
|
+
baseURL,
|
|
32971
|
+
...auth,
|
|
32972
|
+
...openAIAuth,
|
|
32973
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
32974
|
+
};
|
|
32975
|
+
}
|
|
32976
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
32977
|
+
}
|
|
32978
|
+
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
32979
|
+
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
32980
|
+
return {
|
|
32981
|
+
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
32982
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
32983
|
+
};
|
|
32984
|
+
}
|
|
32985
|
+
async function writeJsonAtomic(filePath, content) {
|
|
32986
|
+
await mkdir14(path33.dirname(filePath), { recursive: true, mode: 448 });
|
|
32987
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32988
|
+
await writeFile13(temp, content, { mode: 384 });
|
|
32989
|
+
await rename6(temp, filePath);
|
|
32990
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
32991
|
+
}
|
|
32992
|
+
function detectDefaultProvider() {
|
|
32993
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
32994
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
32995
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
32996
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
32997
|
+
return "openai";
|
|
32998
|
+
}
|
|
32999
|
+
function objectValue(value) {
|
|
33000
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
33001
|
+
}
|
|
33002
|
+
function normalizeOptionalName(value) {
|
|
33003
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
33004
|
+
}
|
|
33005
|
+
async function exists(filePath) {
|
|
33006
|
+
try {
|
|
33007
|
+
await stat8(filePath);
|
|
33008
|
+
return true;
|
|
33009
|
+
} catch {
|
|
33010
|
+
return false;
|
|
33011
|
+
}
|
|
33012
|
+
}
|
|
33013
|
+
function expandHome2(value) {
|
|
33014
|
+
return resolveExpandedPath(value);
|
|
33015
|
+
}
|
|
33016
|
+
|
|
33017
|
+
// src/config-watcher.ts
|
|
33018
|
+
var ConfigWatcher = class extends EventEmitter {
|
|
33019
|
+
#filePath;
|
|
33020
|
+
#debounceMs;
|
|
33021
|
+
#watcher;
|
|
33022
|
+
#pollIntervalMs = 1e3;
|
|
33023
|
+
#lastSeen;
|
|
33024
|
+
#pending;
|
|
33025
|
+
#started = false;
|
|
33026
|
+
constructor(options2 = {}) {
|
|
33027
|
+
super();
|
|
33028
|
+
this.#filePath = options2.path ?? defaultUserConfigPath();
|
|
33029
|
+
this.#debounceMs = options2.debounceMs ?? 300;
|
|
33030
|
+
}
|
|
33031
|
+
get filePath() {
|
|
33032
|
+
return this.#filePath;
|
|
33033
|
+
}
|
|
33034
|
+
start() {
|
|
33035
|
+
if (this.#started) return;
|
|
33036
|
+
this.#started = true;
|
|
33037
|
+
this.#lastSeen = this.#snapshot();
|
|
33038
|
+
try {
|
|
33039
|
+
fs11.mkdirSync(path34.dirname(this.#filePath), { recursive: true, mode: 448 });
|
|
33040
|
+
this.#watcher = fs11.watch(path34.dirname(this.#filePath), { persistent: false }, (_eventType, filename) => {
|
|
33041
|
+
if (filename && filename !== path34.basename(this.#filePath)) return;
|
|
33042
|
+
this.#schedule();
|
|
33043
|
+
});
|
|
33044
|
+
this.#watcher.on("error", () => void 0);
|
|
33045
|
+
} catch {
|
|
33046
|
+
this.#watcher = void 0;
|
|
33047
|
+
}
|
|
33048
|
+
fs11.watchFile(this.#filePath, { persistent: false, interval: this.#pollIntervalMs }, () => this.#schedule());
|
|
33049
|
+
}
|
|
33050
|
+
stop() {
|
|
33051
|
+
this.#started = false;
|
|
33052
|
+
if (this.#pending) clearTimeout(this.#pending);
|
|
33053
|
+
this.#pending = void 0;
|
|
33054
|
+
this.#watcher?.close();
|
|
33055
|
+
this.#watcher = void 0;
|
|
33056
|
+
try {
|
|
33057
|
+
fs11.unwatchFile(this.#filePath);
|
|
33058
|
+
} catch {
|
|
33059
|
+
}
|
|
33060
|
+
}
|
|
33061
|
+
#schedule() {
|
|
33062
|
+
if (this.#pending) clearTimeout(this.#pending);
|
|
33063
|
+
this.#pending = setTimeout(() => this.#emit(), this.#debounceMs);
|
|
33064
|
+
}
|
|
33065
|
+
#emit() {
|
|
33066
|
+
this.#pending = void 0;
|
|
33067
|
+
const snapshot = this.#snapshot();
|
|
33068
|
+
const previous = this.#lastSeen ?? { size: 0, mtimeMs: 0, exists: false };
|
|
33069
|
+
this.#lastSeen = snapshot;
|
|
33070
|
+
if (previous.exists && !snapshot.exists) {
|
|
33071
|
+
this.emit("unlink", this.#filePath);
|
|
33072
|
+
this.emit("changed", this.#filePath, "unlink");
|
|
33073
|
+
return;
|
|
33074
|
+
}
|
|
33075
|
+
if (!previous.exists && snapshot.exists) {
|
|
33076
|
+
this.emit("create", this.#filePath);
|
|
33077
|
+
this.emit("changed", this.#filePath, "create");
|
|
33078
|
+
return;
|
|
33079
|
+
}
|
|
33080
|
+
if (snapshot.exists && (snapshot.mtimeMs !== previous.mtimeMs || snapshot.size !== previous.size)) {
|
|
33081
|
+
this.emit("change", this.#filePath);
|
|
33082
|
+
this.emit("changed", this.#filePath, "change");
|
|
33083
|
+
}
|
|
33084
|
+
}
|
|
33085
|
+
#snapshot() {
|
|
33086
|
+
try {
|
|
33087
|
+
const stat9 = fs11.statSync(this.#filePath);
|
|
33088
|
+
return { size: stat9.size, mtimeMs: stat9.mtimeMs, exists: true };
|
|
33089
|
+
} catch {
|
|
33090
|
+
return { size: 0, mtimeMs: 0, exists: false };
|
|
33091
|
+
}
|
|
33092
|
+
}
|
|
33093
|
+
};
|
|
33094
|
+
function createConfigWatcher(options2) {
|
|
33095
|
+
const watcher = new ConfigWatcher(options2);
|
|
33096
|
+
watcher.start();
|
|
33097
|
+
return watcher;
|
|
33098
|
+
}
|
|
31936
33099
|
|
|
31937
33100
|
// src/vscode-worker.ts
|
|
33101
|
+
if (process.argv.includes("--config-template")) {
|
|
33102
|
+
process.stdout.write(`${JSON.stringify(defaultUserConfig(), null, 2)}
|
|
33103
|
+
`);
|
|
33104
|
+
process.exit(0);
|
|
33105
|
+
}
|
|
33106
|
+
if (process.argv.includes("--config-init")) {
|
|
33107
|
+
const force = process.argv.includes("--force");
|
|
33108
|
+
await createUserConfig({ force }).then(
|
|
33109
|
+
(result) => {
|
|
33110
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path, created: result.created, existed: result.existed ?? false })}
|
|
33111
|
+
`);
|
|
33112
|
+
process.exit(0);
|
|
33113
|
+
},
|
|
33114
|
+
(error) => {
|
|
33115
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33116
|
+
`);
|
|
33117
|
+
process.exit(1);
|
|
33118
|
+
}
|
|
33119
|
+
);
|
|
33120
|
+
}
|
|
33121
|
+
if (process.argv.includes("--config-add-provider")) {
|
|
33122
|
+
await addProvider(jsonArg("--config-add-provider")).then(
|
|
33123
|
+
(result) => {
|
|
33124
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33125
|
+
`);
|
|
33126
|
+
process.exit(0);
|
|
33127
|
+
},
|
|
33128
|
+
(error) => {
|
|
33129
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33130
|
+
`);
|
|
33131
|
+
process.exit(1);
|
|
33132
|
+
}
|
|
33133
|
+
);
|
|
33134
|
+
}
|
|
33135
|
+
if (process.argv.includes("--config-add-model")) {
|
|
33136
|
+
await addModelProfile(jsonArg("--config-add-model")).then(
|
|
33137
|
+
(result) => {
|
|
33138
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33139
|
+
`);
|
|
33140
|
+
process.exit(0);
|
|
33141
|
+
},
|
|
33142
|
+
(error) => {
|
|
33143
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33144
|
+
`);
|
|
33145
|
+
process.exit(1);
|
|
33146
|
+
}
|
|
33147
|
+
);
|
|
33148
|
+
}
|
|
33149
|
+
if (process.argv.includes("--config-set-defaults")) {
|
|
33150
|
+
await updateConfigDefaults(jsonArg("--config-set-defaults")).then(
|
|
33151
|
+
(result) => {
|
|
33152
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
33153
|
+
`);
|
|
33154
|
+
process.exit(0);
|
|
33155
|
+
},
|
|
33156
|
+
(error) => {
|
|
33157
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33158
|
+
`);
|
|
33159
|
+
process.exit(1);
|
|
33160
|
+
}
|
|
33161
|
+
);
|
|
33162
|
+
}
|
|
33163
|
+
if (process.argv.includes("--provider-auth-check")) {
|
|
33164
|
+
await checkProviderAuth(jsonArg("--provider-auth-check")).then(
|
|
33165
|
+
(result) => {
|
|
33166
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
|
|
33167
|
+
`);
|
|
33168
|
+
process.exit(0);
|
|
33169
|
+
},
|
|
33170
|
+
(error) => {
|
|
33171
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
33172
|
+
`);
|
|
33173
|
+
process.exit(1);
|
|
33174
|
+
}
|
|
33175
|
+
);
|
|
33176
|
+
}
|
|
33177
|
+
if (process.argv.includes("--config-path")) {
|
|
33178
|
+
process.stdout.write(`${defaultUserConfigPath()}
|
|
33179
|
+
`);
|
|
33180
|
+
process.exit(0);
|
|
33181
|
+
}
|
|
31938
33182
|
var options = parseOptions();
|
|
31939
33183
|
var store = new TuiStore();
|
|
31940
33184
|
var diffState = normalizeDiffState(options.initialSnapshot?.diff);
|
|
@@ -31956,7 +33200,7 @@ store.handleEvent = (event) => {
|
|
|
31956
33200
|
};
|
|
31957
33201
|
store.subscribe(scheduleSnapshot);
|
|
31958
33202
|
process.on("message", (message) => {
|
|
31959
|
-
void handleMessage(message).catch((error) => send({ type: "error", message:
|
|
33203
|
+
void handleMessage(message).catch((error) => send({ type: "error", message: errorMessage4(error) }));
|
|
31960
33204
|
});
|
|
31961
33205
|
process.on("disconnect", () => {
|
|
31962
33206
|
closed = true;
|
|
@@ -31998,7 +33242,7 @@ async function main() {
|
|
|
31998
33242
|
},
|
|
31999
33243
|
(error) => {
|
|
32000
33244
|
sendSnapshotNow();
|
|
32001
|
-
send({ type: "error", message:
|
|
33245
|
+
send({ type: "error", message: errorMessage4(error) });
|
|
32002
33246
|
process.exit(1);
|
|
32003
33247
|
}
|
|
32004
33248
|
);
|
|
@@ -32061,21 +33305,21 @@ async function undoDiffAction(actionId) {
|
|
|
32061
33305
|
if (!action) throw new Error("No diff action is available to undo.");
|
|
32062
33306
|
if (!canUndoDiffAction(diffState, action.id)) throw new Error("Only the latest change for a file can be undone.");
|
|
32063
33307
|
if (!hasBeforeSnapshot(action)) throw new Error("This diff was not recorded with enough content to undo safely.");
|
|
32064
|
-
const target =
|
|
32065
|
-
const relative =
|
|
32066
|
-
if (relative.startsWith("..") ||
|
|
33308
|
+
const target = path35.resolve(options.cwd, action.path);
|
|
33309
|
+
const relative = path35.relative(options.cwd, target);
|
|
33310
|
+
if (relative.startsWith("..") || path35.isAbsolute(relative)) throw new Error("Refusing to undo a file outside the workspace.");
|
|
32067
33311
|
if (!action.beforeExists) {
|
|
32068
33312
|
await rm4(target, { force: true });
|
|
32069
33313
|
} else {
|
|
32070
|
-
await
|
|
32071
|
-
await
|
|
33314
|
+
await mkdir15(path35.dirname(target), { recursive: true });
|
|
33315
|
+
await writeFile14(target, action.beforeContent ?? "", "utf8");
|
|
32072
33316
|
}
|
|
32073
33317
|
markDiffActionUndone(action.id);
|
|
32074
33318
|
send({ type: "diffUndoCompleted", actionId: action.id, path: action.path });
|
|
32075
33319
|
sendSnapshotNow();
|
|
32076
33320
|
}
|
|
32077
33321
|
function submitPrompt(prompt, displayPrompt, images = []) {
|
|
32078
|
-
if (
|
|
33322
|
+
if (isGoalCommandPrompt(prompt)) clearRestoredGoal();
|
|
32079
33323
|
if (!isCompactCommand(prompt)) clearResolvedRestoredWorkPlan();
|
|
32080
33324
|
if (displayPrompt && displayPrompt !== prompt) displayPromptOverrides.push({ prompt, displayPrompt });
|
|
32081
33325
|
pendingImages = images;
|
|
@@ -32089,6 +33333,32 @@ function submitGoalCommand(command) {
|
|
|
32089
33333
|
if (store.snapshot().inputMode !== "prompt") return;
|
|
32090
33334
|
submitPrompt(`/goal ${normalized}`);
|
|
32091
33335
|
}
|
|
33336
|
+
function jsonArg(flag) {
|
|
33337
|
+
const index = process.argv.indexOf(flag);
|
|
33338
|
+
const raw = index >= 0 ? process.argv[index + 1] : void 0;
|
|
33339
|
+
if (!raw) throw new Error(`${flag} requires a JSON payload.`);
|
|
33340
|
+
const parsed = JSON.parse(raw);
|
|
33341
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${flag} payload must be an object.`);
|
|
33342
|
+
return parsed;
|
|
33343
|
+
}
|
|
33344
|
+
async function checkProviderAuth(options2) {
|
|
33345
|
+
const config = await loadConfig({ cwd: process.cwd(), configPath: options2.configPath });
|
|
33346
|
+
const providerName = typeof options2.provider === "string" && options2.provider.trim() ? options2.provider.trim() : config.defaultProvider;
|
|
33347
|
+
const provider = config.providers[providerName];
|
|
33348
|
+
if (!provider) throw new Error(`Unknown provider: ${providerName}`);
|
|
33349
|
+
const catalog = await listProviderModelCatalog(providerName, provider, {
|
|
33350
|
+
refresh: options2.refresh !== false,
|
|
33351
|
+
timeoutMs: typeof options2.timeoutMs === "number" && options2.timeoutMs > 0 ? options2.timeoutMs : 5e3
|
|
33352
|
+
});
|
|
33353
|
+
return {
|
|
33354
|
+
providerName,
|
|
33355
|
+
providerType: provider.type,
|
|
33356
|
+
status: catalog.status,
|
|
33357
|
+
source: catalog.source,
|
|
33358
|
+
message: catalog.message,
|
|
33359
|
+
models: catalog.models.map((model) => ({ name: model.name, displayName: model.displayName, model: model.model, source: model.source, isDefault: model.isDefault }))
|
|
33360
|
+
};
|
|
33361
|
+
}
|
|
32092
33362
|
function displayUserMessageEvent(event) {
|
|
32093
33363
|
if (isNestedInvocationEvent2(event)) return event;
|
|
32094
33364
|
const index = displayPromptOverrides.findIndex((item) => item.prompt === event.text);
|
|
@@ -32183,8 +33453,8 @@ function clearResolvedRestoredWorkPlan() {
|
|
|
32183
33453
|
progressNotes: []
|
|
32184
33454
|
};
|
|
32185
33455
|
}
|
|
32186
|
-
function
|
|
32187
|
-
return /^\/(?:goal|ralph-loop)\
|
|
33456
|
+
function isGoalCommandPrompt(prompt) {
|
|
33457
|
+
return /^\/(?:goal|ralph-loop)\b/i.test(String(prompt || "").trim());
|
|
32188
33458
|
}
|
|
32189
33459
|
function normalizeGoalCommand(command) {
|
|
32190
33460
|
return command === "pause" || command === "resume" || command === "clear" ? command : void 0;
|
|
@@ -32401,7 +33671,7 @@ function numberOrZero(value) {
|
|
|
32401
33671
|
}
|
|
32402
33672
|
function compactEvent(event) {
|
|
32403
33673
|
if (!event || typeof event !== "object") return event;
|
|
32404
|
-
if (event.type === "model.text.delta") return { ...event,
|
|
33674
|
+
if (event.type === "model.text.delta") return { ...event, textLength: event.text?.length ?? 0 };
|
|
32405
33675
|
return event;
|
|
32406
33676
|
}
|
|
32407
33677
|
function send(message) {
|
|
@@ -32440,11 +33710,16 @@ async function loadAndSendConfigMeta() {
|
|
|
32440
33710
|
}
|
|
32441
33711
|
}) : config;
|
|
32442
33712
|
applyInitialApiKeys();
|
|
32443
|
-
sendConfigMeta();
|
|
33713
|
+
void sendConfigMeta();
|
|
32444
33714
|
} catch (error) {
|
|
32445
|
-
send({ type: "error", message:
|
|
33715
|
+
send({ type: "error", message: errorMessage4(error) });
|
|
32446
33716
|
}
|
|
32447
33717
|
}
|
|
33718
|
+
var configWatcher = createConfigWatcher();
|
|
33719
|
+
configWatcher.on("changed", () => {
|
|
33720
|
+
invalidateCatalogPingCache();
|
|
33721
|
+
void loadAndSendConfigMeta();
|
|
33722
|
+
});
|
|
32448
33723
|
function applyInitialApiKeys() {
|
|
32449
33724
|
for (const [provider, apiKey] of Object.entries(options.apiKeys ?? {})) {
|
|
32450
33725
|
if (typeof apiKey === "string") setApiKey(provider, apiKey, { quiet: true });
|
|
@@ -32454,12 +33729,18 @@ function setApiKey(providerName, apiKey, opts = {}) {
|
|
|
32454
33729
|
const provider = loadedConfig?.providers?.[providerName];
|
|
32455
33730
|
const envName = provider?.apiKeyEnv;
|
|
32456
33731
|
if (envName && apiKey.trim()) process.env[envName] = apiKey.trim();
|
|
32457
|
-
if (!opts.quiet) sendConfigMeta();
|
|
33732
|
+
if (!opts.quiet) void sendConfigMeta();
|
|
32458
33733
|
}
|
|
32459
|
-
function sendConfigMeta() {
|
|
33734
|
+
async function sendConfigMeta() {
|
|
32460
33735
|
if (!loadedConfig) return;
|
|
32461
33736
|
const registry = new AgentRegistry();
|
|
32462
33737
|
for (const agentDefinition of Object.values(loadedConfig.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
|
|
33738
|
+
const providerEntries = sortProviderNames(Object.keys(loadedConfig.providers)).map((name) => [name, loadedConfig.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
|
|
33739
|
+
const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
|
|
33740
|
+
const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
|
|
33741
|
+
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
33742
|
+
return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
|
|
33743
|
+
});
|
|
32463
33744
|
send({
|
|
32464
33745
|
type: "configMeta",
|
|
32465
33746
|
config: {
|
|
@@ -32468,30 +33749,61 @@ function sendConfigMeta() {
|
|
|
32468
33749
|
context: {
|
|
32469
33750
|
main: loadedConfig.context?.main
|
|
32470
33751
|
},
|
|
32471
|
-
|
|
33752
|
+
providerOrder: providers.map((provider) => provider.name),
|
|
33753
|
+
providers,
|
|
32472
33754
|
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
32473
33755
|
}
|
|
32474
33756
|
});
|
|
32475
33757
|
}
|
|
32476
|
-
function providerMeta(name, provider) {
|
|
33758
|
+
async function providerMeta(name, provider) {
|
|
32477
33759
|
const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
|
|
32478
|
-
const
|
|
32479
|
-
const
|
|
33760
|
+
const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
|
|
33761
|
+
const available = providerCatalogAvailable2(catalog);
|
|
33762
|
+
const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
|
|
33763
|
+
name: model.name,
|
|
33764
|
+
displayName: model.displayName ?? model.name,
|
|
33765
|
+
model: model.model,
|
|
33766
|
+
description: model.description,
|
|
33767
|
+
source: model.source,
|
|
33768
|
+
isDefault: model.isDefault
|
|
33769
|
+
})) : providerModelProfiles(name, provider);
|
|
33770
|
+
const labeledProfiles = modelProfiles.map((profile) => {
|
|
33771
|
+
const displayName = displayModelForProfile(profile);
|
|
33772
|
+
return {
|
|
33773
|
+
...profile,
|
|
33774
|
+
label: available ? displayName : unavailableLabel2(displayName),
|
|
33775
|
+
available
|
|
33776
|
+
};
|
|
33777
|
+
});
|
|
33778
|
+
const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
|
|
33779
|
+
const defaultModel = defaultDisplayModelForProvider(name, provider);
|
|
32480
33780
|
return {
|
|
32481
33781
|
name,
|
|
33782
|
+
label: available ? name : unavailableLabel2(name),
|
|
33783
|
+
available,
|
|
32482
33784
|
type: provider.type,
|
|
32483
33785
|
runtime: provider.runtime,
|
|
32484
33786
|
permissionMode: provider.permissionMode,
|
|
32485
33787
|
ga: provider.ga === true || process.env.DEMIAN_CLAUDECODE_GA === "1",
|
|
32486
|
-
model:
|
|
33788
|
+
model: defaultModel,
|
|
33789
|
+
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
|
|
32487
33790
|
models,
|
|
33791
|
+
modelProfiles: labeledProfiles,
|
|
32488
33792
|
baseURL: provider.baseURL,
|
|
32489
33793
|
apiKeyEnv,
|
|
33794
|
+
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
33795
|
+
catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
|
|
32490
33796
|
hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0,
|
|
32491
|
-
hasEnvApiKey: !!
|
|
32492
|
-
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic"
|
|
33797
|
+
hasEnvApiKey: !![apiKeyEnv, ...asStringArray(provider.apiKeyEnvAliases)].find((envName) => envName && process.env[envName]),
|
|
33798
|
+
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
|
|
32493
33799
|
};
|
|
32494
33800
|
}
|
|
33801
|
+
function providerCatalogAvailable2(catalog) {
|
|
33802
|
+
return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
33803
|
+
}
|
|
33804
|
+
function unavailableLabel2(value) {
|
|
33805
|
+
return `(n/a) ${value}`;
|
|
33806
|
+
}
|
|
32495
33807
|
function asStringArray(value) {
|
|
32496
33808
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
32497
33809
|
}
|
|
@@ -32502,6 +33814,6 @@ function contextOptions(value) {
|
|
|
32502
33814
|
if (typeof value.compactAtRatio === "number" && Number.isFinite(value.compactAtRatio) && value.compactAtRatio > 0 && value.compactAtRatio <= 1) context.compactAtRatio = value.compactAtRatio;
|
|
32503
33815
|
return Object.keys(context).length ? context : void 0;
|
|
32504
33816
|
}
|
|
32505
|
-
function
|
|
33817
|
+
function errorMessage4(error) {
|
|
32506
33818
|
return error instanceof Error ? error.message : String(error);
|
|
32507
33819
|
}
|