@karmaniverous/jeeves-watcher 0.9.1 → 0.9.3

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,6 @@ 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';
29
28
  import pino from 'pino';
30
29
  import { v5 } from 'uuid';
31
30
  import * as cheerio from 'cheerio';
@@ -2785,6 +2784,16 @@ function isFacetable(prop) {
2785
2784
  return false;
2786
2785
  return prop.uiHint !== undefined || prop.enum !== undefined;
2787
2786
  }
2787
+ /** uiHint types that represent enumerated value selection. */
2788
+ const ENUMERATED_HINTS = new Set(['dropdown', 'tags', 'select', 'multiselect']);
2789
+ /**
2790
+ * Check whether a uiHint type supports value enumeration.
2791
+ * Non-enumerated hints (text, number, date, range, etc.) should not
2792
+ * aggregate live values — the client uses free-form input instead.
2793
+ */
2794
+ function isEnumeratedHint(uiHint) {
2795
+ return ENUMERATED_HINTS.has(uiHint);
2796
+ }
2788
2797
  /**
2789
2798
  * Build the schema-derived facet structure from inference rules.
2790
2799
  *
@@ -2846,20 +2855,31 @@ function createFacetsHandler(deps) {
2846
2855
  const allValues = valuesManager.getAll();
2847
2856
  const facets = [];
2848
2857
  for (const [field, schema] of cached.fields) {
2849
- // Collect live values from all rules that define this field
2850
- const liveValues = new Set();
2851
- for (const ruleName of schema.rules) {
2852
- const fieldValues = allValues[ruleName]?.[field];
2853
- if (fieldValues) {
2854
- for (const v of fieldValues)
2855
- liveValues.add(v);
2858
+ // Only aggregate live values for enumerated hint types (dropdown, tags, etc.)
2859
+ // Non-enumerated types (text, number, date, range) use free-form input.
2860
+ let values;
2861
+ if (schema.enumValues) {
2862
+ values = schema.enumValues;
2863
+ }
2864
+ else if (isEnumeratedHint(schema.uiHint)) {
2865
+ const liveValues = new Set();
2866
+ for (const ruleName of schema.rules) {
2867
+ const fieldValues = allValues[ruleName]?.[field];
2868
+ if (fieldValues) {
2869
+ for (const v of fieldValues)
2870
+ liveValues.add(v);
2871
+ }
2856
2872
  }
2873
+ values = [...liveValues].sort();
2874
+ }
2875
+ else {
2876
+ values = [];
2857
2877
  }
2858
2878
  facets.push({
2859
2879
  field,
2860
2880
  type: schema.type,
2861
2881
  uiHint: schema.uiHint,
2862
- values: schema.enumValues ?? [...liveValues].sort(),
2882
+ values,
2863
2883
  rules: schema.rules,
2864
2884
  });
2865
2885
  }
@@ -3876,10 +3896,11 @@ function getLogger(logger) {
3876
3896
 
3877
3897
  /**
3878
3898
  * @module embedding/geminiProvider
3879
- * Gemini embedding provider using Google Generative AI.
3899
+ * Gemini embedding provider using the Google Generative AI REST API directly.
3880
3900
  */
