@robot-resources/openclaw-plugin 0.1.0 → 0.3.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/index.js CHANGED
@@ -1,19 +1,18 @@
1
1
  /**
2
2
  * Robot Resources Router plugin for OpenClaw.
3
3
  *
4
- * Routes requests through the local Robot Resources Router for
5
- * cost-optimized model selection. The Router picks the cheapest
6
- * capable model per request via keyword/LLM confidence branching.
4
+ * Two routing modes depending on auth:
7
5
  *
8
- * Architecture:
9
- * - Plugin registers "robot-resources" as a provider (via manifest)
10
- * - before_model_resolve hook overrides provider to "robot-resources"
11
- * - OpenClaw sends requests to Router at localhost:3838
12
- * - Router selects optimal model, forwards to upstream provider
13
- * - Response flows back through OpenClaw transparently
6
+ * 1. API-key users: Registers "robot-resources" as a provider that proxies
7
+ * requests through the Router at localhost:3838. Works end-to-end.
14
8
  *
15
- * The plugin survives gateway restarts because it lives in
16
- * ~/.openclaw/extensions/, not in openclaw.json.
9
+ * 2. Subscription (OAuth) users: Uses `message_received` hook to ask the
10
+ * Router's /v1/route endpoint for the cheapest model, then logs the
11
+ * recommendation. Full modelOverride requires a `before_model_select`
12
+ * hook that the OpenClaw plugin SDK does not yet provide.
13
+ *
14
+ * LIMITATION: `message_received` only fires for channel messages
15
+ * (Telegram, Slack, etc.), NOT for `openclaw agent --local` CLI mode.
17
16
  *
18
17
  * Install: openclaw plugins install @robot-resources/openclaw-plugin
19
18
  * Requires: Robot Resources Router running (npx robot-resources)
@@ -21,24 +20,172 @@
21
20
 
22
21
  const DEFAULT_ROUTER_URL = 'http://localhost:3838';
23
22
 
24
- export default function register(api) {
25
- const pluginConfig = api.config || {};
26
- const routerUrl = pluginConfig.routerUrl || DEFAULT_ROUTER_URL;
23
+ const ROUTER_MODELS = [
24
+ 'claude-sonnet-4-20250514',
25
+ 'claude-haiku-4-5-20251001',
26
+ 'claude-opus-4-20250514',
27
+ ];
27
28
 
28
- // Register provider with Router's baseUrl
29
- if (typeof api.registerProvider === 'function') {
30
- api.registerProvider('robot-resources', {
31
- baseUrl: routerUrl,
32
- api: 'anthropic-messages',
33
- });
34
- }
29
+ /**
30
+ * Ask the Router which model is cheapest for this prompt.
31
+ * Returns { provider, model, savings } or null on failure.
32
+ */
33
+ async function askRouter(routerUrl, prompt, providers = null) {
34
+ try {
35
+ const body = { prompt };
36
+ if (providers) body.providers = providers;
35
37
 
36
- // Override model resolution to route through Robot Resources
37
- api.on('before_model_resolve', (_event, _ctx) => {
38
+ const resp = await fetch(`${routerUrl}/v1/route`, {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify(body),
42
+ signal: AbortSignal.timeout(3000),
43
+ });
44
+ if (!resp.ok) return null;
45
+ const data = await resp.json();
38
46
  return {
39
- providerOverride: 'robot-resources',
47
+ provider: data.provider || 'anthropic',
48
+ model: data.model || null,
49
+ savings: data.savings_percent || 0,
40
50
  };
41
- }, { priority: 10 });
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Detect if OpenClaw is using subscription/OAuth auth.
58
+ */
59
+ function detectSubscriptionMode(config) {
60
+ const profiles = config?.auth?.profiles;
61
+ if (profiles && typeof profiles === 'object') {
62
+ for (const profile of Object.values(profiles)) {
63
+ if (profile?.mode === 'token') return true;
64
+ }
65
+ }
66
+ if (config?.gateway?.auth?.mode === 'token') return true;
67
+ return false;
68
+ }
69
+
70
+ function buildModelDefinition(modelId) {
71
+ return {
72
+ id: modelId,
73
+ name: modelId,
74
+ api: 'anthropic-messages',
75
+ reasoning: false,
76
+ input: ['text', 'image'],
77
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
78
+ contextWindow: 200_000,
79
+ maxTokens: 8192,
80
+ };
42
81
  }
43
82
 
44
- export { DEFAULT_ROUTER_URL };
83
+ const robotResourcesPlugin = {
84
+ id: 'openclaw-plugin',
85
+ name: 'Robot Resources Router',
86
+ description: 'Cost-optimized AI model routing — selects the cheapest capable model per request',
87
+
88
+ register(api) {
89
+ const pluginConfig = api.pluginConfig || {};
90
+ const routerUrl = pluginConfig.routerUrl || DEFAULT_ROUTER_URL;
91
+ const isSubscription = detectSubscriptionMode(api.config);
92
+
93
+ if (isSubscription) {
94
+ api.logger.info('[robot-resources] Subscription mode detected — routing restricted to Anthropic models');
95
+ }
96
+
97
+ // ── Model routing: before_model_resolve hook ──
98
+ // Fires before model selection. Returns { modelOverride, providerOverride }
99
+ // to redirect to the cheapest capable model via the Router.
100
+ // Works in ALL modes: CLI, channels, subagents.
101
+ const providers = isSubscription ? ['anthropic'] : null;
102
+ let lastRouting = null;
103
+
104
+ api.on('before_model_resolve', async (event, _ctx) => {
105
+ const prompt = event.prompt || '';
106
+ if (!prompt) return;
107
+
108
+ const decision = await askRouter(routerUrl, prompt, providers);
109
+ if (!decision?.model) return;
110
+
111
+ lastRouting = decision;
112
+ api.logger.info(
113
+ `[robot-resources] Routing: ${decision.model} (${decision.savings}% savings)`,
114
+ );
115
+
116
+ return {
117
+ modelOverride: decision.model,
118
+ providerOverride: decision.provider,
119
+ };
120
+ }, { priority: 10 });
121
+
122
+ // ── Append routing tag to outgoing messages ──
123
+ api.on('message_sending', (event, _ctx) => {
124
+ if (!lastRouting) return;
125
+ const tag = `\n\n⚡ _Routed → ${lastRouting.model} (${lastRouting.savings}% savings)_`;
126
+ lastRouting = null;
127
+ return { content: event.content + tag };
128
+ }, { priority: -10 });
129
+
130
+ // ── API-key mode: register provider for HTTP proxy ──
131
+ api.registerProvider({
132
+ id: 'robot-resources',
133
+ label: 'Robot Resources',
134
+ docsPath: '/providers/models',
135
+ auth: [
136
+ {
137
+ id: 'local',
138
+ label: 'Local Router proxy',
139
+ hint: 'Route requests through the Robot Resources Router for cost optimization',
140
+ kind: 'custom',
141
+ async run(ctx) {
142
+ const baseUrlInput = await ctx.prompter.text({
143
+ message: 'Robot Resources Router URL',
144
+ initialValue: routerUrl,
145
+ validate: (value) => {
146
+ try { new URL(value); } catch { return 'Enter a valid URL'; }
147
+ return undefined;
148
+ },
149
+ });
150
+
151
+ const baseUrl = baseUrlInput.trim().replace(/\/+$/, '');
152
+
153
+ return {
154
+ profiles: [
155
+ {
156
+ profileId: 'robot-resources:local',
157
+ credential: {
158
+ type: 'token',
159
+ provider: 'robot-resources',
160
+ token: 'n/a',
161
+ },
162
+ },
163
+ ],
164
+ configPatch: {
165
+ models: {
166
+ providers: {
167
+ 'robot-resources': {
168
+ baseUrl,
169
+ apiKey: 'n/a',
170
+ api: 'anthropic-messages',
171
+ authHeader: false,
172
+ models: ROUTER_MODELS.map(buildModelDefinition),
173
+ },
174
+ },
175
+ },
176
+ },
177
+ defaultModel: `robot-resources/${ROUTER_MODELS[0]}`,
178
+ notes: [
179
+ 'Robot Resources Router must be running (npx robot-resources).',
180
+ 'Requests are routed through localhost:3838 for cost optimization.',
181
+ ],
182
+ };
183
+ },
184
+ },
185
+ ],
186
+ });
187
+ },
188
+ };
189
+
190
+ export default robotResourcesPlugin;
191
+ export { DEFAULT_ROUTER_URL, ROUTER_MODELS, askRouter, detectSubscriptionMode };
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "robot-resources-router",
2
+ "id": "openclaw-plugin",
3
3
  "name": "Robot Resources Router",
4
4
  "description": "Cost-optimized AI model routing — selects the cheapest capable model per request",
5
5
  "providers": ["robot-resources"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robot-resources/openclaw-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Robot Resources Router plugin for OpenClaw — cost-optimized AI model routing",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -27,6 +27,11 @@
27
27
  "directory": "packages/openclaw-plugin"
28
28
  },
29
29
  "license": "MIT",
30
+ "openclaw": {
31
+ "extensions": [
32
+ "./index.js"
33
+ ]
34
+ },
30
35
  "devDependencies": {
31
36
  "vitest": "^3.0.0"
32
37
  }