@garuda-agent/garuda 0.14.1 → 0.16.0
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/CHANGELOG.md +49 -1
- package/README.md +78 -8
- package/dist/acpCommand-AJJBKAE5.js +191 -0
- package/dist/chunk-23FXBNBB.js +46 -0
- package/dist/chunk-3EQU2GUX.js +27 -0
- package/dist/{chunk-YIZWYUBG.js → chunk-44QN5SWZ.js} +2 -2
- package/dist/chunk-4JSUUDFQ.js +118 -0
- package/dist/{chunk-KWRJREUH.js → chunk-B25VL4RU.js} +1 -42
- package/dist/{chunk-U4OQVT2T.js → chunk-B6FA4I7Q.js} +2 -2
- package/dist/chunk-EIXUSVFX.js +116 -0
- package/dist/{chunk-YM6T4J5V.js → chunk-ENLDPN4S.js} +8 -111
- package/dist/{chunk-D5O5CIWK.js → chunk-GXZACJUI.js} +8 -25
- package/dist/{chunk-QOW5TK4L.js → chunk-HE2K657U.js} +9 -7
- package/dist/chunk-NP6RIC25.js +331 -0
- package/dist/chunk-NXJ4XLB5.js +19 -0
- package/dist/chunk-R7I2NBPX.js +71 -0
- package/dist/{chunk-ZGZ2ZADL.js → chunk-TUUEBHG2.js} +2 -2
- package/dist/{chunk-W7BPYQMW.js → chunk-ULAJPMY7.js} +1 -67
- package/dist/{chunk-35GMLLJW.js → chunk-USCUXCAU.js} +2 -2
- package/dist/{chunk-ITITLNAR.js → chunk-WIJL72ZE.js} +150 -234
- package/dist/chunk-XOK5OZOC.js +9 -0
- package/dist/cli/index.js +68 -39
- package/dist/{create-Y5SXJUXO.js → create-VXIH2GUZ.js} +6 -5
- package/dist/{evalCommand-5TG2CKZY.js → evalCommand-OX2LQYEK.js} +35 -18
- package/dist/{inkChat-3J26R24L.js → inkChat-YMMMGU7Y.js} +16 -10
- package/dist/{install-XWLU22OH.js → install-EWDSPFFZ.js} +4 -3
- package/dist/{jobCommand-IYWZWTSI.js → jobCommand-ACSGSBQ4.js} +9 -7
- package/dist/{launchd-2FL5UN2U.js → launchd-SSX362VG.js} +5 -4
- package/dist/{lspCommand-TJEZBV2E.js → lspCommand-JRXKRSL7.js} +4 -3
- package/dist/{manager-EBTYO2GR.js → manager-AWU4NHHK.js} +4 -3
- package/dist/{nightCommand-DUNWJASI.js → nightCommand-65MUEV32.js} +9 -7
- package/dist/{remove-AENULZZE.js → remove-6GDSTAIO.js} +4 -3
- package/dist/{run-66Y6R2KU.js → run-ML6GSPN4.js} +5 -3
- package/dist/server-KHHNHUYV.js +694 -0
- package/dist/setupCommand-VA7D5MZK.js +303 -0
- package/package.json +2 -1
- package/dist/chunk-6TWBYMY7.js +0 -9
- package/dist/chunk-V7WPGQLB.js +0 -207
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILTIN_PROVIDERS,
|
|
4
|
+
KEY_NAME,
|
|
5
|
+
MODELS_FILE,
|
|
6
|
+
chooseModel,
|
|
7
|
+
credentialsPath,
|
|
8
|
+
loadModelsConfig,
|
|
9
|
+
maskKey,
|
|
10
|
+
readCredentials,
|
|
11
|
+
resolveModel,
|
|
12
|
+
setDefaultModel,
|
|
13
|
+
writeCredentials
|
|
14
|
+
} from "./chunk-NP6RIC25.js";
|
|
15
|
+
import "./chunk-EIXUSVFX.js";
|
|
16
|
+
import {
|
|
17
|
+
knownModels
|
|
18
|
+
} from "./chunk-R7I2NBPX.js";
|
|
19
|
+
import "./chunk-ATW7BNIG.js";
|
|
20
|
+
import "./chunk-3EQU2GUX.js";
|
|
21
|
+
|
|
22
|
+
// src/cli/setupCommand.ts
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
var CHECK_TIMEOUT_MS = 1e4;
|
|
25
|
+
var LOCAL_LIST_TIMEOUT_MS = 2e3;
|
|
26
|
+
var OTHER = "another model";
|
|
27
|
+
var ONE_WORD = /^[!-~]+$/;
|
|
28
|
+
var PROVIDER_TITLES = {
|
|
29
|
+
anthropic: "Anthropic (Claude)",
|
|
30
|
+
openrouter: "OpenRouter",
|
|
31
|
+
ollama: "Ollama (this machine)",
|
|
32
|
+
lmstudio: "LM Studio (this machine)",
|
|
33
|
+
llamacpp: "llama.cpp (this machine)",
|
|
34
|
+
vllm: "vLLM (this machine)"
|
|
35
|
+
};
|
|
36
|
+
async function setupCommand(options, deps) {
|
|
37
|
+
const { io } = deps;
|
|
38
|
+
if ((deps.platform ?? process.platform) === "win32") {
|
|
39
|
+
io.print(
|
|
40
|
+
"Garuda supports macOS and Linux. On Windows, run Garuda in WSL (Windows Subsystem for Linux)."
|
|
41
|
+
);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
const home = deps.home ?? homedir();
|
|
45
|
+
const env = deps.env ?? process.env;
|
|
46
|
+
const models = await loadModelsConfig(home);
|
|
47
|
+
if (models.problem !== void 0) {
|
|
48
|
+
io.print(models.problem);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
if (options.show === true) return show(io, models.config, home, env);
|
|
52
|
+
if (!io.interactive) {
|
|
53
|
+
io.print(
|
|
54
|
+
"garuda setup asks questions, so it needs a terminal. In a script, set the key in its environment variable (for example ANTHROPIC_API_KEY) and the model with --model or GARUDA_MODEL."
|
|
55
|
+
);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
if (options.forget !== void 0 && options.forget !== false) {
|
|
60
|
+
return await forget(io, home, options.forget === true ? void 0 : options.forget);
|
|
61
|
+
}
|
|
62
|
+
return await setup(io, models.config, home, env, deps.fetch ?? fetch);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.name === "ExitPromptError") {
|
|
65
|
+
io.print("Setup stopped. Nothing changed.");
|
|
66
|
+
return 130;
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function setup(io, config, home, env, fetcher) {
|
|
72
|
+
const stored = await readCredentials(home);
|
|
73
|
+
if (stored.warning !== void 0) {
|
|
74
|
+
io.print(stored.warning);
|
|
75
|
+
io.print(`Fix or remove ${credentialsPath(home)}, then run garuda setup again.`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
const providers = { ...BUILTIN_PROVIDERS, ...config.providers };
|
|
79
|
+
const provider = await io.select(
|
|
80
|
+
"Provider",
|
|
81
|
+
Object.keys(providers).map((name) => ({
|
|
82
|
+
name: PROVIDER_TITLES[name] ?? `${name} (~/${MODELS_FILE})`,
|
|
83
|
+
value: name,
|
|
84
|
+
...config.providers[name] === void 0 ? {} : { description: "from your models.json" }
|
|
85
|
+
}))
|
|
86
|
+
);
|
|
87
|
+
const def = providers[provider];
|
|
88
|
+
const model = await pickModel(io, provider, def, fetcher);
|
|
89
|
+
const spec = provider === "anthropic" ? model : `${provider}/${model}`;
|
|
90
|
+
try {
|
|
91
|
+
resolveModel(spec, config);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
io.print(error.message);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
const keyName = def.type === "anthropic" ? def.apiKeyEnv ?? "ANTHROPIC_API_KEY" : def.apiKeyEnv;
|
|
97
|
+
let newKey;
|
|
98
|
+
if (keyName !== void 0) {
|
|
99
|
+
const fromEnv = env[keyName] ?? "";
|
|
100
|
+
const old = stored.keys[keyName];
|
|
101
|
+
if (fromEnv !== "") {
|
|
102
|
+
io.print(
|
|
103
|
+
`${keyName} is set in your environment, and it wins over a stored key. Garuda stores no key for it.`
|
|
104
|
+
);
|
|
105
|
+
} else if (old !== void 0 && await io.confirm(`Keep the stored ${keyName} (${maskKey(old)})?`, true)) {
|
|
106
|
+
} else {
|
|
107
|
+
newKey = await askKey(io, keyName);
|
|
108
|
+
}
|
|
109
|
+
if (await io.confirm("Check the key now? (a free request that lists models)", true)) {
|
|
110
|
+
for (; ; ) {
|
|
111
|
+
const key = fromEnv !== "" ? fromEnv : newKey ?? old;
|
|
112
|
+
const result = await checkKey(provider, def, key, env, fetcher);
|
|
113
|
+
io.print(result.message);
|
|
114
|
+
if (result.ok) break;
|
|
115
|
+
const next = await io.select("What now?", [
|
|
116
|
+
...fromEnv === "" ? [{ name: "Enter the key again", value: "again" }] : [],
|
|
117
|
+
{ name: "Store it anyway", value: "store" },
|
|
118
|
+
{ name: "Stop (nothing changes)", value: "stop" }
|
|
119
|
+
]);
|
|
120
|
+
if (next === "stop") {
|
|
121
|
+
io.print("Setup stopped. Nothing changed.");
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
if (next === "store") break;
|
|
125
|
+
newKey = await askKey(io, keyName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else if (def.local === true) {
|
|
129
|
+
const result = await checkKey(provider, def, void 0, env, fetcher);
|
|
130
|
+
io.print(result.ok ? result.message : `${result.message} Start the server before you use it.`);
|
|
131
|
+
}
|
|
132
|
+
await setDefaultModel(spec, home);
|
|
133
|
+
if (newKey !== void 0 && keyName !== void 0) {
|
|
134
|
+
await writeCredentials({ ...stored.keys, [keyName]: newKey }, home);
|
|
135
|
+
}
|
|
136
|
+
io.print("");
|
|
137
|
+
io.print(`Model: ${spec} (the default in ~/${MODELS_FILE})`);
|
|
138
|
+
if (keyName !== void 0) {
|
|
139
|
+
const where = (env[keyName] ?? "") !== "" ? "from your environment" : `in ${credentialsPath(home)}`;
|
|
140
|
+
const key = (env[keyName] ?? "") !== "" ? env[keyName] : newKey ?? stored.keys[keyName] ?? "";
|
|
141
|
+
io.print(`Key: ${keyName} ${maskKey(key)} ${where}`);
|
|
142
|
+
}
|
|
143
|
+
if ((env.GARUDA_MODEL ?? "") !== "") {
|
|
144
|
+
io.print(`GARUDA_MODEL is set (${env.GARUDA_MODEL}), and it wins over this default.`);
|
|
145
|
+
}
|
|
146
|
+
io.print("Done. Start Garuda with: garuda");
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
async function pickModel(io, provider, def, fetcher) {
|
|
150
|
+
let listed = [];
|
|
151
|
+
const notes = /* @__PURE__ */ new Map();
|
|
152
|
+
if (def.type === "anthropic") {
|
|
153
|
+
for (const { id, info } of knownModels()) {
|
|
154
|
+
listed.push(id);
|
|
155
|
+
if (info.price !== void 0) {
|
|
156
|
+
notes.set(id, `$${info.price.input} in, $${info.price.output} out per million tokens`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else if (def.local === true && def.baseUrl !== void 0) {
|
|
160
|
+
listed = await localModels(def.baseUrl, fetcher);
|
|
161
|
+
}
|
|
162
|
+
if (listed.length > 0) {
|
|
163
|
+
const picked = await io.select("Model", [
|
|
164
|
+
...listed.map((id) => {
|
|
165
|
+
const note = notes.get(id);
|
|
166
|
+
return { name: id, value: id, ...note === void 0 ? {} : { description: note } };
|
|
167
|
+
}),
|
|
168
|
+
{ name: "Another model (type its name)", value: OTHER }
|
|
169
|
+
]);
|
|
170
|
+
if (picked !== OTHER) return picked;
|
|
171
|
+
}
|
|
172
|
+
for (; ; ) {
|
|
173
|
+
const typed = (await io.input(
|
|
174
|
+
provider === "openrouter" ? "Model (for example qwen/qwen3-coder)" : "Model name"
|
|
175
|
+
)).trim();
|
|
176
|
+
if (typed !== "" && ONE_WORD.test(typed)) return typed;
|
|
177
|
+
io.print("Type a model name with no spaces.");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function localModels(baseUrl, fetcher) {
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetcher(`${baseUrl.replace(/\/+$/, "")}/models`, {
|
|
183
|
+
signal: AbortSignal.timeout(LOCAL_LIST_TIMEOUT_MS)
|
|
184
|
+
});
|
|
185
|
+
if (!response.ok) return [];
|
|
186
|
+
const body = await response.json();
|
|
187
|
+
return (body.data ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.length <= 200 && ONE_WORD.test(id)).slice(0, 50);
|
|
188
|
+
} catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function askKey(io, keyName) {
|
|
193
|
+
for (; ; ) {
|
|
194
|
+
const key = (await io.secret(`${keyName} (the input is hidden)`)).trim();
|
|
195
|
+
if (key !== "" && key.length <= 4096 && ONE_WORD.test(key)) return key;
|
|
196
|
+
io.print("A key is one word with no spaces. Paste it again.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function checkKey(provider, def, key, env, fetcher = fetch) {
|
|
200
|
+
let url;
|
|
201
|
+
const headers = {};
|
|
202
|
+
if (def.type === "anthropic") {
|
|
203
|
+
const base = (env.ANTHROPIC_BASE_URL ?? "") !== "" ? env.ANTHROPIC_BASE_URL : "https://api.anthropic.com";
|
|
204
|
+
url = `${base.replace(/\/+$/, "")}/v1/models?limit=1`;
|
|
205
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
206
|
+
if (key !== void 0) headers["x-api-key"] = key;
|
|
207
|
+
} else {
|
|
208
|
+
url = `${def.baseUrl.replace(/\/+$/, "")}/models`;
|
|
209
|
+
if (key !== void 0) headers.authorization = `Bearer ${key}`;
|
|
210
|
+
}
|
|
211
|
+
const host = new URL(url).host;
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetcher(url, {
|
|
214
|
+
headers,
|
|
215
|
+
redirect: "error",
|
|
216
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
|
|
217
|
+
});
|
|
218
|
+
await response.body?.cancel();
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
message: key === void 0 ? `${provider} answered at ${host}.` : `${provider} answered: the key works.`
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (response.status === 401 || response.status === 403) {
|
|
226
|
+
return { ok: false, message: `${provider} refused the key (HTTP ${response.status}).` };
|
|
227
|
+
}
|
|
228
|
+
return { ok: false, message: `${host} answered HTTP ${response.status}.` };
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const reason = error.name === "TimeoutError" ? "no answer in time" : error.cause?.code ?? error.message;
|
|
231
|
+
return { ok: false, message: `Garuda could not reach ${host} (${reason}).` };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function show(io, config, home, env) {
|
|
235
|
+
const stored = await readCredentials(home);
|
|
236
|
+
const spec = chooseModel(config, env.GARUDA_MODEL);
|
|
237
|
+
io.print(
|
|
238
|
+
spec === void 0 ? "Model: none. Run garuda setup." : `Model: ${spec} (${(env.GARUDA_MODEL ?? "") !== "" ? "GARUDA_MODEL" : `the default in ~/${MODELS_FILE}`})`
|
|
239
|
+
);
|
|
240
|
+
const providers = { ...BUILTIN_PROVIDERS, ...config.providers };
|
|
241
|
+
const names = /* @__PURE__ */ new Set(["ANTHROPIC_API_KEY"]);
|
|
242
|
+
for (const def of Object.values(providers))
|
|
243
|
+
if (def.apiKeyEnv !== void 0) names.add(def.apiKeyEnv);
|
|
244
|
+
for (const name of Object.keys(stored.keys)) names.add(name);
|
|
245
|
+
for (const name of [...names].sort()) {
|
|
246
|
+
const fromEnv = env[name] ?? "";
|
|
247
|
+
const fromFile = stored.keys[name];
|
|
248
|
+
io.print(
|
|
249
|
+
fromEnv !== "" ? `${name}: ${maskKey(fromEnv)} from the environment${fromFile === void 0 ? "" : " (it wins over the stored key)"}` : fromFile !== void 0 ? `${name}: ${maskKey(fromFile)} in ${credentialsPath(home)}` : `${name}: not set`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (stored.warning !== void 0) io.print(stored.warning);
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
async function forget(io, home, name) {
|
|
256
|
+
const stored = await readCredentials(home);
|
|
257
|
+
if (stored.warning !== void 0) {
|
|
258
|
+
io.print(stored.warning);
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
const names = Object.keys(stored.keys);
|
|
262
|
+
if (name !== void 0 && !KEY_NAME.test(name)) {
|
|
263
|
+
io.print(`"${name}" is not a key name (for example ANTHROPIC_API_KEY).`);
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
if (name !== void 0 && stored.keys[name] === void 0) {
|
|
267
|
+
io.print(`No stored key is named ${name}.`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
if (names.length === 0) {
|
|
271
|
+
io.print("No keys are stored.");
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
const gone = name === void 0 ? names : [name];
|
|
275
|
+
const question = gone.length === 1 ? `Remove the stored ${gone[0]} (${maskKey(stored.keys[gone[0]])})?` : `Remove all ${gone.length} stored keys (${gone.join(", ")})?`;
|
|
276
|
+
if (!await io.confirm(question, false)) {
|
|
277
|
+
io.print("Nothing changed.");
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
const rest = Object.fromEntries(Object.entries(stored.keys).filter(([n]) => !gone.includes(n)));
|
|
281
|
+
await writeCredentials(rest, home);
|
|
282
|
+
io.print(`Removed: ${gone.join(", ")}.`);
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
async function terminalIO() {
|
|
286
|
+
const { confirm, input, password, select } = await import("@inquirer/prompts");
|
|
287
|
+
return {
|
|
288
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
289
|
+
select: (message, choices) => select({ message, choices, pageSize: 12 }),
|
|
290
|
+
input: (message, initial) => input({ message, ...initial === void 0 ? {} : { default: initial } }),
|
|
291
|
+
secret: (message) => password({ message, mask: "*" }),
|
|
292
|
+
confirm: (message, initial) => confirm({ message, default: initial }),
|
|
293
|
+
print: (text) => process.stdout.write(`${text}
|
|
294
|
+
`)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
CHECK_TIMEOUT_MS,
|
|
299
|
+
checkKey,
|
|
300
|
+
setupCommand,
|
|
301
|
+
terminalIO
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=setupCommand-VA7D5MZK.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garuda-agent/garuda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Garuda: a terminal coding agent with an OS sandbox, approvals and an audit log.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Madhusudhan Vasanth Kumar",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"packageManager": "pnpm@10.28.0",
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"@agentclientprotocol/sdk": "1.7.0",
|
|
55
56
|
"@anthropic-ai/sdk": "^0.128.0",
|
|
56
57
|
"@inquirer/prompts": "^8.7.2",
|
|
57
58
|
"@modelcontextprotocol/client": "^2.1.0",
|
package/dist/chunk-6TWBYMY7.js
DELETED
package/dist/chunk-V7WPGQLB.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
keepSecretForRedaction
|
|
4
|
-
} from "./chunk-ITITLNAR.js";
|
|
5
|
-
import {
|
|
6
|
-
lookupModel
|
|
7
|
-
} from "./chunk-W7BPYQMW.js";
|
|
8
|
-
import {
|
|
9
|
-
isLoopbackHost
|
|
10
|
-
} from "./chunk-ATW7BNIG.js";
|
|
11
|
-
|
|
12
|
-
// src/model/providers.ts
|
|
13
|
-
import { readFile } from "fs/promises";
|
|
14
|
-
import { homedir } from "os";
|
|
15
|
-
import { join } from "path";
|
|
16
|
-
import { z } from "zod";
|
|
17
|
-
var MODELS_FILE = join(".garuda", "models.json");
|
|
18
|
-
var priceSchema = z.strictObject({
|
|
19
|
-
input: z.number().min(0),
|
|
20
|
-
output: z.number().min(0),
|
|
21
|
-
cacheRead: z.number().min(0),
|
|
22
|
-
cacheWrite: z.number().min(0)
|
|
23
|
-
});
|
|
24
|
-
var providerSchema = z.strictObject({
|
|
25
|
-
type: z.enum(["anthropic", "openai-compatible"]),
|
|
26
|
-
/** For openai-compatible: the URL that ends before /chat/completions. */
|
|
27
|
-
baseUrl: z.url().optional(),
|
|
28
|
-
/** The environment variable that holds the API key. */
|
|
29
|
-
apiKeyEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
|
|
30
|
-
/** A server on this machine: price 0 unless a model entry says otherwise. */
|
|
31
|
-
local: z.boolean().optional(),
|
|
32
|
-
/** Allow plain http to a host that is not this machine. Off: an API key could travel in clear text. */
|
|
33
|
-
allowInsecureHttp: z.boolean().optional()
|
|
34
|
-
});
|
|
35
|
-
var modelSchema = z.strictObject({
|
|
36
|
-
contextWindow: z.number().int().min(4096).optional(),
|
|
37
|
-
price: priceSchema.optional(),
|
|
38
|
-
maxTokens: z.number().int().min(256).optional(),
|
|
39
|
-
/**
|
|
40
|
-
* Tool calls that the model writes as JSON text (openai-compatible only): "whole" (default) when
|
|
41
|
-
* the whole reply is calls, "lines" when calls stand on their own lines between prose, "off".
|
|
42
|
-
*/
|
|
43
|
-
textToolCalls: z.enum(["off", "whole", "lines"]).optional()
|
|
44
|
-
});
|
|
45
|
-
var fileSchema = z.strictObject({
|
|
46
|
-
providers: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,31}$/), providerSchema).default({}),
|
|
47
|
-
/** Keyed by the full spec, for example "ollama/qwen3-coder:30b". */
|
|
48
|
-
models: z.record(z.string(), modelSchema).default({})
|
|
49
|
-
});
|
|
50
|
-
var BUILTIN_PROVIDERS = {
|
|
51
|
-
anthropic: { type: "anthropic" },
|
|
52
|
-
ollama: { type: "openai-compatible", baseUrl: "http://localhost:11434/v1", local: true },
|
|
53
|
-
lmstudio: { type: "openai-compatible", baseUrl: "http://localhost:1234/v1", local: true },
|
|
54
|
-
llamacpp: { type: "openai-compatible", baseUrl: "http://localhost:8080/v1", local: true },
|
|
55
|
-
vllm: { type: "openai-compatible", baseUrl: "http://localhost:8000/v1", local: true },
|
|
56
|
-
openrouter: {
|
|
57
|
-
type: "openai-compatible",
|
|
58
|
-
baseUrl: "https://openrouter.ai/api/v1",
|
|
59
|
-
apiKeyEnv: "OPENROUTER_API_KEY"
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
var OPEN_MODEL_DEFAULT_WINDOW = 32768;
|
|
63
|
-
var ZERO_PRICE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
64
|
-
async function loadModelsConfig(home = homedir()) {
|
|
65
|
-
const file = join(home, MODELS_FILE);
|
|
66
|
-
let text;
|
|
67
|
-
try {
|
|
68
|
-
text = await readFile(file, "utf8");
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error.code === "ENOENT")
|
|
71
|
-
return { config: { providers: {}, models: {} } };
|
|
72
|
-
return {
|
|
73
|
-
config: { providers: {}, models: {} },
|
|
74
|
-
problem: `${file}: ${error.message}`
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const parsed = fileSchema.safeParse(JSON.parse(text));
|
|
79
|
-
if (!parsed.success) {
|
|
80
|
-
return {
|
|
81
|
-
config: { providers: {}, models: {} },
|
|
82
|
-
problem: `${file}: ${z.prettifyError(parsed.error)}`
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return { config: parsed.data };
|
|
86
|
-
} catch (error) {
|
|
87
|
-
return {
|
|
88
|
-
config: { providers: {}, models: {} },
|
|
89
|
-
problem: `${file}: invalid JSON: ${error.message}`
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
var keptKeys = /* @__PURE__ */ new Map();
|
|
94
|
-
function keepProviderKey(name) {
|
|
95
|
-
const value = process.env[name];
|
|
96
|
-
if (value !== void 0 && value !== "") {
|
|
97
|
-
keptKeys.set(name, value);
|
|
98
|
-
keepSecretForRedaction(name, value);
|
|
99
|
-
delete process.env[name];
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function hasProviderKey(name) {
|
|
103
|
-
return keptKeys.has(name) || (process.env[name] ?? "") !== "";
|
|
104
|
-
}
|
|
105
|
-
function providerKey(env, name) {
|
|
106
|
-
if (name === void 0) return void 0;
|
|
107
|
-
return keptKeys.get(name) ?? env[name];
|
|
108
|
-
}
|
|
109
|
-
function resolveModel(spec, config = { providers: {}, models: {} }) {
|
|
110
|
-
const providers = { ...BUILTIN_PROVIDERS, ...config.providers };
|
|
111
|
-
const slash = spec.indexOf("/");
|
|
112
|
-
const [provider, model] = slash === -1 ? ["anthropic", spec] : [spec.slice(0, slash), spec.slice(slash + 1)];
|
|
113
|
-
const def = providers[provider];
|
|
114
|
-
if (def === void 0) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
`Unknown model provider "${provider}" in "${spec}". Known providers: ${Object.keys(providers).join(", ")}. Add others in ~/${MODELS_FILE}.`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
if (model === "") throw new Error(`"${spec}" names no model. Use <provider>/<model>.`);
|
|
120
|
-
if (def.type === "openai-compatible") checkBaseUrl(provider, def);
|
|
121
|
-
const entry = config.models[spec] ?? (provider === "anthropic" ? config.models[model] : void 0);
|
|
122
|
-
const base = def.type === "anthropic" ? lookupModel(model) : { contextWindow: OPEN_MODEL_DEFAULT_WINDOW, ...def.local ? { price: ZERO_PRICE } : {} };
|
|
123
|
-
const info = {
|
|
124
|
-
contextWindow: entry?.contextWindow ?? base.contextWindow,
|
|
125
|
-
...entry?.price !== void 0 ? { price: entry.price } : base.price !== void 0 ? { price: base.price } : {},
|
|
126
|
-
// Thinking by model (0.9): only Claude models on the Anthropic provider have it.
|
|
127
|
-
...base.thinking === void 0 ? {} : { thinking: base.thinking }
|
|
128
|
-
};
|
|
129
|
-
const notes = [];
|
|
130
|
-
if (def.type === "openai-compatible" && entry?.contextWindow === void 0) {
|
|
131
|
-
notes.push(
|
|
132
|
-
`Garuda assumes a ${OPEN_MODEL_DEFAULT_WINDOW}-token context window for ${spec}. Set the real value ("contextWindow") for "${spec}" in ~/${MODELS_FILE}; the server must allow it too (for Ollama: OLLAMA_CONTEXT_LENGTH).`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
spec,
|
|
137
|
-
provider,
|
|
138
|
-
model,
|
|
139
|
-
def,
|
|
140
|
-
info,
|
|
141
|
-
notes,
|
|
142
|
-
...entry?.maxTokens === void 0 ? {} : { maxTokens: entry.maxTokens },
|
|
143
|
-
...def.type === "anthropic" ? {
|
|
144
|
-
async createBatch(env = process.env, options = {}) {
|
|
145
|
-
const apiKey = providerKey(env, def.apiKeyEnv ?? "ANTHROPIC_API_KEY");
|
|
146
|
-
const { AnthropicBatchClient } = await import("./anthropic-NQDBCHJU.js");
|
|
147
|
-
return new AnthropicBatchClient({ model, ...apiKey ? { apiKey } : {}, ...options });
|
|
148
|
-
}
|
|
149
|
-
} : {},
|
|
150
|
-
async create(env = process.env) {
|
|
151
|
-
const apiKey = providerKey(env, def.apiKeyEnv);
|
|
152
|
-
if (def.apiKeyEnv !== void 0 && (apiKey === void 0 || apiKey === "")) {
|
|
153
|
-
throw new Error(`Set ${def.apiKeyEnv} to use the ${provider} provider.`);
|
|
154
|
-
}
|
|
155
|
-
if (def.type === "anthropic") {
|
|
156
|
-
const key = providerKey(env, def.apiKeyEnv ?? "ANTHROPIC_API_KEY");
|
|
157
|
-
const { AnthropicClient } = await import("./anthropic-NQDBCHJU.js");
|
|
158
|
-
return new AnthropicClient({ model, ...key ? { apiKey: key } : {} });
|
|
159
|
-
}
|
|
160
|
-
const { OpenAICompatibleClient } = await import("./openaiCompatible-CJWVJP5M.js");
|
|
161
|
-
return new OpenAICompatibleClient({
|
|
162
|
-
provider,
|
|
163
|
-
baseUrl: def.baseUrl,
|
|
164
|
-
model,
|
|
165
|
-
...apiKey === void 0 ? {} : { apiKey },
|
|
166
|
-
...entry?.textToolCalls === void 0 ? {} : { textToolCalls: entry.textToolCalls }
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function checkBaseUrl(provider, def) {
|
|
172
|
-
if (def.baseUrl === void 0) throw new Error(`Provider "${provider}" needs a baseUrl.`);
|
|
173
|
-
const url = new URL(def.baseUrl);
|
|
174
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
175
|
-
throw new Error(`Provider "${provider}": the baseUrl must use http or https.`);
|
|
176
|
-
}
|
|
177
|
-
if (url.username !== "" || url.password !== "") {
|
|
178
|
-
throw new Error(`Provider "${provider}": put credentials in apiKeyEnv, not in the baseUrl.`);
|
|
179
|
-
}
|
|
180
|
-
if (url.protocol === "http:" && !isLoopbackHost(url.hostname) && def.allowInsecureHttp !== true) {
|
|
181
|
-
throw new Error(
|
|
182
|
-
`Provider "${provider}" uses plain http to ${url.hostname}. Use https, or set "allowInsecureHttp": true for a trusted network.`
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// src/permissions/autoApprover.ts
|
|
188
|
-
var AutoApprover = class {
|
|
189
|
-
requests = [];
|
|
190
|
-
answer;
|
|
191
|
-
constructor(answer = "once") {
|
|
192
|
-
this.answer = typeof answer === "function" ? answer : () => answer;
|
|
193
|
-
}
|
|
194
|
-
async ask(request) {
|
|
195
|
-
this.requests.push(request);
|
|
196
|
-
return this.answer(request);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
export {
|
|
201
|
-
loadModelsConfig,
|
|
202
|
-
keepProviderKey,
|
|
203
|
-
hasProviderKey,
|
|
204
|
-
resolveModel,
|
|
205
|
-
AutoApprover
|
|
206
|
-
};
|
|
207
|
-
//# sourceMappingURL=chunk-V7WPGQLB.js.map
|