agent.libx.js 0.89.3 → 0.89.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +187 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -23,6 +23,25 @@ interface SessionMeta {
|
|
|
23
23
|
costUsd?: number;
|
|
24
24
|
/** Sticky: true if any turn's usage was estimated (streamed without provider usage) → cost is approximate. */
|
|
25
25
|
costEstimated?: boolean;
|
|
26
|
+
/** Per-turn forensic log: timing, outcome, usage, and provider error per turn — for investigating a reported issue. */
|
|
27
|
+
events?: TurnEvent[];
|
|
28
|
+
}
|
|
29
|
+
/** One turn's diagnostics, captured for offline forensics (mirrors what the footer prints, but persisted). */
|
|
30
|
+
interface TurnEvent {
|
|
31
|
+
ts: number;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
model: string;
|
|
34
|
+
finishReason: string;
|
|
35
|
+
steps: number;
|
|
36
|
+
tools: number;
|
|
37
|
+
tokens?: number;
|
|
38
|
+
costUsd?: number;
|
|
39
|
+
estimated?: boolean;
|
|
40
|
+
error?: {
|
|
41
|
+
message: string;
|
|
42
|
+
statusCode?: number;
|
|
43
|
+
code?: string;
|
|
44
|
+
};
|
|
26
45
|
}
|
|
27
46
|
interface SessionData {
|
|
28
47
|
meta: SessionMeta;
|
|
@@ -145,6 +164,7 @@ declare function expandMentions(fs: IFilesystem, line: string): Promise<{
|
|
|
145
164
|
}>;
|
|
146
165
|
/** The headless `--output-format json` result object for a turn. */
|
|
147
166
|
declare function jsonResult(res: RunResult, session: SessionData): {
|
|
167
|
+
error?: any;
|
|
148
168
|
ok: boolean;
|
|
149
169
|
finishReason: "error" | "budget" | "stop" | "max_steps" | "timeout" | "loop" | "max_tool_calls" | "aborted";
|
|
150
170
|
text: string;
|
package/dist/cli.js
CHANGED
|
@@ -2805,7 +2805,7 @@ var Agent = class _Agent {
|
|
|
2805
2805
|
} catch (err2) {
|
|
2806
2806
|
if (err2?.code === "budget") return kill("budget");
|
|
2807
2807
|
if (o.signal?.aborted) return kill("aborted");
|
|
2808
|
-
log3.error(
|
|
2808
|
+
log3.error(`chat() failed: ${err2?.message ?? err2}`, err2);
|
|
2809
2809
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
2810
2810
|
}
|
|
2811
2811
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -2869,12 +2869,14 @@ var Agent = class _Agent {
|
|
|
2869
2869
|
}
|
|
2870
2870
|
async dispatch(tc) {
|
|
2871
2871
|
const tool = this.activeTools.find((t) => t.name === tc.function.name);
|
|
2872
|
-
if (!tool) return `Error: unknown tool '${tc.function.name}'`;
|
|
2873
2872
|
let args = {};
|
|
2873
|
+
let earlyError;
|
|
2874
|
+
if (!tool) earlyError = `Error: unknown tool '${tc.function.name}'`;
|
|
2874
2875
|
try {
|
|
2875
2876
|
args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
2876
2877
|
} catch (e) {
|
|
2877
|
-
|
|
2878
|
+
args = tc.function.arguments;
|
|
2879
|
+
earlyError ??= `Error: invalid JSON arguments for ${tc.function.name}: ${String(e)}`;
|
|
2878
2880
|
}
|
|
2879
2881
|
const hooks = this.activeHooks;
|
|
2880
2882
|
const call = { name: tc.function.name, args };
|
|
@@ -2887,6 +2889,12 @@ var Agent = class _Agent {
|
|
|
2887
2889
|
return blocked;
|
|
2888
2890
|
}
|
|
2889
2891
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
2892
|
+
if (earlyError) {
|
|
2893
|
+
log3.debug(`${tc.function.name} -> ${earlyError}`);
|
|
2894
|
+
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
2895
|
+
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
2896
|
+
return earlyError;
|
|
2897
|
+
}
|
|
2890
2898
|
let result;
|
|
2891
2899
|
let threw = false;
|
|
2892
2900
|
try {
|
|
@@ -4639,6 +4647,137 @@ function completePath(listDir, ref) {
|
|
|
4639
4647
|
|
|
4640
4648
|
// cli/lineEditor.ts
|
|
4641
4649
|
import { emitKeypressEvents } from "readline";
|
|
4650
|
+
|
|
4651
|
+
// cli/bidi.ts
|
|
4652
|
+
var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
4653
|
+
var needsBidi = (s) => RTL_RE.test(s);
|
|
4654
|
+
function classify(c) {
|
|
4655
|
+
if (c >= 1425 && c <= 1469) return "NSM";
|
|
4656
|
+
if (c === 1471 || c === 1473 || c === 1474 || c === 1476 || c === 1477 || c === 1479) return "NSM";
|
|
4657
|
+
if (c >= 1424 && c <= 1535 || c >= 64285 && c <= 64335) return "R";
|
|
4658
|
+
if (c >= 1632 && c <= 1641 || c >= 1776 && c <= 1785) return "AN";
|
|
4659
|
+
if (c >= 1536 && c <= 1791 || c >= 1872 && c <= 1919 || c >= 2208 && c <= 2303 || c >= 64336 && c <= 65023 || c >= 65136 && c <= 65279) return "AL";
|
|
4660
|
+
if (c >= 48 && c <= 57) return "EN";
|
|
4661
|
+
if (c === 43 || c === 45) return "ES";
|
|
4662
|
+
if (c === 35 || c === 36 || c === 37 || c >= 162 && c <= 165) return "ET";
|
|
4663
|
+
if (c === 44 || c === 46 || c === 58 || c === 47 || c === 160) return "CS";
|
|
4664
|
+
if (c === 32 || c === 9 || c === 11 || c === 12) return "WS";
|
|
4665
|
+
if (c >= 33 && c <= 47 || c >= 58 && c <= 64 || c >= 91 && c <= 96 || c >= 123 && c <= 126) return "ON";
|
|
4666
|
+
return "L";
|
|
4667
|
+
}
|
|
4668
|
+
function baseLevel(types) {
|
|
4669
|
+
for (const t of types) {
|
|
4670
|
+
if (t === "L") return 0;
|
|
4671
|
+
if (t === "R" || t === "AL") return 1;
|
|
4672
|
+
}
|
|
4673
|
+
return 0;
|
|
4674
|
+
}
|
|
4675
|
+
function resolveWeak(t, baseDir) {
|
|
4676
|
+
const n = t.length;
|
|
4677
|
+
for (let i = 0; i < n; i++) if (t[i] === "NSM") t[i] = i > 0 ? t[i - 1] : baseDir;
|
|
4678
|
+
for (let i = 0; i < n; i++) if (t[i] === "EN") {
|
|
4679
|
+
for (let j = i - 1; j >= 0; j--) if (t[j] === "L" || t[j] === "R" || t[j] === "AL") {
|
|
4680
|
+
if (t[j] === "AL") t[i] = "AN";
|
|
4681
|
+
break;
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
for (let i = 0; i < n; i++) if (t[i] === "AL") t[i] = "R";
|
|
4685
|
+
for (let i = 1; i < n - 1; i++) {
|
|
4686
|
+
if (t[i] === "ES" && t[i - 1] === "EN" && t[i + 1] === "EN") t[i] = "EN";
|
|
4687
|
+
else if (t[i] === "CS" && t[i - 1] === "EN" && t[i + 1] === "EN") t[i] = "EN";
|
|
4688
|
+
else if (t[i] === "CS" && t[i - 1] === "AN" && t[i + 1] === "AN") t[i] = "AN";
|
|
4689
|
+
}
|
|
4690
|
+
for (let i = 0; i < n; i++) if (t[i] === "ET") {
|
|
4691
|
+
let j = i;
|
|
4692
|
+
while (j < n && t[j] === "ET") j++;
|
|
4693
|
+
if (i > 0 && t[i - 1] === "EN" || j < n && t[j] === "EN") for (let k = i; k < j; k++) t[k] = "EN";
|
|
4694
|
+
i = j - 1;
|
|
4695
|
+
}
|
|
4696
|
+
for (let i = 0; i < n; i++) if (t[i] === "ES" || t[i] === "ET" || t[i] === "CS") t[i] = "ON";
|
|
4697
|
+
for (let i = 0; i < n; i++) if (t[i] === "EN") {
|
|
4698
|
+
let strong = baseDir;
|
|
4699
|
+
for (let j = i - 1; j >= 0; j--) if (t[j] === "L" || t[j] === "R") {
|
|
4700
|
+
strong = t[j];
|
|
4701
|
+
break;
|
|
4702
|
+
}
|
|
4703
|
+
if (strong === "L") t[i] = "L";
|
|
4704
|
+
}
|
|
4705
|
+
return t;
|
|
4706
|
+
}
|
|
4707
|
+
function resolveNeutral(t, baseDir) {
|
|
4708
|
+
const n = t.length;
|
|
4709
|
+
const strong = (x) => x === "L" ? "L" : x === "R" || x === "EN" || x === "AN" ? "R" : void 0;
|
|
4710
|
+
for (let i = 0; i < n; i++) if (t[i] === "ON" || t[i] === "WS") {
|
|
4711
|
+
let j = i;
|
|
4712
|
+
while (j < n && (t[j] === "ON" || t[j] === "WS")) j++;
|
|
4713
|
+
const prev = i > 0 ? strong(t[i - 1]) : baseDir;
|
|
4714
|
+
const next = j < n ? strong(t[j]) : baseDir;
|
|
4715
|
+
const dir = prev && next && prev === next ? prev : baseDir;
|
|
4716
|
+
for (let k = i; k < j; k++) t[k] = dir;
|
|
4717
|
+
i = j - 1;
|
|
4718
|
+
}
|
|
4719
|
+
return t;
|
|
4720
|
+
}
|
|
4721
|
+
function implicitLevels(t, e) {
|
|
4722
|
+
return t.map((x) => e % 2 === 0 ? x === "R" ? e + 1 : x === "EN" || x === "AN" ? e + 2 : e : x === "L" || x === "EN" || x === "AN" ? e + 1 : e);
|
|
4723
|
+
}
|
|
4724
|
+
function reorder(levels) {
|
|
4725
|
+
const n = levels.length;
|
|
4726
|
+
const order = Array.from({ length: n }, (_, i) => i);
|
|
4727
|
+
let highest = 0, lowestOdd = Infinity;
|
|
4728
|
+
for (const l of levels) {
|
|
4729
|
+
if (l > highest) highest = l;
|
|
4730
|
+
if (l % 2 && l < lowestOdd) lowestOdd = l;
|
|
4731
|
+
}
|
|
4732
|
+
for (let lvl = highest; lvl >= lowestOdd; lvl--) {
|
|
4733
|
+
let i = 0;
|
|
4734
|
+
while (i < n) {
|
|
4735
|
+
if (levels[i] >= lvl) {
|
|
4736
|
+
let j = i;
|
|
4737
|
+
while (j < n && levels[j] >= lvl) j++;
|
|
4738
|
+
for (let a = i, b = j - 1; a < b; a++, b--) {
|
|
4739
|
+
const tmp = order[a];
|
|
4740
|
+
order[a] = order[b];
|
|
4741
|
+
order[b] = tmp;
|
|
4742
|
+
}
|
|
4743
|
+
i = j;
|
|
4744
|
+
} else i++;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
return order;
|
|
4748
|
+
}
|
|
4749
|
+
function bidiLine(line) {
|
|
4750
|
+
const n = line.length;
|
|
4751
|
+
if (n === 0) return { visual: "", caret: [0] };
|
|
4752
|
+
const types = Array.from({ length: n }, (_, i) => classify(line.charCodeAt(i)));
|
|
4753
|
+
const e = baseLevel(types);
|
|
4754
|
+
const baseDir = e % 2 ? "R" : "L";
|
|
4755
|
+
const levels = implicitLevels(resolveNeutral(resolveWeak(types, baseDir), baseDir), e);
|
|
4756
|
+
const order = reorder(levels);
|
|
4757
|
+
const logToVis = new Array(n);
|
|
4758
|
+
for (let v = 0; v < n; v++) logToVis[order[v]] = v;
|
|
4759
|
+
const caret = new Array(n + 1);
|
|
4760
|
+
for (let c = 0; c < n; c++) caret[c] = levels[c] % 2 === 0 ? logToVis[c] : logToVis[c] + 1;
|
|
4761
|
+
caret[n] = levels[n - 1] % 2 === 0 ? logToVis[n - 1] + 1 : logToVis[n - 1];
|
|
4762
|
+
const visual = order.map((li) => line[li]).join("");
|
|
4763
|
+
return { visual, caret };
|
|
4764
|
+
}
|
|
4765
|
+
function applyBidi(buf, cursor) {
|
|
4766
|
+
if (!needsBidi(buf)) return { text: buf, cursor };
|
|
4767
|
+
const lines = buf.split("\n");
|
|
4768
|
+
const out = [];
|
|
4769
|
+
let visCursor = cursor, logBase = 0, visBase = 0;
|
|
4770
|
+
for (const line of lines) {
|
|
4771
|
+
const { visual, caret } = bidiLine(line);
|
|
4772
|
+
out.push(visual);
|
|
4773
|
+
if (cursor >= logBase && cursor <= logBase + line.length) visCursor = visBase + caret[cursor - logBase];
|
|
4774
|
+
logBase += line.length + 1;
|
|
4775
|
+
visBase += visual.length + 1;
|
|
4776
|
+
}
|
|
4777
|
+
return { text: out.join("\n"), cursor: visCursor };
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// cli/lineEditor.ts
|
|
4642
4781
|
var visibleWidth = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
4643
4782
|
var isPrintable = (str) => !!str && !/[\x00-\x1f\x7f]/.test(str);
|
|
4644
4783
|
var EditorState = class _EditorState {
|
|
@@ -5210,8 +5349,9 @@ function createLineEditor(out) {
|
|
|
5210
5349
|
const prompt = s.searching ? dim2(`(${s.searchMiss ? "failing " : ""}reverse-i-search)\`${s.searchQuery}': `) : mode ? COLOR[mode.name](`${mode.pfx} ${mode.name} \u203A `) : vimTag + promptArg;
|
|
5211
5350
|
if (curRow > 0) out.write(`\x1B[${curRow}A`);
|
|
5212
5351
|
out.write("\r\x1B[J");
|
|
5213
|
-
const
|
|
5214
|
-
const
|
|
5352
|
+
const rawBuf = mode ? s.buf.slice(mode.pfx.length) : s.buf;
|
|
5353
|
+
const rawCursor = mode ? Math.max(0, s.cursor - mode.pfx.length) : s.cursor;
|
|
5354
|
+
const { text: viewBuf, cursor: viewCursor } = applyBidi(rawBuf, rawCursor);
|
|
5215
5355
|
const { rows, cursorRow, cursorCol } = wrapLayout(visibleWidth(prompt), viewBuf, viewCursor, cols);
|
|
5216
5356
|
const ghost = !mode && !s.searching ? s.ghost() : "";
|
|
5217
5357
|
const ghostFits = ghost && rows.length === 1 && visibleWidth(prompt) + viewBuf.length + ghost.length < cols;
|
|
@@ -5795,6 +5935,22 @@ function resolveModelOrNewest(model) {
|
|
|
5795
5935
|
return fallback;
|
|
5796
5936
|
}
|
|
5797
5937
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
5938
|
+
function loadInstallEnv() {
|
|
5939
|
+
let dir = dirname3(import.meta.path);
|
|
5940
|
+
for (let i = 0; i < 5 && !existsSync7(join8(dir, "package.json")); i++) dir = dirname3(dir);
|
|
5941
|
+
for (const name of [".env", ".env.local"]) {
|
|
5942
|
+
const file = join8(dir, name);
|
|
5943
|
+
if (!existsSync7(file)) continue;
|
|
5944
|
+
for (const line of readFileSync5(file, "utf8").split("\n")) {
|
|
5945
|
+
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
5946
|
+
if (!m || m[1] in process.env) continue;
|
|
5947
|
+
let val = m[2].trim();
|
|
5948
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
|
|
5949
|
+
else val = val.replace(/\s+#.*$/, "").trim();
|
|
5950
|
+
process.env[m[1]] = val;
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5798
5954
|
function apiKeysFromEnv() {
|
|
5799
5955
|
const e = process.env, keys = {};
|
|
5800
5956
|
for (const provider of listProviders()) {
|
|
@@ -5986,6 +6142,9 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0) {
|
|
|
5986
6142
|
function turnCost(model, usage) {
|
|
5987
6143
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0);
|
|
5988
6144
|
}
|
|
6145
|
+
function errInfo(e) {
|
|
6146
|
+
return { message: String(e?.message ?? e), statusCode: e?.statusCode, code: e?.code };
|
|
6147
|
+
}
|
|
5989
6148
|
function fmtUsd(n) {
|
|
5990
6149
|
return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
5991
6150
|
}
|
|
@@ -6210,7 +6369,8 @@ function jsonResult(res, session) {
|
|
|
6210
6369
|
steps: res.steps,
|
|
6211
6370
|
tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
|
|
6212
6371
|
usage: res.usage,
|
|
6213
|
-
sessionId: session.meta.id
|
|
6372
|
+
sessionId: session.meta.id,
|
|
6373
|
+
...res.finishReason === "error" && res.error ? { error: res.error?.message ?? String(res.error) } : {}
|
|
6214
6374
|
};
|
|
6215
6375
|
}
|
|
6216
6376
|
async function readMultiline(readLine) {
|
|
@@ -6253,6 +6413,12 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd()) {
|
|
|
6253
6413
|
spinner.stop();
|
|
6254
6414
|
err(red(` error: ${e?.message ?? e}
|
|
6255
6415
|
`));
|
|
6416
|
+
(session.meta.events ??= []).push({ ts: t0, durationMs: Date.now() - t0, model: agent.options.model, finishReason: "error", steps: 0, tools: 0, error: errInfo(e) });
|
|
6417
|
+
session.meta.updated = Date.now();
|
|
6418
|
+
try {
|
|
6419
|
+
store.save(session);
|
|
6420
|
+
} catch {
|
|
6421
|
+
}
|
|
6256
6422
|
return { ok: false };
|
|
6257
6423
|
} finally {
|
|
6258
6424
|
spinner.stop();
|
|
@@ -6267,8 +6433,13 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd()) {
|
|
|
6267
6433
|
const lastUser = res.messages.map((m2) => m2.role).lastIndexOf("user");
|
|
6268
6434
|
const tools = res.messages.slice(lastUser).filter((m2) => m2.role === "tool").length;
|
|
6269
6435
|
const ok = res.finishReason === "stop";
|
|
6270
|
-
|
|
6436
|
+
const shortId = session.meta.id.slice(-10);
|
|
6437
|
+
err("\n" + (ok ? green(" \u2713 done") : red(` \u2717 ${res.finishReason}`)) + dim(` \xB7 ${res.steps} steps \xB7 ${tools} tools \xB7 ${tok}${secs}s \xB7 ${shortId}
|
|
6271
6438
|
`));
|
|
6439
|
+
if (res.finishReason === "error" && res.error) {
|
|
6440
|
+
const e = res.error;
|
|
6441
|
+
err(red(` ${e?.message ?? e}`) + (e?.statusCode ? dim(` (${e.statusCode}${e.code ? " " + e.code : ""})`) : "") + "\n");
|
|
6442
|
+
}
|
|
6272
6443
|
session.messages = agent.transcript;
|
|
6273
6444
|
session.meta.turns += 1;
|
|
6274
6445
|
session.meta.tokens = (session.meta.tokens ?? 0) + (res.usage?.totalTokens ?? 0);
|
|
@@ -6276,6 +6447,9 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd()) {
|
|
|
6276
6447
|
if (res.usageEstimated) session.meta.costEstimated = true;
|
|
6277
6448
|
session.meta.updated = Date.now();
|
|
6278
6449
|
session.meta.model = agent.options.model;
|
|
6450
|
+
const ev = { ts: t0, durationMs: Date.now() - t0, model: agent.options.model, finishReason: res.finishReason, steps: res.steps, tools, tokens: res.usage?.totalTokens, costUsd: cost, estimated: res.usageEstimated };
|
|
6451
|
+
if (res.finishReason === "error" && res.error) ev.error = errInfo(res.error);
|
|
6452
|
+
(session.meta.events ??= []).push(ev);
|
|
6279
6453
|
if (!session.meta.title) session.meta.title = titleOf(agent.transcript);
|
|
6280
6454
|
try {
|
|
6281
6455
|
store.save(session);
|
|
@@ -6307,7 +6481,10 @@ function startSession(args, store, agent, cwd) {
|
|
|
6307
6481
|
`));
|
|
6308
6482
|
}
|
|
6309
6483
|
const now = Date.now();
|
|
6310
|
-
|
|
6484
|
+
const id = args.sessionId ?? store.newId(now);
|
|
6485
|
+
if (!args.task) err(dim(` session ${id}
|
|
6486
|
+
`));
|
|
6487
|
+
return { meta: { id, created: now, updated: now, cwd, model: agent.options.model, turns: 0, title: "" }, messages: [] };
|
|
6311
6488
|
}
|
|
6312
6489
|
var AGENTS_MD_TEMPLATE = `# ${"${name}"}
|
|
6313
6490
|
|
|
@@ -7082,7 +7259,7 @@ ${extra}` : body, checkpoints, cwd);
|
|
|
7082
7259
|
}
|
|
7083
7260
|
});
|
|
7084
7261
|
}
|
|
7085
|
-
const promptStr = bold(cyan("
|
|
7262
|
+
const promptStr = bold(cyan("agentx \u203A "));
|
|
7086
7263
|
const contPrompt = dim(" \u2026 \u203A ");
|
|
7087
7264
|
const classifyPaste = pastePathClassifier(cwd);
|
|
7088
7265
|
const releaseStdin = () => {
|
|
@@ -7244,6 +7421,7 @@ async function main() {
|
|
|
7244
7421
|
if (!id) process.exit(0);
|
|
7245
7422
|
args.resume = id;
|
|
7246
7423
|
}
|
|
7424
|
+
loadInstallEnv();
|
|
7247
7425
|
const apiKeys = { ...cfg.apiKeys, ...apiKeysFromEnv() };
|
|
7248
7426
|
if (!Object.keys(apiKeys).length) {
|
|
7249
7427
|
console.error(red("No provider key found. Set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY), e.g. in .env."));
|