3901
+ const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
3881
3902
  /**
3882
- * Create a Gemini embedding provider using the Google Generative AI SDK.
3903
+ * Create a Gemini embedding provider using the Google Generative AI REST API.
3883
3904
  *
3884
3905
  * @param config - The embedding configuration.
3885
3906
  * @param logger - Optional pino logger for retry warnings.
@@ -3891,20 +3912,32 @@ function createGeminiProvider(config, logger) {
3891
3912
  throw new Error('Gemini embedding provider requires config.embedding.apiKey');
3892
3913
  }
3893
3914
  const dimensions = config.dimensions ?? 3072;
3915
+ const model = config.model;
3916
+ const apiKey = config.apiKey;
3894
3917
  const log = getLogger(logger);
3895
- const embedder = new GoogleGenerativeAIEmbeddings({
3896
- apiKey: config.apiKey,
3897
- model: config.model,
3898
- });
3918
+ const url = `${GEMINI_API_BASE}/models/${model}:batchEmbedContents?key=${apiKey}`;
3899
3919
  return {
3900
3920
  dimensions,
3901
3921
  async embed(texts) {
3902
3922
  const vectors = await retry(async (attempt) => {
3903
3923
  if (attempt > 1) {
3904
- log.warn({ attempt, provider: 'gemini', model: config.model }, 'Retrying embedding request');
3924
+ log.warn({ attempt, provider: 'gemini', model }, 'Retrying embedding request');
3925
+ }
3926
+ const requests = texts.map((text) => ({
3927
+ model: `models/${model}`,
3928
+ content: { parts: [{ text }] },
3929
+ }));
3930
+ const response = await fetch(url, {
3931
+ method: 'POST',
3932
+ headers: { 'Content-Type': 'application/json' },
3933
+ body: JSON.stringify({ requests }),
3934
+ });
3935
+ if (!response.ok) {
3936
+ const body = await response.text();
3937
+ throw new Error(`Gemini API error ${String(response.status)}: ${body}`);
3905
3938
  }
3906
- // embedDocuments returns vectors for multiple texts
3907
- return embedder.embedDocuments(texts);
3939
+ const data = (await response.json());
3940
+ return data.embeddings.map((e) => e.values);
3908
3941
  }, {
3909
3942
  attempts: 5,
3910
3943
  baseDelayMs: 500,
@@ -3915,7 +3948,7 @@ function createGeminiProvider(config, logger) {
3915
3948
  attempt,
3916
3949
  delayMs,
3917
3950
  provider: 'gemini',
3918
- model: config.model,
3951
+ model,
3919
3952
  err: normalizeError(error),
3920
3953
  }, 'Embedding call failed; will retry');
3921
3954
  },
package/dist/index.js CHANGED
@@ -22,7 +22,6 @@ 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';
26
25
  import pino from 'pino';
27
26
  import { v5 } from 'uuid';
28
27
  import * as cheerio from 'cheerio';
@@ -2471,6 +2470,16 @@ function isFacetable(prop) {
2471
2470
  return false;
2472
2471
  return prop.uiHint !== undefined || prop.enum !== undefined;
2473
2472
  }
2473
+ /** uiHint types that represent enumerated value selection. */
2474
+ const ENUMERATED_HINTS = new Set(['dropdown', 'tags', 'select', 'multiselect']);
2475
+ /**
2476
+ * Check whether a uiHint type supports value enumeration.
2477
+ * Non-enumerated hints (text, number, date, range, etc.) should not
2478
+ * aggregate live values — the client uses free-form input instead.
2479
+ */
2480
+ function isEnumeratedHint(uiHint) {
2481
+ return ENUMERATED_HINTS.has(uiHint);
2482
+ }
2474
2483
  /**
2475
2484
  * Build the schema-derived facet structure from inference rules.
2476
2485
  *
@@ -2532,20 +2541,31 @@ function createFacetsHandler(deps) {
2532
2541
  const allValues = valuesManager.getAll();
2533
2542
  const facets = [];
2534
2543
  for (const [field, schema] of cached.fields) {
2535
- // Collect live values from all rules that define this field
2536
- const liveValues = new Set();
2537
- for (const ruleName of schema.rules) {
2538
- const fieldValues = allValues[ruleName]?.[field];
2539
- if (fieldValues) {
2540
- for (const v of fieldValues)
2541
- liveValues.add(v);
2544
+ // Only aggregate live values for enumerated hint types (dropdown, tags, etc.)
2545
+ // Non-enumerated types (text, number, date, range) use free-form input.
2546
+ let values;
2547
+ if (schema.enumValues) {
2548
+ values = schema.enumValues;
2549
+ }
2550
+ else if (isEnumeratedHint(schema.uiHint)) {
2551
+ const liveValues = new Set();
2552
+ for (const ruleName of schema.rules) {
2553
+ const fieldValues = allValues[ruleName]?.[field];
2554
+ if (fieldValues) {
2555
+ for (const v of fieldValues)
2556
+ liveValues.add(v);
2557
+ }
2542
2558
  }
2559
+ values = [...liveValues].sort();
2560
+ }
2561
+ else {
2562
+ values = [];
2543
2563
  }
2544
2564
  facets.push({
2545
2565
  field,
2546
2566
  type: schema.type,
2547
2567
  uiHint: schema.uiHint,
2548
- values: schema.enumValues ?? [...liveValues].sort(),
2568
+ values,
2549
2569
  rules: schema.rules,
2550
2570
  });
2551
2571
  }
@@ -3852,10 +3872,11 @@ function getLogger(logger) {
3852
3872
 
3853
3873
  /**
3854
3874
  * @module embedding/geminiProvider
3855
- * Gemini embedding provider using Google Generative AI.
3875
+ * Gemini embedding provider using the Google Generative AI REST API directly.
3856
3876
  */
