@tokagent/tokagentos 2.0.24 → 2.0.30
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 +1 -1
- package/scaffold-patches/packages/agent/src/api/plugin-routes.ts +1889 -0
- package/scaffold-patches/packages/agent/src/api/server.ts +4509 -0
- package/scaffold-patches/packages/agent/src/api/trigger-routes.ts +942 -0
- package/scaffold-patches/packages/agent/src/runtime/core-plugins.ts +4 -0
- package/scaffold-patches/packages/agent/src/triggers/runtime.ts +955 -0
- package/scaffold-patches/packages/app-core/src/api/automations-compat-routes.ts +924 -0
- package/scaffold-patches/packages/app-core/src/api/client-agent.ts +2755 -0
- package/scaffold-patches/packages/app-core/src/components/pages/AutomationsView.tsx +446 -26
- package/scaffold-patches/packages/app-core/src/components/pages/SettingsView.tsx +155 -0
- package/scaffold-patches/packages/shared/src/onboarding-presets.characters.ts +16 -16
- package/templates/fullstack-app/package.json +9 -5
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/package.json +1 -1
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/__tests__/routes/estimate-routes.test.ts +5 -2
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/dashboard/app.js +896 -19
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/dashboard/index.html +280 -94
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/dashboard/style.css +969 -235
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/routes/keys-routes.ts +170 -0
- package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/routes/messages-proxy-routes.ts +114 -3
- package/templates/fullstack-app/plugins/plugin-web-fetch/build.ts +35 -0
- package/templates/fullstack-app/plugins/plugin-web-fetch/package.json +37 -0
- package/templates/fullstack-app/plugins/plugin-web-fetch/src/index.ts +471 -0
- package/templates/fullstack-app/plugins/plugin-web-fetch/tsconfig.json +20 -0
- package/templates/fullstack-app/scripts/ensure-plugin-builds.mjs +1 -0
- package/templates/fullstack-app/scripts/verify-llm-plugins.mjs +122 -0
- package/templates-manifest.json +1 -1
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import type { Route, RouteRequest, RouteResponse, IAgentRuntime } from "@elizaos/core";
|
|
15
15
|
import type { IncomingMessage } from "node:http";
|
|
16
|
+
import fs from "node:fs/promises";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import process from "node:process";
|
|
16
19
|
import {
|
|
17
20
|
mintApiKey,
|
|
18
21
|
listApiKeys,
|
|
@@ -184,6 +187,149 @@ async function handleRevokeKey(
|
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// POST /v1/keys/install — write BILLING_CHAT_KEY to project .env
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
//
|
|
194
|
+
// LOCAL ONLY. This endpoint runs on the user's local agent (whether it's
|
|
195
|
+
// configured as billing client or billing server) and:
|
|
196
|
+
// 1. Validates the request body has a syntactically valid `sk-ai-*` key
|
|
197
|
+
// 2. Atomically upserts `BILLING_CHAT_KEY=<key>` into `<cwd>/.env`
|
|
198
|
+
// (preserving all other entries; existing commented `# BILLING_CHAT_KEY`
|
|
199
|
+
// lines are replaced in place rather than duplicated)
|
|
200
|
+
// 3. Mirrors the new value into process.env immediately so in-flight
|
|
201
|
+
// chat calls pick it up without waiting for the restart
|
|
202
|
+
//
|
|
203
|
+
// The restart itself is DELEGATED to the existing `POST /api/restart`
|
|
204
|
+
// endpoint — the dashboard calls this install endpoint first, and then
|
|
205
|
+
// the restart endpoint second. Splitting them avoids duplicating restart
|
|
206
|
+
// strategy logic across runners (dev-ui in-process bounce, prod CLI
|
|
207
|
+
// supervisor catching exit 75, etc.) and keeps this route a pure
|
|
208
|
+
// "write the file" operation.
|
|
209
|
+
//
|
|
210
|
+
// AUTH: requires the same authenticated identity as the rest of /v1/keys/*
|
|
211
|
+
// (SIWE session OR existing API key). Format-validates `sk-ai-...` but does
|
|
212
|
+
// not verify the key was minted by this user — anyone with shell access to
|
|
213
|
+
// the user's machine could already edit .env directly, so the auth check
|
|
214
|
+
// is meant to guard against trivial CSRF, not a malicious LAN attacker.
|
|
215
|
+
const SK_AI_KEY_RE = /^sk-ai-[A-Za-z0-9_-]{16,128}$/;
|
|
216
|
+
|
|
217
|
+
async function readIfExists(filePath: string): Promise<string | null> {
|
|
218
|
+
try {
|
|
219
|
+
return await fs.readFile(filePath, "utf8");
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Atomically upsert `KEY=VALUE` in a project-root .env file.
|
|
228
|
+
*
|
|
229
|
+
* - If the key already exists on a line (commented or not), replace that
|
|
230
|
+
* line with the new uncommented `KEY=VALUE`.
|
|
231
|
+
* - Otherwise append `KEY=VALUE` to the end (with one preceding blank line
|
|
232
|
+
* if the file ends with non-empty content).
|
|
233
|
+
*
|
|
234
|
+
* Atomicity: write to `<filePath>.tmp` then rename. The rename is atomic
|
|
235
|
+
* on POSIX. We do NOT keep a `.bak` for the project .env because users
|
|
236
|
+
* version-control their .env templates separately and the .env itself is
|
|
237
|
+
* gitignored — a `.bak` would just be visual noise.
|
|
238
|
+
*
|
|
239
|
+
* Values are written verbatim (no quoting). sk-ai-* keys are URL-safe
|
|
240
|
+
* base64 (`/^sk-ai-[A-Za-z0-9_-]+$/`) so they never need quoting; callers
|
|
241
|
+
* MUST validate before invoking this function.
|
|
242
|
+
*/
|
|
243
|
+
async function upsertDotenvLine(
|
|
244
|
+
filePath: string,
|
|
245
|
+
key: string,
|
|
246
|
+
value: string,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const existing = (await readIfExists(filePath)) ?? "";
|
|
249
|
+
const lines = existing.length === 0 ? [] : existing.split(/\r?\n/);
|
|
250
|
+
// dotenv-style split leaves a trailing empty element for files ending in
|
|
251
|
+
// newline. Strip it so we can manage trailing newlines explicitly.
|
|
252
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
253
|
+
lines.pop();
|
|
254
|
+
}
|
|
255
|
+
const re = new RegExp(`^\\s*#?\\s*${key.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}\\s*=`);
|
|
256
|
+
let updatedAt = -1;
|
|
257
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
258
|
+
if (re.test(lines[i] ?? "")) {
|
|
259
|
+
lines[i] = `${key}=${value}`;
|
|
260
|
+
updatedAt = i;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (updatedAt < 0) {
|
|
265
|
+
if (lines.length > 0 && (lines[lines.length - 1] ?? "").trim() !== "") {
|
|
266
|
+
lines.push("");
|
|
267
|
+
}
|
|
268
|
+
lines.push(`${key}=${value}`);
|
|
269
|
+
}
|
|
270
|
+
const nextContents = `${lines.join("\n")}\n`;
|
|
271
|
+
const tmp = `${filePath}.tmp`;
|
|
272
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
273
|
+
const handle = await fs.open(tmp, "w", 0o600);
|
|
274
|
+
try {
|
|
275
|
+
await handle.writeFile(nextContents, "utf8");
|
|
276
|
+
await handle.sync();
|
|
277
|
+
} finally {
|
|
278
|
+
await handle.close();
|
|
279
|
+
}
|
|
280
|
+
await fs.rename(tmp, filePath);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function handleInstallKey(
|
|
284
|
+
req: RouteRequest,
|
|
285
|
+
res: RouteResponse,
|
|
286
|
+
_runtime: IAgentRuntime,
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
// Auth check. In client-mode, `resolveBillingIdentity` returns null (no
|
|
289
|
+
// local DB / authSecret) — but the local user is the one running the
|
|
290
|
+
// server, and we serve this from localhost only, so we accept the request
|
|
291
|
+
// unconditionally in client-mode as long as the body is well-formed.
|
|
292
|
+
// In server-mode, require a valid identity.
|
|
293
|
+
const identity = await resolveBillingIdentity(toIncomingMessage(req));
|
|
294
|
+
const billingState = getBillingState();
|
|
295
|
+
const isClientMode = billingState.config.billingMode === "client";
|
|
296
|
+
if (!identity && !isClientMode) {
|
|
297
|
+
res.status(401).json({ error: "Authentication required." });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const body = req.body as Record<string, unknown> | undefined;
|
|
302
|
+
const key = typeof body?.["key"] === "string" ? body["key"].trim() : "";
|
|
303
|
+
if (!SK_AI_KEY_RE.test(key)) {
|
|
304
|
+
res.status(400).json({
|
|
305
|
+
error: "Invalid key format — expected sk-ai-... (16+ url-safe chars).",
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
311
|
+
try {
|
|
312
|
+
await upsertDotenvLine(envPath, "BILLING_CHAT_KEY", key);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
315
|
+
res.status(500).json({ error: `Failed to write .env: ${message}` });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Update in-flight env so subsequent chat calls work even before restart.
|
|
320
|
+
// configureBillingChatMirror() at startup mirrors BILLING_CHAT_KEY → OPENAI_API_KEY,
|
|
321
|
+
// but the OpenAI plugin may cache its key at init — restart is still the safe
|
|
322
|
+
// path. The dashboard calls POST /api/restart after this returns 200.
|
|
323
|
+
process.env["BILLING_CHAT_KEY"] = key;
|
|
324
|
+
process.env["OPENAI_API_KEY"] = key;
|
|
325
|
+
|
|
326
|
+
res.status(200).json({
|
|
327
|
+
ok: true,
|
|
328
|
+
envPath,
|
|
329
|
+
message: "Key saved to .env. Call POST /api/restart to apply.",
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
187
333
|
// ---------------------------------------------------------------------------
|
|
188
334
|
// Route definitions
|
|
189
335
|
// ---------------------------------------------------------------------------
|
|
@@ -197,6 +343,18 @@ export const keysRoutes: Route[] = [
|
|
|
197
343
|
name: "billing-keys-mint",
|
|
198
344
|
handler: handleMintKey,
|
|
199
345
|
},
|
|
346
|
+
// MUST be registered BEFORE the /v1/keys/:id DELETE route in the array
|
|
347
|
+
// (routes are matched in registration order on rawPath: true with
|
|
348
|
+
// params). `install` is a string literal that could otherwise match the
|
|
349
|
+
// `:id` param and route the install POST through revoke handling.
|
|
350
|
+
{
|
|
351
|
+
type: "POST",
|
|
352
|
+
path: "/v1/keys/install",
|
|
353
|
+
rawPath: true,
|
|
354
|
+
public: true,
|
|
355
|
+
name: "billing-keys-install",
|
|
356
|
+
handler: handleInstallKey,
|
|
357
|
+
},
|
|
200
358
|
{
|
|
201
359
|
type: "GET",
|
|
202
360
|
path: "/v1/keys",
|
|
@@ -235,6 +393,18 @@ function clientKeysRoutes(): Route[] {
|
|
|
235
393
|
);
|
|
236
394
|
},
|
|
237
395
|
},
|
|
396
|
+
// /v1/keys/install is LOCAL on both modes (writes the local agent's
|
|
397
|
+
// own .env), so it uses the same direct handler as server-mode.
|
|
398
|
+
// Must precede /v1/keys/:id in this array for the same param-matching
|
|
399
|
+
// reason explained on the server-mode array.
|
|
400
|
+
{
|
|
401
|
+
type: "POST",
|
|
402
|
+
path: "/v1/keys/install",
|
|
403
|
+
rawPath: true,
|
|
404
|
+
public: true,
|
|
405
|
+
name: "billing-keys-install",
|
|
406
|
+
handler: handleInstallKey,
|
|
407
|
+
},
|
|
238
408
|
{
|
|
239
409
|
type: "GET",
|
|
240
410
|
path: "/v1/keys",
|
package/templates/fullstack-app/plugins/plugin-tokagent-billing/src/routes/messages-proxy-routes.ts
CHANGED
|
@@ -26,12 +26,115 @@ import type { Route, RouteRequest, RouteResponse, IAgentRuntime } from "@elizaos
|
|
|
26
26
|
import type { IncomingMessage } from "node:http";
|
|
27
27
|
import { getBillingState, isBillingStateInitialized } from "../state.js";
|
|
28
28
|
import { applyBillingGate } from "../middleware/billing-gate.js";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
computeActualCostUsd,
|
|
31
|
+
estimateInputTokens,
|
|
32
|
+
} from "@tokagentos/billing";
|
|
30
33
|
|
|
31
34
|
function billingUnavailable(res: RouteResponse): void {
|
|
32
35
|
res.status(503).json({ error: "Billing service unavailable." });
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Anthropic prompt-cache auto-injection
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Anthropic's minimum cacheable prefix is 1024 tokens for Sonnet/Opus and
|
|
44
|
+
* 2048 for Haiku. Below that the cache_control marker is a no-op. Use the
|
|
45
|
+
* stricter bound so the optimization always pays off when we add it.
|
|
46
|
+
*/
|
|
47
|
+
const MIN_CACHEABLE_PREFIX_TOKENS = 2048;
|
|
48
|
+
|
|
49
|
+
/** Returns true if any node anywhere in `value` has a `cache_control` key. */
|
|
50
|
+
function hasCacheControlDeep(value: unknown): boolean {
|
|
51
|
+
if (!value || typeof value !== "object") return false;
|
|
52
|
+
if (Array.isArray(value)) return value.some(hasCacheControlDeep);
|
|
53
|
+
const obj = value as Record<string, unknown>;
|
|
54
|
+
if ("cache_control" in obj) return true;
|
|
55
|
+
for (const k of Object.keys(obj)) {
|
|
56
|
+
if (hasCacheControlDeep(obj[k])) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Auto-inject Anthropic prompt-cache markers on stable parts of the request.
|
|
63
|
+
*
|
|
64
|
+
* The billing engine already supports cache pricing end-to-end (see
|
|
65
|
+
* pricing/rates.ts cacheRead/cacheWrite columns and computeActualCostUsd),
|
|
66
|
+
* but most anthropic-sdk callers never set cache_control themselves. Without
|
|
67
|
+
* markers, Anthropic re-reads the full system + tools prefix on every turn
|
|
68
|
+
* at base input rate. With markers, the prefix is served from cache at ~10×
|
|
69
|
+
* cheaper after the first call within 5 minutes.
|
|
70
|
+
*
|
|
71
|
+
* What we touch:
|
|
72
|
+
* - `system`: normalised to array form, marker on the LAST text block
|
|
73
|
+
* - `tools`: marker on the LAST tool definition (Anthropic caches the
|
|
74
|
+
* entire prefix up to and including the marker, so this also covers
|
|
75
|
+
* `system`)
|
|
76
|
+
*
|
|
77
|
+
* What we DON'T touch:
|
|
78
|
+
* - Non-Claude models — other providers ignore or reject the field; their
|
|
79
|
+
* caching is implicit.
|
|
80
|
+
* - Bodies that already have ANY cache_control set — respect client intent.
|
|
81
|
+
* - Bodies whose stable prefix is below Anthropic's minimum cacheable size.
|
|
82
|
+
*
|
|
83
|
+
* Returns a new body when injection happens; the same reference otherwise.
|
|
84
|
+
* Never mutates the input.
|
|
85
|
+
*/
|
|
86
|
+
function maybeInjectAnthropicCache(
|
|
87
|
+
body: Record<string, unknown>,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const model = body.model;
|
|
90
|
+
if (typeof model !== "string" || !model.startsWith("claude-")) return body;
|
|
91
|
+
if (hasCacheControlDeep(body)) return body;
|
|
92
|
+
|
|
93
|
+
const tools = Array.isArray(body.tools) ? body.tools : undefined;
|
|
94
|
+
const sys = body.system;
|
|
95
|
+
const prefixTokens = estimateInputTokens([], tools, sys);
|
|
96
|
+
if (prefixTokens < MIN_CACHEABLE_PREFIX_TOKENS) return body;
|
|
97
|
+
|
|
98
|
+
const next: Record<string, unknown> = { ...body };
|
|
99
|
+
|
|
100
|
+
if (typeof sys === "string" && sys.length > 0) {
|
|
101
|
+
next.system = [
|
|
102
|
+
{ type: "text", text: sys, cache_control: { type: "ephemeral" } },
|
|
103
|
+
];
|
|
104
|
+
} else if (Array.isArray(sys) && sys.length > 0) {
|
|
105
|
+
const cloned = sys.map((b) =>
|
|
106
|
+
b && typeof b === "object" ? { ...(b as Record<string, unknown>) } : b,
|
|
107
|
+
);
|
|
108
|
+
for (let i = cloned.length - 1; i >= 0; i--) {
|
|
109
|
+
const blk = cloned[i];
|
|
110
|
+
if (
|
|
111
|
+
blk &&
|
|
112
|
+
typeof blk === "object" &&
|
|
113
|
+
(blk as Record<string, unknown>).type === "text"
|
|
114
|
+
) {
|
|
115
|
+
(cloned[i] as Record<string, unknown>).cache_control = {
|
|
116
|
+
type: "ephemeral",
|
|
117
|
+
};
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
next.system = cloned;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (tools && tools.length > 0) {
|
|
125
|
+
const clonedTools = tools.map((t) =>
|
|
126
|
+
t && typeof t === "object" ? { ...(t as Record<string, unknown>) } : t,
|
|
127
|
+
);
|
|
128
|
+
const last = clonedTools[clonedTools.length - 1];
|
|
129
|
+
if (last && typeof last === "object") {
|
|
130
|
+
(last as Record<string, unknown>).cache_control = { type: "ephemeral" };
|
|
131
|
+
}
|
|
132
|
+
next.tools = clonedTools;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return next;
|
|
136
|
+
}
|
|
137
|
+
|
|
35
138
|
/**
|
|
36
139
|
* Convert a plugin RouteRequest into the IncomingMessage shape that
|
|
37
140
|
* applyBillingGate / resolveBillingIdentity expect.
|
|
@@ -104,14 +207,22 @@ async function proxyToLiteLLM(
|
|
|
104
207
|
const config = state.config;
|
|
105
208
|
if (!config.enabled) return billingUnavailable(res);
|
|
106
209
|
|
|
107
|
-
const
|
|
108
|
-
if (!
|
|
210
|
+
const rawBody = req.body as Record<string, unknown> | undefined;
|
|
211
|
+
if (!rawBody || typeof rawBody !== "object") {
|
|
109
212
|
res.status(400).json({
|
|
110
213
|
error: { type: "invalid_request_error", message: "JSON body required" },
|
|
111
214
|
});
|
|
112
215
|
return;
|
|
113
216
|
}
|
|
114
217
|
|
|
218
|
+
// Auto-inject Anthropic prompt-cache markers on stable parts of the
|
|
219
|
+
// request. Done BEFORE the billing gate so the reservation sees the
|
|
220
|
+
// markers (gate.detectCacheControl reads them to size at cacheWrite rate
|
|
221
|
+
// — slightly higher first-call reservation, dramatically lower steady
|
|
222
|
+
// state). No-op for non-Claude models or bodies where the caller already
|
|
223
|
+
// set cache_control. See maybeInjectAnthropicCache for the full policy.
|
|
224
|
+
const body = maybeInjectAnthropicCache(rawBody);
|
|
225
|
+
|
|
115
226
|
// Detect streaming. plugin-openai (Vercel AI SDK) defaults to
|
|
116
227
|
// stream:true and there's no way to disable from the agent's chat flow,
|
|
117
228
|
// so we MUST support it. For non-stream we buffer the JSON response;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Build script for @tokagent/plugin-web-fetch. Produces ESM in dist/.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, rmSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
const watch = process.argv.includes("--watch");
|
|
8
|
+
|
|
9
|
+
async function build() {
|
|
10
|
+
if (existsSync("dist")) rmSync("dist", { recursive: true });
|
|
11
|
+
await Bun.build({
|
|
12
|
+
entrypoints: ["./src/index.ts"],
|
|
13
|
+
outdir: "./dist",
|
|
14
|
+
target: "node",
|
|
15
|
+
format: "esm",
|
|
16
|
+
external: ["@elizaos/core"],
|
|
17
|
+
sourcemap: "external",
|
|
18
|
+
});
|
|
19
|
+
console.log("✓ build complete");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (watch) {
|
|
23
|
+
await build();
|
|
24
|
+
const watcher = Bun.watch("./src", { recursive: true });
|
|
25
|
+
for await (const _ of watcher) {
|
|
26
|
+
console.log("[watch] rebuilding...");
|
|
27
|
+
try {
|
|
28
|
+
await build();
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
await build();
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tokagent/plugin-web-fetch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web fetch + Tavily search actions for the agent. FETCH_URL (Node built-in fetch, no key) + WEB_SEARCH (Tavily-backed, requires TAVILY_API_KEY).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist", "README.md"],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "bun run build.ts",
|
|
20
|
+
"dev": "bun run build.ts --watch",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@elizaos/core": "workspace:*"
|
|
25
|
+
},
|
|
26
|
+
"agentConfig": {
|
|
27
|
+
"pluginType": "elizaos:plugin:1.0.0",
|
|
28
|
+
"pluginParameters": {
|
|
29
|
+
"TAVILY_API_KEY": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Tavily search API key — required for the WEB_SEARCH action. Get a free key (1,000 searches/month, no credit card) at https://app.tavily.com/sign-in. Without it the agent cannot fulfill 'search the web' requests; it will reply with a clear error pointing here. Saving this key persists to config.env and triggers a runtime restart so the action picks it up.",
|
|
32
|
+
"required": false,
|
|
33
|
+
"sensitive": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|