-
Notifications
You must be signed in to change notification settings - Fork 7
streaming support + vercel ai sdk v1 & v2 support #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 1.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,42 @@ | ||
| node_modules/ | ||
| # Dependencies | ||
| node_modules | ||
|
|
||
| # Build outputs | ||
| dist | ||
| build | ||
| *.tsbuildinfo | ||
| .turbo | ||
| dist/ | ||
| .env | ||
| .turbo-tsconfig.json | ||
|
|
||
| # Environment files | ||
| .env | ||
| .env.local | ||
| .env.production | ||
| .env.bak | ||
|
|
||
| # IDE | ||
| .idea | ||
| .vscode | ||
| .zed | ||
| .DS_Store | ||
|
|
||
| # Test coverage | ||
| coverage | ||
| .nyc_output | ||
|
|
||
| # Logs | ||
| *.log | ||
| logs | ||
|
|
||
| # Cache | ||
| cache | ||
| .cache | ||
| tokencache | ||
|
|
||
| # Temporary files | ||
| *.tmp | ||
| *.temp | ||
| .tmp | ||
|
|
||
| # Bundler artifacts | ||
| tsup.config.bundled_*.mjs |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,101 +1,62 @@ | ||||||||||||
| #!/usr/bin/env bun | ||||||||||||
|
|
||||||||||||
| const externalDeps = [ | ||||||||||||
| "@elizaos/core", | ||||||||||||
| "@ai-sdk/openai", | ||||||||||||
| "@openrouter/ai-sdk-provider", | ||||||||||||
| "ai", | ||||||||||||
| "undici", | ||||||||||||
| "dotenv", | ||||||||||||
| ]; | ||||||||||||
| import { $ } from "bun"; | ||||||||||||
|
|
||||||||||||
| async function build() { | ||||||||||||
| const totalStart = Date.now(); | ||||||||||||
| const pkg = await Bun.file("package.json").json(); | ||||||||||||
| const externalDeps = [ | ||||||||||||
| ...Object.keys(pkg.dependencies ?? {}), | ||||||||||||
| ...Object.keys(pkg.peerDependencies ?? {}), | ||||||||||||
| ]; | ||||||||||||
|
|
||||||||||||
| // Use the clean script from package.json | ||||||||||||
| if (pkg.scripts?.clean) { | ||||||||||||
| console.log("🧹 Cleaning..."); | ||||||||||||
| await $`bun run clean`.quiet(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Node build | ||||||||||||
| const nodeStart = Date.now(); | ||||||||||||
| console.log("🔨 Building @elizaos/plugin-openrouter for Node..."); | ||||||||||||
| await Bun.build({ | ||||||||||||
| entrypoints: ["src/index.node.ts"], | ||||||||||||
| outdir: "dist/node", | ||||||||||||
| const esmStart = Date.now(); | ||||||||||||
| console.log("🔨 Building @elizaos/plugin-openrouter..."); | ||||||||||||
| const esmResult = await Bun.build({ | ||||||||||||
| entrypoints: ["src/index.ts"], | ||||||||||||
| outdir: "dist", | ||||||||||||
| target: "node", | ||||||||||||
| format: "esm", | ||||||||||||
| sourcemap: "external", | ||||||||||||
| minify: false, | ||||||||||||
| external: [...externalDeps], | ||||||||||||
| }); | ||||||||||||
| console.log(`✅ Node build complete in ${((Date.now() - nodeStart) / 1000).toFixed(2)}s`); | ||||||||||||
|
|
||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Build output paths don't match package.json exportsHigh Severity The rewritten Additional Locations (1) |
||||||||||||
| // Browser build | ||||||||||||
| const browserStart = Date.now(); | ||||||||||||
| console.log("🌐 Building @elizaos/plugin-openrouter for Browser..."); | ||||||||||||
| await Bun.build({ | ||||||||||||
| entrypoints: ["src/index.browser.ts"], | ||||||||||||
| outdir: "dist/browser", | ||||||||||||
| target: "browser", | ||||||||||||
| format: "esm", | ||||||||||||
| sourcemap: "external", | ||||||||||||
| minify: false, | ||||||||||||
| external: externalDeps, | ||||||||||||
| }); | ||||||||||||
|
Comment on lines
+18
to
28
|
||||||||||||
| console.log(`✅ Browser build complete in ${((Date.now() - browserStart) / 1000).toFixed(2)}s`); | ||||||||||||
|
|
||||||||||||
| // Node CJS build | ||||||||||||
| const cjsStart = Date.now(); | ||||||||||||
| console.log("🧱 Building @elizaos/plugin-openrouter for Node (CJS)..."); | ||||||||||||
| const cjsResult = await Bun.build({ | ||||||||||||
| entrypoints: ["src/index.node.ts"], | ||||||||||||
| outdir: "dist/cjs", | ||||||||||||
| target: "node", | ||||||||||||
| format: "cjs", | ||||||||||||
| sourcemap: "external", | ||||||||||||
| minify: false, | ||||||||||||
| external: [...externalDeps], | ||||||||||||
| }); | ||||||||||||
| if (!cjsResult.success) { | ||||||||||||
| console.error(cjsResult.logs); | ||||||||||||
| throw new Error("CJS build failed"); | ||||||||||||
| if (!esmResult.success) { | ||||||||||||
| console.error(esmResult.logs); | ||||||||||||
| throw new Error("ESM build failed"); | ||||||||||||
| } | ||||||||||||
| try { | ||||||||||||
| const { rename } = await import("node:fs/promises"); | ||||||||||||
| await rename("dist/cjs/index.node.js", "dist/cjs/index.node.cjs"); | ||||||||||||
| } catch (e) { | ||||||||||||
| console.warn("CJS rename step warning:", e); | ||||||||||||
| } | ||||||||||||
| console.log(`✅ CJS build complete in ${((Date.now() - cjsStart) / 1000).toFixed(2)}s`); | ||||||||||||
| console.log(`✅ Build complete in ${((Date.now() - esmStart) / 1000).toFixed(2)}s`); | ||||||||||||
|
|
||||||||||||
| // TypeScript declarations | ||||||||||||
| const dtsStart = Date.now(); | ||||||||||||
| console.log("📝 Generating TypeScript declarations..."); | ||||||||||||
| const { mkdir, writeFile } = await import("node:fs/promises"); | ||||||||||||
| const { $ } = await import("bun"); | ||||||||||||
| await $`tsc --project tsconfig.build.json`; | ||||||||||||
| await mkdir("dist/node", { recursive: true }); | ||||||||||||
| await mkdir("dist/browser", { recursive: true }); | ||||||||||||
| await mkdir("dist/cjs", { recursive: true }); | ||||||||||||
| await writeFile( | ||||||||||||
| "dist/node/index.d.ts", | ||||||||||||
| `export * from '../index'; | ||||||||||||
| export { default } from '../index'; | ||||||||||||
| ` | ||||||||||||
| ); | ||||||||||||
| await writeFile( | ||||||||||||
| "dist/browser/index.d.ts", | ||||||||||||
| `export * from '../index'; | ||||||||||||
| export { default } from '../index'; | ||||||||||||
| ` | ||||||||||||
| ); | ||||||||||||
| await writeFile( | ||||||||||||
| "dist/cjs/index.d.ts", | ||||||||||||
| `export * from '../index'; | ||||||||||||
| export { default } from '../index'; | ||||||||||||
| ` | ||||||||||||
| ); | ||||||||||||
| console.log(`✅ Declarations generated in ${((Date.now() - dtsStart) / 1000).toFixed(2)}s`); | ||||||||||||
| if (true) { // Always generate .d.ts | ||||||||||||
| console.log("📝 Generating TypeScript declarations..."); | ||||||||||||
| try { | ||||||||||||
| await $`tsc --project tsconfig.build.json`; | ||||||||||||
| console.log(`✅ Declarations generated in ${((Date.now() - dtsStart) / 1000).toFixed(2)}s`); | ||||||||||||
| } catch (error) { | ||||||||||||
| console.warn(`⚠️ TypeScript declaration generation had errors (${((Date.now() - dtsStart) / 1000).toFixed(2)}s)`); | ||||||||||||
| console.warn(" Build will continue - fix type errors when possible"); | ||||||||||||
|
||||||||||||
| console.warn(" Build will continue - fix type errors when possible"); | |
| console.warn(" Build will continue - fix type errors when possible"); | |
| if (process.env.FAIL_BUILD_ON_DTS_ERROR === "true") { | |
| throw (error instanceof Error ? error : new Error("TypeScript declaration generation failed")); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead else branch behind if (true) constant
The declaration generation block uses if (true) { ... } else { ... }, making the else branch permanently unreachable dead code:
if (true) { // Always generate .d.ts
// ... tsc emit
} else {
// ... tsc --noEmit --incremental (never runs)
}This clutters the build script and obscures intent. The else block should be removed entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ | |
| "dependencies": { | ||
| "@ai-sdk/openai": "^2.0.32", | ||
| "@ai-sdk/ui-utils": "1.2.11", | ||
| "@elizaos/core": "^1.7.0", | ||
| "@elizaos/core": "workspace:*", | ||
|
cursor[bot] marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No—publishing a package that still has runtime deps like How publish/pack rewriting works by package manager:
Practical takeaway: if you publish libraries meant for external consumers, ensure your published tarball contains only normal semver (or normal npm alias specs like Sources: [1] https://stackoverflow.com/questions/69220042/npm-err-unsupported-url-type-workspace-workspace, [2] https://nesbitt.io/2026/01/18/workspaces-and-monorepos-in-package-managers.html, [3] https://dimava.github.io/pnpm/workspaces/, [4] https://yarnpkg.com/features/workspaces, [5] https://bun.sh/docs/pm/workspaces Pin the dependency version or verify the publish tool rewrites Publishing 🤖 Prompt for AI Agents |
||
| "@openrouter/ai-sdk-provider": "^1.2.0", | ||
| "ai": "^5.0.47", | ||
| "undici": "^7.16.0" | ||
|
|
@@ -37,7 +37,8 @@ | |
| "@types/node": "^24.5.2", | ||
| "dotenv": "^17.2.2", | ||
| "prettier": "3.6.2", | ||
| "typescript": "^5.9.3" | ||
| "typescript": "^5.9.3", | ||
| "bun-types": "^1.2.21" | ||
| }, | ||
| "scripts": { | ||
| "build": "bun run build.ts", | ||
|
|
@@ -185,4 +186,4 @@ | |
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { | |
| type ImageGenerationParams, | ||
| type TextEmbeddingParams, | ||
| } from '@elizaos/core'; | ||
| import type { Tool, ToolChoice } from 'ai'; | ||
| import { initializeOpenRouter } from './init'; | ||
| import { handleTextSmall, handleTextLarge } from './models/text'; | ||
| import { handleObjectSmall, handleObjectLarge } from './models/object'; | ||
|
|
@@ -49,21 +50,35 @@ export const openrouterPlugin: Plugin = { | |
| models: { | ||
| [ModelType.TEXT_SMALL]: async ( | ||
| runtime: IAgentRuntime, | ||
| params: GenerateTextParams | ||
| ) => { | ||
| return handleTextSmall(runtime, params); | ||
| params: GenerateTextParams & { | ||
| tools?: Record<string, Tool>; | ||
| toolChoice?: ToolChoice<Record<string, Tool>>; | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tools and toolChoice params accepted but silently droppedMedium Severity The Additional Locations (1) |
||
| ): Promise<string> => { | ||
| const result = await handleTextSmall(runtime, params); | ||
| return typeof result === 'string' ? result : JSON.stringify(result); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JSON.stringify on TextStreamResult breaks streamingMedium Severity When Additional Locations (1) |
||
| }, | ||
|
Comment on lines
+57
to
60
|
||
| [ModelType.TEXT_LARGE]: async ( | ||
| runtime: IAgentRuntime, | ||
| params: GenerateTextParams | ||
| ) => { | ||
| return handleTextLarge(runtime, params); | ||
| params: GenerateTextParams & { | ||
| tools?: Record<string, Tool>; | ||
| toolChoice?: ToolChoice<Record<string, Tool>>; | ||
| }, | ||
| ): Promise<string> => { | ||
| const result = await handleTextLarge(runtime, params); | ||
| return typeof result === 'string' ? result : JSON.stringify(result); | ||
|
Comment on lines
+58
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Streaming result silently broken when When a caller passes const result = await handleTextSmall(runtime, params);
return typeof result === 'string' ? result : JSON.stringify(result);
Comment on lines
51
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The text-model wrappers still break streaming.
🤖 Prompt for AI Agents |
||
| }, | ||
| [ModelType.OBJECT_SMALL]: async (runtime: IAgentRuntime, params: ObjectGenerationParams) => { | ||
| return handleObjectSmall(runtime, params); | ||
| [ModelType.OBJECT_SMALL]: async ( | ||
| runtime: IAgentRuntime, | ||
| params: ObjectGenerationParams, | ||
| ): Promise<Record<string, unknown>> => { | ||
| return handleObjectSmall(runtime, params) as Promise<Record<string, unknown>>; | ||
| }, | ||
| [ModelType.OBJECT_LARGE]: async (runtime: IAgentRuntime, params: ObjectGenerationParams) => { | ||
| return handleObjectLarge(runtime, params); | ||
| [ModelType.OBJECT_LARGE]: async ( | ||
| runtime: IAgentRuntime, | ||
| params: ObjectGenerationParams, | ||
| ): Promise<Record<string, unknown>> => { | ||
| return handleObjectLarge(runtime, params) as Promise<Record<string, unknown>>; | ||
| }, | ||
| [ModelType.IMAGE_DESCRIPTION]: async ( | ||
| runtime: IAgentRuntime, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,18 @@ | ||
| import type { | ||
| GenerateTextParams, | ||
| IAgentRuntime, | ||
| TextStreamResult, | ||
| } from "@elizaos/core"; | ||
| import type { GenerateTextParams, IAgentRuntime } from "@elizaos/core"; | ||
| import { logger, ModelType } from "@elizaos/core"; | ||
| import { generateText, streamText } from "ai"; | ||
| import type { LanguageModel } from "ai"; | ||
|
|
||
| /** Result shape when streaming text (matches AI SDK streamText return) */ | ||
| export interface TextStreamResult { | ||
| textStream: ReadableStream<string>; | ||
| text: Promise<string>; | ||
| usage: Promise<{ promptTokens: number; completionTokens: number; totalTokens: number } | undefined>; | ||
| finishReason: Promise<string | undefined>; | ||
| } | ||
|
|
||
| /** Params extended with optional stream flag used by this plugin */ | ||
| type TextParamsWithStream = GenerateTextParams & { stream?: boolean }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Text-model entrypoints exposing tools/toolChoice:"
rg -n -C2 'tools\?:|toolChoice\?:' src/index.ts
echo
echo "Fields actually forwarded into generateParams:"
sed -n '14,56p' src/models/text.tsRepository: elizaos-plugins/plugin-openrouter Length of output: 2227 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Checking if generateText/streamText are called with tools/toolChoice ==="
rg -n 'generateText|streamText' src/models/text.ts -A 5
echo
echo "=== Checking where the params are passed to buildGenerateParams ==="
rg -n 'buildGenerateParams' src/models/text.ts -B 2 -A 10
echo
echo "=== Checking the complete function signatures in index.ts that accept tools ==="
sed -n '50,70p' src/index.ts
echo
echo "=== Checking how buildGenerateParams result is used ==="
grep -n 'buildGenerateParams' src/models/text.ts
rg -n 'const.*generateParams\|return.*generateParams' src/models/text.ts -A 3Repository: elizaos-plugins/plugin-openrouter Length of output: 4055 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Checking package.json for 'ai' library version ==="
cat package.json | grep -A 2 '"ai"'
echo
echo "=== Checking imports and types from 'ai' library ==="
head -20 src/models/text.ts
echo
echo "=== Checking if tools are used anywhere else in the codebase ==="
rg -n 'tools\|toolChoice' --type ts --type tsx | grep -v 'node_modules' | head -20Repository: elizaos-plugins/plugin-openrouter Length of output: 1201 🌐 Web query:
💡 Result: For the
Sources: [1] [2] [3] [4] [5] [6] 🌐 Web query:
💡 Result:
|
||
|
|
||
| import { createOpenRouterProvider } from "../providers"; | ||
| import { getSmallModel, getLargeModel } from "../utils/config"; | ||
|
|
@@ -16,7 +24,7 @@ import { emitModelUsageEvent } from "../utils/events"; | |
| function buildGenerateParams( | ||
| runtime: IAgentRuntime, | ||
| modelType: typeof ModelType.TEXT_SMALL | typeof ModelType.TEXT_LARGE, | ||
| params: GenerateTextParams, | ||
| params: TextParamsWithStream, | ||
| ) { | ||
| const { prompt, stopSequences = [] } = params; | ||
| const temperature = params.temperature ?? 0.7; | ||
|
Comment on lines
24
to
30
|
||
|
|
@@ -34,7 +42,7 @@ function buildGenerateParams( | |
| modelType === ModelType.TEXT_SMALL ? "TEXT_SMALL" : "TEXT_LARGE"; | ||
|
|
||
| const generateParams: Parameters<typeof generateText>[0] = { | ||
| model: openrouter.chat(modelName), | ||
| model: openrouter.chat(modelName) as LanguageModel, | ||
| prompt: prompt, | ||
| system: runtime.character.system ?? undefined, | ||
| temperature: temperature, | ||
|
|
@@ -88,7 +96,7 @@ function handleStreamingGeneration( | |
| async function generateTextWithModel( | ||
| runtime: IAgentRuntime, | ||
| modelType: typeof ModelType.TEXT_SMALL | typeof ModelType.TEXT_LARGE, | ||
| params: GenerateTextParams, | ||
| params: TextParamsWithStream, | ||
| ): Promise<string | TextStreamResult> { | ||
| const { generateParams, modelName, modelLabel, prompt } = | ||
| buildGenerateParams(runtime, modelType, params); | ||
|
Comment on lines
96
to
102
|
||
|
|
@@ -127,7 +135,7 @@ async function generateTextWithModel( | |
| */ | ||
| export async function handleTextSmall( | ||
| runtime: IAgentRuntime, | ||
| params: GenerateTextParams, | ||
| params: TextParamsWithStream, | ||
| ): Promise<string | TextStreamResult> { | ||
| return generateTextWithModel(runtime, ModelType.TEXT_SMALL, params); | ||
| } | ||
|
|
@@ -141,7 +149,7 @@ export async function handleTextSmall( | |
| */ | ||
| export async function handleTextLarge( | ||
| runtime: IAgentRuntime, | ||
| params: GenerateTextParams, | ||
| params: TextParamsWithStream, | ||
| ): Promise<string | TextStreamResult> { | ||
| return generateTextWithModel(runtime, ModelType.TEXT_LARGE, params); | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't run the repo-wide
cleanscript as part of every build.package.jsonLine 50 deletesnode_modules. Running that here removes the localtscbinary and installed typings before the declaration step, and the catch block below turns the resulting failure into a green build. Use a build-only cleanup for artifacts, and let declaration generation fail the build.Suggested fix
Also applies to: 36-44
🤖 Prompt for AI Agents