@betterdb/semantic-cache 0.6.0 → 0.8.0

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.
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026-present BetterDB Inc.
2
+
3
+ Portions of this software are licensed as follows:
4
+
5
+ - All content residing under the "doc/" directory of this repository is licensed under the "Creative Commons: CC BY-SA 4.0 license".
6
+
7
+ - All content that resides under the "proprietary/" directory of this repository, if that directory exists, is licensed under the license defined in "proprietary/LICENSE".
8
+
9
+ - All third-party components incorporated into the BetterDB Software are licensed under the original license provided by the owner of the applicable component.
10
+
11
+ - Content outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
12
+
13
+ MIT License
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -175,8 +175,6 @@ export declare class SemanticCache {
175
175
  private applyCostToPendingMiss;
176
176
  private assertInitialized;
177
177
  private assertDimension;
178
- private isIndexNotFoundError;
179
- private parseDimensionFromInfo;
180
178
  }
181
179
  export interface ThresholdEffectivenessResult {
182
180
  category: string;
@@ -6,6 +6,7 @@ const node_crypto_2 = require("node:crypto");
6
6
  const api_1 = require("@opentelemetry/api");
7
7
  const errors_1 = require("./errors");
8
8
  const telemetry_1 = require("./telemetry");
9
+ const valkey_search_kit_1 = require("@betterdb/valkey-search-kit");
9
10
  const utils_1 = require("./utils");
10
11
  const defaultCostTable_1 = require("./defaultCostTable");
11
12
  const cluster_1 = require("./cluster");
@@ -151,7 +152,7 @@ class SemanticCache {
151
152
  await this.client.call('FT.DROPINDEX', this.indexName);
152
153
  }
153
154
  catch (err) {
154
- if (!this.isIndexNotFoundError(err)) {
155
+ if (!(0, valkey_search_kit_1.isIndexNotFoundError)(err)) {
155
156
  throw new errors_1.ValkeyCommandError('FT.DROPINDEX', err);
156
157
  }
157
158
  }
@@ -833,16 +834,7 @@ class SemanticCache {
833
834
  catch (err) {
834
835
  throw new errors_1.ValkeyCommandError('FT.INFO', err);
835
836
  }
836
- const info = raw;
837
- let numDocs = 0;
838
- let indexingState = 'unknown';
839
- for (let i = 0; i < info.length - 1; i += 2) {
840
- const key = String(info[i]);
841
- if (key === 'num_docs')
842
- numDocs = parseInt(String(info[i + 1]), 10) || 0;
843
- else if (key === 'indexing')
844
- indexingState = String(info[i + 1]);
845
- }
837
+ const { numDocs, indexingState } = (0, valkey_search_kit_1.parseFtInfoStats)(raw);
846
838
  return { name: this.indexName, numDocs, dimension: this._dimension, indexingState };
847
839
  }
848
840
  /**
@@ -1173,7 +1165,7 @@ class SemanticCache {
1173
1165
  // Try reading an existing index
1174
1166
  try {
1175
1167
  const info = (await this.client.call('FT.INFO', this.indexName));
1176
- const dim = this.parseDimensionFromInfo(info);
1168
+ const dim = (0, valkey_search_kit_1.parseDimensionFromInfo)(info);
1177
1169
  const hasBinaryRefs = this.parseHasBinaryRefsFromInfo(info);
1178
1170
  if (dim > 0)
1179
1171
  return { dim, hasBinaryRefs };
@@ -1184,7 +1176,7 @@ class SemanticCache {
1184
1176
  catch (err) {
1185
1177
  if (err instanceof errors_1.EmbeddingError)
1186
1178
  throw err;
1187
- if (!this.isIndexNotFoundError(err)) {
1179
+ if (!(0, valkey_search_kit_1.isIndexNotFoundError)(err)) {
1188
1180
  throw new errors_1.ValkeyCommandError('FT.INFO', err);
1189
1181
  }
1190
1182
  }
@@ -1417,49 +1409,6 @@ class SemanticCache {
1417
1409
  throw new errors_1.SemanticCacheUsageError(`Embedding dimension mismatch: index expects ${this._dimension}, embedFn returned ${embedding.length}. Call flush() then initialize() to rebuild.`);
1418
1410
  }
1419
1411
  }
1420
- isIndexNotFoundError(err) {
1421
- const msg = err instanceof Error ? err.message.toLowerCase() : '';
1422
- return (msg.includes('unknown index name') ||
1423
- msg.includes('no such index') ||
1424
- msg.includes('not found'));
1425
- }
1426
- parseDimensionFromInfo(info) {
1427
- for (let i = 0; i < info.length - 1; i += 2) {
1428
- const key = String(info[i]);
1429
- if (key !== 'attributes' && key !== 'fields')
1430
- continue;
1431
- const attributes = info[i + 1];
1432
- if (!Array.isArray(attributes))
1433
- continue;
1434
- for (const attr of attributes) {
1435
- if (!Array.isArray(attr))
1436
- continue;
1437
- let isVector = false;
1438
- let dim = 0;
1439
- for (let j = 0; j < attr.length - 1; j++) {
1440
- const attrKey = String(attr[j]);
1441
- if (attrKey === 'type' && String(attr[j + 1]) === 'VECTOR')
1442
- isVector = true;
1443
- if (attrKey.toLowerCase() === 'dim')
1444
- dim = parseInt(String(attr[j + 1]), 10) || 0;
1445
- // Valkey Search 1.2 nests dimension inside an 'index' sub-array
1446
- if (attrKey === 'index' && Array.isArray(attr[j + 1])) {
1447
- const indexArr = attr[j + 1];
1448
- for (let k = 0; k < indexArr.length - 1; k++) {
1449
- if (String(indexArr[k]) === 'dimensions') {
1450
- const d = parseInt(String(indexArr[k + 1]), 10) || 0;
1451
- if (d > 0)
1452
- dim = d;
1453
- }
1454
- }
1455
- }
1456
- }
1457
- if (isVector && dim > 0)
1458
- return dim;
1459
- }
1460
- }
1461
- return 0;
1462
- }
1463
1412
  }
1464
1413
  exports.SemanticCache = SemanticCache;
1465
1414
  // --- Judge helpers ---
@@ -11,14 +11,35 @@ export interface ValkeyLike {
11
11
  export interface Analytics {
12
12
  init(client: ValkeyLike, name: string, configProps?: Record<string, unknown>): Promise<void>;
13
13
  capture(event: string, properties?: Record<string, unknown>): void;
14
+ flush(): Promise<void>;
14
15
  shutdown(): Promise<void>;
15
16
  }
16
17
  export interface AnalyticsOptions {
17
- apiKey?: string;
18
- host?: string;
19
18
  disabled?: boolean;
20
19
  /** Interval in ms for periodic stats snapshots. Default: 300_000 (5 min). 0 to disable. */
21
20
  statsIntervalMs?: number;
22
21
  }
23
22
  export declare const NOOP_ANALYTICS: Analytics;
23
+ type PostHogClient = {
24
+ capture: (opts: {
25
+ distinctId?: string;
26
+ event: string;
27
+ properties?: Record<string, unknown>;
28
+ }) => void;
29
+ flush: () => Promise<void>;
30
+ shutdown: () => Promise<void>;
31
+ };
32
+ export declare class PostHogAnalytics implements Analytics {
33
+ private posthog;
34
+ private distinctId;
35
+ private deploymentId;
36
+ private readonly flushOnExit;
37
+ constructor(posthog: PostHogClient);
38
+ init(client: ValkeyLike, name: string, configProps?: Record<string, unknown>): Promise<void>;
39
+ private resolveDeploymentId;
40
+ capture(event: string, properties?: Record<string, unknown>): void;
41
+ flush(): Promise<void>;
42
+ shutdown(): Promise<void>;
43
+ }
24
44
  export declare function createAnalytics(opts?: AnalyticsOptions): Promise<Analytics>;
45
+ export {};
package/dist/analytics.js CHANGED
@@ -39,8 +39,11 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.NOOP_ANALYTICS = void 0;
42
+ exports.PostHogAnalytics = exports.NOOP_ANALYTICS = void 0;
43
43
  exports.createAnalytics = createAnalytics;
44
+ const node_fs_1 = require("node:fs");
45
+ const node_os_1 = require("node:os");
46
+ const node_path_1 = require("node:path");
44
47
  const EVENT_PREFIX = 'semantic_cache:';
45
48
  // Build-time placeholders — replaced by scripts/inject-telemetry-defaults.mjs
46
49
  // When the placeholder is NOT replaced, the startsWith('__') guard treats it as unset.
@@ -49,41 +52,126 @@ const BAKED_POSTHOG_HOST = '__BETTERDB_POSTHOG_HOST__';
49
52
  exports.NOOP_ANALYTICS = {
50
53
  async init() { },
51
54
  capture() { },
55
+ async flush() { },
52
56
  async shutdown() { },
53
57
  };
54
58
  function isTelemetryOptedOut() {
55
59
  const val = process.env.BETTERDB_TELEMETRY;
56
60
  return val !== undefined && ['false', '0', 'no', 'off'].includes(val.toLowerCase());
57
61
  }
62
+ const INSTALL_ID_ENV = 'BETTERDB_INSTANCE_ID';
63
+ // Holds a minted id for the rest of the process when persistence fails, so
64
+ // repeated calls (or parallel init) return one stable ephemeral identity.
65
+ let ephemeralInstallId;
66
+ function installIdPath() {
67
+ const base = process.env.XDG_STATE_HOME;
68
+ const root = base ? base : (0, node_path_1.join)((0, node_os_1.homedir)(), '.betterdb');
69
+ return (0, node_path_1.join)(root, 'instance_id');
70
+ }
71
+ /**
72
+ * Stable per-install identity for product analytics. Persisted on the local
73
+ * machine (not in Valkey), so a fleet of processes sharing one Valkey is
74
+ * counted as many installs rather than collapsing to one. Pin it via
75
+ * BETTERDB_INSTANCE_ID for ephemeral containers that would otherwise mint a
76
+ * fresh id every run. Falls back to an ephemeral per-process id when no
77
+ * writable location is available.
78
+ */
79
+ function getInstallId() {
80
+ const override = process.env[INSTALL_ID_ENV];
81
+ if (override)
82
+ return override;
83
+ const path = installIdPath();
84
+ try {
85
+ const existing = (0, node_fs_1.readFileSync)(path, 'utf8').trim();
86
+ if (existing)
87
+ return existing;
88
+ }
89
+ catch {
90
+ // no existing id
91
+ }
92
+ const newId = ephemeralInstallId ?? crypto.randomUUID();
93
+ try {
94
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
95
+ (0, node_fs_1.writeFileSync)(path, newId);
96
+ }
97
+ catch {
98
+ // Persistence failed — hold the id for the rest of this process so
99
+ // repeated calls return a stable ephemeral identity.
100
+ ephemeralInstallId = newId;
101
+ }
102
+ return newId;
103
+ }
58
104
  class PostHogAnalytics {
59
105
  posthog;
60
106
  distinctId = '';
107
+ deploymentId = '';
108
+ // Library consumers are frequently short-lived scripts that never call
109
+ // shutdown(), so PostHog's buffered events (flushAt=20, flushInterval=10s)
110
+ // would be dropped when the process exits before the queue drains. Flush
111
+ // when the event loop empties so lifecycle events are actually delivered.
112
+ // Only enabled instances reach here — the opt-out path returns
113
+ // NOOP_ANALYTICS and registers nothing, keeping disabled consumers silent.
114
+ flushOnExit = () => {
115
+ void this.flush();
116
+ };
61
117
  constructor(posthog) {
62
118
  this.posthog = posthog;
119
+ process.once('beforeExit', this.flushOnExit);
63
120
  }
64
121
  async init(client, name, configProps) {
122
+ this.distinctId = getInstallId();
123
+ this.deploymentId = await this.resolveDeploymentId(client, name);
124
+ const merged = { ...(configProps ?? {}) };
125
+ if (this.deploymentId)
126
+ merged.deployment_id = this.deploymentId;
127
+ this.capture('cache_init', merged);
128
+ // Flush the start event immediately so it lands even for processes that exit
129
+ // before the flush interval or the beforeExit hook fires.
130
+ await this.flush();
131
+ }
132
+ async resolveDeploymentId(client, name) {
133
+ // The Valkey-scoped id groups all clients pointed at the same store, so a
134
+ // shared-Valkey fleet can still be rolled up into one deployment.
65
135
  const idKey = `${name}:__instance_id`;
66
- let id = await client.get(idKey);
67
- if (!id) {
68
- id = crypto.randomUUID();
136
+ try {
137
+ const existing = await client.get(idKey);
138
+ if (existing)
139
+ return existing;
140
+ const id = crypto.randomUUID();
69
141
  await client.set(idKey, id);
142
+ return id;
143
+ }
144
+ catch {
145
+ return '';
70
146
  }
71
- this.distinctId = id;
72
- this.capture('cache_init', configProps);
73
147
  }
74
148
  capture(event, properties) {
75
149
  try {
150
+ const props = { ...(properties ?? {}) };
151
+ if (this.deploymentId && props.deployment_id === undefined) {
152
+ props.deployment_id = this.deploymentId;
153
+ }
76
154
  this.posthog.capture({
77
155
  distinctId: this.distinctId,
78
156
  event: `${EVENT_PREFIX}${event}`,
79
- properties,
157
+ properties: props,
80
158
  });
81
159
  }
82
160
  catch {
83
161
  // never throw from analytics
84
162
  }
85
163
  }
164
+ async flush() {
165
+ try {
166
+ await this.posthog.flush();
167
+ }
168
+ catch {
169
+ // swallow
170
+ }
171
+ }
86
172
  async shutdown() {
173
+ // Explicit shutdown supersedes the beforeExit backstop.
174
+ process.removeListener('beforeExit', this.flushOnExit);
87
175
  try {
88
176
  await this.posthog.shutdown();
89
177
  }
@@ -92,18 +180,16 @@ class PostHogAnalytics {
92
180
  }
93
181
  }
94
182
  }
183
+ exports.PostHogAnalytics = PostHogAnalytics;
95
184
  async function createAnalytics(opts) {
96
185
  if (opts?.disabled || isTelemetryOptedOut()) {
97
186
  return exports.NOOP_ANALYTICS;
98
187
  }
99
- // Key resolution: opts.apiKey BETTERDB_POSTHOG_API_KEY env var → baked wheel value
100
- const bakedKey = BAKED_POSTHOG_API_KEY.startsWith('__') ? undefined : BAKED_POSTHOG_API_KEY;
101
- const apiKey = opts?.apiKey ?? process.env.BETTERDB_POSTHOG_API_KEY ?? bakedKey;
188
+ const apiKey = BAKED_POSTHOG_API_KEY.startsWith('__') ? undefined : BAKED_POSTHOG_API_KEY;
102
189
  if (!apiKey) {
103
190
  return exports.NOOP_ANALYTICS;
104
191
  }
105
- const bakedHost = BAKED_POSTHOG_HOST.startsWith('__') ? undefined : BAKED_POSTHOG_HOST;
106
- const host = opts?.host ?? process.env.BETTERDB_POSTHOG_HOST ?? bakedHost;
192
+ const host = BAKED_POSTHOG_HOST.startsWith('__') ? undefined : BAKED_POSTHOG_HOST;
107
193
  try {
108
194
  // @ts-ignore — posthog-node is an optional peer dep
109
195
  const { PostHog } = await Promise.resolve().then(() => __importStar(require('posthog-node')));
package/dist/types.d.ts CHANGED
@@ -90,10 +90,6 @@ export interface SemanticCacheOptions {
90
90
  registry?: Registry;
91
91
  };
92
92
  analytics?: {
93
- /** PostHog API key. Overrides the build-time baked key if set. */
94
- apiKey?: string;
95
- /** PostHog host. Overrides the build-time baked host if set. */
96
- host?: string;
97
93
  /** Disable analytics. Also controlled by BETTERDB_TELEMETRY env var. */
98
94
  disabled?: boolean;
99
95
  /** Interval in ms for periodic stats snapshots. Default: 300_000 (5 min). 0 to disable. */
package/dist/utils.d.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  /** SHA-256 hex digest of a string. */
2
2
  export declare function sha256(text: string): string;
3
- /** Escape a string for safe use as a Valkey Search TAG filter value.
4
- * Spaces are included because Valkey Search treats unescaped spaces as term
5
- * separators (OR semantics), which would broaden the filter unintentionally.
6
- */
7
- export declare function escapeTag(value: string): string;
3
+ export { escapeTag, encodeFloat32, parseFtSearchResponse } from '@betterdb/valkey-search-kit';
8
4
  export type ContentBlock = TextBlock | BinaryBlock | ToolCallBlock | ToolResultBlock | ReasoningBlock;
9
5
  export interface TextBlock {
10
6
  type: 'text';
@@ -58,26 +54,3 @@ export declare function extractText(blocks: ContentBlock[]): string;
58
54
  * Used for the binary_refs TAG field on cache entries.
59
55
  */
60
56
  export declare function extractBinaryRefs(blocks: ContentBlock[]): string[];
61
- /**
62
- * Encode number[] as a little-endian Float32 Buffer.
63
- * Used to store embeddings as binary HSET field values.
64
- */
65
- export declare function encodeFloat32(vec: number[]): Buffer;
66
- /**
67
- * Parse a raw FT.SEARCH response from iovalkey's client.call().
68
- *
69
- * iovalkey returns FT.SEARCH results in the following shape:
70
- * [totalCount, key1, [field1, val1, field2, val2, ...], key2, [...], ...]
71
- *
72
- * - totalCount is a string (e.g. "2")
73
- * - Each key is a string
74
- * - Each field list is a flat string array: [fieldName, value, fieldName, value, ...]
75
- *
76
- * Returns an array of { key: string, fields: Record<string, string> }.
77
- * Returns [] if totalCount is "0" or the response is empty/malformed.
78
- * Never throws — on any parse error, returns [].
79
- */
80
- export declare function parseFtSearchResponse(raw: unknown): Array<{
81
- key: string;
82
- fields: Record<string, string>;
83
- }>;
package/dist/utils.js CHANGED
@@ -1,23 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseFtSearchResponse = exports.encodeFloat32 = exports.escapeTag = void 0;
3
4
  exports.sha256 = sha256;
4
- exports.escapeTag = escapeTag;
5
5
  exports.extractText = extractText;
6
6
  exports.extractBinaryRefs = extractBinaryRefs;
7
- exports.encodeFloat32 = encodeFloat32;
8
- exports.parseFtSearchResponse = parseFtSearchResponse;
9
7
  const node_crypto_1 = require("node:crypto");
10
8
  /** SHA-256 hex digest of a string. */
11
9
  function sha256(text) {
12
10
  return (0, node_crypto_1.createHash)('sha256').update(text).digest('hex');
13
11
  }
14
- /** Escape a string for safe use as a Valkey Search TAG filter value.
15
- * Spaces are included because Valkey Search treats unescaped spaces as term
16
- * separators (OR semantics), which would broaden the filter unintentionally.
17
- */
18
- function escapeTag(value) {
19
- return value.replace(/[,.<>{}[\]"':;!@#$%^&*()\-+=~|/\\ ]/g, '\\$&');
20
- }
12
+ var valkey_search_kit_1 = require("@betterdb/valkey-search-kit");
13
+ Object.defineProperty(exports, "escapeTag", { enumerable: true, get: function () { return valkey_search_kit_1.escapeTag; } });
14
+ Object.defineProperty(exports, "encodeFloat32", { enumerable: true, get: function () { return valkey_search_kit_1.encodeFloat32; } });
15
+ Object.defineProperty(exports, "parseFtSearchResponse", { enumerable: true, get: function () { return valkey_search_kit_1.parseFtSearchResponse; } });
21
16
  /**
22
17
  * Extract all text from a ContentBlock array, joining TextBlock.text values with a space.
23
18
  * Used to derive the embedding text from a multi-modal prompt.
@@ -38,70 +33,3 @@ function extractBinaryRefs(blocks) {
38
33
  .map((b) => b.ref)
39
34
  .sort();
40
35
  }
41
- /**
42
- * Encode number[] as a little-endian Float32 Buffer.
43
- * Used to store embeddings as binary HSET field values.
44
- */
45
- function encodeFloat32(vec) {
46
- const buf = Buffer.alloc(vec.length * 4);
47
- for (let i = 0; i < vec.length; i++) {
48
- buf.writeFloatLE(vec[i], i * 4);
49
- }
50
- return buf;
51
- }
52
- /**
53
- * Parse a raw FT.SEARCH response from iovalkey's client.call().
54
- *
55
- * iovalkey returns FT.SEARCH results in the following shape:
56
- * [totalCount, key1, [field1, val1, field2, val2, ...], key2, [...], ...]
57
- *
58
- * - totalCount is a string (e.g. "2")
59
- * - Each key is a string
60
- * - Each field list is a flat string array: [fieldName, value, fieldName, value, ...]
61
- *
62
- * Returns an array of { key: string, fields: Record<string, string> }.
63
- * Returns [] if totalCount is "0" or the response is empty/malformed.
64
- * Never throws — on any parse error, returns [].
65
- */
66
- function parseFtSearchResponse(raw) {
67
- try {
68
- if (!Array.isArray(raw) || raw.length < 1) {
69
- return [];
70
- }
71
- const totalCount = typeof raw[0] === 'string' ? parseInt(raw[0], 10) : Number(raw[0]);
72
- if (!totalCount || totalCount <= 0) {
73
- return [];
74
- }
75
- const results = [];
76
- let i = 1;
77
- while (i < raw.length) {
78
- const key = raw[i];
79
- if (typeof key !== 'string') {
80
- i++;
81
- continue;
82
- }
83
- const fieldList = raw[i + 1];
84
- const fields = {};
85
- if (Array.isArray(fieldList)) {
86
- const len = fieldList.length - (fieldList.length % 2);
87
- for (let j = 0; j < len; j += 2) {
88
- const fieldName = String(fieldList[j]);
89
- const fieldValue = String(fieldList[j + 1]);
90
- fields[fieldName] = fieldValue;
91
- }
92
- i += 2;
93
- }
94
- else {
95
- // No field list follows the key (e.g. RETURN 0 mode)
96
- results.push({ key, fields });
97
- i++;
98
- continue;
99
- }
100
- results.push({ key, fields });
101
- }
102
- return results;
103
- }
104
- catch {
105
- return [];
106
- }
107
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterdb/semantic-cache",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Valkey-native semantic cache for LLM applications with built-in OpenTelemetry and Prometheus instrumentation",
5
5
  "keywords": [
6
6
  "valkey",
@@ -93,18 +93,11 @@
93
93
  "dist",
94
94
  "README.md"
95
95
  ],
96
- "scripts": {
97
- "build": "tsc && node scripts/inject-telemetry-defaults.mjs",
98
- "typecheck": "tsc --noEmit",
99
- "test": "vitest run",
100
- "test:watch": "vitest",
101
- "clean": "rm -rf dist",
102
- "update:pricing": "node scripts/update-model-prices.mjs"
103
- },
104
96
  "dependencies": {
105
97
  "@opentelemetry/api": "^1.9.0",
106
98
  "posthog-node": ">=4.0.0",
107
- "prom-client": "^15.1.3"
99
+ "prom-client": "^15.1.3",
100
+ "@betterdb/valkey-search-kit": "0.1.0"
108
101
  },
109
102
  "engines": {
110
103
  "node": ">=20.0.0"
@@ -137,5 +130,13 @@
137
130
  "openai": {
138
131
  "optional": true
139
132
  }
133
+ },
134
+ "scripts": {
135
+ "build": "tsc && node scripts/inject-telemetry-defaults.mjs",
136
+ "typecheck": "tsc --noEmit",
137
+ "test": "vitest run",
138
+ "test:watch": "vitest",
139
+ "clean": "rm -rf dist",
140
+ "update:pricing": "node scripts/update-model-prices.mjs"
140
141
  }
141
- }
142
+ }