@openhex-ai/agent-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +207 -0
- package/dist/index-DOE19uln.d.ts +93 -0
- package/dist/index.d.ts +1218 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var OpenhexSdkError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "OpenhexSdkError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var AuthenticationError = class extends OpenhexSdkError {
|
|
9
|
+
constructor(message = "No Openhex API key provided. Set OPENHEX_API_KEY or pass { apiKey }.") {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "AuthenticationError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var ApiError = class extends OpenhexSdkError {
|
|
15
|
+
constructor(message, status, body) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.body = body;
|
|
19
|
+
this.name = "ApiError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var AbortError = class extends OpenhexSdkError {
|
|
23
|
+
constructor(message = "The agent run was aborted.") {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "AbortError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var NotImplementedError = class extends OpenhexSdkError {
|
|
29
|
+
constructor(what) {
|
|
30
|
+
super(`Not implemented yet: ${what}. This is a scaffold \u2014 runtime lands in a follow-up MR.`);
|
|
31
|
+
this.name = "NotImplementedError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/transport.ts
|
|
36
|
+
var HttpTransport = class {
|
|
37
|
+
constructor(opts) {
|
|
38
|
+
this.opts = opts;
|
|
39
|
+
}
|
|
40
|
+
// eslint-disable-next-line require-yield
|
|
41
|
+
async *run(_request) {
|
|
42
|
+
throw new NotImplementedError("HttpTransport.run");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var DEFAULT_BASE_URL = "https://api.openhex.tech";
|
|
46
|
+
|
|
47
|
+
// src/query.ts
|
|
48
|
+
function resolveApiKey(options) {
|
|
49
|
+
const key = options.apiKey ?? process.env.OPENHEX_API_KEY;
|
|
50
|
+
if (!key) throw new AuthenticationError();
|
|
51
|
+
return key;
|
|
52
|
+
}
|
|
53
|
+
function query(params) {
|
|
54
|
+
const options = params.options ?? {};
|
|
55
|
+
const apiKey = resolveApiKey(options);
|
|
56
|
+
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
57
|
+
const transport = new HttpTransport({ apiKey, baseUrl });
|
|
58
|
+
const prompt = typeof params.prompt === "string" ? params.prompt : "<streaming-input>";
|
|
59
|
+
const stream = transport.run({ prompt, options });
|
|
60
|
+
const q = stream;
|
|
61
|
+
q.interrupt = async () => {
|
|
62
|
+
await stream.return?.();
|
|
63
|
+
};
|
|
64
|
+
return q;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/http/httpClient.ts
|
|
68
|
+
var API_PREFIX = "/api/v2";
|
|
69
|
+
function resolveApiKey2(apiKey) {
|
|
70
|
+
const key = apiKey ?? (typeof process !== "undefined" ? process.env?.OPENHEX_API_KEY : void 0);
|
|
71
|
+
if (!key) throw new AuthenticationError();
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
function withTimeout(signal, timeoutMs) {
|
|
75
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
76
|
+
return { signal: signal ?? new AbortController().signal, cancel: () => {
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
81
|
+
if (signal) {
|
|
82
|
+
if (signal.aborted) controller.abort();
|
|
83
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
}
|
|
85
|
+
const timer = setTimeout(() => controller.abort(new Error(`Request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
86
|
+
return {
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
cancel: () => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
signal?.removeEventListener("abort", onAbort);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var HttpClient = class {
|
|
95
|
+
apiKey;
|
|
96
|
+
baseUrl;
|
|
97
|
+
loginType;
|
|
98
|
+
actAs;
|
|
99
|
+
extraHeaders;
|
|
100
|
+
timeoutMs;
|
|
101
|
+
fetchImpl;
|
|
102
|
+
constructor(config = {}) {
|
|
103
|
+
this.apiKey = resolveApiKey2(config.apiKey);
|
|
104
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
105
|
+
this.loginType = config.loginType;
|
|
106
|
+
this.actAs = config.actAs;
|
|
107
|
+
this.extraHeaders = config.headers ?? {};
|
|
108
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
109
|
+
const f = config.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
110
|
+
if (!f) {
|
|
111
|
+
throw new Error("No fetch implementation available. Pass { fetch } in the client config.");
|
|
112
|
+
}
|
|
113
|
+
this.fetchImpl = f.bind(globalThis);
|
|
114
|
+
}
|
|
115
|
+
/** Build the full URL for an API path (prefixing `/api/v2` if needed). */
|
|
116
|
+
url(path) {
|
|
117
|
+
if (/^https?:\/\//.test(path)) return path;
|
|
118
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
119
|
+
const withPrefix = p.startsWith(API_PREFIX) ? p : `${API_PREFIX}${p}`;
|
|
120
|
+
return `${this.baseUrl}${withPrefix}`;
|
|
121
|
+
}
|
|
122
|
+
/** Headers common to every request. `accept` differs for SSE vs JSON. */
|
|
123
|
+
buildHeaders(accept, extra) {
|
|
124
|
+
const headers = {
|
|
125
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
126
|
+
Accept: accept,
|
|
127
|
+
...this.extraHeaders,
|
|
128
|
+
...extra
|
|
129
|
+
};
|
|
130
|
+
if (this.loginType) headers["X-Login-Type"] = this.loginType;
|
|
131
|
+
if (this.actAs) headers["X-Act-As"] = this.actAs;
|
|
132
|
+
return headers;
|
|
133
|
+
}
|
|
134
|
+
/** Map a non-2xx response to an {@link ApiError}, extracting `detail`. */
|
|
135
|
+
async toError(response) {
|
|
136
|
+
let body;
|
|
137
|
+
let detail;
|
|
138
|
+
try {
|
|
139
|
+
body = await response.json();
|
|
140
|
+
detail = body?.detail;
|
|
141
|
+
} catch {
|
|
142
|
+
try {
|
|
143
|
+
detail = await response.text();
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (response.status === 401 || response.status === 403) {
|
|
148
|
+
return new ApiError(detail || "Unauthorized", response.status, body);
|
|
149
|
+
}
|
|
150
|
+
return new ApiError(detail || `Request failed with status ${response.status}`, response.status, body);
|
|
151
|
+
}
|
|
152
|
+
/** Perform a JSON request and parse the response body. */
|
|
153
|
+
async requestJson(path, options = {}) {
|
|
154
|
+
const response = await this.requestRaw(path, options, "application/json");
|
|
155
|
+
if (response.status === 204) return void 0;
|
|
156
|
+
return await response.json();
|
|
157
|
+
}
|
|
158
|
+
/** Perform a request and return the raw {@link Response} (used for SSE). */
|
|
159
|
+
async requestRaw(path, options = {}, accept = "application/json") {
|
|
160
|
+
const { method = "GET", body, headers, signal } = options;
|
|
161
|
+
const timeoutMs = options.timeoutMs ?? this.timeoutMs;
|
|
162
|
+
const { signal: combined, cancel } = withTimeout(signal, timeoutMs);
|
|
163
|
+
const reqHeaders = this.buildHeaders(accept, headers);
|
|
164
|
+
if (body !== void 0) reqHeaders["Content-Type"] = "application/json";
|
|
165
|
+
let response;
|
|
166
|
+
try {
|
|
167
|
+
response = await this.fetchImpl(this.url(path), {
|
|
168
|
+
method,
|
|
169
|
+
headers: reqHeaders,
|
|
170
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
171
|
+
signal: combined
|
|
172
|
+
});
|
|
173
|
+
} finally {
|
|
174
|
+
if (accept !== "text/event-stream") cancel();
|
|
175
|
+
}
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
if (accept !== "text/event-stream") cancel();
|
|
178
|
+
throw await this.toError(response);
|
|
179
|
+
}
|
|
180
|
+
return response;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/http/sse.ts
|
|
185
|
+
function fieldValue(raw) {
|
|
186
|
+
return raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
187
|
+
}
|
|
188
|
+
async function* parseSSEStream(stream, signal) {
|
|
189
|
+
const reader = stream.getReader();
|
|
190
|
+
const decoder = new TextDecoder();
|
|
191
|
+
let buffer = "";
|
|
192
|
+
let dataLines = [];
|
|
193
|
+
let eventType;
|
|
194
|
+
let lastId;
|
|
195
|
+
const onAbort = () => {
|
|
196
|
+
reader.cancel().catch(() => {
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
if (signal) {
|
|
200
|
+
if (signal.aborted) onAbort();
|
|
201
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
while (true) {
|
|
205
|
+
const { done, value } = await reader.read();
|
|
206
|
+
if (done) break;
|
|
207
|
+
buffer += decoder.decode(value, { stream: true });
|
|
208
|
+
let nlIndex;
|
|
209
|
+
while ((nlIndex = buffer.indexOf("\n")) !== -1) {
|
|
210
|
+
let line = buffer.slice(0, nlIndex);
|
|
211
|
+
buffer = buffer.slice(nlIndex + 1);
|
|
212
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
213
|
+
if (line === "") {
|
|
214
|
+
if (dataLines.length > 0) {
|
|
215
|
+
yield { id: lastId, event: eventType, data: dataLines.join("\n") };
|
|
216
|
+
}
|
|
217
|
+
dataLines = [];
|
|
218
|
+
eventType = void 0;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (line.startsWith(":")) continue;
|
|
222
|
+
const colon = line.indexOf(":");
|
|
223
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
224
|
+
const rawVal = colon === -1 ? "" : line.slice(colon + 1);
|
|
225
|
+
const val = fieldValue(rawVal);
|
|
226
|
+
switch (field) {
|
|
227
|
+
case "data":
|
|
228
|
+
dataLines.push(val);
|
|
229
|
+
break;
|
|
230
|
+
case "id":
|
|
231
|
+
lastId = val;
|
|
232
|
+
break;
|
|
233
|
+
case "event":
|
|
234
|
+
eventType = val;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (dataLines.length > 0) {
|
|
240
|
+
yield { id: lastId, event: eventType, data: dataLines.join("\n") };
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
signal?.removeEventListener("abort", onAbort);
|
|
244
|
+
reader.releaseLock();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/http/backoff.ts
|
|
249
|
+
var Backoff = class {
|
|
250
|
+
attempt = 0;
|
|
251
|
+
initialMs;
|
|
252
|
+
maxMs;
|
|
253
|
+
factor;
|
|
254
|
+
constructor(opts = {}) {
|
|
255
|
+
this.initialMs = opts.initialMs ?? 1e3;
|
|
256
|
+
this.maxMs = opts.maxMs ?? 15e3;
|
|
257
|
+
this.factor = opts.factor ?? 2;
|
|
258
|
+
}
|
|
259
|
+
/** Next delay in ms (advances the attempt counter). */
|
|
260
|
+
next() {
|
|
261
|
+
const delay2 = Math.min(this.maxMs, this.initialMs * Math.pow(this.factor, this.attempt));
|
|
262
|
+
this.attempt += 1;
|
|
263
|
+
return delay2;
|
|
264
|
+
}
|
|
265
|
+
/** Reset after a successful connection. */
|
|
266
|
+
reset() {
|
|
267
|
+
this.attempt = 0;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
function delay(ms, signal) {
|
|
271
|
+
return new Promise((resolve, reject) => {
|
|
272
|
+
if (signal?.aborted) return reject(new DOMException("Aborted", "AbortError"));
|
|
273
|
+
const timer = setTimeout(() => {
|
|
274
|
+
signal?.removeEventListener("abort", onAbort);
|
|
275
|
+
resolve();
|
|
276
|
+
}, ms);
|
|
277
|
+
const onAbort = () => {
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
280
|
+
};
|
|
281
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/chat/events.ts
|
|
286
|
+
function isAgentRecord(record) {
|
|
287
|
+
return record.sender === "assistant" || record.sender === "agent";
|
|
288
|
+
}
|
|
289
|
+
function isTurnComplete(record) {
|
|
290
|
+
return isAgentRecord(record) && record.raw?.type === "result";
|
|
291
|
+
}
|
|
292
|
+
function isInterrupt(record) {
|
|
293
|
+
return record.sender === "system" && record.raw?.subtype === "interrupt";
|
|
294
|
+
}
|
|
295
|
+
function contentBlocks(record) {
|
|
296
|
+
const msg = record.raw.message;
|
|
297
|
+
if (msg && typeof msg === "object" && Array.isArray(msg.content)) {
|
|
298
|
+
return msg.content;
|
|
299
|
+
}
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
function extractText(record) {
|
|
303
|
+
if (record.raw?.type === "user" && typeof record.raw.message === "string") {
|
|
304
|
+
return record.raw.message;
|
|
305
|
+
}
|
|
306
|
+
return contentBlocks(record).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
307
|
+
}
|
|
308
|
+
function extractToolCalls(record) {
|
|
309
|
+
return contentBlocks(record).filter((b) => b.type === "tool_use").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/chat/chatClient.ts
|
|
313
|
+
function anySignal(signals) {
|
|
314
|
+
const controller = new AbortController();
|
|
315
|
+
const handlers = [];
|
|
316
|
+
for (const s of signals) {
|
|
317
|
+
if (!s) continue;
|
|
318
|
+
if (s.aborted) {
|
|
319
|
+
controller.abort(s.reason);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
const handler = () => controller.abort(s.reason);
|
|
323
|
+
s.addEventListener("abort", handler, { once: true });
|
|
324
|
+
handlers.push([s, handler]);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
signal: controller.signal,
|
|
328
|
+
cleanup: () => handlers.forEach(([s, h]) => s.removeEventListener("abort", h))
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function parseRecord(evt) {
|
|
332
|
+
if (evt.data === "[DONE]") return null;
|
|
333
|
+
let parsed;
|
|
334
|
+
try {
|
|
335
|
+
parsed = JSON.parse(evt.data);
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
if (parsed._meta === true) return null;
|
|
340
|
+
const record = parsed;
|
|
341
|
+
if (evt.id) record.id = evt.id;
|
|
342
|
+
return record;
|
|
343
|
+
}
|
|
344
|
+
var AgentChatClient = class {
|
|
345
|
+
constructor(http) {
|
|
346
|
+
this.http = http;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Create or continue a conversation and route a message to its agent(s).
|
|
350
|
+
* Returns immediately (non-blocking); consume {@link stream} for the reply.
|
|
351
|
+
*/
|
|
352
|
+
async send(req, opts = {}) {
|
|
353
|
+
return this.http.requestJson("/conversations/send", {
|
|
354
|
+
method: "POST",
|
|
355
|
+
body: req,
|
|
356
|
+
signal: opts.signal
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/** Interrupt the conversation's currently-running turn. */
|
|
360
|
+
async interrupt(conversationId, opts = {}) {
|
|
361
|
+
return this.http.requestJson(`/conversations/${conversationId}/interrupt`, {
|
|
362
|
+
method: "POST",
|
|
363
|
+
signal: opts.signal
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/** Full conversation history (all messages as JSON). */
|
|
367
|
+
async messages(conversationId, opts = {}) {
|
|
368
|
+
return this.http.requestJson(`/conversations/${conversationId}/messages`, { signal: opts.signal });
|
|
369
|
+
}
|
|
370
|
+
/** A page of older history, before a cursor. */
|
|
371
|
+
async history(conversationId, params, opts = {}) {
|
|
372
|
+
const q = new URLSearchParams({ before: params.before });
|
|
373
|
+
if (params.turns != null) q.set("turns", String(params.turns));
|
|
374
|
+
if (params.maxEntries != null) q.set("maxEntries", String(params.maxEntries));
|
|
375
|
+
return this.http.requestJson(`/conversations/${conversationId}/history?${q.toString()}`, {
|
|
376
|
+
signal: opts.signal
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Open the conversation's SSE record stream. Yields every record
|
|
381
|
+
* (replayed history then live tail), auto-reconnecting with capped
|
|
382
|
+
* backoff on drop. The generator ends when the consumer breaks/returns
|
|
383
|
+
* or `signal` aborts.
|
|
384
|
+
*/
|
|
385
|
+
async *stream(conversationId, opts = {}) {
|
|
386
|
+
const { signal, reconnect = true } = opts;
|
|
387
|
+
let cursor = opts.lastEventId;
|
|
388
|
+
const backoff = new Backoff();
|
|
389
|
+
const internal = new AbortController();
|
|
390
|
+
const onAbort = () => internal.abort();
|
|
391
|
+
if (signal) {
|
|
392
|
+
if (signal.aborted) internal.abort();
|
|
393
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
394
|
+
}
|
|
395
|
+
const buildPath = () => {
|
|
396
|
+
const q = new URLSearchParams();
|
|
397
|
+
if (cursor) q.set("lastEventId", cursor);
|
|
398
|
+
if (opts.turns != null) q.set("turns", String(opts.turns));
|
|
399
|
+
if (opts.maxEntries != null) q.set("maxEntries", String(opts.maxEntries));
|
|
400
|
+
const qs = q.toString();
|
|
401
|
+
return `/conversations/${conversationId}/stream${qs ? `?${qs}` : ""}`;
|
|
402
|
+
};
|
|
403
|
+
try {
|
|
404
|
+
while (!internal.signal.aborted) {
|
|
405
|
+
let body = null;
|
|
406
|
+
try {
|
|
407
|
+
const response = await this.http.requestRaw(
|
|
408
|
+
buildPath(),
|
|
409
|
+
{ signal: internal.signal, timeoutMs: 0 },
|
|
410
|
+
"text/event-stream"
|
|
411
|
+
);
|
|
412
|
+
body = response.body;
|
|
413
|
+
backoff.reset();
|
|
414
|
+
} catch (err) {
|
|
415
|
+
if (internal.signal.aborted) return;
|
|
416
|
+
if (!reconnect) throw err;
|
|
417
|
+
await delay(backoff.next(), internal.signal);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (body) {
|
|
421
|
+
try {
|
|
422
|
+
for await (const evt of parseSSEStream(body, internal.signal)) {
|
|
423
|
+
if (evt.data === "[DONE]") return;
|
|
424
|
+
const record = parseRecord(evt);
|
|
425
|
+
if (!record) continue;
|
|
426
|
+
cursor = record.id ?? evt.id ?? cursor;
|
|
427
|
+
yield record;
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
if (internal.signal.aborted) return;
|
|
431
|
+
if (!reconnect) throw err;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (!reconnect || internal.signal.aborted) return;
|
|
435
|
+
await delay(backoff.next(), internal.signal);
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
signal?.removeEventListener("abort", onAbort);
|
|
439
|
+
internal.abort();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Stream a single turn over an existing conversation: resume the event
|
|
444
|
+
* stream strictly after `opts.lastEventId` (the user message's event id)
|
|
445
|
+
* and stop at the agent's terminal `result`. Reconnects on drop; an
|
|
446
|
+
* `idleTimeoutMs` gap with no events aborts (covers cold-start pod
|
|
447
|
+
* provisioning). Use when you sent the message yourself via {@link send}.
|
|
448
|
+
*/
|
|
449
|
+
async *resumeTurn(conversationId, opts = {}) {
|
|
450
|
+
const { signal, idleTimeoutMs = 18e4 } = opts;
|
|
451
|
+
const cursor = opts.lastEventId;
|
|
452
|
+
const idleController = new AbortController();
|
|
453
|
+
const merged = anySignal([signal, idleController.signal]);
|
|
454
|
+
let idleTimer;
|
|
455
|
+
const armIdle = () => {
|
|
456
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
457
|
+
idleTimer = setTimeout(() => idleController.abort(new Error("idle timeout")), idleTimeoutMs);
|
|
458
|
+
};
|
|
459
|
+
armIdle();
|
|
460
|
+
try {
|
|
461
|
+
for await (const record of this.stream(conversationId, {
|
|
462
|
+
lastEventId: cursor,
|
|
463
|
+
signal: merged.signal
|
|
464
|
+
})) {
|
|
465
|
+
armIdle();
|
|
466
|
+
yield record;
|
|
467
|
+
if (isTurnComplete(record)) return;
|
|
468
|
+
}
|
|
469
|
+
if (idleController.signal.aborted && !signal?.aborted) {
|
|
470
|
+
throw new AbortError("Turn timed out waiting for the agent to respond.");
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
474
|
+
merged.cleanup();
|
|
475
|
+
idleController.abort();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Send a message and stream just this turn's records, ending when the
|
|
480
|
+
* agent's `result` event arrives.
|
|
481
|
+
*
|
|
482
|
+
* The message is sent first; the stream then resumes precisely from the
|
|
483
|
+
* user message's event id, so only the agent's reply is surfaced — no
|
|
484
|
+
* history, no heuristics. To learn the (possibly new) conversation id
|
|
485
|
+
* while streaming, use {@link sendMessage} or the stateful
|
|
486
|
+
* {@link Conversation}.
|
|
487
|
+
*
|
|
488
|
+
* The first message in a new conversation auto-provisions the agent's
|
|
489
|
+
* pod, so the first turn can take tens of seconds before any record
|
|
490
|
+
* arrives; `idleTimeoutMs` (default 180s) bounds the wait.
|
|
491
|
+
*/
|
|
492
|
+
async *runTurn(req, opts = {}) {
|
|
493
|
+
const result = await this.send(req, { signal: opts.signal });
|
|
494
|
+
yield* this.resumeTurn(result.conversationId, { ...opts, lastEventId: result.userEventId });
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Send a message and resolve with the aggregated turn (assistant text,
|
|
498
|
+
* tool calls, all records, conversation id, and a resume cursor).
|
|
499
|
+
*/
|
|
500
|
+
async sendMessage(req, opts = {}) {
|
|
501
|
+
const result = await this.send(req, { signal: opts.signal });
|
|
502
|
+
const turn = {
|
|
503
|
+
conversationId: result.conversationId,
|
|
504
|
+
text: "",
|
|
505
|
+
toolCalls: [],
|
|
506
|
+
records: [],
|
|
507
|
+
sessionId: null
|
|
508
|
+
};
|
|
509
|
+
for await (const record of this.resumeTurn(result.conversationId, {
|
|
510
|
+
...opts,
|
|
511
|
+
lastEventId: result.userEventId
|
|
512
|
+
})) {
|
|
513
|
+
turn.records.push(record);
|
|
514
|
+
if (record.id) turn.lastEventId = record.id;
|
|
515
|
+
if (record.sessionId) turn.sessionId = record.sessionId;
|
|
516
|
+
if (record.sender === "assistant" || record.sender === "agent") {
|
|
517
|
+
turn.text += extractText(record);
|
|
518
|
+
turn.toolCalls.push(...extractToolCalls(record));
|
|
519
|
+
}
|
|
520
|
+
if (isTurnComplete(record)) turn.result = record.raw;
|
|
521
|
+
}
|
|
522
|
+
return turn;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Start a stateful conversation handle that remembers its id across
|
|
526
|
+
* turns. The first {@link Conversation.send} creates the conversation
|
|
527
|
+
* (routed to `targetAgentIds`); subsequent sends continue it.
|
|
528
|
+
*/
|
|
529
|
+
conversation(opts = {}) {
|
|
530
|
+
return new Conversation(this, opts.conversationId, opts.targetAgentIds);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
var Conversation = class {
|
|
534
|
+
constructor(client, conversationId, targetAgentIds) {
|
|
535
|
+
this.client = client;
|
|
536
|
+
this.conversationId = conversationId;
|
|
537
|
+
this.targetAgentIds = targetAgentIds;
|
|
538
|
+
}
|
|
539
|
+
/** The conversation id, once the first message has been sent. */
|
|
540
|
+
get id() {
|
|
541
|
+
return this.conversationId;
|
|
542
|
+
}
|
|
543
|
+
/** Build the send request for the next turn (create vs continue). */
|
|
544
|
+
buildRequest(message, extra) {
|
|
545
|
+
return this.conversationId ? { message, conversationId: this.conversationId, ...extra } : { message, targetAgentIds: this.targetAgentIds, ...extra };
|
|
546
|
+
}
|
|
547
|
+
/** Send a turn and resolve with the aggregated result. */
|
|
548
|
+
async send(message, opts = {}) {
|
|
549
|
+
const turn = await this.client.sendMessage(this.buildRequest(message, opts), opts);
|
|
550
|
+
this.conversationId = turn.conversationId;
|
|
551
|
+
return turn;
|
|
552
|
+
}
|
|
553
|
+
/** Send a turn and stream its records as they arrive. */
|
|
554
|
+
async *stream(message, opts = {}) {
|
|
555
|
+
const sent = await this.client.send(this.buildRequest(message, opts), { signal: opts.signal });
|
|
556
|
+
this.conversationId = sent.conversationId;
|
|
557
|
+
yield* this.client.resumeTurn(sent.conversationId, {
|
|
558
|
+
...opts,
|
|
559
|
+
lastEventId: sent.userEventId
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
/** Interrupt the running turn in this conversation. */
|
|
563
|
+
async interrupt() {
|
|
564
|
+
if (!this.conversationId) return { ok: true, interrupted: false, reason: "No conversation yet" };
|
|
565
|
+
return this.client.interrupt(this.conversationId);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// src/chat/trainingChatClient.ts
|
|
570
|
+
function parseRecord2(evt) {
|
|
571
|
+
if (evt.data === "[DONE]") return null;
|
|
572
|
+
let parsed;
|
|
573
|
+
try {
|
|
574
|
+
parsed = JSON.parse(evt.data);
|
|
575
|
+
} catch {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
if (parsed._meta === true) return null;
|
|
579
|
+
const record = parsed;
|
|
580
|
+
if (evt.id) record.id = evt.id;
|
|
581
|
+
return record;
|
|
582
|
+
}
|
|
583
|
+
var TrainingChat = class {
|
|
584
|
+
constructor(http, agentId) {
|
|
585
|
+
this.http = http;
|
|
586
|
+
this.agentId = agentId;
|
|
587
|
+
}
|
|
588
|
+
/** Send a training message; the reply streams asynchronously (see {@link stream}). */
|
|
589
|
+
async send(message, opts = {}) {
|
|
590
|
+
return this.http.requestJson(
|
|
591
|
+
`/agents/${this.agentId}/dev/send`,
|
|
592
|
+
{ method: "POST", body: { message }, signal: opts.signal }
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
/** Interrupt the running training turn. */
|
|
596
|
+
async interrupt(opts = {}) {
|
|
597
|
+
return this.http.requestJson(
|
|
598
|
+
`/agents/${this.agentId}/dev/interrupt`,
|
|
599
|
+
{ method: "POST", signal: opts.signal }
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Training transcript (JSON), paginated backward from `before` (a Redis
|
|
604
|
+
* stream id). The endpoint REQUIRES `before`, so we default to a
|
|
605
|
+
* far-future sentinel to fetch the latest turns; pass a real id to page
|
|
606
|
+
* older history.
|
|
607
|
+
*/
|
|
608
|
+
async messages(opts = {}) {
|
|
609
|
+
const before = opts.before ?? "99999999999999-0";
|
|
610
|
+
return this.http.requestJson(
|
|
611
|
+
`/agents/${this.agentId}/messages/history?before=${encodeURIComponent(before)}`,
|
|
612
|
+
{ signal: opts.signal }
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
/** Raw SSE record stream of the training thread (replayed + live tail). */
|
|
616
|
+
async *stream(opts = {}) {
|
|
617
|
+
const { signal, reconnect = true } = opts;
|
|
618
|
+
let cursor = opts.lastEventId;
|
|
619
|
+
const backoff = new Backoff();
|
|
620
|
+
const internal = new AbortController();
|
|
621
|
+
const onAbort = () => internal.abort();
|
|
622
|
+
if (signal) {
|
|
623
|
+
if (signal.aborted) internal.abort();
|
|
624
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
625
|
+
}
|
|
626
|
+
const buildPath = () => {
|
|
627
|
+
const q = new URLSearchParams();
|
|
628
|
+
if (cursor) q.set("lastEventId", cursor);
|
|
629
|
+
if (opts.turns != null) q.set("turns", String(opts.turns));
|
|
630
|
+
const qs = q.toString();
|
|
631
|
+
return `/agents/${this.agentId}/messages/stream${qs ? `?${qs}` : ""}`;
|
|
632
|
+
};
|
|
633
|
+
try {
|
|
634
|
+
while (!internal.signal.aborted) {
|
|
635
|
+
let body = null;
|
|
636
|
+
try {
|
|
637
|
+
const response = await this.http.requestRaw(
|
|
638
|
+
buildPath(),
|
|
639
|
+
{ signal: internal.signal, timeoutMs: 0 },
|
|
640
|
+
"text/event-stream"
|
|
641
|
+
);
|
|
642
|
+
body = response.body;
|
|
643
|
+
backoff.reset();
|
|
644
|
+
} catch (err) {
|
|
645
|
+
if (internal.signal.aborted) return;
|
|
646
|
+
if (!reconnect) throw err;
|
|
647
|
+
await delay(backoff.next(), internal.signal);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (body) {
|
|
651
|
+
try {
|
|
652
|
+
for await (const evt of parseSSEStream(body, internal.signal)) {
|
|
653
|
+
if (evt.data === "[DONE]") return;
|
|
654
|
+
const record = parseRecord2(evt);
|
|
655
|
+
if (!record) continue;
|
|
656
|
+
cursor = record.id ?? evt.id ?? cursor;
|
|
657
|
+
yield record;
|
|
658
|
+
}
|
|
659
|
+
} catch (err) {
|
|
660
|
+
if (internal.signal.aborted) return;
|
|
661
|
+
if (!reconnect) throw err;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (!reconnect || internal.signal.aborted) return;
|
|
665
|
+
await delay(backoff.next(), internal.signal);
|
|
666
|
+
}
|
|
667
|
+
} finally {
|
|
668
|
+
signal?.removeEventListener("abort", onAbort);
|
|
669
|
+
internal.abort();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Send a training message and stream just that turn's records, ending at
|
|
674
|
+
* the agent's terminal `result`. Streams the latest turn (`turns=1`) after
|
|
675
|
+
* the send; an `idleTimeoutMs` gap (default 180s) bounds cold-start waits.
|
|
676
|
+
*/
|
|
677
|
+
async *runTurn(message, opts = {}) {
|
|
678
|
+
await this.send(message, { signal: opts.signal });
|
|
679
|
+
const { signal, idleTimeoutMs = 18e4 } = opts;
|
|
680
|
+
const idleController = new AbortController();
|
|
681
|
+
let idleTimer;
|
|
682
|
+
const combined = new AbortController();
|
|
683
|
+
const onA = () => combined.abort();
|
|
684
|
+
const onIdle = () => combined.abort();
|
|
685
|
+
signal?.addEventListener("abort", onA, { once: true });
|
|
686
|
+
idleController.signal.addEventListener("abort", onIdle, { once: true });
|
|
687
|
+
const armIdle = () => {
|
|
688
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
689
|
+
idleTimer = setTimeout(() => idleController.abort(new Error("idle timeout")), idleTimeoutMs);
|
|
690
|
+
};
|
|
691
|
+
armIdle();
|
|
692
|
+
try {
|
|
693
|
+
for await (const record of this.stream({ turns: 1, signal: combined.signal })) {
|
|
694
|
+
armIdle();
|
|
695
|
+
yield record;
|
|
696
|
+
if (isTurnComplete(record)) return;
|
|
697
|
+
}
|
|
698
|
+
if (idleController.signal.aborted && !signal?.aborted) {
|
|
699
|
+
throw new AbortError("Training turn timed out waiting for the agent.");
|
|
700
|
+
}
|
|
701
|
+
} finally {
|
|
702
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
703
|
+
signal?.removeEventListener("abort", onA);
|
|
704
|
+
combined.abort();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/workspace/workspaceClient.ts
|
|
710
|
+
var seg = (s) => encodeURIComponent(s);
|
|
711
|
+
var WorkspaceClient = class {
|
|
712
|
+
constructor(http) {
|
|
713
|
+
this.http = http;
|
|
714
|
+
}
|
|
715
|
+
// -- Partner (workspace API key) -----------------------------------------
|
|
716
|
+
/**
|
|
717
|
+
* Identify the workspace the configured API key belongs to. Solves the
|
|
718
|
+
* bootstrap problem for a new partner who holds only the `sk_ws_…` token
|
|
719
|
+
* but every slug-keyed route needs a slug.
|
|
720
|
+
*/
|
|
721
|
+
whoami(opts = {}) {
|
|
722
|
+
return this.http.requestJson("/workspaces/whoami", { signal: opts.signal });
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Provision (or look up) a workspace member by SP user reference.
|
|
726
|
+
* Idempotent on `(workspace, sp_user_ref)` — the first call creates the
|
|
727
|
+
* member, later calls return the existing ids (`created: false`).
|
|
728
|
+
*/
|
|
729
|
+
provisionMember(slug, body, opts = {}) {
|
|
730
|
+
return this.http.requestJson(`/workspaces/${seg(slug)}/members`, {
|
|
731
|
+
method: "POST",
|
|
732
|
+
body,
|
|
733
|
+
signal: opts.signal
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/** Suspend a member (soft — sets status to `suspended`). */
|
|
737
|
+
async suspendMember(slug, memberId, opts = {}) {
|
|
738
|
+
await this.http.requestJson(`/workspaces/${seg(slug)}/members/${seg(memberId)}`, {
|
|
739
|
+
method: "DELETE",
|
|
740
|
+
signal: opts.signal
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Mint a session JWT for an already-provisioned member. The partner
|
|
745
|
+
* authenticates the human on their side, then calls this with the same
|
|
746
|
+
* `sp_user_ref` to get a token the member's client presents to the platform.
|
|
747
|
+
*/
|
|
748
|
+
mintSession(slug, body, opts = {}) {
|
|
749
|
+
return this.http.requestJson(`/workspaces/${seg(slug)}/sessions`, {
|
|
750
|
+
method: "POST",
|
|
751
|
+
body,
|
|
752
|
+
signal: opts.signal
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
// -- Either auth ---------------------------------------------------------
|
|
756
|
+
/** List workspace members with current balances (capped at 500). */
|
|
757
|
+
listMembers(slug, opts = {}) {
|
|
758
|
+
return this.http.requestJson(`/workspaces/${seg(slug)}/members`, {
|
|
759
|
+
signal: opts.signal
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Grant credits to a member from the workspace pool. Idempotent on
|
|
764
|
+
* `body.idempotency_key` — replays return the original purchase.
|
|
765
|
+
*/
|
|
766
|
+
grantCredits(slug, memberId, body, opts = {}) {
|
|
767
|
+
return this.http.requestJson(
|
|
768
|
+
`/workspaces/${seg(slug)}/members/${seg(memberId)}/credits`,
|
|
769
|
+
{ method: "POST", body, signal: opts.signal }
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
// -- Public (no auth) ----------------------------------------------------
|
|
773
|
+
/**
|
|
774
|
+
* Send a one-time SMS verification code for public signup. Requires the
|
|
775
|
+
* workspace to have `public_sms_signup_enabled`.
|
|
776
|
+
*/
|
|
777
|
+
async sendSmsCode(slug, body, opts = {}) {
|
|
778
|
+
await this.http.requestJson(`/workspaces/${seg(slug)}/sms/send`, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
body,
|
|
781
|
+
signal: opts.signal
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
/** Verify an SMS code and mint a member session JWT in one shot. */
|
|
785
|
+
verifySmsCode(slug, body, opts = {}) {
|
|
786
|
+
return this.http.requestJson(`/workspaces/${seg(slug)}/sms/verify`, {
|
|
787
|
+
method: "POST",
|
|
788
|
+
body,
|
|
789
|
+
signal: opts.signal
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* A slug-bound handle so you don't repeat the slug on every call.
|
|
794
|
+
* `client.workspaces.workspace('acme').listMembers()`.
|
|
795
|
+
*/
|
|
796
|
+
workspace(slug) {
|
|
797
|
+
return new Workspace(this, slug);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var Workspace = class {
|
|
801
|
+
constructor(client, slug) {
|
|
802
|
+
this.client = client;
|
|
803
|
+
this.slug = slug;
|
|
804
|
+
}
|
|
805
|
+
listMembers(opts) {
|
|
806
|
+
return this.client.listMembers(this.slug, opts);
|
|
807
|
+
}
|
|
808
|
+
provisionMember(body, opts) {
|
|
809
|
+
return this.client.provisionMember(this.slug, body, opts);
|
|
810
|
+
}
|
|
811
|
+
suspendMember(memberId, opts) {
|
|
812
|
+
return this.client.suspendMember(this.slug, memberId, opts);
|
|
813
|
+
}
|
|
814
|
+
grantCredits(memberId, body, opts) {
|
|
815
|
+
return this.client.grantCredits(this.slug, memberId, body, opts);
|
|
816
|
+
}
|
|
817
|
+
mintSession(body, opts) {
|
|
818
|
+
return this.client.mintSession(this.slug, body, opts);
|
|
819
|
+
}
|
|
820
|
+
sendSmsCode(body, opts) {
|
|
821
|
+
return this.client.sendSmsCode(this.slug, body, opts);
|
|
822
|
+
}
|
|
823
|
+
verifySmsCode(body, opts) {
|
|
824
|
+
return this.client.verifySmsCode(this.slug, body, opts);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/client.ts
|
|
829
|
+
var OpenhexClient = class {
|
|
830
|
+
constructor(config = {}) {
|
|
831
|
+
this.config = config;
|
|
832
|
+
const http = new HttpClient({
|
|
833
|
+
apiKey: config.apiKey,
|
|
834
|
+
baseUrl: config.baseUrl,
|
|
835
|
+
loginType: config.loginType,
|
|
836
|
+
actAs: config.actAs,
|
|
837
|
+
timeoutMs: config.timeoutMs,
|
|
838
|
+
fetch: config.fetch
|
|
839
|
+
});
|
|
840
|
+
this.http = http;
|
|
841
|
+
this.chat = new AgentChatClient(http);
|
|
842
|
+
this.workspaces = new WorkspaceClient(http);
|
|
843
|
+
}
|
|
844
|
+
/** Low- and high-level chat API (conversations protocol). */
|
|
845
|
+
chat;
|
|
846
|
+
/**
|
|
847
|
+
* Workspace API (`/api/v2/workspaces/*`) — provision members, mint
|
|
848
|
+
* member sessions, grant credits, read reporting. Auth is whatever
|
|
849
|
+
* token the client is configured with (a `sk_ws_…` workspace API key
|
|
850
|
+
* for partner calls, or the owner's JWT for SP-admin calls).
|
|
851
|
+
*/
|
|
852
|
+
workspaces;
|
|
853
|
+
http;
|
|
854
|
+
/**
|
|
855
|
+
* A slug-bound workspace handle so you don't repeat the slug on every
|
|
856
|
+
* call. Equivalent to `client.workspaces.workspace(slug)`.
|
|
857
|
+
*/
|
|
858
|
+
workspace(slug) {
|
|
859
|
+
return this.workspaces.workspace(slug);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Training-mode chat with an agent AS ITS OWNER (the dev/train protocol —
|
|
863
|
+
* the agent runs in CREATION/TRAINING mode and can learn from feedback).
|
|
864
|
+
* Defaults to the client's configured `agentId`; pass one to override.
|
|
865
|
+
*/
|
|
866
|
+
training(agentId) {
|
|
867
|
+
const id = agentId ?? this.config.agentId;
|
|
868
|
+
if (!id) {
|
|
869
|
+
throw new Error("training() needs an agent id: set { agentId } on the client or pass one.");
|
|
870
|
+
}
|
|
871
|
+
return new TrainingChat(this.http, id);
|
|
872
|
+
}
|
|
873
|
+
/** Default target agents for new conversations (from `config.agentId`). */
|
|
874
|
+
defaultTargets(opts) {
|
|
875
|
+
if (opts?.targetAgentIds) return opts.targetAgentIds;
|
|
876
|
+
return this.config.agentId ? [this.config.agentId] : void 0;
|
|
877
|
+
}
|
|
878
|
+
/** Build a {@link SendRequest} from a message + turn options. */
|
|
879
|
+
buildRequest(message, opts) {
|
|
880
|
+
if (opts?.conversationId) return { message, conversationId: opts.conversationId };
|
|
881
|
+
const targetAgentIds = this.defaultTargets(opts);
|
|
882
|
+
if (!targetAgentIds) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
"No target agent: set { agentId } on the client, or pass { targetAgentIds } / { conversationId } in options."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
return { message, targetAgentIds };
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Send a message and resolve with the aggregated turn. Creates a new
|
|
891
|
+
* conversation routed to the configured agent unless `opts.conversationId`
|
|
892
|
+
* is given. The returned turn carries `conversationId` to continue.
|
|
893
|
+
*/
|
|
894
|
+
sendMessage(message, opts = {}) {
|
|
895
|
+
return this.chat.sendMessage(this.buildRequest(message, opts), opts);
|
|
896
|
+
}
|
|
897
|
+
/** Stream a single turn's records (see {@link sendMessage}). */
|
|
898
|
+
runTurn(message, opts = {}) {
|
|
899
|
+
return this.chat.runTurn(this.buildRequest(message, opts), opts);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Start a stateful conversation that remembers its id across turns,
|
|
903
|
+
* defaulting its target agents to the configured agent.
|
|
904
|
+
*/
|
|
905
|
+
conversation(opts = {}) {
|
|
906
|
+
return this.chat.conversation({
|
|
907
|
+
conversationId: opts.conversationId,
|
|
908
|
+
targetAgentIds: opts.targetAgentIds ?? this.defaultTargets()
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Run an agent query using this client's connection + default options.
|
|
913
|
+
* Per-call `options` are shallow-merged over the client defaults.
|
|
914
|
+
*/
|
|
915
|
+
query(params) {
|
|
916
|
+
return query({
|
|
917
|
+
prompt: params.prompt,
|
|
918
|
+
options: {
|
|
919
|
+
apiKey: this.config.apiKey,
|
|
920
|
+
baseUrl: this.config.baseUrl,
|
|
921
|
+
agentId: this.config.agentId,
|
|
922
|
+
...this.config.defaultOptions,
|
|
923
|
+
...params.options
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
/** List recent sessions for the configured agent. */
|
|
928
|
+
async listSessions() {
|
|
929
|
+
throw new NotImplementedError("OpenhexClient.listSessions");
|
|
930
|
+
}
|
|
931
|
+
/** Fetch metadata for a single session. */
|
|
932
|
+
async getSession(_sessionId) {
|
|
933
|
+
throw new NotImplementedError("OpenhexClient.getSession");
|
|
934
|
+
}
|
|
935
|
+
/** Delete a session and its stored context. */
|
|
936
|
+
async deleteSession(_sessionId) {
|
|
937
|
+
throw new NotImplementedError("OpenhexClient.deleteSession");
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/tools/index.ts
|
|
942
|
+
function tool(name, description, inputSchema, handler) {
|
|
943
|
+
return { name, description, inputSchema, handler };
|
|
944
|
+
}
|
|
945
|
+
function createSdkMcpServer(config) {
|
|
946
|
+
return {
|
|
947
|
+
type: "sdk",
|
|
948
|
+
name: config.name,
|
|
949
|
+
version: config.version ?? "0.0.1",
|
|
950
|
+
tools: config.tools
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/hooks.ts
|
|
955
|
+
function hookMatcher(matcher, ...hooks) {
|
|
956
|
+
return matcher === void 0 ? { hooks } : { matcher, hooks };
|
|
957
|
+
}
|
|
958
|
+
export {
|
|
959
|
+
API_PREFIX,
|
|
960
|
+
AbortError,
|
|
961
|
+
AgentChatClient,
|
|
962
|
+
ApiError,
|
|
963
|
+
AuthenticationError,
|
|
964
|
+
Backoff,
|
|
965
|
+
Conversation,
|
|
966
|
+
DEFAULT_BASE_URL,
|
|
967
|
+
HttpClient,
|
|
968
|
+
HttpTransport,
|
|
969
|
+
NotImplementedError,
|
|
970
|
+
OpenhexClient,
|
|
971
|
+
OpenhexSdkError,
|
|
972
|
+
TrainingChat,
|
|
973
|
+
Workspace,
|
|
974
|
+
WorkspaceClient,
|
|
975
|
+
createSdkMcpServer,
|
|
976
|
+
delay,
|
|
977
|
+
extractText,
|
|
978
|
+
extractToolCalls,
|
|
979
|
+
hookMatcher,
|
|
980
|
+
isAgentRecord,
|
|
981
|
+
isInterrupt,
|
|
982
|
+
isTurnComplete,
|
|
983
|
+
parseSSEStream,
|
|
984
|
+
query,
|
|
985
|
+
tool
|
|
986
|
+
};
|
|
987
|
+
//# sourceMappingURL=index.js.map
|