@telemai/sdk 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 +202 -0
- package/README.md +332 -0
- package/dist/index.cjs +789 -0
- package/dist/index.d.cts +369 -0
- package/dist/index.d.ts +369 -0
- package/dist/index.js +749 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TelemError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
details;
|
|
6
|
+
constructor(message, options = {}) {
|
|
7
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
8
|
+
this.name = "TelemError";
|
|
9
|
+
this.status = options.status;
|
|
10
|
+
this.code = options.code;
|
|
11
|
+
this.details = options.details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var TimeoutError = class extends TelemError {
|
|
15
|
+
constructor(message, cause) {
|
|
16
|
+
super(message, { code: "timeout", cause });
|
|
17
|
+
this.name = "TimeoutError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/version.ts
|
|
22
|
+
var VERSION = "0.1.0";
|
|
23
|
+
|
|
24
|
+
// src/config.ts
|
|
25
|
+
var DEFAULT_BASE_URL = "https://router.telem.ai";
|
|
26
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
27
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
28
|
+
var BLANK = /^[ \t\n\r\v\f\u00a0\ufeff\u3000]+|[ \t\n\r\v\f\u00a0\ufeff\u3000]+$/g;
|
|
29
|
+
function trimBlank(value) {
|
|
30
|
+
return value.replace(BLANK, "");
|
|
31
|
+
}
|
|
32
|
+
function csvEnv(raw) {
|
|
33
|
+
if (raw === void 0) return void 0;
|
|
34
|
+
const items = raw.split(",").map(trimBlank).filter((item) => item !== "");
|
|
35
|
+
return items.length ? items : void 0;
|
|
36
|
+
}
|
|
37
|
+
function readCredentials(env) {
|
|
38
|
+
try {
|
|
39
|
+
const proc = globalThis.process;
|
|
40
|
+
if (typeof proc?.getBuiltinModule !== "function") return {};
|
|
41
|
+
const home = trimBlank(env.HOME || env.USERPROFILE || "");
|
|
42
|
+
const dir = trimBlank(env.TELEM_CONFIG_DIR || "") || home && `${home}/.telem`;
|
|
43
|
+
if (!dir) return {};
|
|
44
|
+
const path = `${dir.replace(/[/\\]+$/, "")}/credentials.json`;
|
|
45
|
+
const bytes = proc.getBuiltinModule("node:fs").readFileSync(path);
|
|
46
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
47
|
+
const data = JSON.parse(text.replace(/^\ufeff/, ""));
|
|
48
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return {};
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const key of ["apiKey", "baseUrl"]) {
|
|
51
|
+
const value = data[key];
|
|
52
|
+
if (typeof value !== "string") continue;
|
|
53
|
+
const trimmed = trimBlank(value);
|
|
54
|
+
if (trimmed) out[key] = trimmed;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function invalidOption(message) {
|
|
62
|
+
return new TelemError(message, { code: "invalid_request" });
|
|
63
|
+
}
|
|
64
|
+
function resolveConfig(options) {
|
|
65
|
+
const opts = typeof options === "string" ? { apiKey: options } : options;
|
|
66
|
+
const env = globalThis.process?.env ?? {};
|
|
67
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
68
|
+
if (typeof timeoutMs !== "number" || !(timeoutMs > 0)) {
|
|
69
|
+
throw invalidOption(`timeoutMs must be a positive number of milliseconds (Infinity for no timeout), got ${String(timeoutMs)}`);
|
|
70
|
+
}
|
|
71
|
+
const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
72
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0) {
|
|
73
|
+
throw invalidOption(`maxRetries must be a whole number of retries, got ${String(maxRetries)}`);
|
|
74
|
+
}
|
|
75
|
+
const send = opts.fetch ?? globalThis.fetch;
|
|
76
|
+
if (typeof send !== "function") {
|
|
77
|
+
throw invalidOption("no fetch on this runtime: pass one with the `fetch` option");
|
|
78
|
+
}
|
|
79
|
+
let apiKey = opts.apiKey || env.TELEM_API_KEY || void 0;
|
|
80
|
+
let baseUrl = opts.baseUrl || env.TELEM_BASE_URL || void 0;
|
|
81
|
+
if (apiKey === void 0 || baseUrl === void 0) {
|
|
82
|
+
const credentials = readCredentials(env);
|
|
83
|
+
apiKey = apiKey ?? credentials.apiKey;
|
|
84
|
+
baseUrl = baseUrl ?? credentials.baseUrl;
|
|
85
|
+
}
|
|
86
|
+
const resolvedBaseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
87
|
+
let scheme;
|
|
88
|
+
try {
|
|
89
|
+
scheme = new URL(resolvedBaseUrl).protocol;
|
|
90
|
+
} catch {
|
|
91
|
+
scheme = void 0;
|
|
92
|
+
}
|
|
93
|
+
if (scheme !== "http:" && scheme !== "https:") {
|
|
94
|
+
throw invalidOption(`baseUrl must be an http(s) URL, got ${JSON.stringify(resolvedBaseUrl)}`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
apiKey,
|
|
98
|
+
baseUrl: resolvedBaseUrl,
|
|
99
|
+
timeoutMs,
|
|
100
|
+
maxRetries,
|
|
101
|
+
userAgent: opts.userAgent || `telem-sdk-js/${VERSION}`,
|
|
102
|
+
fetch: send,
|
|
103
|
+
sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
104
|
+
// Search defaults: constructor option, else env var, else unset. Only `undefined`
|
|
105
|
+
// is unset — `[]`/`false` are explicit values sent to the server verbatim.
|
|
106
|
+
// numResults/includeRaw/providerOverrides are per-call intent and are not read
|
|
107
|
+
// here at all, so passing one as a client default is inert.
|
|
108
|
+
defaults: {
|
|
109
|
+
tier: opts.tier ?? (env.TELEM_TIER || void 0),
|
|
110
|
+
fields: opts.fields ?? csvEnv(env.TELEM_FIELDS),
|
|
111
|
+
providersInclude: opts.providersInclude ?? csvEnv(env.TELEM_PROVIDERS_INCLUDE),
|
|
112
|
+
providersExclude: opts.providersExclude ?? csvEnv(env.TELEM_PROVIDERS_EXCLUDE),
|
|
113
|
+
includeFullContent: opts.includeFullContent ?? (env.TELEM_FULL_CONTENT === "1" || void 0)
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/http.ts
|
|
119
|
+
var MAX_DELAY_MS = 3e4;
|
|
120
|
+
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
121
|
+
var BASE_DELAY_MS = 500;
|
|
122
|
+
function isObject(value) {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
function asObject(value) {
|
|
126
|
+
return isObject(value) ? value : {};
|
|
127
|
+
}
|
|
128
|
+
function asArray(value) {
|
|
129
|
+
return Array.isArray(value) ? value : [];
|
|
130
|
+
}
|
|
131
|
+
function invalidRequest(message) {
|
|
132
|
+
return new TelemError(message, { code: "invalid_request" });
|
|
133
|
+
}
|
|
134
|
+
function stringList(items, empty, what) {
|
|
135
|
+
const list = asArray(items);
|
|
136
|
+
if (!list.length) throw invalidRequest(empty);
|
|
137
|
+
for (const item of list) {
|
|
138
|
+
if (typeof item !== "string") {
|
|
139
|
+
throw invalidRequest(`${what} must be strings, got ${item === null ? "null" : typeof item}`);
|
|
140
|
+
}
|
|
141
|
+
if (item === "") throw invalidRequest(`${what} must not be empty`);
|
|
142
|
+
}
|
|
143
|
+
return list;
|
|
144
|
+
}
|
|
145
|
+
function errorEnvelope(body) {
|
|
146
|
+
if (!isObject(body)) return void 0;
|
|
147
|
+
if (isObject(body.error)) return body.error;
|
|
148
|
+
if (isObject(body.detail)) return body.detail;
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
function detailFromBody(body) {
|
|
152
|
+
if (!isObject(body)) return void 0;
|
|
153
|
+
const envelope = errorEnvelope(body);
|
|
154
|
+
if (envelope && typeof envelope.message === "string" && envelope.message) {
|
|
155
|
+
return envelope.message;
|
|
156
|
+
}
|
|
157
|
+
const detail = body.detail;
|
|
158
|
+
if (typeof detail === "string" && detail) return detail;
|
|
159
|
+
if (Array.isArray(detail) && detail.length) {
|
|
160
|
+
const messages = detail.map((item) => isObject(item) && typeof item.msg === "string" ? item.msg : void 0).filter((message) => message !== void 0);
|
|
161
|
+
return messages.join(", ") || JSON.stringify(detail);
|
|
162
|
+
}
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
function codeFromBody(body) {
|
|
166
|
+
const envelope = errorEnvelope(body);
|
|
167
|
+
return envelope && typeof envelope.code === "string" ? envelope.code : void 0;
|
|
168
|
+
}
|
|
169
|
+
function isResponseLike(value) {
|
|
170
|
+
return isObject(value) && typeof value.text === "function" && typeof value.status === "number" && typeof value.headers?.get === "function";
|
|
171
|
+
}
|
|
172
|
+
var NotAResponse = class extends Error {
|
|
173
|
+
};
|
|
174
|
+
function defaultMessage(status) {
|
|
175
|
+
if (status === 400) return "Bad request";
|
|
176
|
+
if (status === 401 || status === 403) return "Authentication failed";
|
|
177
|
+
if (status === 404) return "Not found";
|
|
178
|
+
return `Unexpected status code ${status}`;
|
|
179
|
+
}
|
|
180
|
+
function statusError(status, body) {
|
|
181
|
+
return new TelemError(detailFromBody(body) ?? defaultMessage(status), {
|
|
182
|
+
status,
|
|
183
|
+
code: codeFromBody(body),
|
|
184
|
+
details: body
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function connectionError(url, cause) {
|
|
188
|
+
const error = cause;
|
|
189
|
+
const detail = typeof error?.cause?.message === "string" && error.cause.message || typeof error?.message === "string" && error.message || String(cause);
|
|
190
|
+
return new TelemError(`could not reach ${url}: ${detail}`, {
|
|
191
|
+
code: "connection_error",
|
|
192
|
+
cause
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function isRetryable(status) {
|
|
196
|
+
return status >= 500 || status === 429;
|
|
197
|
+
}
|
|
198
|
+
function backoff(attempt) {
|
|
199
|
+
return Math.min(BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS);
|
|
200
|
+
}
|
|
201
|
+
function retryDelay(retryAfter, attempt) {
|
|
202
|
+
if (retryAfter !== null && /^\s*\d+\s*$/.test(retryAfter)) {
|
|
203
|
+
return Math.min(Number(retryAfter) * 1e3, MAX_DELAY_MS);
|
|
204
|
+
}
|
|
205
|
+
return backoff(attempt);
|
|
206
|
+
}
|
|
207
|
+
async function request(config, method, path, body) {
|
|
208
|
+
const url = `${config.baseUrl}/${path.replace(/^\/+/, "")}`;
|
|
209
|
+
const headers = { "User-Agent": config.userAgent };
|
|
210
|
+
if (config.apiKey) headers.Authorization = `Bearer ${config.apiKey}`;
|
|
211
|
+
const init = { method, headers };
|
|
212
|
+
if (body !== void 0) {
|
|
213
|
+
headers["Content-Type"] = "application/json";
|
|
214
|
+
try {
|
|
215
|
+
init.body = JSON.stringify(body);
|
|
216
|
+
} catch (cause) {
|
|
217
|
+
throw invalidRequest(`request body is not JSON: ${cause?.message ?? String(cause)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (let attempt = 0; ; attempt++) {
|
|
221
|
+
const signal = Number.isFinite(config.timeoutMs) ? AbortSignal.timeout(Math.min(config.timeoutMs, MAX_TIMEOUT_MS)) : void 0;
|
|
222
|
+
let onAbort = () => {
|
|
223
|
+
};
|
|
224
|
+
const deadline = signal && new Promise((_, reject) => {
|
|
225
|
+
onAbort = () => reject(signal.reason);
|
|
226
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
227
|
+
});
|
|
228
|
+
const race = (work) => deadline ? Promise.race([work, deadline]) : work;
|
|
229
|
+
let received;
|
|
230
|
+
try {
|
|
231
|
+
const response = await race(config.fetch(url, { ...init, signal }));
|
|
232
|
+
if (!isResponseLike(response)) throw new NotAResponse();
|
|
233
|
+
const text = await race(response.text());
|
|
234
|
+
let parsed;
|
|
235
|
+
try {
|
|
236
|
+
parsed = JSON.parse(text);
|
|
237
|
+
} catch {
|
|
238
|
+
parsed = text;
|
|
239
|
+
}
|
|
240
|
+
received = {
|
|
241
|
+
status: response.status,
|
|
242
|
+
retryAfter: response.headers.get("retry-after"),
|
|
243
|
+
body: parsed
|
|
244
|
+
};
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
if (cause instanceof NotAResponse) {
|
|
247
|
+
throw new TelemError("the injected fetch returned something that is not a Response", {
|
|
248
|
+
code: "connection_error"
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (signal?.aborted) {
|
|
252
|
+
throw new TimeoutError(`request to ${url} timed out after ${config.timeoutMs}ms`, cause);
|
|
253
|
+
}
|
|
254
|
+
if (attempt >= config.maxRetries) throw connectionError(url, cause);
|
|
255
|
+
await config.sleep(backoff(attempt));
|
|
256
|
+
continue;
|
|
257
|
+
} finally {
|
|
258
|
+
signal?.removeEventListener("abort", onAbort);
|
|
259
|
+
}
|
|
260
|
+
if (received.status >= 200 && received.status < 300) return received.body;
|
|
261
|
+
if (isRetryable(received.status) && attempt < config.maxRetries) {
|
|
262
|
+
await config.sleep(retryDelay(received.retryAfter, attempt));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
throw statusError(received.status, received.body);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/trajectory.ts
|
|
270
|
+
var NS_TRAJECTORY = "443866ab-1b45-5ed8-979e-52fdad07b810";
|
|
271
|
+
var NONE = "none";
|
|
272
|
+
var RESERVED_METADATA = /* @__PURE__ */ new Set([
|
|
273
|
+
"message_history",
|
|
274
|
+
"session_key",
|
|
275
|
+
"fingerprint",
|
|
276
|
+
"node_key",
|
|
277
|
+
"kind",
|
|
278
|
+
"parent_node_key",
|
|
279
|
+
"ancestors"
|
|
280
|
+
]);
|
|
281
|
+
var ENCODER = new TextEncoder();
|
|
282
|
+
var NAMESPACE_BYTES = Uint8Array.from(
|
|
283
|
+
NS_TRAJECTORY.replaceAll("-", "").match(/../g) ?? [],
|
|
284
|
+
(pair) => parseInt(pair, 16)
|
|
285
|
+
);
|
|
286
|
+
function hex(bytes) {
|
|
287
|
+
let out = "";
|
|
288
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
async function digest(algorithm, bytes) {
|
|
292
|
+
return new Uint8Array(await crypto.subtle.digest(algorithm, bytes));
|
|
293
|
+
}
|
|
294
|
+
async function sha256(value) {
|
|
295
|
+
return hex(await digest("SHA-256", ENCODER.encode(value)));
|
|
296
|
+
}
|
|
297
|
+
function lp(value) {
|
|
298
|
+
return `${ENCODER.encode(value).length}:${value}`;
|
|
299
|
+
}
|
|
300
|
+
async function uuid5(name) {
|
|
301
|
+
const encoded = ENCODER.encode(name);
|
|
302
|
+
const input = new Uint8Array(NAMESPACE_BYTES.length + encoded.length);
|
|
303
|
+
input.set(NAMESPACE_BYTES);
|
|
304
|
+
input.set(encoded, NAMESPACE_BYTES.length);
|
|
305
|
+
const bytes = (await digest("SHA-1", input)).slice(0, 16);
|
|
306
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
307
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
308
|
+
const chars = hex(bytes);
|
|
309
|
+
return [
|
|
310
|
+
chars.slice(0, 8),
|
|
311
|
+
chars.slice(8, 12),
|
|
312
|
+
chars.slice(12, 16),
|
|
313
|
+
chars.slice(16, 20),
|
|
314
|
+
chars.slice(20)
|
|
315
|
+
].join("-");
|
|
316
|
+
}
|
|
317
|
+
async function fingerprint(harness, conversationId) {
|
|
318
|
+
return await sha256(lp(harness) + lp(conversationId));
|
|
319
|
+
}
|
|
320
|
+
async function sessionKey(harness, conversationId, windowId) {
|
|
321
|
+
const name = lp(harness) + lp(await sha256(conversationId)) + lp(windowId) + lp(NONE);
|
|
322
|
+
return await uuid5(name);
|
|
323
|
+
}
|
|
324
|
+
async function eventNodeKey(harness, session, messageId, toolCallId) {
|
|
325
|
+
const name = lp(harness) + lp(session) + lp(messageId) + lp(toolCallId) + lp("event");
|
|
326
|
+
return await uuid5(name);
|
|
327
|
+
}
|
|
328
|
+
async function snapshotNodeKey(harness, conversationId, messageId) {
|
|
329
|
+
const name = lp(harness) + lp(await sha256(conversationId)) + lp(messageId) + lp("snap");
|
|
330
|
+
return await uuid5(name);
|
|
331
|
+
}
|
|
332
|
+
async function contentAnchor(...parts) {
|
|
333
|
+
return await sha256(parts.map(lp).join(""));
|
|
334
|
+
}
|
|
335
|
+
var HISTORY_TEXT_CAP = 128e3;
|
|
336
|
+
function cap(value) {
|
|
337
|
+
if (value.length <= HISTORY_TEXT_CAP) return value;
|
|
338
|
+
return Array.from(value).slice(0, HISTORY_TEXT_CAP).join("");
|
|
339
|
+
}
|
|
340
|
+
function pyStr(value) {
|
|
341
|
+
if (typeof value === "string") return value;
|
|
342
|
+
if (value === null) return "None";
|
|
343
|
+
if (value === true) return "True";
|
|
344
|
+
if (value === false) return "False";
|
|
345
|
+
return String(value);
|
|
346
|
+
}
|
|
347
|
+
function messageText(content) {
|
|
348
|
+
if (typeof content === "string") return cap(content);
|
|
349
|
+
if (Array.isArray(content)) {
|
|
350
|
+
const pieces = [];
|
|
351
|
+
for (const block of content) {
|
|
352
|
+
if (typeof block === "string") pieces.push(block);
|
|
353
|
+
else if (isObject(block) && typeof block.text === "string") pieces.push(block.text);
|
|
354
|
+
}
|
|
355
|
+
return cap(pieces.join("\n"));
|
|
356
|
+
}
|
|
357
|
+
return content === null || content === void 0 ? "" : cap(pyStr(content));
|
|
358
|
+
}
|
|
359
|
+
async function windowAnchor(messages) {
|
|
360
|
+
const first = messages[0];
|
|
361
|
+
if (first === void 0) return NONE;
|
|
362
|
+
return await contentAnchor(
|
|
363
|
+
first.role === void 0 ? "" : pyStr(first.role),
|
|
364
|
+
messageText(first.content)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
async function buildMetadata(args) {
|
|
368
|
+
const [session, fp] = await Promise.all([
|
|
369
|
+
sessionKey(args.harness, args.conversationId, args.windowId),
|
|
370
|
+
fingerprint(args.harness, args.conversationId)
|
|
371
|
+
]);
|
|
372
|
+
return {
|
|
373
|
+
message_history: args.history,
|
|
374
|
+
session_key: session,
|
|
375
|
+
fingerprint: fp,
|
|
376
|
+
node_key: await eventNodeKey(args.harness, session, args.messageId, args.toolCallId),
|
|
377
|
+
parent_node_key: parentNodeKey(args.ancestors),
|
|
378
|
+
ancestors: args.ancestors
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async function buildSnapshot(args) {
|
|
382
|
+
const [session, fp, node] = await Promise.all([
|
|
383
|
+
sessionKey(args.harness, args.conversationId, args.windowId),
|
|
384
|
+
fingerprint(args.harness, args.conversationId),
|
|
385
|
+
snapshotNodeKey(args.harness, args.conversationId, args.messageId)
|
|
386
|
+
]);
|
|
387
|
+
return {
|
|
388
|
+
session_key: session,
|
|
389
|
+
fingerprint: fp,
|
|
390
|
+
node_key: node,
|
|
391
|
+
parent_node_key: parentNodeKey(args.ancestors),
|
|
392
|
+
context: args.context,
|
|
393
|
+
spawned_at: args.spawnedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function parentNodeKey(ancestors) {
|
|
397
|
+
return ancestors.at(-1)?.node_key ?? null;
|
|
398
|
+
}
|
|
399
|
+
var TRAJECTORY = /* @__PURE__ */ Symbol.for("telem.trajectory");
|
|
400
|
+
function withTrajectory(options, envelope) {
|
|
401
|
+
return { ...options, [TRAJECTORY]: envelope };
|
|
402
|
+
}
|
|
403
|
+
function wireMetadata(options, kind) {
|
|
404
|
+
const metadata = {};
|
|
405
|
+
for (const [key, value] of Object.entries(options.metadata ?? {})) {
|
|
406
|
+
if (!RESERVED_METADATA.has(key)) metadata[key] = value;
|
|
407
|
+
}
|
|
408
|
+
const envelope = options[TRAJECTORY];
|
|
409
|
+
if (envelope !== void 0) Object.assign(metadata, envelope, { kind });
|
|
410
|
+
const sessionId = metadata.session_key === void 0 ? options.session : void 0;
|
|
411
|
+
return { metadata, sessionId };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/methods/fetch.ts
|
|
415
|
+
function fetchBody(url, options) {
|
|
416
|
+
const urls = stringList(
|
|
417
|
+
typeof url === "string" ? [url] : url,
|
|
418
|
+
"fetch() requires at least one URL",
|
|
419
|
+
"fetch() urls"
|
|
420
|
+
);
|
|
421
|
+
const { metadata, sessionId } = wireMetadata(options, "fetch");
|
|
422
|
+
const body = { urls, metadata };
|
|
423
|
+
const block = {};
|
|
424
|
+
if (options.providers !== void 0) block.providers = options.providers;
|
|
425
|
+
if (options.inlineContent !== void 0) block.inline_content = options.inlineContent;
|
|
426
|
+
if (options.inlineMaxChars !== void 0) block.inline_max_chars = options.inlineMaxChars;
|
|
427
|
+
if (options.contentFormat !== void 0) block.content_format = options.contentFormat;
|
|
428
|
+
if (Object.keys(block).length) body.options = block;
|
|
429
|
+
if (sessionId !== void 0) body.session_id = sessionId;
|
|
430
|
+
return body;
|
|
431
|
+
}
|
|
432
|
+
function fetchResult(run) {
|
|
433
|
+
const row = asObject(asArray(asObject(run.output_payload).fetched_results)[0]);
|
|
434
|
+
return {
|
|
435
|
+
url: row.url || run.query || "",
|
|
436
|
+
canonicalUrl: row.canonical_url ?? null,
|
|
437
|
+
title: row.title || "",
|
|
438
|
+
status: row.status || run.status || "",
|
|
439
|
+
content: row.content ?? null,
|
|
440
|
+
contentTruncated: row.content_truncated ?? null,
|
|
441
|
+
contentFormat: row.content_format ?? null,
|
|
442
|
+
contentSha256: row.content_sha256 ?? null,
|
|
443
|
+
contentType: row.content_type ?? null,
|
|
444
|
+
contentRef: row.content_ref ?? null,
|
|
445
|
+
provider: row.provider || "",
|
|
446
|
+
httpStatus: row.http_status ?? null,
|
|
447
|
+
fetchedAt: row.fetched_at ?? null,
|
|
448
|
+
// Key presence, not truthiness: a row that really reported 0 ms keeps its 0.
|
|
449
|
+
latencyMs: ("latency_ms" in row ? row.latency_ms : run.latency_ms) ?? null,
|
|
450
|
+
providerMetadata: row.provider_metadata ?? null,
|
|
451
|
+
error: row.error || run.error || null,
|
|
452
|
+
batchIndex: run.batch_index ?? 0,
|
|
453
|
+
raw: row
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function buildFetchResponse(interaction) {
|
|
457
|
+
const body = asObject(interaction);
|
|
458
|
+
const runs = asArray(body.preprocessor_runs).filter((run) => isObject(run) && run.preprocessor_name === "web_fetch").sort((left, right) => (left.batch_index || 0) - (right.batch_index || 0));
|
|
459
|
+
return {
|
|
460
|
+
results: runs.map(fetchResult),
|
|
461
|
+
sessionId: String(body.session_id ?? ""),
|
|
462
|
+
interactionId: String(body.interaction_id ?? ""),
|
|
463
|
+
status: body.status ?? "",
|
|
464
|
+
raw: body
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
async function fetchUrls(config, url, options) {
|
|
468
|
+
const body = fetchBody(url, options);
|
|
469
|
+
return buildFetchResponse(await request(config, "POST", "/v1/fetch", body));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/methods/providers.ts
|
|
473
|
+
async function providers(config) {
|
|
474
|
+
const body = await request(config, "GET", "/v1/preprocessors");
|
|
475
|
+
return asArray(asObject(body).preprocessors).filter(isObject).map((entry) => ({
|
|
476
|
+
name: entry.name ?? "",
|
|
477
|
+
type: entry.type ?? "",
|
|
478
|
+
activeByDefault: entry.active_by_default ?? false,
|
|
479
|
+
description: entry.description ?? null,
|
|
480
|
+
method: entry.method ?? null,
|
|
481
|
+
url: entry.url ?? null,
|
|
482
|
+
normalized: entry.normalized ?? false,
|
|
483
|
+
tiers: asArray(entry.tiers)
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/methods/search.ts
|
|
488
|
+
var MIN_SCHEMA_VERSION = 2;
|
|
489
|
+
function searchBlock(defaults, options) {
|
|
490
|
+
const fromCallTier = options.tier !== void 0;
|
|
491
|
+
const fromCallFields = options.fields !== void 0;
|
|
492
|
+
let tier = fromCallTier ? options.tier : defaults.tier;
|
|
493
|
+
let fields = fromCallFields ? options.fields : defaults.fields;
|
|
494
|
+
if (tier !== void 0 && fields !== void 0) {
|
|
495
|
+
if (fromCallTier && !fromCallFields) fields = void 0;
|
|
496
|
+
else tier = void 0;
|
|
497
|
+
}
|
|
498
|
+
let include = options.providersInclude ?? defaults.providersInclude;
|
|
499
|
+
let exclude = options.providersExclude ?? defaults.providersExclude;
|
|
500
|
+
if (include !== void 0 && exclude !== void 0) {
|
|
501
|
+
const excluded = new Set(exclude);
|
|
502
|
+
include = include.filter((name) => !excluded.has(name));
|
|
503
|
+
exclude = void 0;
|
|
504
|
+
}
|
|
505
|
+
const includeFullContent = options.includeFullContent ?? defaults.includeFullContent;
|
|
506
|
+
const providers2 = {};
|
|
507
|
+
if (include !== void 0) providers2.include = include;
|
|
508
|
+
if (exclude !== void 0) providers2.exclude = exclude;
|
|
509
|
+
const block = {};
|
|
510
|
+
if (tier !== void 0) block.tier = tier;
|
|
511
|
+
if (fields !== void 0) block.fields = fields;
|
|
512
|
+
if (Object.keys(providers2).length) block.providers = providers2;
|
|
513
|
+
if (options.providerOverrides !== void 0) block.provider_overrides = options.providerOverrides;
|
|
514
|
+
if (options.numResults !== void 0) block.num_results = options.numResults;
|
|
515
|
+
if (options.includeRaw !== void 0) block.include_raw = options.includeRaw;
|
|
516
|
+
if (options.rerank !== void 0) block.rerank = options.rerank;
|
|
517
|
+
if (includeFullContent !== void 0) block.include_full_content = includeFullContent;
|
|
518
|
+
return block;
|
|
519
|
+
}
|
|
520
|
+
function userInput(query) {
|
|
521
|
+
if (typeof query === "string") {
|
|
522
|
+
if (query === "") throw invalidRequest("search() requires a non-empty query");
|
|
523
|
+
return { query };
|
|
524
|
+
}
|
|
525
|
+
const queries = stringList(query, "search() requires at least one query", "search() queries");
|
|
526
|
+
if (queries.length === 1) return { query: queries[0] };
|
|
527
|
+
return queries.map((item) => ({ query: item }));
|
|
528
|
+
}
|
|
529
|
+
function searchBody(config, query, options) {
|
|
530
|
+
const { metadata, sessionId } = wireMetadata(options, "search");
|
|
531
|
+
if (options.goal !== void 0) metadata.goal = options.goal;
|
|
532
|
+
if (options.context !== void 0) metadata.context = options.context;
|
|
533
|
+
const body = {
|
|
534
|
+
user_input: userInput(query),
|
|
535
|
+
postprocessor_names: [],
|
|
536
|
+
metadata
|
|
537
|
+
};
|
|
538
|
+
const block = searchBlock(config.defaults, options);
|
|
539
|
+
if (Object.keys(block).length) body.search = block;
|
|
540
|
+
if (sessionId !== void 0) body.session_id = sessionId;
|
|
541
|
+
return body;
|
|
542
|
+
}
|
|
543
|
+
function assertV2(data) {
|
|
544
|
+
let observed;
|
|
545
|
+
if (isObject(data)) {
|
|
546
|
+
const version = data.normalized_schema_version;
|
|
547
|
+
if (typeof version === "number" && Number.isInteger(version) && version >= MIN_SCHEMA_VERSION) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
observed = version === void 0 ? "undefined" : JSON.stringify(version);
|
|
551
|
+
} else {
|
|
552
|
+
observed = Array.isArray(data) ? "array" : data === null ? "null" : typeof data;
|
|
553
|
+
}
|
|
554
|
+
throw new TelemError(
|
|
555
|
+
`server echoed normalized_schema_version=${observed}; expected >= ${MIN_SCHEMA_VERSION} \u2014 the server pre-dates the V2 normalized contract, or is a dev deployment with no adapter-backed search providers configured; point the SDK at a provider-configured V2 deployment`,
|
|
556
|
+
{ code: "unsupported_server_version" }
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
function searchResults(envelope, provider) {
|
|
560
|
+
return asArray(envelope.results).filter(isObject).map((row) => ({
|
|
561
|
+
url: row.url || "",
|
|
562
|
+
title: row.title || "",
|
|
563
|
+
summary: row.summary ?? null,
|
|
564
|
+
excerpt: row.excerpt ?? null,
|
|
565
|
+
fullContent: row.full_content ?? null,
|
|
566
|
+
publishDate: row.publish_date ?? null,
|
|
567
|
+
rank: row.rank ?? null,
|
|
568
|
+
thumbnail: row.thumbnail ?? null,
|
|
569
|
+
favicon: row.favicon ?? null,
|
|
570
|
+
source: row.source ?? null,
|
|
571
|
+
enrichments: row.enrichments ?? null,
|
|
572
|
+
fetchMeta: row.fetch_meta ?? null,
|
|
573
|
+
provider,
|
|
574
|
+
raw: row
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
function providerRun(run) {
|
|
578
|
+
const row = asObject(run);
|
|
579
|
+
const envelope = asObject(row.output_payload);
|
|
580
|
+
const provider = row.preprocessor_name ?? "";
|
|
581
|
+
return {
|
|
582
|
+
// -- from the run row --
|
|
583
|
+
provider,
|
|
584
|
+
status: row.status ?? "",
|
|
585
|
+
error: row.error ?? null,
|
|
586
|
+
latencyMs: row.latency_ms ?? null,
|
|
587
|
+
preprocessorRunId: row.id ? String(row.id) : null,
|
|
588
|
+
query: row.query ?? "",
|
|
589
|
+
batchIndex: row.batch_index ?? 0,
|
|
590
|
+
raw: row.raw_payload ?? null,
|
|
591
|
+
// -- from the envelope interior --
|
|
592
|
+
results: searchResults(envelope, provider),
|
|
593
|
+
tier: envelope.tier ?? null,
|
|
594
|
+
fields: asArray(envelope.fields),
|
|
595
|
+
answer: envelope.answer ?? null,
|
|
596
|
+
entities: envelope.entities ?? null,
|
|
597
|
+
related: envelope.related ?? null,
|
|
598
|
+
verticals: envelope.verticals ?? null,
|
|
599
|
+
usage: envelope.usage ?? null,
|
|
600
|
+
warnings: asArray(envelope.warnings)
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function buildSearchResponse(interaction) {
|
|
604
|
+
const body = asObject(interaction);
|
|
605
|
+
const byProvider = asArray(body.preprocessor_runs).map(providerRun);
|
|
606
|
+
return {
|
|
607
|
+
results: byProvider.flatMap((run) => run.results),
|
|
608
|
+
byProvider,
|
|
609
|
+
sessionId: String(body.session_id ?? ""),
|
|
610
|
+
interactionId: String(body.interaction_id ?? ""),
|
|
611
|
+
status: body.status ?? "",
|
|
612
|
+
normalizedSchemaVersion: body.normalized_schema_version ?? null,
|
|
613
|
+
raw: body
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async function search(config, query, options) {
|
|
617
|
+
const body = searchBody(config, query, options);
|
|
618
|
+
const data = await request(config, "POST", "/v1/interactions", body);
|
|
619
|
+
assertV2(data);
|
|
620
|
+
return buildSearchResponse(data);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/methods/sessions.ts
|
|
624
|
+
function sessionsFrom(body) {
|
|
625
|
+
return asArray(asObject(body).sessions).filter(isObject).map((entry) => ({
|
|
626
|
+
id: String(entry.id ?? ""),
|
|
627
|
+
createdAt: entry.created_at ?? null,
|
|
628
|
+
updatedAt: entry.updated_at ?? null,
|
|
629
|
+
interactionCount: entry.interaction_count ?? 0,
|
|
630
|
+
latestInteractionAt: entry.latest_interaction_at ?? null,
|
|
631
|
+
raw: entry
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
function resultsFrom(body) {
|
|
635
|
+
const data = asObject(body);
|
|
636
|
+
return {
|
|
637
|
+
sessionId: String(data.session_id ?? ""),
|
|
638
|
+
query: data.query || "",
|
|
639
|
+
goal: data.goal || "",
|
|
640
|
+
context: data.context || "",
|
|
641
|
+
previousPreprocessorResults: asArray(data.previous_preprocessor_results),
|
|
642
|
+
raw: data
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function createSessions(config) {
|
|
646
|
+
const path = (sessionId, suffix) => {
|
|
647
|
+
if (!sessionId || /^\.+$/.test(sessionId)) throw invalidRequest("a session id is required");
|
|
648
|
+
return `/v1/sessions/${encodeURIComponent(sessionId)}/${suffix}`;
|
|
649
|
+
};
|
|
650
|
+
return {
|
|
651
|
+
/**
|
|
652
|
+
* List the caller's sessions, most recent first.
|
|
653
|
+
*
|
|
654
|
+
* Requires an API key: sessions are per-caller, and a keyless client gets the
|
|
655
|
+
* server's 401 as a `TelemError` with `status: 401`. There is no client-side
|
|
656
|
+
* pre-flight check.
|
|
657
|
+
*
|
|
658
|
+
* @returns One summary per session, in the server's own order.
|
|
659
|
+
*/
|
|
660
|
+
async list() {
|
|
661
|
+
return sessionsFrom(await request(config, "GET", "/v1/sessions"));
|
|
662
|
+
},
|
|
663
|
+
/**
|
|
664
|
+
* Fetch the aggregated websearch preprocessor results for a session.
|
|
665
|
+
*
|
|
666
|
+
* @param sessionId - The session id.
|
|
667
|
+
* @returns The session's query, goal and context plus the per-step results.
|
|
668
|
+
*/
|
|
669
|
+
async results(sessionId) {
|
|
670
|
+
const route = path(sessionId, "websearch-preprocessor-results");
|
|
671
|
+
return resultsFrom(await request(config, "GET", route));
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/index.ts
|
|
677
|
+
var Telem = class {
|
|
678
|
+
/** Resolved API key, or `undefined` for an anonymous client. */
|
|
679
|
+
apiKey;
|
|
680
|
+
/** Resolved base URL, trailing slash stripped. */
|
|
681
|
+
baseUrl;
|
|
682
|
+
/** Resolved per-request timeout in milliseconds. */
|
|
683
|
+
timeoutMs;
|
|
684
|
+
/** Resolved retry budget (attempts after the first). */
|
|
685
|
+
maxRetries;
|
|
686
|
+
/** The session read surface: `list()` and `results(id)`. */
|
|
687
|
+
sessions;
|
|
688
|
+
#config;
|
|
689
|
+
/**
|
|
690
|
+
* @param options - An API key string, or an options object. Anything left unset
|
|
691
|
+
* resolves from `TELEM_*` env vars, then `~/.telem/credentials.json` (credentials
|
|
692
|
+
* only, read lazily), then the defaults.
|
|
693
|
+
*/
|
|
694
|
+
constructor(options = {}) {
|
|
695
|
+
const config = resolveConfig(options);
|
|
696
|
+
this.#config = config;
|
|
697
|
+
this.apiKey = config.apiKey;
|
|
698
|
+
this.baseUrl = config.baseUrl;
|
|
699
|
+
this.timeoutMs = config.timeoutMs;
|
|
700
|
+
this.maxRetries = config.maxRetries;
|
|
701
|
+
this.sessions = createSessions(config);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Run a web search.
|
|
705
|
+
*
|
|
706
|
+
* @param query - One query, or an array of queries batched into one interaction.
|
|
707
|
+
* @param options - Per-call options; each overrides the matching client default.
|
|
708
|
+
* `null` and `undefined` both mean none.
|
|
709
|
+
* @returns The flattened results plus the per-provider breakdown.
|
|
710
|
+
*/
|
|
711
|
+
async search(query, options) {
|
|
712
|
+
return await search(this.#config, query, options ?? {});
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Fetch the readable content of one or more URLs.
|
|
716
|
+
*
|
|
717
|
+
* @param url - One URL, or an array of URLs fetched in one request.
|
|
718
|
+
* @param options - Per-call fetch options. `null` and `undefined` both mean none.
|
|
719
|
+
* @returns One result per requested URL, in request order.
|
|
720
|
+
*/
|
|
721
|
+
async fetch(url, options) {
|
|
722
|
+
return await fetchUrls(this.#config, url, options ?? {});
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* List the search providers this deployment can run.
|
|
726
|
+
*
|
|
727
|
+
* @returns One entry per configured provider, in the server's own order.
|
|
728
|
+
*/
|
|
729
|
+
async providers() {
|
|
730
|
+
return await providers(this.#config);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
var index_default = Telem;
|
|
734
|
+
export {
|
|
735
|
+
RESERVED_METADATA,
|
|
736
|
+
Telem,
|
|
737
|
+
TelemError,
|
|
738
|
+
TimeoutError,
|
|
739
|
+
buildMetadata,
|
|
740
|
+
buildSnapshot,
|
|
741
|
+
contentAnchor,
|
|
742
|
+
index_default as default,
|
|
743
|
+
eventNodeKey,
|
|
744
|
+
fingerprint,
|
|
745
|
+
sessionKey,
|
|
746
|
+
snapshotNodeKey,
|
|
747
|
+
windowAnchor,
|
|
748
|
+
withTrajectory
|
|
749
|
+
};
|