@elizaos/plugin-openrouter 2.0.3-beta.6 → 2.0.3-beta.7
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/dist/browser/index.browser.js +1053 -0
- package/dist/browser/index.browser.js.map +19 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.node.cjs +1083 -0
- package/dist/cjs/index.node.cjs.map +19 -0
- package/dist/index.d.ts +5 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.node.js +1053 -0
- package/dist/node/index.node.js.map +19 -0
- package/package.json +6 -5
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
// plugin.ts
|
|
2
|
+
import {
|
|
3
|
+
logger as logger5,
|
|
4
|
+
ModelType as ModelType5
|
|
5
|
+
} from "@elizaos/core";
|
|
6
|
+
|
|
7
|
+
// init.ts
|
|
8
|
+
import { logger } from "@elizaos/core";
|
|
9
|
+
|
|
10
|
+
// utils/config.ts
|
|
11
|
+
var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
12
|
+
var DEFAULT_SMALL_MODEL = "google/gemini-2.5-flash-lite";
|
|
13
|
+
var DEFAULT_LARGE_MODEL = "google/gemini-2.5-flash";
|
|
14
|
+
var DEFAULT_IMAGE_MODEL = "x-ai/grok-2-vision-1212";
|
|
15
|
+
var DEFAULT_IMAGE_GENERATION_MODEL = "google/gemini-2.5-flash-image-preview";
|
|
16
|
+
var DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small";
|
|
17
|
+
var DEFAULT_TRANSCRIPTION_MODEL = "openai/whisper-large-v3";
|
|
18
|
+
var DEFAULT_EMBEDDING_DIMENSIONS = 1536;
|
|
19
|
+
function getEnvValue(key) {
|
|
20
|
+
if (typeof process === "undefined" || !process.env) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const value = process.env[key];
|
|
24
|
+
return value === undefined ? undefined : String(value);
|
|
25
|
+
}
|
|
26
|
+
function getSetting(runtime, key, defaultValue) {
|
|
27
|
+
const value = runtime.getSetting(key);
|
|
28
|
+
if (value !== undefined && value !== null) {
|
|
29
|
+
return String(value);
|
|
30
|
+
}
|
|
31
|
+
return getEnvValue(key) ?? defaultValue;
|
|
32
|
+
}
|
|
33
|
+
function getBaseURL(runtime) {
|
|
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", DEFAULT_BASE_URL) || DEFAULT_BASE_URL;
|
|
39
|
+
}
|
|
40
|
+
function getApiKey(runtime) {
|
|
41
|
+
return getSetting(runtime, "OPENROUTER_API_KEY");
|
|
42
|
+
}
|
|
43
|
+
function getSmallModel(runtime) {
|
|
44
|
+
return getSetting(runtime, "OPENROUTER_SMALL_MODEL") ?? getSetting(runtime, "SMALL_MODEL", DEFAULT_SMALL_MODEL) ?? DEFAULT_SMALL_MODEL;
|
|
45
|
+
}
|
|
46
|
+
function getNanoModel(runtime) {
|
|
47
|
+
return getSetting(runtime, "OPENROUTER_NANO_MODEL") ?? getSetting(runtime, "NANO_MODEL") ?? getSmallModel(runtime);
|
|
48
|
+
}
|
|
49
|
+
function getMediumModel(runtime) {
|
|
50
|
+
return getSetting(runtime, "OPENROUTER_MEDIUM_MODEL") ?? getSetting(runtime, "MEDIUM_MODEL") ?? getSmallModel(runtime);
|
|
51
|
+
}
|
|
52
|
+
function getLargeModel(runtime) {
|
|
53
|
+
return getSetting(runtime, "OPENROUTER_LARGE_MODEL") ?? getSetting(runtime, "LARGE_MODEL", DEFAULT_LARGE_MODEL) ?? DEFAULT_LARGE_MODEL;
|
|
54
|
+
}
|
|
55
|
+
function getMegaModel(runtime) {
|
|
56
|
+
return getSetting(runtime, "OPENROUTER_MEGA_MODEL") ?? getSetting(runtime, "MEGA_MODEL") ?? getLargeModel(runtime);
|
|
57
|
+
}
|
|
58
|
+
function getResponseHandlerModel(runtime) {
|
|
59
|
+
return getSetting(runtime, "OPENROUTER_RESPONSE_HANDLER_MODEL") ?? getSetting(runtime, "OPENROUTER_SHOULD_RESPOND_MODEL") ?? getSetting(runtime, "RESPONSE_HANDLER_MODEL") ?? getSetting(runtime, "SHOULD_RESPOND_MODEL") ?? getNanoModel(runtime);
|
|
60
|
+
}
|
|
61
|
+
function getActionPlannerModel(runtime) {
|
|
62
|
+
return getSetting(runtime, "OPENROUTER_ACTION_PLANNER_MODEL") ?? getSetting(runtime, "OPENROUTER_PLANNER_MODEL") ?? getSetting(runtime, "ACTION_PLANNER_MODEL") ?? getSetting(runtime, "PLANNER_MODEL") ?? getMediumModel(runtime);
|
|
63
|
+
}
|
|
64
|
+
function getImageModel(runtime) {
|
|
65
|
+
return getSetting(runtime, "OPENROUTER_IMAGE_MODEL") ?? getSetting(runtime, "IMAGE_MODEL", DEFAULT_IMAGE_MODEL) ?? DEFAULT_IMAGE_MODEL;
|
|
66
|
+
}
|
|
67
|
+
function getImageGenerationModel(runtime) {
|
|
68
|
+
return getSetting(runtime, "OPENROUTER_IMAGE_GENERATION_MODEL") ?? getSetting(runtime, "IMAGE_GENERATION_MODEL", DEFAULT_IMAGE_GENERATION_MODEL) ?? DEFAULT_IMAGE_GENERATION_MODEL;
|
|
69
|
+
}
|
|
70
|
+
function getEmbeddingModel(runtime) {
|
|
71
|
+
return getSetting(runtime, "OPENROUTER_EMBEDDING_MODEL") ?? getSetting(runtime, "EMBEDDING_MODEL", DEFAULT_EMBEDDING_MODEL) ?? DEFAULT_EMBEDDING_MODEL;
|
|
72
|
+
}
|
|
73
|
+
function getTranscriptionModel(runtime) {
|
|
74
|
+
return getSetting(runtime, "OPENROUTER_TRANSCRIPTION_MODEL") ?? getSetting(runtime, "TRANSCRIPTION_MODEL", DEFAULT_TRANSCRIPTION_MODEL) ?? DEFAULT_TRANSCRIPTION_MODEL;
|
|
75
|
+
}
|
|
76
|
+
function getEmbeddingDimensions(runtime) {
|
|
77
|
+
const setting = getSetting(runtime, "OPENROUTER_EMBEDDING_DIMENSIONS") ?? getSetting(runtime, "EMBEDDING_DIMENSIONS");
|
|
78
|
+
return setting ? parseInt(setting, 10) : DEFAULT_EMBEDDING_DIMENSIONS;
|
|
79
|
+
}
|
|
80
|
+
function shouldAutoCleanupImages(runtime) {
|
|
81
|
+
const setting = getSetting(runtime, "OPENROUTER_AUTO_CLEANUP_IMAGES", "false");
|
|
82
|
+
return setting?.toLowerCase() === "true";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// init.ts
|
|
86
|
+
globalThis.AI_SDK_LOG_WARNINGS ??= false;
|
|
87
|
+
function initializeOpenRouter(_config, runtime) {
|
|
88
|
+
(async () => {
|
|
89
|
+
try {
|
|
90
|
+
const isBrowser = typeof globalThis !== "undefined" && globalThis.document;
|
|
91
|
+
if (isBrowser) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!getApiKey(runtime)) {
|
|
95
|
+
logger.warn("OPENROUTER_API_KEY is not set in environment - OpenRouter functionality will be limited");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const baseURL = getBaseURL(runtime);
|
|
99
|
+
const response = await fetch(`${baseURL}/models`, {
|
|
100
|
+
headers: { Authorization: `Bearer ${getApiKey(runtime)}` }
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
logger.warn(`OpenRouter API key validation failed: ${response.statusText}`);
|
|
104
|
+
} else {
|
|
105
|
+
logger.log("OpenRouter API key validated successfully");
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
logger.warn(`Error validating OpenRouter API key: ${message}`);
|
|
110
|
+
}
|
|
111
|
+
})();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// models/audio.ts
|
|
115
|
+
import { logger as logger2, ModelType } from "@elizaos/core";
|
|
116
|
+
|
|
117
|
+
// utils/events.ts
|
|
118
|
+
import { EventType } from "@elizaos/core";
|
|
119
|
+
function emitModelUsageEvent(runtime, modelType, _prompt, usage, modelName, modelLabel) {
|
|
120
|
+
const inputTokens = usage.inputTokens ?? usage.promptTokens ?? 0;
|
|
121
|
+
const outputTokens = usage.outputTokens ?? usage.completionTokens ?? 0;
|
|
122
|
+
const totalTokens = usage.totalTokens ?? inputTokens + outputTokens;
|
|
123
|
+
const model = modelName?.trim() || modelLabel?.trim() || String(modelType);
|
|
124
|
+
runtime.emitEvent(EventType.MODEL_USED, {
|
|
125
|
+
runtime,
|
|
126
|
+
source: "openrouter",
|
|
127
|
+
provider: "openrouter",
|
|
128
|
+
type: modelType,
|
|
129
|
+
model,
|
|
130
|
+
modelName: model,
|
|
131
|
+
modelLabel: modelLabel ?? String(modelType),
|
|
132
|
+
tokens: {
|
|
133
|
+
prompt: inputTokens,
|
|
134
|
+
completion: outputTokens,
|
|
135
|
+
total: totalTokens
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
promptTokens: inputTokens,
|
|
140
|
+
completionTokens: outputTokens,
|
|
141
|
+
totalTokens
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// models/audio.ts
|
|
146
|
+
function isBlobOrFile(value) {
|
|
147
|
+
if (typeof Blob !== "undefined" && value instanceof Blob)
|
|
148
|
+
return true;
|
|
149
|
+
return typeof File !== "undefined" && value instanceof File;
|
|
150
|
+
}
|
|
151
|
+
function isBuffer(value) {
|
|
152
|
+
return typeof Buffer !== "undefined" && Buffer.isBuffer(value);
|
|
153
|
+
}
|
|
154
|
+
function isCoreTranscriptionParams(value) {
|
|
155
|
+
return typeof value === "object" && value !== null && "audioUrl" in value && typeof value.audioUrl === "string";
|
|
156
|
+
}
|
|
157
|
+
function isOpenRouterTranscriptionParams(value) {
|
|
158
|
+
return typeof value === "object" && value !== null && "audio" in value && (isBlobOrFile(value.audio) || isBuffer(value.audio));
|
|
159
|
+
}
|
|
160
|
+
function audioFormatFromMimeType(mimeType) {
|
|
161
|
+
if (!mimeType?.startsWith("audio/"))
|
|
162
|
+
return null;
|
|
163
|
+
const subtype = mimeType.slice("audio/".length).split(";")[0]?.trim();
|
|
164
|
+
if (!subtype)
|
|
165
|
+
return null;
|
|
166
|
+
if (subtype === "mpeg")
|
|
167
|
+
return "mp3";
|
|
168
|
+
if (subtype === "x-wav")
|
|
169
|
+
return "wav";
|
|
170
|
+
return subtype;
|
|
171
|
+
}
|
|
172
|
+
function audioFormatFromUrl(url) {
|
|
173
|
+
const pathname = new URL(url).pathname;
|
|
174
|
+
const match = pathname.match(/\.([a-z0-9]+)$/i);
|
|
175
|
+
return match?.[1]?.toLowerCase() ?? null;
|
|
176
|
+
}
|
|
177
|
+
function detectBufferFormat(buffer) {
|
|
178
|
+
if (buffer.length >= 12 && buffer.toString("ascii", 0, 4) === "RIFF") {
|
|
179
|
+
return "wav";
|
|
180
|
+
}
|
|
181
|
+
if (buffer.length >= 4 && buffer.toString("ascii", 0, 4) === "OggS") {
|
|
182
|
+
return "ogg";
|
|
183
|
+
}
|
|
184
|
+
if (buffer.length >= 4 && buffer.toString("ascii", 0, 4) === "fLaC") {
|
|
185
|
+
return "flac";
|
|
186
|
+
}
|
|
187
|
+
if (buffer.length >= 3 && buffer.toString("ascii", 0, 3) === "ID3") {
|
|
188
|
+
return "mp3";
|
|
189
|
+
}
|
|
190
|
+
if (buffer.length >= 2 && buffer[0] === 255 && ((buffer[1] ?? 0) & 224) === 224) {
|
|
191
|
+
return "mp3";
|
|
192
|
+
}
|
|
193
|
+
return "webm";
|
|
194
|
+
}
|
|
195
|
+
function validateAudioUrl(url) {
|
|
196
|
+
const parsed = new URL(url);
|
|
197
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
198
|
+
throw new Error("TRANSCRIPTION audioUrl must use http or https");
|
|
199
|
+
}
|
|
200
|
+
return parsed.toString();
|
|
201
|
+
}
|
|
202
|
+
async function fetchAudioFromUrl(url) {
|
|
203
|
+
const audioUrl = validateAudioUrl(url);
|
|
204
|
+
const response = await fetch(audioUrl);
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
throw new Error(`Failed to fetch audio from URL: ${audioUrl}`);
|
|
207
|
+
}
|
|
208
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
209
|
+
return {
|
|
210
|
+
base64: bytes.toString("base64"),
|
|
211
|
+
format: audioFormatFromMimeType(response.headers.get("content-type") ?? undefined) ?? audioFormatFromUrl(audioUrl) ?? detectBufferFormat(bytes)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async function encodeAudio(input) {
|
|
215
|
+
if (isBuffer(input)) {
|
|
216
|
+
return {
|
|
217
|
+
base64: input.toString("base64"),
|
|
218
|
+
format: detectBufferFormat(input)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const bytes = Buffer.from(await input.arrayBuffer());
|
|
222
|
+
return {
|
|
223
|
+
base64: bytes.toString("base64"),
|
|
224
|
+
format: audioFormatFromMimeType(input.type) ?? (typeof File !== "undefined" && input instanceof File ? audioFormatFromUrl(`https://local/${input.name}`) : null) ?? detectBufferFormat(bytes)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function normalizeTranscriptionInput(input) {
|
|
228
|
+
if (typeof input === "string") {
|
|
229
|
+
const audio = await fetchAudioFromUrl(input);
|
|
230
|
+
return { audio: { data: audio.base64, format: audio.format } };
|
|
231
|
+
}
|
|
232
|
+
if (isBlobOrFile(input) || isBuffer(input)) {
|
|
233
|
+
const audio = await encodeAudio(input);
|
|
234
|
+
return { audio: { data: audio.base64, format: audio.format } };
|
|
235
|
+
}
|
|
236
|
+
if (isCoreTranscriptionParams(input)) {
|
|
237
|
+
const audio = await fetchAudioFromUrl(input.audioUrl);
|
|
238
|
+
return { audio: { data: audio.base64, format: audio.format } };
|
|
239
|
+
}
|
|
240
|
+
if (isOpenRouterTranscriptionParams(input)) {
|
|
241
|
+
const audio = await encodeAudio(input.audio);
|
|
242
|
+
return {
|
|
243
|
+
audio: {
|
|
244
|
+
data: audio.base64,
|
|
245
|
+
format: input.format ?? audioFormatFromMimeType(input.mimeType) ?? audio.format
|
|
246
|
+
},
|
|
247
|
+
...input.language ? { language: input.language } : {},
|
|
248
|
+
...input.model ? { model: input.model } : {},
|
|
249
|
+
...input.temperature !== undefined ? { temperature: input.temperature } : {}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
throw new Error("TRANSCRIPTION expects Buffer, Blob, File, URL string, or TranscriptionParams");
|
|
253
|
+
}
|
|
254
|
+
async function handleTranscription(runtime, input) {
|
|
255
|
+
const apiKey = getApiKey(runtime);
|
|
256
|
+
if (!apiKey) {
|
|
257
|
+
throw new Error("OPENROUTER_API_KEY is not set");
|
|
258
|
+
}
|
|
259
|
+
const normalized = await normalizeTranscriptionInput(input);
|
|
260
|
+
const modelName = normalized.model ?? getTranscriptionModel(runtime);
|
|
261
|
+
const baseURL = getBaseURL(runtime);
|
|
262
|
+
logger2.debug(`[OpenRouter] Using TRANSCRIPTION model: ${modelName}`);
|
|
263
|
+
const response = await fetch(`${baseURL}/audio/transcriptions`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${apiKey}`,
|
|
267
|
+
"Content-Type": "application/json"
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
model: modelName,
|
|
271
|
+
input_audio: normalized.audio,
|
|
272
|
+
...normalized.language ? { language: normalized.language } : {},
|
|
273
|
+
...normalized.temperature !== undefined ? { temperature: normalized.temperature } : {}
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
278
|
+
throw new Error(`OpenRouter transcription failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
279
|
+
}
|
|
280
|
+
const result = await response.json();
|
|
281
|
+
if (typeof result.text !== "string") {
|
|
282
|
+
throw new Error("OpenRouter transcription response did not include text");
|
|
283
|
+
}
|
|
284
|
+
if (result.usage) {
|
|
285
|
+
emitModelUsageEvent(runtime, ModelType.TRANSCRIPTION, "audio transcription", {
|
|
286
|
+
inputTokens: result.usage.input_tokens ?? 0,
|
|
287
|
+
outputTokens: result.usage.output_tokens ?? 0,
|
|
288
|
+
totalTokens: result.usage.total_tokens ?? 0
|
|
289
|
+
}, modelName);
|
|
290
|
+
}
|
|
291
|
+
return result.text;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// models/embedding.ts
|
|
295
|
+
import { logger as logger3, ModelType as ModelType2, VECTOR_DIMS } from "@elizaos/core";
|
|
296
|
+
async function handleTextEmbedding(runtime, params) {
|
|
297
|
+
const embeddingModelName = getEmbeddingModel(runtime);
|
|
298
|
+
const embeddingDimension = Number.parseInt(getSetting(runtime, "OPENROUTER_EMBEDDING_DIMENSIONS") ?? getSetting(runtime, "EMBEDDING_DIMENSIONS") ?? "1536", 10);
|
|
299
|
+
if (!Object.values(VECTOR_DIMS).includes(embeddingDimension)) {
|
|
300
|
+
const errorMsg = `Invalid embedding dimension: ${embeddingDimension}. Must be one of: ${Object.values(VECTOR_DIMS).join(", ")}`;
|
|
301
|
+
logger3.error(errorMsg);
|
|
302
|
+
throw new Error(errorMsg);
|
|
303
|
+
}
|
|
304
|
+
if (params === null) {
|
|
305
|
+
const testVector = Array(embeddingDimension).fill(0);
|
|
306
|
+
testVector[0] = 0.1;
|
|
307
|
+
return testVector;
|
|
308
|
+
}
|
|
309
|
+
let text;
|
|
310
|
+
if (typeof params === "string") {
|
|
311
|
+
text = params;
|
|
312
|
+
} else if (typeof params === "object" && typeof params.text === "string") {
|
|
313
|
+
text = params.text;
|
|
314
|
+
} else {
|
|
315
|
+
const errorMsg = "Invalid input format for embedding";
|
|
316
|
+
logger3.error(errorMsg);
|
|
317
|
+
throw new Error(errorMsg);
|
|
318
|
+
}
|
|
319
|
+
if (!text.trim()) {
|
|
320
|
+
const errorMsg = "Empty text for embedding";
|
|
321
|
+
logger3.error(errorMsg);
|
|
322
|
+
throw new Error(errorMsg);
|
|
323
|
+
}
|
|
324
|
+
const maxChars = 8000 * 4;
|
|
325
|
+
if (text.length > maxChars) {
|
|
326
|
+
logger3.warn(`[OpenRouter] Embedding input too long (~${Math.ceil(text.length / 4)} tokens), truncating to ~8000 tokens`);
|
|
327
|
+
text = text.slice(0, maxChars);
|
|
328
|
+
}
|
|
329
|
+
const apiKey = getApiKey(runtime);
|
|
330
|
+
if (!apiKey) {
|
|
331
|
+
const errorMsg = "OPENROUTER_API_KEY is not set";
|
|
332
|
+
logger3.error(errorMsg);
|
|
333
|
+
throw new Error(errorMsg);
|
|
334
|
+
}
|
|
335
|
+
const baseURL = getBaseURL(runtime);
|
|
336
|
+
try {
|
|
337
|
+
const response = await fetch(`${baseURL}/embeddings`, {
|
|
338
|
+
method: "POST",
|
|
339
|
+
headers: {
|
|
340
|
+
Authorization: `Bearer ${apiKey}`,
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
"HTTP-Referer": getSetting(runtime, "OPENROUTER_HTTP_REFERER") || "",
|
|
343
|
+
"X-Title": getSetting(runtime, "OPENROUTER_X_TITLE") || "ElizaOS"
|
|
344
|
+
},
|
|
345
|
+
body: JSON.stringify({
|
|
346
|
+
model: embeddingModelName,
|
|
347
|
+
input: text
|
|
348
|
+
})
|
|
349
|
+
});
|
|
350
|
+
if (!response.ok) {
|
|
351
|
+
logger3.error(`OpenRouter API error: ${response.status} - ${response.statusText}`);
|
|
352
|
+
throw new Error(`OpenRouter API error: ${response.status} - ${response.statusText}`);
|
|
353
|
+
}
|
|
354
|
+
const data = await response.json();
|
|
355
|
+
const embedding = data.data?.[0]?.embedding;
|
|
356
|
+
if (!embedding) {
|
|
357
|
+
logger3.error("API returned invalid structure");
|
|
358
|
+
throw new Error("API returned invalid structure");
|
|
359
|
+
}
|
|
360
|
+
if (!Array.isArray(embedding) || embedding.length !== embeddingDimension) {
|
|
361
|
+
const errorMsg = `Embedding length ${embedding.length} does not match configured dimension ${embeddingDimension}`;
|
|
362
|
+
logger3.error(errorMsg);
|
|
363
|
+
throw new Error(errorMsg);
|
|
364
|
+
}
|
|
365
|
+
if (data.usage) {
|
|
366
|
+
const usage = {
|
|
367
|
+
inputTokens: data.usage.prompt_tokens,
|
|
368
|
+
outputTokens: 0,
|
|
369
|
+
totalTokens: data.usage.total_tokens
|
|
370
|
+
};
|
|
371
|
+
emitModelUsageEvent(runtime, ModelType2.TEXT_EMBEDDING, text, usage, embeddingModelName);
|
|
372
|
+
}
|
|
373
|
+
return embedding;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
376
|
+
logger3.error(`Error generating embedding: ${message}`);
|
|
377
|
+
throw error instanceof Error ? error : new Error(message);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// models/image.ts
|
|
382
|
+
import { logger as logger4, ModelType as ModelType3 } from "@elizaos/core";
|
|
383
|
+
import { generateText } from "ai";
|
|
384
|
+
|
|
385
|
+
// providers/openrouter.ts
|
|
386
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
387
|
+
function createOpenRouterProvider(runtime) {
|
|
388
|
+
const apiKey = getApiKey(runtime);
|
|
389
|
+
const isBrowser = typeof globalThis !== "undefined" && globalThis.document;
|
|
390
|
+
const baseURL = getBaseURL(runtime);
|
|
391
|
+
return createOpenRouter({
|
|
392
|
+
apiKey: isBrowser ? undefined : apiKey,
|
|
393
|
+
baseURL
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
// models/image.ts
|
|
397
|
+
var DEFAULT_IMAGE_DESCRIPTION_PROMPT = `Analyze this image and respond with:
|
|
398
|
+
Title: <short title>
|
|
399
|
+
Description: <detailed description>`;
|
|
400
|
+
function parseTitle(content) {
|
|
401
|
+
const titleMatch = content.match(/title[:\s]+(.+?)(?:\n|$)/i);
|
|
402
|
+
if (titleMatch?.[1]) {
|
|
403
|
+
return titleMatch[1].trim();
|
|
404
|
+
}
|
|
405
|
+
const firstLine = content.split(`
|
|
406
|
+
`).map((line) => line.trim()).find((line) => line.length > 0);
|
|
407
|
+
return firstLine ? firstLine.slice(0, 100) : "Image Analysis";
|
|
408
|
+
}
|
|
409
|
+
function parseDescription(content) {
|
|
410
|
+
const withoutTitle = content.replace(/title[:\s]+(.+?)(?:\n|$)/i, "").trim();
|
|
411
|
+
const withoutDescriptionLabel = withoutTitle.replace(/^description[:\s]+/i, "").trim();
|
|
412
|
+
return withoutDescriptionLabel.length > 0 ? withoutDescriptionLabel : content.trim();
|
|
413
|
+
}
|
|
414
|
+
async function handleImageDescription(runtime, params) {
|
|
415
|
+
const openrouter = createOpenRouterProvider(runtime);
|
|
416
|
+
const modelName = getImageModel(runtime);
|
|
417
|
+
const imageUrl = typeof params === "string" ? params : params.imageUrl;
|
|
418
|
+
const prompt = typeof params === "string" ? DEFAULT_IMAGE_DESCRIPTION_PROMPT : params.prompt ?? DEFAULT_IMAGE_DESCRIPTION_PROMPT;
|
|
419
|
+
if (!imageUrl || imageUrl.trim().length === 0) {
|
|
420
|
+
throw new Error("[OpenRouter] IMAGE_DESCRIPTION requires a valid image URL.");
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const generateParams = {
|
|
424
|
+
model: openrouter.chat(modelName),
|
|
425
|
+
messages: [
|
|
426
|
+
{
|
|
427
|
+
role: "user",
|
|
428
|
+
content: [
|
|
429
|
+
{ type: "text", text: prompt },
|
|
430
|
+
{ type: "image", image: imageUrl }
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
};
|
|
435
|
+
const response = await generateText(generateParams);
|
|
436
|
+
if (response.usage) {
|
|
437
|
+
emitModelUsageEvent(runtime, ModelType3.IMAGE_DESCRIPTION, prompt, response.usage, modelName);
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
title: parseTitle(response.text),
|
|
441
|
+
description: parseDescription(response.text)
|
|
442
|
+
};
|
|
443
|
+
} catch (error) {
|
|
444
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
445
|
+
logger4.error(`Error describing image: ${message}`);
|
|
446
|
+
throw error instanceof Error ? error : new Error(message);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function handleImageGeneration(runtime, params) {
|
|
450
|
+
const openrouter = createOpenRouterProvider(runtime);
|
|
451
|
+
const modelName = getImageGenerationModel(runtime);
|
|
452
|
+
const prompt = params.prompt?.trim();
|
|
453
|
+
if (!prompt) {
|
|
454
|
+
throw new Error("[OpenRouter] IMAGE generation requires a non-empty prompt.");
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const generateParams = {
|
|
458
|
+
model: openrouter.chat(modelName),
|
|
459
|
+
prompt: `Generate an image: ${prompt}`
|
|
460
|
+
};
|
|
461
|
+
const response = await generateText(generateParams);
|
|
462
|
+
if (response.usage) {
|
|
463
|
+
emitModelUsageEvent(runtime, ModelType3.IMAGE, params.prompt, response.usage, modelName);
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
imageUrl: response.text,
|
|
467
|
+
caption: prompt
|
|
468
|
+
};
|
|
469
|
+
} catch (error) {
|
|
470
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
471
|
+
logger4.error(`Error generating image: ${message}`);
|
|
472
|
+
throw error instanceof Error ? error : new Error(message);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// models/text.ts
|
|
477
|
+
import {
|
|
478
|
+
buildCanonicalSystemPrompt,
|
|
479
|
+
dropDuplicateLeadingSystemMessage,
|
|
480
|
+
ModelType as ModelType4,
|
|
481
|
+
resolveEffectiveSystemPrompt
|
|
482
|
+
} from "@elizaos/core";
|
|
483
|
+
import {
|
|
484
|
+
generateText as generateText2,
|
|
485
|
+
jsonSchema,
|
|
486
|
+
streamText
|
|
487
|
+
} from "ai";
|
|
488
|
+
var RESPONSES_ROUTED_PREFIXES = ["openai/", "anthropic/"];
|
|
489
|
+
var NO_SAMPLING_MODEL_PATTERNS = ["o1", "o3", "o4", "gpt-5", "gpt-5-mini"];
|
|
490
|
+
var TEXT_NANO_MODEL_TYPE = ModelType4.TEXT_NANO;
|
|
491
|
+
var TEXT_MEDIUM_MODEL_TYPE = ModelType4.TEXT_MEDIUM;
|
|
492
|
+
var TEXT_MEGA_MODEL_TYPE = ModelType4.TEXT_MEGA;
|
|
493
|
+
var RESPONSE_HANDLER_MODEL_TYPE = ModelType4.RESPONSE_HANDLER;
|
|
494
|
+
var ACTION_PLANNER_MODEL_TYPE = ModelType4.ACTION_PLANNER;
|
|
495
|
+
function buildUserContent(params, options = { includePrompt: true }) {
|
|
496
|
+
const content = [];
|
|
497
|
+
if (options.includePrompt !== false && typeof params.prompt === "string" && params.prompt.length > 0) {
|
|
498
|
+
content.push({ type: "text", text: params.prompt });
|
|
499
|
+
}
|
|
500
|
+
for (const attachment of params.attachments ?? []) {
|
|
501
|
+
content.push({
|
|
502
|
+
type: "file",
|
|
503
|
+
data: attachment.data,
|
|
504
|
+
mediaType: attachment.mediaType,
|
|
505
|
+
...attachment.filename ? { filename: attachment.filename } : {}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return content;
|
|
509
|
+
}
|
|
510
|
+
function appendUserContentToMessages(messages, extraContent) {
|
|
511
|
+
if (extraContent.length === 0) {
|
|
512
|
+
return messages;
|
|
513
|
+
}
|
|
514
|
+
const lastUserIndex = messages.findLastIndex((message) => message.role === "user");
|
|
515
|
+
if (lastUserIndex === -1) {
|
|
516
|
+
return [...messages, { role: "user", content: extraContent }];
|
|
517
|
+
}
|
|
518
|
+
const nextMessages = [...messages];
|
|
519
|
+
const userMessage = nextMessages[lastUserIndex];
|
|
520
|
+
if (!userMessage) {
|
|
521
|
+
return messages;
|
|
522
|
+
}
|
|
523
|
+
const existingContent = userMessage.content;
|
|
524
|
+
const content = [
|
|
525
|
+
...typeof existingContent === "string" ? [{ type: "text", text: existingContent }] : Array.isArray(existingContent) ? existingContent : [],
|
|
526
|
+
...extraContent
|
|
527
|
+
];
|
|
528
|
+
nextMessages[lastUserIndex] = {
|
|
529
|
+
...userMessage,
|
|
530
|
+
content
|
|
531
|
+
};
|
|
532
|
+
return nextMessages;
|
|
533
|
+
}
|
|
534
|
+
function textFromMessages(messages) {
|
|
535
|
+
if (!messages || messages.length === 0)
|
|
536
|
+
return "";
|
|
537
|
+
return messages.map((message) => {
|
|
538
|
+
const content = message.content;
|
|
539
|
+
if (typeof content === "string")
|
|
540
|
+
return content;
|
|
541
|
+
if (!Array.isArray(content))
|
|
542
|
+
return "";
|
|
543
|
+
return content.map((part) => part && typeof part === "object" && ("text" in part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join(`
|
|
544
|
+
`);
|
|
545
|
+
}).filter(Boolean).join(`
|
|
546
|
+
`);
|
|
547
|
+
}
|
|
548
|
+
function supportsSamplingParameters(modelName) {
|
|
549
|
+
const lowerModelName = modelName.toLowerCase();
|
|
550
|
+
if (RESPONSES_ROUTED_PREFIXES.some((prefix) => lowerModelName.startsWith(prefix))) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
return !NO_SAMPLING_MODEL_PATTERNS.some((pattern) => lowerModelName.includes(pattern));
|
|
554
|
+
}
|
|
555
|
+
function isRecord(value) {
|
|
556
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
557
|
+
}
|
|
558
|
+
function readToolSet(value) {
|
|
559
|
+
if (!value) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const isArr = Array.isArray(value);
|
|
563
|
+
const entries = isArr ? value.map((v, i) => [String(i), v]) : Object.entries(value);
|
|
564
|
+
const namedKeys = new Set;
|
|
565
|
+
for (const [, rawTool] of entries) {
|
|
566
|
+
if (isRecord(rawTool) && typeof rawTool.name === "string" && rawTool.name) {
|
|
567
|
+
namedKeys.add(rawTool.name);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const tools = {};
|
|
571
|
+
let sawNamedTool = false;
|
|
572
|
+
for (const [origKey, rawTool] of entries) {
|
|
573
|
+
if (!isRecord(rawTool)) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const functionTool = isRecord(rawTool.function) ? rawTool.function : undefined;
|
|
577
|
+
const name = typeof rawTool.name === "string" && rawTool.name ? rawTool.name : typeof functionTool?.name === "string" && functionTool.name ? functionTool.name : undefined;
|
|
578
|
+
if (name) {
|
|
579
|
+
sawNamedTool = true;
|
|
580
|
+
const schema = isRecord(rawTool.parameters) ? rawTool.parameters : isRecord(functionTool?.parameters) ? functionTool.parameters : isRecord(rawTool.input_schema) ? rawTool.input_schema : { type: "object" };
|
|
581
|
+
const description = typeof rawTool.description === "string" ? rawTool.description : typeof functionTool?.description === "string" ? functionTool.description : undefined;
|
|
582
|
+
tools[name] = {
|
|
583
|
+
...description ? { description } : {},
|
|
584
|
+
inputSchema: jsonSchema(schema)
|
|
585
|
+
};
|
|
586
|
+
} else if (!isArr && !namedKeys.has(origKey)) {
|
|
587
|
+
tools[origKey] = rawTool;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (sawNamedTool) {
|
|
591
|
+
return Object.keys(tools).length > 0 ? tools : undefined;
|
|
592
|
+
}
|
|
593
|
+
return !isArr && isRecord(value) ? value : undefined;
|
|
594
|
+
}
|
|
595
|
+
function readToolChoice(value) {
|
|
596
|
+
if (!value) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (typeof value === "string" && (value === "auto" || value === "none" || value === "required")) {
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
if (!isRecord(value)) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const choice = value;
|
|
606
|
+
if (choice.type === "tool" && typeof choice.name === "string") {
|
|
607
|
+
return { type: "tool", toolName: choice.name };
|
|
608
|
+
}
|
|
609
|
+
if (choice.type === "function" && isRecord(choice.function)) {
|
|
610
|
+
const name = choice.function.name;
|
|
611
|
+
return typeof name === "string" ? { type: "tool", toolName: name } : undefined;
|
|
612
|
+
}
|
|
613
|
+
return typeof choice.name === "string" ? { type: "tool", toolName: choice.name } : undefined;
|
|
614
|
+
}
|
|
615
|
+
function buildStructuredOutput(responseSchema) {
|
|
616
|
+
if (responseSchema && typeof responseSchema === "object" && "responseFormat" in responseSchema && "parseCompleteOutput" in responseSchema) {
|
|
617
|
+
return responseSchema;
|
|
618
|
+
}
|
|
619
|
+
const schemaOptions = responseSchema && typeof responseSchema === "object" && "schema" in responseSchema ? responseSchema : { schema: responseSchema };
|
|
620
|
+
return {
|
|
621
|
+
name: "object",
|
|
622
|
+
responseFormat: Promise.resolve({
|
|
623
|
+
type: "json",
|
|
624
|
+
schema: schemaOptions.schema,
|
|
625
|
+
...schemaOptions.name ? { name: schemaOptions.name } : {},
|
|
626
|
+
...schemaOptions.description ? { description: schemaOptions.description } : {}
|
|
627
|
+
}),
|
|
628
|
+
async parseCompleteOutput({ text }) {
|
|
629
|
+
return JSON.parse(text);
|
|
630
|
+
},
|
|
631
|
+
async parsePartialOutput() {
|
|
632
|
+
return;
|
|
633
|
+
},
|
|
634
|
+
createElementStreamTransform() {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function usesNativeTextResult(params) {
|
|
640
|
+
return Boolean(params.messages || params.tools || params.toolChoice || params.responseSchema);
|
|
641
|
+
}
|
|
642
|
+
function getModelNameForType(runtime, modelType) {
|
|
643
|
+
switch (modelType) {
|
|
644
|
+
case TEXT_NANO_MODEL_TYPE:
|
|
645
|
+
return getNanoModel(runtime);
|
|
646
|
+
case TEXT_MEDIUM_MODEL_TYPE:
|
|
647
|
+
return getMediumModel(runtime);
|
|
648
|
+
case ModelType4.TEXT_SMALL:
|
|
649
|
+
return getSmallModel(runtime);
|
|
650
|
+
case ModelType4.TEXT_LARGE:
|
|
651
|
+
return getLargeModel(runtime);
|
|
652
|
+
case TEXT_MEGA_MODEL_TYPE:
|
|
653
|
+
return getMegaModel(runtime);
|
|
654
|
+
case RESPONSE_HANDLER_MODEL_TYPE:
|
|
655
|
+
return getResponseHandlerModel(runtime);
|
|
656
|
+
case ACTION_PLANNER_MODEL_TYPE:
|
|
657
|
+
return getActionPlannerModel(runtime);
|
|
658
|
+
default:
|
|
659
|
+
return getLargeModel(runtime);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function getModelLabelForType(modelType) {
|
|
663
|
+
switch (modelType) {
|
|
664
|
+
case TEXT_NANO_MODEL_TYPE:
|
|
665
|
+
return "TEXT_NANO";
|
|
666
|
+
case TEXT_MEDIUM_MODEL_TYPE:
|
|
667
|
+
return "TEXT_MEDIUM";
|
|
668
|
+
case ModelType4.TEXT_SMALL:
|
|
669
|
+
return "TEXT_SMALL";
|
|
670
|
+
case ModelType4.TEXT_LARGE:
|
|
671
|
+
return "TEXT_LARGE";
|
|
672
|
+
case TEXT_MEGA_MODEL_TYPE:
|
|
673
|
+
return "TEXT_MEGA";
|
|
674
|
+
case RESPONSE_HANDLER_MODEL_TYPE:
|
|
675
|
+
return "RESPONSE_HANDLER";
|
|
676
|
+
case ACTION_PLANNER_MODEL_TYPE:
|
|
677
|
+
return "ACTION_PLANNER";
|
|
678
|
+
default:
|
|
679
|
+
return String(modelType);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function buildGenerateParams(runtime, modelType, params) {
|
|
683
|
+
const paramsWithAttachments = params;
|
|
684
|
+
const prompt = typeof params.prompt === "string" ? params.prompt : undefined;
|
|
685
|
+
const usagePrompt = prompt ?? textFromMessages(paramsWithAttachments.messages);
|
|
686
|
+
const paramsWithMax = params;
|
|
687
|
+
const resolvedMaxOutput = params.omitMaxTokens ? undefined : paramsWithMax.maxOutputTokens ?? paramsWithMax.maxTokens ?? 8192;
|
|
688
|
+
const openrouter = createOpenRouterProvider(runtime);
|
|
689
|
+
const modelName = getModelNameForType(runtime, modelType);
|
|
690
|
+
const modelLabel = getModelLabelForType(modelType);
|
|
691
|
+
const supportsSampling = supportsSamplingParameters(modelName);
|
|
692
|
+
const stopSequences = Array.isArray(params.stopSequences) && params.stopSequences.length > 0 ? params.stopSequences : undefined;
|
|
693
|
+
const userContent = (paramsWithAttachments.attachments?.length ?? 0) > 0 ? buildUserContent(paramsWithAttachments) : undefined;
|
|
694
|
+
const attachmentContent = paramsWithAttachments.messages && (paramsWithAttachments.attachments?.length ?? 0) > 0 ? buildUserContent(paramsWithAttachments, { includePrompt: false }) : undefined;
|
|
695
|
+
const temperature = params.temperature ?? 0.7;
|
|
696
|
+
const frequencyPenalty = params.frequencyPenalty ?? 0.7;
|
|
697
|
+
const presencePenalty = params.presencePenalty ?? 0.7;
|
|
698
|
+
const systemPrompt = resolveEffectiveSystemPrompt({
|
|
699
|
+
params: paramsWithAttachments,
|
|
700
|
+
fallback: buildCanonicalSystemPrompt({ character: runtime.character })
|
|
701
|
+
});
|
|
702
|
+
const wireMessages = dropDuplicateLeadingSystemMessage(paramsWithAttachments.messages, systemPrompt);
|
|
703
|
+
const promptOrMessages = paramsWithAttachments.messages ? wireMessages && wireMessages.length > 0 ? {
|
|
704
|
+
messages: attachmentContent ? appendUserContentToMessages(wireMessages, attachmentContent) : wireMessages
|
|
705
|
+
} : userContent ? { messages: [{ role: "user", content: userContent }] } : prompt !== undefined ? { prompt } : (() => {
|
|
706
|
+
throw new Error("OpenRouter text generation requires prompt, messages, or attachments");
|
|
707
|
+
})() : userContent ? { messages: [{ role: "user", content: userContent }] } : prompt !== undefined ? { prompt } : (() => {
|
|
708
|
+
throw new Error("OpenRouter text generation requires prompt, messages, or attachments");
|
|
709
|
+
})();
|
|
710
|
+
const rawProviderOptions = paramsWithAttachments.providerOptions;
|
|
711
|
+
const { openrouter: rawOpenrouterOptions, ...restProviderOptions } = rawProviderOptions ?? {};
|
|
712
|
+
const openrouterOptions = {
|
|
713
|
+
...rawOpenrouterOptions ?? {}
|
|
714
|
+
};
|
|
715
|
+
const mergedProviderOptions = {
|
|
716
|
+
...restProviderOptions,
|
|
717
|
+
...Object.keys(openrouterOptions).length > 0 ? { openrouter: openrouterOptions } : {}
|
|
718
|
+
};
|
|
719
|
+
const resolvedProviderOptions = Object.keys(mergedProviderOptions).length > 0 ? mergedProviderOptions : undefined;
|
|
720
|
+
const normalizedTools = readToolSet(paramsWithAttachments.tools);
|
|
721
|
+
const normalizedToolChoice = readToolChoice(paramsWithAttachments.toolChoice);
|
|
722
|
+
const generateParams = {
|
|
723
|
+
model: openrouter.chat(modelName),
|
|
724
|
+
...promptOrMessages,
|
|
725
|
+
system: systemPrompt,
|
|
726
|
+
...supportsSampling ? {
|
|
727
|
+
temperature,
|
|
728
|
+
frequencyPenalty,
|
|
729
|
+
presencePenalty,
|
|
730
|
+
...stopSequences ? { stopSequences } : {}
|
|
731
|
+
} : {},
|
|
732
|
+
...resolvedMaxOutput !== undefined ? { maxOutputTokens: resolvedMaxOutput } : {},
|
|
733
|
+
...normalizedTools ? { tools: normalizedTools } : {},
|
|
734
|
+
...normalizedToolChoice ? { toolChoice: normalizedToolChoice } : {},
|
|
735
|
+
...paramsWithAttachments.responseSchema ? { output: buildStructuredOutput(paramsWithAttachments.responseSchema) } : {},
|
|
736
|
+
...resolvedProviderOptions ? { providerOptions: resolvedProviderOptions } : {}
|
|
737
|
+
};
|
|
738
|
+
return {
|
|
739
|
+
generateParams,
|
|
740
|
+
modelName,
|
|
741
|
+
modelLabel,
|
|
742
|
+
prompt: usagePrompt,
|
|
743
|
+
shouldReturnNativeResult: usesNativeTextResult(paramsWithAttachments)
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelName, modelLabel, shouldReturnNativeResult) {
|
|
747
|
+
const streamResult = streamText(generateParams);
|
|
748
|
+
const usagePromise = Promise.resolve(streamResult.usage).then((usage) => {
|
|
749
|
+
if (!usage) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
return emitModelUsageEvent(runtime, modelType, prompt, usage, modelName, modelLabel);
|
|
753
|
+
});
|
|
754
|
+
const ignoreUsageError = () => {
|
|
755
|
+
return;
|
|
756
|
+
};
|
|
757
|
+
async function* textStreamWithUsage() {
|
|
758
|
+
let completed = false;
|
|
759
|
+
try {
|
|
760
|
+
for await (const chunk of streamResult.textStream) {
|
|
761
|
+
yield chunk;
|
|
762
|
+
}
|
|
763
|
+
completed = true;
|
|
764
|
+
} finally {
|
|
765
|
+
if (completed) {
|
|
766
|
+
await usagePromise.catch(ignoreUsageError);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
textStream: textStreamWithUsage(),
|
|
772
|
+
text: Promise.resolve(streamResult.text).then(async (text) => {
|
|
773
|
+
await usagePromise.catch(ignoreUsageError);
|
|
774
|
+
return text;
|
|
775
|
+
}),
|
|
776
|
+
...shouldReturnNativeResult ? { toolCalls: Promise.resolve(streamResult.toolCalls) } : {},
|
|
777
|
+
usage: usagePromise,
|
|
778
|
+
finishReason: Promise.resolve(streamResult.finishReason)
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function buildNativeTextResult(result) {
|
|
782
|
+
const inputTokens = result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0;
|
|
783
|
+
const outputTokens = result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0;
|
|
784
|
+
if (!result.usage) {
|
|
785
|
+
return {
|
|
786
|
+
text: result.text,
|
|
787
|
+
toolCalls: result.toolCalls ?? [],
|
|
788
|
+
finishReason: result.finishReason
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const cacheRead = result.usage.cacheReadInputTokens ?? result.usage.cachedInputTokens;
|
|
792
|
+
const cacheCreation = result.usage.cacheCreationInputTokens;
|
|
793
|
+
const usage = {
|
|
794
|
+
promptTokens: inputTokens,
|
|
795
|
+
completionTokens: outputTokens,
|
|
796
|
+
totalTokens: result.usage.totalTokens ?? inputTokens + outputTokens,
|
|
797
|
+
...typeof cacheRead === "number" ? { cacheReadInputTokens: cacheRead } : {},
|
|
798
|
+
...typeof cacheCreation === "number" ? { cacheCreationInputTokens: cacheCreation } : {}
|
|
799
|
+
};
|
|
800
|
+
return {
|
|
801
|
+
text: result.text,
|
|
802
|
+
toolCalls: result.toolCalls ?? [],
|
|
803
|
+
finishReason: result.finishReason,
|
|
804
|
+
usage
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
async function generateTextWithModel(runtime, modelType, params) {
|
|
808
|
+
const { generateParams, modelName, modelLabel, prompt, shouldReturnNativeResult } = buildGenerateParams(runtime, modelType, params);
|
|
809
|
+
if (params.stream) {
|
|
810
|
+
return handleStreamingGeneration(runtime, modelType, generateParams, prompt, modelName, modelLabel, shouldReturnNativeResult);
|
|
811
|
+
}
|
|
812
|
+
const response = await generateText2(generateParams);
|
|
813
|
+
if (response.usage) {
|
|
814
|
+
emitModelUsageEvent(runtime, modelType, prompt, response.usage, modelName, modelLabel);
|
|
815
|
+
}
|
|
816
|
+
if (shouldReturnNativeResult) {
|
|
817
|
+
return buildNativeTextResult(response);
|
|
818
|
+
}
|
|
819
|
+
return response.text;
|
|
820
|
+
}
|
|
821
|
+
async function handleTextSmall(runtime, params) {
|
|
822
|
+
return generateTextWithModel(runtime, ModelType4.TEXT_SMALL, params);
|
|
823
|
+
}
|
|
824
|
+
async function handleTextNano(runtime, params) {
|
|
825
|
+
return generateTextWithModel(runtime, TEXT_NANO_MODEL_TYPE, params);
|
|
826
|
+
}
|
|
827
|
+
async function handleTextMedium(runtime, params) {
|
|
828
|
+
return generateTextWithModel(runtime, TEXT_MEDIUM_MODEL_TYPE, params);
|
|
829
|
+
}
|
|
830
|
+
async function handleTextLarge(runtime, params) {
|
|
831
|
+
return generateTextWithModel(runtime, ModelType4.TEXT_LARGE, params);
|
|
832
|
+
}
|
|
833
|
+
async function handleTextMega(runtime, params) {
|
|
834
|
+
return generateTextWithModel(runtime, TEXT_MEGA_MODEL_TYPE, params);
|
|
835
|
+
}
|
|
836
|
+
async function handleResponseHandler(runtime, params) {
|
|
837
|
+
return generateTextWithModel(runtime, RESPONSE_HANDLER_MODEL_TYPE, params);
|
|
838
|
+
}
|
|
839
|
+
async function handleActionPlanner(runtime, params) {
|
|
840
|
+
return generateTextWithModel(runtime, ACTION_PLANNER_MODEL_TYPE, params);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// plugin.ts
|
|
844
|
+
function getProcessEnv() {
|
|
845
|
+
if (typeof process === "undefined" || !process.env) {
|
|
846
|
+
return {};
|
|
847
|
+
}
|
|
848
|
+
return process.env;
|
|
849
|
+
}
|
|
850
|
+
var env = getProcessEnv();
|
|
851
|
+
var TEXT_NANO_MODEL_TYPE2 = ModelType5.TEXT_NANO;
|
|
852
|
+
var TEXT_MEDIUM_MODEL_TYPE2 = ModelType5.TEXT_MEDIUM;
|
|
853
|
+
var TEXT_MEGA_MODEL_TYPE2 = ModelType5.TEXT_MEGA;
|
|
854
|
+
var RESPONSE_HANDLER_MODEL_TYPE2 = ModelType5.RESPONSE_HANDLER;
|
|
855
|
+
var ACTION_PLANNER_MODEL_TYPE2 = ModelType5.ACTION_PLANNER;
|
|
856
|
+
var openrouterPlugin = {
|
|
857
|
+
name: "openrouter",
|
|
858
|
+
description: "OpenRouter multi-model AI gateway plugin",
|
|
859
|
+
autoEnable: {
|
|
860
|
+
envKeys: ["OPENROUTER_API_KEY"]
|
|
861
|
+
},
|
|
862
|
+
config: {
|
|
863
|
+
OPENROUTER_API_KEY: env.OPENROUTER_API_KEY ?? null,
|
|
864
|
+
OPENROUTER_BASE_URL: env.OPENROUTER_BASE_URL ?? null,
|
|
865
|
+
OPENROUTER_NANO_MODEL: env.OPENROUTER_NANO_MODEL ?? null,
|
|
866
|
+
OPENROUTER_MEDIUM_MODEL: env.OPENROUTER_MEDIUM_MODEL ?? null,
|
|
867
|
+
OPENROUTER_SMALL_MODEL: env.OPENROUTER_SMALL_MODEL ?? null,
|
|
868
|
+
OPENROUTER_LARGE_MODEL: env.OPENROUTER_LARGE_MODEL ?? null,
|
|
869
|
+
OPENROUTER_MEGA_MODEL: env.OPENROUTER_MEGA_MODEL ?? null,
|
|
870
|
+
OPENROUTER_RESPONSE_HANDLER_MODEL: env.OPENROUTER_RESPONSE_HANDLER_MODEL ?? null,
|
|
871
|
+
OPENROUTER_SHOULD_RESPOND_MODEL: env.OPENROUTER_SHOULD_RESPOND_MODEL ?? null,
|
|
872
|
+
OPENROUTER_ACTION_PLANNER_MODEL: env.OPENROUTER_ACTION_PLANNER_MODEL ?? null,
|
|
873
|
+
OPENROUTER_PLANNER_MODEL: env.OPENROUTER_PLANNER_MODEL ?? null,
|
|
874
|
+
OPENROUTER_IMAGE_MODEL: env.OPENROUTER_IMAGE_MODEL ?? null,
|
|
875
|
+
OPENROUTER_IMAGE_GENERATION_MODEL: env.OPENROUTER_IMAGE_GENERATION_MODEL ?? null,
|
|
876
|
+
OPENROUTER_EMBEDDING_MODEL: env.OPENROUTER_EMBEDDING_MODEL ?? null,
|
|
877
|
+
OPENROUTER_TRANSCRIPTION_MODEL: env.OPENROUTER_TRANSCRIPTION_MODEL ?? null,
|
|
878
|
+
OPENROUTER_EMBEDDING_DIMENSIONS: env.OPENROUTER_EMBEDDING_DIMENSIONS ?? null,
|
|
879
|
+
OPENROUTER_AUTO_CLEANUP_IMAGES: env.OPENROUTER_AUTO_CLEANUP_IMAGES ?? null,
|
|
880
|
+
NANO_MODEL: env.NANO_MODEL ?? null,
|
|
881
|
+
MEDIUM_MODEL: env.MEDIUM_MODEL ?? null,
|
|
882
|
+
SMALL_MODEL: env.SMALL_MODEL ?? null,
|
|
883
|
+
LARGE_MODEL: env.LARGE_MODEL ?? null,
|
|
884
|
+
MEGA_MODEL: env.MEGA_MODEL ?? null,
|
|
885
|
+
RESPONSE_HANDLER_MODEL: env.RESPONSE_HANDLER_MODEL ?? null,
|
|
886
|
+
SHOULD_RESPOND_MODEL: env.SHOULD_RESPOND_MODEL ?? null,
|
|
887
|
+
ACTION_PLANNER_MODEL: env.ACTION_PLANNER_MODEL ?? null,
|
|
888
|
+
PLANNER_MODEL: env.PLANNER_MODEL ?? null,
|
|
889
|
+
IMAGE_MODEL: env.IMAGE_MODEL ?? null,
|
|
890
|
+
IMAGE_GENERATION_MODEL: env.IMAGE_GENERATION_MODEL ?? null,
|
|
891
|
+
EMBEDDING_MODEL: env.EMBEDDING_MODEL ?? null,
|
|
892
|
+
TRANSCRIPTION_MODEL: env.TRANSCRIPTION_MODEL ?? null,
|
|
893
|
+
EMBEDDING_DIMENSIONS: env.EMBEDDING_DIMENSIONS ?? null
|
|
894
|
+
},
|
|
895
|
+
async init(config, runtime) {
|
|
896
|
+
initializeOpenRouter(config, runtime);
|
|
897
|
+
},
|
|
898
|
+
models: {
|
|
899
|
+
[TEXT_NANO_MODEL_TYPE2]: async (runtime, params) => {
|
|
900
|
+
return handleTextNano(runtime, params);
|
|
901
|
+
},
|
|
902
|
+
[ModelType5.TEXT_SMALL]: async (runtime, params) => {
|
|
903
|
+
return handleTextSmall(runtime, params);
|
|
904
|
+
},
|
|
905
|
+
[TEXT_MEDIUM_MODEL_TYPE2]: async (runtime, params) => {
|
|
906
|
+
return handleTextMedium(runtime, params);
|
|
907
|
+
},
|
|
908
|
+
[ModelType5.TEXT_LARGE]: async (runtime, params) => {
|
|
909
|
+
return handleTextLarge(runtime, params);
|
|
910
|
+
},
|
|
911
|
+
[TEXT_MEGA_MODEL_TYPE2]: async (runtime, params) => {
|
|
912
|
+
return handleTextMega(runtime, params);
|
|
913
|
+
},
|
|
914
|
+
[RESPONSE_HANDLER_MODEL_TYPE2]: async (runtime, params) => {
|
|
915
|
+
return handleResponseHandler(runtime, params);
|
|
916
|
+
},
|
|
917
|
+
[ACTION_PLANNER_MODEL_TYPE2]: async (runtime, params) => {
|
|
918
|
+
return handleActionPlanner(runtime, params);
|
|
919
|
+
},
|
|
920
|
+
[ModelType5.IMAGE_DESCRIPTION]: async (runtime, params) => {
|
|
921
|
+
return handleImageDescription(runtime, params);
|
|
922
|
+
},
|
|
923
|
+
[ModelType5.IMAGE]: async (runtime, params) => {
|
|
924
|
+
const result = await handleImageGeneration(runtime, params);
|
|
925
|
+
return [{ url: result.imageUrl }];
|
|
926
|
+
},
|
|
927
|
+
[ModelType5.TEXT_EMBEDDING]: async (runtime, params) => {
|
|
928
|
+
return handleTextEmbedding(runtime, params);
|
|
929
|
+
},
|
|
930
|
+
[ModelType5.TRANSCRIPTION]: async (runtime, params) => {
|
|
931
|
+
return handleTranscription(runtime, params);
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
tests: [
|
|
935
|
+
{
|
|
936
|
+
name: "openrouter_plugin_tests",
|
|
937
|
+
tests: [
|
|
938
|
+
{
|
|
939
|
+
name: "openrouter_test_text_small",
|
|
940
|
+
fn: async (runtime) => {
|
|
941
|
+
try {
|
|
942
|
+
const runModel = runtime.useModel.bind(runtime);
|
|
943
|
+
const text = await runModel(ModelType5.TEXT_SMALL, {
|
|
944
|
+
prompt: "What is the nature of reality in 10 words?"
|
|
945
|
+
});
|
|
946
|
+
if (text.length === 0) {
|
|
947
|
+
throw new Error("Failed to generate text");
|
|
948
|
+
}
|
|
949
|
+
logger5.log({ text }, "generated with test_text_small");
|
|
950
|
+
} catch (error) {
|
|
951
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
952
|
+
logger5.error(`Error in test_text_small: ${message}`);
|
|
953
|
+
throw error;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
name: "openrouter_test_text_large",
|
|
959
|
+
fn: async (runtime) => {
|
|
960
|
+
try {
|
|
961
|
+
const runModel = runtime.useModel.bind(runtime);
|
|
962
|
+
const text = await runModel(ModelType5.TEXT_LARGE, {
|
|
963
|
+
prompt: "What is the nature of reality in 10 words?"
|
|
964
|
+
});
|
|
965
|
+
if (text.length === 0) {
|
|
966
|
+
throw new Error("Failed to generate text");
|
|
967
|
+
}
|
|
968
|
+
logger5.log({ text }, "generated with test_text_large");
|
|
969
|
+
} catch (error) {
|
|
970
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
971
|
+
logger5.error(`Error in test_text_large: ${message}`);
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
name: "openrouter_test_structured_output_via_text_large",
|
|
978
|
+
fn: async (runtime) => {
|
|
979
|
+
try {
|
|
980
|
+
const result = await runtime.useModel(ModelType5.TEXT_LARGE, {
|
|
981
|
+
prompt: "Create a simple JSON object with a message field saying hello",
|
|
982
|
+
responseSchema: {
|
|
983
|
+
type: "object",
|
|
984
|
+
properties: { message: { type: "string" } },
|
|
985
|
+
required: ["message"]
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
logger5.log({ result }, "Generated structured output via TEXT_LARGE");
|
|
989
|
+
if (!result) {
|
|
990
|
+
throw new Error("Failed to generate structured output");
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
994
|
+
logger5.error(`Error in test_structured_output_via_text_large: ${message}`);
|
|
995
|
+
throw error;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
name: "openrouter_test_text_embedding",
|
|
1001
|
+
fn: async (runtime) => {
|
|
1002
|
+
try {
|
|
1003
|
+
const runModel = runtime.useModel.bind(runtime);
|
|
1004
|
+
const embedding = await runModel(ModelType5.TEXT_EMBEDDING, {
|
|
1005
|
+
text: "Hello, world!"
|
|
1006
|
+
});
|
|
1007
|
+
logger5.log({ embedding }, "embedding");
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1010
|
+
logger5.error(`Error in test_text_embedding: ${message}`);
|
|
1011
|
+
throw error;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
}
|
|
1017
|
+
]
|
|
1018
|
+
};
|
|
1019
|
+
var plugin_default = openrouterPlugin;
|
|
1020
|
+
|
|
1021
|
+
// index.ts
|
|
1022
|
+
var openrouterPlugin2 = plugin_default;
|
|
1023
|
+
export {
|
|
1024
|
+
shouldAutoCleanupImages,
|
|
1025
|
+
openrouterPlugin2 as openrouterPlugin,
|
|
1026
|
+
getTranscriptionModel,
|
|
1027
|
+
getSmallModel,
|
|
1028
|
+
getSetting,
|
|
1029
|
+
getResponseHandlerModel,
|
|
1030
|
+
getNanoModel,
|
|
1031
|
+
getMegaModel,
|
|
1032
|
+
getMediumModel,
|
|
1033
|
+
getLargeModel,
|
|
1034
|
+
getImageModel,
|
|
1035
|
+
getImageGenerationModel,
|
|
1036
|
+
getEmbeddingModel,
|
|
1037
|
+
getEmbeddingDimensions,
|
|
1038
|
+
getBaseURL,
|
|
1039
|
+
getApiKey,
|
|
1040
|
+
getActionPlannerModel,
|
|
1041
|
+
openrouterPlugin2 as default,
|
|
1042
|
+
DEFAULT_TRANSCRIPTION_MODEL,
|
|
1043
|
+
DEFAULT_SMALL_MODEL,
|
|
1044
|
+
DEFAULT_LARGE_MODEL,
|
|
1045
|
+
DEFAULT_IMAGE_MODEL,
|
|
1046
|
+
DEFAULT_IMAGE_GENERATION_MODEL,
|
|
1047
|
+
DEFAULT_EMBEDDING_MODEL,
|
|
1048
|
+
DEFAULT_EMBEDDING_DIMENSIONS,
|
|
1049
|
+
DEFAULT_BASE_URL
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
//# debugId=6C3DF0FBFC9021DA64756E2164756E21
|
|
1053
|
+
//# sourceMappingURL=index.node.js.map
|