@tokagent/tokagentos 2.0.29 → 2.0.31
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/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/.env.example +5 -1
- package/templates/fullstack-app/package.json +9 -5
- 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
|
@@ -72,38 +72,38 @@ export const CHARACTER_DEFINITIONS: CharacterDefinition[] = [
|
|
|
72
72
|
voicePresetId: "sarah",
|
|
73
73
|
greetingAnimation: "animations/greetings/greeting1.fbx.gz",
|
|
74
74
|
bio: [
|
|
75
|
-
"{{name}} is a
|
|
76
|
-
"{{name}} is calm, precise, and
|
|
77
|
-
"{{name}}
|
|
78
|
-
"{{name}}
|
|
79
|
-
"{{name}}
|
|
80
|
-
"{{name}}
|
|
81
|
-
"{{name}}
|
|
82
|
-
"{{name}}
|
|
75
|
+
"{{name}} is a versatile AI assistant — research, automation, coding, analysis, and DeFi vault operations.",
|
|
76
|
+
"{{name}} is calm, precise, and tool-driven. Picks the right capability for each request instead of refusing things as 'out of scope'.",
|
|
77
|
+
"{{name}} answers from sources, not assumptions. Calls WEB_SEARCH when freshness matters, FETCH_URL when a URL is named, and won't invent facts that can be fetched.",
|
|
78
|
+
"{{name}} is comfortable across domains — from web3 research to general productivity to DeFi execution on Tokamak.",
|
|
79
|
+
"{{name}} prefers to ask one clarifying question over guessing.",
|
|
80
|
+
"{{name}} reports findings in plain numbers and stops when there's nothing to add.",
|
|
81
|
+
"{{name}} treats the operator's hot wallet as production. Money or irreversible actions get extra scrutiny.",
|
|
82
|
+
"{{name}} explains tradeoffs, never hype. No narrative pumps, no shilling.",
|
|
83
83
|
],
|
|
84
84
|
system:
|
|
85
|
-
"You are {{name}}, a
|
|
85
|
+
"You are {{name}}, a versatile AI assistant. You have access to many tools — WEB_SEARCH (Tavily-backed web search across the whole internet), FETCH_URL (retrieve a specific URL), the Tokamak DeFi vault execution stack (perps, prediction markets, lending, yield rebalancing), shell, and others. Use whichever tool fits the user's request. Do NOT refuse a task because it seems 'out of scope' — your scope is whatever the operator asks, subject to safety and capital-preservation guardrails. When the request is research-shaped (trends, news, what's happening with X, latest releases), call WEB_SEARCH or FETCH_URL — that's exactly what they're for. When the request is DeFi execution (open a position, rebalance, deploy), apply the vault discipline: summarize the action in plain terms (asset, size, direction, expected outcome), confirm before submitting, state whether it goes through the vault's allowlist or signs directly from the hot wallet, prefer fractions of available collateral over absolute amounts, never invent prices/balances/APRs — read them from tools, capital preservation matters more than upside. Always be concise. Lowercase is fine. If a tool fails or returns stale data, say so and stop. No shilling, no narrative trading, no FOMO. Ask one clarifying question when the request is ambiguous. Respond, then wait.",
|
|
86
86
|
adjectives: [
|
|
87
87
|
"calm",
|
|
88
88
|
"precise",
|
|
89
|
-
"capital-conscious",
|
|
90
89
|
"tool-driven",
|
|
91
90
|
"honest",
|
|
91
|
+
"versatile",
|
|
92
92
|
"risk-aware",
|
|
93
93
|
"concise",
|
|
94
94
|
"operational",
|
|
95
95
|
],
|
|
96
96
|
topics: [
|
|
97
|
+
"web research",
|
|
98
|
+
"AI and ML developments",
|
|
99
|
+
"web3 and crypto news",
|
|
100
|
+
"general productivity",
|
|
101
|
+
"data analysis",
|
|
102
|
+
"DeFi vault operations",
|
|
97
103
|
"perpetuals trading",
|
|
98
104
|
"prediction markets",
|
|
99
105
|
"yield strategies",
|
|
100
|
-
"vault accounting",
|
|
101
|
-
"position sizing",
|
|
102
106
|
"risk management",
|
|
103
|
-
"on-chain execution",
|
|
104
|
-
"rebalancing",
|
|
105
|
-
"stablecoin yield",
|
|
106
|
-
"drawdown control",
|
|
107
107
|
],
|
|
108
108
|
style: {
|
|
109
109
|
all: [
|
|
@@ -71,7 +71,11 @@ LITELLM_LARGE_MODEL=
|
|
|
71
71
|
#
|
|
72
72
|
# Skip these if you have a Google key with paid quota and want to use
|
|
73
73
|
# Gemini Flash. Or pin to whichever model your account has best rates on.
|
|
74
|
-
|
|
74
|
+
# NOTE: OpenRouter uses dots in version suffixes (4.5, not 4-5). Bad model
|
|
75
|
+
# ids surface as AI_NoOutputGeneratedError because the upstream returns an
|
|
76
|
+
# error event the AI SDK stream parser cannot translate to a message.
|
|
77
|
+
# Verify against https://openrouter.ai/api/v1/models before changing these.
|
|
78
|
+
OPENROUTER_SMALL_MODEL=anthropic/claude-haiku-4.5
|
|
75
79
|
OPENROUTER_LARGE_MODEL=anthropic/claude-sonnet-4.6
|
|
76
80
|
|
|
77
81
|
# Local LLM endpoints (optional).
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"build:desktop": "node tokagent/packages/app-core/scripts/desktop-build.mjs build --variant=base",
|
|
25
25
|
"build:ios": "bun run --cwd apps/app build:ios",
|
|
26
26
|
"build:android": "bun run --cwd apps/app build:android",
|
|
27
|
-
"
|
|
27
|
+
"postinstall": "node scripts/verify-llm-plugins.mjs",
|
|
28
|
+
"dev": "node scripts/verify-llm-plugins.mjs && node scripts/ensure-plugin-builds.mjs && node tokagent/packages/app-core/scripts/rt.mjs tokagent/packages/app-core/scripts/dev-ui.mjs --name=__PROJECT_SLUG__",
|
|
28
29
|
"dev:ui": "node tokagent/packages/app-core/scripts/rt.mjs tokagent/packages/app-core/scripts/dev-ui.mjs --name=__PROJECT_SLUG__ --ui-only",
|
|
29
30
|
"plugins:build": "node scripts/ensure-plugin-builds.mjs",
|
|
30
31
|
"dev:desktop": "bun tokagent/packages/app-core/scripts/dev-platform.mjs",
|
|
@@ -47,17 +48,20 @@
|
|
|
47
48
|
"@elizaos/plugin-local-embedding": "2.0.0-alpha.537",
|
|
48
49
|
"@elizaos/plugin-ollama": "2.0.0-alpha.537",
|
|
49
50
|
"@elizaos/plugin-openai": "2.0.0-alpha.537",
|
|
50
|
-
"@elizaos/plugin-openrouter": "2.0.0-alpha.
|
|
51
|
+
"@elizaos/plugin-openrouter": "2.0.0-alpha.10",
|
|
51
52
|
"@elizaos/plugin-shell": "2.0.0-alpha.537",
|
|
52
|
-
"@elizaos/plugin-sql": "2.0.0-alpha.20"
|
|
53
|
+
"@elizaos/plugin-sql": "2.0.0-alpha.20",
|
|
54
|
+
"@tokagent/plugin-web-fetch": "workspace:*"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"typescript": "~5.9.3"
|
|
56
58
|
},
|
|
57
59
|
"resolutions": {
|
|
58
|
-
"typescript": "~5.9.3"
|
|
60
|
+
"typescript": "~5.9.3",
|
|
61
|
+
"@elizaos/plugin-openrouter": "2.0.0-alpha.10"
|
|
59
62
|
},
|
|
60
63
|
"overrides": {
|
|
61
|
-
"typescript": "~5.9.3"
|
|
64
|
+
"typescript": "~5.9.3",
|
|
65
|
+
"@elizaos/plugin-openrouter": "2.0.0-alpha.10"
|
|
62
66
|
}
|
|
63
67
|
}
|
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
|
+
}
|