@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.
@@ -56,7 +56,11 @@ var import_undici = require("undici");
56
56
 
57
57
  // src/utils/config.ts
58
58
  function getSetting(runtime, key, defaultValue) {
59
- return runtime.getSetting(key) ?? process.env[key] ?? defaultValue;
59
+ const value = runtime.getSetting(key);
60
+ if (value !== undefined && value !== null) {
61
+ return String(value);
62
+ }
63
+ return process.env[key] ?? defaultValue;
60
64
  }
61
65
  function getBaseURL(runtime) {
62
66
  const browserURL = getSetting(runtime, "OPENROUTER_BROWSER_BASE_URL");
@@ -87,15 +91,6 @@ function shouldAutoCleanupImages(runtime) {
87
91
  const setting = getSetting(runtime, "OPENROUTER_AUTO_CLEANUP_IMAGES", "false");
88
92
  return setting?.toLowerCase() === "true";
89
93
  }
90
- function getToolExecutionMaxSteps(runtime) {
91
- const setting = getSetting(runtime, "OPENROUTER_TOOL_EXECUTION_MAX_STEPS", "15");
92
- const value = parseInt(setting || "15", 10);
93
- if (Number.isNaN(value) || value < 1)
94
- return 15;
95
- if (value > 100)
96
- return 100;
97
- return value;
98
- }
99
94
 
100
95
  // src/init.ts
101
96
  function initializeOpenRouter(_config, runtime) {
@@ -134,8 +129,8 @@ function initializeOpenRouter(_config, runtime) {
134
129
  }
135
130
 
136
131
  // src/models/text.ts
137
- var import_core4 = require("@elizaos/core");
138
- var import_ai2 = require("ai");
132
+ var import_core3 = require("@elizaos/core");
133
+ var import_ai = require("ai");
139
134
 
140
135
  // src/providers/openrouter.ts
141
136
  var import_ai_sdk_provider = require("@openrouter/ai-sdk-provider");
@@ -156,6 +151,8 @@ function emitModelUsageEvent(runtime, type, prompt, usage) {
156
151
  const outputTokens = Number(usage.outputTokens || 0);
157
152
  const totalTokens = Number(usage.totalTokens != null ? usage.totalTokens : inputTokens + outputTokens);
158
153
  runtime.emitEvent(import_core2.EventType.MODEL_USED, {
154
+ runtime,
155
+ source: "openrouter",
159
156
  provider: "openrouter",
160
157
  type,
161
158
  prompt: truncatedPrompt,
@@ -167,13 +164,80 @@ function emitModelUsageEvent(runtime, type, prompt, usage) {
167
164
  });
168
165
  }
169
166
 
167
+ // src/models/text.ts
168
+ function buildGenerateParams(runtime, modelType, params) {
169
+ const { prompt, stopSequences = [] } = params;
170
+ const temperature = params.temperature ?? 0.7;
171
+ const frequencyPenalty = params.frequencyPenalty ?? 0.7;
172
+ const presencePenalty = params.presencePenalty ?? 0.7;
173
+ const resolvedMaxOutput = params.maxOutputTokens ?? params.maxTokens ?? 8192;
174
+ const openrouter = createOpenRouterProvider(runtime);
175
+ const modelName = modelType === import_core3.ModelType.TEXT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
176
+ const modelLabel = modelType === import_core3.ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE";
177
+ const generateParams = {
178
+ model: openrouter.chat(modelName),
179
+ prompt,
180
+ system: runtime.character.system ?? undefined,
181
+ temperature,
182
+ frequencyPenalty,
183
+ presencePenalty,
184
+ stopSequences
185
+ };
186
+ generateParams.maxOutputTokens = resolvedMaxOutput;
187
+ return { generateParams, modelName, modelLabel, prompt };
188
+ }
189
+ function handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelLabel) {
190
+ import_core3.logger.debug(`[OpenRouter] Streaming text with ${modelLabel} model`);
191
+ const streamResult = import_ai.streamText(generateParams);
192
+ return {
193
+ textStream: streamResult.textStream,
194
+ text: streamResult.text,
195
+ usage: streamResult.usage.then((usage) => {
196
+ if (usage) {
197
+ emitModelUsageEvent(runtime, modelType, prompt, usage);
198
+ const inputTokens = usage.inputTokens ?? 0;
199
+ const outputTokens = usage.outputTokens ?? 0;
200
+ return {
201
+ promptTokens: inputTokens,
202
+ completionTokens: outputTokens,
203
+ totalTokens: inputTokens + outputTokens
204
+ };
205
+ }
206
+ return;
207
+ }),
208
+ finishReason: streamResult.finishReason
209
+ };
210
+ }
211
+ async function generateTextWithModel(runtime, modelType, params) {
212
+ const { generateParams, modelName, modelLabel, prompt } = buildGenerateParams(runtime, modelType, params);
213
+ import_core3.logger.debug(`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`);
214
+ if (params.stream) {
215
+ return handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelLabel);
216
+ }
217
+ const response = await import_ai.generateText(generateParams);
218
+ if (response.usage) {
219
+ emitModelUsageEvent(runtime, modelType, prompt, response.usage);
220
+ }
221
+ return response.text;
222
+ }
223
+ async function handleTextSmall(runtime, params) {
224
+ return generateTextWithModel(runtime, import_core3.ModelType.TEXT_SMALL, params);
225
+ }
226
+ async function handleTextLarge(runtime, params) {
227
+ return generateTextWithModel(runtime, import_core3.ModelType.TEXT_LARGE, params);
228
+ }
229
+
230
+ // src/models/object.ts
231
+ var import_core5 = require("@elizaos/core");
232
+ var import_ai3 = require("ai");
233
+
170
234
  // src/utils/helpers.ts
171
- var import_core3 = require("@elizaos/core");
172
- var import_ai = require("ai");
235
+ var import_core4 = require("@elizaos/core");
236
+ var import_ai2 = require("ai");
173
237
  function getJsonRepairFunction() {
174
238
  return async ({ text, error }) => {
175
239
  try {
176
- if (error instanceof import_ai.JSONParseError) {
240
+ if (error instanceof import_ai2.JSONParseError) {
177
241
  const cleanedText = text.replace(/```json\n|\n```|```/g, "");
178
242
  JSON.parse(cleanedText);
179
243
  return cleanedText;
@@ -181,17 +245,11 @@ function getJsonRepairFunction() {
181
245
  return null;
182
246
  } catch (jsonError) {
183
247
  const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
184
- import_core3.logger.warn(`Failed to repair JSON text: ${message}`);
248
+ import_core4.logger.warn(`Failed to repair JSON text: ${message}`);
185
249
  return null;
186
250
  }
187
251
  };
188
252
  }
189
- function handleEmptyToolResponse(modelType) {
190
- import_core3.logger.warn(`[${modelType}] No text generated after tool execution`);
191
- const fallbackText = "I executed the requested action. The tool completed successfully.";
192
- import_core3.logger.warn(`[${modelType}] Using fallback response text`);
193
- return fallbackText;
194
- }
195
253
  function parseImageDescriptionResponse(responseText) {
196
254
  try {
197
255
  const jsonResponse = JSON.parse(responseText);
@@ -199,7 +257,7 @@ function parseImageDescriptionResponse(responseText) {
199
257
  return jsonResponse;
200
258
  }
201
259
  } catch (e) {
202
- import_core3.logger.debug(`Parsing as JSON failed, processing as text: ${e}`);
260
+ import_core4.logger.debug(`Parsing as JSON failed, processing as text: ${e}`);
203
261
  }
204
262
  const titleMatch = responseText.match(/title[:\s]+(.+?)(?:\n|$)/i);
205
263
  const title = titleMatch?.[1]?.trim() || "Image Analysis";
@@ -207,8 +265,8 @@ function parseImageDescriptionResponse(responseText) {
207
265
  return { title, description };
208
266
  }
209
267
  async function handleObjectGenerationError(error) {
210
- if (error instanceof import_ai.JSONParseError) {
211
- import_core3.logger.error(`[generateObject] Failed to parse JSON: ${error.message}`);
268
+ if (error instanceof import_ai2.JSONParseError) {
269
+ import_core4.logger.error(`[generateObject] Failed to parse JSON: ${error.message}`);
212
270
  const repairFunction = getJsonRepairFunction();
213
271
  const repairedJsonString = await repairFunction({
214
272
  text: error.text,
@@ -217,148 +275,29 @@ async function handleObjectGenerationError(error) {
217
275
  if (repairedJsonString) {
218
276
  try {
219
277
  const repairedObject = JSON.parse(repairedJsonString);
220
- import_core3.logger.log("[generateObject] Successfully repaired JSON.");
278
+ import_core4.logger.log("[generateObject] Successfully repaired JSON.");
221
279
  return repairedObject;
222
280
  } catch (repairParseError) {
223
281
  const message = repairParseError instanceof Error ? repairParseError.message : String(repairParseError);
224
- import_core3.logger.error(`[generateObject] Failed to parse repaired JSON: ${message}`);
282
+ import_core4.logger.error(`[generateObject] Failed to parse repaired JSON: ${message}`);
225
283
  if (repairParseError instanceof Error)
226
284
  throw repairParseError;
227
285
  throw Object.assign(new Error(message), { cause: repairParseError });
228
286
  }
229
287
  } else {
230
- import_core3.logger.error("[generateObject] JSON repair failed.");
288
+ import_core4.logger.error("[generateObject] JSON repair failed.");
231
289
  throw error;
232
290
  }
233
291
  } else {
234
292
  const message = error instanceof Error ? error.message : String(error);
235
- import_core3.logger.error(`[generateObject] Unknown error: ${message}`);
293
+ import_core4.logger.error(`[generateObject] Unknown error: ${message}`);
236
294
  if (error instanceof Error)
237
295
  throw error;
238
296
  throw Object.assign(new Error(message), { cause: error });
239
297
  }
240
298
  }
241
- function isLikelyBase64(key, value) {
242
- const base64KeyPattern = /^(data|content|body|payload|encoded|b64|base64|document)$/i;
243
- if (!base64KeyPattern.test(key))
244
- return false;
245
- if (value.length < 20 || value.length > 1024 * 1024)
246
- return false;
247
- if (value.length % 4 !== 0)
248
- return false;
249
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value))
250
- return false;
251
- return true;
252
- }
253
- function decodeBase64Fields(obj, depth = 0) {
254
- if (depth > 5)
255
- return obj;
256
- if (!obj || typeof obj !== "object")
257
- return obj;
258
- if (Array.isArray(obj))
259
- return obj.map((item) => decodeBase64Fields(item, depth + 1));
260
- const decoded = {};
261
- for (const [key, value] of Object.entries(obj)) {
262
- if (typeof value === "string" && isLikelyBase64(key, value)) {
263
- try {
264
- decoded[key] = Buffer.from(value, "base64").toString("utf8");
265
- import_core3.logger.debug(`[decodeBase64] Decoded field '${key}' (${value.length} chars)`);
266
- } catch (error) {
267
- import_core3.logger.warn(`[decodeBase64] Failed to decode field '${key}': ${error}`);
268
- decoded[key] = value;
269
- }
270
- } else if (value && typeof value === "object") {
271
- decoded[key] = decodeBase64Fields(value, depth + 1);
272
- } else {
273
- decoded[key] = value;
274
- }
275
- }
276
- return decoded;
277
- }
278
-
279
- // src/models/text.ts
280
- async function generateTextWithModel(runtime, modelType, params) {
281
- const { prompt, stopSequences = [], tools, toolChoice } = params;
282
- const temperature = params.temperature ?? 0.7;
283
- const frequencyPenalty = params.frequencyPenalty ?? 0.7;
284
- const presencePenalty = params.presencePenalty ?? 0.7;
285
- const resolvedMaxOutput = params.maxOutputTokens ?? params.maxTokens ?? 8192;
286
- const openrouter = createOpenRouterProvider(runtime);
287
- const modelName = modelType === import_core4.ModelType.TEXT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
288
- const modelLabel = modelType === import_core4.ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE";
289
- import_core4.logger.debug(`[OpenRouter] Generating text with ${modelLabel} model: ${modelName}`);
290
- const generateParams = {
291
- model: openrouter.chat(modelName),
292
- prompt,
293
- system: runtime.character.system ?? undefined,
294
- temperature,
295
- frequencyPenalty,
296
- presencePenalty,
297
- stopSequences
298
- };
299
- generateParams.maxOutputTokens = resolvedMaxOutput;
300
- if (tools) {
301
- generateParams.tools = tools;
302
- const maxSteps = getToolExecutionMaxSteps(runtime);
303
- generateParams.stopWhen = import_ai2.stepCountIs(maxSteps);
304
- import_core4.logger.debug(`[OpenRouter] Using maxSteps: ${maxSteps} for tool execution`);
305
- }
306
- if (toolChoice) {
307
- generateParams.toolChoice = toolChoice;
308
- }
309
- let capturedToolResults = [];
310
- let capturedToolCalls = [];
311
- if (tools) {
312
- generateParams.onStepFinish = async (stepResult) => {
313
- if (stepResult.toolCalls && stepResult.toolCalls.length > 0) {
314
- capturedToolCalls = [
315
- ...capturedToolCalls,
316
- ...stepResult.toolCalls
317
- ];
318
- }
319
- if (stepResult.content && Array.isArray(stepResult.content)) {
320
- const toolResultsFromContent = stepResult.content.filter((content) => content.type === "tool-result" && content.output).map((content) => ({
321
- toolCallId: content.toolCallId,
322
- result: decodeBase64Fields(content.output)
323
- }));
324
- if (toolResultsFromContent.length > 0) {
325
- capturedToolResults = [...capturedToolResults, ...toolResultsFromContent];
326
- }
327
- }
328
- };
329
- }
330
- const response = await import_ai2.generateText(generateParams);
331
- let responseText;
332
- if (tools && (!response.text || response.text.trim() === "" || response.text === "Tools executed successfully.")) {
333
- responseText = handleEmptyToolResponse(modelLabel);
334
- } else {
335
- responseText = response.text;
336
- }
337
- if (response.usage) {
338
- emitModelUsageEvent(runtime, modelType, prompt, response.usage);
339
- }
340
- if (tools && response.steps && response.steps.length > 0) {
341
- return {
342
- text: responseText,
343
- toolCalls: capturedToolCalls,
344
- toolResults: capturedToolResults,
345
- steps: response.steps,
346
- usage: response.usage,
347
- finishReason: response.finishReason
348
- };
349
- }
350
- return responseText;
351
- }
352
- async function handleTextSmall(runtime, params) {
353
- return generateTextWithModel(runtime, import_core4.ModelType.TEXT_SMALL, params);
354
- }
355
- async function handleTextLarge(runtime, params) {
356
- return generateTextWithModel(runtime, import_core4.ModelType.TEXT_LARGE, params);
357
- }
358
299
 
359
300
  // src/models/object.ts
360
- var import_core5 = require("@elizaos/core");
361
- var import_ai3 = require("ai");
362
301
  async function generateObjectWithModel(runtime, modelType, params) {
363
302
  const openrouter = createOpenRouterProvider(runtime);
364
303
  const modelName = modelType === import_core5.ModelType.OBJECT_SMALL ? getSmallModel(runtime) : getLargeModel(runtime);
@@ -368,7 +307,7 @@ async function generateObjectWithModel(runtime, modelType, params) {
368
307
  try {
369
308
  const { object, usage } = await import_ai3.generateObject({
370
309
  model: openrouter.chat(modelName),
371
- ...params.schema && { schema: params.schema },
310
+ ...params.schema && { schema: import_ai3.jsonSchema(params.schema) },
372
311
  output: params.schema ? "object" : "no-schema",
373
312
  prompt: params.prompt,
374
313
  temperature,
@@ -739,8 +678,128 @@ var openrouterPlugin = {
739
678
  [import_core9.ModelType.TEXT_EMBEDDING]: async (runtime, params) => {
740
679
  return handleTextEmbedding(runtime, params);
741
680
  }
742
- }
681
+ },
682
+ tests: [
683
+ {
684
+ name: "openrouter_plugin_tests",
685
+ tests: [
686
+ {
687
+ name: "openrouter_test_text_small",
688
+ fn: async (runtime) => {
689
+ try {
690
+ const text = await runtime.useModel(import_core9.ModelType.TEXT_SMALL, {
691
+ prompt: "What is the nature of reality in 10 words?"
692
+ });
693
+ if (text.length === 0) {
694
+ throw new Error("Failed to generate text");
695
+ }
696
+ import_core9.logger.log({ text }, "generated with test_text_small");
697
+ } catch (error) {
698
+ const message = error instanceof Error ? error.message : String(error);
699
+ import_core9.logger.error(`Error in test_text_small: ${message}`);
700
+ throw error;
701
+ }
702
+ }
703
+ },
704
+ {
705
+ name: "openrouter_test_text_large",
706
+ fn: async (runtime) => {
707
+ try {
708
+ const text = await runtime.useModel(import_core9.ModelType.TEXT_LARGE, {
709
+ prompt: "What is the nature of reality in 10 words?"
710
+ });
711
+ if (text.length === 0) {
712
+ throw new Error("Failed to generate text");
713
+ }
714
+ import_core9.logger.log({ text }, "generated with test_text_large");
715
+ } catch (error) {
716
+ const message = error instanceof Error ? error.message : String(error);
717
+ import_core9.logger.error(`Error in test_text_large: ${message}`);
718
+ throw error;
719
+ }
720
+ }
721
+ },
722
+ {
723
+ name: "openrouter_test_text_generation_large",
724
+ fn: async (runtime) => {
725
+ try {
726
+ const result = await runtime.useModel(import_core9.ModelType.TEXT_LARGE, {
727
+ prompt: "Say hello in 5 words."
728
+ });
729
+ if (!result || result.length === 0) {
730
+ throw new Error("Text generation returned empty result");
731
+ }
732
+ import_core9.logger.log({ result }, "Text generation test completed");
733
+ } catch (error) {
734
+ const message = error instanceof Error ? error.message : String(error);
735
+ import_core9.logger.error(`Error in openrouter_test_text_generation_large: ${message}`);
736
+ throw error;
737
+ }
738
+ }
739
+ },
740
+ {
741
+ name: "openrouter_test_streaming",
742
+ fn: async (runtime) => {
743
+ try {
744
+ const chunks = [];
745
+ const result = await runtime.useModel(import_core9.ModelType.TEXT_LARGE, {
746
+ prompt: "Count from 1 to 5.",
747
+ onStreamChunk: (chunk) => {
748
+ chunks.push(chunk);
749
+ }
750
+ });
751
+ if (!result || result.length === 0) {
752
+ throw new Error("Streaming returned empty result");
753
+ }
754
+ if (chunks.length === 0) {
755
+ throw new Error("No streaming chunks received");
756
+ }
757
+ import_core9.logger.log({ chunks: chunks.length, result: result.substring(0, 50) }, "Streaming test completed");
758
+ } catch (error) {
759
+ const message = error instanceof Error ? error.message : String(error);
760
+ import_core9.logger.error(`Error in openrouter_test_streaming: ${message}`);
761
+ throw error;
762
+ }
763
+ }
764
+ },
765
+ {
766
+ name: "openrouter_test_object_small",
767
+ fn: async (runtime) => {
768
+ try {
769
+ const result = await runtime.useModel(import_core9.ModelType.OBJECT_SMALL, {
770
+ prompt: "Create a simple JSON object with a message field saying hello",
771
+ schema: { type: "object" }
772
+ });
773
+ import_core9.logger.log({ result }, "Generated object with test_object_small");
774
+ if (!result || typeof result === "object" && "error" in result) {
775
+ throw new Error("Failed to generate object");
776
+ }
777
+ } catch (error) {
778
+ const message = error instanceof Error ? error.message : String(error);
779
+ import_core9.logger.error(`Error in test_object_small: ${message}`);
780
+ throw error;
781
+ }
782
+ }
783
+ },
784
+ {
785
+ name: "openrouter_test_text_embedding",
786
+ fn: async (runtime) => {
787
+ try {
788
+ const embedding = await runtime.useModel(import_core9.ModelType.TEXT_EMBEDDING, {
789
+ text: "Hello, world!"
790
+ });
791
+ import_core9.logger.log({ embedding }, "embedding");
792
+ } catch (error) {
793
+ const message = error instanceof Error ? error.message : String(error);
794
+ import_core9.logger.error(`Error in test_text_embedding: ${message}`);
795
+ throw error;
796
+ }
797
+ }
798
+ }
799
+ ]
800
+ }
801
+ ]
743
802
  };
744
803
  var src_default = openrouterPlugin;
745
804
 
746
- //# debugId=58E9583DC191044464756E2164756E21
805
+ //# debugId=65CA0E3B77F6CA5664756E2164756E21