@karmaniverous/jeeves-watcher 0.9.2 → 0.9.4

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.
@@ -25,7 +25,7 @@ import { JSONPath } from 'jsonpath-plus';
25
25
  import { createHash } from 'node:crypto';
26
26
  import crypto from 'crypto';
27
27
  import { cosmiconfig } from 'cosmiconfig';
28
- import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
28
+ import https from 'node:https';
29
29
  import pino from 'pino';
30
30
  import { v5 } from 'uuid';
31
31
  import * as cheerio from 'cheerio';
@@ -3897,10 +3897,43 @@ function getLogger(logger) {
3897
3897
 
3898
3898
  /**
3899
3899
  * @module embedding/geminiProvider
3900
- * Gemini embedding provider using Google Generative AI.
3901
- */
3900
+ * Gemini embedding provider using the Google Generative AI REST API directly.
3901
+ * Uses node:https with a keep-alive agent for reliable performance in
3902
+ * long-running processes (avoids undici/fetch event-loop contention).
3903
+ */
3904
+ const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
3905
+ /** Persistent HTTPS agent for connection reuse. */
3906
+ const agent = new https.Agent({ keepAlive: true });
3907
+ /** Make an HTTPS POST request using node:https (bypasses undici/fetch). */
3908
+ function httpsPost(url, body) {
3909
+ return new Promise((resolve, reject) => {
3910
+ const parsed = new URL(url);
3911
+ const req = https.request({
3912
+ hostname: parsed.hostname,
3913
+ path: parsed.pathname + parsed.search,
3914
+ method: 'POST',
3915
+ agent,
3916
+ headers: {
3917
+ 'Content-Type': 'application/json',
3918
+ 'Content-Length': Buffer.byteLength(body),
3919
+ },
3920
+ }, (res) => {
3921
+ const chunks = [];
3922
+ res.on('data', (chunk) => chunks.push(chunk));
3923
+ res.on('end', () => {
3924
+ resolve({
3925
+ status: res.statusCode ?? 0,
3926
+ body: Buffer.concat(chunks).toString('utf8'),
3927
+ });
3928
+ });
3929
+ });
3930
+ req.on('error', reject);
3931
+ req.write(body);
3932
+ req.end();
3933
+ });
3934
+ }
3902
3935
  /**
3903
- * Create a Gemini embedding provider using the Google Generative AI SDK.
3936
+ * Create a Gemini embedding provider using the Google Generative AI REST API.
3904
3937
  *
3905
3938
  * @param config - The embedding configuration.
3906
3939
  * @param logger - Optional pino logger for retry warnings.
@@ -3912,20 +3945,27 @@ function createGeminiProvider(config, logger) {
3912
3945
  throw new Error('Gemini embedding provider requires config.embedding.apiKey');
3913
3946
  }
3914
3947
  const dimensions = config.dimensions ?? 3072;
3948
+ const model = config.model;
3949
+ const apiKey = config.apiKey;
3915
3950
  const log = getLogger(logger);
3916
- const embedder = new GoogleGenerativeAIEmbeddings({
3917
- apiKey: config.apiKey,
3918
- model: config.model,
3919
- });
3951
+ const url = `${GEMINI_API_BASE}/models/${model}:batchEmbedContents?key=${apiKey}`;
3920
3952
  return {
3921
3953
  dimensions,
3922
3954
  async embed(texts) {
3923
3955
  const vectors = await retry(async (attempt) => {
3924
3956
  if (attempt > 1) {
3925
- log.warn({ attempt, provider: 'gemini', model: config.model }, 'Retrying embedding request');
3957
+ log.warn({ attempt, provider: 'gemini', model }, 'Retrying embedding request');
3958
+ }
3959
+ const requests = texts.map((text) => ({
3960
+ model: `models/${model}`,
3961
+ content: { parts: [{ text }] },
3962
+ }));
3963
+ const response = await httpsPost(url, JSON.stringify({ requests }));
3964
+ if (response.status < 200 || response.status >= 300) {
3965
+ throw new Error(`Gemini API error ${String(response.status)}: ${response.body}`);
3926
3966
  }
3927
- // embedDocuments returns vectors for multiple texts
3928
- return embedder.embedDocuments(texts);
3967
+ const data = JSON.parse(response.body);
3968
+ return data.embeddings.map((e) => e.values);
3929
3969
  }, {
3930
3970
  attempts: 5,
3931
3971
  baseDelayMs: 500,
@@ -3936,7 +3976,7 @@ function createGeminiProvider(config, logger) {
3936
3976
  attempt,
3937
3977
  delayMs,
3938
3978
  provider: 'gemini',
3939
- model: config.model,
3979
+ model,
3940
3980
  err: normalizeError(error),
3941
3981
  }, 'Embedding call failed; will retry');
3942
3982
  },
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { createHash } from 'node:crypto';
22
22
  import crypto from 'crypto';
23
23
  import chokidar from 'chokidar';
24
24
  import { cosmiconfig } from 'cosmiconfig';
25
- import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
25
+ import https from 'node:https';
26
26
  import pino from 'pino';
27
27
  import { v5 } from 'uuid';
28
28
  import * as cheerio from 'cheerio';
@@ -3873,10 +3873,43 @@ function getLogger(logger) {
3873
3873
 
3874
3874
  /**
3875
3875
  * @module embedding/geminiProvider
3876
- * Gemini embedding provider using Google Generative AI.
3877
- */
3876
+ * Gemini embedding provider using the Google Generative AI REST API directly.
3877
+ * Uses node:https with a keep-alive agent for reliable performance in
3878
+ * long-running processes (avoids undici/fetch event-loop contention).
3879
+ */
3880
+ const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
3881
+ /** Persistent HTTPS agent for connection reuse. */
3882
+ const agent = new https.Agent({ keepAlive: true });
3883
+ /** Make an HTTPS POST request using node:https (bypasses undici/fetch). */
3884
+ function httpsPost(url, body) {
3885
+ return new Promise((resolve, reject) => {
3886
+ const parsed = new URL(url);
3887
+ const req = https.request({
3888
+ hostname: parsed.hostname,
3889
+ path: parsed.pathname + parsed.search,
3890
+ method: 'POST',
3891
+ agent,
3892
+ headers: {
3893
+ 'Content-Type': 'application/json',
3894
+ 'Content-Length': Buffer.byteLength(body),
3895
+ },
3896
+ }, (res) => {
3897
+ const chunks = [];
3898
+ res.on('data', (chunk) => chunks.push(chunk));
3899
+ res.on('end', () => {
3900
+ resolve({
3901
+ status: res.statusCode ?? 0,
3902
+ body: Buffer.concat(chunks).toString('utf8'),
3903
+ });
3904
+ });
3905
+ });
3906
+ req.on('error', reject);
3907
+ req.write(body);
3908
+ req.end();
3909
+ });
3910
+ }
3878
3911
  /**
3879
- * Create a Gemini embedding provider using the Google Generative AI SDK.
3912
+ * Create a Gemini embedding provider using the Google Generative AI REST API.
3880
3913
  *
3881
3914
  * @param config - The embedding configuration.
3882
3915
  * @param logger - Optional pino logger for retry warnings.
@@ -3888,20 +3921,27 @@ function createGeminiProvider(config, logger) {
3888
3921
  throw new Error('Gemini embedding provider requires config.embedding.apiKey');
3889
3922
  }
3890
3923
  const dimensions = config.dimensions ?? 3072;
3924
+ const model = config.model;
3925
+ const apiKey = config.apiKey;
3891
3926
  const log = getLogger(logger);
3892
- const embedder = new GoogleGenerativeAIEmbeddings({
3893
- apiKey: config.apiKey,
3894
- model: config.model,
3895
- });
3927
+ const url = `${GEMINI_API_BASE}/models/${model}:batchEmbedContents?key=${apiKey}`;
3896
3928
  return {
3897
3929
  dimensions,
3898
3930
  async embed(texts) {
3899
3931
  const vectors = await retry(async (attempt) => {
3900
3932
  if (attempt > 1) {
3901
- log.warn({ attempt, provider: 'gemini', model: config.model }, 'Retrying embedding request');
3933
+ log.warn({ attempt, provider: 'gemini', model }, 'Retrying embedding request');
3934
+ }
3935
+ const requests = texts.map((text) => ({
3936
+ model: `models/${model}`,
3937
+ content: { parts: [{ text }] },
3938
+ }));
3939
+ const response = await httpsPost(url, JSON.stringify({ requests }));
3940
+ if (response.status < 200 || response.status >= 300) {
3941
+ throw new Error(`Gemini API error ${String(response.status)}: ${response.body}`);
3902
3942
  }
3903
- // embedDocuments returns vectors for multiple texts
3904
- return embedder.embedDocuments(texts);
3943
+ const data = JSON.parse(response.body);
3944
+ return data.embeddings.map((e) => e.values);
3905
3945
  }, {
3906
3946
  attempts: 5,
3907
3947
  baseDelayMs: 500,
@@ -3912,7 +3952,7 @@ function createGeminiProvider(config, logger) {
3912
3952
  attempt,
3913
3953
  delayMs,
3914
3954
  provider: 'gemini',
3915
- model: config.model,
3955
+ model,
3916
3956
  err: normalizeError(error),
3917
3957
  }, 'Embedding call failed; will retry');
3918
3958
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Filesystem watcher that keeps a Qdrant vector store in sync with document changes",
6
6
  "license": "BSD-3-Clause",
@@ -52,8 +52,6 @@
52
52
  "dependencies": {
53
53
  "@commander-js/extra-typings": "^14.0.0",
54
54
  "@karmaniverous/jsonmap": "^2.1.1",
55
- "@langchain/google-genai": "*",
56
- "@langchain/qdrant": "*",
57
55
  "@langchain/textsplitters": "*",
58
56
  "@qdrant/js-client-rest": "*",
59
57
  "ajv": "^8.18.0",