warm-sdk 0.0.0-stage → 0.2.1
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 +201 -0
- package/README.md +137 -2
- package/dist/index.cjs +676 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +653 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MemoryClient: () => MemoryClient,
|
|
24
|
+
WarmClient: () => WarmClient,
|
|
25
|
+
WarmError: () => WarmError
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/validation.ts
|
|
30
|
+
var MAX_BYTES = 16 * 1024 * 1024;
|
|
31
|
+
var JsonSizeError = class extends RangeError {
|
|
32
|
+
};
|
|
33
|
+
var encoder = new TextEncoder();
|
|
34
|
+
var byteLength = (value) => encoder.encode(value).byteLength;
|
|
35
|
+
function string(value, name, max = MAX_BYTES, nonempty = false) {
|
|
36
|
+
if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
|
|
37
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
38
|
+
const unit = value.charCodeAt(i);
|
|
39
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
40
|
+
const next = value.charCodeAt(++i);
|
|
41
|
+
if (!(next >= 56320 && next <= 57343)) throw new TypeError(`${name} must be valid Unicode`);
|
|
42
|
+
} else if (unit >= 56320 && unit <= 57343) throw new TypeError(`${name} must be valid Unicode`);
|
|
43
|
+
}
|
|
44
|
+
if (value.length > max || byteLength(value) > max || nonempty && !value.trim()) {
|
|
45
|
+
throw new RangeError(`${name} has an invalid byte length`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function integer(value, name, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
50
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) throw new TypeError(`${name} must be a safe integer`);
|
|
51
|
+
if (value < min || value > max) throw new RangeError(`${name} is outside its supported range`);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function number(value, name, min = -Infinity, max = Infinity) {
|
|
55
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} must be finite`);
|
|
56
|
+
if (value < min || value > max) throw new RangeError(`${name} is outside its supported range`);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function boolean(value, name) {
|
|
60
|
+
if (typeof value !== "boolean") throw new TypeError(`${name} must be a boolean`);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
function record(value, name, keys) {
|
|
64
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${name} must be an object`);
|
|
65
|
+
const prototype = Object.getPrototypeOf(value);
|
|
66
|
+
if (prototype !== null && prototype !== Object.prototype) throw new TypeError(`${name} must be a plain object`);
|
|
67
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
68
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
69
|
+
if (typeof key !== "string" || !("value" in descriptor) || !descriptor.enumerable || keys && !keys.includes(key)) {
|
|
70
|
+
throw new TypeError(`${name} contains an unsupported property`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function array(value, name, max = 1048576, min = 0) {
|
|
76
|
+
if (!Array.isArray(value)) throw new TypeError(`${name} must be an array`);
|
|
77
|
+
if (value.length < min || value.length > max) throw new RangeError(`${name} has an invalid length`);
|
|
78
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
79
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
80
|
+
if (!descriptor || !("value" in descriptor)) throw new TypeError(`${name} must not contain holes or accessors`);
|
|
81
|
+
}
|
|
82
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) throw new TypeError(`${name} contains extra properties`);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function id(value) {
|
|
86
|
+
const result = string(value, "id", 256);
|
|
87
|
+
if (!result.length) throw new RangeError("id must not be empty");
|
|
88
|
+
if (/[\u0000-\u001f\u007f-\u009f]/.test(result) || result.startsWith("hms:")) throw new TypeError("id contains a reserved prefix or control character");
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function segment(value) {
|
|
92
|
+
const result = id(value);
|
|
93
|
+
if (result === "." || result === "..") throw new TypeError("Dot-only document IDs cannot be addressed through a URL path");
|
|
94
|
+
return encodeURIComponent(result).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
95
|
+
}
|
|
96
|
+
function vector(value) {
|
|
97
|
+
const values = array(value, "embedding", 1048576, 1).map((item) => number(item, "embedding value"));
|
|
98
|
+
if (values.every((item) => item === 0)) throw new RangeError("Embedding must not be all zeros");
|
|
99
|
+
return values;
|
|
100
|
+
}
|
|
101
|
+
function choice(value, name, values) {
|
|
102
|
+
if (typeof value !== "string" || !values.includes(value)) throw new TypeError(`${name} has an unsupported value`);
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
function json(value, limit = MAX_BYTES) {
|
|
106
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
107
|
+
let bytes = 0;
|
|
108
|
+
const add = (size) => {
|
|
109
|
+
bytes += size;
|
|
110
|
+
if (bytes > limit) throw new JsonSizeError("JSON exceeds its byte limit");
|
|
111
|
+
};
|
|
112
|
+
const visit = (item, depth) => {
|
|
113
|
+
if (depth > 64) throw new RangeError("JSON nesting exceeds 64 levels");
|
|
114
|
+
if (item === null || typeof item === "boolean") {
|
|
115
|
+
add(item === null ? 4 : item ? 4 : 5);
|
|
116
|
+
return item;
|
|
117
|
+
}
|
|
118
|
+
if (typeof item === "number") {
|
|
119
|
+
number(item, "JSON number");
|
|
120
|
+
add(String(item).length);
|
|
121
|
+
return item;
|
|
122
|
+
}
|
|
123
|
+
if (typeof item === "string") {
|
|
124
|
+
string(item, "JSON string", limit);
|
|
125
|
+
add(byteLength(JSON.stringify(item)));
|
|
126
|
+
return item;
|
|
127
|
+
}
|
|
128
|
+
if (typeof item !== "object") throw new TypeError("JSON contains an unsupported value");
|
|
129
|
+
if (ancestors.has(item)) throw new TypeError("JSON must not contain cycles");
|
|
130
|
+
ancestors.add(item);
|
|
131
|
+
let result;
|
|
132
|
+
if (Array.isArray(item)) {
|
|
133
|
+
const items = array(item, "JSON array");
|
|
134
|
+
add(2 + Math.max(0, items.length - 1));
|
|
135
|
+
result = items.map((entry) => visit(entry, depth + 1));
|
|
136
|
+
} else {
|
|
137
|
+
const source = record(item, "JSON object");
|
|
138
|
+
const entries = Object.entries(source);
|
|
139
|
+
add(2 + Math.max(0, entries.length - 1));
|
|
140
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
141
|
+
for (const [key, entry] of entries) {
|
|
142
|
+
string(key, "JSON key", limit);
|
|
143
|
+
add(byteLength(JSON.stringify(key)) + 1);
|
|
144
|
+
target[key] = visit(entry, depth + 1);
|
|
145
|
+
}
|
|
146
|
+
result = target;
|
|
147
|
+
}
|
|
148
|
+
ancestors.delete(item);
|
|
149
|
+
return result;
|
|
150
|
+
};
|
|
151
|
+
return visit(value, 0);
|
|
152
|
+
}
|
|
153
|
+
function metadata(value, reserved = false) {
|
|
154
|
+
const object = record(value, "metadata");
|
|
155
|
+
if (reserved && Object.hasOwn(object, "_mem")) throw new TypeError("metadata._mem is reserved");
|
|
156
|
+
return json(object, 64 * 1024);
|
|
157
|
+
}
|
|
158
|
+
function signal(value) {
|
|
159
|
+
if (value === void 0) return void 0;
|
|
160
|
+
if (value === null || typeof value !== "object" || typeof value.aborted !== "boolean" || typeof value.addEventListener !== "function" || typeof value.removeEventListener !== "function") {
|
|
161
|
+
throw new TypeError("signal must be an AbortSignal");
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function requestSignal(options) {
|
|
166
|
+
return signal(record(options, "request options", ["signal"]).signal);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/responses.ts
|
|
170
|
+
var text = (value) => {
|
|
171
|
+
string(value, "response string");
|
|
172
|
+
};
|
|
173
|
+
var numeric = (value) => {
|
|
174
|
+
number(value, "response number");
|
|
175
|
+
};
|
|
176
|
+
var count = (value) => {
|
|
177
|
+
integer(value, "response count");
|
|
178
|
+
};
|
|
179
|
+
var millis = (value) => {
|
|
180
|
+
integer(value, "response time", Number.MIN_SAFE_INTEGER);
|
|
181
|
+
};
|
|
182
|
+
var bool = (value) => {
|
|
183
|
+
boolean(value, "response boolean");
|
|
184
|
+
};
|
|
185
|
+
var anything = (value) => {
|
|
186
|
+
if (value === void 0) throw new TypeError("Missing response value");
|
|
187
|
+
};
|
|
188
|
+
var optional = (check) => (value) => {
|
|
189
|
+
if (value !== void 0) check(value);
|
|
190
|
+
};
|
|
191
|
+
var nullable = (check) => (value) => {
|
|
192
|
+
if (value !== null) check(value);
|
|
193
|
+
};
|
|
194
|
+
var maybe = (check) => optional(nullable(check));
|
|
195
|
+
var list = (check) => (value) => {
|
|
196
|
+
array(value, "response array").forEach(check);
|
|
197
|
+
};
|
|
198
|
+
var strings = list(text);
|
|
199
|
+
var shape = (fields) => (value) => {
|
|
200
|
+
const object = record(value, "response");
|
|
201
|
+
for (const [key, check] of Object.entries(fields)) check(object[key]);
|
|
202
|
+
};
|
|
203
|
+
var status = (value) => {
|
|
204
|
+
choice(value, "fact status", ["current", "superseded", "expired", "not-yet-valid"]);
|
|
205
|
+
};
|
|
206
|
+
var remembered = shape({ id: text, created: bool, supersedes: strings, supersededBy: strings, ingestTime: millis });
|
|
207
|
+
var chain = shape({ id: text, relation: (value) => {
|
|
208
|
+
choice(value, "chain relation", ["predecessor", "successor"]);
|
|
209
|
+
}, effectiveFrom: millis, ingestTime: millis });
|
|
210
|
+
var explanation = shape({
|
|
211
|
+
retrieved: bool,
|
|
212
|
+
rank: maybe(count),
|
|
213
|
+
lexicalScore: maybe(numeric),
|
|
214
|
+
semanticScore: maybe(numeric),
|
|
215
|
+
retrievalScore: maybe(numeric),
|
|
216
|
+
lexicalWeight: numeric,
|
|
217
|
+
semanticWeight: numeric,
|
|
218
|
+
fusion: text,
|
|
219
|
+
matchedTerms: strings,
|
|
220
|
+
recencyFactor: numeric,
|
|
221
|
+
historicalFactor: numeric,
|
|
222
|
+
finalScore: maybe(numeric),
|
|
223
|
+
status,
|
|
224
|
+
asOf: millis,
|
|
225
|
+
knownAt: maybe(millis),
|
|
226
|
+
validFrom: maybe(millis),
|
|
227
|
+
validTo: maybe(millis),
|
|
228
|
+
withinValidity: bool,
|
|
229
|
+
chain: list(chain)
|
|
230
|
+
});
|
|
231
|
+
var factFields = {
|
|
232
|
+
id: text,
|
|
233
|
+
text: maybe(text),
|
|
234
|
+
status,
|
|
235
|
+
ingestTime: maybe(millis),
|
|
236
|
+
eventTime: maybe(millis),
|
|
237
|
+
validFrom: maybe(millis),
|
|
238
|
+
validTo: maybe(millis),
|
|
239
|
+
subject: maybe(text),
|
|
240
|
+
entities: strings,
|
|
241
|
+
supersedes: strings,
|
|
242
|
+
supersededBy: strings,
|
|
243
|
+
metadata: anything
|
|
244
|
+
};
|
|
245
|
+
var fact = shape({ ...factFields, effectiveFrom: millis });
|
|
246
|
+
var recalled = shape({ ...factFields, score: numeric, sourceUri: maybe(text), explanation: maybe(explanation) });
|
|
247
|
+
var vectorIndex = shape({ rows: count, graph: bool, current: bool, staleMutations: count, rebuildRecommended: bool });
|
|
248
|
+
var schemas = {
|
|
249
|
+
added: shape({ added: count }),
|
|
250
|
+
deleted: shape({ deleted: bool }),
|
|
251
|
+
chunk: shape({ revision: text, document: shape({ id: text, version: text, metadata: anything, chunkCount: count, sourceUri: maybe(text) }), index: count, id: text, startByte: count, endByte: count, text: maybe(text) }),
|
|
252
|
+
query: shape({ matches: list(shape({ id: text, text, metadata: anything, score: numeric })) }),
|
|
253
|
+
stats: shape({ tenant: text, documents: count, vectors: count, documentIndexTrained: bool, vectorIndex, usedBytes: numeric, capacityBytes: numeric }),
|
|
254
|
+
maintained: shape({ documentIndexTrained: bool, vectorIndex }),
|
|
255
|
+
capabilities: shape({ capabilities: shape({ contractVersion: count, profile: text, operations: strings, temporalQueries: bool, exactMetadataFilters: bool, subjectEntityFilters: bool, documentPagination: bool, committedErrorCode: text }) }),
|
|
256
|
+
remembered: shape({ remembered: list(remembered) }),
|
|
257
|
+
recalled: shape({ results: list(recalled) }),
|
|
258
|
+
explained: shape({ explanation }),
|
|
259
|
+
get: shape({ fact: nullable(fact) }),
|
|
260
|
+
history: shape({ history: list(fact) }),
|
|
261
|
+
forgotten: shape({ forgotten: strings, dryRun: bool, purged: bool }),
|
|
262
|
+
consolidated: shape({ created: list(remembered), forgotten: strings, dryRun: bool })
|
|
263
|
+
};
|
|
264
|
+
function response(schema) {
|
|
265
|
+
return (value) => {
|
|
266
|
+
schema(value);
|
|
267
|
+
return value;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function envelope(schema, key) {
|
|
271
|
+
return (value) => {
|
|
272
|
+
schema(value);
|
|
273
|
+
return value[key];
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/transport.ts
|
|
278
|
+
var WarmError = class extends Error {
|
|
279
|
+
code;
|
|
280
|
+
status;
|
|
281
|
+
statusCode;
|
|
282
|
+
requestId;
|
|
283
|
+
constructor(message, code, status2, requestId) {
|
|
284
|
+
super(message);
|
|
285
|
+
this.name = "WarmError";
|
|
286
|
+
this.code = code;
|
|
287
|
+
this.status = status2;
|
|
288
|
+
this.statusCode = status2;
|
|
289
|
+
this.requestId = requestId;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var Transport = class {
|
|
293
|
+
url;
|
|
294
|
+
#fetch;
|
|
295
|
+
#headers;
|
|
296
|
+
#timeout;
|
|
297
|
+
#secrets;
|
|
298
|
+
constructor(options) {
|
|
299
|
+
const config = record(options, "client options", ["url", "apiKey", "tenantId", "timeoutMs", "fetch"]);
|
|
300
|
+
const supplied = config.url === void 0 ? "http://localhost:8080" : string(config.url, "url", 8192, true);
|
|
301
|
+
let parsed;
|
|
302
|
+
try {
|
|
303
|
+
parsed = new URL(supplied);
|
|
304
|
+
} catch {
|
|
305
|
+
throw new TypeError("url must be an absolute HTTP(S) URL");
|
|
306
|
+
}
|
|
307
|
+
if (!/^https?:\/\//i.test(supplied) || !["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password || supplied.slice(supplied.indexOf("//") + 2).split("/")[0].includes("@") || supplied.includes("?") || supplied.includes("#") || /[\u0000-\u0020\u007f\\]/.test(supplied)) {
|
|
308
|
+
throw new TypeError("url must be HTTP(S) without userinfo, a query, fragment, whitespace or backslash");
|
|
309
|
+
}
|
|
310
|
+
this.url = parsed.href.replace(/\/+$/, "");
|
|
311
|
+
this.#headers = new Headers({ Accept: "application/json", "Content-Type": "application/json" });
|
|
312
|
+
this.#secrets = [];
|
|
313
|
+
if (config.apiKey !== void 0) {
|
|
314
|
+
const key = string(config.apiKey, "apiKey", 8192, true);
|
|
315
|
+
if (!/^[\x21-\x7e]+$/.test(key)) throw new TypeError("apiKey must contain visible ASCII without whitespace");
|
|
316
|
+
this.#headers.set("Authorization", `Bearer ${key}`);
|
|
317
|
+
this.#secrets = [key, encodeURIComponent(key)];
|
|
318
|
+
}
|
|
319
|
+
if (config.tenantId !== void 0) {
|
|
320
|
+
const tenant = string(config.tenantId, "tenantId", 64, true);
|
|
321
|
+
if (!/^[A-Za-z0-9_-]+$/.test(tenant)) throw new TypeError("tenantId must contain only letters, digits, underscores or hyphens");
|
|
322
|
+
this.#headers.set("X-Tenant-ID", tenant);
|
|
323
|
+
}
|
|
324
|
+
this.#timeout = config.timeoutMs === void 0 ? 3e4 : integer(config.timeoutMs, "timeoutMs", 1, 2147483647);
|
|
325
|
+
const implementation = config.fetch === void 0 ? globalThis.fetch : config.fetch;
|
|
326
|
+
if (typeof implementation !== "function") throw new TypeError("A Fetch API implementation is required");
|
|
327
|
+
const fetcher = implementation;
|
|
328
|
+
this.#fetch = config.fetch === void 0 ? fetcher.bind(globalThis) : fetcher;
|
|
329
|
+
}
|
|
330
|
+
#safe(value, fallback, max) {
|
|
331
|
+
if (typeof value !== "string" || /[<>]/.test(value)) return fallback;
|
|
332
|
+
let result = value;
|
|
333
|
+
for (const secret of this.#secrets) result = result.split(secret).join("[redacted]");
|
|
334
|
+
return result.replace(/Bearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, max) || fallback;
|
|
335
|
+
}
|
|
336
|
+
#error(response2, body) {
|
|
337
|
+
const value = body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
338
|
+
const legacy = value.error !== null && typeof value.error === "object" && !Array.isArray(value.error) ? value.error : {};
|
|
339
|
+
const rawCode = this.#safe(value.code ?? legacy.code, "http_error", 64);
|
|
340
|
+
const code = /^[A-Za-z0-9_.:-]+$/.test(rawCode) ? rawCode : "http_error";
|
|
341
|
+
const message = this.#safe(value.detail ?? legacy.message, `WARM request failed (HTTP ${response2.status})`, 1024);
|
|
342
|
+
const identifier = this.#safe(response2.headers.get("x-request-id") ?? value.request_id, "", 128);
|
|
343
|
+
return new WarmError(message, code, response2.status, /^[A-Za-z0-9_.:-]+$/.test(identifier) ? identifier : void 0);
|
|
344
|
+
}
|
|
345
|
+
async request(method, path, payload, signal2, decode) {
|
|
346
|
+
let body;
|
|
347
|
+
if (payload !== void 0) {
|
|
348
|
+
try {
|
|
349
|
+
body = JSON.stringify(json(payload));
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error instanceof JsonSizeError) throw new WarmError("Request exceeds 16 MiB", "request_too_large");
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
if (byteLength(body) > MAX_BYTES) throw new WarmError("Request exceeds 16 MiB", "request_too_large");
|
|
355
|
+
}
|
|
356
|
+
if (signal2?.aborted) throw new WarmError("Request was aborted", "aborted");
|
|
357
|
+
const controller = new AbortController();
|
|
358
|
+
let timedOut = false;
|
|
359
|
+
let rejectAbort;
|
|
360
|
+
const aborted = new Promise((_, reject) => {
|
|
361
|
+
rejectAbort = reject;
|
|
362
|
+
});
|
|
363
|
+
const abort = () => {
|
|
364
|
+
controller.abort();
|
|
365
|
+
rejectAbort(new WarmError(timedOut ? "Request timed out" : "Request was aborted", timedOut ? "timeout" : "aborted"));
|
|
366
|
+
};
|
|
367
|
+
signal2?.addEventListener("abort", abort, { once: true });
|
|
368
|
+
const timer = setTimeout(() => {
|
|
369
|
+
timedOut = true;
|
|
370
|
+
abort();
|
|
371
|
+
}, this.#timeout);
|
|
372
|
+
let response2;
|
|
373
|
+
let reader;
|
|
374
|
+
let finished = false;
|
|
375
|
+
try {
|
|
376
|
+
if (signal2?.aborted) abort();
|
|
377
|
+
const fetched = Promise.resolve().then(() => {
|
|
378
|
+
if (controller.signal.aborted) throw new WarmError("Request was aborted", "aborted");
|
|
379
|
+
return this.#fetch(`${this.url}${path}`, {
|
|
380
|
+
method,
|
|
381
|
+
headers: new Headers(this.#headers),
|
|
382
|
+
body,
|
|
383
|
+
signal: controller.signal,
|
|
384
|
+
redirect: "manual",
|
|
385
|
+
credentials: "omit",
|
|
386
|
+
referrerPolicy: "no-referrer"
|
|
387
|
+
});
|
|
388
|
+
}).then((value) => {
|
|
389
|
+
if (controller.signal.aborted) void value.body?.cancel().catch(() => {
|
|
390
|
+
});
|
|
391
|
+
return value;
|
|
392
|
+
});
|
|
393
|
+
response2 = await Promise.race([fetched, aborted]);
|
|
394
|
+
if (response2.type === "opaqueredirect" || response2.redirected || response2.status >= 300 && response2.status < 400) {
|
|
395
|
+
throw new WarmError("WARM redirects are not followed", "redirect_error", response2.status || void 0);
|
|
396
|
+
}
|
|
397
|
+
const declared = response2.headers.get("content-length");
|
|
398
|
+
if (declared && /^\d+$/.test(declared) && Number(declared) > MAX_BYTES) throw new WarmError("Response exceeds 16 MiB", "response_too_large", response2.status);
|
|
399
|
+
const chunks = [];
|
|
400
|
+
let length = 0;
|
|
401
|
+
if (response2.body) {
|
|
402
|
+
reader = response2.body.getReader();
|
|
403
|
+
for (; ; ) {
|
|
404
|
+
const chunk = await Promise.race([reader.read(), aborted]);
|
|
405
|
+
if (chunk.done) break;
|
|
406
|
+
length += chunk.value.byteLength;
|
|
407
|
+
if (length > MAX_BYTES) throw new WarmError("Response exceeds 16 MiB", "response_too_large", response2.status);
|
|
408
|
+
chunks.push(chunk.value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
finished = true;
|
|
412
|
+
const bytes = new Uint8Array(length);
|
|
413
|
+
let offset = 0;
|
|
414
|
+
for (const chunk of chunks) {
|
|
415
|
+
bytes.set(chunk, offset);
|
|
416
|
+
offset += chunk.byteLength;
|
|
417
|
+
}
|
|
418
|
+
let parsed;
|
|
419
|
+
try {
|
|
420
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
421
|
+
} catch {
|
|
422
|
+
if (!response2.ok) throw this.#error(response2, void 0);
|
|
423
|
+
throw new WarmError("WARM returned an invalid JSON response", "invalid_response", response2.status);
|
|
424
|
+
}
|
|
425
|
+
if (!response2.ok) throw this.#error(response2, parsed);
|
|
426
|
+
try {
|
|
427
|
+
json(parsed);
|
|
428
|
+
return decode(parsed);
|
|
429
|
+
} catch {
|
|
430
|
+
throw new WarmError("WARM returned a response outside the documented contract", "invalid_response", response2.status);
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof WarmError) throw error;
|
|
434
|
+
if (controller.signal.aborted) throw new WarmError(timedOut ? "Request timed out" : "Request was aborted", timedOut ? "timeout" : "aborted");
|
|
435
|
+
throw new WarmError("Unable to complete the WARM request", "network_error");
|
|
436
|
+
} finally {
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
signal2?.removeEventListener("abort", abort);
|
|
439
|
+
if (!finished) {
|
|
440
|
+
if (reader) void reader.cancel().catch(() => {
|
|
441
|
+
});
|
|
442
|
+
else if (response2?.body) void response2.body.cancel().catch(() => {
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
reader?.releaseLock();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// src/client.ts
|
|
451
|
+
var time = (value) => integer(value, "time", Number.MIN_SAFE_INTEGER);
|
|
452
|
+
var text2 = (value) => string(value, "text", 64 * 1024);
|
|
453
|
+
var optionalString = (value) => string(value, "string");
|
|
454
|
+
var filter = (value) => json(record(value, "filter"));
|
|
455
|
+
var bool2 = (value) => boolean(value, "option");
|
|
456
|
+
var rememberKeys = ["id", "eventTime", "validFrom", "validTo", "subject", "supersedes", "entities", "metadata", "embedding", "sourceUri"];
|
|
457
|
+
var recallKeys = ["k", "asOf", "knownAt", "history", "subject", "entities", "filter", "embedding", "lexicalWeight", "semanticWeight", "minSemanticScore", "fusion", "candidateLimit", "explain"];
|
|
458
|
+
function copy(source, target, key, validate, wire = key, nullable2 = true) {
|
|
459
|
+
if (source[key] !== void 0) target[wire] = source[key] === null && nullable2 ? null : validate(source[key]);
|
|
460
|
+
}
|
|
461
|
+
function label(value) {
|
|
462
|
+
const result = string(value, "label");
|
|
463
|
+
string(result.trim(), "label", 1024, true);
|
|
464
|
+
if (result.includes("\0")) throw new TypeError("Labels must not contain NUL");
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
function labels(value) {
|
|
468
|
+
const items = array(value, "entities").map(label);
|
|
469
|
+
if (new Set(items.map((item) => item.trim())).size > 256) throw new RangeError("At most 256 distinct entities are supported");
|
|
470
|
+
return items;
|
|
471
|
+
}
|
|
472
|
+
function links(value) {
|
|
473
|
+
const items = array(value, "supersedes").map(id);
|
|
474
|
+
if (new Set(items).size > 256) throw new RangeError("At most 256 distinct supersedes IDs are supported");
|
|
475
|
+
return items;
|
|
476
|
+
}
|
|
477
|
+
function fact2(source, content) {
|
|
478
|
+
const out = { text: string(content, "text", 64 * 1024, true) };
|
|
479
|
+
copy(source, out, "id", id);
|
|
480
|
+
for (const key of ["eventTime", "validFrom", "validTo"]) copy(source, out, key, time);
|
|
481
|
+
copy(source, out, "subject", label);
|
|
482
|
+
copy(source, out, "supersedes", links);
|
|
483
|
+
copy(source, out, "entities", labels);
|
|
484
|
+
copy(source, out, "metadata", (value) => metadata(value, true));
|
|
485
|
+
copy(source, out, "embedding", vector);
|
|
486
|
+
copy(source, out, "sourceUri", optionalString);
|
|
487
|
+
if (typeof out.validFrom === "number" && typeof out.validTo === "number" && out.validFrom >= out.validTo) throw new RangeError("validFrom must be before validTo");
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function recall(source, query) {
|
|
491
|
+
const out = { query: string(query, "query", 8192) };
|
|
492
|
+
copy(source, out, "k", (value) => integer(value, "k", 1, 1e3));
|
|
493
|
+
copy(source, out, "candidateLimit", (value) => integer(value, "candidateLimit", 1, 4096));
|
|
494
|
+
for (const key of ["asOf", "knownAt"]) copy(source, out, key, time);
|
|
495
|
+
copy(source, out, "history", (value) => choice(value, "history", ["current", "all", "historical"]));
|
|
496
|
+
copy(source, out, "subject", label);
|
|
497
|
+
copy(source, out, "entities", labels);
|
|
498
|
+
copy(source, out, "filter", filter);
|
|
499
|
+
copy(source, out, "embedding", vector);
|
|
500
|
+
for (const key of ["lexicalWeight", "semanticWeight"]) copy(source, out, key, (value) => number(value, key, 0));
|
|
501
|
+
copy(source, out, "minSemanticScore", (value) => number(value, "minSemanticScore", -1, 1));
|
|
502
|
+
copy(source, out, "fusion", (value) => choice(value, "fusion", ["blend", "rrf"]));
|
|
503
|
+
copy(source, out, "explain", bool2, "explain", false);
|
|
504
|
+
if (typeof out.k === "number" && typeof out.candidateLimit === "number" && out.candidateLimit < out.k) throw new RangeError("candidateLimit must be at least k");
|
|
505
|
+
const weight = (out.lexicalWeight ?? 1) + (out.semanticWeight ?? 1);
|
|
506
|
+
if (!Number.isFinite(weight) || weight <= 0) throw new RangeError("Search weights must have a finite positive sum");
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
function forgetOptions(options, dryRun) {
|
|
510
|
+
return {
|
|
511
|
+
includeChain: options.includeChain === void 0 ? false : boolean(options.includeChain, "includeChain"),
|
|
512
|
+
dryRun: options.dryRun === void 0 ? dryRun : boolean(options.dryRun, "dryRun"),
|
|
513
|
+
purge: options.purge === void 0 ? false : boolean(options.purge, "purge")
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function boundedBatch(values, convert, overhead) {
|
|
517
|
+
const items = array(values, "batch", 4096);
|
|
518
|
+
let bytes = overhead + Math.max(0, items.length - 1);
|
|
519
|
+
return items.map((value) => {
|
|
520
|
+
const item = convert(value);
|
|
521
|
+
bytes += byteLength(JSON.stringify(item));
|
|
522
|
+
if (bytes > MAX_BYTES) throw new WarmError("Request exceeds 16 MiB", "request_too_large");
|
|
523
|
+
return item;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
var WarmClient = class {
|
|
527
|
+
url;
|
|
528
|
+
#transport;
|
|
529
|
+
#memory;
|
|
530
|
+
constructor(options = {}) {
|
|
531
|
+
this.#transport = new Transport(options);
|
|
532
|
+
this.url = this.#transport.url;
|
|
533
|
+
this.#memory = new MemoryClient(this.#transport);
|
|
534
|
+
}
|
|
535
|
+
get memory() {
|
|
536
|
+
return this.#memory;
|
|
537
|
+
}
|
|
538
|
+
async addDocuments(documents, options = {}) {
|
|
539
|
+
const signal2 = requestSignal(options);
|
|
540
|
+
const payload = boundedBatch(documents, (item) => {
|
|
541
|
+
const source = record(item, "document", ["id", "text", "vector", "metadata", "importIdentity", "importAdoptLegacy"]);
|
|
542
|
+
const out = { id: id(source.id) };
|
|
543
|
+
copy(source, out, "text", text2);
|
|
544
|
+
copy(source, out, "vector", vector);
|
|
545
|
+
copy(source, out, "metadata", (value) => metadata(value));
|
|
546
|
+
copy(source, out, "importIdentity", (value) => {
|
|
547
|
+
const identity = string(value, "importIdentity", 64);
|
|
548
|
+
if (!/^[0-9a-f]{64}$/.test(identity)) throw new TypeError("importIdentity must be a lowercase SHA-256 digest");
|
|
549
|
+
return identity;
|
|
550
|
+
}, "import_identity");
|
|
551
|
+
copy(source, out, "importAdoptLegacy", bool2, "import_adopt_legacy", false);
|
|
552
|
+
const stamp = out.metadata == null ? void 0 : out.metadata.warm_import_identity;
|
|
553
|
+
if (out.import_identity != null || stamp !== void 0 || out.import_adopt_legacy === true) {
|
|
554
|
+
if (typeof out.import_identity !== "string" || stamp !== out.import_identity) throw new TypeError("importIdentity must match metadata.warm_import_identity");
|
|
555
|
+
}
|
|
556
|
+
if (out.vector == null && !(typeof out.text === "string" && out.text.trim())) throw new TypeError("A document requires a vector or nonempty text for a server-side embedder");
|
|
557
|
+
return out;
|
|
558
|
+
}, 2);
|
|
559
|
+
if (payload.length === 0) {
|
|
560
|
+
if (signal2?.aborted) throw new WarmError("Request was aborted", "aborted");
|
|
561
|
+
return { added: 0 };
|
|
562
|
+
}
|
|
563
|
+
return this.#transport.request("POST", "/api/v1/documents/batch", payload, signal2, response(schemas.added));
|
|
564
|
+
}
|
|
565
|
+
async deleteDocument(id2, options = {}) {
|
|
566
|
+
return this.#transport.request("DELETE", `/api/v1/documents/${segment(id2)}`, void 0, requestSignal(options), response(schemas.deleted));
|
|
567
|
+
}
|
|
568
|
+
async readDocumentChunk(id2, index = 0, options = {}) {
|
|
569
|
+
const path = `/api/v1/documents/${segment(id2)}/chunks/${integer(index, "chunk index")}`;
|
|
570
|
+
return this.#transport.request("GET", path, void 0, requestSignal(options), response(schemas.chunk));
|
|
571
|
+
}
|
|
572
|
+
async stats(options = {}) {
|
|
573
|
+
return this.#transport.request("GET", "/api/v1/stats", void 0, requestSignal(options), response(schemas.stats));
|
|
574
|
+
}
|
|
575
|
+
async maintainDocumentIndex(options = {}) {
|
|
576
|
+
return this.#transport.request("POST", "/api/v1/index/maintain", {}, requestSignal(options), response(schemas.maintained));
|
|
577
|
+
}
|
|
578
|
+
async query(input, options = {}) {
|
|
579
|
+
const source = record(input, "query input", ["query", "queryVector", "today", "topK", "filter", "documentIds", "index", "ef"]);
|
|
580
|
+
const out = { top_k: source.topK === void 0 ? 3 : source.topK === null ? null : integer(source.topK, "topK", 1) };
|
|
581
|
+
copy(source, out, "query", (value) => string(value, "query", 8192, true));
|
|
582
|
+
copy(source, out, "queryVector", vector, "query_vector");
|
|
583
|
+
copy(source, out, "today", optionalString);
|
|
584
|
+
copy(source, out, "filter", filter);
|
|
585
|
+
copy(source, out, "documentIds", (value) => {
|
|
586
|
+
const ids = array(value, "documentIds", 1e3, 1).map(id);
|
|
587
|
+
if (new Set(ids).size !== ids.length) throw new TypeError("documentIds must be unique");
|
|
588
|
+
return ids;
|
|
589
|
+
}, "document_ids");
|
|
590
|
+
copy(source, out, "index", (value) => choice(value, "index", ["auto", "exact", "graph"]));
|
|
591
|
+
copy(source, out, "ef", (value) => integer(value, "ef", 1, 4096));
|
|
592
|
+
if (out.query == null && out.query_vector == null) throw new TypeError("query requires query or queryVector");
|
|
593
|
+
return this.#transport.request("POST", "/api/v1/query", out, requestSignal(options), response(schemas.query));
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var MemoryClient = class {
|
|
597
|
+
#transport;
|
|
598
|
+
constructor(transport) {
|
|
599
|
+
this.#transport = transport;
|
|
600
|
+
}
|
|
601
|
+
async capabilities(contractVersion = 1, options = {}) {
|
|
602
|
+
const payload = { contractVersion: integer(contractVersion, "contractVersion", 1, 4294967295) };
|
|
603
|
+
return this.#transport.request("POST", "/api/v1/memory/capabilities", payload, requestSignal(options), envelope(schemas.capabilities, "capabilities"));
|
|
604
|
+
}
|
|
605
|
+
async remember(text3, options = {}) {
|
|
606
|
+
const source = record(options, "remember options", [...rememberKeys, "signal"]);
|
|
607
|
+
const payload = fact2(source, text3);
|
|
608
|
+
return this.#transport.request("POST", "/api/v1/memory/remember", payload, signal(source.signal), (value) => {
|
|
609
|
+
const items = envelope(schemas.remembered, "remembered")(value);
|
|
610
|
+
if (items.length !== 1) throw new TypeError("Expected one remembered fact");
|
|
611
|
+
return items[0];
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
async rememberMany(facts, options = {}) {
|
|
615
|
+
const values = boundedBatch(facts, (item) => {
|
|
616
|
+
const source = record(item, "fact", ["text", ...rememberKeys]);
|
|
617
|
+
return fact2(source, source.text);
|
|
618
|
+
}, 12);
|
|
619
|
+
if (!values.length) throw new RangeError("rememberMany requires at least one fact");
|
|
620
|
+
return this.#transport.request("POST", "/api/v1/memory/remember", { facts: values }, requestSignal(options), (value) => {
|
|
621
|
+
const items = envelope(schemas.remembered, "remembered")(value);
|
|
622
|
+
if (items.length !== values.length) throw new TypeError("Remembered count does not match submitted facts");
|
|
623
|
+
return items;
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
async recall(query, options = {}) {
|
|
627
|
+
const source = record(options, "recall options", [...recallKeys, "signal"]);
|
|
628
|
+
return this.#transport.request("POST", "/api/v1/memory/recall", recall(source, query), signal(source.signal), envelope(schemas.recalled, "results"));
|
|
629
|
+
}
|
|
630
|
+
async explain(query, id2, options = {}) {
|
|
631
|
+
const source = record(options, "explain options", [...recallKeys, "signal"]);
|
|
632
|
+
const payload = { ...recall(source, query), id: id(id2) };
|
|
633
|
+
return this.#transport.request("POST", "/api/v1/memory/explain", payload, signal(source.signal), envelope(schemas.explained, "explanation"));
|
|
634
|
+
}
|
|
635
|
+
async forget(scope, options = {}) {
|
|
636
|
+
const source = record(scope, "forget scope");
|
|
637
|
+
const kind = choice(source.kind, "scope kind", ["ids", "subject", "entity", "ingestedBefore", "eventBefore", "matching"]);
|
|
638
|
+
let chosen;
|
|
639
|
+
if (kind === "ids") {
|
|
640
|
+
record(source, "forget scope", ["kind", "ids"]);
|
|
641
|
+
chosen = { kind, ids: array(source.ids, "ids").map(id) };
|
|
642
|
+
} else if (kind === "subject" || kind === "entity") {
|
|
643
|
+
record(source, "forget scope", ["kind", kind]);
|
|
644
|
+
chosen = { kind, [kind]: label(source[kind]) };
|
|
645
|
+
} else if (kind === "matching") {
|
|
646
|
+
record(source, "forget scope", ["kind", "filter"]);
|
|
647
|
+
const matching = record(source.filter, "filter");
|
|
648
|
+
if (!Object.keys(matching).length || Object.hasOwn(matching, "_mem")) throw new TypeError("Forget filter must be nonempty and must not contain reserved metadata._mem");
|
|
649
|
+
chosen = { kind, filter: filter(matching) };
|
|
650
|
+
} else {
|
|
651
|
+
record(source, "forget scope", ["kind", "time"]);
|
|
652
|
+
chosen = { kind, time: time(source.time) };
|
|
653
|
+
}
|
|
654
|
+
const settings = record(options, "forget options", ["includeChain", "dryRun", "purge", "signal"]);
|
|
655
|
+
return this.#transport.request("POST", "/api/v1/memory/forget", { scope: chosen, ...forgetOptions(settings, false) }, signal(settings.signal), response(schemas.forgotten));
|
|
656
|
+
}
|
|
657
|
+
async get(id2, options = {}) {
|
|
658
|
+
return this.#transport.request("POST", "/api/v1/memory/get", { id: id(id2) }, requestSignal(options), envelope(schemas.get, "fact"));
|
|
659
|
+
}
|
|
660
|
+
async history(id2, options = {}) {
|
|
661
|
+
return this.#transport.request("POST", "/api/v1/memory/history", { id: id(id2) }, requestSignal(options), envelope(schemas.history, "history"));
|
|
662
|
+
}
|
|
663
|
+
async applyForgetting(options = {}) {
|
|
664
|
+
const source = record(options, "forgetting options", ["includeChain", "dryRun", "purge", "signal"]);
|
|
665
|
+
return this.#transport.request("POST", "/api/v1/memory/applyForgetting", forgetOptions(source, true), signal(source.signal), response(schemas.forgotten));
|
|
666
|
+
}
|
|
667
|
+
async consolidate(options = {}) {
|
|
668
|
+
const source = record(options, "consolidation options", ["dryRun", "allowDestructive", "signal"]);
|
|
669
|
+
const payload = {
|
|
670
|
+
dryRun: source.dryRun === void 0 ? true : boolean(source.dryRun, "dryRun"),
|
|
671
|
+
allowDestructive: source.allowDestructive === void 0 ? false : boolean(source.allowDestructive, "allowDestructive")
|
|
672
|
+
};
|
|
673
|
+
return this.#transport.request("POST", "/api/v1/memory/consolidate", payload, signal(source.signal), response(schemas.consolidated));
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
//# sourceMappingURL=index.cjs.map
|