@keb-org/consol 0.0.0-stage → 0.1.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 +21 -0
- package/README.md +265 -2
- package/dist/consol.d.ts +36 -0
- package/dist/errors.d.ts +29 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +815 -0
- package/dist/index.mjs +5 -0
- package/dist/stdio.d.ts +120 -0
- package/dist/transport.d.ts +38 -0
- package/dist/types.d.ts +395 -0
- package/dist/vault.d.ts +180 -0
- package/package.json +58 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (canCache)
|
|
31
|
+
cache.set(mod, to);
|
|
32
|
+
return to;
|
|
33
|
+
};
|
|
34
|
+
var __toCommonJS = (from) => {
|
|
35
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
36
|
+
if (entry)
|
|
37
|
+
return entry;
|
|
38
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (var key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(entry, key))
|
|
42
|
+
__defProp(entry, key, {
|
|
43
|
+
get: __accessProp.bind(from, key),
|
|
44
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
__moduleCache.set(from, entry);
|
|
48
|
+
return entry;
|
|
49
|
+
};
|
|
50
|
+
var __moduleCache;
|
|
51
|
+
var __returnValue = (v) => v;
|
|
52
|
+
function __exportSetter(name, newValue) {
|
|
53
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
54
|
+
}
|
|
55
|
+
var __export = (target, all) => {
|
|
56
|
+
for (var name in all)
|
|
57
|
+
__defProp(target, name, {
|
|
58
|
+
get: all[name],
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
set: __exportSetter.bind(all, name)
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/index.ts
|
|
66
|
+
var exports_src = {};
|
|
67
|
+
__export(exports_src, {
|
|
68
|
+
Consol: () => Consol,
|
|
69
|
+
ConsolError: () => ConsolError,
|
|
70
|
+
Vault: () => Vault,
|
|
71
|
+
createVault: () => createVault,
|
|
72
|
+
httpStatus: () => httpStatus,
|
|
73
|
+
openVault: () => openVault,
|
|
74
|
+
sliceUtf8: () => sliceUtf8
|
|
75
|
+
});
|
|
76
|
+
module.exports = __toCommonJS(exports_src);
|
|
77
|
+
|
|
78
|
+
// src/errors.ts
|
|
79
|
+
var BRAND = "__consolError";
|
|
80
|
+
|
|
81
|
+
class ConsolError extends Error {
|
|
82
|
+
code;
|
|
83
|
+
retryable;
|
|
84
|
+
field;
|
|
85
|
+
tool;
|
|
86
|
+
status;
|
|
87
|
+
stderrTail;
|
|
88
|
+
result;
|
|
89
|
+
constructor(code, message, details = {}) {
|
|
90
|
+
super(message, details.cause === undefined ? undefined : { cause: details.cause });
|
|
91
|
+
this.name = "ConsolError";
|
|
92
|
+
this.code = code;
|
|
93
|
+
this.retryable = details.retryable ?? (code === "vault_busy" || code === "index_unavailable" || code === "storage_unavailable");
|
|
94
|
+
if (details.field !== undefined)
|
|
95
|
+
this.field = details.field;
|
|
96
|
+
if (details.tool !== undefined)
|
|
97
|
+
this.tool = details.tool;
|
|
98
|
+
if (details.status !== undefined)
|
|
99
|
+
this.status = details.status;
|
|
100
|
+
if (details.stderrTail !== undefined)
|
|
101
|
+
this.stderrTail = details.stderrTail;
|
|
102
|
+
if (details.result !== undefined)
|
|
103
|
+
this.result = details.result;
|
|
104
|
+
Object.defineProperty(this, BRAND, { value: true });
|
|
105
|
+
}
|
|
106
|
+
static is(error) {
|
|
107
|
+
return error instanceof ConsolError || typeof error === "object" && error !== null && error[BRAND] === true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function replyError(reply, tool) {
|
|
111
|
+
const e = reply.error;
|
|
112
|
+
if (!e)
|
|
113
|
+
return;
|
|
114
|
+
return new ConsolError(e.code ?? "internal", e.message ?? `${tool ?? "request"} failed`, {
|
|
115
|
+
retryable: e.retryable,
|
|
116
|
+
field: e.field,
|
|
117
|
+
tool,
|
|
118
|
+
result: reply
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// src/stdio.ts
|
|
122
|
+
var import_node_child_process = require("node:child_process");
|
|
123
|
+
var import_node_fs = require("node:fs");
|
|
124
|
+
var readline = __toESM(require("node:readline"));
|
|
125
|
+
var import_node_string_decoder = require("node:string_decoder");
|
|
126
|
+
|
|
127
|
+
// src/transport.ts
|
|
128
|
+
var DEFAULT_TIMEOUT_MS = 300000;
|
|
129
|
+
function deadline(ms, fallback) {
|
|
130
|
+
const value = ms ?? fallback;
|
|
131
|
+
if (!Number.isInteger(value) || value < 0)
|
|
132
|
+
throw new ConsolError("invalid_argument", `invalid timeoutMs ${String(ms)}`);
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
function checkVersion(got, sdk) {
|
|
136
|
+
const semver = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
137
|
+
const server = typeof got === "string" ? semver.exec(got) : null;
|
|
138
|
+
const own = semver.exec(sdk);
|
|
139
|
+
if (!server || !own || server[1] !== own[1] || server[2] !== own[2]) {
|
|
140
|
+
throw new ConsolError("incompatible_server", `server ${String(got ?? "unknown")} is not SDK ${sdk} (major.minor must match)`);
|
|
141
|
+
}
|
|
142
|
+
return got;
|
|
143
|
+
}
|
|
144
|
+
var anySignal = (signals) => AbortSignal.any(signals);
|
|
145
|
+
var RETRYABLE_STATUS = [502, 503, 504];
|
|
146
|
+
|
|
147
|
+
class HttpTransport {
|
|
148
|
+
url;
|
|
149
|
+
headers;
|
|
150
|
+
timeoutMs;
|
|
151
|
+
sdkVersion;
|
|
152
|
+
version = null;
|
|
153
|
+
closed = new AbortController;
|
|
154
|
+
constructor(url, headers, timeoutMs, sdkVersion) {
|
|
155
|
+
this.url = url;
|
|
156
|
+
this.headers = headers;
|
|
157
|
+
this.timeoutMs = timeoutMs;
|
|
158
|
+
this.sdkVersion = sdkVersion;
|
|
159
|
+
}
|
|
160
|
+
async handle(body, _ctx, options = {}) {
|
|
161
|
+
if (this.closed.signal.aborted)
|
|
162
|
+
throw new ConsolError("closed", "vault is closed");
|
|
163
|
+
const ms = deadline(options.timeoutMs, this.timeoutMs);
|
|
164
|
+
const user = options.signal;
|
|
165
|
+
if (user?.aborted)
|
|
166
|
+
throw new ConsolError("aborted", "request aborted before dispatch", { cause: user.reason });
|
|
167
|
+
const timer = ms > 0 ? AbortSignal.timeout(ms) : undefined;
|
|
168
|
+
const headers = new Headers(this.headers);
|
|
169
|
+
headers.set("content-type", "application/json");
|
|
170
|
+
let status;
|
|
171
|
+
let text;
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch(this.url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers,
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
signal: anySignal([this.closed.signal, ...user ? [user] : [], ...timer ? [timer] : []])
|
|
178
|
+
});
|
|
179
|
+
status = res.status;
|
|
180
|
+
text = await res.text();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (this.closed.signal.aborted)
|
|
183
|
+
throw new ConsolError("closed", "vault is closed", { cause: error });
|
|
184
|
+
if (user?.aborted)
|
|
185
|
+
throw new ConsolError("aborted", "request aborted", { cause: error });
|
|
186
|
+
if (timer?.aborted)
|
|
187
|
+
throw new ConsolError("timeout", `request timed out after ${ms}ms`, { cause: error });
|
|
188
|
+
const code = error?.cause?.code;
|
|
189
|
+
throw new ConsolError("request_failed", `request failed: ${code ?? error?.message ?? String(error)}`, {
|
|
190
|
+
retryable: true,
|
|
191
|
+
cause: error
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
let reply;
|
|
195
|
+
try {
|
|
196
|
+
const parsed = JSON.parse(text);
|
|
197
|
+
if (typeof parsed?.version === "string")
|
|
198
|
+
reply = parsed;
|
|
199
|
+
} catch {}
|
|
200
|
+
if (!reply) {
|
|
201
|
+
if (status === 401 || status === 403)
|
|
202
|
+
throw new ConsolError("forbidden", `request refused (HTTP ${status})`, { status });
|
|
203
|
+
throw new ConsolError("request_failed", `not a consol reply (HTTP ${status})`, { status, retryable: RETRYABLE_STATUS.includes(status) });
|
|
204
|
+
}
|
|
205
|
+
this.version = checkVersion(reply.version, this.sdkVersion);
|
|
206
|
+
return reply;
|
|
207
|
+
}
|
|
208
|
+
close() {
|
|
209
|
+
this.closed.abort();
|
|
210
|
+
return Promise.resolve();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/stdio.ts
|
|
215
|
+
var STDERR_TAIL_BYTES = 4096;
|
|
216
|
+
var EXIT_WAIT_MS = 2000;
|
|
217
|
+
var KILL_WAIT_MS = 4000;
|
|
218
|
+
function engineEnv(o) {
|
|
219
|
+
const env = {};
|
|
220
|
+
const db = o.db ?? process.env.CONSOL_DB_URL;
|
|
221
|
+
if (db !== undefined)
|
|
222
|
+
env.CONSOL_DB_URL = db;
|
|
223
|
+
const e = o.embedding;
|
|
224
|
+
if (e !== undefined) {
|
|
225
|
+
env.CONSOL_EMBED_URL = e.url;
|
|
226
|
+
if (e.apiKey !== undefined)
|
|
227
|
+
env.CONSOL_EMBED_API_KEY = e.apiKey;
|
|
228
|
+
env.CONSOL_EMBED_MODEL = e.model;
|
|
229
|
+
if (e.dims !== undefined)
|
|
230
|
+
env.CONSOL_EMBED_DIMS = String(e.dims);
|
|
231
|
+
if (e.queryPrefix !== undefined)
|
|
232
|
+
env.CONSOL_EMBED_QUERY_PREFIX = e.queryPrefix;
|
|
233
|
+
if (e.batchSize !== undefined)
|
|
234
|
+
env.CONSOL_EMBED_BATCH = String(e.batchSize);
|
|
235
|
+
if (e.timeoutMs !== undefined)
|
|
236
|
+
env.CONSOL_EMBED_TIMEOUT_MS = String(e.timeoutMs);
|
|
237
|
+
}
|
|
238
|
+
const j = o.jev;
|
|
239
|
+
if (j !== undefined) {
|
|
240
|
+
if (j.url !== undefined)
|
|
241
|
+
env.CONSOL_JEV_URL = j.url;
|
|
242
|
+
if (j.apiKey !== undefined)
|
|
243
|
+
env.CONSOL_JEV_API_KEY = j.apiKey;
|
|
244
|
+
if (j.model !== undefined)
|
|
245
|
+
env.CONSOL_JEV_MODEL = j.model;
|
|
246
|
+
if (j.auto === false)
|
|
247
|
+
env.CONSOL_JEV_AUTO = "0";
|
|
248
|
+
}
|
|
249
|
+
return env;
|
|
250
|
+
}
|
|
251
|
+
function platformExe() {
|
|
252
|
+
return process.platform === "win32" ? "consol.exe" : "consol";
|
|
253
|
+
}
|
|
254
|
+
function isFile(path) {
|
|
255
|
+
try {
|
|
256
|
+
return import_node_fs.statSync(path).isFile();
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function missingBinary(attempt) {
|
|
262
|
+
return new ConsolError("binary_not_found", `binary not found: ${attempt}`, { stderrTail: attempt });
|
|
263
|
+
}
|
|
264
|
+
function resolveBinary(explicit) {
|
|
265
|
+
const exe = platformExe();
|
|
266
|
+
if (explicit) {
|
|
267
|
+
if (!isFile(explicit))
|
|
268
|
+
throw missingBinary(`explicit:${explicit}`);
|
|
269
|
+
return { path: explicit, attempts: [`explicit:${explicit}`] };
|
|
270
|
+
}
|
|
271
|
+
const fromEnv = process.env.CONSOL_BIN;
|
|
272
|
+
if (fromEnv) {
|
|
273
|
+
if (!isFile(fromEnv))
|
|
274
|
+
throw missingBinary(`CONSOL_BIN:${fromEnv}`);
|
|
275
|
+
return { path: fromEnv, attempts: [`CONSOL_BIN:${fromEnv}`] };
|
|
276
|
+
}
|
|
277
|
+
const spec = `@keb-org/consol-${process.platform}-${process.arch}/bin/${exe}`;
|
|
278
|
+
try {
|
|
279
|
+
const sdkRequire = require;
|
|
280
|
+
return { path: sdkRequire.resolve(spec), attempts: [`package:${spec}`] };
|
|
281
|
+
} catch {
|
|
282
|
+
return { path: exe, attempts: [`package:${spec}`, `PATH:${exe}`] };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function childEnv(base = {}, extra = {}) {
|
|
286
|
+
const env = {};
|
|
287
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
288
|
+
const name = process.platform === "win32" ? key.toUpperCase() : key;
|
|
289
|
+
if (value === undefined || name.startsWith("CONSOL_") || name === "RUST_LOG")
|
|
290
|
+
continue;
|
|
291
|
+
env[key] = value;
|
|
292
|
+
}
|
|
293
|
+
return { ...env, ...base, ...extra };
|
|
294
|
+
}
|
|
295
|
+
function lastBytes(text) {
|
|
296
|
+
const bytes = Buffer.from(text, "utf8");
|
|
297
|
+
if (bytes.length <= STDERR_TAIL_BYTES)
|
|
298
|
+
return text;
|
|
299
|
+
let start = bytes.length - STDERR_TAIL_BYTES;
|
|
300
|
+
while ((bytes[start] & 192) === 128)
|
|
301
|
+
start++;
|
|
302
|
+
return bytes.subarray(start).toString("utf8");
|
|
303
|
+
}
|
|
304
|
+
var READY = 0;
|
|
305
|
+
|
|
306
|
+
class StdioTransport {
|
|
307
|
+
opts;
|
|
308
|
+
attempts;
|
|
309
|
+
binary;
|
|
310
|
+
timeoutMs;
|
|
311
|
+
child = null;
|
|
312
|
+
reader = null;
|
|
313
|
+
generation = 0;
|
|
314
|
+
nextId = 1;
|
|
315
|
+
pending = new Map;
|
|
316
|
+
starting = null;
|
|
317
|
+
started = false;
|
|
318
|
+
closed = false;
|
|
319
|
+
closing = null;
|
|
320
|
+
stderrTail = [];
|
|
321
|
+
stderrTailBytes = 0;
|
|
322
|
+
version = null;
|
|
323
|
+
constructor(opts) {
|
|
324
|
+
this.timeoutMs = deadline(opts.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
325
|
+
this.opts = opts;
|
|
326
|
+
const resolved = resolveBinary(opts.binaryPath);
|
|
327
|
+
this.binary = resolved.path;
|
|
328
|
+
this.attempts = resolved.attempts;
|
|
329
|
+
}
|
|
330
|
+
decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
331
|
+
stderrPartial = "";
|
|
332
|
+
onStderr(chunk) {
|
|
333
|
+
const lines = (this.stderrPartial + this.decoder.write(chunk)).split(/\r?\n/);
|
|
334
|
+
this.stderrPartial = lastBytes(lines.pop() ?? "");
|
|
335
|
+
for (const line of lines)
|
|
336
|
+
this.pushLine(line);
|
|
337
|
+
}
|
|
338
|
+
flushStderr() {
|
|
339
|
+
const rest = (this.stderrPartial + this.decoder.end()).trim();
|
|
340
|
+
this.stderrPartial = "";
|
|
341
|
+
this.decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
342
|
+
if (rest)
|
|
343
|
+
this.pushLine(rest);
|
|
344
|
+
}
|
|
345
|
+
pushLine(line) {
|
|
346
|
+
const text = lastBytes(line.trim().replace(/\x1b\[[0-9;]*[A-Za-z]/g, ""));
|
|
347
|
+
if (!text)
|
|
348
|
+
return;
|
|
349
|
+
this.stderrTail.push(text);
|
|
350
|
+
this.stderrTailBytes += Buffer.byteLength(text, "utf8");
|
|
351
|
+
while (this.stderrTailBytes > STDERR_TAIL_BYTES && this.stderrTail.length > 1) {
|
|
352
|
+
const dropped = this.stderrTail.shift();
|
|
353
|
+
this.stderrTailBytes -= Buffer.byteLength(dropped, "utf8");
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
this.opts.onLog?.(text);
|
|
357
|
+
} catch {}
|
|
358
|
+
}
|
|
359
|
+
tailText() {
|
|
360
|
+
return this.stderrTail.join(`
|
|
361
|
+
`);
|
|
362
|
+
}
|
|
363
|
+
updateRef() {
|
|
364
|
+
const child = this.child;
|
|
365
|
+
if (!child || child.exitCode !== null || child.signalCode !== null)
|
|
366
|
+
return;
|
|
367
|
+
try {
|
|
368
|
+
if (this.pending.size > 0 || this.starting !== null)
|
|
369
|
+
child.ref();
|
|
370
|
+
else
|
|
371
|
+
child.unref();
|
|
372
|
+
} catch {}
|
|
373
|
+
}
|
|
374
|
+
onDeath(error) {
|
|
375
|
+
const child = this.child;
|
|
376
|
+
this.child = null;
|
|
377
|
+
this.reader?.close();
|
|
378
|
+
this.reader = null;
|
|
379
|
+
try {
|
|
380
|
+
child?.kill();
|
|
381
|
+
} catch {}
|
|
382
|
+
const failure = this.closed ? new ConsolError("closed", "vault is closed", { cause: error }) : error;
|
|
383
|
+
for (const pending of this.pending.values())
|
|
384
|
+
pending.reject(failure);
|
|
385
|
+
this.pending.clear();
|
|
386
|
+
this.updateRef();
|
|
387
|
+
}
|
|
388
|
+
close() {
|
|
389
|
+
this.closing ??= this.shutdown();
|
|
390
|
+
return this.closing;
|
|
391
|
+
}
|
|
392
|
+
async shutdown() {
|
|
393
|
+
this.closed = true;
|
|
394
|
+
const child = this.child;
|
|
395
|
+
this.child = null;
|
|
396
|
+
this.reader?.close();
|
|
397
|
+
this.reader = null;
|
|
398
|
+
const failure = new ConsolError("closed", "vault is closed");
|
|
399
|
+
for (const pending of this.pending.values())
|
|
400
|
+
pending.reject(failure);
|
|
401
|
+
this.pending.clear();
|
|
402
|
+
if (!child)
|
|
403
|
+
return;
|
|
404
|
+
await new Promise((resolve) => {
|
|
405
|
+
if (!child.pid || child.exitCode !== null || child.signalCode !== null) {
|
|
406
|
+
resolve();
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const terminate = setTimeout(() => {
|
|
410
|
+
try {
|
|
411
|
+
child.kill();
|
|
412
|
+
} catch {}
|
|
413
|
+
}, EXIT_WAIT_MS);
|
|
414
|
+
const force = setTimeout(() => {
|
|
415
|
+
try {
|
|
416
|
+
child.kill("SIGKILL");
|
|
417
|
+
} catch {}
|
|
418
|
+
}, KILL_WAIT_MS);
|
|
419
|
+
child.once("close", () => {
|
|
420
|
+
clearTimeout(terminate);
|
|
421
|
+
clearTimeout(force);
|
|
422
|
+
resolve();
|
|
423
|
+
});
|
|
424
|
+
try {
|
|
425
|
+
child.stdin?.end();
|
|
426
|
+
} catch {
|
|
427
|
+
resolve();
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async start() {
|
|
432
|
+
if (this.closed)
|
|
433
|
+
throw new ConsolError("closed", "vault is closed");
|
|
434
|
+
if (!this.starting && !this.child)
|
|
435
|
+
this.starting = this.spawn();
|
|
436
|
+
if (this.starting) {
|
|
437
|
+
try {
|
|
438
|
+
await this.starting;
|
|
439
|
+
} finally {
|
|
440
|
+
this.starting = null;
|
|
441
|
+
this.updateRef();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (this.closed)
|
|
445
|
+
throw new ConsolError("closed", "vault is closed");
|
|
446
|
+
}
|
|
447
|
+
async spawn() {
|
|
448
|
+
const generation = ++this.generation;
|
|
449
|
+
const child = import_node_child_process.spawn(this.binary, ["stdio", ...this.opts.location], {
|
|
450
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
451
|
+
env: childEnv(engineEnv(this.opts), this.opts.env)
|
|
452
|
+
});
|
|
453
|
+
this.child = child;
|
|
454
|
+
for (const stream of [child.stdin, child.stdout, child.stderr])
|
|
455
|
+
stream?.unref?.();
|
|
456
|
+
this.updateRef();
|
|
457
|
+
child.stderr?.on("data", (chunk) => this.onStderr(chunk));
|
|
458
|
+
child.stderr?.on("error", () => {});
|
|
459
|
+
const dead = (reason, cause) => {
|
|
460
|
+
if (this.child !== child)
|
|
461
|
+
return;
|
|
462
|
+
this.flushStderr();
|
|
463
|
+
const tail = this.stderrTail.slice(-3).join(" | ");
|
|
464
|
+
this.onDeath(new ConsolError("server_exited", `${reason}${tail ? `: ${tail}` : ""}`, {
|
|
465
|
+
stderrTail: this.tailText() || undefined,
|
|
466
|
+
...cause === undefined ? {} : { cause }
|
|
467
|
+
}));
|
|
468
|
+
};
|
|
469
|
+
child.once("error", (error) => {
|
|
470
|
+
if (error?.code === "ENOENT") {
|
|
471
|
+
if (this.child !== child)
|
|
472
|
+
return;
|
|
473
|
+
this.onDeath(new ConsolError("binary_not_found", `binary not found: ${this.binary}`, {
|
|
474
|
+
stderrTail: this.attempts.join(`
|
|
475
|
+
`),
|
|
476
|
+
cause: error
|
|
477
|
+
}));
|
|
478
|
+
} else {
|
|
479
|
+
dead(`server failed to start: ${error.message}`, error);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
child.once("exit", (code, signal) => {
|
|
483
|
+
dead(`server exited (${signal ?? code})`);
|
|
484
|
+
});
|
|
485
|
+
child.stdin?.on("error", () => {
|
|
486
|
+
dead("server stdin failed");
|
|
487
|
+
});
|
|
488
|
+
const reader = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
489
|
+
this.reader = reader;
|
|
490
|
+
reader.once("close", () => {
|
|
491
|
+
dead("server closed output");
|
|
492
|
+
});
|
|
493
|
+
reader.on("line", (line) => {
|
|
494
|
+
if (this.child === child && generation === this.generation)
|
|
495
|
+
this.onLine(line);
|
|
496
|
+
});
|
|
497
|
+
try {
|
|
498
|
+
checkVersion(await this.call("ready", READY, null, {}), this.opts.sdkVersion);
|
|
499
|
+
const boot = this.opts.boot;
|
|
500
|
+
if (boot) {
|
|
501
|
+
const op = boot.create && !this.started ? "create_vault" : "open_vault";
|
|
502
|
+
const reply = await this.call(op, this.nextId++, { op, vault: boot.vault }, {});
|
|
503
|
+
const error = replyError(reply);
|
|
504
|
+
if (error)
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
this.started = true;
|
|
508
|
+
this.stderrTail = [];
|
|
509
|
+
this.stderrTailBytes = 0;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (this.child === child) {
|
|
512
|
+
this.child = null;
|
|
513
|
+
this.reader?.close();
|
|
514
|
+
this.reader = null;
|
|
515
|
+
try {
|
|
516
|
+
child.kill();
|
|
517
|
+
} catch {}
|
|
518
|
+
}
|
|
519
|
+
throw error instanceof ConsolError ? error : new ConsolError("internal", String(error), { cause: error });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
onLine(line) {
|
|
523
|
+
if (!line.trim())
|
|
524
|
+
return;
|
|
525
|
+
let msg;
|
|
526
|
+
try {
|
|
527
|
+
msg = JSON.parse(line);
|
|
528
|
+
} catch {
|
|
529
|
+
this.onDeath(new ConsolError("server_exited", "invalid JSON from server", {
|
|
530
|
+
stderrTail: this.tailText() || undefined
|
|
531
|
+
}));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const id = msg?.ready === true ? READY : msg?.id;
|
|
535
|
+
if (typeof id !== "number")
|
|
536
|
+
return;
|
|
537
|
+
const pending = this.pending.get(id);
|
|
538
|
+
if (!pending || pending.settled)
|
|
539
|
+
return;
|
|
540
|
+
this.pending.delete(id);
|
|
541
|
+
if (id === READY) {
|
|
542
|
+
pending.resolve(msg.version);
|
|
543
|
+
} else {
|
|
544
|
+
try {
|
|
545
|
+
this.version = checkVersion(msg.reply?.version, this.opts.sdkVersion);
|
|
546
|
+
pending.resolve(msg.reply);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
pending.reject(error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
this.updateRef();
|
|
552
|
+
}
|
|
553
|
+
call(label, id, frame, options) {
|
|
554
|
+
return new Promise((resolve, reject) => {
|
|
555
|
+
const signal = options.signal;
|
|
556
|
+
if (signal?.aborted) {
|
|
557
|
+
reject(new ConsolError("aborted", `${label} aborted before dispatch`, { cause: signal.reason }));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
let line = null;
|
|
561
|
+
let timeoutMs;
|
|
562
|
+
try {
|
|
563
|
+
timeoutMs = deadline(options.timeoutMs, this.timeoutMs);
|
|
564
|
+
line = frame ? JSON.stringify({ id, ...frame }) + `
|
|
565
|
+
` : null;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
reject(ConsolError.is(error) ? error : new ConsolError("invalid_argument", `${label}: body is not JSON`, { cause: error }));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const input = this.child?.stdin;
|
|
571
|
+
if (line && !input?.writable) {
|
|
572
|
+
reject(new ConsolError(this.closed ? "closed" : "server_exited", "server is not running"));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const pending = { resolve, reject, settled: false };
|
|
576
|
+
const done = (error, value) => {
|
|
577
|
+
if (pending.settled)
|
|
578
|
+
return;
|
|
579
|
+
pending.settled = true;
|
|
580
|
+
if (pending.timer !== undefined)
|
|
581
|
+
clearTimeout(pending.timer);
|
|
582
|
+
if (pending.onAbort && signal)
|
|
583
|
+
signal.removeEventListener("abort", pending.onAbort);
|
|
584
|
+
this.pending.delete(id);
|
|
585
|
+
if (error)
|
|
586
|
+
reject(error);
|
|
587
|
+
else
|
|
588
|
+
resolve(value);
|
|
589
|
+
this.updateRef();
|
|
590
|
+
};
|
|
591
|
+
pending.resolve = (value) => done(null, value);
|
|
592
|
+
pending.reject = (error) => done(error);
|
|
593
|
+
if (timeoutMs > 0) {
|
|
594
|
+
pending.timer = setTimeout(() => done(new ConsolError("timeout", `${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
595
|
+
pending.timer.unref?.();
|
|
596
|
+
}
|
|
597
|
+
if (signal) {
|
|
598
|
+
pending.onAbort = () => done(new ConsolError("aborted", `${label} aborted`, { cause: signal.reason }));
|
|
599
|
+
signal.addEventListener("abort", pending.onAbort, { once: true });
|
|
600
|
+
}
|
|
601
|
+
this.pending.set(id, pending);
|
|
602
|
+
this.updateRef();
|
|
603
|
+
if (line) {
|
|
604
|
+
input.write(line, (error) => {
|
|
605
|
+
if (error)
|
|
606
|
+
done(new ConsolError("server_exited", "server stdin failed", { cause: error }));
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
async send(frame, options) {
|
|
612
|
+
if (options.signal?.aborted) {
|
|
613
|
+
throw new ConsolError("aborted", `${frame.op} aborted before dispatch`, { cause: options.signal.reason });
|
|
614
|
+
}
|
|
615
|
+
await this.start();
|
|
616
|
+
return await this.call(frame.op, this.nextId++, frame, options);
|
|
617
|
+
}
|
|
618
|
+
handle(body, ctx, options = {}) {
|
|
619
|
+
return this.send({ op: "handle", ctx: { vaults: ctx.vaults, agent: ctx.agent ?? null }, body }, options);
|
|
620
|
+
}
|
|
621
|
+
vaultOp(op, vault, options = {}) {
|
|
622
|
+
return this.send({ op, vault }, options);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/vault.ts
|
|
627
|
+
var import_node_path = require("node:path");
|
|
628
|
+
|
|
629
|
+
// src/types.ts
|
|
630
|
+
function sliceUtf8(text, start, end) {
|
|
631
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) {
|
|
632
|
+
throw new RangeError(`invalid byte range [${start}, ${end})`);
|
|
633
|
+
}
|
|
634
|
+
const bytes = Buffer.from(text, "utf8");
|
|
635
|
+
if (end > bytes.length) {
|
|
636
|
+
throw new RangeError(`byte range [${start}, ${end}) exceeds text length ${bytes.length}`);
|
|
637
|
+
}
|
|
638
|
+
const isContinuation = (b) => (b & 192) === 128;
|
|
639
|
+
if (start > 0 && isContinuation(bytes[start])) {
|
|
640
|
+
throw new RangeError(`start ${start} splits a UTF-8 character`);
|
|
641
|
+
}
|
|
642
|
+
if (end < bytes.length && isContinuation(bytes[end])) {
|
|
643
|
+
throw new RangeError(`end ${end} splits a UTF-8 character`);
|
|
644
|
+
}
|
|
645
|
+
return bytes.subarray(start, end).toString("utf8");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/vault.ts
|
|
649
|
+
var CLIENT_INFO = { name: "consol-js-client", version: "0.1.0" };
|
|
650
|
+
var LOCAL = "default";
|
|
651
|
+
function payload(tool, reply) {
|
|
652
|
+
const error = replyError(reply, tool);
|
|
653
|
+
if (error)
|
|
654
|
+
throw error;
|
|
655
|
+
if (tool === "recall")
|
|
656
|
+
return { ...reply.result, text: reply.text ?? "" };
|
|
657
|
+
return reply.result;
|
|
658
|
+
}
|
|
659
|
+
var construct;
|
|
660
|
+
|
|
661
|
+
class Vault {
|
|
662
|
+
transport;
|
|
663
|
+
vault;
|
|
664
|
+
agent;
|
|
665
|
+
static {
|
|
666
|
+
construct = (transport, vault, agent) => new Vault(transport, vault, agent);
|
|
667
|
+
}
|
|
668
|
+
constructor(transport, vault, agent) {
|
|
669
|
+
this.transport = transport;
|
|
670
|
+
this.vault = vault;
|
|
671
|
+
this.agent = agent;
|
|
672
|
+
}
|
|
673
|
+
get serverVersion() {
|
|
674
|
+
return this.transport.version;
|
|
675
|
+
}
|
|
676
|
+
close() {
|
|
677
|
+
return this.transport.close();
|
|
678
|
+
}
|
|
679
|
+
[Symbol.asyncDispose]() {
|
|
680
|
+
return this.close();
|
|
681
|
+
}
|
|
682
|
+
callTool(name, args, options) {
|
|
683
|
+
const agent = typeof args.agent === "string" ? args.agent : this.agent;
|
|
684
|
+
const ctx = { vaults: [this.vault], agent: null };
|
|
685
|
+
return this.transport.handle({ tool: name, vault: this.vault, agent, args }, ctx, options);
|
|
686
|
+
}
|
|
687
|
+
async recall(args, options) {
|
|
688
|
+
return payload("recall", await this.callTool("recall", { ...args }, options));
|
|
689
|
+
}
|
|
690
|
+
async read(args, options) {
|
|
691
|
+
return payload("read", await this.callTool("read", { ...args }, options));
|
|
692
|
+
}
|
|
693
|
+
async remember(args, options) {
|
|
694
|
+
return payload("remember", await this.callTool("remember", { ...args }, options));
|
|
695
|
+
}
|
|
696
|
+
async remove(args, options) {
|
|
697
|
+
return payload("remove", await this.callTool("remove", { ...args }, options));
|
|
698
|
+
}
|
|
699
|
+
async reflect(args = {}, options) {
|
|
700
|
+
return payload("reflect", await this.callTool("reflect", { ...args }, options));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function required(options, fields) {
|
|
704
|
+
for (const field of fields) {
|
|
705
|
+
const value = options?.[field];
|
|
706
|
+
if (typeof value !== "string" || value === "")
|
|
707
|
+
throw new ConsolError("invalid_argument", `${field} is required`, { field });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function local(options, create) {
|
|
711
|
+
required(options, ["path", "agent"]);
|
|
712
|
+
const path = options.path.startsWith("s3://") ? options.path : import_node_path.resolve(options.path);
|
|
713
|
+
const transport = new StdioTransport({
|
|
714
|
+
...options,
|
|
715
|
+
location: ["--vault", path],
|
|
716
|
+
boot: { vault: LOCAL, create },
|
|
717
|
+
sdkVersion: CLIENT_INFO.version
|
|
718
|
+
});
|
|
719
|
+
try {
|
|
720
|
+
await transport.start();
|
|
721
|
+
} catch (error) {
|
|
722
|
+
await transport.close();
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
return construct(transport, LOCAL, options.agent);
|
|
726
|
+
}
|
|
727
|
+
function createVault(options) {
|
|
728
|
+
return local(options, true);
|
|
729
|
+
}
|
|
730
|
+
async function openVault(options) {
|
|
731
|
+
const hosted = options;
|
|
732
|
+
if (hosted?.url === undefined)
|
|
733
|
+
return local(options, false);
|
|
734
|
+
required(options, ["url", "vault", "agent"]);
|
|
735
|
+
if (options.path !== undefined) {
|
|
736
|
+
throw new ConsolError("invalid_argument", "give either path (local) or url (hosted), not both", { field: "path" });
|
|
737
|
+
}
|
|
738
|
+
let url;
|
|
739
|
+
try {
|
|
740
|
+
url = new URL(hosted.url);
|
|
741
|
+
} catch {
|
|
742
|
+
throw new ConsolError("invalid_argument", "url is not a valid URL", { field: "url" });
|
|
743
|
+
}
|
|
744
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
745
|
+
throw new ConsolError("invalid_argument", "url must be http or https", { field: "url" });
|
|
746
|
+
}
|
|
747
|
+
const { vault, agent, headers = {}, timeoutMs } = options;
|
|
748
|
+
const transport = new HttpTransport(url.href, headers, timeoutMs ?? 300000, CLIENT_INFO.version);
|
|
749
|
+
return construct(transport, vault, agent);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/consol.ts
|
|
753
|
+
var STATUS = new Map;
|
|
754
|
+
for (const [status, codes] of [
|
|
755
|
+
[400, "invalid_argument secret_rejected"],
|
|
756
|
+
[403, "forbidden"],
|
|
757
|
+
[404, "not_found work_not_found vault_not_found"],
|
|
758
|
+
[409, "already_exists vault_exists version_conflict basis_mismatch already_decided unresolved_writes"],
|
|
759
|
+
[413, "content_too_large"],
|
|
760
|
+
[422, "unsupported postcondition_failed team_not_attached"],
|
|
761
|
+
[503, "vault_busy index_unavailable storage_unavailable"]
|
|
762
|
+
]) {
|
|
763
|
+
for (const code of codes.split(" "))
|
|
764
|
+
STATUS.set(code, status);
|
|
765
|
+
}
|
|
766
|
+
function httpStatus(reply) {
|
|
767
|
+
return reply.error ? STATUS.get(reply.error.code) ?? 500 : 200;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
class Consol {
|
|
771
|
+
transport;
|
|
772
|
+
constructor(transport) {
|
|
773
|
+
this.transport = transport;
|
|
774
|
+
}
|
|
775
|
+
static async start(options) {
|
|
776
|
+
const { vaultRoot, vault, ...engine } = options ?? {};
|
|
777
|
+
if (vaultRoot === undefined === (vault === undefined)) {
|
|
778
|
+
throw new ConsolError("invalid_argument", "give exactly one of vaultRoot or vault", { field: "vaultRoot" });
|
|
779
|
+
}
|
|
780
|
+
const transport = new StdioTransport({
|
|
781
|
+
...engine,
|
|
782
|
+
location: vaultRoot === undefined ? ["--vault", vault] : ["--vault-root", vaultRoot],
|
|
783
|
+
sdkVersion: CLIENT_INFO.version
|
|
784
|
+
});
|
|
785
|
+
try {
|
|
786
|
+
await transport.start();
|
|
787
|
+
} catch (error) {
|
|
788
|
+
await transport.close();
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
791
|
+
return new Consol(transport);
|
|
792
|
+
}
|
|
793
|
+
async handle(body, ctx, options) {
|
|
794
|
+
if (ctx === null || typeof ctx !== "object" || ctx.vaults !== null && !Array.isArray(ctx.vaults)) {
|
|
795
|
+
throw new ConsolError("invalid_argument", "ctx.vaults is a list of vault names, or null for any", { field: "ctx.vaults" });
|
|
796
|
+
}
|
|
797
|
+
if (ctx.agent !== undefined && ctx.agent !== null && typeof ctx.agent !== "string") {
|
|
798
|
+
throw new ConsolError("invalid_argument", "ctx.agent is a string", { field: "ctx.agent" });
|
|
799
|
+
}
|
|
800
|
+
return this.transport.handle(body, ctx, options);
|
|
801
|
+
}
|
|
802
|
+
async createVault(name, options) {
|
|
803
|
+
const error = replyError(await this.transport.vaultOp("create_vault", name, options));
|
|
804
|
+
if (error)
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
async openVault(name, options) {
|
|
808
|
+
const error = replyError(await this.transport.vaultOp("open_vault", name, options));
|
|
809
|
+
if (error)
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
close() {
|
|
813
|
+
return this.transport.close();
|
|
814
|
+
}
|
|
815
|
+
}
|