@elizaos/plugin-openrouter 1.3.1 → 1.5.13
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/README.md +32 -0
- package/dist/browser/index.browser.js +981 -0
- package/dist/browser/index.browser.js.map +20 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.node.cjs +643 -0
- package/dist/cjs/index.node.js.map +19 -0
- package/dist/index.browser.d.ts +2 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.node.d.ts +2 -0
- package/dist/init.d.ts +6 -0
- package/dist/models/image.d.ts +14 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/object.d.ts +9 -0
- package/dist/models/text.d.ts +17 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/{index.js → node/index.node.js} +192 -119
- package/dist/node/index.node.js.map +19 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/openrouter.d.ts +8 -0
- package/dist/types/index.d.ts +60 -0
- package/dist/utils/config.d.ts +64 -0
- package/dist/utils/events.d.ts +6 -0
- package/dist/utils/helpers.d.ts +29 -0
- package/dist/utils/image-storage.d.ts +8 -0
- package/dist/utils/index.d.ts +2 -0
- package/package.json +34 -20
- package/dist/index.js.map +0 -1
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
1
20
|
// src/index.ts
|
|
2
21
|
import {
|
|
3
22
|
ModelType as ModelType3
|
|
@@ -12,11 +31,11 @@ function getSetting(runtime, key, defaultValue) {
|
|
|
12
31
|
return runtime.getSetting(key) ?? process.env[key] ?? defaultValue;
|
|
13
32
|
}
|
|
14
33
|
function getBaseURL(runtime) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
) || "https://openrouter.ai/api/v1";
|
|
34
|
+
const browserURL = getSetting(runtime, "OPENROUTER_BROWSER_BASE_URL");
|
|
35
|
+
if (typeof globalThis !== "undefined" && globalThis.document && browserURL) {
|
|
36
|
+
return browserURL;
|
|
37
|
+
}
|
|
38
|
+
return getSetting(runtime, "OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1") || "https://openrouter.ai/api/v1";
|
|
20
39
|
}
|
|
21
40
|
function getApiKey(runtime) {
|
|
22
41
|
return getSetting(runtime, "OPENROUTER_API_KEY");
|
|
@@ -25,11 +44,7 @@ function getSmallModel(runtime) {
|
|
|
25
44
|
return getSetting(runtime, "OPENROUTER_SMALL_MODEL") ?? getSetting(runtime, "SMALL_MODEL", "google/gemini-2.0-flash-001") ?? "google/gemini-2.0-flash-001";
|
|
26
45
|
}
|
|
27
46
|
function getLargeModel(runtime) {
|
|
28
|
-
return getSetting(runtime, "OPENROUTER_LARGE_MODEL") ?? getSetting(
|
|
29
|
-
runtime,
|
|
30
|
-
"LARGE_MODEL",
|
|
31
|
-
"google/gemini-2.5-flash"
|
|
32
|
-
) ?? "google/gemini-2.5-flash";
|
|
47
|
+
return getSetting(runtime, "OPENROUTER_LARGE_MODEL") ?? getSetting(runtime, "LARGE_MODEL", "google/gemini-2.5-flash") ?? "google/gemini-2.5-flash";
|
|
33
48
|
}
|
|
34
49
|
function getImageModel(runtime) {
|
|
35
50
|
return getSetting(runtime, "OPENROUTER_IMAGE_MODEL") ?? getSetting(runtime, "IMAGE_MODEL", "x-ai/grok-2-vision-1212") ?? "x-ai/grok-2-vision-1212";
|
|
@@ -41,15 +56,26 @@ function shouldAutoCleanupImages(runtime) {
|
|
|
41
56
|
const setting = getSetting(runtime, "OPENROUTER_AUTO_CLEANUP_IMAGES", "false");
|
|
42
57
|
return setting?.toLowerCase() === "true";
|
|
43
58
|
}
|
|
59
|
+
function getToolExecutionMaxSteps(runtime) {
|
|
60
|
+
const setting = getSetting(runtime, "OPENROUTER_TOOL_EXECUTION_MAX_STEPS", "15");
|
|
61
|
+
const value = parseInt(setting || "15", 10);
|
|
62
|
+
if (Number.isNaN(value) || value < 1)
|
|
63
|
+
return 15;
|
|
64
|
+
if (value > 100)
|
|
65
|
+
return 100;
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
44
68
|
|
|
45
69
|
// src/init.ts
|
|
46
70
|
function initializeOpenRouter(_config, runtime) {
|
|
47
71
|
(async () => {
|
|
48
72
|
try {
|
|
73
|
+
const isBrowser = typeof globalThis !== "undefined" && globalThis.document;
|
|
74
|
+
if (isBrowser) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
49
77
|
if (!getApiKey(runtime)) {
|
|
50
|
-
logger.warn(
|
|
51
|
-
"OPENROUTER_API_KEY is not set in environment - OpenRouter functionality will be limited"
|
|
52
|
-
);
|
|
78
|
+
logger.warn("OPENROUTER_API_KEY is not set in environment - OpenRouter functionality will be limited");
|
|
53
79
|
return;
|
|
54
80
|
}
|
|
55
81
|
try {
|
|
@@ -58,73 +84,62 @@ function initializeOpenRouter(_config, runtime) {
|
|
|
58
84
|
headers: { Authorization: `Bearer ${getApiKey(runtime)}` }
|
|
59
85
|
});
|
|
60
86
|
if (!response.ok) {
|
|
61
|
-
logger.warn(
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
logger.warn(
|
|
65
|
-
"OpenRouter functionality will be limited until a valid API key is provided"
|
|
66
|
-
);
|
|
87
|
+
logger.warn(`OpenRouter API key validation failed: ${response.statusText}`);
|
|
88
|
+
logger.warn("OpenRouter functionality will be limited until a valid API key is provided");
|
|
67
89
|
} else {
|
|
68
90
|
logger.log("OpenRouter API key validated successfully");
|
|
69
91
|
}
|
|
70
92
|
} catch (fetchError) {
|
|
71
93
|
const message = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
72
94
|
logger.warn(`Error validating OpenRouter API key: ${message}`);
|
|
73
|
-
logger.warn(
|
|
74
|
-
"OpenRouter functionality will be limited until a valid API key is provided"
|
|
75
|
-
);
|
|
95
|
+
logger.warn("OpenRouter functionality will be limited until a valid API key is provided");
|
|
76
96
|
}
|
|
77
97
|
} catch (error) {
|
|
78
98
|
const message = error?.errors?.map((e) => e.message).join(", ") || (error instanceof Error ? error.message : String(error));
|
|
79
|
-
logger.warn(
|
|
80
|
-
`OpenRouter plugin configuration issue: ${message} - You need to configure the OPENROUTER_API_KEY in your environment variables`
|
|
81
|
-
);
|
|
99
|
+
logger.warn(`OpenRouter plugin configuration issue: ${message} - You need to configure the OPENROUTER_API_KEY in your environment variables`);
|
|
82
100
|
}
|
|
83
101
|
})();
|
|
84
102
|
return;
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
// src/models/text.ts
|
|
88
|
-
import { logger as
|
|
106
|
+
import { logger as logger3, ModelType } from "@elizaos/core";
|
|
89
107
|
import { generateText } from "ai";
|
|
90
108
|
|
|
91
109
|
// src/providers/openrouter.ts
|
|
92
110
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
93
|
-
import { logger as logger2 } from "@elizaos/core";
|
|
94
111
|
function createOpenRouterProvider(runtime) {
|
|
95
112
|
const apiKey = getApiKey(runtime);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"OpenRouter API Key is missing when trying to create provider"
|
|
99
|
-
);
|
|
100
|
-
throw new Error("OpenRouter API Key is missing.");
|
|
101
|
-
}
|
|
113
|
+
const isBrowser = typeof globalThis !== "undefined" && globalThis.document;
|
|
114
|
+
const baseURL = getBaseURL(runtime);
|
|
102
115
|
return createOpenRouter({
|
|
103
|
-
apiKey
|
|
104
|
-
|
|
105
|
-
// The @ai-sdk/provider utils might handle OPENROUTER_BASE_URL env var.
|
|
116
|
+
apiKey: isBrowser ? undefined : apiKey,
|
|
117
|
+
baseURL
|
|
106
118
|
});
|
|
107
119
|
}
|
|
108
|
-
|
|
109
120
|
// src/utils/events.ts
|
|
110
121
|
import {
|
|
111
122
|
EventType
|
|
112
123
|
} from "@elizaos/core";
|
|
113
124
|
function emitModelUsageEvent(runtime, type, prompt, usage) {
|
|
125
|
+
const truncatedPrompt = typeof prompt === "string" ? prompt.length > 200 ? `${prompt.slice(0, 200)}…` : prompt : "";
|
|
126
|
+
const inputTokens = Number(usage.inputTokens || 0);
|
|
127
|
+
const outputTokens = Number(usage.outputTokens || 0);
|
|
128
|
+
const totalTokens = Number(usage.totalTokens != null ? usage.totalTokens : inputTokens + outputTokens);
|
|
114
129
|
runtime.emitEvent(EventType.MODEL_USED, {
|
|
115
130
|
provider: "openrouter",
|
|
116
131
|
type,
|
|
117
|
-
prompt,
|
|
132
|
+
prompt: truncatedPrompt,
|
|
118
133
|
tokens: {
|
|
119
|
-
prompt:
|
|
120
|
-
completion:
|
|
121
|
-
total:
|
|
134
|
+
prompt: inputTokens,
|
|
135
|
+
completion: outputTokens,
|
|
136
|
+
total: totalTokens
|
|
122
137
|
}
|
|
123
138
|
});
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
// src/utils/helpers.ts
|
|
127
|
-
import { logger as
|
|
142
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
128
143
|
import { JSONParseError } from "ai";
|
|
129
144
|
function getJsonRepairFunction() {
|
|
130
145
|
return async ({ text, error }) => {
|
|
@@ -137,15 +152,15 @@ function getJsonRepairFunction() {
|
|
|
137
152
|
return null;
|
|
138
153
|
} catch (jsonError) {
|
|
139
154
|
const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
140
|
-
|
|
155
|
+
logger2.warn(`Failed to repair JSON text: ${message}`);
|
|
141
156
|
return null;
|
|
142
157
|
}
|
|
143
158
|
};
|
|
144
159
|
}
|
|
145
160
|
function handleEmptyToolResponse(modelType) {
|
|
146
|
-
|
|
161
|
+
logger2.warn(`[${modelType}] No text generated after tool execution`);
|
|
147
162
|
const fallbackText = "I executed the requested action. The tool completed successfully.";
|
|
148
|
-
|
|
163
|
+
logger2.warn(`[${modelType}] Using fallback response text`);
|
|
149
164
|
return fallbackText;
|
|
150
165
|
}
|
|
151
166
|
function parseImageDescriptionResponse(responseText) {
|
|
@@ -155,7 +170,7 @@ function parseImageDescriptionResponse(responseText) {
|
|
|
155
170
|
return jsonResponse;
|
|
156
171
|
}
|
|
157
172
|
} catch (e) {
|
|
158
|
-
|
|
173
|
+
logger2.debug(`Parsing as JSON failed, processing as text: ${e}`);
|
|
159
174
|
}
|
|
160
175
|
const titleMatch = responseText.match(/title[:\s]+(.+?)(?:\n|$)/i);
|
|
161
176
|
const title = titleMatch?.[1]?.trim() || "Image Analysis";
|
|
@@ -164,7 +179,7 @@ function parseImageDescriptionResponse(responseText) {
|
|
|
164
179
|
}
|
|
165
180
|
async function handleObjectGenerationError(error) {
|
|
166
181
|
if (error instanceof JSONParseError) {
|
|
167
|
-
|
|
182
|
+
logger2.error(`[generateObject] Failed to parse JSON: ${error.message}`);
|
|
168
183
|
const repairFunction = getJsonRepairFunction();
|
|
169
184
|
const repairedJsonString = await repairFunction({
|
|
170
185
|
text: error.text,
|
|
@@ -173,47 +188,54 @@ async function handleObjectGenerationError(error) {
|
|
|
173
188
|
if (repairedJsonString) {
|
|
174
189
|
try {
|
|
175
190
|
const repairedObject = JSON.parse(repairedJsonString);
|
|
176
|
-
|
|
191
|
+
logger2.log("[generateObject] Successfully repaired JSON.");
|
|
177
192
|
return repairedObject;
|
|
178
193
|
} catch (repairParseError) {
|
|
179
194
|
const message = repairParseError instanceof Error ? repairParseError.message : String(repairParseError);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (repairParseError instanceof Error) throw repairParseError;
|
|
195
|
+
logger2.error(`[generateObject] Failed to parse repaired JSON: ${message}`);
|
|
196
|
+
if (repairParseError instanceof Error)
|
|
197
|
+
throw repairParseError;
|
|
184
198
|
throw Object.assign(new Error(message), { cause: repairParseError });
|
|
185
199
|
}
|
|
186
200
|
} else {
|
|
187
|
-
|
|
201
|
+
logger2.error("[generateObject] JSON repair failed.");
|
|
188
202
|
throw error;
|
|
189
203
|
}
|
|
190
204
|
} else {
|
|
191
205
|
const message = error instanceof Error ? error.message : String(error);
|
|
192
|
-
|
|
193
|
-
if (error instanceof Error)
|
|
206
|
+
logger2.error(`[generateObject] Unknown error: ${message}`);
|
|
207
|
+
if (error instanceof Error)
|
|
208
|
+
throw error;
|
|
194
209
|
throw Object.assign(new Error(message), { cause: error });
|
|
195
210
|
}
|
|
196
211
|
}
|
|
197
212
|
function isLikelyBase64(key, value) {
|
|
198
213
|
const base64KeyPattern = /^(data|content|body|payload|encoded|b64|base64|document)$/i;
|
|
199
|
-
if (!base64KeyPattern.test(key))
|
|
200
|
-
|
|
201
|
-
if (value.length
|
|
202
|
-
|
|
214
|
+
if (!base64KeyPattern.test(key))
|
|
215
|
+
return false;
|
|
216
|
+
if (value.length < 20 || value.length > 1024 * 1024)
|
|
217
|
+
return false;
|
|
218
|
+
if (value.length % 4 !== 0)
|
|
219
|
+
return false;
|
|
220
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value))
|
|
221
|
+
return false;
|
|
203
222
|
return true;
|
|
204
223
|
}
|
|
205
224
|
function decodeBase64Fields(obj, depth = 0) {
|
|
206
|
-
if (depth > 5)
|
|
207
|
-
|
|
208
|
-
if (
|
|
225
|
+
if (depth > 5)
|
|
226
|
+
return obj;
|
|
227
|
+
if (!obj || typeof obj !== "object")
|
|
228
|
+
return obj;
|
|
229
|
+
if (Array.isArray(obj))
|
|
230
|
+
return obj.map((item) => decodeBase64Fields(item, depth + 1));
|
|
209
231
|
const decoded = {};
|
|
210
232
|
for (const [key, value] of Object.entries(obj)) {
|
|
211
233
|
if (typeof value === "string" && isLikelyBase64(key, value)) {
|
|
212
234
|
try {
|
|
213
235
|
decoded[key] = Buffer.from(value, "base64").toString("utf8");
|
|
214
|
-
|
|
236
|
+
logger2.debug(`[decodeBase64] Decoded field '${key}' (${value.length} chars)`);
|
|
215
237
|
} catch (error) {
|
|
216
|
-
|
|
238
|
+
logger2.warn(`[decodeBase64] Failed to decode field '${key}': ${error}`);
|
|
217
239
|
decoded[key] = value;
|
|
218
240
|
}
|
|
219
241
|
} else if (value && typeof value === "object") {
|
|
@@ -231,31 +253,26 @@ async function generateTextWithModel(runtime, modelType, params) {
|
|
|
231
253
|
const temperature = params.temperature ?? 0.7;
|
|
232
254
|
const frequencyPenalty = params.frequencyPenalty ?? 0.7;
|
|
233
255
|
const presencePenalty = params.presencePenalty ?? 0.7;
|
|
234
|
-
const
|
|
256
|
+
const resolvedMaxOutput = params.maxOutputTokens ?? params.maxTokens ?? 8192;
|
|
235
257
|
const openrouter = createOpenRouterProvider(runtime);
|
|
236
258
|
const modelName = modelType === ModelType.TEXT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
|
|
237
259
|
const modelLabel = modelType === ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE";
|
|
238
|
-
|
|
239
|
-
`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`
|
|
240
|
-
);
|
|
260
|
+
logger3.log(`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`);
|
|
241
261
|
const generateParams = {
|
|
242
262
|
model: openrouter.chat(modelName),
|
|
243
263
|
prompt,
|
|
244
|
-
system: runtime.character.system ??
|
|
264
|
+
system: runtime.character.system ?? undefined,
|
|
245
265
|
temperature,
|
|
246
|
-
maxTokens: maxResponseLength,
|
|
247
266
|
frequencyPenalty,
|
|
248
267
|
presencePenalty,
|
|
249
268
|
stopSequences
|
|
250
269
|
};
|
|
270
|
+
generateParams.maxOutputTokens = resolvedMaxOutput;
|
|
251
271
|
if (tools) {
|
|
252
272
|
generateParams.tools = tools;
|
|
253
|
-
|
|
254
|
-
generateParams.
|
|
255
|
-
|
|
256
|
-
require_parameters: true
|
|
257
|
-
}
|
|
258
|
-
};
|
|
273
|
+
const maxSteps = getToolExecutionMaxSteps(runtime);
|
|
274
|
+
generateParams.maxSteps = maxSteps;
|
|
275
|
+
logger3.log(`[OpenRouter] Using maxSteps: ${maxSteps} for tool execution`);
|
|
259
276
|
}
|
|
260
277
|
if (toolChoice) {
|
|
261
278
|
generateParams.toolChoice = toolChoice;
|
|
@@ -265,11 +282,14 @@ async function generateTextWithModel(runtime, modelType, params) {
|
|
|
265
282
|
if (tools) {
|
|
266
283
|
generateParams.onStepFinish = async (stepResult) => {
|
|
267
284
|
if (stepResult.toolCalls && stepResult.toolCalls.length > 0) {
|
|
268
|
-
capturedToolCalls = [
|
|
285
|
+
capturedToolCalls = [
|
|
286
|
+
...capturedToolCalls,
|
|
287
|
+
...stepResult.toolCalls
|
|
288
|
+
];
|
|
269
289
|
}
|
|
270
290
|
if (stepResult.toolResults && stepResult.toolResults.length > 0) {
|
|
271
291
|
const decodedToolResults = stepResult.toolResults.map((result) => ({
|
|
272
|
-
|
|
292
|
+
toolCallId: result.toolCallId,
|
|
273
293
|
result: decodeBase64Fields(result.result)
|
|
274
294
|
}));
|
|
275
295
|
capturedToolResults = [...capturedToolResults, ...decodedToolResults];
|
|
@@ -291,7 +311,6 @@ async function generateTextWithModel(runtime, modelType, params) {
|
|
|
291
311
|
text: responseText,
|
|
292
312
|
toolCalls: capturedToolCalls,
|
|
293
313
|
toolResults: capturedToolResults,
|
|
294
|
-
// Include other useful properties
|
|
295
314
|
usage: response.usage,
|
|
296
315
|
finishReason: response.finishReason
|
|
297
316
|
};
|
|
@@ -308,19 +327,20 @@ async function handleTextLarge(runtime, params) {
|
|
|
308
327
|
// src/models/object.ts
|
|
309
328
|
import {
|
|
310
329
|
ModelType as ModelType2,
|
|
311
|
-
logger as
|
|
330
|
+
logger as logger4
|
|
312
331
|
} from "@elizaos/core";
|
|
313
332
|
import { generateObject } from "ai";
|
|
314
333
|
async function generateObjectWithModel(runtime, modelType, params) {
|
|
315
334
|
const openrouter = createOpenRouterProvider(runtime);
|
|
316
335
|
const modelName = modelType === ModelType2.OBJECT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
|
|
317
336
|
const modelLabel = modelType === ModelType2.OBJECT_SMALL ? "OBJECT_SMALL" : "OBJECT_LARGE";
|
|
318
|
-
|
|
337
|
+
logger4.log(`[OpenRouter] Using ${modelLabel} model: ${modelName}`);
|
|
319
338
|
const temperature = params.temperature ?? 0.7;
|
|
320
339
|
try {
|
|
321
340
|
const { object, usage } = await generateObject({
|
|
322
341
|
model: openrouter.chat(modelName),
|
|
323
|
-
|
|
342
|
+
...params.schema && { schema: params.schema },
|
|
343
|
+
output: params.schema ? "object" : "no-schema",
|
|
324
344
|
prompt: params.prompt,
|
|
325
345
|
temperature,
|
|
326
346
|
experimental_repairText: getJsonRepairFunction()
|
|
@@ -342,18 +362,61 @@ async function handleObjectLarge(runtime, params) {
|
|
|
342
362
|
|
|
343
363
|
// src/models/image.ts
|
|
344
364
|
import {
|
|
345
|
-
logger as
|
|
365
|
+
logger as logger6
|
|
346
366
|
} from "@elizaos/core";
|
|
347
367
|
import { generateText as generateText2 } from "ai";
|
|
348
368
|
|
|
349
369
|
// src/utils/image-storage.ts
|
|
350
|
-
import {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
370
|
+
import { logger as logger5, getGeneratedDir } from "@elizaos/core";
|
|
371
|
+
function isBrowser() {
|
|
372
|
+
return typeof globalThis !== "undefined" && globalThis.document;
|
|
373
|
+
}
|
|
374
|
+
function sanitizeId(id) {
|
|
375
|
+
const src = (id ?? "").toString();
|
|
376
|
+
const normalized = src.normalize("NFKC");
|
|
377
|
+
let safe = normalized.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
378
|
+
safe = safe.replace(/_+/g, "_");
|
|
379
|
+
safe = safe.slice(0, 64);
|
|
380
|
+
safe = safe.replace(/^_+|_+$/g, "");
|
|
381
|
+
return safe || "agent";
|
|
382
|
+
}
|
|
383
|
+
function base64ToBytes(base64) {
|
|
384
|
+
const cleaned = base64.replace(/\s+/g, "");
|
|
385
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
386
|
+
const lookup = new Array(256).fill(-1);
|
|
387
|
+
for (let i = 0;i < chars.length; i++)
|
|
388
|
+
lookup[chars.charCodeAt(i)] = i;
|
|
389
|
+
const len = cleaned.length;
|
|
390
|
+
let pad = 0;
|
|
391
|
+
if (len >= 2 && cleaned[len - 1] === "=")
|
|
392
|
+
pad++;
|
|
393
|
+
if (len >= 2 && cleaned[len - 2] === "=")
|
|
394
|
+
pad++;
|
|
395
|
+
const outLen = (len * 3 >> 2) - pad;
|
|
396
|
+
const out = new Uint8Array(outLen);
|
|
397
|
+
let o = 0;
|
|
398
|
+
for (let i = 0;i < len; i += 4) {
|
|
399
|
+
const c0 = lookup[cleaned.charCodeAt(i)];
|
|
400
|
+
const c1 = lookup[cleaned.charCodeAt(i + 1)];
|
|
401
|
+
const c2 = lookup[cleaned.charCodeAt(i + 2)];
|
|
402
|
+
const c3 = lookup[cleaned.charCodeAt(i + 3)];
|
|
403
|
+
const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
|
|
404
|
+
if (o < outLen)
|
|
405
|
+
out[o++] = n >> 16 & 255;
|
|
406
|
+
if (o < outLen)
|
|
407
|
+
out[o++] = n >> 8 & 255;
|
|
408
|
+
if (o < outLen)
|
|
409
|
+
out[o++] = n & 255;
|
|
410
|
+
}
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
354
413
|
async function saveBase64Image(base64Url, agentId, index = 0) {
|
|
414
|
+
if (isBrowser()) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
355
417
|
const m = base64Url.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/);
|
|
356
|
-
if (!m)
|
|
418
|
+
if (!m)
|
|
419
|
+
return null;
|
|
357
420
|
const mime = m[1];
|
|
358
421
|
const base64Data = m[2];
|
|
359
422
|
const extMap = {
|
|
@@ -366,27 +429,41 @@ async function saveBase64Image(base64Url, agentId, index = 0) {
|
|
|
366
429
|
"image/tiff": "tiff"
|
|
367
430
|
};
|
|
368
431
|
const extension = extMap[mime];
|
|
369
|
-
if (!extension)
|
|
370
|
-
|
|
432
|
+
if (!extension)
|
|
433
|
+
return null;
|
|
434
|
+
const { join } = await import("node:path");
|
|
435
|
+
const safeAgentId = sanitizeId(agentId);
|
|
436
|
+
const baseDir = join(getGeneratedDir(), safeAgentId);
|
|
437
|
+
const { existsSync } = await import("node:fs");
|
|
371
438
|
if (!existsSync(baseDir)) {
|
|
439
|
+
const { mkdir } = await import("node:fs/promises");
|
|
372
440
|
await mkdir(baseDir, { recursive: true });
|
|
373
441
|
}
|
|
374
442
|
const timestamp = Date.now();
|
|
375
443
|
const filename = `image_${timestamp}_${index}.${extension}`;
|
|
376
444
|
const filepath = join(baseDir, filename);
|
|
377
|
-
const buffer =
|
|
445
|
+
const buffer = base64ToBytes(base64Data);
|
|
446
|
+
const { writeFile } = await import("node:fs/promises");
|
|
378
447
|
await writeFile(filepath, buffer);
|
|
379
|
-
|
|
448
|
+
logger5.info(`[OpenRouter] Saved generated image to ${filepath}`);
|
|
380
449
|
return filepath;
|
|
381
450
|
}
|
|
382
451
|
function deleteImage(filepath) {
|
|
452
|
+
if (isBrowser()) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
383
455
|
try {
|
|
384
|
-
|
|
385
|
-
unlinkSync(
|
|
386
|
-
|
|
387
|
-
|
|
456
|
+
(async () => {
|
|
457
|
+
const { existsSync, unlinkSync } = await import("node:fs");
|
|
458
|
+
if (existsSync(filepath)) {
|
|
459
|
+
unlinkSync(filepath);
|
|
460
|
+
logger5.debug(`[OpenRouter] Deleted image: ${filepath}`);
|
|
461
|
+
}
|
|
462
|
+
})().catch((error) => {
|
|
463
|
+
logger5.warn(`[OpenRouter] Failed to delete image ${filepath}:`, String(error));
|
|
464
|
+
});
|
|
388
465
|
} catch (error) {
|
|
389
|
-
|
|
466
|
+
logger5.warn(`[OpenRouter] Failed to delete image ${filepath}:`, String(error));
|
|
390
467
|
}
|
|
391
468
|
}
|
|
392
469
|
|
|
@@ -395,8 +472,8 @@ async function handleImageDescription(runtime, params) {
|
|
|
395
472
|
let imageUrl;
|
|
396
473
|
let promptText;
|
|
397
474
|
const modelName = getImageModel(runtime);
|
|
398
|
-
|
|
399
|
-
const
|
|
475
|
+
logger6.log(`[OpenRouter] Using IMAGE_DESCRIPTION model: ${modelName}`);
|
|
476
|
+
const maxOutputTokens = 300;
|
|
400
477
|
if (typeof params === "string") {
|
|
401
478
|
imageUrl = params;
|
|
402
479
|
promptText = "Please analyze this image and provide a title and detailed description.";
|
|
@@ -419,12 +496,12 @@ async function handleImageDescription(runtime, params) {
|
|
|
419
496
|
const { text: responseText } = await generateText2({
|
|
420
497
|
model,
|
|
421
498
|
messages,
|
|
422
|
-
|
|
499
|
+
maxOutputTokens
|
|
423
500
|
});
|
|
424
501
|
return parseImageDescriptionResponse(responseText);
|
|
425
502
|
} catch (error) {
|
|
426
503
|
const message = error instanceof Error ? error.message : String(error);
|
|
427
|
-
|
|
504
|
+
logger6.error(`Error analyzing image: ${message}`);
|
|
428
505
|
return {
|
|
429
506
|
title: "Failed to analyze image",
|
|
430
507
|
description: `Error: ${message}`
|
|
@@ -433,18 +510,15 @@ async function handleImageDescription(runtime, params) {
|
|
|
433
510
|
}
|
|
434
511
|
async function handleImageGeneration(runtime, params) {
|
|
435
512
|
const modelName = getImageGenerationModel(runtime);
|
|
436
|
-
|
|
513
|
+
logger6.log(`[OpenRouter] Using IMAGE_GENERATION model: ${modelName}`);
|
|
437
514
|
const apiKey = getApiKey(runtime);
|
|
438
515
|
try {
|
|
439
|
-
if (!apiKey) {
|
|
440
|
-
logger7.error("[OpenRouter] OpenRouter API key is missing");
|
|
441
|
-
return [];
|
|
442
|
-
}
|
|
443
516
|
const baseUrl = getBaseURL(runtime);
|
|
517
|
+
const isBrowser2 = typeof globalThis !== "undefined" && globalThis.document;
|
|
444
518
|
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
445
519
|
method: "POST",
|
|
446
520
|
headers: {
|
|
447
|
-
Authorization: `Bearer ${apiKey}
|
|
521
|
+
...isBrowser2 ? {} : { Authorization: `Bearer ${apiKey}` },
|
|
448
522
|
"Content-Type": "application/json"
|
|
449
523
|
},
|
|
450
524
|
body: JSON.stringify({
|
|
@@ -457,8 +531,7 @@ async function handleImageGeneration(runtime, params) {
|
|
|
457
531
|
],
|
|
458
532
|
modalities: ["image", "text"]
|
|
459
533
|
}),
|
|
460
|
-
|
|
461
|
-
signal: AbortSignal.timeout ? AbortSignal.timeout(6e4) : void 0
|
|
534
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(60000) : undefined
|
|
462
535
|
});
|
|
463
536
|
if (!response.ok) {
|
|
464
537
|
const errorText = await response.text().catch(() => "");
|
|
@@ -472,16 +545,15 @@ async function handleImageGeneration(runtime, params) {
|
|
|
472
545
|
const base64Url = image.image_url.url;
|
|
473
546
|
const filepath = await saveBase64Image(base64Url, runtime.agentId, index);
|
|
474
547
|
if (filepath) {
|
|
475
|
-
|
|
548
|
+
logger6.log(`[OpenRouter] Returning image with filepath: ${filepath}`);
|
|
476
549
|
images.push({
|
|
477
550
|
url: filepath
|
|
478
|
-
// Use actual file path
|
|
479
551
|
});
|
|
480
552
|
savedPaths.push(filepath);
|
|
481
553
|
} else if (!base64Url.startsWith("data:")) {
|
|
482
554
|
images.push({ url: base64Url });
|
|
483
555
|
} else {
|
|
484
|
-
|
|
556
|
+
logger6.warn(`[OpenRouter] Failed to save image ${index + 1}, skipping`);
|
|
485
557
|
}
|
|
486
558
|
}
|
|
487
559
|
}
|
|
@@ -490,16 +562,16 @@ async function handleImageGeneration(runtime, params) {
|
|
|
490
562
|
savedPaths.forEach((path) => {
|
|
491
563
|
deleteImage(path);
|
|
492
564
|
});
|
|
493
|
-
},
|
|
565
|
+
}, 30000);
|
|
494
566
|
}
|
|
495
567
|
if (images.length === 0) {
|
|
496
568
|
throw new Error("No images generated in response");
|
|
497
569
|
}
|
|
498
|
-
|
|
570
|
+
logger6.log(`[OpenRouter] Generated ${images.length} image(s)`);
|
|
499
571
|
return images;
|
|
500
572
|
} catch (error) {
|
|
501
573
|
const message = error instanceof Error ? error.message : String(error);
|
|
502
|
-
|
|
574
|
+
logger6.error(`[OpenRouter] Error generating image: ${message}`);
|
|
503
575
|
return [];
|
|
504
576
|
}
|
|
505
577
|
}
|
|
@@ -545,9 +617,10 @@ var openrouterPlugin = {
|
|
|
545
617
|
}
|
|
546
618
|
}
|
|
547
619
|
};
|
|
548
|
-
var
|
|
620
|
+
var src_default = openrouterPlugin;
|
|
549
621
|
export {
|
|
550
|
-
|
|
551
|
-
|
|
622
|
+
openrouterPlugin,
|
|
623
|
+
src_default as default
|
|
552
624
|
};
|
|
553
|
-
|
|
625
|
+
|
|
626
|
+
//# debugId=9A894E662DDC9ED364756E2164756E21
|