3877
+ const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
3857
3878
  /**
3858
- * Create a Gemini embedding provider using the Google Generative AI SDK.
3879
+ * Create a Gemini embedding provider using the Google Generative AI REST API.
3859
3880
  *
3860
3881
  * @param config - The embedding configuration.
3861
3882
  * @param logger - Optional pino logger for retry warnings.
@@ -3867,20 +3888,32 @@ function createGeminiProvider(config, logger) {
3867
3888
  throw new Error('Gemini embedding provider requires config.embedding.apiKey');
3868
3889
  }
3869
3890
  const dimensions = config.dimensions ?? 3072;
3891
+ const model = config.model;
3892
+ const apiKey = config.apiKey;
3870
3893
  const log = getLogger(logger);
3871
- const embedder = new GoogleGenerativeAIEmbeddings({
3872
- apiKey: config.apiKey,
3873
- model: config.model,
3874
- });
3894
+ const url = `${GEMINI_API_BASE}/models/${model}:batchEmbedContents?key=${apiKey}`;
3875
3895
  return {
3876
3896
  dimensions,
3877
3897
  async embed(texts) {
3878
3898
  const vectors = await retry(async (attempt) => {
3879
3899
  if (attempt > 1) {
3880
- log.warn({ attempt, provider: 'gemini', model: config.model }, 'Retrying embedding request');
3900
+ log.warn({ attempt, provider: 'gemini', model }, 'Retrying embedding request');
3901
+ }
3902
+ const requests = texts.map((text) => ({
3903
+ model: `models/${model}`,
3904
+ content: { parts: [{ text }] },
3905
+ }));
3906
+ const response = await fetch(url, {
3907
+ method: 'POST',
3908
+ headers: { 'Content-Type': 'application/json' },
3909
+ body: JSON.stringify({ requests }),
3910
+ });
3911
+ if (!response.ok) {
3912
+ const body = await response.text();
3913
+ throw new Error(`Gemini API error ${String(response.status)}: ${body}`);
3881
3914
  }
3882
- // embedDocuments returns vectors for multiple texts
3883
- return embedder.embedDocuments(texts);
3915
+ const data = (await response.json());
3916
+ return data.embeddings.map((e) => e.values);
3884
3917
  }, {
3885
3918
  attempts: 5,
3886
3919
  baseDelayMs: 500,
@@ -3891,7 +3924,7 @@ function createGeminiProvider(config, logger) {
3891
3924
  attempt,
3892
3925
  delayMs,
3893
3926
  provider: 'gemini',
3894
- model: config.model,
3927
+ model,
3895
3928
  err: normalizeError(error),
3896
3929
  }, 'Embedding call failed; will retry');
3897
3930
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
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",