diff --git a/.gitignore b/.gitignore index 88519fa..8549952 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,44 @@ +# Dependencies node_modules -.turbo + +# Build outputs dist +build +*.tsbuildinfo +.turbo +.turbo-tsconfig.json + +# Environment files .env -.eliza** +.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 +# prr state file (auto-generated) +.pr-resolver-state.json diff --git a/.prr/lessons.md b/.prr/lessons.md new file mode 100644 index 0000000..ce16411 --- /dev/null +++ b/.prr/lessons.md @@ -0,0 +1,16 @@ +# PRR Lessons Learned + +> This file is auto-generated by [prr](https://github.com/elizaOS/prr). +> It contains lessons learned from PR review fixes to help improve future fix attempts. +> You can edit this file manually or let prr update it. +> To share lessons across your team, commit this file to your repo. + +## Global Lessons + +- Fixer attempted disallowed file(s): __tests__/index.test.ts. Only edit the file(s) listed in TARGET FILE(S): src/index.ts, src/index.test.ts. + +## File-Specific Lessons + +### src/index.ts + +- Fix for src/index.ts:208 - Need to add unit tests in test/ directory that verify textStream/textStreamDone event ordering and timeout behavior using mocked models diff --git a/README.md b/README.md index e99f980..c70dba5 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,24 @@ const object = await runtime.useModel(ModelType.OBJECT_LARGE, { - You can pre-download models using `ollama pull modelname` - Check model availability with `ollama list` +## Publishing (standalone repo) + +When developing in a monorepo you keep `@elizaos/core` as `workspace:*`. When publishing from the standalone repo (e.g. after syncing from monorepo), the published package must use a real version so consumers can install. + +- **After syncing from monorepo:** run `bun run prepare-publish` to set `@elizaos/core` to the version in `publishCoreVersion` (or set `CORE_VERSION` env). Commit that change so installs work in the standalone repo. +- **Before every publish:** `prepublishOnly` runs automatically and ensures the packed tarball has the correct core version even if `package.json` still has `workspace:*`. +- To change the published core version, update `publishCoreVersion` in `package.json` (or use `CORE_VERSION=1.8.0 bun run prepare-publish`). + +### Git hooks (monorepo) + +To always **commit** the verified core version and **check out** with `workspace:*` for local dev, install the hooks from the plugin directory (in the monorepo): + +```bash +cd packages/plugin-ollama && bun run install-git-hooks +``` + +- **pre-commit:** Sets `@elizaos/core` to `publishCoreVersion` and stages `package.json`, so every commit has the publishable version. +- **post-checkout:** In a monorepo only, sets `@elizaos/core` back to `workspace:*` after checkout so installs use the local workspace. Not run when this repo is the standalone plugin repo (so installs keep working there). ## License diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..b56f6e4 --- /dev/null +++ b/build.ts @@ -0,0 +1,47 @@ +#!/usr/bin/env bun +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..."); + // Note: always runs clean to ensure a fresh build environment without stale artifacts + await $`bun run clean`.quiet(); + } + + const esmStart = Date.now(); + console.log("๐Ÿ”จ Building @elizaos/plugin-ollama..."); + const esmResult = await Bun.build({ + entrypoints: ["src/index.ts"], + outdir: "dist", + target: "node", + format: "esm", + sourcemap: "external", + minify: false, + external: externalDeps, + }); + if (!esmResult.success) { + console.error(esmResult.logs); + throw new Error("ESM build failed"); + } + console.log(`โœ… Build complete in ${((Date.now() - esmStart) / 1000).toFixed(2)}s`); + + const dtsStart = Date.now(); + console.log("๐Ÿ“ Generating TypeScript declarations..."); + await $`tsc --project tsconfig.build.json`; + console.log(`โœ… Declarations generated in ${((Date.now() - dtsStart) / 1000).toFixed(2)}s`); + + console.log(`๐ŸŽ‰ All builds finished in ${((Date.now() - totalStart) / 1000).toFixed(2)}s`); +} + +build().catch((err) => { + console.error("Build failed:", err); + process.exit(1); +}); diff --git a/package.json b/package.json index e694486..8a34f05 100644 --- a/package.json +++ b/package.json @@ -23,22 +23,27 @@ ], "dependencies": { "@ai-sdk/ui-utils": "^1.2.8", - "@elizaos/core": "^1.0.0", + "@elizaos/core": "1.7.2", "ai": "^4.3.9", "js-tiktoken": "^1.0.18", "ollama-ai-provider": "^1.2.0", "tsup": "8.4.0" }, "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "bun run build.ts", + "dev": "bun run build.ts", "lint": "prettier --write ./src", "clean": "rm -rf dist .turbo node_modules .turbo-tsconfig.json tsconfig.tsbuildinfo", "format": "prettier --write ./src", "format:check": "prettier --check ./src", "test": "bun test", - "postinstall": "bun scripts/install-ollama.js" + "postinstall": "bun scripts/install-ollama.js", + "prepare-publish": "bun run scripts/rewrite-core-version.ts", + "restore-workspace": "bun run scripts/rewrite-core-version.ts --restore", + "prepublishOnly": "bun run scripts/rewrite-core-version.ts", + "install-git-hooks": "bun run scripts/install-git-hooks.ts" }, + "publishCoreVersion": "1.7.2", "publishConfig": { "access": "public" }, diff --git a/scripts/git-hooks/post-checkout b/scripts/git-hooks/post-checkout new file mode 100644 index 0000000..82380c1 --- /dev/null +++ b/scripts/git-hooks/post-checkout @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Post-checkout: in a monorepo, set @elizaos/core back to workspace:* so dev uses local core. +# Skip when this is the standalone repo (no workspace to link). +set -e +ROOT="$(git rev-parse --show-toplevel)" +if [ -f "$ROOT/packages/plugin-ollama/package.json" ] && grep -q '"publishCoreVersion"' "$ROOT/packages/plugin-ollama/package.json" 2>/dev/null; then + PLUGIN_DIR="$ROOT/packages/plugin-ollama" + (cd "$PLUGIN_DIR" && bun run scripts/rewrite-core-version.ts --restore) +fi +# If repo root has publishCoreVersion (standalone), do not restore โ€” keep committed version for installs. +exit 0 diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit new file mode 100644 index 0000000..bc00ab9 --- /dev/null +++ b/scripts/git-hooks/pre-commit @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Pre-commit: ensure @elizaos/core is the verified version from publishCoreVersion so we commit the publishable state. +set -e +ROOT="$(git rev-parse --show-toplevel)" +if [ -f "$ROOT/packages/plugin-ollama/package.json" ] && grep -q '"publishCoreVersion"' "$ROOT/packages/plugin-ollama/package.json" 2>/dev/null; then + PLUGIN_DIR="$ROOT/packages/plugin-ollama" +else + [ -f "$ROOT/package.json" ] && grep -q '"publishCoreVersion"' "$ROOT/package.json" 2>/dev/null || exit 0 + PLUGIN_DIR="$ROOT" +fi +(cd "$PLUGIN_DIR" && bun run scripts/rewrite-core-version.ts) +git add "$PLUGIN_DIR/package.json" +exit 0 diff --git a/scripts/install-git-hooks.ts b/scripts/install-git-hooks.ts new file mode 100644 index 0000000..c1b699f --- /dev/null +++ b/scripts/install-git-hooks.ts @@ -0,0 +1,28 @@ +#!/usr/bin/env bun +/** + * Install git hooks so that: + * - pre-commit: commit with @elizaos/core = publishCoreVersion (verified for publish) + * - post-checkout: in monorepo, restore workspace:* for local dev + * + * Run from plugin dir: bun run scripts/install-git-hooks.ts + * Uses git rev-parse to find .git (works when plugin is in monorepo or standalone). + */ +import { $ } from "bun"; +import { mkdir, writeFile } from "fs/promises"; +import { join } from "path"; + +const pluginDir = join(import.meta.dir, ".."); +const gitDir = (await $`git -C "${pluginDir}" rev-parse --git-dir`.text()).trim(); +const hooksDir = join(pluginDir, gitDir, "hooks"); +const hooksSource = join(pluginDir, "scripts", "git-hooks"); + +await mkdir(hooksDir, { recursive: true }); + +for (const name of ["pre-commit", "post-checkout"]) { + const src = join(hooksSource, name); + const dest = join(hooksDir, name); + const content = await Bun.file(src).text(); + await writeFile(dest, content, { mode: 0o755 }); + console.log(`Installed ${name} -> ${dest}`); +} +console.log("Git hooks installed. Pre-commit will use publishCoreVersion; post-checkout will restore workspace:* in monorepo."); diff --git a/scripts/rewrite-core-version.ts b/scripts/rewrite-core-version.ts new file mode 100644 index 0000000..e5a8cc9 --- /dev/null +++ b/scripts/rewrite-core-version.ts @@ -0,0 +1,41 @@ +#!/usr/bin/env bun +/** + * For standalone publish: replace workspace:* with a real @elizaos/core version + * so installs work outside the monorepo. Run after syncing from monorepo or in + * prepublishOnly. Use --restore to set back to workspace:* (e.g. after publish). + * + * Usage: + * bun run scripts/rewrite-core-version.ts # set to publish version + * bun run scripts/rewrite-core-version.ts --restore # set back to workspace:* + * + * Version: from package.json "publishCoreVersion" or env CORE_VERSION or "1.7.2" + */ + +const RESTORE = process.argv.includes("--restore"); +const pkgPath = new URL("../package.json", import.meta.url); +const pkg = (await Bun.file(pkgPath).json()) as Record & { + dependencies?: Record; + publishCoreVersion?: string; +}; +const coreVersion = + process.env.CORE_VERSION ?? pkg.publishCoreVersion ?? "1.7.2"; +const deps = pkg.dependencies ?? {}; +const current = deps["@elizaos/core"]; + +if (RESTORE) { + if (current !== coreVersion) { + console.log(`@elizaos/core is "${current}", not "${coreVersion}", skipping restore`); + process.exit(0); + } + deps["@elizaos/core"] = "workspace:*"; + console.log('Set @elizaos/core to "workspace:*"'); +} else { + if (current === coreVersion) { + console.log(`@elizaos/core already "${coreVersion}"`); + process.exit(0); + } + deps["@elizaos/core"] = coreVersion; + console.log(`Set @elizaos/core to "${coreVersion}"`); +} + +await Bun.write(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); diff --git a/src/index.test.ts b/src/index.test.ts index f0da7f9..8fc25be 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,28 @@ import { describe, it, expect } from 'bun:test'; import { ollamaPlugin } from './index'; +import { createOllama } from 'ollama-ai-provider'; + +// Mocked runtime for testing +const mockRuntime = { + emit: jest.fn(), + getSetting: jest.fn().mockReturnValue(null), +}; + +const mockModel = jest.fn().mockReturnValue({ + doStream: jest.fn(), +}); + +const mockStreamText = jest.fn().mockImplementation(() => ({ + textStream: (async function*() { + yield 'Hello, '; + yield 'world!'; + })(), +})); + +// Replace actual streamText with mock +jest.mock('ai', () => ({ + streamText: mockStreamText, +})); describe('ollamaPlugin', () => { it('should export ollamaPlugin', () => { @@ -15,7 +38,27 @@ describe('ollamaPlugin', () => { expect(typeof ollamaPlugin.init).toBe('function'); }); - it('should have providers array', () => { + it('should emit text stream events in order', async () => { + const result = await ollamaPlugin.models[ModelType.TEXT_SMALL](mockRuntime, { + prompt: 'Test prompt', + }); + + expect(mockRuntime.emit).toHaveBeenCalledWith('textStream', 'Hello, '); + expect(mockRuntime.emit).toHaveBeenCalledWith('textStream', 'world!'); + expect(mockRuntime.emit).toHaveBeenCalledWith('textStreamDone', true); + }); + + it('should handle timeout behavior correctly', async () => { + const timeoutModel = createOllama({}); + const generateOllamaTextSpy = jest.spyOn(timeoutModel, 'doStream'); + + await ollamaPlugin.models[ModelType.TEXT_SMALL](mockRuntime, { + prompt: 'Test prompt with timeout', + }); + + expect(generateOllamaTextSpy).toHaveBeenCalled(); + expect(mockRuntime.emit).toHaveBeenCalledWith('textStreamDone', true); + }); expect(ollamaPlugin.providers).toBeDefined(); expect(Array.isArray(ollamaPlugin.providers)).toBe(true); }); diff --git a/src/index.ts b/src/index.ts index fba64ae..4ec94dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,138 @@ -import type { ObjectGenerationParams, Plugin, TextEmbeddingParams } from '@elizaos/core'; +import type { + IAgentRuntime, + ObjectGenerationParams, + Plugin, + TextEmbeddingParams, +} from '@elizaos/core'; import { type GenerateTextParams, ModelType, logger } from '@elizaos/core'; -import { generateObject, generateText, embed } from 'ai'; +import { generateObject, streamText, embed } from 'ai'; +import type { EmbeddingModel, LanguageModel } from 'ai'; import { createOllama } from 'ollama-ai-provider'; // Default Ollama API URL const OLLAMA_API_URL = 'http://localhost:11434/api'; +// Default timeout in milliseconds (10 minutes for large model operations) +const DEFAULT_TIMEOUT_MS = 600_000; + +const V2_SUPPORTED_URLS: Record = {}; + +/** + * Wrap an Ollama v1 language model so the AI SDK 5 accepts it (expects v2 with supportedUrls). + * Uses a Proxy so the SDK always sees specificationVersion "v2" and supportedUrls, and so + * doStream receives a mode object (ollama-ai-provider getArguments expects mode.type). + */ +function wrapAsV2LanguageModel(model: T): LanguageModel { + return new Proxy(model, { + get(target, prop) { + if (prop === "specificationVersion") return "v2"; + if (prop === "supportedUrls") return V2_SUPPORTED_URLS; + const value = Reflect.get(target, prop); + if (prop === "doStream" && typeof value === "function") { + return function (this: unknown, ...args: unknown[]) { + const [options, ...rest] = args; + let opts = options; + if (options && typeof options === "object") { + const o = options as Record & { mode?: { type?: string } }; + if (!o.mode?.type) { + opts = { ...o, mode: { type: "regular" } }; + } + } + return (value as (...a: unknown[]) => unknown).apply(target, [opts, ...rest]); + }; + } + return value; + }, + has(target, prop) { + if (prop === "specificationVersion" || prop === "supportedUrls") return true; + return Reflect.has(target, prop); + }, + }) as unknown as LanguageModel; +} + +/** + * Wrap an Ollama v1 embedding model so the AI SDK 5 accepts it (expects v2). + * Uses a Proxy so specificationVersion is overridden but methods like doEmbed are preserved. + */ +function wrapAsV2EmbeddingModel(model: T): EmbeddingModel { + return new Proxy(model, { + get(target, prop) { + if (prop === "specificationVersion") return "v2"; + return Reflect.get(target, prop); + }, + has(target, prop) { + if (prop === "specificationVersion") return true; + return Reflect.has(target, prop); + }, + }) as unknown as EmbeddingModel; +} + +/** Fetch-like signature (avoids requiring preconnect etc. from full typeof fetch) */ +type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise; + +/** + * Creates a fetch function with timeout support. + * @param baseFetch - The base fetch function to wrap + * @param timeoutMs - Timeout in milliseconds + * @returns A fetch function that will abort after the specified timeout + */ +function createFetchWithTimeout( + baseFetch: FetchLike | undefined, + timeoutMs: number = DEFAULT_TIMEOUT_MS +): FetchLike { + const fetchFn = baseFetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)); + return async (input: RequestInfo | URL, init?: RequestInit) => { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + + const existingSignal = init?.signal; + let signalToUse: AbortSignal; + + if (existingSignal) { + if (typeof AbortSignal !== 'undefined' && 'any' in AbortSignal) { + signalToUse = (AbortSignal as unknown as { any: (signals: AbortSignal[]) => AbortSignal }).any([existingSignal, controller.signal]); + } else { + if (existingSignal.aborted) { + controller.abort((existingSignal as AbortSignal & { reason?: unknown }).reason); + } else { + existingSignal.addEventListener( + 'abort', + () => controller.abort((existingSignal as AbortSignal & { reason?: unknown }).reason), + { once: true } + ); + } + signalToUse = controller.signal; + } + } else { + signalToUse = controller.signal; + } + + try { + const response = await fetchFn(input, { + ...init, + signal: signalToUse, + }); + return response; + } finally { + clearTimeout(timeoutId); + } + }; +} + +/** + * Helper to coerce runtime.getSetting result to string. + * Compatible with IAgentRuntime.getSetting (string | boolean | null | any). + */ +function getSettingAsString( + runtime: Pick, + key: string, + defaultValue?: string +): string | undefined { + const value = runtime.getSetting(key); + if (value === undefined || value === null) return defaultValue; + return String(value); +} + /** * Retrieves the Ollama API base URL from runtime settings. * @@ -14,10 +141,10 @@ const OLLAMA_API_URL = 'http://localhost:11434/api'; * * @returns The base URL for the Ollama API. */ -function getBaseURL(runtime: { getSetting: (key: string) => string | undefined }): string { +function getBaseURL(runtime: Pick): string { const apiEndpoint = - runtime.getSetting('OLLAMA_API_ENDPOINT') || - runtime.getSetting('OLLAMA_API_URL') || + getSettingAsString(runtime, "OLLAMA_API_ENDPOINT") || + getSettingAsString(runtime, "OLLAMA_API_URL") || OLLAMA_API_URL; // Ensure the URL ends with /api for ollama-ai-provider @@ -27,29 +154,33 @@ function getBaseURL(runtime: { getSetting: (key: string) => string | undefined } return apiEndpoint; } +/** Cache of (apiBase + model) we've already verified so we don't hammer /api/show. */ +const verifiedModels = new Set(); + /** * Ensures that the specified Ollama model is available locally, downloading it if necessary. - * - * Checks for the presence of the model via the Ollama API and attempts to download it if not found. Logs progress and errors during the process. + * Each (apiBase, model) is only checked once per process to avoid hammering /api/show. */ async function ensureModelAvailable( - runtime: { - getSetting: (key: string) => string | undefined; - fetch?: typeof fetch; - }, + runtime: Pick & { fetch?: typeof fetch }, model: string, providedBaseURL?: string ) { const baseURL = providedBaseURL || getBaseURL(runtime); // Remove /api suffix for direct API calls - const apiBase = baseURL.endsWith('/api') ? baseURL.slice(0, -4) : baseURL; + const apiBase = baseURL.endsWith("/api") ? baseURL.slice(0, -4) : baseURL; + const cacheKey = `${apiBase}:${model}`; + if (verifiedModels.has(cacheKey)) return; try { const showRes = await fetch(`${apiBase}/api/show`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model }), }); - if (showRes.ok) return; + if (showRes.ok) { + verifiedModels.add(cacheKey); + return; + } logger.info(`[Ollama] Model ${model} not found locally. Downloading...`); const pullRes = await fetch(`${apiBase}/api/pull`, { method: 'POST', @@ -60,6 +191,7 @@ async function ensureModelAvailable( logger.error(`Failed to pull model ${model}: ${pullRes.statusText}`); } else { logger.info(`[Ollama] Downloaded model ${model}`); + verifiedModels.add(cacheKey); } } catch (err) { logger.error({ error: err }, 'Error ensuring model availability'); @@ -82,23 +214,34 @@ async function generateOllamaText( frequencyPenalty: number; presencePenalty: number; stopSequences: string[]; - } + }, + runtime: Pick ) { try { - const { text: ollamaResponse } = await generateText({ - model: ollama(model), + const result = streamText({ + model: wrapAsV2LanguageModel(ollama(model)), prompt: params.prompt, system: params.system, temperature: params.temperature, - maxTokens: params.maxTokens, + maxOutputTokens: params.maxTokens, frequencyPenalty: params.frequencyPenalty, presencePenalty: params.presencePenalty, stopSequences: params.stopSequences, }); + + const out = [] + for await (const delta of result.textStream) { + runtime.emit('textStream', delta) + out.push(delta) + } + runtime.emit('textStreamDone', true) + const ollamaResponse = out.join('') + return ollamaResponse; } catch (error: unknown) { - logger.error({ error }, 'Error in generateOllamaText'); - return 'Error generating text. Please try again later.'; + runtime.emit('textStreamDone', false) + logger.error({ error: error instanceof Error ? error : new Error(String(error)) }, "Error in generateOllamaText"); + return "Error generating text. Please try again later."; } } @@ -114,14 +257,14 @@ async function generateOllamaObject( ) { try { const { object } = await generateObject({ - model: ollama(model), - output: 'no-schema', + model: wrapAsV2LanguageModel(ollama(model)), + output: "no-schema", prompt: params.prompt, temperature: params.temperature, }); return object; } catch (error: unknown) { - logger.error({ error }, 'Error generating object'); + logger.error({ err: error instanceof Error ? error : new Error(String(error)) }, "Error generating object"); return {}; } } @@ -186,11 +329,13 @@ export const ollamaPlugin: Plugin = { try { const baseURL = getBaseURL(runtime); const ollama = createOllama({ - fetch: runtime.fetch, + fetch: createFetchWithTimeout(runtime.fetch), baseURL, }); - const modelName = runtime.getSetting('OLLAMA_EMBEDDING_MODEL') || 'nomic-embed-text:latest'; + const modelName = + getSettingAsString(runtime, "OLLAMA_EMBEDDING_MODEL") || + "nomic-embed-text:latest"; logger.log(`[Ollama] Using TEXT_EMBEDDING model: ${modelName}`); await ensureModelAvailable(runtime, modelName, baseURL); const text = @@ -212,7 +357,7 @@ export const ollamaPlugin: Plugin = { // Use ollama.embedding() as shown in the docs try { const { embedding } = await embed({ - model: ollama.embedding(modelName), + model: wrapAsV2EmbeddingModel(ollama.embedding(modelName)), value: embeddingText, }); return embedding; @@ -234,14 +379,14 @@ export const ollamaPlugin: Plugin = { const max_response_length = 8000; const baseURL = getBaseURL(runtime); const ollama = createOllama({ - fetch: runtime.fetch, + fetch: createFetchWithTimeout(runtime.fetch) as typeof fetch, baseURL, }); const model = - runtime.getSetting('OLLAMA_SMALL_MODEL') || - runtime.getSetting('SMALL_MODEL') || - 'gemma3:latest'; + getSettingAsString(runtime, "OLLAMA_SMALL_MODEL") || + getSettingAsString(runtime, "SMALL_MODEL") || + "gemma3:latest"; logger.log(`[Ollama] Using TEXT_SMALL model: ${model}`); await ensureModelAvailable(runtime, model, baseURL); @@ -256,7 +401,7 @@ export const ollamaPlugin: Plugin = { frequencyPenalty: frequency_penalty, presencePenalty: presence_penalty, stopSequences, - }); + }, runtime); } catch (error) { logger.error({ error }, 'Error in TEXT_SMALL model'); return 'Error generating text. Please try again later.'; @@ -275,12 +420,12 @@ export const ollamaPlugin: Plugin = { ) => { try { const model = - runtime.getSetting('OLLAMA_LARGE_MODEL') || - runtime.getSetting('LARGE_MODEL') || - 'gemma3:latest'; + getSettingAsString(runtime, "OLLAMA_LARGE_MODEL") || + getSettingAsString(runtime, "LARGE_MODEL") || + "gemma3:latest"; const baseURL = getBaseURL(runtime); const ollama = createOllama({ - fetch: runtime.fetch, + fetch: createFetchWithTimeout(runtime.fetch) as typeof fetch, baseURL, }); @@ -294,23 +439,26 @@ export const ollamaPlugin: Plugin = { frequencyPenalty, presencePenalty, stopSequences, - }); + }, runtime); } catch (error) { logger.error({ error }, 'Error in TEXT_LARGE model'); return 'Error generating text. Please try again later.'; } }, - [ModelType.OBJECT_SMALL]: async (runtime, params: ObjectGenerationParams) => { + [ModelType.OBJECT_SMALL]: async ( + runtime, + params: ObjectGenerationParams, + ): Promise> => { try { const baseURL = getBaseURL(runtime); const ollama = createOllama({ - fetch: runtime.fetch, + fetch: createFetchWithTimeout(runtime.fetch) as typeof fetch, baseURL, }); const model = - runtime.getSetting('OLLAMA_SMALL_MODEL') || - runtime.getSetting('SMALL_MODEL') || - 'gemma3:latest'; + getSettingAsString(runtime, "OLLAMA_SMALL_MODEL") || + getSettingAsString(runtime, "SMALL_MODEL") || + "gemma3:latest"; logger.log(`[Ollama] Using OBJECT_SMALL model: ${model}`); await ensureModelAvailable(runtime, model, baseURL); @@ -318,24 +466,27 @@ export const ollamaPlugin: Plugin = { logger.info('Using OBJECT_SMALL without schema validation'); } - return await generateOllamaObject(ollama, model, params); + return await generateOllamaObject(ollama, model, params) as Record; } catch (error) { logger.error({ error }, 'Error in OBJECT_SMALL model'); // Return empty object instead of crashing return {}; } }, - [ModelType.OBJECT_LARGE]: async (runtime, params: ObjectGenerationParams) => { + [ModelType.OBJECT_LARGE]: async ( + runtime, + params: ObjectGenerationParams, + ): Promise> => { try { const baseURL = getBaseURL(runtime); const ollama = createOllama({ - fetch: runtime.fetch, + fetch: createFetchWithTimeout(runtime.fetch) as typeof fetch, baseURL, }); const model = - runtime.getSetting('OLLAMA_LARGE_MODEL') || - runtime.getSetting('LARGE_MODEL') || - 'gemma3:latest'; + getSettingAsString(runtime, "OLLAMA_LARGE_MODEL") || + getSettingAsString(runtime, "LARGE_MODEL") || + "gemma3:latest"; logger.log(`[Ollama] Using OBJECT_LARGE model: ${model}`); await ensureModelAvailable(runtime, model, baseURL); @@ -343,7 +494,7 @@ export const ollamaPlugin: Plugin = { logger.info('Using OBJECT_LARGE without schema validation'); } - return await generateOllamaObject(ollama, model, params); + return await generateOllamaObject(ollama, model, params) as Record; } catch (error) { logger.error({ error }, 'Error in OBJECT_LARGE model'); // Return empty object instead of crashing @@ -382,10 +533,13 @@ export const ollamaPlugin: Plugin = { name: 'ollama_test_text_embedding', fn: async (runtime) => { try { - const embedding = await runtime.useModel(ModelType.TEXT_EMBEDDING, { - text: 'Hello, world!', - }); - logger.log({ embedding }, 'Generated embedding'); + const embedding = await runtime.useModel( + ModelType.TEXT_EMBEDDING, + { + text: "Hello, world!", + }, + ); + logger.log(`embedding length: ${embedding.length}`); } catch (error) { logger.error({ error }, 'Error in test_text_embedding'); } @@ -435,7 +589,7 @@ export const ollamaPlugin: Plugin = { temperature: 0.7, schema: undefined, }); - logger.log({ object }, 'Generated object'); + logger.log("Generated object:", JSON.stringify(object)); } catch (error) { logger.error({ error }, 'Error in test_object_small'); } @@ -451,7 +605,7 @@ export const ollamaPlugin: Plugin = { temperature: 0.7, schema: undefined, }); - logger.log({ object }, 'Generated object'); + logger.log("Generated object:", JSON.stringify(object)); } catch (error) { logger.error({ error }, 'Error in test_object_large'); } diff --git a/tsconfig.build.json b/tsconfig.build.json index 625391d..e447fc5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,8 +6,18 @@ "sourceMap": true, "inlineSources": true, "declaration": true, - "emitDeclarationOnly": true + "emitDeclarationOnly": true, + "skipLibCheck": true, + "paths": {} }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] -} + "include": [ + "src/index.ts", + "src/**/*.d.ts" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c22f2f3..f8888ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,9 @@ "outDir": "dist", "rootDir": "src", "baseUrl": "../..", - "lib": ["ESNext"], + "lib": [ + "ESNext" + ], "target": "ESNext", "module": "Preserve", "moduleResolution": "Bundler", @@ -22,9 +24,15 @@ "moduleDetection": "force", "allowArbitraryExtensions": true, "paths": { - "@elizaos/core": ["../core/src"], - "@elizaos/core/*": ["../core/src/*"] + "@elizaos/core": [ + "../core/src" + ], + "@elizaos/core/*": [ + "../core/src/*" + ] } }, - "include": ["src/**/*.ts"] -} + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts index 0fc4943..4ba183e 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -6,8 +6,8 @@ export default defineConfig({ tsconfig: './tsconfig.build.json', // Use build-specific tsconfig sourcemap: true, clean: true, - format: ['esm'], // Ensure you're targeting CommonJS - dts: false, // Skip DTS generation to avoid external import issues // Ensure you're targeting CommonJS + format: ['esm'], + dts: true, external: [ 'dotenv', // Externalize dotenv to prevent bundling 'fs', // Externalize fs to use Node.js built-in module