@tokagent/tokagentos 2.0.33 → 2.0.35
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/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Remove LLM provider API keys (OPENROUTER, ANTHROPIC, OPENAI, GOOGLE, GROQ)
|
|
4
|
+
* from the agents DB table. mergeDbSettings() in @elizaos/core loads
|
|
5
|
+
* agents.secrets and agents.settings.secrets into character.settings.secrets
|
|
6
|
+
* on every boot, and runtime.getSetting() checks character.settings.secrets
|
|
7
|
+
* BEFORE process.env — so a stale DB-stored key shadows the .env value
|
|
8
|
+
* forever. After running this, .env becomes the source of truth.
|
|
9
|
+
*
|
|
10
|
+
* Run with the agent STOPPED (`Ctrl-C` your `bun run dev`):
|
|
11
|
+
* bun run scripts/clear-db-llm-secrets.mjs
|
|
12
|
+
*/
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const PROJECT_ROOT = path.resolve(__dirname, "..");
|
|
19
|
+
const DB_DIR = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
|
|
20
|
+
|
|
21
|
+
if (!existsSync(DB_DIR)) {
|
|
22
|
+
console.error(`PGLite directory not found at ${DB_DIR}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const LLM_KEYS = [
|
|
27
|
+
"OPENROUTER_API_KEY",
|
|
28
|
+
"ANTHROPIC_API_KEY",
|
|
29
|
+
"OPENAI_API_KEY",
|
|
30
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
31
|
+
"GOOGLE_API_KEY",
|
|
32
|
+
"GROQ_API_KEY",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const { PGlite } = await import("@electric-sql/pglite");
|
|
36
|
+
const db = new PGlite(DB_DIR);
|
|
37
|
+
await db.waitReady;
|
|
38
|
+
|
|
39
|
+
const before = await db.query(
|
|
40
|
+
"SELECT id, name, secrets, settings FROM agents",
|
|
41
|
+
);
|
|
42
|
+
console.log(`Agents in DB: ${before.rows.length}`);
|
|
43
|
+
for (const row of before.rows) {
|
|
44
|
+
const id = row.id;
|
|
45
|
+
const name = row.name;
|
|
46
|
+
const topSecrets = row.secrets && typeof row.secrets === "object" ? row.secrets : {};
|
|
47
|
+
const settingsSecrets =
|
|
48
|
+
row.settings &&
|
|
49
|
+
typeof row.settings === "object" &&
|
|
50
|
+
row.settings.secrets &&
|
|
51
|
+
typeof row.settings.secrets === "object"
|
|
52
|
+
? row.settings.secrets
|
|
53
|
+
: {};
|
|
54
|
+
const allKeys = new Set([
|
|
55
|
+
...Object.keys(topSecrets),
|
|
56
|
+
...Object.keys(settingsSecrets),
|
|
57
|
+
]);
|
|
58
|
+
const llmKeysPresent = [...allKeys].filter((k) => LLM_KEYS.includes(k));
|
|
59
|
+
if (llmKeysPresent.length === 0) {
|
|
60
|
+
console.log(` ${name} (${id}): no LLM keys stored ✓`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
console.log(` ${name} (${id}): clearing ${llmKeysPresent.join(", ")}`);
|
|
64
|
+
const newTopSecrets = { ...topSecrets };
|
|
65
|
+
const newSettings =
|
|
66
|
+
row.settings && typeof row.settings === "object" ? { ...row.settings } : {};
|
|
67
|
+
const newSettingsSecrets = { ...settingsSecrets };
|
|
68
|
+
for (const k of llmKeysPresent) {
|
|
69
|
+
delete newTopSecrets[k];
|
|
70
|
+
delete newSettingsSecrets[k];
|
|
71
|
+
}
|
|
72
|
+
newSettings.secrets = newSettingsSecrets;
|
|
73
|
+
await db.query(
|
|
74
|
+
"UPDATE agents SET secrets = $1, settings = $2 WHERE id = $3",
|
|
75
|
+
[
|
|
76
|
+
JSON.stringify(newTopSecrets),
|
|
77
|
+
JSON.stringify(newSettings),
|
|
78
|
+
id,
|
|
79
|
+
],
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("\nDone. Restart `bun run dev` — .env is now the source of truth.");
|
|
84
|
+
await db.close();
|
|
@@ -229,6 +229,148 @@ if (env.OPENROUTER_API_KEY) {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* DB-shadow check.
|
|
234
|
+
*
|
|
235
|
+
* Failure mode observed in the field: an LLM key set during first-boot
|
|
236
|
+
* onboarding is persisted into the `agents` table (`secrets` and
|
|
237
|
+
* `settings.secrets` columns). On every restart, mergeDbSettings() in
|
|
238
|
+
* @elizaos/core copies it into character.settings.secrets, and
|
|
239
|
+
* runtime.getSetting() reads that BEFORE process.env. So rotating .env
|
|
240
|
+
* does nothing — the DB value wins forever, even after .env is changed.
|
|
241
|
+
*
|
|
242
|
+
* We open PGLite read-only, scan agents.secrets and agents.settings.secrets,
|
|
243
|
+
* and warn loudly if any LLM key differs from .env. We don't auto-mutate
|
|
244
|
+
* the DB — clearing secrets is a destructive operation that should be
|
|
245
|
+
* explicit (use scripts/clear-db-llm-secrets.mjs).
|
|
246
|
+
*
|
|
247
|
+
* Best-effort only: skipped if PGLite isn't installed, the DB dir doesn't
|
|
248
|
+
* exist (fresh project), or the `agents` table isn't there yet (pre-boot).
|
|
249
|
+
*/
|
|
250
|
+
async function checkDbShadow() {
|
|
251
|
+
const dbDir = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
|
|
252
|
+
if (!existsSync(dbDir)) return;
|
|
253
|
+
let PGlite;
|
|
254
|
+
try {
|
|
255
|
+
({ PGlite } = await import("@electric-sql/pglite"));
|
|
256
|
+
} catch {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
let db;
|
|
260
|
+
try {
|
|
261
|
+
db = new PGlite(dbDir);
|
|
262
|
+
await db.waitReady;
|
|
263
|
+
} catch {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const result = await db.query(
|
|
268
|
+
"SELECT id, name, secrets, settings FROM agents",
|
|
269
|
+
);
|
|
270
|
+
for (const row of result.rows) {
|
|
271
|
+
const topSecrets =
|
|
272
|
+
row.secrets && typeof row.secrets === "object" ? row.secrets : {};
|
|
273
|
+
const settingsSecrets =
|
|
274
|
+
row.settings &&
|
|
275
|
+
typeof row.settings === "object" &&
|
|
276
|
+
row.settings.secrets &&
|
|
277
|
+
typeof row.settings.secrets === "object"
|
|
278
|
+
? row.settings.secrets
|
|
279
|
+
: {};
|
|
280
|
+
for (const key of SHADOWED_KEYS) {
|
|
281
|
+
const dbValue = topSecrets[key] ?? settingsSecrets[key];
|
|
282
|
+
const dotenvValue = env[key];
|
|
283
|
+
if (!dbValue) continue;
|
|
284
|
+
if (!dotenvValue) {
|
|
285
|
+
// DB has it, .env doesn't. Probably set via onboarding UI and not
|
|
286
|
+
// mirrored. Not necessarily a bug — but flag it so the user knows.
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (dbValue !== dotenvValue) {
|
|
290
|
+
fail(
|
|
291
|
+
`${key} in PGLite database (agent "${row.name}") differs from .env.\n` +
|
|
292
|
+
` db : ${String(dbValue).slice(0, 12)}…${String(dbValue).slice(-4)} (this is what the agent reads)\n` +
|
|
293
|
+
` .env : ${String(dotenvValue).slice(0, 12)}…${String(dotenvValue).slice(-4)} (this is what you probably edited)\n` +
|
|
294
|
+
` mergeDbSettings() in @elizaos/core loads the DB value into character.settings.secrets, and\n` +
|
|
295
|
+
` runtime.getSetting() reads that BEFORE process.env. Editing .env doesn't help.\n` +
|
|
296
|
+
` Fix: stop \`bun run dev\` and run \`bun run scripts/clear-db-llm-secrets.mjs\`,\n` +
|
|
297
|
+
` then start again. .env will then be the source of truth.`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
// agents table doesn't exist yet (fresh boot) — nothing to check.
|
|
304
|
+
} finally {
|
|
305
|
+
try {
|
|
306
|
+
await db.close();
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await checkDbShadow();
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* No-provider-configured check.
|
|
315
|
+
*
|
|
316
|
+
* Failure mode observed in the field: the user clears all direct provider
|
|
317
|
+
* keys (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, etc.) intending to run in
|
|
318
|
+
* x402-only mode, but doesn't set BILLING_CHAT_KEY. The runtime then has
|
|
319
|
+
* NO TEXT_SMALL/TEXT_LARGE provider registered — every chat request fails
|
|
320
|
+
* with `[router] No provider registered for TEXT_SMALL`, which is opaque
|
|
321
|
+
* about the actual fix (mint a sk-ai-* key and set BILLING_CHAT_KEY).
|
|
322
|
+
*
|
|
323
|
+
* Wire (see core-plugins.ts:configureBillingChatMirror): when both
|
|
324
|
+
* BILLING_CHAT_KEY and TOKAGENT_GATEWAY_URL are set, OPENAI_API_KEY +
|
|
325
|
+
* OPENAI_BASE_URL are mirrored onto the gateway and plugin-openai loads
|
|
326
|
+
* as the gateway provider. Either one alone is a no-op.
|
|
327
|
+
*/
|
|
328
|
+
const DIRECT_LLM_KEYS = [
|
|
329
|
+
"ANTHROPIC_API_KEY",
|
|
330
|
+
"OPENAI_API_KEY",
|
|
331
|
+
"OPENROUTER_API_KEY",
|
|
332
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
333
|
+
"GOOGLE_API_KEY",
|
|
334
|
+
"GEMINI_API_KEY",
|
|
335
|
+
"GROQ_API_KEY",
|
|
336
|
+
"XAI_API_KEY",
|
|
337
|
+
"DEEPSEEK_API_KEY",
|
|
338
|
+
"MISTRAL_API_KEY",
|
|
339
|
+
"TOGETHER_API_KEY",
|
|
340
|
+
"OLLAMA_BASE_URL",
|
|
341
|
+
];
|
|
342
|
+
const hasDirectKey = DIRECT_LLM_KEYS.some((k) => env[k]?.trim());
|
|
343
|
+
const hasBillingChatKey = !!env.BILLING_CHAT_KEY?.trim();
|
|
344
|
+
const hasGatewayUrl = !!env.TOKAGENT_GATEWAY_URL?.trim();
|
|
345
|
+
const hasLiteLLM =
|
|
346
|
+
!!env.LITELLM_BASE_URL?.trim() && !!env.LITELLM_API_KEY?.trim();
|
|
347
|
+
if (!hasDirectKey && !hasBillingChatKey && !hasLiteLLM) {
|
|
348
|
+
fail(
|
|
349
|
+
`No LLM provider configured. The agent has nothing to call for chat.\n` +
|
|
350
|
+
` Pick one of these three paths:\n` +
|
|
351
|
+
`\n` +
|
|
352
|
+
` [A] Direct provider: set one of ${DIRECT_LLM_KEYS.slice(0, 4).join(", ")},\n` +
|
|
353
|
+
` etc. in .env. Plugin auto-loads, chat hits the provider directly.\n` +
|
|
354
|
+
`\n` +
|
|
355
|
+
` [B] x402 billing (Tokamak rail): mint a sk-ai-* key from the billing\n` +
|
|
356
|
+
` dashboard, then set BILLING_CHAT_KEY=sk-ai-... in .env.\n` +
|
|
357
|
+
` Open http://localhost:3000/tokagent-billing/v1/billing/dashboard/\n` +
|
|
358
|
+
` → connect wallet → top up PTON → mint key. Without this, the\n` +
|
|
359
|
+
` runtime router has zero TEXT_SMALL/TEXT_LARGE providers.\n` +
|
|
360
|
+
`\n` +
|
|
361
|
+
` [C] LiteLLM proxy: set LITELLM_BASE_URL + LITELLM_API_KEY in .env\n` +
|
|
362
|
+
` (plus LITELLM_SMALL_MODEL / LITELLM_LARGE_MODEL).`,
|
|
363
|
+
);
|
|
364
|
+
} else if (hasBillingChatKey && !hasGatewayUrl) {
|
|
365
|
+
fail(
|
|
366
|
+
`BILLING_CHAT_KEY is set but TOKAGENT_GATEWAY_URL is not. The billing\n` +
|
|
367
|
+
` mirror in core-plugins.ts requires BOTH to wire the gateway as an\n` +
|
|
368
|
+
` OpenAI-compatible provider — set TOKAGENT_GATEWAY_URL to the URL of\n` +
|
|
369
|
+
` the tokagent-billing-server you're a client of (the hosted default is\n` +
|
|
370
|
+
` https://billing-service-production-a8e7.up.railway.app).`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
232
374
|
if (hadFailure) {
|
|
233
375
|
console.error(
|
|
234
376
|
"\n\x1b[31m[verify-llm-plugins]\x1b[0m One or more LLM-provider checks failed. Chat will not work until this is resolved.\n",
|