@telemai/sdk 0.1.0 → 0.2.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/README.md +42 -2
- package/dist/chunk-RYY4S5LP.js +373 -0
- package/dist/index.cjs +80 -46
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +26 -318
- package/dist/vercel/index.cjs +746 -0
- package/dist/vercel/index.d.cts +269 -0
- package/dist/vercel/index.d.ts +269 -0
- package/dist/vercel/index.js +583 -0
- package/package.json +28 -4
package/README.md
CHANGED
|
@@ -198,8 +198,38 @@ console.log(summary.query, summary.goal, summary.context)
|
|
|
198
198
|
`results` returns the query, the goal and the context of the session, plus the
|
|
199
199
|
results of each search step.
|
|
200
200
|
|
|
201
|
+
## Vercel AI SDK
|
|
202
|
+
|
|
203
|
+
If your agent runs on the Vercel AI SDK, `@telemai/sdk/vercel` gives it
|
|
204
|
+
`telem_search` and `telem_fetch` tools with query lineage attached to every call,
|
|
205
|
+
subagents included. Build the tool set once per conversation and pass it to
|
|
206
|
+
`generateText`:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { generateText } from "ai"
|
|
210
|
+
import Telem from "@telemai/sdk"
|
|
211
|
+
import { createTelemVercelTools } from "@telemai/sdk/vercel"
|
|
212
|
+
|
|
213
|
+
const telem = new Telem()
|
|
214
|
+
const tools = createTelemVercelTools({ telem, harness: "my-app" })
|
|
215
|
+
|
|
216
|
+
const result = await generateText({ model, tools, prompt: "Is the Nintendo Museum open over New Year?" })
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
A subagent is a `generateText` started inside one of your tools. Pass that tool to
|
|
220
|
+
`createTelemVercelTools` and use the same tool set inside the subagent; its calls
|
|
221
|
+
then appear in the console under the tool call that started it. `ai` version 5
|
|
222
|
+
or later is an optional peer dependency, used by this subpath only. The full
|
|
223
|
+
guide, subagents, your own tools, and the options, is on the
|
|
224
|
+
[Vercel AI SDK page](https://docs.telem.ai/integrations/vercel-ai/). The runnable example is `examples/vercel-ai-agents.mjs`
|
|
225
|
+
in the SDK source, first in the [examples list](https://github.com/TelemAI/TelemSDK/tree/main/js-sdk/examples).
|
|
226
|
+
|
|
201
227
|
## Query lineage
|
|
202
228
|
|
|
229
|
+
If your agent uses the Vercel AI SDK, use
|
|
230
|
+
[`@telemai/sdk/vercel`](https://docs.telem.ai/integrations/vercel-ai/). It attaches lineage data to
|
|
231
|
+
every call by itself, subagents included, and you build nothing below by hand.
|
|
232
|
+
|
|
203
233
|
Every search and fetch from an agent can carry **lineage data**. Lineage data tells
|
|
204
234
|
Telem which conversation made the call, and where in the conversation it happened.
|
|
205
235
|
The console then shows all the calls of one conversation together, in order, and
|
|
@@ -245,20 +275,30 @@ const windowId = await windowAnchor(messages) // once, from the first message
|
|
|
245
275
|
const completion = await openai.chat.completions.create({ model: "gpt-4o", messages, tools })
|
|
246
276
|
for (const call of completion.choices[0].message.tool_calls ?? []) {
|
|
247
277
|
if (call.type !== "function") continue
|
|
278
|
+
const { query, goal, context } = JSON.parse(call.function.arguments)
|
|
248
279
|
const lineage = await buildMetadata({
|
|
249
280
|
harness: "my-app", // a stable name for your integration
|
|
250
281
|
conversationId,
|
|
251
282
|
windowId,
|
|
252
283
|
messageId: completion.id, // this model turn
|
|
253
284
|
toolCallId: call.id, // this tool call
|
|
254
|
-
history:
|
|
285
|
+
history: [ // [{ role, content }]: the conversation so far, ending with this call
|
|
286
|
+
...messages,
|
|
287
|
+
{ role: "assistant", content: toolMarker("telem_search", "running", { query, goal, context }) },
|
|
288
|
+
],
|
|
255
289
|
ancestors: [], // the parent snapshot; [] for the first agent
|
|
256
290
|
})
|
|
257
|
-
const { query, goal, context } = JSON.parse(call.function.arguments)
|
|
258
291
|
const response = await telem.search(query, withTrajectory({ goal, context }, lineage))
|
|
259
292
|
}
|
|
260
293
|
```
|
|
261
294
|
|
|
295
|
+
`history` has a rule the console depends on, and the example follows it on its
|
|
296
|
+
`history` line: write each tool call into the history as a marker with
|
|
297
|
+
`toolMarker(name, status, input)`, and end the history of every call with the
|
|
298
|
+
call being made, marked `running`. When a call completes, keep its marker in the
|
|
299
|
+
history as `completed`. The console reads the conversation's goals from those
|
|
300
|
+
markers, so build them with the helper rather than by hand.
|
|
301
|
+
|
|
262
302
|
`withTrajectory` attaches the lineage data to one call. Use it the same way with
|
|
263
303
|
`telem.fetch`. When lineage data is present, the client ignores a `session` id.
|
|
264
304
|
|
|
@@ -0,0 +1,373 @@
|
|
|
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/http.ts
|
|
22
|
+
var MAX_DELAY_MS = 3e4;
|
|
23
|
+
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
24
|
+
var BASE_DELAY_MS = 500;
|
|
25
|
+
function isObject(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function asObject(value) {
|
|
29
|
+
return isObject(value) ? value : {};
|
|
30
|
+
}
|
|
31
|
+
function asArray(value) {
|
|
32
|
+
return Array.isArray(value) ? value : [];
|
|
33
|
+
}
|
|
34
|
+
function invalidRequest(message) {
|
|
35
|
+
return new TelemError(message, { code: "invalid_request" });
|
|
36
|
+
}
|
|
37
|
+
function stringList(items, empty, what) {
|
|
38
|
+
const list = asArray(items);
|
|
39
|
+
if (!list.length) throw invalidRequest(empty);
|
|
40
|
+
for (const item of list) {
|
|
41
|
+
if (typeof item !== "string") {
|
|
42
|
+
throw invalidRequest(`${what} must be strings, got ${item === null ? "null" : typeof item}`);
|
|
43
|
+
}
|
|
44
|
+
if (item === "") throw invalidRequest(`${what} must not be empty`);
|
|
45
|
+
}
|
|
46
|
+
return list;
|
|
47
|
+
}
|
|
48
|
+
function errorEnvelope(body) {
|
|
49
|
+
if (!isObject(body)) return void 0;
|
|
50
|
+
if (isObject(body.error)) return body.error;
|
|
51
|
+
if (isObject(body.detail)) return body.detail;
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
function detailFromBody(body) {
|
|
55
|
+
if (!isObject(body)) return void 0;
|
|
56
|
+
const envelope = errorEnvelope(body);
|
|
57
|
+
if (envelope && typeof envelope.message === "string" && envelope.message) {
|
|
58
|
+
return envelope.message;
|
|
59
|
+
}
|
|
60
|
+
const detail = body.detail;
|
|
61
|
+
if (typeof detail === "string" && detail) return detail;
|
|
62
|
+
if (Array.isArray(detail) && detail.length) {
|
|
63
|
+
const messages = detail.map((item) => isObject(item) && typeof item.msg === "string" ? item.msg : void 0).filter((message) => message !== void 0);
|
|
64
|
+
return messages.join(", ") || JSON.stringify(detail);
|
|
65
|
+
}
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
function codeFromBody(body) {
|
|
69
|
+
const envelope = errorEnvelope(body);
|
|
70
|
+
return envelope && typeof envelope.code === "string" ? envelope.code : void 0;
|
|
71
|
+
}
|
|
72
|
+
function isResponseLike(value) {
|
|
73
|
+
return isObject(value) && typeof value.text === "function" && typeof value.status === "number" && typeof value.headers?.get === "function";
|
|
74
|
+
}
|
|
75
|
+
var NotAResponse = class extends Error {
|
|
76
|
+
};
|
|
77
|
+
function defaultMessage(status) {
|
|
78
|
+
if (status === 400) return "Bad request";
|
|
79
|
+
if (status === 401 || status === 403) return "Authentication failed";
|
|
80
|
+
if (status === 404) return "Not found";
|
|
81
|
+
return `Unexpected status code ${status}`;
|
|
82
|
+
}
|
|
83
|
+
function statusError(status, body) {
|
|
84
|
+
return new TelemError(detailFromBody(body) ?? defaultMessage(status), {
|
|
85
|
+
status,
|
|
86
|
+
code: codeFromBody(body),
|
|
87
|
+
details: body
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function connectionError(url, cause) {
|
|
91
|
+
const error = cause;
|
|
92
|
+
const detail = typeof error?.cause?.message === "string" && error.cause.message || typeof error?.message === "string" && error.message || String(cause);
|
|
93
|
+
return new TelemError(`could not reach ${url}: ${detail}`, {
|
|
94
|
+
code: "connection_error",
|
|
95
|
+
cause
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function isRetryable(status) {
|
|
99
|
+
return status >= 500 || status === 429;
|
|
100
|
+
}
|
|
101
|
+
function backoff(attempt) {
|
|
102
|
+
return Math.min(BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS);
|
|
103
|
+
}
|
|
104
|
+
function retryDelay(retryAfter, attempt) {
|
|
105
|
+
if (retryAfter !== null && /^\s*\d+\s*$/.test(retryAfter)) {
|
|
106
|
+
return Math.min(Number(retryAfter) * 1e3, MAX_DELAY_MS);
|
|
107
|
+
}
|
|
108
|
+
return backoff(attempt);
|
|
109
|
+
}
|
|
110
|
+
async function request(config, method, path, body, abort) {
|
|
111
|
+
const url = `${config.baseUrl}/${path.replace(/^\/+/, "")}`;
|
|
112
|
+
const headers = { "User-Agent": config.userAgent };
|
|
113
|
+
if (config.apiKey) headers.Authorization = `Bearer ${config.apiKey}`;
|
|
114
|
+
const init = { method, headers };
|
|
115
|
+
if (body !== void 0) {
|
|
116
|
+
headers["Content-Type"] = "application/json";
|
|
117
|
+
try {
|
|
118
|
+
init.body = JSON.stringify(body);
|
|
119
|
+
} catch (cause) {
|
|
120
|
+
throw invalidRequest(`request body is not JSON: ${cause?.message ?? String(cause)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let onAbort = () => {
|
|
124
|
+
};
|
|
125
|
+
const abortion = abort && new Promise((_, reject) => {
|
|
126
|
+
onAbort = () => reject(abort.reason ?? new Error("aborted"));
|
|
127
|
+
abort.addEventListener("abort", onAbort, { once: true });
|
|
128
|
+
});
|
|
129
|
+
abortion?.catch(() => {
|
|
130
|
+
});
|
|
131
|
+
const pause = (ms) => abortion ? Promise.race([config.sleep(ms), abortion]) : config.sleep(ms);
|
|
132
|
+
try {
|
|
133
|
+
return await attempts();
|
|
134
|
+
} finally {
|
|
135
|
+
abort?.removeEventListener("abort", onAbort);
|
|
136
|
+
}
|
|
137
|
+
async function attempts() {
|
|
138
|
+
for (let attempt = 0; ; attempt++) {
|
|
139
|
+
if (abort?.aborted) throw abort.reason ?? new Error("aborted");
|
|
140
|
+
const timeout = Number.isFinite(config.timeoutMs) ? AbortSignal.timeout(Math.min(config.timeoutMs, MAX_TIMEOUT_MS)) : void 0;
|
|
141
|
+
const signal = timeout && abort ? typeof AbortSignal.any === "function" ? AbortSignal.any([timeout, abort]) : timeout : timeout ?? abort;
|
|
142
|
+
let onAbort2 = () => {
|
|
143
|
+
};
|
|
144
|
+
const deadline = signal && new Promise((_, reject) => {
|
|
145
|
+
onAbort2 = () => reject(signal.reason);
|
|
146
|
+
signal.addEventListener("abort", onAbort2, { once: true });
|
|
147
|
+
});
|
|
148
|
+
const race = (work) => deadline ? Promise.race([work, deadline]) : work;
|
|
149
|
+
let received;
|
|
150
|
+
try {
|
|
151
|
+
const response = await race(config.fetch(url, { ...init, signal }));
|
|
152
|
+
if (!isResponseLike(response)) throw new NotAResponse();
|
|
153
|
+
const text = await race(response.text());
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
parsed = JSON.parse(text);
|
|
157
|
+
} catch {
|
|
158
|
+
parsed = text;
|
|
159
|
+
}
|
|
160
|
+
received = {
|
|
161
|
+
status: response.status,
|
|
162
|
+
retryAfter: response.headers.get("retry-after"),
|
|
163
|
+
body: parsed
|
|
164
|
+
};
|
|
165
|
+
} catch (cause) {
|
|
166
|
+
if (cause instanceof NotAResponse) {
|
|
167
|
+
throw new TelemError("the injected fetch returned something that is not a Response", {
|
|
168
|
+
code: "connection_error"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (abort?.aborted) throw abort.reason ?? cause;
|
|
172
|
+
if (signal?.aborted) {
|
|
173
|
+
throw new TimeoutError(`request to ${url} timed out after ${config.timeoutMs}ms`, cause);
|
|
174
|
+
}
|
|
175
|
+
if (attempt >= config.maxRetries) throw connectionError(url, cause);
|
|
176
|
+
await pause(backoff(attempt));
|
|
177
|
+
continue;
|
|
178
|
+
} finally {
|
|
179
|
+
signal?.removeEventListener("abort", onAbort2);
|
|
180
|
+
}
|
|
181
|
+
if (received.status >= 200 && received.status < 300) return received.body;
|
|
182
|
+
if (isRetryable(received.status) && attempt < config.maxRetries) {
|
|
183
|
+
await pause(retryDelay(received.retryAfter, attempt));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
throw statusError(received.status, received.body);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/trajectory.ts
|
|
192
|
+
var NS_TRAJECTORY = "443866ab-1b45-5ed8-979e-52fdad07b810";
|
|
193
|
+
var NONE = "none";
|
|
194
|
+
var RESERVED_METADATA = /* @__PURE__ */ new Set([
|
|
195
|
+
"message_history",
|
|
196
|
+
"session_key",
|
|
197
|
+
"fingerprint",
|
|
198
|
+
"node_key",
|
|
199
|
+
"kind",
|
|
200
|
+
"parent_node_key",
|
|
201
|
+
"ancestors"
|
|
202
|
+
]);
|
|
203
|
+
var ENCODER = new TextEncoder();
|
|
204
|
+
var NAMESPACE_BYTES = Uint8Array.from(
|
|
205
|
+
NS_TRAJECTORY.replaceAll("-", "").match(/../g) ?? [],
|
|
206
|
+
(pair) => parseInt(pair, 16)
|
|
207
|
+
);
|
|
208
|
+
function hex(bytes) {
|
|
209
|
+
let out = "";
|
|
210
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
async function digest(algorithm, bytes) {
|
|
214
|
+
return new Uint8Array(await crypto.subtle.digest(algorithm, bytes));
|
|
215
|
+
}
|
|
216
|
+
async function sha256(value) {
|
|
217
|
+
return hex(await digest("SHA-256", ENCODER.encode(value)));
|
|
218
|
+
}
|
|
219
|
+
function lp(value) {
|
|
220
|
+
return `${ENCODER.encode(value).length}:${value}`;
|
|
221
|
+
}
|
|
222
|
+
async function uuid5(name) {
|
|
223
|
+
const encoded = ENCODER.encode(name);
|
|
224
|
+
const input = new Uint8Array(NAMESPACE_BYTES.length + encoded.length);
|
|
225
|
+
input.set(NAMESPACE_BYTES);
|
|
226
|
+
input.set(encoded, NAMESPACE_BYTES.length);
|
|
227
|
+
const bytes = (await digest("SHA-1", input)).slice(0, 16);
|
|
228
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
229
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
230
|
+
const chars = hex(bytes);
|
|
231
|
+
return [
|
|
232
|
+
chars.slice(0, 8),
|
|
233
|
+
chars.slice(8, 12),
|
|
234
|
+
chars.slice(12, 16),
|
|
235
|
+
chars.slice(16, 20),
|
|
236
|
+
chars.slice(20)
|
|
237
|
+
].join("-");
|
|
238
|
+
}
|
|
239
|
+
async function fingerprint(harness, conversationId) {
|
|
240
|
+
return await sha256(lp(harness) + lp(conversationId));
|
|
241
|
+
}
|
|
242
|
+
async function sessionKey(harness, conversationId, windowId) {
|
|
243
|
+
const name = lp(harness) + lp(await sha256(conversationId)) + lp(windowId) + lp(NONE);
|
|
244
|
+
return await uuid5(name);
|
|
245
|
+
}
|
|
246
|
+
async function eventNodeKey(harness, session, messageId, toolCallId) {
|
|
247
|
+
const name = lp(harness) + lp(session) + lp(messageId) + lp(toolCallId) + lp("event");
|
|
248
|
+
return await uuid5(name);
|
|
249
|
+
}
|
|
250
|
+
async function snapshotNodeKey(harness, conversationId, messageId) {
|
|
251
|
+
const name = lp(harness) + lp(await sha256(conversationId)) + lp(messageId) + lp("snap");
|
|
252
|
+
return await uuid5(name);
|
|
253
|
+
}
|
|
254
|
+
async function contentAnchor(...parts) {
|
|
255
|
+
return await sha256(parts.map(lp).join(""));
|
|
256
|
+
}
|
|
257
|
+
var HISTORY_TEXT_CAP = 128e3;
|
|
258
|
+
var TOOL_INPUT_CAP = 128e3;
|
|
259
|
+
function cap(value) {
|
|
260
|
+
if (value.length <= HISTORY_TEXT_CAP) return value;
|
|
261
|
+
return Array.from(value).slice(0, HISTORY_TEXT_CAP).join("");
|
|
262
|
+
}
|
|
263
|
+
function toolMarker(name, status, args) {
|
|
264
|
+
let serialized = "";
|
|
265
|
+
try {
|
|
266
|
+
const text = JSON.stringify(args);
|
|
267
|
+
if (typeof text === "string") serialized = text;
|
|
268
|
+
} catch {
|
|
269
|
+
serialized = "";
|
|
270
|
+
}
|
|
271
|
+
if (serialized.length > TOOL_INPUT_CAP) serialized = Array.from(serialized).slice(0, TOOL_INPUT_CAP).join("");
|
|
272
|
+
const suffix = serialized ? ` ${serialized}` : "";
|
|
273
|
+
return `[tool ${name || "?"}: ${status}${suffix}]`;
|
|
274
|
+
}
|
|
275
|
+
function pyStr(value) {
|
|
276
|
+
if (typeof value === "string") return value;
|
|
277
|
+
if (value === null) return "None";
|
|
278
|
+
if (value === true) return "True";
|
|
279
|
+
if (value === false) return "False";
|
|
280
|
+
return String(value);
|
|
281
|
+
}
|
|
282
|
+
function messageText(content) {
|
|
283
|
+
if (typeof content === "string") return cap(content);
|
|
284
|
+
if (Array.isArray(content)) {
|
|
285
|
+
const pieces = [];
|
|
286
|
+
for (const block of content) {
|
|
287
|
+
if (typeof block === "string") pieces.push(block);
|
|
288
|
+
else if (isObject(block) && typeof block.text === "string") pieces.push(block.text);
|
|
289
|
+
}
|
|
290
|
+
return cap(pieces.join("\n"));
|
|
291
|
+
}
|
|
292
|
+
return content === null || content === void 0 ? "" : cap(pyStr(content));
|
|
293
|
+
}
|
|
294
|
+
async function windowAnchor(messages) {
|
|
295
|
+
const first = messages[0];
|
|
296
|
+
if (first === void 0) return NONE;
|
|
297
|
+
return await contentAnchor(
|
|
298
|
+
first.role === void 0 ? "" : pyStr(first.role),
|
|
299
|
+
messageText(first.content)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
async function buildMetadata(args) {
|
|
303
|
+
const [session, fp] = await Promise.all([
|
|
304
|
+
sessionKey(args.harness, args.conversationId, args.windowId),
|
|
305
|
+
fingerprint(args.harness, args.conversationId)
|
|
306
|
+
]);
|
|
307
|
+
return {
|
|
308
|
+
message_history: args.history,
|
|
309
|
+
session_key: session,
|
|
310
|
+
fingerprint: fp,
|
|
311
|
+
node_key: await eventNodeKey(args.harness, session, args.messageId, args.toolCallId),
|
|
312
|
+
parent_node_key: parentNodeKey(args.ancestors),
|
|
313
|
+
ancestors: args.ancestors
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
async function buildSnapshot(args) {
|
|
317
|
+
const [session, fp, node] = await Promise.all([
|
|
318
|
+
sessionKey(args.harness, args.conversationId, args.windowId),
|
|
319
|
+
fingerprint(args.harness, args.conversationId),
|
|
320
|
+
snapshotNodeKey(args.harness, args.conversationId, args.messageId)
|
|
321
|
+
]);
|
|
322
|
+
return {
|
|
323
|
+
session_key: session,
|
|
324
|
+
fingerprint: fp,
|
|
325
|
+
node_key: node,
|
|
326
|
+
parent_node_key: parentNodeKey(args.ancestors),
|
|
327
|
+
context: args.context,
|
|
328
|
+
spawned_at: args.spawnedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function parentNodeKey(ancestors) {
|
|
332
|
+
return ancestors.at(-1)?.node_key ?? null;
|
|
333
|
+
}
|
|
334
|
+
var TRAJECTORY = /* @__PURE__ */ Symbol.for("telem.trajectory");
|
|
335
|
+
function withTrajectory(options, envelope) {
|
|
336
|
+
return { ...options, [TRAJECTORY]: envelope };
|
|
337
|
+
}
|
|
338
|
+
function wireMetadata(options, kind) {
|
|
339
|
+
const metadata = {};
|
|
340
|
+
for (const [key, value] of Object.entries(options.metadata ?? {})) {
|
|
341
|
+
if (!RESERVED_METADATA.has(key)) metadata[key] = value;
|
|
342
|
+
}
|
|
343
|
+
const envelope = options[TRAJECTORY];
|
|
344
|
+
if (envelope !== void 0) Object.assign(metadata, envelope, { kind });
|
|
345
|
+
const sessionId = metadata.session_key === void 0 ? options.session : void 0;
|
|
346
|
+
return { metadata, sessionId };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export {
|
|
350
|
+
TelemError,
|
|
351
|
+
TimeoutError,
|
|
352
|
+
isObject,
|
|
353
|
+
asObject,
|
|
354
|
+
asArray,
|
|
355
|
+
invalidRequest,
|
|
356
|
+
stringList,
|
|
357
|
+
request,
|
|
358
|
+
RESERVED_METADATA,
|
|
359
|
+
sha256,
|
|
360
|
+
fingerprint,
|
|
361
|
+
sessionKey,
|
|
362
|
+
eventNodeKey,
|
|
363
|
+
snapshotNodeKey,
|
|
364
|
+
contentAnchor,
|
|
365
|
+
cap,
|
|
366
|
+
toolMarker,
|
|
367
|
+
messageText,
|
|
368
|
+
windowAnchor,
|
|
369
|
+
buildMetadata,
|
|
370
|
+
buildSnapshot,
|
|
371
|
+
withTrajectory,
|
|
372
|
+
wireMetadata
|
|
373
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
fingerprint: () => fingerprint,
|
|
33
33
|
sessionKey: () => sessionKey,
|
|
34
34
|
snapshotNodeKey: () => snapshotNodeKey,
|
|
35
|
+
toolMarker: () => toolMarker,
|
|
35
36
|
windowAnchor: () => windowAnchor,
|
|
36
37
|
withTrajectory: () => withTrajectory
|
|
37
38
|
});
|
|
@@ -58,7 +59,7 @@ var TimeoutError = class extends TelemError {
|
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
// src/version.ts
|
|
61
|
-
var VERSION = "0.
|
|
62
|
+
var VERSION = "0.2.0";
|
|
62
63
|
|
|
63
64
|
// src/config.ts
|
|
64
65
|
var DEFAULT_BASE_URL = "https://router.telem.ai";
|
|
@@ -243,7 +244,7 @@ function retryDelay(retryAfter, attempt) {
|
|
|
243
244
|
}
|
|
244
245
|
return backoff(attempt);
|
|
245
246
|
}
|
|
246
|
-
async function request(config, method, path, body) {
|
|
247
|
+
async function request(config, method, path, body, abort) {
|
|
247
248
|
const url = `${config.baseUrl}/${path.replace(/^\/+/, "")}`;
|
|
248
249
|
const headers = { "User-Agent": config.userAgent };
|
|
249
250
|
if (config.apiKey) headers.Authorization = `Bearer ${config.apiKey}`;
|
|
@@ -256,52 +257,71 @@ async function request(config, method, path, body) {
|
|
|
256
257
|
throw invalidRequest(`request body is not JSON: ${cause?.message ?? String(cause)}`);
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
received = {
|
|
280
|
-
status: response.status,
|
|
281
|
-
retryAfter: response.headers.get("retry-after"),
|
|
282
|
-
body: parsed
|
|
260
|
+
let onAbort = () => {
|
|
261
|
+
};
|
|
262
|
+
const abortion = abort && new Promise((_, reject) => {
|
|
263
|
+
onAbort = () => reject(abort.reason ?? new Error("aborted"));
|
|
264
|
+
abort.addEventListener("abort", onAbort, { once: true });
|
|
265
|
+
});
|
|
266
|
+
abortion?.catch(() => {
|
|
267
|
+
});
|
|
268
|
+
const pause = (ms) => abortion ? Promise.race([config.sleep(ms), abortion]) : config.sleep(ms);
|
|
269
|
+
try {
|
|
270
|
+
return await attempts();
|
|
271
|
+
} finally {
|
|
272
|
+
abort?.removeEventListener("abort", onAbort);
|
|
273
|
+
}
|
|
274
|
+
async function attempts() {
|
|
275
|
+
for (let attempt = 0; ; attempt++) {
|
|
276
|
+
if (abort?.aborted) throw abort.reason ?? new Error("aborted");
|
|
277
|
+
const timeout = Number.isFinite(config.timeoutMs) ? AbortSignal.timeout(Math.min(config.timeoutMs, MAX_TIMEOUT_MS)) : void 0;
|
|
278
|
+
const signal = timeout && abort ? typeof AbortSignal.any === "function" ? AbortSignal.any([timeout, abort]) : timeout : timeout ?? abort;
|
|
279
|
+
let onAbort2 = () => {
|
|
283
280
|
};
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
281
|
+
const deadline = signal && new Promise((_, reject) => {
|
|
282
|
+
onAbort2 = () => reject(signal.reason);
|
|
283
|
+
signal.addEventListener("abort", onAbort2, { once: true });
|
|
284
|
+
});
|
|
285
|
+
const race = (work) => deadline ? Promise.race([work, deadline]) : work;
|
|
286
|
+
let received;
|
|
287
|
+
try {
|
|
288
|
+
const response = await race(config.fetch(url, { ...init, signal }));
|
|
289
|
+
if (!isResponseLike(response)) throw new NotAResponse();
|
|
290
|
+
const text = await race(response.text());
|
|
291
|
+
let parsed;
|
|
292
|
+
try {
|
|
293
|
+
parsed = JSON.parse(text);
|
|
294
|
+
} catch {
|
|
295
|
+
parsed = text;
|
|
296
|
+
}
|
|
297
|
+
received = {
|
|
298
|
+
status: response.status,
|
|
299
|
+
retryAfter: response.headers.get("retry-after"),
|
|
300
|
+
body: parsed
|
|
301
|
+
};
|
|
302
|
+
} catch (cause) {
|
|
303
|
+
if (cause instanceof NotAResponse) {
|
|
304
|
+
throw new TelemError("the injected fetch returned something that is not a Response", {
|
|
305
|
+
code: "connection_error"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (abort?.aborted) throw abort.reason ?? cause;
|
|
309
|
+
if (signal?.aborted) {
|
|
310
|
+
throw new TimeoutError(`request to ${url} timed out after ${config.timeoutMs}ms`, cause);
|
|
311
|
+
}
|
|
312
|
+
if (attempt >= config.maxRetries) throw connectionError(url, cause);
|
|
313
|
+
await pause(backoff(attempt));
|
|
314
|
+
continue;
|
|
315
|
+
} finally {
|
|
316
|
+
signal?.removeEventListener("abort", onAbort2);
|
|
289
317
|
}
|
|
290
|
-
if (
|
|
291
|
-
|
|
318
|
+
if (received.status >= 200 && received.status < 300) return received.body;
|
|
319
|
+
if (isRetryable(received.status) && attempt < config.maxRetries) {
|
|
320
|
+
await pause(retryDelay(received.retryAfter, attempt));
|
|
321
|
+
continue;
|
|
292
322
|
}
|
|
293
|
-
|
|
294
|
-
await config.sleep(backoff(attempt));
|
|
295
|
-
continue;
|
|
296
|
-
} finally {
|
|
297
|
-
signal?.removeEventListener("abort", onAbort);
|
|
323
|
+
throw statusError(received.status, received.body);
|
|
298
324
|
}
|
|
299
|
-
if (received.status >= 200 && received.status < 300) return received.body;
|
|
300
|
-
if (isRetryable(received.status) && attempt < config.maxRetries) {
|
|
301
|
-
await config.sleep(retryDelay(received.retryAfter, attempt));
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
throw statusError(received.status, received.body);
|
|
305
325
|
}
|
|
306
326
|
}
|
|
307
327
|
|
|
@@ -372,10 +392,23 @@ async function contentAnchor(...parts) {
|
|
|
372
392
|
return await sha256(parts.map(lp).join(""));
|
|
373
393
|
}
|
|
374
394
|
var HISTORY_TEXT_CAP = 128e3;
|
|
395
|
+
var TOOL_INPUT_CAP = 128e3;
|
|
375
396
|
function cap(value) {
|
|
376
397
|
if (value.length <= HISTORY_TEXT_CAP) return value;
|
|
377
398
|
return Array.from(value).slice(0, HISTORY_TEXT_CAP).join("");
|
|
378
399
|
}
|
|
400
|
+
function toolMarker(name, status, args) {
|
|
401
|
+
let serialized = "";
|
|
402
|
+
try {
|
|
403
|
+
const text = JSON.stringify(args);
|
|
404
|
+
if (typeof text === "string") serialized = text;
|
|
405
|
+
} catch {
|
|
406
|
+
serialized = "";
|
|
407
|
+
}
|
|
408
|
+
if (serialized.length > TOOL_INPUT_CAP) serialized = Array.from(serialized).slice(0, TOOL_INPUT_CAP).join("");
|
|
409
|
+
const suffix = serialized ? ` ${serialized}` : "";
|
|
410
|
+
return `[tool ${name || "?"}: ${status}${suffix}]`;
|
|
411
|
+
}
|
|
379
412
|
function pyStr(value) {
|
|
380
413
|
if (typeof value === "string") return value;
|
|
381
414
|
if (value === null) return "None";
|
|
@@ -505,7 +538,7 @@ function buildFetchResponse(interaction) {
|
|
|
505
538
|
}
|
|
506
539
|
async function fetchUrls(config, url, options) {
|
|
507
540
|
const body = fetchBody(url, options);
|
|
508
|
-
return buildFetchResponse(await request(config, "POST", "/v1/fetch", body));
|
|
541
|
+
return buildFetchResponse(await request(config, "POST", "/v1/fetch", body, options.signal));
|
|
509
542
|
}
|
|
510
543
|
|
|
511
544
|
// src/methods/providers.ts
|
|
@@ -654,7 +687,7 @@ function buildSearchResponse(interaction) {
|
|
|
654
687
|
}
|
|
655
688
|
async function search(config, query, options) {
|
|
656
689
|
const body = searchBody(config, query, options);
|
|
657
|
-
const data = await request(config, "POST", "/v1/interactions", body);
|
|
690
|
+
const data = await request(config, "POST", "/v1/interactions", body, options.signal);
|
|
658
691
|
assertV2(data);
|
|
659
692
|
return buildSearchResponse(data);
|
|
660
693
|
}
|
|
@@ -783,6 +816,7 @@ var index_default = Telem;
|
|
|
783
816
|
fingerprint,
|
|
784
817
|
sessionKey,
|
|
785
818
|
snapshotNodeKey,
|
|
819
|
+
toolMarker,
|
|
786
820
|
windowAnchor,
|
|
787
821
|
withTrajectory
|
|
788
822
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -70,6 +70,9 @@ type SearchOptions = {
|
|
|
70
70
|
goal?: string;
|
|
71
71
|
context?: string;
|
|
72
72
|
session?: string;
|
|
73
|
+
/** Abort this call: the request stops, no retry follows, and the call rejects
|
|
74
|
+
* with the signal's reason. Merged with the per-attempt timeout. */
|
|
75
|
+
signal?: AbortSignal;
|
|
73
76
|
/** Freeform metadata for the wire. Reserved trajectory-lineage keys
|
|
74
77
|
* (`RESERVED_METADATA`) are STRIPPED from here — lineage may only be attached
|
|
75
78
|
* through `withTrajectory`, fed by `buildMetadata`, never smuggled in as data. */
|
|
@@ -84,6 +87,9 @@ type FetchOptions = {
|
|
|
84
87
|
inlineMaxChars?: number;
|
|
85
88
|
contentFormat?: string;
|
|
86
89
|
session?: string;
|
|
90
|
+
/** Abort this call: the request stops, no retry follows, and the call rejects
|
|
91
|
+
* with the signal's reason. Merged with the per-attempt timeout. */
|
|
92
|
+
signal?: AbortSignal;
|
|
87
93
|
metadata?: Json;
|
|
88
94
|
};
|
|
89
95
|
type TelemOptions = SearchDefaults & {
|
|
@@ -258,6 +264,12 @@ declare function snapshotNodeKey(harness: string, conversationId: string, messag
|
|
|
258
264
|
/** Stable anchor id for content a host did not assign an id to.
|
|
259
265
|
* Length-prefixed like every other key component, so concatenation stays injective. */
|
|
260
266
|
declare function contentAnchor(...parts: string[]): Promise<string>;
|
|
267
|
+
/** `tool_marker` from telem/integrations/_trajectory_v5.py: one tool call as the
|
|
268
|
+
* compact acting-trace marker every harness renders into history. The exporter
|
|
269
|
+
* cuts goals on `[tool telem_search:`, so the text is a contract. Compact JSON,
|
|
270
|
+
* key order kept, matches Python's `separators=(",", ":")`; an unserializable
|
|
271
|
+
* input drops the suffix rather than the marker. */
|
|
272
|
+
declare function toolMarker(name: string, status: string, args: unknown): string;
|
|
261
273
|
/**
|
|
262
274
|
* Identify this context-window generation by its first message.
|
|
263
275
|
*
|
|
@@ -366,4 +378,4 @@ declare class Telem {
|
|
|
366
378
|
providers(): Promise<ProviderInfo[]>;
|
|
367
379
|
}
|
|
368
380
|
|
|
369
|
-
export { type FetchOptions, type FetchResponse, type FetchResult, type Json, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, windowAnchor, withTrajectory };
|
|
381
|
+
export { type FetchOptions, type FetchResponse, type FetchResult, type Json, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, toolMarker, windowAnchor, withTrajectory };
|