@telemai/sdk 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -8
- package/dist/chunk-RYY4S5LP.js +373 -0
- package/dist/index.cjs +94 -48
- package/dist/index.d.cts +20 -5
- package/dist/index.d.ts +20 -5
- package/dist/index.js +40 -320
- package/dist/vercel/index.cjs +746 -0
- package/dist/vercel/index.d.cts +265 -0
- package/dist/vercel/index.d.ts +265 -0
- package/dist/vercel/index.js +583 -0
- package/package.json +38 -7
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ See [Authentication](https://docs.telem.ai/authentication/) for the full rules.
|
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
44
|
|
|
45
|
-
const telem = new Telem
|
|
45
|
+
const telem = new Telem // environment, then ~/.telem/credentials.json
|
|
46
46
|
const telem = new Telem("tlm_...") // just a key
|
|
47
47
|
const telem = new Telem({
|
|
48
48
|
apiKey: "tlm_...",
|
|
@@ -176,7 +176,7 @@ Do not expect a shorter list. The options are on the
|
|
|
176
176
|
List the providers the deployment can run:
|
|
177
177
|
|
|
178
178
|
```ts
|
|
179
|
-
for (const provider of await telem.providers
|
|
179
|
+
for (const provider of await telem.providers) {
|
|
180
180
|
console.log(provider.name, provider.activeByDefault, provider.tiers)
|
|
181
181
|
}
|
|
182
182
|
```
|
|
@@ -187,7 +187,7 @@ Every search belongs to a session. The response carries its id as `sessionId`. R
|
|
|
187
187
|
your sessions back to see what an agent searched for. Both methods require an API key:
|
|
188
188
|
|
|
189
189
|
```ts
|
|
190
|
-
for (const session of await telem.sessions.list
|
|
190
|
+
for (const session of await telem.sessions.list) {
|
|
191
191
|
console.log(session.id, session.interactionCount, session.latestInteractionAt)
|
|
192
192
|
}
|
|
193
193
|
|
|
@@ -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/telem-js-sdk/tree/main/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
|
|
@@ -221,8 +251,8 @@ Node SDK, where the completion id is the turn and each tool call has its own id:
|
|
|
221
251
|
|
|
222
252
|
```ts
|
|
223
253
|
|
|
224
|
-
const telem = new Telem
|
|
225
|
-
const openai = new OpenAI
|
|
254
|
+
const telem = new Telem
|
|
255
|
+
const openai = new OpenAI
|
|
226
256
|
const messages = [{ role: "user" as const, content: "Is the Nintendo Museum open over New Year?" }]
|
|
227
257
|
const tools = [
|
|
228
258
|
{
|
|
@@ -239,26 +269,36 @@ const tools = [
|
|
|
239
269
|
},
|
|
240
270
|
]
|
|
241
271
|
|
|
242
|
-
const conversationId = crypto.randomUUID
|
|
272
|
+
const conversationId = crypto.randomUUID // once, when the conversation starts
|
|
243
273
|
const windowId = await windowAnchor(messages) // once, from the first message
|
|
244
274
|
|
|
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
|
+
};
|