@elizaos/plugin-openrouter 1.5.15 → 1.5.17

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.
@@ -19,7 +19,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
20
  // src/index.ts
21
21
  import {
22
- ModelType as ModelType4
22
+ ModelType as ModelType4,
23
+ logger as logger8
23
24
  } from "@elizaos/core";
24
25
 
25
26
  // src/init.ts
@@ -28,7 +29,11 @@ import { fetch as fetch2 } from "undici";
28
29
 
29
30
  // src/utils/config.ts
30
31
  function getSetting(runtime, key, defaultValue) {
31
- return runtime.getSetting(key) ?? process.env[key] ?? defaultValue;
32
+ const value = runtime.getSetting(key);
33
+ if (value !== undefined && value !== null) {
34
+ return String(value);
35
+ }
36
+ return process.env[key] ?? defaultValue;
32
37
  }
33
38
  function getBaseURL(runtime) {
34
39
  const browserURL = getSetting(runtime, "OPENROUTER_BROWSER_BASE_URL");
@@ -59,15 +64,6 @@ function shouldAutoCleanupImages(runtime) {
59
64
  const setting = getSetting(runtime, "OPENROUTER_AUTO_CLEANUP_IMAGES", "false");
60
65
  return setting?.toLowerCase() === "true";
61
66
  }
62
- function getToolExecutionMaxSteps(runtime) {
63
- const setting = getSetting(runtime, "OPENROUTER_TOOL_EXECUTION_MAX_STEPS", "15");
64
- const value = parseInt(setting || "15", 10);
65
- if (Number.isNaN(value) || value < 1)
66
- return 15;
67
- if (value > 100)
68
- return 100;
69
- return value;
70
- }
71
67
 
72
68
  // src/init.ts
73
69
  function initializeOpenRouter(_config, runtime) {
@@ -106,8 +102,8 @@ function initializeOpenRouter(_config, runtime) {
106
102
  }
107
103
 
108
104
  // src/models/text.ts
109
- import { logger as logger3, ModelType } from "@elizaos/core";
110
- import { generateText, stepCountIs } from "ai";
105
+ import { logger as logger2, ModelType } from "@elizaos/core";
106
+ import { generateText, streamText } from "ai";
111
107
 
112
108
  // src/providers/openrouter.ts
113
109
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
@@ -130,6 +126,8 @@ function emitModelUsageEvent(runtime, type, prompt, usage) {
130
126
  const outputTokens = Number(usage.outputTokens || 0);
131
127
  const totalTokens = Number(usage.totalTokens != null ? usage.totalTokens : inputTokens + outputTokens);
132
128
  runtime.emitEvent(EventType.MODEL_USED, {
129
+ runtime,
130
+ source: "openrouter",
133
131
  provider: "openrouter",
134
132
  type,
135
133
  prompt: truncatedPrompt,
@@ -141,8 +139,78 @@ function emitModelUsageEvent(runtime, type, prompt, usage) {
141
139
  });
142
140
  }
143
141
 
142
+ // src/models/text.ts
143
+ function buildGenerateParams(runtime, modelType, params) {
144
+ const { prompt, stopSequences = [] } = params;
145
+ const temperature = params.temperature ?? 0.7;
146
+ const frequencyPenalty = params.frequencyPenalty ?? 0.7;
147
+ const presencePenalty = params.presencePenalty ?? 0.7;
148
+ const resolvedMaxOutput = params.maxOutputTokens ?? params.maxTokens ?? 8192;
149
+ const openrouter = createOpenRouterProvider(runtime);
150
+ const modelName = modelType === ModelType.TEXT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
151
+ const modelLabel = modelType === ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE";
152
+ const generateParams = {
153
+ model: openrouter.chat(modelName),
154
+ prompt,
155
+ system: runtime.character.system ?? undefined,
156
+ temperature,
157
+ frequencyPenalty,
158
+ presencePenalty,
159
+ stopSequences
160
+ };
161
+ generateParams.maxOutputTokens = resolvedMaxOutput;
162
+ return { generateParams, modelName, modelLabel, prompt };
163
+ }
164
+ function handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelLabel) {
165
+ logger2.debug(`[OpenRouter] Streaming text with ${modelLabel} model`);
166
+ const streamResult = streamText(generateParams);
167
+ return {
168
+ textStream: streamResult.textStream,
169
+ text: streamResult.text,
170
+ usage: streamResult.usage.then((usage) => {
171
+ if (usage) {
172
+ emitModelUsageEvent(runtime, modelType, prompt, usage);
173
+ const inputTokens = usage.inputTokens ?? 0;
174
+ const outputTokens = usage.outputTokens ?? 0;
175
+ return {
176
+ promptTokens: inputTokens,
177
+ completionTokens: outputTokens,
178
+ totalTokens: inputTokens + outputTokens
179
+ };
180
+ }
181
+ return;
182
+ }),
183
+ finishReason: streamResult.finishReason
184
+ };
185
+ }
186
+ async function generateTextWithModel(runtime, modelType, params) {
187
+ const { generateParams, modelName, modelLabel, prompt } = buildGenerateParams(runtime, modelType, params);
188
+ logger2.debug(`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`);
189
+ if (params.stream) {
190
+ return handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelLabel);
191
+ }
192
+ const response = await generateText(generateParams);
193
+ if (response.usage) {
194
+ emitModelUsageEvent(runtime, modelType, prompt, response.usage);
195
+ }
196
+ return response.text;
197
+ }
198
+ async function handleTextSmall(runtime, params) {
199
+ return generateTextWithModel(runtime, ModelType.TEXT_SMALL, params);
200
+ }
201
+ async function handleTextLarge(runtime, params) {
202
+ return generateTextWithModel(runtime, ModelType.TEXT_LARGE, params);
203
+ }
204
+
205
+ // src/models/object.ts
206
+ import {
207
+ ModelType as ModelType2,
208
+ logger as logger4
209
+ } from "@elizaos/core";
210
+ import { generateObject, jsonSchema } from "ai";
211
+
144
212
  // src/utils/helpers.ts
145
- import { logger as logger2 } from "@elizaos/core";
213
+ import { logger as logger3 } from "@elizaos/core";
146
214
  import { JSONParseError } from "ai";
147
215
  function getJsonRepairFunction() {
148
216
  return async ({ text, error }) => {
@@ -155,17 +223,11 @@ function getJsonRepairFunction() {
155
223
  return null;
156
224
  } catch (jsonError) {
157
225
  const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
158
- logger2.warn(`Failed to repair JSON text: ${message}`);
226
+ logger3.warn(`Failed to repair JSON text: ${message}`);
159
227
  return null;
160
228
  }
161
229
  };
162
230
  }
163
- function handleEmptyToolResponse(modelType) {
164
- logger2.warn(`[${modelType}] No text generated after tool execution`);
165
- const fallbackText = "I executed the requested action. The tool completed successfully.";
166
- logger2.warn(`[${modelType}] Using fallback response text`);
167
- return fallbackText;
168
- }
169
231
  function parseImageDescriptionResponse(responseText) {
170
232
  try {
171
233
  const jsonResponse = JSON.parse(responseText);
@@ -173,7 +235,7 @@ function parseImageDescriptionResponse(responseText) {
173
235
  return jsonResponse;
174
236
  }
175
237
  } catch (e) {
176
- logger2.debug(`Parsing as JSON failed, processing as text: ${e}`);
238
+ logger3.debug(`Parsing as JSON failed, processing as text: ${e}`);
177
239
  }
178
240
  const titleMatch = responseText.match(/title[:\s]+(.+?)(?:\n|$)/i);
179
241
  const title = titleMatch?.[1]?.trim() || "Image Analysis";
@@ -182,7 +244,7 @@ function parseImageDescriptionResponse(responseText) {
182
244
  }
183
245
  async function handleObjectGenerationError(error) {
184
246
  if (error instanceof JSONParseError) {
185
- logger2.error(`[generateObject] Failed to parse JSON: ${error.message}`);
247
+ logger3.error(`[generateObject] Failed to parse JSON: ${error.message}`);
186
248
  const repairFunction = getJsonRepairFunction();
187
249
  const repairedJsonString = await repairFunction({
188
250
  text: error.text,
@@ -191,151 +253,29 @@ async function handleObjectGenerationError(error) {
191
253
  if (repairedJsonString) {
192
254
  try {
193
255
  const repairedObject = JSON.parse(repairedJsonString);
194
- logger2.log("[generateObject] Successfully repaired JSON.");
256
+ logger3.log("[generateObject] Successfully repaired JSON.");
195
257
  return repairedObject;
196
258
  } catch (repairParseError) {
197
259
  const message = repairParseError instanceof Error ? repairParseError.message : String(repairParseError);
198
- logger2.error(`[generateObject] Failed to parse repaired JSON: ${message}`);
260
+ logger3.error(`[generateObject] Failed to parse repaired JSON: ${message}`);
199
261
  if (repairParseError instanceof Error)
200
262
  throw repairParseError;
201
263
  throw Object.assign(new Error(message), { cause: repairParseError });
202
264
  }
203
265
  } else {
204
- logger2.error("[generateObject] JSON repair failed.");
266
+ logger3.error("[generateObject] JSON repair failed.");
205
267
  throw error;
206
268
  }
207
269
  } else {
208
270
  const message = error instanceof Error ? error.message : String(error);
209
- logger2.error(`[generateObject] Unknown error: ${message}`);
271
+ logger3.error(`[generateObject] Unknown error: ${message}`);
210
272
  if (error instanceof Error)
211
273
  throw error;
212
274
  throw Object.assign(new Error(message), { cause: error });
213
275
  }
214
276
  }
215
- function isLikelyBase64(key, value) {
216
- const base64KeyPattern = /^(data|content|body|payload|encoded|b64|base64|document)$/i;
217
- if (!base64KeyPattern.test(key))
218
- return false;
219
- if (value.length < 20 || value.length > 1024 * 1024)
220
- return false;
221
- if (value.length % 4 !== 0)
222
- return false;
223
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value))
224
- return false;
225
- return true;
226
- }
227
- function decodeBase64Fields(obj, depth = 0) {
228
- if (depth > 5)
229
- return obj;
230
- if (!obj || typeof obj !== "object")
231
- return obj;
232
- if (Array.isArray(obj))
233
- return obj.map((item) => decodeBase64Fields(item, depth + 1));
234
- const decoded = {};
235
- for (const [key, value] of Object.entries(obj)) {
236
- if (typeof value === "string" && isLikelyBase64(key, value)) {
237
- try {
238
- decoded[key] = Buffer.from(value, "base64").toString("utf8");
239
- logger2.debug(`[decodeBase64] Decoded field '${key}' (${value.length} chars)`);
240
- } catch (error) {
241
- logger2.warn(`[decodeBase64] Failed to decode field '${key}': ${error}`);
242
- decoded[key] = value;
243
- }
244
- } else if (value && typeof value === "object") {
245
- decoded[key] = decodeBase64Fields(value, depth + 1);
246
- } else {
247
- decoded[key] = value;
248
- }
249
- }
250
- return decoded;
251
- }
252
-
253
- // src/models/text.ts
254
- async function generateTextWithModel(runtime, modelType, params) {
255
- const { prompt, stopSequences = [], tools, toolChoice } = params;
256
- const temperature = params.temperature ?? 0.7;
257
- const frequencyPenalty = params.frequencyPenalty ?? 0.7;
258
- const presencePenalty = params.presencePenalty ?? 0.7;
259
- const resolvedMaxOutput = params.maxOutputTokens ?? params.maxTokens ?? 8192;
260
- const openrouter = createOpenRouterProvider(runtime);
261
- const modelName = modelType === ModelType.TEXT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
262
- const modelLabel = modelType === ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE";
263
- logger3.debug(`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`);
264
- const generateParams = {
265
- model: openrouter.chat(modelName),
266
- prompt,
267
- system: runtime.character.system ?? undefined,
268
- temperature,
269
- frequencyPenalty,
270
- presencePenalty,
271
- stopSequences
272
- };
273
- generateParams.maxOutputTokens = resolvedMaxOutput;
274
- if (tools) {
275
- generateParams.tools = tools;
276
- const maxSteps = getToolExecutionMaxSteps(runtime);
277
- generateParams.stopWhen = stepCountIs(maxSteps);
278
- logger3.debug(`[OpenRouter] Using maxSteps: ${maxSteps} for tool execution`);
279
- }
280
- if (toolChoice) {
281
- generateParams.toolChoice = toolChoice;
282
- }
283
- let capturedToolResults = [];
284
- let capturedToolCalls = [];
285
- if (tools) {
286
- generateParams.onStepFinish = async (stepResult) => {
287
- if (stepResult.toolCalls && stepResult.toolCalls.length > 0) {
288
- capturedToolCalls = [
289
- ...capturedToolCalls,
290
- ...stepResult.toolCalls
291
- ];
292
- }
293
- if (stepResult.content && Array.isArray(stepResult.content)) {
294
- const toolResultsFromContent = stepResult.content.filter((content) => content.type === "tool-result" && content.output).map((content) => ({
295
- toolCallId: content.toolCallId,
296
- result: decodeBase64Fields(content.output)
297
- }));
298
- if (toolResultsFromContent.length > 0) {
299
- capturedToolResults = [...capturedToolResults, ...toolResultsFromContent];
300
- }
301
- }
302
- };
303
- }
304
- const response = await generateText(generateParams);
305
- let responseText;
306
- if (tools && (!response.text || response.text.trim() === "" || response.text === "Tools executed successfully.")) {
307
- responseText = handleEmptyToolResponse(modelLabel);
308
- } else {
309
- responseText = response.text;
310
- }
311
- if (response.usage) {
312
- emitModelUsageEvent(runtime, modelType, prompt, response.usage);
313
- }
314
- if (tools && response.steps && response.steps.length > 0) {
315
- return {
316
- text: responseText,
317
- toolCalls: capturedToolCalls,
318
- toolResults: capturedToolResults,
319
- steps: response.steps,
320
- usage: response.usage,
321
- finishReason: response.finishReason
322
- };
323
- }
324
- return responseText;
325
- }
326
- async function handleTextSmall(runtime, params) {
327
- return generateTextWithModel(runtime, ModelType.TEXT_SMALL, params);
328
- }
329
- async function handleTextLarge(runtime, params) {
330
- return generateTextWithModel(runtime, ModelType.TEXT_LARGE, params);
331
- }
332
277
 
333
278
  // src/models/object.ts
334
- import {
335
- ModelType as ModelType2,
336
- logger as logger4
337
- } from "@elizaos/core";
338
- import { generateObject } from "ai";
339
279
  async function generateObjectWithModel(runtime, modelType, params) {
340
280
  const openrouter = createOpenRouterProvider(runtime);
341
281
  const modelName = modelType === ModelType2.OBJECT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
@@ -345,7 +285,7 @@ async function generateObjectWithModel(runtime, modelType, params) {
345
285
  try {
346
286
  const { object, usage } = await generateObject({
347
287
  model: openrouter.chat(modelName),
348
- ...params.schema && { schema: params.schema },
288
+ ...params.schema && { schema: jsonSchema(params.schema) },
349
289
  output: params.schema ? "object" : "no-schema",
350
290
  prompt: params.prompt,
351
291
  temperature,
@@ -718,7 +658,127 @@ var openrouterPlugin = {
718
658
  [ModelType4.TEXT_EMBEDDING]: async (runtime, params) => {
719
659
  return handleTextEmbedding(runtime, params);
720
660
  }
721
- }
661
+ },
662
+ tests: [
663
+ {
664
+ name: "openrouter_plugin_tests",
665
+ tests: [
666
+ {
667
+ name: "openrouter_test_text_small",
668
+ fn: async (runtime) => {
669
+ try {
670
+ const text = await runtime.useModel(ModelType4.TEXT_SMALL, {
671
+ prompt: "What is the nature of reality in 10 words?"
672
+ });
673
+ if (text.length === 0) {
674
+ throw new Error("Failed to generate text");
675
+ }
676
+ logger8.log({ text }, "generated with test_text_small");
677
+ } catch (error) {
678
+ const message = error instanceof Error ? error.message : String(error);
679
+ logger8.error(`Error in test_text_small: ${message}`);
680
+ throw error;
681
+ }
682
+ }
683
+ },
684
+ {
685
+ name: "openrouter_test_text_large",
686
+ fn: async (runtime) => {
687
+ try {
688
+ const text = await runtime.useModel(ModelType4.TEXT_LARGE, {
689
+ prompt: "What is the nature of reality in 10 words?"
690
+ });
691
+ if (text.length === 0) {
692
+ throw new Error("Failed to generate text");
693
+ }
694
+ logger8.log({ text }, "generated with test_text_large");
695
+ } catch (error) {
696
+ const message = error instanceof Error ? error.message : String(error);
697
+ logger8.error(`Error in test_text_large: ${message}`);
698
+ throw error;
699
+ }
700
+ }
701
+ },
702
+ {
703
+ name: "openrouter_test_text_generation_large",
704
+ fn: async (runtime) => {
705
+ try {
706
+ const result = await runtime.useModel(ModelType4.TEXT_LARGE, {
707
+ prompt: "Say hello in 5 words."
708
+ });
709
+ if (!result || result.length === 0) {
710
+ throw new Error("Text generation returned empty result");
711
+ }
712
+ logger8.log({ result }, "Text generation test completed");
713
+ } catch (error) {
714
+ const message = error instanceof Error ? error.message : String(error);
715
+ logger8.error(`Error in openrouter_test_text_generation_large: ${message}`);
716
+ throw error;
717
+ }
718
+ }
719
+ },
720
+ {
721
+ name: "openrouter_test_streaming",
722
+ fn: async (runtime) => {
723
+ try {
724
+ const chunks = [];
725
+ const result = await runtime.useModel(ModelType4.TEXT_LARGE, {
726
+ prompt: "Count from 1 to 5.",
727
+ onStreamChunk: (chunk) => {
728
+ chunks.push(chunk);
729
+ }
730
+ });
731
+ if (!result || result.length === 0) {
732
+ throw new Error("Streaming returned empty result");
733
+ }
734
+ if (chunks.length === 0) {
735
+ throw new Error("No streaming chunks received");
736
+ }
737
+ logger8.log({ chunks: chunks.length, result: result.substring(0, 50) }, "Streaming test completed");
738
+ } catch (error) {
739
+ const message = error instanceof Error ? error.message : String(error);
740
+ logger8.error(`Error in openrouter_test_streaming: ${message}`);
741
+ throw error;
742
+ }
743
+ }
744
+ },
745
+ {
746
+ name: "openrouter_test_object_small",
747
+ fn: async (runtime) => {
748
+ try {
749
+ const result = await runtime.useModel(ModelType4.OBJECT_SMALL, {
750
+ prompt: "Create a simple JSON object with a message field saying hello",
751
+ schema: { type: "object" }
752
+ });
753
+ logger8.log({ result }, "Generated object with test_object_small");
754
+ if (!result || typeof result === "object" && "error" in result) {
755
+ throw new Error("Failed to generate object");
756
+ }
757
+ } catch (error) {
758
+ const message = error instanceof Error ? error.message : String(error);
759
+ logger8.error(`Error in test_object_small: ${message}`);
760
+ throw error;
761
+ }
762
+ }
763
+ },
764
+ {
765
+ name: "openrouter_test_text_embedding",
766
+ fn: async (runtime) => {
767
+ try {
768
+ const embedding = await runtime.useModel(ModelType4.TEXT_EMBEDDING, {
769
+ text: "Hello, world!"
770
+ });
771
+ logger8.log({ embedding }, "embedding");
772
+ } catch (error) {
773
+ const message = error instanceof Error ? error.message : String(error);
774
+ logger8.error(`Error in test_text_embedding: ${message}`);
775
+ throw error;
776
+ }
777
+ }
778
+ }
779
+ ]
780
+ }
781
+ ]
722
782
  };
723
783
  var src_default = openrouterPlugin;
724
784
  export {
@@ -726,4 +786,4 @@ export {
726
786
  src_default as default
727
787
  };
728
788
 
729
- //# debugId=A89A49588DC602FE64756E2164756E21
789
+ //# debugId=4624CB80FF24B85064756E2164756E21