demian-cli 1.1.1 → 1.1.2
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 +40 -3
- package/dist/cli.mjs +146 -3
- package/dist/tui.mjs +1278 -429
- package/dist/vscode-worker.mjs +978 -429
- package/docs/ko/README.md +39 -3
- package/package.json +1 -1
package/dist/vscode-worker.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/vscode-worker.ts
|
|
4
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4
5
|
import { mkdir as mkdir16, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
|
|
5
6
|
import path36 from "node:path";
|
|
6
7
|
|
|
@@ -762,6 +763,24 @@ function isPathLikeKey(key) {
|
|
|
762
763
|
}
|
|
763
764
|
|
|
764
765
|
// src/ui/tui/store.ts
|
|
766
|
+
var TUI_CONFIG_ACTION_PREFIX = "demian-config:";
|
|
767
|
+
var TUI_SESSION_ACTION_PREFIX = "demian-session:";
|
|
768
|
+
function parseTuiConfigAction(prompt) {
|
|
769
|
+
if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
|
|
770
|
+
try {
|
|
771
|
+
return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
|
|
772
|
+
} catch {
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function parseTuiSessionAction(prompt) {
|
|
777
|
+
if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
|
|
778
|
+
try {
|
|
779
|
+
return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
|
|
780
|
+
} catch {
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
765
784
|
var TuiStore = class {
|
|
766
785
|
#state = {
|
|
767
786
|
status: {},
|
|
@@ -770,8 +789,14 @@ var TuiStore = class {
|
|
|
770
789
|
inputMode: "starting",
|
|
771
790
|
sessionOptions: [],
|
|
772
791
|
sessionCursor: 0,
|
|
792
|
+
sessionCancelBehavior: "exit",
|
|
773
793
|
providerOptions: [],
|
|
774
794
|
providerCursor: 0,
|
|
795
|
+
configModelCursor: 0,
|
|
796
|
+
configAddProviderCursor: 0,
|
|
797
|
+
configModelInput: "",
|
|
798
|
+
configProviderInput: "",
|
|
799
|
+
configProviderPresets: defaultConfigProviderPresets(),
|
|
775
800
|
agentOptions: [],
|
|
776
801
|
agentCursor: 0,
|
|
777
802
|
permissionPreset: "auto",
|
|
@@ -807,6 +832,7 @@ var TuiStore = class {
|
|
|
807
832
|
#pendingPermissionTimeout;
|
|
808
833
|
#syntheticPermissionId = 0;
|
|
809
834
|
#activeClaudeCodePlan;
|
|
835
|
+
#notifyTimer;
|
|
810
836
|
snapshot() {
|
|
811
837
|
return {
|
|
812
838
|
...this.#state,
|
|
@@ -814,6 +840,7 @@ var TuiStore = class {
|
|
|
814
840
|
selection: this.#state.selection ? { ...this.#state.selection } : void 0,
|
|
815
841
|
sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
|
|
816
842
|
providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
|
|
843
|
+
configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
|
|
817
844
|
agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
|
|
818
845
|
blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
|
|
819
846
|
warnings: [...this.#state.warnings],
|
|
@@ -1035,6 +1062,10 @@ var TuiStore = class {
|
|
|
1035
1062
|
agent: context.agent,
|
|
1036
1063
|
cwd: context.cwd
|
|
1037
1064
|
};
|
|
1065
|
+
this.#state.configPath = context.configPath;
|
|
1066
|
+
this.#state.configCreated = context.configCreated;
|
|
1067
|
+
this.#state.configDefaultProvider = context.configDefaultProvider;
|
|
1068
|
+
this.#state.configMessage = context.configMessage;
|
|
1038
1069
|
if (context.contextEfficiency) {
|
|
1039
1070
|
this.#state.contextEfficiency = {
|
|
1040
1071
|
...this.#state.contextEfficiency ?? { selectedTools: [] },
|
|
@@ -1066,7 +1097,7 @@ var TuiStore = class {
|
|
|
1066
1097
|
currentPermissionPreset() {
|
|
1067
1098
|
return this.#state.permissionPreset;
|
|
1068
1099
|
}
|
|
1069
|
-
requestSessionSelection(options2) {
|
|
1100
|
+
requestSessionSelection(options2, behavior = {}) {
|
|
1070
1101
|
if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
|
|
1071
1102
|
const normalized = options2.length > 0 ? options2.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
|
|
1072
1103
|
return new Promise((resolve) => {
|
|
@@ -1074,6 +1105,7 @@ var TuiStore = class {
|
|
|
1074
1105
|
this.#state.inputMode = "session";
|
|
1075
1106
|
this.#state.sessionOptions = normalized;
|
|
1076
1107
|
this.#state.sessionCursor = 0;
|
|
1108
|
+
this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
|
|
1077
1109
|
this.#state.promptInput = "";
|
|
1078
1110
|
this.#state.promptError = void 0;
|
|
1079
1111
|
this.#state.streamingText = "";
|
|
@@ -1083,6 +1115,14 @@ var TuiStore = class {
|
|
|
1083
1115
|
this.#notify();
|
|
1084
1116
|
});
|
|
1085
1117
|
}
|
|
1118
|
+
cancelSessionSelection() {
|
|
1119
|
+
if (this.#state.inputMode !== "session") return false;
|
|
1120
|
+
if (this.#state.sessionCancelBehavior === "prompt") {
|
|
1121
|
+
this.#resolveSession({ kind: "cancel" });
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
1086
1126
|
moveSessionCursor(delta) {
|
|
1087
1127
|
if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
|
|
1088
1128
|
const count = this.#state.sessionOptions.length;
|
|
@@ -1103,6 +1143,10 @@ var TuiStore = class {
|
|
|
1103
1143
|
if (this.#state.inputMode !== "session") return;
|
|
1104
1144
|
this.#resolveSession({ kind: "new" });
|
|
1105
1145
|
}
|
|
1146
|
+
submitSessionSelectionShortcut() {
|
|
1147
|
+
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1148
|
+
this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
|
|
1149
|
+
}
|
|
1106
1150
|
requestPrompt(initialPrompt) {
|
|
1107
1151
|
if (this.#exitRequested) return Promise.resolve("");
|
|
1108
1152
|
const prompt = (initialPrompt ?? "").trim();
|
|
@@ -1200,6 +1244,143 @@ var TuiStore = class {
|
|
|
1200
1244
|
this.#state.activity = `provider ${option.name} selected`;
|
|
1201
1245
|
this.#notify();
|
|
1202
1246
|
}
|
|
1247
|
+
openConfigManager(message) {
|
|
1248
|
+
const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
|
|
1249
|
+
if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
|
|
1250
|
+
this.#state.inputMode = "config";
|
|
1251
|
+
const current = this.#state.selection?.providerName ?? this.#state.status.provider;
|
|
1252
|
+
this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
|
|
1253
|
+
this.#state.configMessage = message ?? this.#state.configMessage;
|
|
1254
|
+
this.#state.settingsError = void 0;
|
|
1255
|
+
this.#state.activity = "configure providers and models";
|
|
1256
|
+
this.#notify();
|
|
1257
|
+
}
|
|
1258
|
+
openConfigProviderInput(field) {
|
|
1259
|
+
if (this.#state.inputMode !== "config") return;
|
|
1260
|
+
const provider = this.#selectedConfigProvider();
|
|
1261
|
+
if (!provider) return;
|
|
1262
|
+
this.#state.inputMode = "configProviderInput";
|
|
1263
|
+
this.#state.configProviderInputField = field;
|
|
1264
|
+
this.#state.configProviderInput = configProviderFieldValue(provider, field);
|
|
1265
|
+
this.#state.settingsError = void 0;
|
|
1266
|
+
this.#state.activity = `edit ${field} for ${provider.name}`;
|
|
1267
|
+
this.#notify();
|
|
1268
|
+
}
|
|
1269
|
+
appendConfigProviderInput(input2) {
|
|
1270
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1271
|
+
this.#state.configProviderInput += input2;
|
|
1272
|
+
this.#state.settingsError = void 0;
|
|
1273
|
+
this.#notify();
|
|
1274
|
+
}
|
|
1275
|
+
backspaceConfigProviderInput() {
|
|
1276
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1277
|
+
this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
|
|
1278
|
+
this.#state.settingsError = void 0;
|
|
1279
|
+
this.#notify();
|
|
1280
|
+
}
|
|
1281
|
+
submitConfigProviderInput() {
|
|
1282
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1283
|
+
const provider = this.#selectedConfigProvider();
|
|
1284
|
+
const field = this.#state.configProviderInputField;
|
|
1285
|
+
if (!provider || !field) return;
|
|
1286
|
+
this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
|
|
1287
|
+
}
|
|
1288
|
+
moveConfigProviderCursor(delta) {
|
|
1289
|
+
if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
|
|
1290
|
+
const count = this.#state.providerOptions.length;
|
|
1291
|
+
this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
|
|
1292
|
+
this.#notify();
|
|
1293
|
+
}
|
|
1294
|
+
submitConfigDefaultProvider() {
|
|
1295
|
+
if (this.#state.inputMode !== "config") return;
|
|
1296
|
+
const option = this.#selectedConfigProvider();
|
|
1297
|
+
if (!option) return;
|
|
1298
|
+
this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
|
|
1299
|
+
}
|
|
1300
|
+
openConfigModelSelector() {
|
|
1301
|
+
if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
|
|
1302
|
+
const option = this.#selectedConfigProvider();
|
|
1303
|
+
if (!option) return;
|
|
1304
|
+
this.#state.inputMode = "configModel";
|
|
1305
|
+
this.#state.configModelCursor = 0;
|
|
1306
|
+
this.#state.settingsError = void 0;
|
|
1307
|
+
this.#state.activity = `choose default model for ${option.name}`;
|
|
1308
|
+
this.#notify();
|
|
1309
|
+
}
|
|
1310
|
+
moveConfigModelCursor(delta) {
|
|
1311
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1312
|
+
const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
|
|
1313
|
+
if (profiles.length === 0) return;
|
|
1314
|
+
this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
|
|
1315
|
+
this.#notify();
|
|
1316
|
+
}
|
|
1317
|
+
submitConfigDefaultModel() {
|
|
1318
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1319
|
+
const provider = this.#selectedConfigProvider();
|
|
1320
|
+
const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
|
|
1321
|
+
if (!provider || !profile?.model) return;
|
|
1322
|
+
this.#submitConfigAction({
|
|
1323
|
+
type: "setDefaultModel",
|
|
1324
|
+
provider: provider.name,
|
|
1325
|
+
profileName: profile.name,
|
|
1326
|
+
name: profile.name,
|
|
1327
|
+
displayName: profile.displayName,
|
|
1328
|
+
model: profile.model
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
openConfigModelInput() {
|
|
1332
|
+
if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
|
|
1333
|
+
if (!this.#selectedConfigProvider()) return;
|
|
1334
|
+
this.#state.inputMode = "configModelInput";
|
|
1335
|
+
this.#state.configModelInput = "";
|
|
1336
|
+
this.#state.settingsError = void 0;
|
|
1337
|
+
this.#state.activity = "add a model profile";
|
|
1338
|
+
this.#notify();
|
|
1339
|
+
}
|
|
1340
|
+
appendConfigModelInput(input2) {
|
|
1341
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1342
|
+
this.#state.configModelInput += input2;
|
|
1343
|
+
this.#state.settingsError = void 0;
|
|
1344
|
+
this.#notify();
|
|
1345
|
+
}
|
|
1346
|
+
backspaceConfigModelInput() {
|
|
1347
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1348
|
+
this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
|
|
1349
|
+
this.#state.settingsError = void 0;
|
|
1350
|
+
this.#notify();
|
|
1351
|
+
}
|
|
1352
|
+
submitConfigModelInput() {
|
|
1353
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1354
|
+
const provider = this.#selectedConfigProvider();
|
|
1355
|
+
const model = this.#state.configModelInput.trim();
|
|
1356
|
+
if (!provider || !model) {
|
|
1357
|
+
this.#state.settingsError = "Model ID is required.";
|
|
1358
|
+
this.#notify();
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
|
|
1362
|
+
}
|
|
1363
|
+
openConfigAddProviderSelector() {
|
|
1364
|
+
if (this.#state.inputMode !== "config") return;
|
|
1365
|
+
this.#state.inputMode = "configAddProvider";
|
|
1366
|
+
this.#state.configAddProviderCursor = 0;
|
|
1367
|
+
this.#state.settingsError = void 0;
|
|
1368
|
+
this.#state.activity = "add provider preset";
|
|
1369
|
+
this.#notify();
|
|
1370
|
+
}
|
|
1371
|
+
moveConfigAddProviderCursor(delta) {
|
|
1372
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1373
|
+
const count = this.#state.configProviderPresets.length;
|
|
1374
|
+
if (count === 0) return;
|
|
1375
|
+
this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
|
|
1376
|
+
this.#notify();
|
|
1377
|
+
}
|
|
1378
|
+
submitConfigAddProvider() {
|
|
1379
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1380
|
+
const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
|
|
1381
|
+
if (!preset) return;
|
|
1382
|
+
this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
|
|
1383
|
+
}
|
|
1203
1384
|
openAgentSelector() {
|
|
1204
1385
|
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1205
1386
|
if (this.#state.agentOptions.length === 0) {
|
|
@@ -1298,9 +1479,12 @@ var TuiStore = class {
|
|
|
1298
1479
|
this.#notify();
|
|
1299
1480
|
}
|
|
1300
1481
|
closeSettings() {
|
|
1301
|
-
if (
|
|
1482
|
+
if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
|
|
1302
1483
|
this.#state.inputMode = "prompt";
|
|
1303
1484
|
this.#state.modelInput = "";
|
|
1485
|
+
this.#state.configModelInput = "";
|
|
1486
|
+
this.#state.configProviderInput = "";
|
|
1487
|
+
this.#state.configProviderInputField = void 0;
|
|
1304
1488
|
this.#state.settingsError = void 0;
|
|
1305
1489
|
this.#state.activity = "waiting for first message";
|
|
1306
1490
|
this.#notify();
|
|
@@ -1429,9 +1613,12 @@ var TuiStore = class {
|
|
|
1429
1613
|
this.#state.streamingFinalized = false;
|
|
1430
1614
|
this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
|
|
1431
1615
|
}
|
|
1432
|
-
if (event.type === "model.text.delta"
|
|
1616
|
+
if (event.type === "model.text.delta") {
|
|
1617
|
+
if (isNestedInvocationEvent(event)) return;
|
|
1433
1618
|
this.#state.streamingText += event.text;
|
|
1434
1619
|
this.#state.streamingFinalized = false;
|
|
1620
|
+
this.#notify({ deferMs: 50 });
|
|
1621
|
+
return;
|
|
1435
1622
|
}
|
|
1436
1623
|
if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
|
|
1437
1624
|
this.#state.streamingText = event.text;
|
|
@@ -2145,14 +2332,28 @@ var TuiStore = class {
|
|
|
2145
2332
|
resolve(prompt);
|
|
2146
2333
|
this.#notify();
|
|
2147
2334
|
}
|
|
2335
|
+
#selectedConfigProvider() {
|
|
2336
|
+
return this.#state.providerOptions[this.#state.providerCursor];
|
|
2337
|
+
}
|
|
2338
|
+
#submitConfigAction(action) {
|
|
2339
|
+
this.#state.settingsError = void 0;
|
|
2340
|
+
this.#state.configModelInput = "";
|
|
2341
|
+
this.#state.configProviderInput = "";
|
|
2342
|
+
this.#state.configProviderInputField = void 0;
|
|
2343
|
+
this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
|
|
2344
|
+
}
|
|
2148
2345
|
#resolveSession(selection) {
|
|
2149
2346
|
const resolve = this.#sessionResolve;
|
|
2150
2347
|
if (!resolve) return;
|
|
2151
2348
|
this.#sessionResolve = void 0;
|
|
2349
|
+
this.#state.sessionCancelBehavior = "exit";
|
|
2152
2350
|
if (selection.kind === "exit") {
|
|
2153
2351
|
this.#state.inputMode = "done";
|
|
2154
2352
|
this.#state.done = true;
|
|
2155
2353
|
this.#state.activity = "exiting";
|
|
2354
|
+
} else if (selection.kind === "cancel") {
|
|
2355
|
+
this.#state.inputMode = "prompt";
|
|
2356
|
+
this.#state.activity = "waiting for next message";
|
|
2156
2357
|
} else {
|
|
2157
2358
|
this.#state.inputMode = "starting";
|
|
2158
2359
|
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
@@ -2264,7 +2465,23 @@ var TuiStore = class {
|
|
|
2264
2465
|
}
|
|
2265
2466
|
};
|
|
2266
2467
|
}
|
|
2267
|
-
#notify() {
|
|
2468
|
+
#notify(options2 = {}) {
|
|
2469
|
+
const deferMs = options2.deferMs ?? 0;
|
|
2470
|
+
if (deferMs > 0) {
|
|
2471
|
+
if (this.#notifyTimer) return;
|
|
2472
|
+
this.#notifyTimer = setTimeout(() => {
|
|
2473
|
+
this.#notifyTimer = void 0;
|
|
2474
|
+
this.#emitNotify();
|
|
2475
|
+
}, deferMs);
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
if (this.#notifyTimer) {
|
|
2479
|
+
clearTimeout(this.#notifyTimer);
|
|
2480
|
+
this.#notifyTimer = void 0;
|
|
2481
|
+
}
|
|
2482
|
+
this.#emitNotify();
|
|
2483
|
+
}
|
|
2484
|
+
#emitNotify() {
|
|
2268
2485
|
for (const listener of this.#listeners) listener();
|
|
2269
2486
|
}
|
|
2270
2487
|
};
|
|
@@ -2427,6 +2644,25 @@ function cloneContextEfficiency(context) {
|
|
|
2427
2644
|
lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
|
|
2428
2645
|
};
|
|
2429
2646
|
}
|
|
2647
|
+
function configProviderFieldValue(provider, field) {
|
|
2648
|
+
if (field === "baseURL") return provider.baseURL ?? "";
|
|
2649
|
+
if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
|
|
2650
|
+
return provider.auth?.header ?? "";
|
|
2651
|
+
}
|
|
2652
|
+
function defaultConfigProviderPresets() {
|
|
2653
|
+
return [
|
|
2654
|
+
{ label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
|
|
2655
|
+
{ label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
|
|
2656
|
+
{ label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
|
|
2657
|
+
{ label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
|
|
2658
|
+
{ label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
|
|
2659
|
+
{ label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
|
|
2660
|
+
{ label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
|
|
2661
|
+
{ label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
|
|
2662
|
+
{ label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
|
|
2663
|
+
{ label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
|
|
2664
|
+
];
|
|
2665
|
+
}
|
|
2430
2666
|
function cloneToolRunDetails(details) {
|
|
2431
2667
|
if (!details) return void 0;
|
|
2432
2668
|
return {
|
|
@@ -2527,7 +2763,8 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2527
2763
|
}
|
|
2528
2764
|
|
|
2529
2765
|
// src/ui/tui/controller.ts
|
|
2530
|
-
import
|
|
2766
|
+
import path34 from "node:path";
|
|
2767
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2531
2768
|
|
|
2532
2769
|
// src/agents/types.ts
|
|
2533
2770
|
function normalizeAgent(input2) {
|
|
@@ -26966,10 +27203,380 @@ function claudeCodeRuntimePolicyHash(config) {
|
|
|
26966
27203
|
}
|
|
26967
27204
|
var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
|
|
26968
27205
|
|
|
26969
|
-
// src/
|
|
26970
|
-
import { mkdir as mkdir6,
|
|
27206
|
+
// src/config-scaffold.ts
|
|
27207
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
26971
27208
|
import os8 from "node:os";
|
|
26972
27209
|
import path14 from "node:path";
|
|
27210
|
+
function defaultUserConfigPath() {
|
|
27211
|
+
return path14.join(os8.homedir(), ".demian", "config.json");
|
|
27212
|
+
}
|
|
27213
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
27214
|
+
return {
|
|
27215
|
+
version: 2,
|
|
27216
|
+
defaultProvider,
|
|
27217
|
+
providers: {
|
|
27218
|
+
openai: {
|
|
27219
|
+
type: "openai-compatible",
|
|
27220
|
+
baseURL: "https://api.openai.com/v1",
|
|
27221
|
+
apiKey: "",
|
|
27222
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
27223
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
27224
|
+
},
|
|
27225
|
+
anthropic: {
|
|
27226
|
+
type: "anthropic",
|
|
27227
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
27228
|
+
apiKey: "",
|
|
27229
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
27230
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
27231
|
+
},
|
|
27232
|
+
gemini: {
|
|
27233
|
+
type: "openai-compatible",
|
|
27234
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
27235
|
+
apiKey: "",
|
|
27236
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
27237
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
27238
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
27239
|
+
},
|
|
27240
|
+
groq: {
|
|
27241
|
+
type: "openai-compatible",
|
|
27242
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
27243
|
+
apiKey: "",
|
|
27244
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
27245
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
27246
|
+
},
|
|
27247
|
+
azure: {
|
|
27248
|
+
type: "openai-compatible",
|
|
27249
|
+
auth: { type: "api-key", header: "api-key" },
|
|
27250
|
+
modelProfiles: [
|
|
27251
|
+
{
|
|
27252
|
+
name: "azure-example",
|
|
27253
|
+
displayName: "Azure example",
|
|
27254
|
+
model: "azure-deployment-name",
|
|
27255
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
27256
|
+
apiKey: "",
|
|
27257
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
27258
|
+
}
|
|
27259
|
+
]
|
|
27260
|
+
},
|
|
27261
|
+
lmstudio: {
|
|
27262
|
+
type: "openai-compatible",
|
|
27263
|
+
baseURL: "http://localhost:1234/v1",
|
|
27264
|
+
apiKey: "lm-studio",
|
|
27265
|
+
modelProfiles: [],
|
|
27266
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
27267
|
+
},
|
|
27268
|
+
"ollama-local": {
|
|
27269
|
+
type: "openai-compatible",
|
|
27270
|
+
baseURL: "http://localhost:11434/v1",
|
|
27271
|
+
apiKey: "ollama",
|
|
27272
|
+
modelProfiles: [],
|
|
27273
|
+
quirks: { omitTemperature: true },
|
|
27274
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
27275
|
+
},
|
|
27276
|
+
"ollama-cloud": {
|
|
27277
|
+
type: "ollama",
|
|
27278
|
+
baseURL: "https://ollama.com/api",
|
|
27279
|
+
apiKey: "",
|
|
27280
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
27281
|
+
modelProfiles: [],
|
|
27282
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
27283
|
+
},
|
|
27284
|
+
llamacpp: {
|
|
27285
|
+
type: "openai-compatible",
|
|
27286
|
+
baseURL: "http://localhost:8080/v1",
|
|
27287
|
+
apiKey: "llama.cpp",
|
|
27288
|
+
modelProfiles: [],
|
|
27289
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
27290
|
+
},
|
|
27291
|
+
vllm: {
|
|
27292
|
+
type: "openai-compatible",
|
|
27293
|
+
baseURL: "http://localhost:8000/v1",
|
|
27294
|
+
apiKey: "vllm",
|
|
27295
|
+
modelProfiles: [],
|
|
27296
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
27297
|
+
},
|
|
27298
|
+
codex: {
|
|
27299
|
+
type: "codex",
|
|
27300
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
27301
|
+
authStore: "auto",
|
|
27302
|
+
allowApiKeyFallback: false,
|
|
27303
|
+
promptCacheKey: "root-session",
|
|
27304
|
+
catalog: { type: "codex-oauth-models" }
|
|
27305
|
+
},
|
|
27306
|
+
claudecode: {
|
|
27307
|
+
type: "claudecode",
|
|
27308
|
+
runtime: "agent-sdk",
|
|
27309
|
+
cliPath: "~/.local/bin/claude",
|
|
27310
|
+
cwdMode: "session",
|
|
27311
|
+
historyPolicy: "passthrough-resume",
|
|
27312
|
+
onInvalidResume: "fresh",
|
|
27313
|
+
attachmentFallback: "block",
|
|
27314
|
+
allowSubagents: false,
|
|
27315
|
+
sanitizeApiKeyEnv: true,
|
|
27316
|
+
authPreflight: true,
|
|
27317
|
+
useBareMode: false,
|
|
27318
|
+
usageLedgerScope: "process",
|
|
27319
|
+
sessionLock: true,
|
|
27320
|
+
abortPolicy: "record-only",
|
|
27321
|
+
catalog: { type: "claudecode-supported-models" }
|
|
27322
|
+
}
|
|
27323
|
+
}
|
|
27324
|
+
};
|
|
27325
|
+
}
|
|
27326
|
+
async function createUserConfig(options2 = {}) {
|
|
27327
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27328
|
+
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
27329
|
+
`;
|
|
27330
|
+
if (options2.print) return { path: filePath, created: false, content };
|
|
27331
|
+
const existed = await exists(filePath);
|
|
27332
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
|
|
27333
|
+
await writeJsonAtomic(filePath, content);
|
|
27334
|
+
return { path: filePath, created: true, content, existed };
|
|
27335
|
+
}
|
|
27336
|
+
async function addProvider(options2) {
|
|
27337
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27338
|
+
const config = await readConfigObject(filePath);
|
|
27339
|
+
const providers = objectValue(config.providers);
|
|
27340
|
+
const name = options2.name;
|
|
27341
|
+
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
27342
|
+
providers[name] = providerPreset(options2);
|
|
27343
|
+
config.providers = providers;
|
|
27344
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27345
|
+
`;
|
|
27346
|
+
await writeJsonAtomic(filePath, content);
|
|
27347
|
+
return { path: filePath, created: true, content };
|
|
27348
|
+
}
|
|
27349
|
+
async function addModelProfile(options2) {
|
|
27350
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27351
|
+
const config = await readConfigObject(filePath);
|
|
27352
|
+
const providers = objectValue(config.providers);
|
|
27353
|
+
const provider = objectValue(providers[options2.provider]);
|
|
27354
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
27355
|
+
const displayName = options2.displayName ?? options2.name;
|
|
27356
|
+
const previousName = normalizeOptionalName(options2.previousName);
|
|
27357
|
+
const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
|
|
27358
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
27359
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
27360
|
+
const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
|
|
27361
|
+
if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Pick a different profile name.`);
|
|
27362
|
+
if (existingByName >= 0 && existingByPreviousName < 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
27363
|
+
if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options2.force) {
|
|
27364
|
+
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.`);
|
|
27365
|
+
}
|
|
27366
|
+
const next = {
|
|
27367
|
+
name: options2.name,
|
|
27368
|
+
displayName,
|
|
27369
|
+
model: options2.model,
|
|
27370
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27371
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27372
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27373
|
+
};
|
|
27374
|
+
if (updateIndex >= 0) profiles[updateIndex] = next;
|
|
27375
|
+
else profiles.push(next);
|
|
27376
|
+
provider.modelProfiles = profiles;
|
|
27377
|
+
providers[options2.provider] = provider;
|
|
27378
|
+
config.providers = providers;
|
|
27379
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27380
|
+
`;
|
|
27381
|
+
await writeJsonAtomic(filePath, content);
|
|
27382
|
+
return { path: filePath, created: true, content };
|
|
27383
|
+
}
|
|
27384
|
+
async function updateConfigDefaults(options2) {
|
|
27385
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27386
|
+
const config = await readConfigObject(filePath);
|
|
27387
|
+
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
27388
|
+
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
27389
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
27390
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
27391
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
27392
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27393
|
+
`;
|
|
27394
|
+
await writeJsonAtomic(filePath, content);
|
|
27395
|
+
return { path: filePath, created: true, content };
|
|
27396
|
+
}
|
|
27397
|
+
async function setDefaultModelProfile(options2) {
|
|
27398
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27399
|
+
const config = await readConfigObject(filePath);
|
|
27400
|
+
const providers = objectValue(config.providers);
|
|
27401
|
+
const provider = providers[options2.provider];
|
|
27402
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27403
|
+
const providerConfig = { ...provider };
|
|
27404
|
+
const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
|
|
27405
|
+
const requestedName = normalizeOptionalName(options2.profileName ?? options2.name);
|
|
27406
|
+
const requestedModel = normalizeOptionalName(options2.model);
|
|
27407
|
+
const requestedDisplayName = normalizeOptionalName(options2.displayName) ?? requestedModel ?? requestedName;
|
|
27408
|
+
let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
|
|
27409
|
+
if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
|
|
27410
|
+
if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
|
|
27411
|
+
const profile = index >= 0 ? {
|
|
27412
|
+
...profiles[index],
|
|
27413
|
+
...requestedName ? { name: requestedName } : {},
|
|
27414
|
+
...requestedDisplayName ? { displayName: requestedDisplayName } : {},
|
|
27415
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
27416
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27417
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27418
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27419
|
+
} : newModelProfile({
|
|
27420
|
+
name: requestedName,
|
|
27421
|
+
displayName: requestedDisplayName,
|
|
27422
|
+
model: requestedModel ?? requestedName,
|
|
27423
|
+
baseURL: options2.baseURL,
|
|
27424
|
+
apiKey: options2.apiKey,
|
|
27425
|
+
apiKeyEnv: options2.apiKeyEnv
|
|
27426
|
+
});
|
|
27427
|
+
if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
|
|
27428
|
+
if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
|
|
27429
|
+
if (index >= 0) profiles.splice(index, 1);
|
|
27430
|
+
profiles.unshift(profile);
|
|
27431
|
+
providerConfig.modelProfiles = profiles;
|
|
27432
|
+
providers[options2.provider] = providerConfig;
|
|
27433
|
+
config.providers = providers;
|
|
27434
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27435
|
+
`;
|
|
27436
|
+
await writeJsonAtomic(filePath, content);
|
|
27437
|
+
return { path: filePath, created: true, content };
|
|
27438
|
+
}
|
|
27439
|
+
async function updateProviderSettings(options2) {
|
|
27440
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27441
|
+
const config = await readConfigObject(filePath);
|
|
27442
|
+
const providers = objectValue(config.providers);
|
|
27443
|
+
const provider = providers[options2.provider];
|
|
27444
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27445
|
+
const providerConfig = { ...provider };
|
|
27446
|
+
if (options2.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options2.baseURL);
|
|
27447
|
+
if (options2.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options2.apiKey);
|
|
27448
|
+
if (options2.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options2.apiKeyEnv);
|
|
27449
|
+
if (options2.authHeader !== void 0) {
|
|
27450
|
+
const header = options2.authHeader.trim();
|
|
27451
|
+
if (header) providerConfig.auth = { type: "api-key", header };
|
|
27452
|
+
else delete providerConfig.auth;
|
|
27453
|
+
}
|
|
27454
|
+
providers[options2.provider] = providerConfig;
|
|
27455
|
+
config.providers = providers;
|
|
27456
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27457
|
+
`;
|
|
27458
|
+
await writeJsonAtomic(filePath, content);
|
|
27459
|
+
return { path: filePath, created: true, content };
|
|
27460
|
+
}
|
|
27461
|
+
async function readConfigObject(filePath) {
|
|
27462
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
27463
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
27464
|
+
raw.version ??= 2;
|
|
27465
|
+
raw.providers = objectValue(raw.providers);
|
|
27466
|
+
return raw;
|
|
27467
|
+
}
|
|
27468
|
+
function providerPreset(options2) {
|
|
27469
|
+
const preset = options2.preset ?? options2.name;
|
|
27470
|
+
const auth = apiKeyAuthFields(options2);
|
|
27471
|
+
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
27472
|
+
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` } };
|
|
27473
|
+
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` } };
|
|
27474
|
+
if (preset === "gemini") {
|
|
27475
|
+
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
27476
|
+
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` } };
|
|
27477
|
+
}
|
|
27478
|
+
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` } };
|
|
27479
|
+
if (preset === "azure") {
|
|
27480
|
+
return {
|
|
27481
|
+
type: "openai-compatible",
|
|
27482
|
+
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
27483
|
+
...auth,
|
|
27484
|
+
modelProfiles: [
|
|
27485
|
+
{
|
|
27486
|
+
name: "azure-example",
|
|
27487
|
+
displayName: "Azure example",
|
|
27488
|
+
model: "azure-deployment-name",
|
|
27489
|
+
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
27490
|
+
...options2.apiKey ? {} : { apiKey: "" },
|
|
27491
|
+
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
27492
|
+
}
|
|
27493
|
+
]
|
|
27494
|
+
};
|
|
27495
|
+
}
|
|
27496
|
+
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` } };
|
|
27497
|
+
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` } };
|
|
27498
|
+
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` } };
|
|
27499
|
+
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` } };
|
|
27500
|
+
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` } };
|
|
27501
|
+
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" } };
|
|
27502
|
+
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" } };
|
|
27503
|
+
if ((options2.type ?? preset) === "openai-compatible") {
|
|
27504
|
+
const baseURL = options2.baseURL;
|
|
27505
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
27506
|
+
return {
|
|
27507
|
+
type: "openai-compatible",
|
|
27508
|
+
baseURL,
|
|
27509
|
+
...auth,
|
|
27510
|
+
...openAIAuth,
|
|
27511
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
27512
|
+
};
|
|
27513
|
+
}
|
|
27514
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
27515
|
+
}
|
|
27516
|
+
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
27517
|
+
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
27518
|
+
return {
|
|
27519
|
+
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27520
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
27521
|
+
};
|
|
27522
|
+
}
|
|
27523
|
+
async function writeJsonAtomic(filePath, content) {
|
|
27524
|
+
await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
27525
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
27526
|
+
await writeFile5(temp, content, { mode: 384 });
|
|
27527
|
+
await rename4(temp, filePath);
|
|
27528
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
27529
|
+
}
|
|
27530
|
+
function detectDefaultProvider() {
|
|
27531
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
27532
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
27533
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
27534
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
27535
|
+
return "openai";
|
|
27536
|
+
}
|
|
27537
|
+
function objectValue(value) {
|
|
27538
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
27539
|
+
}
|
|
27540
|
+
function normalizeOptionalName(value) {
|
|
27541
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
27542
|
+
}
|
|
27543
|
+
function newModelProfile(options2) {
|
|
27544
|
+
const model = options2.model?.trim();
|
|
27545
|
+
if (!model) throw new Error("Model ID is required.");
|
|
27546
|
+
const name = options2.name?.trim() || modelProfileNameFromModel(model);
|
|
27547
|
+
return {
|
|
27548
|
+
name,
|
|
27549
|
+
displayName: options2.displayName?.trim() || name,
|
|
27550
|
+
model,
|
|
27551
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27552
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27553
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27554
|
+
};
|
|
27555
|
+
}
|
|
27556
|
+
function modelProfileNameFromModel(model) {
|
|
27557
|
+
return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
|
|
27558
|
+
}
|
|
27559
|
+
function setOrDelete(target, key, value) {
|
|
27560
|
+
const trimmed = value.trim();
|
|
27561
|
+
if (trimmed) target[key] = trimmed;
|
|
27562
|
+
else delete target[key];
|
|
27563
|
+
}
|
|
27564
|
+
async function exists(filePath) {
|
|
27565
|
+
try {
|
|
27566
|
+
await stat3(filePath);
|
|
27567
|
+
return true;
|
|
27568
|
+
} catch {
|
|
27569
|
+
return false;
|
|
27570
|
+
}
|
|
27571
|
+
}
|
|
27572
|
+
function expandHome2(value) {
|
|
27573
|
+
return resolveExpandedPath(value);
|
|
27574
|
+
}
|
|
27575
|
+
|
|
27576
|
+
// src/transcript.ts
|
|
27577
|
+
import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
|
|
27578
|
+
import os9 from "node:os";
|
|
27579
|
+
import path15 from "node:path";
|
|
26973
27580
|
var TranscriptWriter = class {
|
|
26974
27581
|
filePath;
|
|
26975
27582
|
#enabled;
|
|
@@ -26977,9 +27584,9 @@ var TranscriptWriter = class {
|
|
|
26977
27584
|
#queue = Promise.resolve();
|
|
26978
27585
|
constructor(options2) {
|
|
26979
27586
|
this.#enabled = options2.enabled !== false;
|
|
26980
|
-
const dir =
|
|
26981
|
-
this.filePath =
|
|
26982
|
-
this.#ready = this.#enabled ?
|
|
27587
|
+
const dir = path15.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
|
|
27588
|
+
this.filePath = path15.join(dir, "session.jsonl");
|
|
27589
|
+
this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
26983
27590
|
}
|
|
26984
27591
|
write(event) {
|
|
26985
27592
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -26995,14 +27602,14 @@ var TranscriptWriter = class {
|
|
|
26995
27602
|
}
|
|
26996
27603
|
};
|
|
26997
27604
|
function defaultDemianStorageDir() {
|
|
26998
|
-
return
|
|
27605
|
+
return path15.join(os9.homedir(), ".demian");
|
|
26999
27606
|
}
|
|
27000
27607
|
|
|
27001
27608
|
// src/external-runtime/snapshot-diff.ts
|
|
27002
27609
|
import { createHash as createHash2 } from "node:crypto";
|
|
27003
27610
|
import { createReadStream } from "node:fs";
|
|
27004
|
-
import { readdir, readFile as
|
|
27005
|
-
import
|
|
27611
|
+
import { readdir, readFile as readFile8, stat as stat4 } from "node:fs/promises";
|
|
27612
|
+
import path16 from "node:path";
|
|
27006
27613
|
|
|
27007
27614
|
// src/workspace/diff.ts
|
|
27008
27615
|
var CONTEXT_LINES = 3;
|
|
@@ -27212,7 +27819,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
27212
27819
|
}
|
|
27213
27820
|
async function walk(root, relativeDir, entries, options2) {
|
|
27214
27821
|
if (entries.size >= options2.maxFiles) return;
|
|
27215
|
-
const absoluteDir =
|
|
27822
|
+
const absoluteDir = path16.join(root, relativeDir);
|
|
27216
27823
|
let items;
|
|
27217
27824
|
try {
|
|
27218
27825
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -27223,8 +27830,8 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27223
27830
|
if (entries.size >= options2.maxFiles) return;
|
|
27224
27831
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
27225
27832
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
27226
|
-
const relativePath = normalizeRelativePath(
|
|
27227
|
-
const absolutePath =
|
|
27833
|
+
const relativePath = normalizeRelativePath(path16.join(relativeDir, item.name));
|
|
27834
|
+
const absolutePath = path16.join(root, relativePath);
|
|
27228
27835
|
if (item.isDirectory()) {
|
|
27229
27836
|
await walk(root, relativePath, entries, options2);
|
|
27230
27837
|
continue;
|
|
@@ -27235,7 +27842,7 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27235
27842
|
}
|
|
27236
27843
|
}
|
|
27237
27844
|
async function snapshotFile(filePath, relativePath, maxTextBytes) {
|
|
27238
|
-
const info = await
|
|
27845
|
+
const info = await stat4(filePath);
|
|
27239
27846
|
const sha2562 = await sha256File(filePath);
|
|
27240
27847
|
const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
|
|
27241
27848
|
return { path: relativePath, size: info.size, sha256: sha2562, content };
|
|
@@ -27246,7 +27853,7 @@ function snapshotDiffText(entry) {
|
|
|
27246
27853
|
`;
|
|
27247
27854
|
}
|
|
27248
27855
|
async function readTextContent(filePath) {
|
|
27249
|
-
const buffer = await
|
|
27856
|
+
const buffer = await readFile8(filePath);
|
|
27250
27857
|
if (buffer.includes(0)) return void 0;
|
|
27251
27858
|
return buffer.toString("utf8");
|
|
27252
27859
|
}
|
|
@@ -27260,7 +27867,7 @@ function sha256File(filePath) {
|
|
|
27260
27867
|
});
|
|
27261
27868
|
}
|
|
27262
27869
|
function normalizeRelativePath(value) {
|
|
27263
|
-
return value.split(
|
|
27870
|
+
return value.split(path16.sep).join("/");
|
|
27264
27871
|
}
|
|
27265
27872
|
|
|
27266
27873
|
// src/external-runtime/session-runner.ts
|
|
@@ -27695,7 +28302,7 @@ function safeJson(value) {
|
|
|
27695
28302
|
}
|
|
27696
28303
|
|
|
27697
28304
|
// src/hooks/dispatcher.ts
|
|
27698
|
-
import
|
|
28305
|
+
import path18 from "node:path";
|
|
27699
28306
|
|
|
27700
28307
|
// src/hooks/command.ts
|
|
27701
28308
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -27788,7 +28395,7 @@ var blockDangerousBashHook = {
|
|
|
27788
28395
|
};
|
|
27789
28396
|
|
|
27790
28397
|
// src/hooks/builtin/protect-env-files.ts
|
|
27791
|
-
import
|
|
28398
|
+
import path17 from "node:path";
|
|
27792
28399
|
var protectEnvFilesHook = {
|
|
27793
28400
|
name: "protect-env-files",
|
|
27794
28401
|
event: "PreToolUse",
|
|
@@ -27796,7 +28403,7 @@ var protectEnvFilesHook = {
|
|
|
27796
28403
|
run(ctx) {
|
|
27797
28404
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
27798
28405
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27799
|
-
const filePath = typeof input2.path === "string" ?
|
|
28406
|
+
const filePath = typeof input2.path === "string" ? path17.resolve(ctx.cwd, input2.path) : "";
|
|
27800
28407
|
if (filePath && isEnvFile(filePath)) {
|
|
27801
28408
|
return {
|
|
27802
28409
|
decision: "block",
|
|
@@ -27833,7 +28440,7 @@ var maskSecretsHook = {
|
|
|
27833
28440
|
};
|
|
27834
28441
|
|
|
27835
28442
|
// src/hooks/builtin/inject-env-info.ts
|
|
27836
|
-
import
|
|
28443
|
+
import os10 from "node:os";
|
|
27837
28444
|
var injectEnvInfoHook = {
|
|
27838
28445
|
name: "inject-env-info",
|
|
27839
28446
|
event: "SessionStart",
|
|
@@ -27842,7 +28449,7 @@ var injectEnvInfoHook = {
|
|
|
27842
28449
|
decision: "allow",
|
|
27843
28450
|
patch: {
|
|
27844
28451
|
systemNote: [
|
|
27845
|
-
`Environment: ${
|
|
28452
|
+
`Environment: ${os10.type()} ${os10.release()} (${os10.platform()}/${os10.arch()})`,
|
|
27846
28453
|
`cwd: ${ctx.cwd}`,
|
|
27847
28454
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
27848
28455
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -27911,14 +28518,14 @@ function matchesHook(match, ctx) {
|
|
|
27911
28518
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27912
28519
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
27913
28520
|
if (!filePath) return false;
|
|
27914
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
28521
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path18.resolve(ctx.cwd, filePath)))) return false;
|
|
27915
28522
|
}
|
|
27916
28523
|
return true;
|
|
27917
28524
|
}
|
|
27918
28525
|
|
|
27919
28526
|
// src/multimodal.ts
|
|
27920
|
-
import { readFile as
|
|
27921
|
-
import
|
|
28527
|
+
import { readFile as readFile9, stat as stat5 } from "node:fs/promises";
|
|
28528
|
+
import path19 from "node:path";
|
|
27922
28529
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27923
28530
|
async function buildUserContent(prompt, images = [], options2) {
|
|
27924
28531
|
if (images.length === 0) return prompt;
|
|
@@ -27937,16 +28544,16 @@ async function buildUserContent(prompt, images = [], options2) {
|
|
|
27937
28544
|
async function imageToUrl(input2, options2) {
|
|
27938
28545
|
if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
|
|
27939
28546
|
const filePath = resolveInsideCwd(options2.cwd, input2);
|
|
27940
|
-
const info = await
|
|
28547
|
+
const info = await stat5(filePath);
|
|
27941
28548
|
if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
|
|
27942
28549
|
const maxImageBytes = options2.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
27943
28550
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
27944
28551
|
const mime = mimeFromPath(filePath);
|
|
27945
|
-
const bytes = await
|
|
28552
|
+
const bytes = await readFile9(filePath);
|
|
27946
28553
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
27947
28554
|
}
|
|
27948
28555
|
function mimeFromPath(filePath) {
|
|
27949
|
-
switch (
|
|
28556
|
+
switch (path19.extname(filePath).toLowerCase()) {
|
|
27950
28557
|
case ".jpg":
|
|
27951
28558
|
case ".jpeg":
|
|
27952
28559
|
return "image/jpeg";
|
|
@@ -27957,15 +28564,15 @@ function mimeFromPath(filePath) {
|
|
|
27957
28564
|
case ".webp":
|
|
27958
28565
|
return "image/webp";
|
|
27959
28566
|
default:
|
|
27960
|
-
throw new Error(`Unsupported image extension: ${
|
|
28567
|
+
throw new Error(`Unsupported image extension: ${path19.extname(filePath) || "(none)"}`);
|
|
27961
28568
|
}
|
|
27962
28569
|
}
|
|
27963
28570
|
|
|
27964
28571
|
// src/permissions/persistent-grants.ts
|
|
27965
|
-
import { mkdir as
|
|
28572
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
|
|
27966
28573
|
import fs8 from "node:fs";
|
|
27967
|
-
import
|
|
27968
|
-
import
|
|
28574
|
+
import os11 from "node:os";
|
|
28575
|
+
import path20 from "node:path";
|
|
27969
28576
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
27970
28577
|
var PersistentGrantStore = class {
|
|
27971
28578
|
filePath;
|
|
@@ -27996,22 +28603,22 @@ var PersistentGrantStore = class {
|
|
|
27996
28603
|
}
|
|
27997
28604
|
async #read() {
|
|
27998
28605
|
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
27999
|
-
const data = JSON.parse(await
|
|
28606
|
+
const data = JSON.parse(await readFile10(this.filePath, "utf8"));
|
|
28000
28607
|
return {
|
|
28001
28608
|
version: 1,
|
|
28002
28609
|
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
28003
28610
|
};
|
|
28004
28611
|
}
|
|
28005
28612
|
async #write(file) {
|
|
28006
|
-
await
|
|
28007
|
-
await
|
|
28613
|
+
await mkdir8(path20.dirname(this.filePath), { recursive: true });
|
|
28614
|
+
await writeFile7(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
28008
28615
|
`, { mode: 384 });
|
|
28009
28616
|
}
|
|
28010
28617
|
};
|
|
28011
28618
|
function resolveGrantPath(cwd, config) {
|
|
28012
|
-
if (config.path) return
|
|
28013
|
-
if ((config.scope ?? "project") === "user") return
|
|
28014
|
-
return
|
|
28619
|
+
if (config.path) return path20.resolve(cwd, config.path);
|
|
28620
|
+
if ((config.scope ?? "project") === "user") return path20.join(os11.homedir(), ".demian", "grants.json");
|
|
28621
|
+
return path20.join(cwd, ".demian", "grants.json");
|
|
28015
28622
|
}
|
|
28016
28623
|
function isGrantRecord(value) {
|
|
28017
28624
|
if (!value || typeof value !== "object") return false;
|
|
@@ -28444,25 +29051,25 @@ function fail(content, metadata) {
|
|
|
28444
29051
|
}
|
|
28445
29052
|
|
|
28446
29053
|
// src/tools/output.ts
|
|
28447
|
-
import { mkdir as
|
|
28448
|
-
import
|
|
29054
|
+
import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
|
|
29055
|
+
import path21 from "node:path";
|
|
28449
29056
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
28450
29057
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
28451
29058
|
async function capToolOutput(result, options2) {
|
|
28452
29059
|
const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
|
|
28453
29060
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
28454
29061
|
if (bytes <= capBytes) return result;
|
|
28455
|
-
const dir =
|
|
28456
|
-
await
|
|
28457
|
-
const outputPath =
|
|
28458
|
-
await
|
|
29062
|
+
const dir = path21.join(options2.cwd, ".demian", "tmp");
|
|
29063
|
+
await mkdir9(dir, { recursive: true });
|
|
29064
|
+
const outputPath = path21.join(dir, `output-${safeCallId(options2.callId)}.txt`);
|
|
29065
|
+
await writeFile8(outputPath, result.content, "utf8");
|
|
28459
29066
|
return {
|
|
28460
29067
|
...result,
|
|
28461
29068
|
content: [
|
|
28462
29069
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
28463
29070
|
`
|
|
28464
29071
|
|
|
28465
|
-
[Full output saved to ${
|
|
29072
|
+
[Full output saved to ${path21.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
28466
29073
|
|
|
28467
29074
|
`,
|
|
28468
29075
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -28487,8 +29094,8 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
28487
29094
|
}
|
|
28488
29095
|
|
|
28489
29096
|
// src/tools/read-file.ts
|
|
28490
|
-
import { open as open2, stat as
|
|
28491
|
-
import
|
|
29097
|
+
import { open as open2, stat as stat6 } from "node:fs/promises";
|
|
29098
|
+
import path22 from "node:path";
|
|
28492
29099
|
|
|
28493
29100
|
// src/tools/validation.ts
|
|
28494
29101
|
function assertObject(input2, toolName) {
|
|
@@ -28558,7 +29165,7 @@ var readFileTool = {
|
|
|
28558
29165
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28559
29166
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
28560
29167
|
const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
28561
|
-
const info = await
|
|
29168
|
+
const info = await stat6(filePath);
|
|
28562
29169
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28563
29170
|
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28564
29171
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
@@ -28572,7 +29179,7 @@ var readFileTool = {
|
|
|
28572
29179
|
const truncated = start + sliced.length < lines.length;
|
|
28573
29180
|
return ok(content + (truncated ? `
|
|
28574
29181
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
28575
|
-
path:
|
|
29182
|
+
path: path22.relative(ctx.cwd, filePath),
|
|
28576
29183
|
lines: sliced.length,
|
|
28577
29184
|
totalLines: lines.length,
|
|
28578
29185
|
truncated
|
|
@@ -28610,8 +29217,8 @@ function looksBinary(buffer) {
|
|
|
28610
29217
|
}
|
|
28611
29218
|
|
|
28612
29219
|
// src/tools/write-file.ts
|
|
28613
|
-
import { mkdir as
|
|
28614
|
-
import
|
|
29220
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
29221
|
+
import path23 from "node:path";
|
|
28615
29222
|
var writeFileTool = {
|
|
28616
29223
|
name: "write_file",
|
|
28617
29224
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -28629,8 +29236,8 @@ var writeFileTool = {
|
|
|
28629
29236
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28630
29237
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
28631
29238
|
const before = await readOptionalTextFile(filePath);
|
|
28632
|
-
await
|
|
28633
|
-
await
|
|
29239
|
+
await mkdir10(path23.dirname(filePath), { recursive: true });
|
|
29240
|
+
await writeFile9(filePath, content, "utf8");
|
|
28634
29241
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28635
29242
|
const diff = createTextDiff(before, content, relative);
|
|
28636
29243
|
return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
|
|
@@ -28643,7 +29250,7 @@ var writeFileTool = {
|
|
|
28643
29250
|
};
|
|
28644
29251
|
async function readOptionalTextFile(filePath) {
|
|
28645
29252
|
try {
|
|
28646
|
-
return await
|
|
29253
|
+
return await readFile11(filePath, "utf8");
|
|
28647
29254
|
} catch (error) {
|
|
28648
29255
|
if (isNotFound(error)) return void 0;
|
|
28649
29256
|
throw error;
|
|
@@ -28654,7 +29261,7 @@ function isNotFound(error) {
|
|
|
28654
29261
|
}
|
|
28655
29262
|
|
|
28656
29263
|
// src/tools/edit-file.ts
|
|
28657
|
-
import { readFile as
|
|
29264
|
+
import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
|
|
28658
29265
|
var editFileTool = {
|
|
28659
29266
|
name: "edit_file",
|
|
28660
29267
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
@@ -28676,12 +29283,12 @@ var editFileTool = {
|
|
|
28676
29283
|
const newString = stringField(object2, "newString", { allowEmpty: true });
|
|
28677
29284
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
28678
29285
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
28679
|
-
const before = await
|
|
29286
|
+
const before = await readFile12(filePath, "utf8");
|
|
28680
29287
|
const matches = countMatches(before, oldString);
|
|
28681
29288
|
if (matches === 0) throw new Error("oldString was not found");
|
|
28682
29289
|
if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
|
|
28683
29290
|
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
28684
|
-
await
|
|
29291
|
+
await writeFile10(filePath, after, "utf8");
|
|
28685
29292
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28686
29293
|
const diff = createTextDiff(before, after, relative);
|
|
28687
29294
|
return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
|
|
@@ -28705,7 +29312,7 @@ function countMatches(text, needle) {
|
|
|
28705
29312
|
|
|
28706
29313
|
// src/tools/bash.ts
|
|
28707
29314
|
import { spawn as spawn5 } from "node:child_process";
|
|
28708
|
-
import
|
|
29315
|
+
import path25 from "node:path";
|
|
28709
29316
|
|
|
28710
29317
|
// src/sandbox/env-only.ts
|
|
28711
29318
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -28765,7 +29372,7 @@ function bwrapPath() {
|
|
|
28765
29372
|
|
|
28766
29373
|
// src/sandbox/macos.ts
|
|
28767
29374
|
import { existsSync as existsSync3 } from "node:fs";
|
|
28768
|
-
import
|
|
29375
|
+
import path24 from "node:path";
|
|
28769
29376
|
function canUseMacOSSandbox() {
|
|
28770
29377
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
28771
29378
|
}
|
|
@@ -28791,7 +29398,7 @@ function macosProfile(cwd, config) {
|
|
|
28791
29398
|
}
|
|
28792
29399
|
if (mode === "workspace-write") {
|
|
28793
29400
|
lines.push("(deny file-write*)");
|
|
28794
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
29401
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
|
|
28795
29402
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
28796
29403
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
28797
29404
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -28853,7 +29460,7 @@ ${result.stderr}` : ""
|
|
|
28853
29460
|
].filter(Boolean).join("\n");
|
|
28854
29461
|
return ok(content, {
|
|
28855
29462
|
command,
|
|
28856
|
-
workdir:
|
|
29463
|
+
workdir: path25.relative(ctx.cwd, workdir) || ".",
|
|
28857
29464
|
exitCode: result.exitCode,
|
|
28858
29465
|
signal: result.signal,
|
|
28859
29466
|
timedOut: result.timedOut,
|
|
@@ -28905,8 +29512,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
28905
29512
|
|
|
28906
29513
|
// src/tools/grep.ts
|
|
28907
29514
|
import { spawn as spawn6 } from "node:child_process";
|
|
28908
|
-
import { readdir as readdir2, readFile as
|
|
28909
|
-
import
|
|
29515
|
+
import { readdir as readdir2, readFile as readFile13, stat as stat7 } from "node:fs/promises";
|
|
29516
|
+
import path26 from "node:path";
|
|
28910
29517
|
var MAX_MATCHES = 200;
|
|
28911
29518
|
var grepTool = {
|
|
28912
29519
|
name: "grep",
|
|
@@ -28981,7 +29588,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
28981
29588
|
}
|
|
28982
29589
|
function rewriteRgPath(cwd, line) {
|
|
28983
29590
|
const [file, rest] = splitFirst(line, ":");
|
|
28984
|
-
if (!
|
|
29591
|
+
if (!path26.isAbsolute(file)) return line;
|
|
28985
29592
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
28986
29593
|
}
|
|
28987
29594
|
function splitFirst(value, delimiter) {
|
|
@@ -28996,13 +29603,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28996
29603
|
if (out.length > MAX_MATCHES) return;
|
|
28997
29604
|
const relative = relativeToCwd(cwd, item);
|
|
28998
29605
|
if (isIgnoredPath(relative)) return;
|
|
28999
|
-
const info = await
|
|
29606
|
+
const info = await stat7(item);
|
|
29000
29607
|
if (info.isDirectory()) {
|
|
29001
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
29608
|
+
for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
|
|
29002
29609
|
return;
|
|
29003
29610
|
}
|
|
29004
29611
|
if (glob && !matchGlob(glob, relative)) return;
|
|
29005
|
-
const buffer = await
|
|
29612
|
+
const buffer = await readFile13(item);
|
|
29006
29613
|
if (buffer.includes(0)) return;
|
|
29007
29614
|
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
29008
29615
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -29016,8 +29623,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
29016
29623
|
}
|
|
29017
29624
|
|
|
29018
29625
|
// src/tools/glob.ts
|
|
29019
|
-
import { readdir as readdir3, stat as
|
|
29020
|
-
import
|
|
29626
|
+
import { readdir as readdir3, stat as stat8 } from "node:fs/promises";
|
|
29627
|
+
import path27 from "node:path";
|
|
29021
29628
|
var MAX_PATHS = 1e3;
|
|
29022
29629
|
var globTool = {
|
|
29023
29630
|
name: "glob",
|
|
@@ -29049,10 +29656,10 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
29049
29656
|
async function walk2(dir) {
|
|
29050
29657
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
29051
29658
|
for (const entry of entries) {
|
|
29052
|
-
const absolute =
|
|
29659
|
+
const absolute = path27.join(dir, entry.name);
|
|
29053
29660
|
const relative = relativeToCwd(cwd, absolute);
|
|
29054
29661
|
if (isIgnoredPath(relative)) continue;
|
|
29055
|
-
const info = await
|
|
29662
|
+
const info = await stat8(absolute);
|
|
29056
29663
|
if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
|
|
29057
29664
|
if (entry.isDirectory()) await walk2(absolute);
|
|
29058
29665
|
}
|
|
@@ -30051,14 +30658,14 @@ async function runExecutionSession(options2) {
|
|
|
30051
30658
|
}
|
|
30052
30659
|
|
|
30053
30660
|
// src/goals/lock.ts
|
|
30054
|
-
import { mkdir as
|
|
30055
|
-
import
|
|
30661
|
+
import { mkdir as mkdir12, open as open3, rm as rm2 } from "node:fs/promises";
|
|
30662
|
+
import path29 from "node:path";
|
|
30056
30663
|
|
|
30057
30664
|
// src/goals/storage.ts
|
|
30058
30665
|
import { createHash as createHash3 } from "node:crypto";
|
|
30059
|
-
import { mkdir as
|
|
30666
|
+
import { mkdir as mkdir11, readFile as readFile14, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
|
|
30060
30667
|
import fs9 from "node:fs";
|
|
30061
|
-
import
|
|
30668
|
+
import path28 from "node:path";
|
|
30062
30669
|
var GoalStore = class {
|
|
30063
30670
|
cwd;
|
|
30064
30671
|
dir;
|
|
@@ -30069,12 +30676,12 @@ var GoalStore = class {
|
|
|
30069
30676
|
this.cwd = cwd;
|
|
30070
30677
|
this.scope = normalizeGoalStoreScope(options2.scope);
|
|
30071
30678
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
30072
|
-
this.activePath =
|
|
30073
|
-
this.archiveDir =
|
|
30679
|
+
this.activePath = path28.join(this.dir, "active.json");
|
|
30680
|
+
this.archiveDir = path28.join(this.dir, "archive");
|
|
30074
30681
|
}
|
|
30075
30682
|
async loadActive() {
|
|
30076
30683
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
30077
|
-
const text = await
|
|
30684
|
+
const text = await readFile14(this.activePath, "utf8");
|
|
30078
30685
|
try {
|
|
30079
30686
|
return JSON.parse(text);
|
|
30080
30687
|
} catch {
|
|
@@ -30083,36 +30690,36 @@ var GoalStore = class {
|
|
|
30083
30690
|
}
|
|
30084
30691
|
}
|
|
30085
30692
|
async saveActive(state) {
|
|
30086
|
-
await
|
|
30087
|
-
const tmp =
|
|
30088
|
-
await
|
|
30693
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30694
|
+
const tmp = path28.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
30695
|
+
await writeFile11(tmp, `${JSON.stringify(state, null, 2)}
|
|
30089
30696
|
`, "utf8");
|
|
30090
|
-
await
|
|
30697
|
+
await rename5(tmp, this.activePath);
|
|
30091
30698
|
}
|
|
30092
30699
|
async archiveActive(status = "cleared") {
|
|
30093
30700
|
const state = await this.loadActive();
|
|
30094
30701
|
if (!state) return void 0;
|
|
30095
|
-
await
|
|
30702
|
+
await mkdir11(this.archiveDir, { recursive: true });
|
|
30096
30703
|
const archived = {
|
|
30097
30704
|
...state,
|
|
30098
30705
|
status,
|
|
30099
30706
|
updatedAt: Date.now()
|
|
30100
30707
|
};
|
|
30101
|
-
await
|
|
30708
|
+
await writeFile11(path28.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
30102
30709
|
`, "utf8");
|
|
30103
30710
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
30104
30711
|
return archived;
|
|
30105
30712
|
}
|
|
30106
30713
|
async backupCorrupted(text) {
|
|
30107
|
-
await
|
|
30108
|
-
const backup =
|
|
30109
|
-
await
|
|
30714
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30715
|
+
const backup = path28.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
30716
|
+
await writeFile11(backup, text, "utf8");
|
|
30110
30717
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
30111
30718
|
}
|
|
30112
30719
|
};
|
|
30113
30720
|
function goalStoreDir(cwd, scope) {
|
|
30114
30721
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
30115
|
-
return safeScope ?
|
|
30722
|
+
return safeScope ? path28.join(cwd, ".demian", "goals", "sessions", safeScope) : path28.join(cwd, ".demian", "goals");
|
|
30116
30723
|
}
|
|
30117
30724
|
function normalizeGoalStoreScope(scope) {
|
|
30118
30725
|
const trimmed = scope?.trim();
|
|
@@ -30126,10 +30733,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
30126
30733
|
var GoalLock = class {
|
|
30127
30734
|
path;
|
|
30128
30735
|
constructor(cwd, scope) {
|
|
30129
|
-
this.path =
|
|
30736
|
+
this.path = path29.join(goalStoreDir(cwd, scope), "active.lock");
|
|
30130
30737
|
}
|
|
30131
30738
|
async acquire() {
|
|
30132
|
-
await
|
|
30739
|
+
await mkdir12(path29.dirname(this.path), { recursive: true });
|
|
30133
30740
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
30134
30741
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
30135
30742
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -30717,8 +31324,8 @@ function sha256(value) {
|
|
|
30717
31324
|
|
|
30718
31325
|
// src/root-session.ts
|
|
30719
31326
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30720
|
-
import
|
|
30721
|
-
import
|
|
31327
|
+
import os12 from "node:os";
|
|
31328
|
+
import path30 from "node:path";
|
|
30722
31329
|
|
|
30723
31330
|
// src/tools/cowork.ts
|
|
30724
31331
|
function createCoworkTool(options2) {
|
|
@@ -31541,8 +32148,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31541
32148
|
}
|
|
31542
32149
|
}
|
|
31543
32150
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
31544
|
-
const root = await mkdtemp(
|
|
31545
|
-
const cwd =
|
|
32151
|
+
const root = await mkdtemp(path30.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
32152
|
+
const cwd = path30.join(root, "workspace");
|
|
31546
32153
|
await cp(this.#options.cwd, cwd, {
|
|
31547
32154
|
recursive: true,
|
|
31548
32155
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -31897,7 +32504,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
31897
32504
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
31898
32505
|
}
|
|
31899
32506
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
31900
|
-
const relative =
|
|
32507
|
+
const relative = path30.relative(root, source).split(path30.sep).join("/");
|
|
31901
32508
|
if (!relative) return true;
|
|
31902
32509
|
const parts = relative.split("/");
|
|
31903
32510
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -32109,13 +32716,13 @@ function finitePositiveInteger(value) {
|
|
|
32109
32716
|
}
|
|
32110
32717
|
|
|
32111
32718
|
// src/ui/preferences.ts
|
|
32112
|
-
import { mkdir as
|
|
32719
|
+
import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
32113
32720
|
import fs10 from "node:fs";
|
|
32114
|
-
import
|
|
32721
|
+
import path31 from "node:path";
|
|
32115
32722
|
var UiPreferenceStore = class {
|
|
32116
32723
|
filePath;
|
|
32117
32724
|
constructor(cwd, filePath) {
|
|
32118
|
-
this.filePath = filePath ?
|
|
32725
|
+
this.filePath = filePath ? path31.resolve(cwd, filePath) : path31.join(cwd, ".demian", "preferences.json");
|
|
32119
32726
|
}
|
|
32120
32727
|
async load() {
|
|
32121
32728
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -32133,13 +32740,13 @@ var UiPreferenceStore = class {
|
|
|
32133
32740
|
model: selection.model,
|
|
32134
32741
|
updatedAt: Date.now()
|
|
32135
32742
|
};
|
|
32136
|
-
await
|
|
32137
|
-
await
|
|
32743
|
+
await mkdir13(path31.dirname(this.filePath), { recursive: true });
|
|
32744
|
+
await writeFile12(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
32138
32745
|
`, { mode: 384 });
|
|
32139
32746
|
}
|
|
32140
32747
|
async #read() {
|
|
32141
32748
|
try {
|
|
32142
|
-
return JSON.parse(await
|
|
32749
|
+
return JSON.parse(await readFile15(this.filePath, "utf8"));
|
|
32143
32750
|
} catch {
|
|
32144
32751
|
return void 0;
|
|
32145
32752
|
}
|
|
@@ -32155,9 +32762,9 @@ function preferenceKey(selection) {
|
|
|
32155
32762
|
|
|
32156
32763
|
// src/models/catalog.ts
|
|
32157
32764
|
import crypto6 from "node:crypto";
|
|
32158
|
-
import { mkdir as
|
|
32159
|
-
import
|
|
32160
|
-
import
|
|
32765
|
+
import { mkdir as mkdir14, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
32766
|
+
import os13 from "node:os";
|
|
32767
|
+
import path32 from "node:path";
|
|
32161
32768
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
32162
32769
|
var PING_TIMEOUT_MS = 1500;
|
|
32163
32770
|
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
@@ -32469,12 +33076,12 @@ async function fetchJson2(url, options2) {
|
|
|
32469
33076
|
return text ? JSON.parse(text) : {};
|
|
32470
33077
|
}
|
|
32471
33078
|
function cachePath(cacheKey) {
|
|
32472
|
-
return
|
|
33079
|
+
return path32.join(os13.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
32473
33080
|
}
|
|
32474
33081
|
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
32475
33082
|
if (!allow) return void 0;
|
|
32476
33083
|
try {
|
|
32477
|
-
const raw = JSON.parse(await
|
|
33084
|
+
const raw = JSON.parse(await readFile16(cachePath(cacheKey), "utf8"));
|
|
32478
33085
|
if (!raw.result) return void 0;
|
|
32479
33086
|
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
32480
33087
|
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
@@ -32484,10 +33091,10 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
|
32484
33091
|
}
|
|
32485
33092
|
async function writeCatalogCache(cacheKey, result) {
|
|
32486
33093
|
const filePath = cachePath(cacheKey);
|
|
32487
|
-
await
|
|
33094
|
+
await mkdir14(path32.dirname(filePath), { recursive: true, mode: 448 });
|
|
32488
33095
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32489
|
-
await
|
|
32490
|
-
await
|
|
33096
|
+
await writeFile13(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
33097
|
+
await rename6(temp, filePath);
|
|
32491
33098
|
}
|
|
32492
33099
|
function safeCacheName(providerName) {
|
|
32493
33100
|
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
@@ -32538,7 +33145,7 @@ async function codexClientVersion(override) {
|
|
|
32538
33145
|
}
|
|
32539
33146
|
async function readPackageVersion() {
|
|
32540
33147
|
try {
|
|
32541
|
-
const raw = JSON.parse(await
|
|
33148
|
+
const raw = JSON.parse(await readFile16(new URL("../package.json", import.meta.url), "utf8"));
|
|
32542
33149
|
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
32543
33150
|
} catch {
|
|
32544
33151
|
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
@@ -32602,6 +33209,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
32602
33209
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
32603
33210
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
32604
33211
|
available,
|
|
33212
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
33213
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
33214
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
32605
33215
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
32606
33216
|
};
|
|
32607
33217
|
}).sort((left, right) => {
|
|
@@ -32673,13 +33283,20 @@ function providerCatalogAvailable(catalog) {
|
|
|
32673
33283
|
function unavailableLabel(value) {
|
|
32674
33284
|
return `(n/a) ${value}`;
|
|
32675
33285
|
}
|
|
33286
|
+
function sanitizeProviderAuth(auth) {
|
|
33287
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
33288
|
+
const record = auth;
|
|
33289
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
33290
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
33291
|
+
return type || header ? { type, header } : void 0;
|
|
33292
|
+
}
|
|
32676
33293
|
function errorMessage3(error) {
|
|
32677
33294
|
return error instanceof Error ? error.message : String(error);
|
|
32678
33295
|
}
|
|
32679
33296
|
|
|
32680
33297
|
// src/ui/conversations.ts
|
|
32681
|
-
import { mkdir as
|
|
32682
|
-
import
|
|
33298
|
+
import { mkdir as mkdir15, readFile as readFile17, readdir as readdir4, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
|
|
33299
|
+
import path33 from "node:path";
|
|
32683
33300
|
var CONVERSATIONS_DIR = "conversations";
|
|
32684
33301
|
var CONVERSATION_INDEX_FILE = "conversations.json";
|
|
32685
33302
|
var CONVERSATION_FILE = "conversation.json";
|
|
@@ -32692,13 +33309,13 @@ function createConversationRecord(input2) {
|
|
|
32692
33309
|
title: input2.title?.trim() || "New session",
|
|
32693
33310
|
createdAt: timestamp,
|
|
32694
33311
|
updatedAt: timestamp,
|
|
32695
|
-
cwd:
|
|
33312
|
+
cwd: path33.resolve(input2.cwd),
|
|
32696
33313
|
modelHistory: cleanModelHistory(input2.history ?? []),
|
|
32697
33314
|
snapshot: input2.snapshot
|
|
32698
33315
|
};
|
|
32699
33316
|
}
|
|
32700
33317
|
async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
|
|
32701
|
-
const index = await readJson(
|
|
33318
|
+
const index = await readJson(path33.join(storageDir, CONVERSATION_INDEX_FILE));
|
|
32702
33319
|
const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
|
|
32703
33320
|
if (!item || typeof item !== "object") return void 0;
|
|
32704
33321
|
const object2 = item;
|
|
@@ -32732,7 +33349,7 @@ async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
|
|
|
32732
33349
|
async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
32733
33350
|
const root = storageDir ?? defaultDemianStorageDir();
|
|
32734
33351
|
const kept = [...records].sort((a, b2) => b2.updatedAt - a.updatedAt).slice(0, MAX_STORED_CONVERSATIONS).map((record) => ({ ...record, id: sanitizeConversationId(record.id), modelHistory: cleanModelHistory(record.modelHistory) }));
|
|
32735
|
-
await
|
|
33352
|
+
await mkdir15(path33.join(root, CONVERSATIONS_DIR), { recursive: true });
|
|
32736
33353
|
for (const record of kept) {
|
|
32737
33354
|
await writeJson(conversationRecordPath(root, record.id), {
|
|
32738
33355
|
version: 1,
|
|
@@ -32745,7 +33362,7 @@ async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
|
32745
33362
|
snapshot: record.snapshot
|
|
32746
33363
|
});
|
|
32747
33364
|
}
|
|
32748
|
-
await writeJson(
|
|
33365
|
+
await writeJson(path33.join(root, CONVERSATION_INDEX_FILE), {
|
|
32749
33366
|
version: 1,
|
|
32750
33367
|
selectedSessionId,
|
|
32751
33368
|
conversations: kept.map((record) => ({
|
|
@@ -32770,7 +33387,7 @@ function findConversation(records, target) {
|
|
|
32770
33387
|
return records.find((record) => record.id === value || record.id.startsWith(value)) ?? records.find((record) => record.title.toLowerCase() === normalized) ?? records.find((record) => record.title.toLowerCase().includes(normalized));
|
|
32771
33388
|
}
|
|
32772
33389
|
function sortConversationRecords(records, cwd) {
|
|
32773
|
-
const resolvedCwd =
|
|
33390
|
+
const resolvedCwd = path33.resolve(cwd);
|
|
32774
33391
|
return [...records].sort((a, b2) => {
|
|
32775
33392
|
const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
|
|
32776
33393
|
const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
|
|
@@ -32785,7 +33402,7 @@ function conversationRuntimeStatus(record, cwd) {
|
|
|
32785
33402
|
return isConversationForCwd(record, cwd) ? "ready" : "stored";
|
|
32786
33403
|
}
|
|
32787
33404
|
function isConversationForCwd(record, cwd) {
|
|
32788
|
-
return
|
|
33405
|
+
return path33.resolve(record.cwd) === path33.resolve(cwd);
|
|
32789
33406
|
}
|
|
32790
33407
|
function conversationSummaryLine(record, index, activeId) {
|
|
32791
33408
|
const active = record.id === activeId ? "*" : " ";
|
|
@@ -32805,14 +33422,14 @@ function cleanModelHistory(history) {
|
|
|
32805
33422
|
return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
|
|
32806
33423
|
}
|
|
32807
33424
|
function conversationDir(storageDir, id) {
|
|
32808
|
-
return
|
|
33425
|
+
return path33.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
|
|
32809
33426
|
}
|
|
32810
33427
|
function conversationRecordPath(storageDir, id) {
|
|
32811
|
-
return
|
|
33428
|
+
return path33.join(conversationDir(storageDir, id), CONVERSATION_FILE);
|
|
32812
33429
|
}
|
|
32813
33430
|
async function listConversationRecordIds(storageDir) {
|
|
32814
33431
|
try {
|
|
32815
|
-
const entries = await readdir4(
|
|
33432
|
+
const entries = await readdir4(path33.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
|
|
32816
33433
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
|
|
32817
33434
|
} catch {
|
|
32818
33435
|
return [];
|
|
@@ -32820,14 +33437,14 @@ async function listConversationRecordIds(storageDir) {
|
|
|
32820
33437
|
}
|
|
32821
33438
|
async function readJson(filePath) {
|
|
32822
33439
|
try {
|
|
32823
|
-
return JSON.parse(await
|
|
33440
|
+
return JSON.parse(await readFile17(filePath, "utf8"));
|
|
32824
33441
|
} catch {
|
|
32825
33442
|
return void 0;
|
|
32826
33443
|
}
|
|
32827
33444
|
}
|
|
32828
33445
|
async function writeJson(filePath, value) {
|
|
32829
|
-
await
|
|
32830
|
-
await
|
|
33446
|
+
await mkdir15(path33.dirname(filePath), { recursive: true });
|
|
33447
|
+
await writeFile14(filePath, `${JSON.stringify(value, null, 2)}
|
|
32831
33448
|
`, "utf8");
|
|
32832
33449
|
}
|
|
32833
33450
|
function normalizeConversationRecord(value, fallbackId) {
|
|
@@ -32882,11 +33499,12 @@ function formatRelativeAge(ms2) {
|
|
|
32882
33499
|
|
|
32883
33500
|
// src/ui/tui/controller.ts
|
|
32884
33501
|
async function runTuiSession(flags, store2) {
|
|
32885
|
-
const cwd =
|
|
33502
|
+
const cwd = path34.resolve(flags.cwd ?? process.cwd());
|
|
32886
33503
|
const eventBus = new EventBus();
|
|
32887
33504
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
32888
|
-
const
|
|
32889
|
-
|
|
33505
|
+
const configInit = await ensureTuiConfigExists(flags);
|
|
33506
|
+
let loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
33507
|
+
let config = flags.context ? mergeConfig(loadedConfig2, {
|
|
32890
33508
|
context: {
|
|
32891
33509
|
main: flags.context
|
|
32892
33510
|
}
|
|
@@ -32906,6 +33524,10 @@ async function runTuiSession(flags, store2) {
|
|
|
32906
33524
|
store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
32907
33525
|
agent: agentName,
|
|
32908
33526
|
cwd,
|
|
33527
|
+
configPath: configMutationPath(flags),
|
|
33528
|
+
configCreated: configInit.created,
|
|
33529
|
+
configDefaultProvider: config.defaultProvider,
|
|
33530
|
+
configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
|
|
32909
33531
|
agentOptions,
|
|
32910
33532
|
contextEfficiency: {
|
|
32911
33533
|
maxContextTokens: config.context.main.maxContextTokens,
|
|
@@ -32914,6 +33536,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32914
33536
|
}
|
|
32915
33537
|
});
|
|
32916
33538
|
let nextPrompt = flags.prompt;
|
|
33539
|
+
let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
|
|
32917
33540
|
const conversationsEnabled = flags.conversationManagement === true;
|
|
32918
33541
|
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
32919
33542
|
let currentConversation = conversationState?.current;
|
|
@@ -32954,13 +33577,28 @@ async function runTuiSession(flags, store2) {
|
|
|
32954
33577
|
await applyStartupSessionSelection(selected);
|
|
32955
33578
|
}
|
|
32956
33579
|
for (; ; ) {
|
|
32957
|
-
const
|
|
33580
|
+
const promptPromise = store2.requestPrompt(nextPrompt);
|
|
33581
|
+
if (openConfigOnFirstPrompt) {
|
|
33582
|
+
openConfigOnFirstPrompt = false;
|
|
33583
|
+
store2.openConfigManager(`Created config at ${configInit.path}`);
|
|
33584
|
+
}
|
|
33585
|
+
const prompt = await promptPromise;
|
|
32958
33586
|
nextPrompt = void 0;
|
|
32959
33587
|
if (store2.exitRequested()) {
|
|
32960
33588
|
await saveCurrentSelection();
|
|
32961
33589
|
return 0;
|
|
32962
33590
|
}
|
|
32963
33591
|
if (!prompt) return 0;
|
|
33592
|
+
const configAction = parseTuiConfigAction(prompt);
|
|
33593
|
+
if (configAction) {
|
|
33594
|
+
await handleConfigAction(configAction);
|
|
33595
|
+
continue;
|
|
33596
|
+
}
|
|
33597
|
+
const sessionAction = parseTuiSessionAction(prompt);
|
|
33598
|
+
if (sessionAction) {
|
|
33599
|
+
await handleSessionSelectionShortcut();
|
|
33600
|
+
continue;
|
|
33601
|
+
}
|
|
32964
33602
|
const sessionCommand = parseSessionCommand(prompt);
|
|
32965
33603
|
if (sessionCommand) {
|
|
32966
33604
|
await handleSessionCommand(sessionCommand);
|
|
@@ -33237,6 +33875,25 @@ ${sessionListMessage()}`);
|
|
|
33237
33875
|
await persistCurrentConversation();
|
|
33238
33876
|
}
|
|
33239
33877
|
}
|
|
33878
|
+
async function handleSessionSelectionShortcut() {
|
|
33879
|
+
if (!conversationsEnabled) {
|
|
33880
|
+
store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
33881
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33882
|
+
return;
|
|
33883
|
+
}
|
|
33884
|
+
if (!currentConversation) {
|
|
33885
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
33886
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33887
|
+
}
|
|
33888
|
+
await persistCurrentConversation();
|
|
33889
|
+
const selection = await store2.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
|
|
33890
|
+
if (selection.kind === "cancel") {
|
|
33891
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33892
|
+
return;
|
|
33893
|
+
}
|
|
33894
|
+
if (selection.kind === "exit" || store2.exitRequested()) return;
|
|
33895
|
+
await applyStartupSessionSelection(selection);
|
|
33896
|
+
}
|
|
33240
33897
|
function startupSessionOptions() {
|
|
33241
33898
|
const records = sortedConversations();
|
|
33242
33899
|
const sessionOptions = records.map((record) => ({
|
|
@@ -33246,11 +33903,15 @@ ${sessionListMessage()}`);
|
|
|
33246
33903
|
cwd: record.cwd,
|
|
33247
33904
|
status: conversationRuntimeStatus(record, cwd),
|
|
33248
33905
|
updatedAt: record.updatedAt,
|
|
33249
|
-
currentWorkspace:
|
|
33906
|
+
currentWorkspace: path34.resolve(record.cwd) === cwd
|
|
33250
33907
|
}));
|
|
33251
33908
|
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
33252
33909
|
}
|
|
33253
33910
|
async function applyStartupSessionSelection(selection) {
|
|
33911
|
+
if (selection.kind === "cancel") {
|
|
33912
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33913
|
+
return;
|
|
33914
|
+
}
|
|
33254
33915
|
if (selection.kind === "new") {
|
|
33255
33916
|
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33256
33917
|
conversationRecords.set(next.id, next);
|
|
@@ -33307,6 +33968,87 @@ ${sessionListMessage()}`);
|
|
|
33307
33968
|
await preferenceStore.save(selection);
|
|
33308
33969
|
savedSelectionKey = key;
|
|
33309
33970
|
}
|
|
33971
|
+
async function handleConfigAction(action) {
|
|
33972
|
+
const path37 = configMutationPath(flags);
|
|
33973
|
+
try {
|
|
33974
|
+
let message = "Config updated.";
|
|
33975
|
+
let nextSelection = {};
|
|
33976
|
+
if (action.type === "setDefaultProvider") {
|
|
33977
|
+
await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
|
|
33978
|
+
message = `Default provider saved: ${action.provider}`;
|
|
33979
|
+
nextSelection = { provider: action.provider };
|
|
33980
|
+
} else if (action.type === "setDefaultModel") {
|
|
33981
|
+
await setDefaultModelProfile({
|
|
33982
|
+
path: path37,
|
|
33983
|
+
provider: action.provider,
|
|
33984
|
+
profileName: action.profileName,
|
|
33985
|
+
name: action.name,
|
|
33986
|
+
displayName: action.displayName,
|
|
33987
|
+
model: action.model
|
|
33988
|
+
});
|
|
33989
|
+
message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
|
|
33990
|
+
nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
|
|
33991
|
+
} else if (action.type === "addProvider") {
|
|
33992
|
+
await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
|
|
33993
|
+
message = `Provider added: ${action.name}`;
|
|
33994
|
+
nextSelection = { provider: action.name };
|
|
33995
|
+
} else if (action.type === "updateProvider") {
|
|
33996
|
+
await updateProviderSettings({
|
|
33997
|
+
path: path37,
|
|
33998
|
+
provider: action.provider,
|
|
33999
|
+
...action.field === "baseURL" ? { baseURL: action.value } : {},
|
|
34000
|
+
...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
|
|
34001
|
+
...action.field === "authHeader" ? { authHeader: action.value } : {}
|
|
34002
|
+
});
|
|
34003
|
+
message = `Provider ${action.field} saved: ${action.provider}`;
|
|
34004
|
+
nextSelection = { provider: action.provider };
|
|
34005
|
+
}
|
|
34006
|
+
await reloadConfigSettings(message, nextSelection);
|
|
34007
|
+
} catch (error) {
|
|
34008
|
+
store2.prepareForPrompt("config action failed");
|
|
34009
|
+
store2.showSystemMessage("Config", errorMessage(error));
|
|
34010
|
+
store2.openConfigManager(errorMessage(error));
|
|
34011
|
+
}
|
|
34012
|
+
}
|
|
34013
|
+
async function reloadConfigSettings(message, selectionFlags = {}) {
|
|
34014
|
+
loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
34015
|
+
config = flags.context ? mergeConfig(loadedConfig2, {
|
|
34016
|
+
context: {
|
|
34017
|
+
main: flags.context
|
|
34018
|
+
}
|
|
34019
|
+
}) : loadedConfig2;
|
|
34020
|
+
const currentSelection = store2.currentSelection();
|
|
34021
|
+
const selection = createInteractiveModelSelection(
|
|
34022
|
+
config,
|
|
34023
|
+
selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
|
|
34024
|
+
);
|
|
34025
|
+
store2.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
34026
|
+
agent: store2.currentAgentName(),
|
|
34027
|
+
cwd,
|
|
34028
|
+
configPath: configMutationPath(flags),
|
|
34029
|
+
configDefaultProvider: config.defaultProvider,
|
|
34030
|
+
configMessage: message,
|
|
34031
|
+
agentOptions,
|
|
34032
|
+
contextEfficiency: {
|
|
34033
|
+
maxContextTokens: config.context.main.maxContextTokens,
|
|
34034
|
+
compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
|
|
34035
|
+
compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
|
|
34036
|
+
}
|
|
34037
|
+
});
|
|
34038
|
+
store2.openConfigManager(message);
|
|
34039
|
+
}
|
|
34040
|
+
}
|
|
34041
|
+
async function ensureTuiConfigExists(flags) {
|
|
34042
|
+
const target = configMutationPath(flags);
|
|
34043
|
+
if (!flags.configPath) {
|
|
34044
|
+
const jsondPath = target.replace(/\.json$/i, ".jsond");
|
|
34045
|
+
if (existsSync4(target) || existsSync4(jsondPath)) return { path: target, created: false };
|
|
34046
|
+
}
|
|
34047
|
+
const result = await createUserConfig({ path: target });
|
|
34048
|
+
return { path: result.path, created: result.created };
|
|
34049
|
+
}
|
|
34050
|
+
function configMutationPath(flags) {
|
|
34051
|
+
return flags.configPath ? path34.resolve(flags.configPath) : defaultUserConfigPath();
|
|
33310
34052
|
}
|
|
33311
34053
|
function isGoalStateClearingAction(action) {
|
|
33312
34054
|
return action === "status" || action === "pause" || action === "resume";
|
|
@@ -33344,289 +34086,6 @@ function sessionUsage() {
|
|
|
33344
34086
|
import { EventEmitter } from "node:events";
|
|
33345
34087
|
import fs11 from "node:fs";
|
|
33346
34088
|
import path35 from "node:path";
|
|
33347
|
-
|
|
33348
|
-
// src/config-scaffold.ts
|
|
33349
|
-
import { chmod as chmod3, mkdir as mkdir15, readFile as readFile17, rename as rename6, stat as stat8, writeFile as writeFile14 } from "node:fs/promises";
|
|
33350
|
-
import os13 from "node:os";
|
|
33351
|
-
import path34 from "node:path";
|
|
33352
|
-
function defaultUserConfigPath() {
|
|
33353
|
-
return path34.join(os13.homedir(), ".demian", "config.json");
|
|
33354
|
-
}
|
|
33355
|
-
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
33356
|
-
return {
|
|
33357
|
-
version: 2,
|
|
33358
|
-
defaultProvider,
|
|
33359
|
-
providers: {
|
|
33360
|
-
openai: {
|
|
33361
|
-
type: "openai-compatible",
|
|
33362
|
-
baseURL: "https://api.openai.com/v1",
|
|
33363
|
-
apiKey: "",
|
|
33364
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
33365
|
-
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
33366
|
-
},
|
|
33367
|
-
anthropic: {
|
|
33368
|
-
type: "anthropic",
|
|
33369
|
-
baseURL: "https://api.anthropic.com/v1",
|
|
33370
|
-
apiKey: "",
|
|
33371
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
33372
|
-
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
33373
|
-
},
|
|
33374
|
-
gemini: {
|
|
33375
|
-
type: "openai-compatible",
|
|
33376
|
-
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
33377
|
-
apiKey: "",
|
|
33378
|
-
apiKeyEnv: "GEMINI_API_KEY",
|
|
33379
|
-
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
33380
|
-
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
33381
|
-
},
|
|
33382
|
-
groq: {
|
|
33383
|
-
type: "openai-compatible",
|
|
33384
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
33385
|
-
apiKey: "",
|
|
33386
|
-
apiKeyEnv: "GROQ_API_KEY",
|
|
33387
|
-
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
33388
|
-
},
|
|
33389
|
-
azure: {
|
|
33390
|
-
type: "openai-compatible",
|
|
33391
|
-
auth: { type: "api-key", header: "api-key" },
|
|
33392
|
-
modelProfiles: [
|
|
33393
|
-
{
|
|
33394
|
-
name: "azure-example",
|
|
33395
|
-
displayName: "Azure example",
|
|
33396
|
-
model: "azure-deployment-name",
|
|
33397
|
-
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
33398
|
-
apiKey: "",
|
|
33399
|
-
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
33400
|
-
}
|
|
33401
|
-
]
|
|
33402
|
-
},
|
|
33403
|
-
lmstudio: {
|
|
33404
|
-
type: "openai-compatible",
|
|
33405
|
-
baseURL: "http://localhost:1234/v1",
|
|
33406
|
-
apiKey: "lm-studio",
|
|
33407
|
-
modelProfiles: [],
|
|
33408
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
33409
|
-
},
|
|
33410
|
-
"ollama-local": {
|
|
33411
|
-
type: "openai-compatible",
|
|
33412
|
-
baseURL: "http://localhost:11434/v1",
|
|
33413
|
-
apiKey: "ollama",
|
|
33414
|
-
modelProfiles: [],
|
|
33415
|
-
quirks: { omitTemperature: true },
|
|
33416
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
33417
|
-
},
|
|
33418
|
-
"ollama-cloud": {
|
|
33419
|
-
type: "ollama",
|
|
33420
|
-
baseURL: "https://ollama.com/api",
|
|
33421
|
-
apiKey: "",
|
|
33422
|
-
apiKeyEnv: "OLLAMA_API_KEY",
|
|
33423
|
-
modelProfiles: [],
|
|
33424
|
-
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
33425
|
-
},
|
|
33426
|
-
llamacpp: {
|
|
33427
|
-
type: "openai-compatible",
|
|
33428
|
-
baseURL: "http://localhost:8080/v1",
|
|
33429
|
-
apiKey: "llama.cpp",
|
|
33430
|
-
modelProfiles: [],
|
|
33431
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
33432
|
-
},
|
|
33433
|
-
vllm: {
|
|
33434
|
-
type: "openai-compatible",
|
|
33435
|
-
baseURL: "http://localhost:8000/v1",
|
|
33436
|
-
apiKey: "vllm",
|
|
33437
|
-
modelProfiles: [],
|
|
33438
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
33439
|
-
},
|
|
33440
|
-
codex: {
|
|
33441
|
-
type: "codex",
|
|
33442
|
-
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
33443
|
-
authStore: "auto",
|
|
33444
|
-
allowApiKeyFallback: false,
|
|
33445
|
-
promptCacheKey: "root-session",
|
|
33446
|
-
catalog: { type: "codex-oauth-models" }
|
|
33447
|
-
},
|
|
33448
|
-
claudecode: {
|
|
33449
|
-
type: "claudecode",
|
|
33450
|
-
runtime: "agent-sdk",
|
|
33451
|
-
cliPath: "~/.local/bin/claude",
|
|
33452
|
-
cwdMode: "session",
|
|
33453
|
-
historyPolicy: "passthrough-resume",
|
|
33454
|
-
onInvalidResume: "fresh",
|
|
33455
|
-
attachmentFallback: "block",
|
|
33456
|
-
allowSubagents: false,
|
|
33457
|
-
sanitizeApiKeyEnv: true,
|
|
33458
|
-
authPreflight: true,
|
|
33459
|
-
useBareMode: false,
|
|
33460
|
-
usageLedgerScope: "process",
|
|
33461
|
-
sessionLock: true,
|
|
33462
|
-
abortPolicy: "record-only",
|
|
33463
|
-
catalog: { type: "claudecode-supported-models" }
|
|
33464
|
-
}
|
|
33465
|
-
}
|
|
33466
|
-
};
|
|
33467
|
-
}
|
|
33468
|
-
async function createUserConfig(options2 = {}) {
|
|
33469
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33470
|
-
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
33471
|
-
`;
|
|
33472
|
-
if (options2.print) return { path: filePath, created: false, content };
|
|
33473
|
-
const existed = await exists(filePath);
|
|
33474
|
-
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile17(filePath, "utf8"), existed: true };
|
|
33475
|
-
await writeJsonAtomic(filePath, content);
|
|
33476
|
-
return { path: filePath, created: true, content, existed };
|
|
33477
|
-
}
|
|
33478
|
-
async function addProvider(options2) {
|
|
33479
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33480
|
-
const config = await readConfigObject(filePath);
|
|
33481
|
-
const providers = objectValue(config.providers);
|
|
33482
|
-
const name = options2.name;
|
|
33483
|
-
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
33484
|
-
providers[name] = providerPreset(options2);
|
|
33485
|
-
config.providers = providers;
|
|
33486
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33487
|
-
`;
|
|
33488
|
-
await writeJsonAtomic(filePath, content);
|
|
33489
|
-
return { path: filePath, created: true, content };
|
|
33490
|
-
}
|
|
33491
|
-
async function addModelProfile(options2) {
|
|
33492
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33493
|
-
const config = await readConfigObject(filePath);
|
|
33494
|
-
const providers = objectValue(config.providers);
|
|
33495
|
-
const provider = objectValue(providers[options2.provider]);
|
|
33496
|
-
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
33497
|
-
const displayName = options2.displayName ?? options2.name;
|
|
33498
|
-
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
33499
|
-
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
33500
|
-
if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
33501
|
-
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
|
|
33502
|
-
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.`);
|
|
33503
|
-
}
|
|
33504
|
-
const next = {
|
|
33505
|
-
name: options2.name,
|
|
33506
|
-
displayName,
|
|
33507
|
-
model: options2.model,
|
|
33508
|
-
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
33509
|
-
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
33510
|
-
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
33511
|
-
};
|
|
33512
|
-
if (existingByName >= 0) profiles[existingByName] = next;
|
|
33513
|
-
else profiles.push(next);
|
|
33514
|
-
provider.modelProfiles = profiles;
|
|
33515
|
-
providers[options2.provider] = provider;
|
|
33516
|
-
config.providers = providers;
|
|
33517
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33518
|
-
`;
|
|
33519
|
-
await writeJsonAtomic(filePath, content);
|
|
33520
|
-
return { path: filePath, created: true, content };
|
|
33521
|
-
}
|
|
33522
|
-
async function updateConfigDefaults(options2) {
|
|
33523
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33524
|
-
const config = await readConfigObject(filePath);
|
|
33525
|
-
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
33526
|
-
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
33527
|
-
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
33528
|
-
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
33529
|
-
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
33530
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33531
|
-
`;
|
|
33532
|
-
await writeJsonAtomic(filePath, content);
|
|
33533
|
-
return { path: filePath, created: true, content };
|
|
33534
|
-
}
|
|
33535
|
-
async function readConfigObject(filePath) {
|
|
33536
|
-
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
33537
|
-
const raw = JSON.parse(await readFile17(filePath, "utf8"));
|
|
33538
|
-
raw.version ??= 2;
|
|
33539
|
-
raw.providers = objectValue(raw.providers);
|
|
33540
|
-
return raw;
|
|
33541
|
-
}
|
|
33542
|
-
function providerPreset(options2) {
|
|
33543
|
-
const preset = options2.preset ?? options2.name;
|
|
33544
|
-
const auth = apiKeyAuthFields(options2);
|
|
33545
|
-
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
33546
|
-
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` } };
|
|
33547
|
-
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` } };
|
|
33548
|
-
if (preset === "gemini") {
|
|
33549
|
-
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
33550
|
-
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` } };
|
|
33551
|
-
}
|
|
33552
|
-
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` } };
|
|
33553
|
-
if (preset === "azure") {
|
|
33554
|
-
return {
|
|
33555
|
-
type: "openai-compatible",
|
|
33556
|
-
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
33557
|
-
...auth,
|
|
33558
|
-
modelProfiles: [
|
|
33559
|
-
{
|
|
33560
|
-
name: "azure-example",
|
|
33561
|
-
displayName: "Azure example",
|
|
33562
|
-
model: "azure-deployment-name",
|
|
33563
|
-
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
33564
|
-
...options2.apiKey ? {} : { apiKey: "" },
|
|
33565
|
-
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
33566
|
-
}
|
|
33567
|
-
]
|
|
33568
|
-
};
|
|
33569
|
-
}
|
|
33570
|
-
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` } };
|
|
33571
|
-
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` } };
|
|
33572
|
-
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` } };
|
|
33573
|
-
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` } };
|
|
33574
|
-
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` } };
|
|
33575
|
-
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" } };
|
|
33576
|
-
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" } };
|
|
33577
|
-
if ((options2.type ?? preset) === "openai-compatible") {
|
|
33578
|
-
const baseURL = options2.baseURL;
|
|
33579
|
-
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
33580
|
-
return {
|
|
33581
|
-
type: "openai-compatible",
|
|
33582
|
-
baseURL,
|
|
33583
|
-
...auth,
|
|
33584
|
-
...openAIAuth,
|
|
33585
|
-
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
33586
|
-
};
|
|
33587
|
-
}
|
|
33588
|
-
throw new Error(`Unknown provider preset: ${preset}`);
|
|
33589
|
-
}
|
|
33590
|
-
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
33591
|
-
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
33592
|
-
return {
|
|
33593
|
-
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
33594
|
-
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
33595
|
-
};
|
|
33596
|
-
}
|
|
33597
|
-
async function writeJsonAtomic(filePath, content) {
|
|
33598
|
-
await mkdir15(path34.dirname(filePath), { recursive: true, mode: 448 });
|
|
33599
|
-
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
33600
|
-
await writeFile14(temp, content, { mode: 384 });
|
|
33601
|
-
await rename6(temp, filePath);
|
|
33602
|
-
await chmod3(filePath, 384).catch(() => void 0);
|
|
33603
|
-
}
|
|
33604
|
-
function detectDefaultProvider() {
|
|
33605
|
-
if (process.env.OPENAI_API_KEY) return "openai";
|
|
33606
|
-
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
33607
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
33608
|
-
if (process.env.GROQ_API_KEY) return "groq";
|
|
33609
|
-
return "openai";
|
|
33610
|
-
}
|
|
33611
|
-
function objectValue(value) {
|
|
33612
|
-
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
33613
|
-
}
|
|
33614
|
-
function normalizeOptionalName(value) {
|
|
33615
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
33616
|
-
}
|
|
33617
|
-
async function exists(filePath) {
|
|
33618
|
-
try {
|
|
33619
|
-
await stat8(filePath);
|
|
33620
|
-
return true;
|
|
33621
|
-
} catch {
|
|
33622
|
-
return false;
|
|
33623
|
-
}
|
|
33624
|
-
}
|
|
33625
|
-
function expandHome2(value) {
|
|
33626
|
-
return resolveExpandedPath(value);
|
|
33627
|
-
}
|
|
33628
|
-
|
|
33629
|
-
// src/config-watcher.ts
|
|
33630
34089
|
var ConfigWatcher = class extends EventEmitter {
|
|
33631
34090
|
#filePath;
|
|
33632
34091
|
#debounceMs;
|
|
@@ -33715,6 +34174,20 @@ if (process.argv.includes("--config-template")) {
|
|
|
33715
34174
|
`);
|
|
33716
34175
|
process.exit(0);
|
|
33717
34176
|
}
|
|
34177
|
+
if (process.argv.includes("--config-read")) {
|
|
34178
|
+
await readConfigMeta(jsonArg("--config-read")).then(
|
|
34179
|
+
(result) => {
|
|
34180
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
|
|
34181
|
+
`);
|
|
34182
|
+
process.exit(0);
|
|
34183
|
+
},
|
|
34184
|
+
(error) => {
|
|
34185
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34186
|
+
`);
|
|
34187
|
+
process.exit(1);
|
|
34188
|
+
}
|
|
34189
|
+
);
|
|
34190
|
+
}
|
|
33718
34191
|
if (process.argv.includes("--config-init")) {
|
|
33719
34192
|
const force = process.argv.includes("--force");
|
|
33720
34193
|
await createUserConfig({ force }).then(
|
|
@@ -33772,6 +34245,34 @@ if (process.argv.includes("--config-set-defaults")) {
|
|
|
33772
34245
|
}
|
|
33773
34246
|
);
|
|
33774
34247
|
}
|
|
34248
|
+
if (process.argv.includes("--config-set-default-model")) {
|
|
34249
|
+
await setDefaultModelProfile(jsonArg("--config-set-default-model")).then(
|
|
34250
|
+
(result) => {
|
|
34251
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34252
|
+
`);
|
|
34253
|
+
process.exit(0);
|
|
34254
|
+
},
|
|
34255
|
+
(error) => {
|
|
34256
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34257
|
+
`);
|
|
34258
|
+
process.exit(1);
|
|
34259
|
+
}
|
|
34260
|
+
);
|
|
34261
|
+
}
|
|
34262
|
+
if (process.argv.includes("--config-update-provider")) {
|
|
34263
|
+
await updateProviderSettings(jsonArg("--config-update-provider")).then(
|
|
34264
|
+
(result) => {
|
|
34265
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34266
|
+
`);
|
|
34267
|
+
process.exit(0);
|
|
34268
|
+
},
|
|
34269
|
+
(error) => {
|
|
34270
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34271
|
+
`);
|
|
34272
|
+
process.exit(1);
|
|
34273
|
+
}
|
|
34274
|
+
);
|
|
34275
|
+
}
|
|
33775
34276
|
if (process.argv.includes("--provider-auth-check")) {
|
|
33776
34277
|
await checkProviderAuth(jsonArg("--provider-auth-check")).then(
|
|
33777
34278
|
(result) => {
|
|
@@ -34346,30 +34847,43 @@ function setApiKey(providerName, apiKey, opts = {}) {
|
|
|
34346
34847
|
}
|
|
34347
34848
|
async function sendConfigMeta() {
|
|
34348
34849
|
if (!loadedConfig) return;
|
|
34850
|
+
send({ type: "configMeta", config: await buildConfigMeta(loadedConfig) });
|
|
34851
|
+
}
|
|
34852
|
+
async function readConfigMeta(options2) {
|
|
34853
|
+
const cwd = typeof options2.cwd === "string" && options2.cwd.trim() ? options2.cwd : process.cwd();
|
|
34854
|
+
const configPath = typeof options2.configPath === "string" && options2.configPath.trim() ? options2.configPath : defaultUserConfigPath();
|
|
34855
|
+
let created = false;
|
|
34856
|
+
if (options2.create && !existsSync5(configPath)) {
|
|
34857
|
+
const result = await createUserConfig({ path: configPath });
|
|
34858
|
+
created = result.created;
|
|
34859
|
+
}
|
|
34860
|
+
const config = await loadConfig({ cwd, configPath: options2.configPath });
|
|
34861
|
+
return { path: configPath, created, config: await buildConfigMeta(config) };
|
|
34862
|
+
}
|
|
34863
|
+
async function buildConfigMeta(config) {
|
|
34349
34864
|
const registry = new AgentRegistry();
|
|
34350
|
-
for (const agentDefinition of Object.values(
|
|
34351
|
-
const providerEntries = sortProviderNames(Object.keys(
|
|
34865
|
+
for (const agentDefinition of Object.values(config.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
|
|
34866
|
+
const providerEntries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
|
|
34352
34867
|
const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
|
|
34353
34868
|
const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
|
|
34354
34869
|
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
34355
34870
|
return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
|
|
34356
34871
|
});
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
34360
|
-
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
34368
|
-
}
|
|
34369
|
-
});
|
|
34872
|
+
return {
|
|
34873
|
+
defaultProvider: config.defaultProvider,
|
|
34874
|
+
defaultAgent: config.defaultAgent,
|
|
34875
|
+
context: {
|
|
34876
|
+
main: config.context?.main
|
|
34877
|
+
},
|
|
34878
|
+
providerOrder: providers.map((provider) => provider.name),
|
|
34879
|
+
providers,
|
|
34880
|
+
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
34881
|
+
};
|
|
34370
34882
|
}
|
|
34371
34883
|
async function providerMeta(name, provider) {
|
|
34372
34884
|
const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
|
|
34885
|
+
const configuredProfiles = configuredModelProfiles(provider);
|
|
34886
|
+
const auth = sanitizeProviderAuth2(provider.auth);
|
|
34373
34887
|
const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
|
|
34374
34888
|
const available = providerCatalogAvailable2(catalog);
|
|
34375
34889
|
const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
|
|
@@ -34402,7 +34916,16 @@ async function providerMeta(name, provider) {
|
|
|
34402
34916
|
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
|
|
34403
34917
|
models,
|
|
34404
34918
|
modelProfiles: labeledProfiles,
|
|
34919
|
+
configuredModelProfiles: configuredProfiles,
|
|
34920
|
+
configured: {
|
|
34921
|
+
baseURL: provider.baseURL,
|
|
34922
|
+
apiKeyEnv,
|
|
34923
|
+
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
34924
|
+
auth,
|
|
34925
|
+
hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0
|
|
34926
|
+
},
|
|
34405
34927
|
baseURL: provider.baseURL,
|
|
34928
|
+
auth,
|
|
34406
34929
|
apiKeyEnv,
|
|
34407
34930
|
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
34408
34931
|
catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
|
|
@@ -34411,6 +34934,26 @@ async function providerMeta(name, provider) {
|
|
|
34411
34934
|
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
|
|
34412
34935
|
};
|
|
34413
34936
|
}
|
|
34937
|
+
function configuredModelProfiles(provider) {
|
|
34938
|
+
if (!Array.isArray(provider.modelProfiles)) return [];
|
|
34939
|
+
return provider.modelProfiles.filter((profile) => profile && typeof profile === "object" && typeof profile.model === "string" && profile.model.trim()).map((profile, index) => {
|
|
34940
|
+
const name = typeof profile.name === "string" && profile.name.trim() ? profile.name.trim() : typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : profile.model.trim();
|
|
34941
|
+
const displayName = typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : name;
|
|
34942
|
+
return {
|
|
34943
|
+
name,
|
|
34944
|
+
displayName,
|
|
34945
|
+
model: profile.model.trim(),
|
|
34946
|
+
description: typeof profile.description === "string" ? profile.description : void 0,
|
|
34947
|
+
baseURL: typeof profile.baseURL === "string" ? profile.baseURL : void 0,
|
|
34948
|
+
apiKeyEnv: typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : void 0,
|
|
34949
|
+
apiKeyEnvAliases: asStringArray(profile.apiKeyEnvAliases),
|
|
34950
|
+
auth: sanitizeProviderAuth2(profile.auth),
|
|
34951
|
+
hasInlineApiKey: typeof profile.apiKey === "string" && profile.apiKey.length > 0,
|
|
34952
|
+
source: "config",
|
|
34953
|
+
isDefault: index === 0
|
|
34954
|
+
};
|
|
34955
|
+
});
|
|
34956
|
+
}
|
|
34414
34957
|
function providerCatalogAvailable2(catalog) {
|
|
34415
34958
|
return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
34416
34959
|
}
|
|
@@ -34420,6 +34963,12 @@ function unavailableLabel2(value) {
|
|
|
34420
34963
|
function asStringArray(value) {
|
|
34421
34964
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
34422
34965
|
}
|
|
34966
|
+
function sanitizeProviderAuth2(auth) {
|
|
34967
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
34968
|
+
const type = typeof auth.type === "string" ? auth.type : void 0;
|
|
34969
|
+
const header = typeof auth.header === "string" ? auth.header : void 0;
|
|
34970
|
+
return type || header ? { type, header } : void 0;
|
|
34971
|
+
}
|
|
34423
34972
|
function contextOptions(value) {
|
|
34424
34973
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
34425
34974
|
const context = {};
|