Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions .github/workflows/embedding-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
- "src/services/embedding.ts"
- "src/services/onnxruntime-resolve.ts"
- "scripts/verify-embedding-backend.mjs"
- "scripts/verify-nested-onnxruntime-fixture.mjs"
- ".github/workflows/embedding-backend.yml"
workflow_dispatch:

Expand All @@ -24,9 +25,18 @@ jobs:
strategy:
fail-fast: false
matrix:
# macos-15-intel = Intel (darwin/x64); macos-15 = Apple Silicon (darwin/arm64).
# Intel must keep resolving onnxruntime-node@1.22.0 (#184).
os: [ubuntu-latest, macos-15-intel, macos-15, windows-latest]
# Intel: macos-15-intel / macos-26-intel (darwin/x64).
# Apple Silicon: macos-15 / macos-26 (darwin/arm64).
# Intel must keep resolving onnxruntime-node@1.22.0 (#184 / #210).
os:
[
ubuntu-latest,
macos-15-intel,
macos-26-intel,
macos-15,
macos-26,
windows-latest,
]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
Expand Down Expand Up @@ -72,3 +82,36 @@ jobs:

- name: Embedding smoke (Node)
run: node scripts/verify-embedding-backend.mjs

# Targeted #210 regression: OpenCode-shaped nested install on the oldest
# available standard Intel runner, using the reporter's Bun/Node versions.
nested-intel-regression:
name: macos-15-intel / nested OpenCode fixture
runs-on: macos-15-intel
steps:
- uses: actions/checkout@v5

- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"

- uses: actions/setup-node@v5
with:
node-version: "22"
package-manager-cache: false

- name: Install source dependencies
run: bun install

- name: Install web dependencies
working-directory: web
run: bun install

- name: Build package
run: bun run build

- name: Nested fixture + embedding (Bun 1.3.14)
run: bun scripts/verify-nested-onnxruntime-fixture.mjs

- name: Nested fixture + embedding (Node 22)
run: node scripts/verify-nested-onnxruntime-fixture.mjs
5 changes: 5 additions & 0 deletions .github/workflows/platform-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- "src/**"
- "scripts/native-deps-smoke.mjs"
- "scripts/verify-libsql-vector.mjs"
- "scripts/verify-nested-onnxruntime-fixture.mjs"
- "scripts/smoke-test.mjs"
- "web/**"
- ".github/workflows/platform-smoke.yml"
Expand All @@ -21,11 +22,15 @@ jobs:
strategy:
fail-fast: false
matrix:
# Intel: macos-15-intel / macos-26-intel (darwin/x64).
# Apple Silicon: macos-15 / macos-26 (darwin/arm64).
os:
- ubuntu-latest
- windows-latest
- macos-15-intel
- macos-26-intel
- macos-15
- macos-26

