From cb78fa46a165f1dfcc37313e0cc07e44ed35d13d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 15 Jan 2026 18:37:02 +0000 Subject: [PATCH] fix: make node-llama-cpp optional --- CHANGELOG.md | 1 + package.json | 4 +- pnpm-lock.yaml | 235 +++++++++++++++++------ src/agents/pi-embedded-helpers/google.ts | 1 - src/agents/pi-embedded-helpers/images.ts | 10 +- src/auto-reply/commands-registry.ts | 13 +- src/auto-reply/templating.test.ts | 4 +- src/auto-reply/templating.ts | 2 +- src/discord/monitor/native-command.ts | 2 +- src/memory/embeddings.test.ts | 61 ++++++ src/memory/embeddings.ts | 28 ++- src/memory/node-llama.ts | 3 + 12 files changed, 277 insertions(+), 87 deletions(-) create mode 100644 src/memory/node-llama.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d3f035f..b3ced9cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2026.1.15 (unreleased) - Fix: guard model fallback against undefined provider/model values. (#954) — thanks @roshanasingh4. +- Memory: make `node-llama-cpp` an optional dependency (avoid Node 25 install failures) and improve local-embeddings fallback/errors. - Browser: add `snapshot refs=aria` (Playwright aria-ref ids) for self-resolving refs across `snapshot` → `act`. - Browser: `profile="chrome"` now defaults to host control and returns clearer “attach a tab” errors. - Browser: extension mode recovers when only one tab is attached (stale targetId fallback). diff --git a/package.json b/package.json index b45ecef7f..6a408315c 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,6 @@ "json5": "^2.2.3", "long": "5.3.2", "markdown-it": "^14.1.0", - "node-llama-cpp": "3.14.5", "osc-progress": "^0.2.0", "playwright-core": "1.57.0", "proper-lockfile": "^4.1.2", @@ -181,6 +180,9 @@ "ws": "^8.19.0", "zod": "^4.3.5" }, + "optionalDependencies": { + "node-llama-cpp": "3.14.5" + }, "devDependencies": { "@grammyjs/types": "^3.23.0", "@lit-labs/signals": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd6483ae2..7372bbe9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,9 +122,6 @@ importers: markdown-it: specifier: ^14.1.0 version: 14.1.0 - node-llama-cpp: - specifier: 3.14.5 - version: 3.14.5(typescript@5.9.3) osc-progress: specifier: ^0.2.0 version: 0.2.0 @@ -237,6 +234,10 @@ importers: wireit: specifier: ^0.14.12 version: 0.14.12 + optionalDependencies: + node-llama-cpp: + specifier: 3.14.5 + version: 3.14.5(typescript@5.9.3) extensions/matrix: dependencies: @@ -4942,7 +4943,8 @@ snapshots: hono: 4.11.4 optional: true - '@huggingface/jinja@0.5.3': {} + '@huggingface/jinja@0.5.3': + optional: true '@img/colour@1.0.0': {} @@ -5081,8 +5083,10 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true - '@kwsites/promise-deferred@1.1.1': {} + '@kwsites/promise-deferred@1.1.1': + optional: true '@lit-labs/signals@0.2.0': dependencies: @@ -5338,6 +5342,7 @@ snapshots: '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) '@octokit/types': 16.0.0 '@octokit/webhooks': 14.2.0 + optional: true '@octokit/auth-app@8.1.2': dependencies: @@ -5349,6 +5354,7 @@ snapshots: toad-cache: 3.7.0 universal-github-app-jwt: 2.2.2 universal-user-agent: 7.0.3 + optional: true '@octokit/auth-oauth-app@9.0.3': dependencies: @@ -5357,6 +5363,7 @@ snapshots: '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/auth-oauth-device@8.0.3': dependencies: @@ -5364,6 +5371,7 @@ snapshots: '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/auth-oauth-user@6.0.2': dependencies: @@ -5372,13 +5380,16 @@ snapshots: '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + optional: true - '@octokit/auth-token@6.0.0': {} + '@octokit/auth-token@6.0.0': + optional: true '@octokit/auth-unauthenticated@7.0.3': dependencies: '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 + optional: true '@octokit/core@7.0.6': dependencies: @@ -5389,17 +5400,20 @@ snapshots: '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/endpoint@11.0.2': dependencies: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/graphql@9.0.3': dependencies: '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/oauth-app@8.0.3': dependencies: @@ -5411,8 +5425,10 @@ snapshots: '@octokit/oauth-methods': 6.0.2 '@types/aws-lambda': 8.10.159 universal-user-agent: 7.0.3 + optional: true - '@octokit/oauth-authorization-url@8.0.0': {} + '@octokit/oauth-authorization-url@8.0.0': + optional: true '@octokit/oauth-methods@6.0.2': dependencies: @@ -5420,24 +5436,30 @@ snapshots: '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 + optional: true - '@octokit/openapi-types@27.0.0': {} + '@octokit/openapi-types@27.0.0': + optional: true - '@octokit/openapi-webhooks-types@12.1.0': {} + '@octokit/openapi-webhooks-types@12.1.0': + optional: true '@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 + optional: true '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 '@octokit/types': 16.0.0 + optional: true '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 '@octokit/types': 16.0.0 + optional: true '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': dependencies: @@ -5445,16 +5467,19 @@ snapshots: '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 bottleneck: 2.19.5 + optional: true '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 '@octokit/types': 16.0.0 bottleneck: 2.19.5 + optional: true '@octokit/request-error@7.1.0': dependencies: '@octokit/types': 16.0.0 + optional: true '@octokit/request@10.0.7': dependencies: @@ -5463,18 +5488,22 @@ snapshots: '@octokit/types': 16.0.0 fast-content-type-parse: 3.0.0 universal-user-agent: 7.0.3 + optional: true '@octokit/types@16.0.0': dependencies: '@octokit/openapi-types': 27.0.0 + optional: true - '@octokit/webhooks-methods@6.0.0': {} + '@octokit/webhooks-methods@6.0.0': + optional: true '@octokit/webhooks@14.2.0': dependencies: '@octokit/openapi-webhooks-types': 12.1.0 '@octokit/request-error': 7.1.0 '@octokit/webhooks-methods': 6.0.0 + optional: true '@oxc-project/types@0.107.0': {} @@ -6111,7 +6140,8 @@ snapshots: '@thi.ng/errors@2.6.0': optional: true - '@tinyhttp/content-disposition@2.2.2': {} + '@tinyhttp/content-disposition@2.2.2': + optional: true '@tokenizer/inflate@0.4.1': dependencies: @@ -6127,7 +6157,8 @@ snapshots: tslib: 2.8.1 optional: true - '@types/aws-lambda@8.10.159': {} + '@types/aws-lambda@8.10.159': + optional: true '@types/body-parser@1.19.6': dependencies: @@ -6426,7 +6457,8 @@ snapshots: another-json@0.2.0: {} - ansi-escapes@6.2.1: {} + ansi-escapes@6.2.1: + optional: true ansi-regex@5.0.1: {} @@ -6445,12 +6477,14 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.1.0: {} + aproba@2.1.0: + optional: true are-we-there-yet@3.0.1: dependencies: delegates: 1.0.0 readable-stream: 3.6.2 + optional: true argparse@2.0.1: {} @@ -6469,6 +6503,7 @@ snapshots: async-retry@1.3.3: dependencies: retry: 0.13.1 + optional: true asynckit@0.4.0: {} @@ -6508,7 +6543,8 @@ snapshots: base64-js@1.5.1: {} - before-after-hook@4.0.0: {} + before-after-hook@4.0.0: + optional: true bignumber.js@9.3.1: {} @@ -6593,7 +6629,8 @@ snapshots: chalk@5.6.2: {} - chmodrp@1.0.2: {} + chmodrp@1.0.2: + optional: true chokidar@3.6.0: dependencies: @@ -6611,7 +6648,8 @@ snapshots: dependencies: readdirp: 5.0.0 - chownr@2.0.0: {} + chownr@2.0.0: + optional: true chownr@3.0.0: {} @@ -6621,7 +6659,8 @@ snapshots: mitt: 3.0.1 zod: 3.25.76 - ci-info@4.3.1: {} + ci-info@4.3.1: + optional: true class-variance-authority@0.7.1: dependencies: @@ -6630,6 +6669,7 @@ snapshots: cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 + optional: true cli-highlight@2.1.11: dependencies: @@ -6640,7 +6680,8 @@ snapshots: parse5-htmlparser2-tree-adapter: 6.0.1 yargs: 16.2.0 - cli-spinners@2.9.2: {} + cli-spinners@2.9.2: + optional: true cliui@7.0.4: dependencies: @@ -6653,6 +6694,7 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + optional: true clsx@2.1.1: {} @@ -6672,6 +6714,7 @@ snapshots: yargs: 17.7.2 transitivePeerDependencies: - supports-color + optional: true codec-parser@2.5.0: optional: true @@ -6684,19 +6727,22 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} + color-support@1.1.3: + optional: true combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - commander@10.0.1: {} + commander@10.0.1: + optional: true commander@14.0.2: {} commander@8.3.0: {} - console-control-strings@1.1.0: {} + console-control-strings@1.1.0: + optional: true content-disposition@1.0.1: {} @@ -6730,11 +6776,13 @@ snapshots: dependencies: ms: 2.1.3 - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true delayed-stream@1.0.0: {} - delegates@1.0.0: {} + delegates@1.0.0: + optional: true depd@2.0.0: {} @@ -6772,7 +6820,8 @@ snapshots: ee-first@1.1.1: {} - emoji-regex@10.6.0: {} + emoji-regex@10.6.0: + optional: true emoji-regex@8.0.0: {} @@ -6782,7 +6831,8 @@ snapshots: entities@4.5.0: {} - env-var@7.5.0: {} + env-var@7.5.0: + optional: true es-define-property@1.0.1: {} @@ -6885,7 +6935,8 @@ snapshots: extend@3.0.2: {} - fast-content-type-parse@3.0.0: {} + fast-content-type-parse@3.0.0: + optional: true fast-deep-equal@3.1.3: {} @@ -6925,11 +6976,13 @@ snapshots: transitivePeerDependencies: - supports-color - filename-reserved-regex@3.0.0: {} + filename-reserved-regex@3.0.0: + optional: true filenamify@6.0.0: dependencies: filename-reserved-regex: 3.0.0 + optional: true fill-range@7.1.1: dependencies: @@ -6976,10 +7029,12 @@ snapshots: graceful-fs: 4.2.11 jsonfile: 6.2.0 universalify: 2.0.1 + optional: true fs-minipass@2.1.0: dependencies: minipass: 3.3.6 + optional: true fsevents@2.3.2: optional: true @@ -6999,6 +7054,7 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 + optional: true gaxios@7.1.3: dependencies: @@ -7108,7 +7164,8 @@ snapshots: dependencies: has-symbols: 1.1.0 - has-unicode@2.0.1: {} + has-unicode@2.0.1: + optional: true hashery@1.4.0: dependencies: @@ -7158,13 +7215,15 @@ snapshots: ieee754@1.2.1: {} - ignore@7.0.5: {} + ignore@7.0.5: + optional: true immediate@3.0.6: {} inherits@2.0.4: {} - ini@1.3.8: {} + ini@1.3.8: + optional: true ipaddr.js@1.9.1: {} @@ -7191,6 +7250,7 @@ snapshots: strip-ansi: 7.1.2 optionalDependencies: '@reflink/reflink': 0.1.19 + optional: true is-binary-path@2.1.0: dependencies: @@ -7205,12 +7265,14 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.4.0 + optional: true is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - is-interactive@2.0.0: {} + is-interactive@2.0.0: + optional: true is-network-error@1.3.0: {} @@ -7220,9 +7282,11 @@ snapshots: is-stream@2.0.1: {} - is-unicode-supported@1.3.0: {} + is-unicode-supported@1.3.0: + optional: true - is-unicode-supported@2.1.0: {} + is-unicode-supported@2.1.0: + optional: true is-url@1.2.4: {} @@ -7230,7 +7294,8 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: {} + isexe@3.1.1: + optional: true istanbul-lib-coverage@3.2.2: {} @@ -7283,6 +7348,7 @@ snapshots: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + optional: true jsonwebtoken@9.0.3: dependencies: @@ -7340,9 +7406,11 @@ snapshots: dependencies: immediate: 3.0.6 - lifecycle-utils@2.1.0: {} + lifecycle-utils@2.1.0: + optional: true - lifecycle-utils@3.0.1: {} + lifecycle-utils@3.0.1: + optional: true lightningcss-android-arm64@1.30.2: optional: true @@ -7418,7 +7486,8 @@ snapshots: lodash.clonedeep@4.5.0: {} - lodash.debounce@4.0.8: {} + lodash.debounce@4.0.8: + optional: true lodash.includes@4.3.0: {} @@ -7440,11 +7509,13 @@ snapshots: dependencies: chalk: 5.6.2 is-unicode-supported: 1.3.0 + optional: true log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 yoctocolors: 2.1.2 + optional: true loglevel@1.9.2: {} @@ -7455,6 +7526,7 @@ snapshots: lowdb@7.0.1: dependencies: steno: 4.0.2 + optional: true lru-cache@10.4.3: {} @@ -7535,6 +7607,7 @@ snapshots: memory-stream@1.0.0: dependencies: readable-stream: 3.6.2 + optional: true merge-descriptors@2.0.0: {} @@ -7557,7 +7630,8 @@ snapshots: dependencies: mime-db: 1.54.0 - mimic-function@5.0.1: {} + mimic-function@5.0.1: + optional: true minimatch@10.1.1: dependencies: @@ -7567,13 +7641,16 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@1.2.8: {} + minimist@1.2.8: + optional: true minipass@3.3.6: dependencies: yallist: 4.0.0 + optional: true - minipass@5.0.0: {} + minipass@5.0.0: + optional: true minipass@7.1.2: {} @@ -7581,6 +7658,7 @@ snapshots: dependencies: minipass: 3.3.6 yallist: 4.0.0 + optional: true minizlib@3.1.0: dependencies: @@ -7588,7 +7666,8 @@ snapshots: mitt@3.0.1: {} - mkdirp@1.0.4: {} + mkdirp@1.0.4: + optional: true mpg123-decoder@1.0.3: dependencies: @@ -7621,13 +7700,16 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.6: {} + nanoid@5.1.6: + optional: true negotiator@1.0.0: {} - node-addon-api@8.5.0: {} + node-addon-api@8.5.0: + optional: true - node-api-headers@1.7.0: {} + node-api-headers@1.7.0: + optional: true node-domexception@1.0.0: {} @@ -7689,6 +7771,7 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + optional: true node-wav@0.0.2: optional: true @@ -7701,6 +7784,7 @@ snapshots: console-control-strings: 1.1.0 gauge: 4.0.4 set-blocking: 2.0.0 + optional: true object-assign@4.1.1: {} @@ -7723,6 +7807,7 @@ snapshots: '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 '@octokit/webhooks': 14.2.0 + optional: true ogg-opus-decoder@1.7.3: dependencies: @@ -7753,6 +7838,7 @@ snapshots: onetime@7.0.0: dependencies: mimic-function: 5.0.1 + optional: true openai@6.10.0(ws@8.19.0)(zod@4.3.5): optionalDependencies: @@ -7775,6 +7861,7 @@ snapshots: stdin-discarder: 0.2.2 string-width: 7.2.0 strip-ansi: 7.1.2 + optional: true osc-progress@0.2.0: {} @@ -7845,9 +7932,11 @@ snapshots: pako@1.0.11: {} - parse-ms@3.0.0: {} + parse-ms@3.0.0: + optional: true - parse-ms@4.0.0: {} + parse-ms@4.0.0: + optional: true parse5-htmlparser2-tree-adapter@6.0.1: dependencies: @@ -7925,15 +8014,18 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - pretty-bytes@6.1.1: {} + pretty-bytes@6.1.1: + optional: true pretty-ms@8.0.0: dependencies: parse-ms: 3.0.0 + optional: true pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 + optional: true prism-media@1.3.5: optional: true @@ -8043,6 +8135,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true readable-stream@2.3.8: dependencies: @@ -8059,6 +8152,7 @@ snapshots: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + optional: true readable-stream@4.5.2: dependencies: @@ -8086,6 +8180,7 @@ snapshots: dependencies: onetime: 7.0.0 signal-exit: 4.1.0 + optional: true retry@0.12.0: {} @@ -8198,7 +8293,8 @@ snapshots: transitivePeerDependencies: - supports-color - set-blocking@2.0.0: {} + set-blocking@2.0.0: + optional: true setimmediate@1.0.5: {} @@ -8288,6 +8384,7 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color + optional: true simple-yenc@1.0.4: optional: true @@ -8300,12 +8397,14 @@ snapshots: sisteransi@1.0.5: {} - sleep-promise@9.1.0: {} + sleep-promise@9.1.0: + optional: true slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + optional: true sonic-boom@4.2.0: dependencies: @@ -8328,7 +8427,8 @@ snapshots: std-env@3.10.0: {} - stdin-discarder@0.2.2: {} + stdin-discarder@0.2.2: + optional: true stdout-update@4.0.1: dependencies: @@ -8336,8 +8436,10 @@ snapshots: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.1.2 + optional: true - steno@4.0.2: {} + steno@4.0.2: + optional: true string-width@4.2.3: dependencies: @@ -8356,6 +8458,7 @@ snapshots: emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + optional: true string_decoder@1.1.1: dependencies: @@ -8373,7 +8476,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true strnum@2.1.2: {} @@ -8403,6 +8507,7 @@ snapshots: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + optional: true tar@7.5.2: dependencies: @@ -8443,7 +8548,8 @@ snapshots: dependencies: is-number: 7.0.0 - toad-cache@3.7.0: {} + toad-cache@3.7.0: + optional: true toidentifier@1.0.1: {} @@ -8504,17 +8610,21 @@ snapshots: pako: 0.2.9 tiny-inflate: 1.0.3 - universal-github-app-jwt@2.2.2: {} + universal-github-app-jwt@2.2.2: + optional: true - universal-user-agent@7.0.3: {} + universal-user-agent@7.0.3: + optional: true - universalify@2.0.1: {} + universalify@2.0.1: + optional: true unpipe@1.0.0: {} urijs@1.19.11: {} - url-join@4.0.1: {} + url-join@4.0.1: + optional: true util-deprecate@1.0.2: {} @@ -8524,7 +8634,8 @@ snapshots: uuid@8.3.2: {} - validate-npm-package-name@6.0.2: {} + validate-npm-package-name@6.0.2: + optional: true vary@1.1.2: {} @@ -8602,6 +8713,7 @@ snapshots: which@5.0.0: dependencies: isexe: 3.1.1 + optional: true why-is-node-running@2.3.0: dependencies: @@ -8611,6 +8723,7 @@ snapshots: wide-align@1.1.5: dependencies: string-width: 4.2.3 + optional: true wireit@0.14.12: dependencies: @@ -8651,7 +8764,8 @@ snapshots: yargs-parser@20.2.9: {} - yargs-parser@21.1.1: {} + yargs-parser@21.1.1: + optional: true yargs@16.2.0: dependencies: @@ -8672,6 +8786,7 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 + optional: true yoctocolors@2.1.2: {} diff --git a/src/agents/pi-embedded-helpers/google.ts b/src/agents/pi-embedded-helpers/google.ts index e3c6200bd..fcda91ee3 100644 --- a/src/agents/pi-embedded-helpers/google.ts +++ b/src/agents/pi-embedded-helpers/google.ts @@ -13,7 +13,6 @@ export function isAntigravityClaude(api?: string | null, modelId?: string): bool return modelId?.toLowerCase().includes("claude") ?? false; } - export { sanitizeGoogleTurnOrdering }; /** diff --git a/src/agents/pi-embedded-helpers/images.ts b/src/agents/pi-embedded-helpers/images.ts index bd390b842..91dd61988 100644 --- a/src/agents/pi-embedded-helpers/images.ts +++ b/src/agents/pi-embedded-helpers/images.ts @@ -30,7 +30,11 @@ function isEmptyAssistantErrorMessage( export async function sanitizeSessionMessagesImages( messages: AgentMessage[], label: string, - options?: { sanitizeToolCallIds?: boolean; enforceToolCallLast?: boolean; preserveSignatures?: boolean }, + options?: { + sanitizeToolCallIds?: boolean; + enforceToolCallLast?: boolean; + preserveSignatures?: boolean; + }, ): Promise { // We sanitize historical session messages because Anthropic can reject a request // if the transcript contains oversized base64 images (see MAX_IMAGE_DIMENSION_PX). @@ -77,8 +81,8 @@ export async function sanitizeSessionMessagesImages( const content = assistantMsg.content; if (Array.isArray(content)) { const strippedContent = options?.preserveSignatures - ? content // Keep signatures for Antigravity Claude - : stripThoughtSignatures(content); // Strip for Gemini + ? content // Keep signatures for Antigravity Claude + : stripThoughtSignatures(content); // Strip for Gemini const filteredContent = strippedContent.filter((block) => { if (!block || typeof block !== "object") return true; diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 4da2f9869..759b575a7 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -140,19 +140,8 @@ function formatPositionalArgs( let rendered: string; if (typeof value === "string") { rendered = value.trim(); - } else if ( - typeof value === "number" || - typeof value === "boolean" || - typeof value === "bigint" - ) { - rendered = String(value); - } else if (typeof value === "symbol") { - rendered = value.toString(); - } else if (typeof value === "function") { - rendered = value.toString(); } else { - // Objects and arrays - rendered = JSON.stringify(value); + rendered = String(value); } if (!rendered) continue; parts.push(rendered); diff --git a/src/auto-reply/templating.test.ts b/src/auto-reply/templating.test.ts index aeefdc53a..a4be64f4b 100644 --- a/src/auto-reply/templating.test.ts +++ b/src/auto-reply/templating.test.ts @@ -8,9 +8,7 @@ describe("applyTemplate", () => { overrides.MessageSid = 42; overrides.IsNewSession = true; - expect(applyTemplate("sid={{MessageSid}} new={{IsNewSession}}", ctx)).toBe( - "sid=42 new=true", - ); + expect(applyTemplate("sid={{MessageSid}} new={{IsNewSession}}", ctx)).toBe("sid=42 new=true"); }); it("renders arrays of primitives", () => { diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index 647088460..03bd488f6 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -100,7 +100,7 @@ function formatTemplateValue(value: unknown): string { .join(","); } if (typeof value === "object") { - return JSON.stringify(value); + return ""; } return ""; } diff --git a/src/discord/monitor/native-command.ts b/src/discord/monitor/native-command.ts index ccba87475..567314dbe 100644 --- a/src/discord/monitor/native-command.ts +++ b/src/discord/monitor/native-command.ts @@ -118,7 +118,7 @@ function readDiscordCommandArgs( if (!definitions || definitions.length === 0) return undefined; const values: CommandArgValues = {}; for (const definition of definitions) { - let value: string | number | boolean | null; + let value: string | number | boolean | null | undefined; if (definition.type === "number") { value = interaction.options.getNumber(definition.name); } else if (definition.type === "boolean") { diff --git a/src/memory/embeddings.test.ts b/src/memory/embeddings.test.ts index cf5e768f9..bd4e47be6 100644 --- a/src/memory/embeddings.test.ts +++ b/src/memory/embeddings.test.ts @@ -14,6 +14,7 @@ const createFetchMock = () => describe("embedding provider remote overrides", () => { afterEach(() => { vi.resetAllMocks(); + vi.resetModules(); vi.unstubAllGlobals(); }); @@ -107,3 +108,63 @@ describe("embedding provider remote overrides", () => { expect(headers.Authorization).toBe("Bearer provider-key"); }); }); + +describe("embedding provider local fallback", () => { + afterEach(() => { + vi.resetAllMocks(); + vi.resetModules(); + vi.unstubAllGlobals(); + vi.doUnmock("./node-llama.js"); + }); + + it("falls back to openai when node-llama-cpp is missing", async () => { + vi.doMock("./node-llama.js", () => ({ + importNodeLlamaCpp: async () => { + throw Object.assign(new Error("Cannot find package 'node-llama-cpp'"), { + code: "ERR_MODULE_NOT_FOUND", + }); + }, + })); + + const fetchMock = createFetchMock(); + vi.stubGlobal("fetch", fetchMock); + + const { createEmbeddingProvider } = await import("./embeddings.js"); + const authModule = await import("../agents/model-auth.js"); + vi.mocked(authModule.resolveApiKeyForProvider).mockResolvedValue({ + apiKey: "provider-key", + }); + + const result = await createEmbeddingProvider({ + config: {} as never, + provider: "local", + model: "text-embedding-3-small", + fallback: "openai", + }); + + expect(result.provider.id).toBe("openai"); + expect(result.fallbackFrom).toBe("local"); + expect(result.fallbackReason).toContain("node-llama-cpp"); + }); + + it("throws a helpful error when local is requested and fallback is none", async () => { + vi.doMock("./node-llama.js", () => ({ + importNodeLlamaCpp: async () => { + throw Object.assign(new Error("Cannot find package 'node-llama-cpp'"), { + code: "ERR_MODULE_NOT_FOUND", + }); + }, + })); + + const { createEmbeddingProvider } = await import("./embeddings.js"); + + await expect( + createEmbeddingProvider({ + config: {} as never, + provider: "local", + model: "text-embedding-3-small", + fallback: "none", + }), + ).rejects.toThrow(/optional dependency node-llama-cpp/i); + }); +}); diff --git a/src/memory/embeddings.ts b/src/memory/embeddings.ts index f525af2bc..0d6226127 100644 --- a/src/memory/embeddings.ts +++ b/src/memory/embeddings.ts @@ -1,6 +1,7 @@ import type { Llama, LlamaEmbeddingContext, LlamaModel } from "node-llama-cpp"; import { resolveApiKeyForProvider } from "../agents/model-auth.js"; import type { ClawdbotConfig } from "../config/config.js"; +import { importNodeLlamaCpp } from "./node-llama.js"; export type EmbeddingProvider = { id: string; @@ -105,7 +106,7 @@ async function createLocalEmbeddingProvider( const modelCacheDir = options.local?.modelCacheDir?.trim(); // Lazy-load node-llama-cpp to keep startup light unless local is enabled. - const { getLlama, resolveModelFile, LlamaLogLevel } = await import("node-llama-cpp"); + const { getLlama, resolveModelFile, LlamaLogLevel } = await importNodeLlamaCpp(); let llama: Llama | null = null; let embeddingModel: LlamaModel | null = null; @@ -181,15 +182,32 @@ function formatError(err: unknown): string { return String(err); } +function isNodeLlamaCppMissing(err: unknown): boolean { + if (!(err instanceof Error)) return false; + const code = (err as Error & { code?: unknown }).code; + if (code === "ERR_MODULE_NOT_FOUND") { + return err.message.includes("node-llama-cpp"); + } + return false; +} + function formatLocalSetupError(err: unknown): string { const detail = formatError(err); + const missing = isNodeLlamaCppMissing(err); return [ "Local embeddings unavailable.", - detail ? `Reason: ${detail}` : undefined, + missing + ? "Reason: optional dependency node-llama-cpp is missing (or failed to install)." + : detail + ? `Reason: ${detail}` + : undefined, + missing && detail ? `Detail: ${detail}` : null, "To enable local embeddings:", - "1) pnpm approve-builds", - "2) select node-llama-cpp", - "3) pnpm rebuild node-llama-cpp", + "1) Use Node 22 LTS (recommended for installs/updates)", + missing + ? "2) Reinstall Clawdbot (this should install node-llama-cpp): npm i -g clawdbot@latest" + : null, + "3) If you use pnpm: pnpm approve-builds (select node-llama-cpp), then pnpm rebuild node-llama-cpp", 'Or set agents.defaults.memorySearch.provider = "openai" (remote).', ] .filter(Boolean) diff --git a/src/memory/node-llama.ts b/src/memory/node-llama.ts new file mode 100644 index 000000000..9327a1c45 --- /dev/null +++ b/src/memory/node-llama.ts @@ -0,0 +1,3 @@ +export async function importNodeLlamaCpp() { + return import("node-llama-cpp"); +}