steps:
- uses: actions/checkout@v5
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ This plugin uses embedded Turso/libSQL with native vector indexes (`F32_BLOB`, `
- Internet access on first use if you use the default local embedding model, because the model is downloaded by `@huggingface/transformers`.
- For source/development installs, run `bun install` before building or testing. The published plugin package installs its runtime dependencies automatically through OpenCode.

**CI-tested platforms:** Linux, Windows, macOS 15 and macOS 26 on both Intel (`darwin/x64`) and Apple Silicon (`darwin/arm64`). Older macOS releases are not excluded by that matrix; they are simply outside the current GitHub-hosted runner set.

**Notes:**

- Vector embeddings are stored and searched directly in Turso/libSQL; inserts update the vector index automatically.
Expand Down Expand Up @@ -228,7 +230,7 @@ Example — remote OpenAI embeddings:

Changing `embeddingModel` (or dimensions) can trigger re-embedding of stored memories on next startup. Prefer picking a model once and sticking with it for a given data directory.

**Intel Mac (`darwin/x64`):** newer `onnxruntime-node` builds may ship without an x64 native binding, so local embedding init can fail. Use a remote endpoint via `embeddingApiUrl` + `embeddingApiKey` (example above). If you stay on local embeddings after a plugin upgrade, clear OpenCode's nested plugin cache (`~/.cache/opencode/packages/opencode-mem@*`) so the install picks up the pinned runtime.
**Intel Mac (`darwin/x64`):** newer `onnxruntime-node` builds may ship without an x64 native binding, so local embedding init can fail. `opencode-mem` pins `onnxruntime-node@1.22.0` and loads transformers through a CJS resolve shim so OpenCode nested installs keep that binding. If init still fails after a plugin upgrade, clear OpenCode's nested plugin cache (`~/.cache/opencode/packages/opencode-mem@*`) and reinstall, or use a remote endpoint via `embeddingApiUrl` + `embeddingApiKey` (example above).

### Memory Scope

Expand Down Expand Up @@ -349,7 +351,7 @@ Troubleshooting:
- If auto-capture reports that a provider is not connected, confirm the provider name with `opencode providers list` and configure that provider in opencode first.
- If a proxy or custom provider returns plain text instead of structured/tool output, choose another model/provider or use one of the manual provider modes above.
- For models that reject `temperature`, add `"memoryTemperature": false` when using manual API configuration.
- **Intel Mac (darwin/x64) local embedding:** if embedding init fails (missing onnxruntime x64 binding), switch to a remote embedding endpoint via `embeddingApiUrl` + `embeddingApiKey`, or clear `~/.cache/opencode/packages/opencode-mem@*` after upgrading so the nested install picks up the pinned `onnxruntime-node`. See [Choosing / configuring embeddings](#choosing-configuring-embeddings). MLX is not supported.
- **Intel Mac (darwin/x64) local embedding:** if embedding init fails, clear `~/.cache/opencode/packages/opencode-mem@*` after upgrading so the nested install picks up the pinned `onnxruntime-node@1.22.0`, or switch to a remote embedding endpoint via `embeddingApiUrl` + `embeddingApiKey`. See [Choosing / configuring embeddings](#choosing-configuring-embeddings). MLX is not supported.

## Public Subpath Exports

Expand Down
54 changes: 49 additions & 5 deletions scripts/verify-embedding-backend.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* feature-extraction path loads and runs the native ONNX runtime without
* crashing on the host platform.
*
* This is the reproducible form of the manual checks requested when migrating
* off @xenova/transformers: the prior revert (8fb0836) was motivated by native
* ONNX runtime crashes under Windows + Bun, so this runs in CI across
* ubuntu / macOS / windows to catch a regression before merge.
* Mirrors the production loader: prefer the CJS export so OpenCode nested
* installs can pin onnxruntime-node@1.22.0 via Module._resolveFilename (#210).
* This script deliberately does not import prepareOnnxruntimeForTransformers()
* because the embedding-backend workflow runs without a TypeScript build.
*
* Uses a tiny model (all-MiniLM-L6-v2, ~25 MB) — the goal is to exercise the
* runtime load + a real embedding call, not to validate any specific model.
Expand All @@ -16,15 +16,59 @@
* `node scripts/verify-embedding-backend.mjs`.
*/

import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { existsSync, readFileSync } from "node:fs";

const MODEL = "Xenova/all-MiniLM-L6-v2";
const EXPECTED_DIMS = 384;
const PINNED_ONNX_VERSION = "1.22.0";

const runtime = typeof globalThis.Bun !== "undefined" ? "bun" : "node";
console.log(
`[verify-embedding] runtime=${runtime} platform=${process.platform} arch=${process.arch}`
);

const { pipeline, env } = await import("@huggingface/transformers");
function readPackageJsonNear(entry) {
let dir = dirname(entry);
for (let i = 0; i < 6; i++) {
const candidate = join(dir, "package.json");
if (existsSync(candidate)) {
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
// onnxruntime-common ships helper package.json files under dist/* without version.
if (typeof parsed.version === "string" && parsed.version.length > 0) {
return parsed;
}
}
dir = dirname(dir);
}
throw new Error(`versioned package.json not found near ${entry}`);
}

const requireFromHere = createRequire(import.meta.url);
const transformersSpecifier = ["@huggingface", "transformers"].join("/");
const { pipeline, env } = requireFromHere(transformersSpecifier);

// Assert production-shaped CJS load resolved the pinned onnxruntime stack.
const onnxEntry = requireFromHere.resolve("onnxruntime-node");
const onnxPkg = readPackageJsonNear(onnxEntry);
if (onnxPkg.name !== "onnxruntime-node" || onnxPkg.version !== PINNED_ONNX_VERSION) {
console.error(
`[verify-embedding] FAIL: expected onnxruntime-node@${PINNED_ONNX_VERSION}, got ${onnxPkg.name}@${onnxPkg.version} at ${onnxEntry}`
);
process.exit(1);
}
console.log(`[verify-embedding] onnxruntime-node@${onnxPkg.version} at ${onnxEntry}`);

const commonEntry = createRequire(onnxEntry).resolve("onnxruntime-common");
const commonPkg = readPackageJsonNear(commonEntry);
if (commonPkg.name !== "onnxruntime-common" || commonPkg.version !== PINNED_ONNX_VERSION) {
console.error(
`[verify-embedding] FAIL: expected onnxruntime-common@${PINNED_ONNX_VERSION}, got ${commonPkg.name}@${commonPkg.version} at ${commonEntry}`
);
process.exit(1);
}
console.log(`[verify-embedding] onnxruntime-common@${commonPkg.version} at ${commonEntry}`);

// Mirror the plugin's runtime configuration.
env.allowLocalModels = true;
Expand Down
Loading