codai-sdk 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,15 +1,483 @@
1
- // src/index.ts
1
+ // src/http.ts
2
2
  var CodaiError = class extends Error {
3
- constructor(message, status, body) {
3
+ constructor(message, status, body, meta = {}) {
4
4
  super(message);
5
5
  this.status = status;
6
6
  this.body = body;
7
7
  this.name = "CodaiError";
8
+ this.code = meta.code ?? extractCode(body);
9
+ this.requestId = meta.requestId ?? null;
10
+ this.retryAfter = meta.retryAfter ?? null;
8
11
  }
9
12
  status;
10
13
  body;
14
+ /** Stable gateway error code (`invalid_api_key`, `rate_limit_exceeded`, …) when the body was an error envelope. */
15
+ code;
16
+ /** `x-codai-trace-id` (or `x-request-id`) of the failed response, when present. */
17
+ requestId;
18
+ /** Seconds to wait before retrying, from `Retry-After` on 429s. */
19
+ retryAfter;
11
20
  };
21
+ function extractCode(body) {
22
+ if (body && typeof body === "object") {
23
+ const err = body.error;
24
+ if (err && typeof err === "object" && typeof err.code === "string")
25
+ return err.code;
26
+ }
27
+ return null;
28
+ }
29
+ function extractMessage(body) {
30
+ if (body && typeof body === "object") {
31
+ const err = body.error;
32
+ if (err && typeof err === "object" && typeof err.message === "string") return err.message;
33
+ }
34
+ return null;
35
+ }
12
36
  var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
37
+ var EXTENSION_HEADERS = {
38
+ sessionId: "x-codai-session-id",
39
+ requestId: "x-request-id",
40
+ effort: "x-codai-effort",
41
+ thinking: "x-codai-thinking",
42
+ thinkingBudget: "x-codai-thinking-budget",
43
+ thinkingPin: "x-codai-thinking-pin",
44
+ cache: "x-codai-cache",
45
+ noTask: "x-codai-no-task",
46
+ taskId: "x-codai-task-id",
47
+ taskOutcome: "x-codai-task-outcome",
48
+ taskEvidence: "x-codai-task-evidence",
49
+ client: "x-codai-client",
50
+ incognito: "x-codai-incognito",
51
+ noRecall: "x-codai-no-recall",
52
+ provenOnly: "x-codai-proven-only",
53
+ newSession: "x-codai-new-session",
54
+ repo: "x-codai-repo",
55
+ agentId: "x-codai-agent-id",
56
+ disableSubagents: "x-codai-disable-subagents",
57
+ mode: "x-codai-mode",
58
+ serverTools: "x-codai-server-tools",
59
+ orchestrate: "x-codai-orchestrate",
60
+ cascade: "x-codai-cascade",
61
+ bestOf: "x-codai-best-of",
62
+ bestOfDepth: "x-codai-best-of-depth",
63
+ reflect: "x-codai-reflect",
64
+ stepVerify: "x-codai-step-verify",
65
+ plan: "x-codai-plan",
66
+ consensus: "x-codai-consensus",
67
+ compact: "x-codai-compact",
68
+ retrieval: "x-codai-retrieval",
69
+ heuristics: "x-codai-heuristics",
70
+ identity: "x-codai-identity",
71
+ playbook: "x-codai-playbook",
72
+ noPlaybook: "x-codai-no-playbook",
73
+ debug: "x-codai-debug",
74
+ device: "x-codai-device",
75
+ deviceName: "x-codai-device-name",
76
+ devicePlatform: "x-codai-device-platform",
77
+ pushToken: "x-codai-push-token",
78
+ shareToken: "x-codai-share-token"
79
+ };
80
+ function extensionHeaders(ext) {
81
+ const out = {};
82
+ if (!ext) return out;
83
+ for (const [key, header] of Object.entries(EXTENSION_HEADERS)) {
84
+ const value = ext[key];
85
+ if (value === void 0 || value === null) continue;
86
+ if (typeof value === "boolean") out[header] = value ? "1" : "0";
87
+ else if (key === "compact" && value === "auto") out[header] = "1";
88
+ else out[header] = String(value);
89
+ }
90
+ if (ext.headers) Object.assign(out, ext.headers);
91
+ return out;
92
+ }
93
+ var HttpClient = class {
94
+ apiKey;
95
+ baseUrl;
96
+ timeoutMs;
97
+ maxRetries;
98
+ defaults;
99
+ fetchImpl;
100
+ constructor(opts) {
101
+ this.apiKey = opts.apiKey;
102
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
103
+ this.timeoutMs = opts.timeoutMs;
104
+ this.maxRetries = opts.maxRetries;
105
+ this.defaults = opts.defaults ?? {};
106
+ this.fetchImpl = opts.fetch ?? ((input, init) => fetch(input, init));
107
+ }
108
+ url(path, query) {
109
+ const url = new URL(`${this.baseUrl}${path}`);
110
+ if (query) {
111
+ for (const [k, v] of Object.entries(query)) {
112
+ if (v === void 0 || v === null) continue;
113
+ url.searchParams.set(k, typeof v === "boolean" ? v ? "1" : "0" : String(v));
114
+ }
115
+ }
116
+ return url.toString();
117
+ }
118
+ headers(ext, json = true) {
119
+ return {
120
+ Authorization: `Bearer ${this.apiKey}`,
121
+ ...json ? { "Content-Type": "application/json" } : {},
122
+ ...extensionHeaders(this.defaults),
123
+ ...extensionHeaders(ext)
124
+ };
125
+ }
126
+ /** Perform a request; throws `CodaiError` on non-2xx (after retries on 429/5xx). */
127
+ async request(path, init = {}) {
128
+ const url = this.url(path, init.query);
129
+ const hasJson = init.body !== void 0;
130
+ const headers = this.headers(init.ext, hasJson);
131
+ const method = init.method ?? (hasJson || init.rawBody !== void 0 ? "POST" : "GET");
132
+ const retries = init.noRetry || init.rawBody !== void 0 ? 0 : this.maxRetries;
133
+ const timeout = init.timeoutMs ?? this.timeoutMs;
134
+ const accept = new Set(init.acceptStatus ?? []);
135
+ let lastErr;
136
+ for (let attempt = 0; attempt <= retries; attempt++) {
137
+ const signal = init.signal ?? (timeout > 0 ? AbortSignal.timeout(timeout) : void 0);
138
+ try {
139
+ const res = await this.fetchImpl(url, {
140
+ method,
141
+ headers,
142
+ ...hasJson ? { body: JSON.stringify(init.body) } : {},
143
+ ...init.rawBody !== void 0 ? { body: init.rawBody } : {},
144
+ ...signal ? { signal } : {}
145
+ });
146
+ if (res.ok || accept.has(res.status)) return res;
147
+ if (RETRYABLE.has(res.status) && attempt < retries) {
148
+ await sleep(500 * 2 ** attempt + Math.random() * 250);
149
+ continue;
150
+ }
151
+ throw await this.toError(path, res);
152
+ } catch (err) {
153
+ if (err instanceof CodaiError) throw err;
154
+ lastErr = err;
155
+ if (init.signal?.aborted) throw err;
156
+ if (attempt < retries) {
157
+ await sleep(500 * 2 ** attempt);
158
+ continue;
159
+ }
160
+ }
161
+ }
162
+ throw new CodaiError(`codai ${path} failed after retries: ${String(lastErr)}`, 0, null);
163
+ }
164
+ async json(path, init = {}) {
165
+ const res = await this.request(path, init);
166
+ return await res.json();
167
+ }
168
+ /**
169
+ * Open a `text/event-stream` and iterate parsed frames. Frames are
170
+ * `\n\n`-separated blocks; each yields `{ event, data }` where `event`
171
+ * defaults to `message` and `data` is the joined `data:` payload (raw
172
+ * string — the caller decides whether it is JSON or `[DONE]`). Comment
173
+ * lines (`: ping`) are skipped. The stream is retried like any request
174
+ * up to the first byte; after that errors propagate.
175
+ */
176
+ async *sse(path, init = {}) {
177
+ const res = await this.request(path, {
178
+ ...init,
179
+ timeoutMs: init.timeoutMs ?? 0,
180
+ noRetry: true
181
+ });
182
+ yield* readSse(res, init.signal);
183
+ return res;
184
+ }
185
+ async toError(path, res) {
186
+ let body = null;
187
+ try {
188
+ body = await res.json();
189
+ } catch {
190
+ }
191
+ const retryAfterRaw = res.headers.get("retry-after");
192
+ const retryAfter = retryAfterRaw && /^\d+$/.test(retryAfterRaw) ? Number(retryAfterRaw) : null;
193
+ const message = extractMessage(body);
194
+ return new CodaiError(
195
+ message ? `codai ${path} \u2192 ${res.status}: ${message}` : `codai ${path} \u2192 ${res.status}`,
196
+ res.status,
197
+ body,
198
+ {
199
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
200
+ retryAfter
201
+ }
202
+ );
203
+ }
204
+ };
205
+ async function* readSse(res, signal) {
206
+ const reader = res.body?.getReader();
207
+ if (!reader) throw new CodaiError("no response body", res.status, null);
208
+ const decoder = new TextDecoder();
209
+ let buffer = "";
210
+ const onAbort = () => void reader.cancel().catch(() => {
211
+ });
212
+ signal?.addEventListener("abort", onAbort, { once: true });
213
+ try {
214
+ for (; ; ) {
215
+ const { done, value } = await reader.read();
216
+ if (done) break;
217
+ buffer += decoder.decode(value, { stream: true });
218
+ buffer = buffer.replace(/\r\n/g, "\n");
219
+ const blocks = buffer.split("\n\n");
220
+ buffer = blocks.pop() ?? "";
221
+ for (const block of blocks) {
222
+ const frame = parseSseBlock(block);
223
+ if (frame) yield frame;
224
+ }
225
+ }
226
+ const tail = buffer.trim() ? parseSseBlock(buffer) : null;
227
+ if (tail) yield tail;
228
+ } finally {
229
+ signal?.removeEventListener("abort", onAbort);
230
+ reader.releaseLock?.();
231
+ }
232
+ }
233
+ function parseSseBlock(block) {
234
+ let event = "message";
235
+ const data = [];
236
+ for (const line of block.split("\n")) {
237
+ if (!line || line.startsWith(":")) continue;
238
+ if (line.startsWith("event:")) event = line.slice(6).trim();
239
+ else if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
240
+ }
241
+ if (data.length === 0) return null;
242
+ return { event, data: data.join("\n") };
243
+ }
244
+ function frameJson(frame) {
245
+ if (frame.data === "[DONE]") return null;
246
+ try {
247
+ return JSON.parse(frame.data);
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
252
+ function sleep(ms) {
253
+ return new Promise((r) => setTimeout(r, ms));
254
+ }
255
+
256
+ // src/resources/account.ts
257
+ var Account = class {
258
+ constructor(http) {
259
+ this.http = http;
260
+ }
261
+ http;
262
+ /** `GET /v1/account` — the full account card. */
263
+ get(opts = {}) {
264
+ return this.http.json("/v1/account", { ext: opts.ext, signal: opts.signal });
265
+ }
266
+ /** `PATCH /v1/account` — consents, username, referral (at least one field). */
267
+ update(body, opts = {}) {
268
+ return this.http.json("/v1/account", {
269
+ method: "PATCH",
270
+ body,
271
+ ext: opts.ext,
272
+ signal: opts.signal
273
+ });
274
+ }
275
+ };
276
+ var Receipts = class {
277
+ constructor(http) {
278
+ this.http = http;
279
+ }
280
+ http;
281
+ /**
282
+ * `GET /v1/receipt` — spend receipt. `sessionId` wins over `since`; with
283
+ * neither, the last 24 hours.
284
+ */
285
+ get(query = {}, opts = {}) {
286
+ return this.http.json("/v1/receipt", {
287
+ query: {
288
+ session_id: query.sessionId,
289
+ since: query.since instanceof Date ? query.since.toISOString() : query.since
290
+ },
291
+ ext: opts.ext,
292
+ signal: opts.signal
293
+ });
294
+ }
295
+ };
296
+ var Feedback = class {
297
+ constructor(http) {
298
+ this.http = http;
299
+ }
300
+ http;
301
+ /**
302
+ * `POST /v1/feedback` — rate one turn. Exactly one of `event_id`,
303
+ * `session_id`, `client_request_id` must be present.
304
+ */
305
+ submit(body, opts = {}) {
306
+ return this.http.json("/v1/feedback", {
307
+ body,
308
+ ext: opts.ext,
309
+ signal: opts.signal
310
+ });
311
+ }
312
+ };
313
+ var PhoneModels = class {
314
+ constructor(http) {
315
+ this.http = http;
316
+ }
317
+ http;
318
+ /** `GET /v1/phone/models` — on-device model catalog with signed URLs. */
319
+ async list(opts = {}) {
320
+ const raw = await this.http.json("/v1/phone/models", {
321
+ ext: opts.ext,
322
+ signal: opts.signal
323
+ });
324
+ return [...raw.models ?? []];
325
+ }
326
+ };
327
+
328
+ // src/resources/agents.ts
329
+ var AgentRuns = class {
330
+ constructor(http) {
331
+ this.http = http;
332
+ }
333
+ http;
334
+ /** `POST /v1/agents/runs` — returns 202 with the run id. */
335
+ create(body, opts = {}) {
336
+ return this.http.json("/v1/agents/runs", {
337
+ body,
338
+ ext: opts.ext,
339
+ signal: opts.signal
340
+ });
341
+ }
342
+ /** `GET /v1/agents/runs/{id}` */
343
+ get(id, opts = {}) {
344
+ return this.http.json(`/v1/agents/runs/${encodeURIComponent(id)}`, {
345
+ ext: opts.ext,
346
+ signal: opts.signal
347
+ });
348
+ }
349
+ /** `GET /v1/agents/runs/{id}/steps` — newest first. */
350
+ async steps(id, opts = {}) {
351
+ const raw = await this.http.json(
352
+ `/v1/agents/runs/${encodeURIComponent(id)}/steps`,
353
+ { ext: opts.ext, signal: opts.signal }
354
+ );
355
+ return [...raw.steps ?? []];
356
+ }
357
+ /** `GET /v1/agents/runs/stats?days=` */
358
+ stats(days, opts = {}) {
359
+ return this.http.json("/v1/agents/runs/stats", {
360
+ query: { days },
361
+ ext: opts.ext,
362
+ signal: opts.signal
363
+ });
364
+ }
365
+ /** `POST /v1/agents/runs/{id}/cancel` */
366
+ cancel(id, opts = {}) {
367
+ return this.http.json(
368
+ `/v1/agents/runs/${encodeURIComponent(id)}/cancel`,
369
+ { method: "POST", body: {}, ext: opts.ext, signal: opts.signal }
370
+ );
371
+ }
372
+ /**
373
+ * `GET /v1/agents/runs/{id}/stream` — async iterator over `step` / `done` /
374
+ * `timeout` events. The iterator ends after `done`; on `timeout` (30 min)
375
+ * it also ends and you may reconnect (full replay from seq 0).
376
+ */
377
+ async *stream(id, opts = {}) {
378
+ const frames = this.http.sse(`/v1/agents/runs/${encodeURIComponent(id)}/stream`, {
379
+ ext: opts.ext,
380
+ signal: opts.signal
381
+ });
382
+ for await (const frame of frames) {
383
+ const data = frameJson(frame);
384
+ if (!data) continue;
385
+ const ev = { event: frame.event, data };
386
+ yield ev;
387
+ if (ev.event === "done" || ev.event === "timeout") return;
388
+ }
389
+ }
390
+ };
391
+ var Agents = class {
392
+ constructor(http) {
393
+ this.http = http;
394
+ this.runs = new AgentRuns(http);
395
+ }
396
+ http;
397
+ runs;
398
+ /** `POST /v1/agents/run` — blocks until the agent loop finishes. */
399
+ async run(body, opts = {}) {
400
+ const res = await this.http.request("/v1/agents/run", {
401
+ body,
402
+ ext: opts.ext,
403
+ signal: opts.signal
404
+ });
405
+ const raw = await res.json();
406
+ return {
407
+ result: String(raw.result ?? ""),
408
+ model: String(raw.model ?? ""),
409
+ eventId: raw.event_id ?? res.headers.get("x-codai-event-id"),
410
+ usage: raw.usage ?? null,
411
+ raw,
412
+ headers: res.headers
413
+ };
414
+ }
415
+ };
416
+
417
+ // src/resources/audio.ts
418
+ var Audio = class {
419
+ constructor(http) {
420
+ this.http = http;
421
+ }
422
+ http;
423
+ /** Speech-to-text (multipart upload; never retried). Returns the transcript (0.2.x shape). */
424
+ async transcribe(opts) {
425
+ return (await this.transcribeDetailed(opts)).text;
426
+ }
427
+ /** Speech-to-text with the raw upstream JSON and routing headers. */
428
+ async transcribeDetailed(opts) {
429
+ const form = new FormData();
430
+ const blob = opts.file instanceof Blob ? opts.file : new Blob([opts.file]);
431
+ form.append("file", blob, opts.filename ?? "audio.webm");
432
+ form.append("model", opts.model ?? "codai-transcribe");
433
+ if (opts.language) form.append("language", opts.language);
434
+ if (opts.prompt) form.append("prompt", opts.prompt);
435
+ if (opts.responseFormat) form.append("response_format", opts.responseFormat);
436
+ if (opts.temperature !== void 0) form.append("temperature", String(opts.temperature));
437
+ const res = await this.http.request("/v1/audio/transcriptions", {
438
+ method: "POST",
439
+ rawBody: form,
440
+ ext: opts.ext,
441
+ signal: opts.signal
442
+ });
443
+ const raw = await res.json();
444
+ return {
445
+ text: typeof raw.text === "string" ? raw.text : "",
446
+ raw,
447
+ routedTo: res.headers.get("x-codai-routed-to"),
448
+ headers: res.headers
449
+ };
450
+ }
451
+ /** Text-to-speech. Returns the raw audio bytes (0.2.x shape); see `speechDetailed()` for headers. */
452
+ async speech(body, opts = {}) {
453
+ return (await this.speechDetailed(body, opts)).audio;
454
+ }
455
+ /** Text-to-speech with the upstream `content-type` and routing headers. */
456
+ async speechDetailed(body, opts = {}) {
457
+ const res = await this.http.request("/v1/audio/speech", {
458
+ body: { ...body, model: body.model ?? "codai-tts", voice: body.voice ?? "alloy" },
459
+ ext: opts.ext,
460
+ signal: opts.signal
461
+ });
462
+ return {
463
+ audio: await res.arrayBuffer(),
464
+ contentType: res.headers.get("content-type") ?? "audio/mpeg",
465
+ routedTo: res.headers.get("x-codai-routed-to"),
466
+ headers: res.headers
467
+ };
468
+ }
469
+ };
470
+
471
+ // src/resources/chat.ts
472
+ function toUsage(u) {
473
+ if (!u) return null;
474
+ const cached = u.prompt_tokens_details?.cached_tokens;
475
+ return {
476
+ promptTokens: u.prompt_tokens ?? 0,
477
+ completionTokens: u.completion_tokens ?? 0,
478
+ ...cached !== void 0 ? { cachedTokens: cached } : {}
479
+ };
480
+ }
13
481
  function mergeToolCall(acc, index, fragment) {
14
482
  const existing = acc.get(index) ?? { index };
15
483
  for (const [key, val] of Object.entries(fragment)) {
@@ -17,11 +485,8 @@ function mergeToolCall(acc, index, fragment) {
17
485
  if (key === "function" && val && typeof val === "object") {
18
486
  const fn = existing.function ?? {};
19
487
  for (const [fk, fv] of Object.entries(val)) {
20
- if (fk === "arguments") {
21
- fn.arguments = String(fn.arguments ?? "") + String(fv ?? "");
22
- } else {
23
- fn[fk] = fv;
24
- }
488
+ if (fk === "arguments") fn.arguments = String(fn.arguments ?? "") + String(fv ?? "");
489
+ else fn[fk] = fv;
25
490
  }
26
491
  existing.function = fn;
27
492
  } else {
@@ -30,49 +495,44 @@ function mergeToolCall(acc, index, fragment) {
30
495
  }
31
496
  acc.set(index, existing);
32
497
  }
33
- var Codai = class {
34
- apiKey;
35
- baseUrl;
36
- sessionId;
37
- timeoutMs;
38
- maxRetries;
39
- constructor(opts) {
40
- if (!opts.apiKey) throw new Error("Codai: apiKey is required");
41
- this.apiKey = opts.apiKey;
42
- this.baseUrl = (opts.baseUrl ?? "https://ai.codai.ro").replace(/\/$/, "");
43
- this.sessionId = opts.sessionId;
44
- this.timeoutMs = opts.timeoutMs ?? 12e4;
45
- this.maxRetries = opts.maxRetries ?? 2;
46
- }
47
- // ---------------------------------------------------------------- chat
48
- async chat(opts) {
49
- const res = await this.request("/v1/chat/completions", this.chatBody(opts, false), {
50
- signal: opts.signal,
51
- headers: this.chatHeaders(opts)
498
+ var ChatCompletions = class {
499
+ constructor(http) {
500
+ this.http = http;
501
+ }
502
+ http;
503
+ /**
504
+ * Non-streaming completion. `stream` is forced to `false`; use
505
+ * `stream()` for SSE.
506
+ */
507
+ async create(body, opts = {}) {
508
+ const res = await this.http.request("/v1/chat/completions", {
509
+ body: { ...body, model: body.model ?? "codai", stream: false },
510
+ ext: opts.ext,
511
+ signal: opts.signal
52
512
  });
53
513
  const raw = await res.json();
54
- const choices = raw.choices;
514
+ const first = raw.choices?.[0];
55
515
  const usage = raw.usage;
56
516
  return {
57
- content: String(choices?.[0]?.message?.content ?? ""),
517
+ content: typeof first?.message?.content === "string" ? first.message.content : "",
518
+ toolCalls: [...first?.message?.tool_calls ?? []],
58
519
  raw,
59
- requestId: res.headers.get("x-request-id"),
520
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
521
+ eventId: res.headers.get("x-codai-event-id"),
60
522
  routedTo: res.headers.get("x-codai-routed-to"),
61
523
  execVerify: res.headers.get("x-codai-exec-verify"),
62
- usage: usage ? {
63
- promptTokens: usage.prompt_tokens ?? 0,
64
- completionTokens: usage.completion_tokens ?? 0
65
- } : null
524
+ usage: toUsage(usage),
525
+ headers: res.headers
66
526
  };
67
527
  }
68
- /**
69
- * Streaming chat. Async-iterate the returned object for text deltas, then
70
- * `await stream.final` for metadata (requestId, usage, routedTo, toolCalls).
71
- */
72
- chatStream(opts) {
73
- const requestPromise = this.request("/v1/chat/completions", this.chatBody(opts, true), {
528
+ /** Streaming completion (`stream: true`). */
529
+ stream(body, opts = {}) {
530
+ const requestPromise = this.http.request("/v1/chat/completions", {
531
+ body: { ...body, model: body.model ?? "codai", stream: true },
532
+ ext: opts.ext,
74
533
  signal: opts.signal,
75
- headers: this.chatHeaders(opts)
534
+ timeoutMs: 0,
535
+ noRetry: true
76
536
  });
77
537
  let resolveFinal;
78
538
  let rejectFinal;
@@ -83,257 +543,1454 @@ var Codai = class {
83
543
  final.catch(() => {
84
544
  });
85
545
  const toolCallsByIndex = /* @__PURE__ */ new Map();
86
- async function* iterate() {
87
- let content = "";
88
- let usage = null;
89
- let res;
546
+ let content = "";
547
+ let usage = null;
548
+ let finishReason = null;
549
+ let res = null;
550
+ const buildResult = () => ({
551
+ content,
552
+ toolCalls: [...toolCallsByIndex.values()],
553
+ requestId: res?.headers.get("x-codai-trace-id") ?? res?.headers.get("x-request-id") ?? null,
554
+ routedTo: res?.headers.get("x-codai-routed-to") ?? null,
555
+ execVerify: res?.headers.get("x-codai-exec-verify") ?? null,
556
+ usage,
557
+ finishReason,
558
+ headers: res?.headers ?? null
559
+ });
560
+ async function* chunks() {
90
561
  try {
91
562
  res = await requestPromise;
92
- } catch (err) {
93
- rejectFinal(err);
94
- throw err;
95
- }
96
- const reader = res.body?.getReader();
97
- if (!reader) {
98
- const e = new CodaiError("no response body", res.status, null);
99
- rejectFinal(e);
100
- throw e;
101
- }
102
- const decoder = new TextDecoder();
103
- let buffer = "";
104
- let finished = false;
105
- const buildResult = () => ({
106
- content,
107
- toolCalls: [...toolCallsByIndex.values()],
108
- requestId: res.headers.get("x-request-id"),
109
- routedTo: res.headers.get("x-codai-routed-to"),
110
- execVerify: res.headers.get("x-codai-exec-verify"),
111
- usage
112
- });
113
- try {
114
- for (; ; ) {
115
- const { done, value } = await reader.read();
116
- if (done) break;
117
- buffer += decoder.decode(value, { stream: true });
118
- const frames = buffer.split("\n\n");
119
- buffer = frames.pop() ?? "";
120
- for (const frame of frames) {
121
- for (const line of frame.split("\n")) {
122
- if (!line.startsWith("data:")) continue;
123
- const data = line.slice(5).trim();
124
- if (data === "[DONE]") {
125
- finished = true;
126
- break;
127
- }
128
- let chunk;
129
- try {
130
- chunk = JSON.parse(data);
131
- } catch {
132
- continue;
133
- }
134
- if (chunk.usage) {
135
- usage = {
136
- promptTokens: chunk.usage.prompt_tokens ?? 0,
137
- completionTokens: chunk.usage.completion_tokens ?? 0
138
- };
139
- }
140
- const delta = chunk.choices?.[0]?.delta;
141
- if (delta?.tool_calls) {
142
- for (const tc of delta.tool_calls) {
143
- const idx = typeof tc.index === "number" ? tc.index : 0;
144
- mergeToolCall(toolCallsByIndex, idx, tc);
145
- }
146
- }
147
- if (delta?.content) {
148
- content += delta.content;
149
- yield delta.content;
150
- }
563
+ for await (const frame of readSse(res, opts.signal)) {
564
+ if (frame.data === "[DONE]") break;
565
+ const chunk = frameJson(frame);
566
+ if (!chunk) continue;
567
+ if (chunk.usage) usage = toUsage(chunk.usage);
568
+ const choice = chunk.choices?.[0];
569
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
570
+ const delta = choice?.delta;
571
+ if (delta?.tool_calls) {
572
+ for (const tc of delta.tool_calls) {
573
+ mergeToolCall(toolCallsByIndex, typeof tc.index === "number" ? tc.index : 0, tc);
151
574
  }
152
- if (finished) break;
153
575
  }
154
- if (finished) break;
576
+ if (delta?.content) content += delta.content;
577
+ yield chunk;
155
578
  }
156
579
  resolveFinal(buildResult());
157
580
  } catch (err) {
158
581
  rejectFinal(err);
159
582
  throw err;
160
- } finally {
161
- reader.releaseLock?.();
162
583
  }
163
584
  }
164
- const gen = iterate.call(this);
585
+ async function* text() {
586
+ for await (const chunk of chunks()) {
587
+ const delta = chunk.choices?.[0]?.delta?.content;
588
+ if (delta) yield delta;
589
+ }
590
+ }
165
591
  return {
166
- [Symbol.asyncIterator]: () => gen,
592
+ [Symbol.asyncIterator]: () => text(),
593
+ chunks: () => ({ [Symbol.asyncIterator]: () => chunks() }),
167
594
  final
168
595
  };
169
596
  }
170
- // -------------------------------------------------------------- agents
171
- agents = {
172
- run: async (opts) => {
173
- const res = await this.request(
174
- "/v1/agents/run",
175
- {
176
- task: opts.task,
177
- ...opts.context ? { context: opts.context } : {},
178
- ...opts.system ? { system: opts.system } : {},
179
- ...opts.model ? { model: opts.model } : {}
180
- },
181
- { signal: opts.signal }
182
- );
183
- const body = await res.json();
184
- return {
185
- result: String(body.result ?? ""),
186
- model: String(body.model ?? ""),
187
- eventId: body.event_id ?? null,
188
- usage: body.usage ?? null
189
- };
190
- }
191
- };
192
- // ------------------------------------------------------------ feedback
193
- /** Rate a completed request: +1 (helpful) or -1 (not helpful). */
194
- async feedback(requestId, rating, comment) {
195
- await this.request("/v1/feedback", {
196
- event_id: requestId,
197
- rating,
198
- ...comment ? { comment } : {}
199
- });
200
- }
201
- // -------------------------------------------------------------- models
202
- async models() {
203
- const res = await this.request("/v1/models", null, { method: "GET" });
204
- const body = await res.json();
205
- return body.data ?? [];
597
+ };
598
+ var Chat = class {
599
+ completions;
600
+ constructor(http) {
601
+ this.completions = new ChatCompletions(http);
602
+ }
603
+ };
604
+
605
+ // src/resources/devices.ts
606
+ var Devices = class {
607
+ constructor(http) {
608
+ this.http = http;
609
+ }
610
+ http;
611
+ /** `GET /v1/devices` — most recently seen first. */
612
+ async list(opts = {}) {
613
+ const raw = await this.http.json("/v1/devices", {
614
+ ext: opts.ext,
615
+ signal: opts.signal
616
+ });
617
+ return [...raw.devices ?? []];
618
+ }
619
+ /** `PATCH /v1/devices/{id}` — push token and/or capabilities. */
620
+ update(id, body, opts = {}) {
621
+ return this.http.json(`/v1/devices/${encodeURIComponent(id)}`, {
622
+ method: "PATCH",
623
+ body,
624
+ ext: opts.ext,
625
+ signal: opts.signal
626
+ });
627
+ }
628
+ /** `DELETE /v1/devices/{id}` */
629
+ delete(id, opts = {}) {
630
+ return this.http.json(`/v1/devices/${encodeURIComponent(id)}`, {
631
+ method: "DELETE",
632
+ ext: opts.ext,
633
+ signal: opts.signal
634
+ });
206
635
  }
207
- // -------------------------------------------------------------- tokens
208
636
  /**
209
- * Mint a short-lived scoped token (60–3600s) for browser/webview clients.
210
- * The token authenticates only its scope's surface; chat is not mintable.
637
+ * `GET /v1/devices/me/dispatch?limit=` — pending controls dispatched at the
638
+ * calling device (`x-codai-device` required) across all owned sessions.
211
639
  */
212
- async mintToken(scope, ttlSeconds) {
213
- const res = await this.request("/v1/tokens", {
214
- scope,
215
- ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {}
640
+ dispatchInbox(query = {}, opts = {}) {
641
+ return this.http.json("/v1/devices/me/dispatch", {
642
+ query: { limit: query.limit },
643
+ ext: opts.ext,
644
+ signal: opts.signal
216
645
  });
217
- const body = await res.json();
218
- return { token: body.token, expiresAt: body.expires_at, scope: body.scope };
219
646
  }
220
- // ---------------------------------------------------------- embeddings
221
- async embeddings(opts) {
222
- const res = await this.request(
223
- "/v1/embeddings",
224
- {
225
- model: opts.model ?? "codai-embed",
226
- input: opts.input,
227
- ...opts.dimensions !== void 0 ? { dimensions: opts.dimensions } : {}
228
- },
229
- { signal: opts.signal }
230
- );
231
- const body = await res.json();
232
- return { embeddings: (body.data ?? []).map((d) => d.embedding), raw: body };
233
- }
234
- // --------------------------------------------------------------- audio
235
- audio = {
236
- /** Speech-to-text (multipart upload, max 25 MB). Returns the transcript. */
237
- transcribe: async (opts) => {
238
- const form = new FormData();
239
- const blob = opts.file instanceof Blob ? opts.file : new Blob([opts.file]);
240
- form.append("file", blob, opts.filename ?? "audio.webm");
241
- form.append("model", opts.model ?? "codai-transcribe");
242
- const res = await fetch(`${this.baseUrl}/v1/audio/transcriptions`, {
243
- method: "POST",
244
- headers: { Authorization: `Bearer ${this.apiKey}` },
245
- body: form,
246
- signal: opts.signal ?? AbortSignal.timeout(this.timeoutMs)
247
- });
248
- if (!res.ok) {
249
- let errBody = null;
250
- try {
251
- errBody = await res.json();
252
- } catch {
253
- }
254
- throw new CodaiError(`codai /v1/audio/transcriptions \u2192 ${res.status}`, res.status, errBody);
255
- }
256
- const body = await res.json();
257
- return body.text ?? "";
258
- },
259
- /** Text-to-speech. Returns the audio bytes (default mp3). */
260
- speech: async (opts) => {
261
- const res = await this.request(
262
- "/v1/audio/speech",
263
- {
264
- model: opts.model ?? "codai-tts",
265
- input: opts.input,
266
- voice: opts.voice ?? "alloy"
267
- },
268
- { signal: opts.signal }
269
- );
270
- return res.arrayBuffer();
271
- }
272
- };
273
- // ------------------------------------------------------------ internal
274
- chatBody(opts, stream) {
647
+ };
648
+
649
+ // src/resources/embeddings.ts
650
+ var Embeddings = class {
651
+ constructor(http) {
652
+ this.http = http;
653
+ }
654
+ http;
655
+ async create(body, opts = {}) {
656
+ const res = await this.http.request("/v1/embeddings", {
657
+ body: { ...body, model: body.model ?? "codai-embed" },
658
+ ext: opts.ext,
659
+ signal: opts.signal
660
+ });
661
+ const raw = await res.json();
275
662
  return {
276
- model: opts.model ?? "codai",
277
- messages: opts.messages,
278
- stream,
279
- ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
280
- ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {},
281
- ...opts.tools ? { tools: opts.tools } : {}
663
+ embeddings: (raw.data ?? []).map((d) => [...d.embedding]),
664
+ raw,
665
+ routedTo: res.headers.get("x-codai-routed-to"),
666
+ fallback: res.headers.get("x-codai-embed-fallback"),
667
+ headers: res.headers
282
668
  };
283
669
  }
284
- chatHeaders(opts) {
285
- const h = {};
286
- const session = opts.sessionId ?? this.sessionId;
287
- if (session) h["x-codai-session-id"] = session;
288
- if (opts.agentMode) h["x-codai-mode"] = "agent";
289
- if (opts.compact) h["x-codai-compact"] = opts.compact;
290
- if (opts.bestOf !== void 0) h["x-codai-best-of"] = String(opts.bestOf);
291
- if (opts.agentId) h["x-codai-agent-id"] = opts.agentId;
292
- return h;
293
- }
294
- async request(path, body, init = {}) {
295
- const url = `${this.baseUrl}${path}`;
296
- let lastErr;
297
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
298
- try {
299
- const res = await fetch(url, {
300
- method: init.method ?? "POST",
301
- headers: {
302
- Authorization: `Bearer ${this.apiKey}`,
303
- ...body !== null ? { "Content-Type": "application/json" } : {},
304
- ...init.headers
305
- },
306
- ...body !== null ? { body: JSON.stringify(body) } : {},
307
- signal: init.signal ?? AbortSignal.timeout(this.timeoutMs)
308
- });
309
- if (res.ok) return res;
310
- if (RETRYABLE.has(res.status) && attempt < this.maxRetries) {
311
- await sleep(500 * 2 ** attempt + Math.random() * 250);
312
- continue;
313
- }
314
- let errBody = null;
315
- try {
316
- errBody = await res.json();
317
- } catch {
318
- }
319
- throw new CodaiError(`codai ${path} \u2192 ${res.status}`, res.status, errBody);
320
- } catch (err) {
321
- if (err instanceof CodaiError) throw err;
322
- lastErr = err;
323
- if (attempt < this.maxRetries) {
324
- await sleep(500 * 2 ** attempt);
325
- continue;
326
- }
327
- }
328
- }
329
- throw new CodaiError(`codai ${path} failed after retries: ${String(lastErr)}`, 0, null);
670
+ };
671
+
672
+ // src/resources/environments.ts
673
+ var envPath = (id, suffix = "") => `/v1/environments/${encodeURIComponent(id)}${suffix}`;
674
+ var EnvironmentMembers = class {
675
+ constructor(http) {
676
+ this.http = http;
677
+ }
678
+ http;
679
+ /** `GET /v1/environments/{id}/members` — any role. */
680
+ async list(envId, opts = {}) {
681
+ const raw = await this.http.json(envPath(envId, "/members"), {
682
+ ext: opts.ext,
683
+ signal: opts.signal
684
+ });
685
+ return [...raw.members ?? []];
686
+ }
687
+ /** `POST /v1/environments/{id}/members` — exactly one of `user_id` / `email`. */
688
+ async add(envId, body, opts = {}) {
689
+ const raw = await this.http.json(
690
+ envPath(envId, "/members"),
691
+ { body, ext: opts.ext, signal: opts.signal }
692
+ );
693
+ return raw.member;
694
+ }
695
+ /** `PUT /v1/environments/{id}/members/me/ssh-key` — one `authorized_keys` line. */
696
+ setSshKey(envId, publicKey, opts = {}) {
697
+ const body = { public_key: publicKey };
698
+ return this.http.json(envPath(envId, "/members/me/ssh-key"), {
699
+ method: "PUT",
700
+ body,
701
+ ext: opts.ext,
702
+ signal: opts.signal
703
+ });
704
+ }
705
+ /** `PATCH /v1/environments/{id}/members/{userId}` — 409 `last_owner` on demoting the last owner. */
706
+ async setRole(envId, userId, role, opts = {}) {
707
+ const raw = await this.http.json(
708
+ envPath(envId, `/members/${encodeURIComponent(userId)}`),
709
+ { method: "PATCH", body: { role }, ext: opts.ext, signal: opts.signal }
710
+ );
711
+ return raw.member;
712
+ }
713
+ /** `DELETE /v1/environments/{id}/members/{userId}` — pass your own id to leave. */
714
+ remove(envId, userId, opts = {}) {
715
+ return this.http.json(
716
+ envPath(envId, `/members/${encodeURIComponent(userId)}`),
717
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
718
+ );
719
+ }
720
+ /** `POST /v1/environments/{id}/members/me/ssh-cert` — 12 h OpenSSH user certificate for your key. */
721
+ sshCert(envId, body, opts = {}) {
722
+ return this.http.json(envPath(envId, "/members/me/ssh-cert"), {
723
+ method: "POST",
724
+ body,
725
+ ext: opts.ext,
726
+ signal: opts.signal,
727
+ noRetry: true
728
+ });
330
729
  }
331
730
  };
332
- function sleep(ms) {
333
- return new Promise((r) => setTimeout(r, ms));
731
+ var EnvironmentTasks = class {
732
+ constructor(http) {
733
+ this.http = http;
734
+ }
735
+ http;
736
+ /** `GET /v1/environments/{id}/tasks` */
737
+ list(envId, opts = {}) {
738
+ return this.http.json(envPath(envId, "/tasks"), { ext: opts.ext, signal: opts.signal });
739
+ }
740
+ /** `POST /v1/environments/{id}/tasks` — queued; you get a notification when it finishes. */
741
+ create(envId, body, opts = {}) {
742
+ return this.http.json(envPath(envId, "/tasks"), {
743
+ method: "POST",
744
+ body,
745
+ ext: opts.ext,
746
+ signal: opts.signal,
747
+ noRetry: true
748
+ });
749
+ }
750
+ /** `GET /v1/environments/{id}/tasks/{taskId}` */
751
+ get(envId, taskId, opts = {}) {
752
+ return this.http.json(envPath(envId, `/tasks/${encodeURIComponent(taskId)}`), { ext: opts.ext, signal: opts.signal });
753
+ }
754
+ };
755
+ var EnvironmentPorts = class {
756
+ constructor(http) {
757
+ this.http = http;
758
+ }
759
+ http;
760
+ /** `GET /v1/environments/{id}/ports` — published (forwarded) ports. */
761
+ async list(envId, opts = {}) {
762
+ const raw = await this.http.json(envPath(envId, "/ports"), {
763
+ ext: opts.ext,
764
+ signal: opts.signal
765
+ });
766
+ return [...raw.ports ?? []];
767
+ }
768
+ /** `PUT /v1/environments/{id}/ports/{port}` — publish or change visibility (upsert). */
769
+ async set(envId, port, body, opts = {}) {
770
+ const raw = await this.http.json(
771
+ envPath(envId, `/ports/${encodeURIComponent(String(port))}`),
772
+ { method: "PUT", body, ext: opts.ext, signal: opts.signal }
773
+ );
774
+ return raw.port;
775
+ }
776
+ /** `DELETE /v1/environments/{id}/ports/{port}` — unpublish. */
777
+ remove(envId, port, opts = {}) {
778
+ return this.http.json(
779
+ envPath(envId, `/ports/${encodeURIComponent(String(port))}`),
780
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
781
+ );
782
+ }
783
+ };
784
+ var EnvironmentSecrets = class {
785
+ constructor(http) {
786
+ this.http = http;
787
+ }
788
+ http;
789
+ /** `GET /v1/environments/{id}/secrets` — names and timestamps only, never values. */
790
+ async list(envId, opts = {}) {
791
+ const raw = await this.http.json(envPath(envId, "/secrets"), {
792
+ ext: opts.ext,
793
+ signal: opts.signal
794
+ });
795
+ return [...raw.secrets ?? []];
796
+ }
797
+ /** `PUT /v1/environments/{id}/secrets/{name}` — upsert; `name` is `ENV_STYLE`. */
798
+ set(envId, name, value, opts = {}) {
799
+ const body = { value };
800
+ return this.http.json(
801
+ envPath(envId, `/secrets/${encodeURIComponent(name)}`),
802
+ { method: "PUT", body, ext: opts.ext, signal: opts.signal }
803
+ );
804
+ }
805
+ /** `DELETE /v1/environments/{id}/secrets/{name}` */
806
+ remove(envId, name, opts = {}) {
807
+ return this.http.json(
808
+ envPath(envId, `/secrets/${encodeURIComponent(name)}`),
809
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
810
+ );
811
+ }
812
+ };
813
+ var Environments = class {
814
+ constructor(http) {
815
+ this.http = http;
816
+ this.members = new EnvironmentMembers(http);
817
+ this.ports = new EnvironmentPorts(http);
818
+ this.secrets = new EnvironmentSecrets(http);
819
+ this.tasks = new EnvironmentTasks(http);
820
+ }
821
+ http;
822
+ members;
823
+ ports;
824
+ secrets;
825
+ tasks;
826
+ /** `GET /v1/environments/{id}/ssh-ca` — CA public keys to trust (`@cert-authority` lines). */
827
+ sshCa(envId, opts = {}) {
828
+ return this.http.json(envPath(envId, "/ssh-ca"), { ext: opts.ext, signal: opts.signal });
829
+ }
830
+ /** `GET /v1/environments?project_id=&for_user_id=me` — newest first, every state. */
831
+ async list(query = {}, opts = {}) {
832
+ const raw = await this.http.json("/v1/environments", {
833
+ query: { project_id: query.project_id, for_user_id: query.for_user_id },
834
+ ext: opts.ext,
835
+ signal: opts.signal
836
+ });
837
+ return [...raw.environments ?? []];
838
+ }
839
+ /**
840
+ * `POST /v1/environments` — `byo` starts `pending`; managed providers start
841
+ * `creating`. 409 `already_has_env` in a `vm_per_dev` project (the error
842
+ * body carries the `environment_id` to reuse).
843
+ */
844
+ async create(body, opts = {}) {
845
+ const raw = await this.http.json("/v1/environments", {
846
+ body,
847
+ ext: opts.ext,
848
+ signal: opts.signal,
849
+ noRetry: true
850
+ });
851
+ return raw.environment;
852
+ }
853
+ /**
854
+ * `POST /v1/environments/enroll` — daemon side. The one-time `codai_env_…`
855
+ * token in the body is the only credential; the client's API key header is
856
+ * ignored by this route. Managed environments additionally receive a
857
+ * freshly minted `api_key`, returned exactly once.
858
+ */
859
+ enroll(body, opts = {}) {
860
+ return this.http.json("/v1/environments/enroll", {
861
+ body,
862
+ ext: opts.ext,
863
+ signal: opts.signal,
864
+ // The token is consumed on success; a retried POST would 401.
865
+ noRetry: true
866
+ });
867
+ }
868
+ /** `GET /v1/environments/{id}` — environment, your role, members, ports, recent events. */
869
+ get(envId, opts = {}) {
870
+ return this.http.json(envPath(envId), {
871
+ ext: opts.ext,
872
+ signal: opts.signal
873
+ });
874
+ }
875
+ /** `PATCH /v1/environments/{id}` — `name` and/or `idle_timeout_minutes` (5..1440). */
876
+ async update(envId, body, opts = {}) {
877
+ const raw = await this.http.json(envPath(envId), {
878
+ method: "PATCH",
879
+ body,
880
+ ext: opts.ext,
881
+ signal: opts.signal
882
+ });
883
+ return raw.environment;
884
+ }
885
+ /** `POST /v1/environments/{id}/start` — managed providers only (`stopped` → `starting`). */
886
+ start(envId, opts = {}) {
887
+ return this.transition(envId, "start", opts);
888
+ }
889
+ /** `POST /v1/environments/{id}/stop` — managed providers only (`running` → `stopping`). */
890
+ stop(envId, opts = {}) {
891
+ return this.transition(envId, "stop", opts);
892
+ }
893
+ /** `POST /v1/environments/{id}/archive` — keeps history, releases the machine. */
894
+ archive(envId, opts = {}) {
895
+ return this.transition(envId, "archive", opts);
896
+ }
897
+ /** `POST /v1/environments/{id}/destroy` — owner only; terminal. */
898
+ destroy(envId, opts = {}) {
899
+ return this.transition(envId, "destroy", opts);
900
+ }
901
+ /**
902
+ * `POST /v1/environments/{id}/enroll-token` — `byo` only; a 30-minute
903
+ * one-time token for `enroll()`. Minting again replaces the previous one.
904
+ */
905
+ enrollToken(envId, opts = {}) {
906
+ return this.http.json(envPath(envId, "/enroll-token"), {
907
+ method: "POST",
908
+ ext: opts.ext,
909
+ signal: opts.signal,
910
+ noRetry: true
911
+ });
912
+ }
913
+ transition(envId, action, opts) {
914
+ return this.http.json(envPath(envId, `/${action}`), {
915
+ method: "POST",
916
+ ext: opts.ext,
917
+ signal: opts.signal
918
+ });
919
+ }
920
+ };
921
+
922
+ // src/resources/health.ts
923
+ var Health = class {
924
+ constructor(http) {
925
+ this.http = http;
926
+ }
927
+ http;
928
+ /** Liveness probe. */
929
+ get(opts = {}) {
930
+ return this.http.json("/health", { signal: opts.signal, noRetry: true });
931
+ }
932
+ /**
933
+ * Deep readiness probe. A 503 is returned as `{ ok: false, ... }` rather
934
+ * than thrown, so callers can read the failing check.
935
+ */
936
+ async ready(opts = {}) {
937
+ const res = await this.http.request("/health/ready", {
938
+ signal: opts.signal,
939
+ noRetry: true,
940
+ acceptStatus: [503]
941
+ });
942
+ const body = await res.json();
943
+ return { ...body, httpStatus: res.status };
944
+ }
945
+ /** Registry snapshot (aliases + provider health). */
946
+ status(opts = {}) {
947
+ return this.http.json("/status", { signal: opts.signal, noRetry: true });
948
+ }
949
+ };
950
+
951
+ // src/resources/hosts.ts
952
+ var Hosts = class {
953
+ constructor(http) {
954
+ this.http = http;
955
+ }
956
+ http;
957
+ /** `GET /v1/hosts` — your devices currently connected as hosts. */
958
+ async list(opts = {}) {
959
+ const raw = await this.http.json("/v1/hosts", {
960
+ ext: opts.ext,
961
+ signal: opts.signal
962
+ });
963
+ return [...raw.hosts ?? []];
964
+ }
965
+ /**
966
+ * `POST /v1/hosts/{deviceId}/exec` — run one op on a connected host and
967
+ * block until it answers (or `timeout_ms`, default 60 s).
968
+ */
969
+ exec(deviceId, op, args = {}, extra = {}) {
970
+ const body = {
971
+ op,
972
+ args,
973
+ ...extra.timeoutMs !== void 0 ? { timeout_ms: extra.timeoutMs } : {},
974
+ ...extra.label !== void 0 ? { label: extra.label } : {}
975
+ };
976
+ return this.http.json(`/v1/hosts/${encodeURIComponent(deviceId)}/exec`, {
977
+ body,
978
+ ext: extra.ext,
979
+ signal: extra.signal,
980
+ // The gateway itself waits up to timeout_ms + 2 s; give the HTTP layer headroom.
981
+ timeoutMs: (extra.timeoutMs ?? 6e4) + 1e4,
982
+ noRetry: true
983
+ });
984
+ }
985
+ /** `POST /v1/hosts/exec/{reqId}/result` — host side: deliver an exec result. */
986
+ postResult(reqId, body, opts = {}) {
987
+ return this.http.json(
988
+ `/v1/hosts/exec/${encodeURIComponent(reqId)}/result`,
989
+ { body, ext: opts.ext, signal: opts.signal }
990
+ );
991
+ }
992
+ /**
993
+ * `GET /v1/hosts/stream?os=&hostname=&roots=` — host side: announce this
994
+ * device and receive `hello` / `ping` / `exec` events. Closed by the server
995
+ * after 6 h; reconnect to re-announce presence.
996
+ */
997
+ async *stream(query = {}, opts = {}) {
998
+ const frames = this.http.sse("/v1/hosts/stream", {
999
+ query: {
1000
+ os: query.os,
1001
+ hostname: query.hostname,
1002
+ roots: query.roots?.length ? query.roots.join(",") : void 0
1003
+ },
1004
+ ext: opts.ext,
1005
+ signal: opts.signal
1006
+ });
1007
+ for await (const frame of frames) {
1008
+ const data = frameJson(frame);
1009
+ if (!data) continue;
1010
+ yield { event: frame.event, data };
1011
+ }
1012
+ }
1013
+ };
1014
+
1015
+ // src/resources/messages.ts
1016
+ var Messages = class {
1017
+ constructor(http) {
1018
+ this.http = http;
1019
+ }
1020
+ http;
1021
+ async create(body, opts = {}) {
1022
+ const res = await this.http.request("/v1/messages", {
1023
+ body: { ...body, model: body.model ?? "codai", stream: false },
1024
+ ext: opts.ext,
1025
+ signal: opts.signal
1026
+ });
1027
+ const raw = await res.json();
1028
+ const text = (raw.content ?? []).map((b) => b.type === "text" && typeof b.text === "string" ? b.text : "").join("");
1029
+ return {
1030
+ text,
1031
+ raw,
1032
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
1033
+ routedTo: res.headers.get("x-codai-routed-to"),
1034
+ headers: res.headers
1035
+ };
1036
+ }
1037
+ stream(body, opts = {}) {
1038
+ const requestPromise = this.http.request("/v1/messages", {
1039
+ body: { ...body, model: body.model ?? "codai", stream: true },
1040
+ ext: opts.ext,
1041
+ signal: opts.signal,
1042
+ timeoutMs: 0,
1043
+ noRetry: true
1044
+ });
1045
+ let resolveFinal;
1046
+ let rejectFinal;
1047
+ const final = new Promise((resolve, reject) => {
1048
+ resolveFinal = resolve;
1049
+ rejectFinal = reject;
1050
+ });
1051
+ final.catch(() => {
1052
+ });
1053
+ let text = "";
1054
+ let stopReason = null;
1055
+ let inputTokens = null;
1056
+ let outputTokens = null;
1057
+ let res = null;
1058
+ async function* events() {
1059
+ try {
1060
+ res = await requestPromise;
1061
+ for await (const frame of readSse(res, opts.signal)) {
1062
+ const ev = frameJson(frame);
1063
+ if (!ev) continue;
1064
+ if (typeof ev.type !== "string") ev.type = frame.event;
1065
+ if (ev.type === "message_start") {
1066
+ const usage = ev.message?.usage;
1067
+ if (usage?.input_tokens !== void 0) inputTokens = usage.input_tokens;
1068
+ } else if (ev.type === "content_block_delta") {
1069
+ const delta = ev.delta;
1070
+ if (delta?.type === "text_delta" && typeof delta.text === "string") text += delta.text;
1071
+ } else if (ev.type === "message_delta") {
1072
+ const delta = ev.delta;
1073
+ if (delta?.stop_reason) stopReason = delta.stop_reason;
1074
+ const usage = ev.usage;
1075
+ if (usage?.output_tokens !== void 0) outputTokens = usage.output_tokens;
1076
+ }
1077
+ yield ev;
1078
+ if (ev.type === "message_stop") break;
1079
+ }
1080
+ resolveFinal({
1081
+ text,
1082
+ stopReason,
1083
+ usage: inputTokens !== null || outputTokens !== null ? { inputTokens: inputTokens ?? 0, outputTokens: outputTokens ?? 0 } : null,
1084
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id") ?? null,
1085
+ routedTo: res.headers.get("x-codai-routed-to"),
1086
+ headers: res.headers
1087
+ });
1088
+ } catch (err) {
1089
+ rejectFinal(err);
1090
+ throw err;
1091
+ }
1092
+ }
1093
+ async function* textOnly() {
1094
+ for await (const ev of events()) {
1095
+ if (ev.type !== "content_block_delta") continue;
1096
+ const delta = ev.delta;
1097
+ if (delta?.type === "text_delta" && delta.text) yield delta.text;
1098
+ }
1099
+ }
1100
+ return {
1101
+ [Symbol.asyncIterator]: () => events(),
1102
+ text: () => ({ [Symbol.asyncIterator]: () => textOnly() }),
1103
+ final
1104
+ };
1105
+ }
1106
+ };
1107
+
1108
+ // src/resources/models.ts
1109
+ var Models = class {
1110
+ constructor(http) {
1111
+ this.http = http;
1112
+ }
1113
+ http;
1114
+ /** Every model the calling key may address (OpenAI list shape, `data[]`). */
1115
+ async list(opts = {}) {
1116
+ const raw = await this.http.json("/v1/models", {
1117
+ ext: opts.ext,
1118
+ signal: opts.signal
1119
+ });
1120
+ return [...raw.data ?? []];
1121
+ }
1122
+ };
1123
+
1124
+ // src/resources/orgs.ts
1125
+ var OrgMembers = class {
1126
+ constructor(http) {
1127
+ this.http = http;
1128
+ }
1129
+ http;
1130
+ /** `GET /v1/orgs/{id}/members` */
1131
+ async list(orgId, opts = {}) {
1132
+ const raw = await this.http.json(
1133
+ `/v1/orgs/${encodeURIComponent(orgId)}/members`,
1134
+ { ext: opts.ext, signal: opts.signal }
1135
+ );
1136
+ return [...raw.members ?? []];
1137
+ }
1138
+ /** `POST /v1/orgs/{id}/members` — add or re-role (owner/admin). */
1139
+ add(orgId, body, opts = {}) {
1140
+ return this.http.json(
1141
+ `/v1/orgs/${encodeURIComponent(orgId)}/members`,
1142
+ { body, ext: opts.ext, signal: opts.signal }
1143
+ );
1144
+ }
1145
+ /** `DELETE /v1/orgs/{id}/members/{userId}` */
1146
+ remove(orgId, userId, opts = {}) {
1147
+ return this.http.json(
1148
+ `/v1/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`,
1149
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
1150
+ );
1151
+ }
1152
+ };
1153
+ var Orgs = class {
1154
+ constructor(http) {
1155
+ this.http = http;
1156
+ this.members = new OrgMembers(http);
1157
+ }
1158
+ http;
1159
+ members;
1160
+ /** `POST /v1/orgs` */
1161
+ create(name, opts = {}) {
1162
+ return this.http.json("/v1/orgs", { body: { name }, ext: opts.ext, signal: opts.signal });
1163
+ }
1164
+ /** `GET /v1/orgs` */
1165
+ async list(opts = {}) {
1166
+ const raw = await this.http.json("/v1/orgs", {
1167
+ ext: opts.ext,
1168
+ signal: opts.signal
1169
+ });
1170
+ return [...raw.orgs ?? []];
1171
+ }
1172
+ };
1173
+
1174
+ // src/resources/projects.ts
1175
+ var Projects = class {
1176
+ constructor(http) {
1177
+ this.http = http;
1178
+ }
1179
+ http;
1180
+ /** `GET /v1/projects` — projects you own plus those of your orgs, newest first. */
1181
+ async list(opts = {}) {
1182
+ const raw = await this.http.json("/v1/projects", {
1183
+ ext: opts.ext,
1184
+ signal: opts.signal
1185
+ });
1186
+ return [...raw.projects ?? []];
1187
+ }
1188
+ /** `POST /v1/projects` — 409 `slug_taken` when the slug is already yours. */
1189
+ async create(body, opts = {}) {
1190
+ const raw = await this.http.json("/v1/projects", {
1191
+ body,
1192
+ ext: opts.ext,
1193
+ signal: opts.signal
1194
+ });
1195
+ return raw.project;
1196
+ }
1197
+ /** `GET /v1/projects/{id}` — the project plus every environment in it. */
1198
+ get(projectId, opts = {}) {
1199
+ return this.http.json(`/v1/projects/${encodeURIComponent(projectId)}`, {
1200
+ ext: opts.ext,
1201
+ signal: opts.signal
1202
+ });
1203
+ }
1204
+ /** `PATCH /v1/projects/{id}` — owner or org owner/admin; the slug cannot change. */
1205
+ async update(projectId, body, opts = {}) {
1206
+ const raw = await this.http.json(
1207
+ `/v1/projects/${encodeURIComponent(projectId)}`,
1208
+ { method: "PATCH", body, ext: opts.ext, signal: opts.signal }
1209
+ );
1210
+ return raw.project;
1211
+ }
1212
+ /** `DELETE /v1/projects/{id}` — 409 `project_has_environments` until all are destroyed. */
1213
+ delete(projectId, opts = {}) {
1214
+ return this.http.json(`/v1/projects/${encodeURIComponent(projectId)}`, {
1215
+ method: "DELETE",
1216
+ ext: opts.ext,
1217
+ signal: opts.signal
1218
+ });
1219
+ }
1220
+ };
1221
+
1222
+ // src/resources/responses.ts
1223
+ var Responses = class {
1224
+ constructor(http) {
1225
+ this.http = http;
1226
+ }
1227
+ http;
1228
+ async create(body, opts = {}) {
1229
+ const res = await this.http.request("/v1/responses", {
1230
+ body: { ...body, model: body.model ?? "codai", stream: false },
1231
+ ext: opts.ext,
1232
+ signal: opts.signal
1233
+ });
1234
+ const raw = await res.json();
1235
+ return {
1236
+ outputText: typeof raw.output_text === "string" ? raw.output_text : "",
1237
+ raw,
1238
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
1239
+ routedTo: res.headers.get("x-codai-routed-to"),
1240
+ headers: res.headers
1241
+ };
1242
+ }
1243
+ stream(body, opts = {}) {
1244
+ const requestPromise = this.http.request("/v1/responses", {
1245
+ body: { ...body, model: body.model ?? "codai", stream: true },
1246
+ ext: opts.ext,
1247
+ signal: opts.signal,
1248
+ timeoutMs: 0,
1249
+ noRetry: true
1250
+ });
1251
+ let resolveFinal;
1252
+ let rejectFinal;
1253
+ const final = new Promise((resolve, reject) => {
1254
+ resolveFinal = resolve;
1255
+ rejectFinal = reject;
1256
+ });
1257
+ final.catch(() => {
1258
+ });
1259
+ let outputText = "";
1260
+ let response = null;
1261
+ let res = null;
1262
+ async function* events() {
1263
+ try {
1264
+ res = await requestPromise;
1265
+ for await (const frame of readSse(res, opts.signal)) {
1266
+ const ev = frameJson(frame);
1267
+ if (!ev) continue;
1268
+ if (typeof ev.type !== "string") ev.type = frame.event;
1269
+ if (ev.type === "response.output_text.delta" && typeof ev.delta === "string") {
1270
+ outputText += ev.delta;
1271
+ } else if (ev.type === "response.completed" && ev.response) {
1272
+ response = ev.response;
1273
+ }
1274
+ yield ev;
1275
+ if (ev.type === "response.completed") break;
1276
+ }
1277
+ resolveFinal({
1278
+ outputText,
1279
+ response,
1280
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id") ?? null,
1281
+ routedTo: res.headers.get("x-codai-routed-to"),
1282
+ headers: res.headers
1283
+ });
1284
+ } catch (err) {
1285
+ rejectFinal(err);
1286
+ throw err;
1287
+ }
1288
+ }
1289
+ async function* textOnly() {
1290
+ for await (const ev of events()) {
1291
+ if (ev.type === "response.output_text.delta" && typeof ev.delta === "string")
1292
+ yield ev.delta;
1293
+ }
1294
+ }
1295
+ return {
1296
+ [Symbol.asyncIterator]: () => events(),
1297
+ text: () => ({ [Symbol.asyncIterator]: () => textOnly() }),
1298
+ final
1299
+ };
1300
+ }
1301
+ };
1302
+
1303
+ // src/resources/sessions.ts
1304
+ var enc = encodeURIComponent;
1305
+ var SessionLease = class {
1306
+ constructor(http) {
1307
+ this.http = http;
1308
+ }
1309
+ http;
1310
+ /** `POST /v1/sessions/{id}/lease` — CAS claim; `force: true` takes over a live lease. */
1311
+ acquire(sessionId, body = {}, opts = {}) {
1312
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1313
+ method: "POST",
1314
+ body,
1315
+ ext: opts.ext,
1316
+ signal: opts.signal
1317
+ });
1318
+ }
1319
+ /** `PUT /v1/sessions/{id}/lease` — heartbeat every 10 s (TTL 30 s). */
1320
+ renew(sessionId, opts = {}) {
1321
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1322
+ method: "PUT",
1323
+ ext: opts.ext,
1324
+ signal: opts.signal
1325
+ });
1326
+ }
1327
+ /** `DELETE /v1/sessions/{id}/lease` */
1328
+ release(sessionId, opts = {}) {
1329
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1330
+ method: "DELETE",
1331
+ ext: opts.ext,
1332
+ signal: opts.signal
1333
+ });
1334
+ }
1335
+ };
1336
+ var SessionEvents = class {
1337
+ constructor(http) {
1338
+ this.http = http;
1339
+ }
1340
+ http;
1341
+ /** `GET /v1/sessions/{id}/events?after=&limit=` */
1342
+ list(sessionId, query = {}, opts = {}) {
1343
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/events`, {
1344
+ query: { after: query.after, limit: query.limit },
1345
+ ext: opts.ext,
1346
+ signal: opts.signal
1347
+ });
1348
+ }
1349
+ /** `POST /v1/sessions/{id}/events` — executor only (needs the live lease). */
1350
+ append(sessionId, body, opts = {}) {
1351
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/events`, {
1352
+ body,
1353
+ ext: opts.ext,
1354
+ signal: opts.signal
1355
+ });
1356
+ }
1357
+ };
1358
+ var SessionControls = class {
1359
+ constructor(http) {
1360
+ this.http = http;
1361
+ }
1362
+ http;
1363
+ /** `GET /v1/sessions/{id}/controls?applied=&target=` — drain the control queue. */
1364
+ async list(sessionId, query = {}, opts = {}) {
1365
+ const raw = await this.http.json(
1366
+ `/v1/sessions/${enc(sessionId)}/controls`,
1367
+ {
1368
+ query: { applied: query.applied, target: query.target },
1369
+ ext: opts.ext,
1370
+ signal: opts.signal
1371
+ }
1372
+ );
1373
+ return [...raw.controls ?? []];
1374
+ }
1375
+ /** `POST /v1/sessions/{id}/control` — send a control to the executor (editor+). */
1376
+ submit(sessionId, body, opts = {}) {
1377
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/control`, {
1378
+ body,
1379
+ ext: opts.ext,
1380
+ signal: opts.signal
1381
+ });
1382
+ }
1383
+ /** `POST /v1/sessions/{id}/control/{cid}/applied` — executor only. */
1384
+ markApplied(sessionId, controlId, opts = {}) {
1385
+ return this.http.json(
1386
+ `/v1/sessions/${enc(sessionId)}/control/${enc(controlId)}/applied`,
1387
+ { method: "POST", ext: opts.ext, signal: opts.signal }
1388
+ );
1389
+ }
1390
+ };
1391
+ var SessionShares = class {
1392
+ constructor(http) {
1393
+ this.http = http;
1394
+ }
1395
+ http;
1396
+ /** `GET /v1/sessions/{id}/shares` — owner only. */
1397
+ async list(sessionId, opts = {}) {
1398
+ const raw = await this.http.json(
1399
+ `/v1/sessions/${enc(sessionId)}/shares`,
1400
+ { ext: opts.ext, signal: opts.signal }
1401
+ );
1402
+ return [...raw.shares ?? []];
1403
+ }
1404
+ /** `POST /v1/sessions/{id}/shares` — `token` is returned once for link shares. */
1405
+ create(sessionId, body, opts = {}) {
1406
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/shares`, {
1407
+ body,
1408
+ ext: opts.ext,
1409
+ signal: opts.signal
1410
+ });
1411
+ }
1412
+ /** `DELETE /v1/sessions/{id}/shares/{shareId}` */
1413
+ delete(sessionId, shareId, opts = {}) {
1414
+ return this.http.json(
1415
+ `/v1/sessions/${enc(sessionId)}/shares/${enc(shareId)}`,
1416
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
1417
+ );
1418
+ }
1419
+ /** `GET /v1/sessions/shared-with-me` */
1420
+ async sharedWithMe(query = {}, opts = {}) {
1421
+ const raw = await this.http.json(
1422
+ "/v1/sessions/shared-with-me",
1423
+ {
1424
+ query: { archived: query.archived ? "1" : void 0 },
1425
+ ext: opts.ext,
1426
+ signal: opts.signal
1427
+ }
1428
+ );
1429
+ return [...raw.sessions ?? []];
1430
+ }
1431
+ };
1432
+ var Sessions = class {
1433
+ constructor(http) {
1434
+ this.http = http;
1435
+ this.events = new SessionEvents(http);
1436
+ this.controls = new SessionControls(http);
1437
+ this.lease = new SessionLease(http);
1438
+ this.shares = new SessionShares(http);
1439
+ }
1440
+ http;
1441
+ events;
1442
+ controls;
1443
+ lease;
1444
+ shares;
1445
+ /** `POST /v1/sessions` — idempotent on `session_key` (200 existing / 201 created). */
1446
+ async create(body = {}, opts = {}) {
1447
+ const res = await this.http.request("/v1/sessions", {
1448
+ body,
1449
+ ext: opts.ext,
1450
+ signal: opts.signal
1451
+ });
1452
+ const session = await res.json();
1453
+ return { ...session, created: res.status === 201 };
1454
+ }
1455
+ /** `GET /v1/sessions?limit=&archived=` — own sessions first, then shared. */
1456
+ async list(query = {}, opts = {}) {
1457
+ const raw = await this.http.json("/v1/sessions", {
1458
+ query: { limit: query.limit, archived: query.archived ? "1" : void 0, v: "2" },
1459
+ ext: opts.ext,
1460
+ signal: opts.signal
1461
+ });
1462
+ return [...raw.sessions ?? []];
1463
+ }
1464
+ /** `GET /v1/sessions/{id}` — detail with members, lease and presence. */
1465
+ get(sessionId, query = {}, opts = {}) {
1466
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1467
+ query: { share: query.share, v: "2" },
1468
+ ext: opts.ext,
1469
+ signal: opts.signal
1470
+ });
1471
+ }
1472
+ /** `PATCH /v1/sessions/{id}` — rename / archive (owner). */
1473
+ update(sessionId, body, opts = {}) {
1474
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1475
+ method: "PATCH",
1476
+ body,
1477
+ ext: opts.ext,
1478
+ signal: opts.signal
1479
+ });
1480
+ }
1481
+ /** `DELETE /v1/sessions/{id}` — irreversible (owner). */
1482
+ delete(sessionId, opts = {}) {
1483
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1484
+ method: "DELETE",
1485
+ ext: opts.ext,
1486
+ signal: opts.signal
1487
+ });
1488
+ }
1489
+ /** `POST /v1/sessions/{id}/dispatch` — queue a `send` for one device and wake it over FCM. */
1490
+ dispatch(sessionId, body, opts = {}) {
1491
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/dispatch`, {
1492
+ body,
1493
+ ext: opts.ext,
1494
+ signal: opts.signal
1495
+ });
1496
+ }
1497
+ /**
1498
+ * `GET /v1/sessions/{id}/stream?after=` — live SSE of `event` / `control` /
1499
+ * `lease` / `presence` frames. `: ping` heartbeats are skipped. The server
1500
+ * closes after 30 min; reconnect with `after` = highest `seq` seen.
1501
+ */
1502
+ async *stream(sessionId, query = {}, opts = {}) {
1503
+ const frames = this.http.sse(`/v1/sessions/${enc(sessionId)}/stream`, {
1504
+ query: { after: query.after, share: query.share },
1505
+ ext: opts.ext,
1506
+ signal: opts.signal
1507
+ });
1508
+ for await (const frame of frames) {
1509
+ const data = frameJson(frame);
1510
+ if (!data) continue;
1511
+ yield { event: frame.event, data };
1512
+ }
1513
+ }
1514
+ };
1515
+
1516
+ // src/resources/system-one.ts
1517
+ var SystemOne = class {
1518
+ constructor(http) {
1519
+ this.http = http;
1520
+ }
1521
+ http;
1522
+ /**
1523
+ * Ask one or more typed questions about `state`. Throws on 503 `s1_unavailable` like any other
1524
+ * error — keep a local fallback and catch it: the gateway never substitutes a generative model.
1525
+ */
1526
+ async create(body, opts = {}) {
1527
+ const res = await this.http.request("/v1/systemone", {
1528
+ body,
1529
+ ext: opts.ext,
1530
+ signal: opts.signal
1531
+ });
1532
+ return await res.json();
1533
+ }
1534
+ };
1535
+
1536
+ // src/resources/tasks.ts
1537
+ var Tasks = class {
1538
+ constructor(http) {
1539
+ this.http = http;
1540
+ }
1541
+ http;
1542
+ /** `GET /v1/tasks` — newest first; page with `cursor` = previous `next_cursor`. */
1543
+ list(query = {}, opts = {}) {
1544
+ return this.http.json("/v1/tasks", {
1545
+ query: { outcome: query.outcome, limit: query.limit, cursor: query.cursor },
1546
+ ext: opts.ext,
1547
+ signal: opts.signal
1548
+ });
1549
+ }
1550
+ /** `GET /v1/tasks/pending` — tasks awaiting your confirmation (max 100). */
1551
+ async pending(opts = {}) {
1552
+ const raw = await this.http.json("/v1/tasks/pending", {
1553
+ ext: opts.ext,
1554
+ signal: opts.signal
1555
+ });
1556
+ return [...raw.tasks ?? []];
1557
+ }
1558
+ /** `GET /v1/tasks/stats?since=` */
1559
+ stats(since, opts = {}) {
1560
+ return this.http.json("/v1/tasks/stats", {
1561
+ query: { since: since instanceof Date ? since.toISOString() : since },
1562
+ ext: opts.ext,
1563
+ signal: opts.signal
1564
+ });
1565
+ }
1566
+ /** `GET /v1/tasks/{id}` */
1567
+ get(id, opts = {}) {
1568
+ return this.http.json(`/v1/tasks/${encodeURIComponent(id)}`, {
1569
+ ext: opts.ext,
1570
+ signal: opts.signal
1571
+ });
1572
+ }
1573
+ /** `POST /v1/tasks/{id}/confirm` — answer "Did codai solve it?". */
1574
+ confirm(id, body, opts = {}) {
1575
+ return this.http.json(`/v1/tasks/${encodeURIComponent(id)}/confirm`, {
1576
+ body,
1577
+ ext: opts.ext,
1578
+ signal: opts.signal
1579
+ });
1580
+ }
1581
+ };
1582
+
1583
+ // src/resources/triggers.ts
1584
+ var Triggers = class {
1585
+ constructor(http) {
1586
+ this.http = http;
1587
+ }
1588
+ http;
1589
+ /** `GET /v1/triggers` — never includes the signing secret. */
1590
+ list(opts = {}) {
1591
+ return this.http.json("/v1/triggers", { ext: opts.ext, signal: opts.signal });
1592
+ }
1593
+ /** `POST /v1/triggers` — the response carries the signing `secret` ONCE. */
1594
+ create(body, opts = {}) {
1595
+ return this.http.json("/v1/triggers", { method: "POST", body, ext: opts.ext, signal: opts.signal });
1596
+ }
1597
+ /** `PATCH /v1/triggers/{id}` — name / enabled / policy. */
1598
+ update(id, body, opts = {}) {
1599
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}`, { method: "PATCH", body, ext: opts.ext, signal: opts.signal });
1600
+ }
1601
+ /** `DELETE /v1/triggers/{id}` — its hook URL stops working immediately. */
1602
+ delete(id, opts = {}) {
1603
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}`, { method: "DELETE", ext: opts.ext, signal: opts.signal });
1604
+ }
1605
+ /** `POST /v1/triggers/{id}/rotate-secret` — new secret, shown once. */
1606
+ rotateSecret(id, opts = {}) {
1607
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}/rotate-secret`, { method: "POST", body: {}, ext: opts.ext, signal: opts.signal });
1608
+ }
1609
+ /** `POST /v1/triggers/{id}/test` — manual fire through the full pipeline; returns the decision + reason. */
1610
+ /** `PUT /v1/triggers/{id}/secret` — store a secret issued by the source (e.g. a Sentry integration's client secret). */
1611
+ setSecret(id, secret, opts = {}) {
1612
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}/secret`, { method: "PUT", body: { secret }, ext: opts.ext, signal: opts.signal, noRetry: true });
1613
+ }
1614
+ /** `POST /v1/triggers/{id}/test` — manual fire through the full pipeline; returns the decision + reason. */
1615
+ test(id, body = {}, opts = {}) {
1616
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}/test`, { method: "POST", body, ext: opts.ext, signal: opts.signal });
1617
+ }
1618
+ /** `GET /v1/triggers/{id}/events?limit=` — recent deliveries with decisions. */
1619
+ events(id, limit, opts = {}) {
1620
+ return this.http.json(`/v1/triggers/${encodeURIComponent(id)}/events`, { query: { limit }, ext: opts.ext, signal: opts.signal });
1621
+ }
1622
+ /**
1623
+ * `POST /v1/hooks/{id}` — send an event to a `generic` / `form` trigger from your own code. Signs the body
1624
+ * (`X-Codai-Signature: t=<unix>,v1=HMAC_SHA256(secret, "<t>.<body>")`, WebCrypto — Node 20+, browsers, Deno,
1625
+ * Workers). `deliveryId` makes retries idempotent. The hook needs no API key (the signature is the auth).
1626
+ */
1627
+ async fireHook(id, secret, event, opts = {}) {
1628
+ const raw = JSON.stringify(event);
1629
+ const t = Math.floor(Date.now() / 1e3);
1630
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
1631
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${t}.${raw}`)));
1632
+ const v1 = Array.from(mac, (b) => b.toString(16).padStart(2, "0")).join("");
1633
+ return this.http.json(`/v1/hooks/${encodeURIComponent(id)}`, {
1634
+ method: "POST",
1635
+ rawBody: raw,
1636
+ ext: {
1637
+ ...opts.ext,
1638
+ headers: {
1639
+ ...opts.ext?.headers,
1640
+ "Content-Type": "application/json",
1641
+ "x-codai-signature": `t=${t},v1=${v1}`,
1642
+ ...opts.deliveryId ? { "x-codai-delivery": opts.deliveryId } : {}
1643
+ }
1644
+ },
1645
+ signal: opts.signal
1646
+ });
1647
+ }
1648
+ };
1649
+ var Notifications = class {
1650
+ constructor(http) {
1651
+ this.http = http;
1652
+ }
1653
+ http;
1654
+ /** `GET /v1/notifications?unread=1&limit=` — newest first. */
1655
+ list(query = {}, opts = {}) {
1656
+ return this.http.json("/v1/notifications", {
1657
+ query: { unread: query.unread ? "1" : void 0, limit: query.limit },
1658
+ ext: opts.ext,
1659
+ signal: opts.signal
1660
+ });
1661
+ }
1662
+ /** `POST /v1/notifications/read` — `ids` omitted = every unread one. */
1663
+ markRead(ids, opts = {}) {
1664
+ return this.http.json("/v1/notifications/read", { method: "POST", body: ids ? { ids } : {}, ext: opts.ext, signal: opts.signal });
1665
+ }
1666
+ };
1667
+
1668
+ // src/resources/tokens.ts
1669
+ var Tokens = class {
1670
+ constructor(http) {
1671
+ this.http = http;
1672
+ }
1673
+ http;
1674
+ /**
1675
+ * Mint a short-lived token (60–3600 s, default 600) bound to one surface.
1676
+ * Only a real `codai_` key may mint.
1677
+ */
1678
+ async create(scope, ttlSeconds, opts = {}) {
1679
+ const raw = await this.http.json("/v1/tokens", {
1680
+ body: { scope, ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {} },
1681
+ ext: opts.ext,
1682
+ signal: opts.signal
1683
+ });
1684
+ return { token: raw.token, expiresAt: raw.expires_at, scope: raw.scope, raw };
1685
+ }
1686
+ };
1687
+
1688
+ // src/resources/tools.ts
1689
+ var Tools = class {
1690
+ constructor(http) {
1691
+ this.http = http;
1692
+ }
1693
+ http;
1694
+ /** `POST /v1/tools/search` — verified web search (never falls back to an unverified provider). */
1695
+ search(body, opts = {}) {
1696
+ return this.http.json("/v1/tools/search", {
1697
+ body,
1698
+ ext: opts.ext,
1699
+ signal: opts.signal
1700
+ });
1701
+ }
1702
+ /** `POST /v1/tools/fetch` — fetch + extract a public page (SSRF-guarded). */
1703
+ fetch(body, opts = {}) {
1704
+ return this.http.json("/v1/tools/fetch", {
1705
+ body,
1706
+ ext: opts.ext,
1707
+ signal: opts.signal
1708
+ });
1709
+ }
1710
+ };
1711
+
1712
+ // src/operations.ts
1713
+ var OPERATION_METHODS = {
1714
+ // Chat & Completions
1715
+ createChatCompletion: "chat.completions.create",
1716
+ createResponse: "responses.create",
1717
+ createMessage: "messages.create",
1718
+ // Embeddings & Audio
1719
+ createEmbeddings: "embeddings.create",
1720
+ createSystemOne: "systemOne.create",
1721
+ createTranscription: "audio.transcribe",
1722
+ createSpeech: "audio.speech",
1723
+ createEphemeralToken: "tokens.create",
1724
+ // Models
1725
+ listModels: "models.list",
1726
+ // Health
1727
+ getHealth: "health.get",
1728
+ getHealthReady: "health.ready",
1729
+ getStatus: "health.status",
1730
+ // Agents
1731
+ runAgentSync: "agents.run",
1732
+ createAgentRun: "agents.runs.create",
1733
+ getAgentRunStats: "agents.runs.stats",
1734
+ getAgentRun: "agents.runs.get",
1735
+ listAgentRunSteps: "agents.runs.steps",
1736
+ streamAgentRun: "agents.runs.stream",
1737
+ cancelAgentRun: "agents.runs.cancel",
1738
+ // Tools
1739
+ toolsSearch: "tools.search",
1740
+ toolsFetch: "tools.fetch",
1741
+ // Tasks
1742
+ listTasks: "tasks.list",
1743
+ listPendingTasks: "tasks.pending",
1744
+ getTaskStats: "tasks.stats",
1745
+ getTask: "tasks.get",
1746
+ confirmTask: "tasks.confirm",
1747
+ // Event triggers + inbox
1748
+ listTriggers: "triggers.list",
1749
+ createTrigger: "triggers.create",
1750
+ updateTrigger: "triggers.update",
1751
+ deleteTrigger: "triggers.delete",
1752
+ rotateTriggerSecret: "triggers.rotateSecret",
1753
+ setTriggerSecret: "triggers.setSecret",
1754
+ testTrigger: "triggers.test",
1755
+ listTriggerEvents: "triggers.events",
1756
+ receiveTriggerHook: "triggers.fireHook",
1757
+ listNotifications: "notifications.list",
1758
+ markNotificationsRead: "notifications.markRead",
1759
+ // Sessions
1760
+ createSession: "sessions.create",
1761
+ listSessions: "sessions.list",
1762
+ getSession: "sessions.get",
1763
+ updateSession: "sessions.update",
1764
+ deleteSession: "sessions.delete",
1765
+ listSessionEvents: "sessions.events.list",
1766
+ appendSessionEvents: "sessions.events.append",
1767
+ listSessionControls: "sessions.controls.list",
1768
+ submitSessionControl: "sessions.controls.submit",
1769
+ markSessionControlApplied: "sessions.controls.markApplied",
1770
+ dispatchToDevice: "sessions.dispatch",
1771
+ claimSessionLease: "sessions.lease.acquire",
1772
+ heartbeatSessionLease: "sessions.lease.renew",
1773
+ releaseSessionLease: "sessions.lease.release",
1774
+ streamSession: "sessions.stream",
1775
+ // Devices
1776
+ getMyDispatchInbox: "devices.dispatchInbox",
1777
+ listDevices: "devices.list",
1778
+ updateDevice: "devices.update",
1779
+ deleteDevice: "devices.delete",
1780
+ // Hosts
1781
+ listHosts: "hosts.list",
1782
+ streamHost: "hosts.stream",
1783
+ execOnHost: "hosts.exec",
1784
+ postHostExecResult: "hosts.postResult",
1785
+ // Orgs & Sharing
1786
+ listSessionsSharedWithMe: "sessions.shares.sharedWithMe",
1787
+ listSessionShares: "sessions.shares.list",
1788
+ createSessionShare: "sessions.shares.create",
1789
+ deleteSessionShare: "sessions.shares.delete",
1790
+ createOrg: "orgs.create",
1791
+ listOrgs: "orgs.list",
1792
+ listOrgMembers: "orgs.members.list",
1793
+ addOrgMember: "orgs.members.add",
1794
+ removeOrgMember: "orgs.members.remove",
1795
+ // Projects
1796
+ listProjects: "projects.list",
1797
+ createProject: "projects.create",
1798
+ getProject: "projects.get",
1799
+ updateProject: "projects.update",
1800
+ deleteProject: "projects.delete",
1801
+ // Environments
1802
+ listEnvironments: "environments.list",
1803
+ createEnvironment: "environments.create",
1804
+ enrollEnvironment: "environments.enroll",
1805
+ getEnvironment: "environments.get",
1806
+ updateEnvironment: "environments.update",
1807
+ startEnvironment: "environments.start",
1808
+ stopEnvironment: "environments.stop",
1809
+ archiveEnvironment: "environments.archive",
1810
+ destroyEnvironment: "environments.destroy",
1811
+ createEnvironmentEnrollToken: "environments.enrollToken",
1812
+ listEnvironmentMembers: "environments.members.list",
1813
+ addEnvironmentMember: "environments.members.add",
1814
+ setMyEnvironmentSshKey: "environments.members.setSshKey",
1815
+ setEnvironmentMemberRole: "environments.members.setRole",
1816
+ removeEnvironmentMember: "environments.members.remove",
1817
+ issueMyEnvironmentSshCert: "environments.members.sshCert",
1818
+ getEnvironmentSshCa: "environments.sshCa",
1819
+ listEnvironmentTasks: "environments.tasks.list",
1820
+ createEnvironmentTask: "environments.tasks.create",
1821
+ getEnvironmentTask: "environments.tasks.get",
1822
+ listEnvironmentPorts: "environments.ports.list",
1823
+ setEnvironmentPort: "environments.ports.set",
1824
+ removeEnvironmentPort: "environments.ports.remove",
1825
+ listEnvironmentSecrets: "environments.secrets.list",
1826
+ setEnvironmentSecret: "environments.secrets.set",
1827
+ removeEnvironmentSecret: "environments.secrets.remove",
1828
+ // Account
1829
+ getAccount: "account.get",
1830
+ patchAccount: "account.update",
1831
+ getReceipt: "receipt.get",
1832
+ submitFeedback: "feedback.submit",
1833
+ listPhoneModels: "phoneModels.list"
1834
+ };
1835
+ var STREAMING_OPERATIONS = /* @__PURE__ */ new Set([
1836
+ "streamAgentRun",
1837
+ "streamSession",
1838
+ "streamHost"
1839
+ ]);
1840
+
1841
+ // src/index.ts
1842
+ var Codai = class {
1843
+ http;
1844
+ /** Resource group (`chat.completions.create/stream`) that is also callable with the 0.2.x `chat({ messages })` shape. */
1845
+ chat;
1846
+ messages;
1847
+ responses;
1848
+ embeddings;
1849
+ audio;
1850
+ /** Typed decisions from codai-s1 (`systemOne.create({ state, questions })`). */
1851
+ systemOne;
1852
+ tokens;
1853
+ models;
1854
+ health;
1855
+ agents;
1856
+ tools;
1857
+ tasks;
1858
+ /** Event triggers — webhooks that notify you or start agent sessions. */
1859
+ triggers;
1860
+ /** Inbox of trigger / cloud-task notifications. */
1861
+ notifications;
1862
+ sessions;
1863
+ devices;
1864
+ hosts;
1865
+ orgs;
1866
+ projects;
1867
+ environments;
1868
+ account;
1869
+ receipt;
1870
+ feedback;
1871
+ phoneModels;
1872
+ constructor(opts) {
1873
+ if (!opts.apiKey) throw new Error("Codai: apiKey is required");
1874
+ this.http = new HttpClient({
1875
+ apiKey: opts.apiKey,
1876
+ baseUrl: opts.baseUrl ?? "https://ai.codai.ro",
1877
+ timeoutMs: opts.timeoutMs ?? 12e4,
1878
+ maxRetries: opts.maxRetries ?? 2,
1879
+ fetch: opts.fetch,
1880
+ defaults: {
1881
+ ...opts.defaults,
1882
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
1883
+ ...opts.device ? { device: opts.device } : {},
1884
+ ...opts.deviceName ? { deviceName: opts.deviceName } : {},
1885
+ ...opts.devicePlatform ? { devicePlatform: opts.devicePlatform } : {},
1886
+ ...opts.client ? { client: opts.client } : {}
1887
+ }
1888
+ });
1889
+ const http = this.http;
1890
+ this.messages = new Messages(http);
1891
+ this.responses = new Responses(http);
1892
+ this.audio = new Audio(http);
1893
+ this.systemOne = new SystemOne(http);
1894
+ this.tokens = new Tokens(http);
1895
+ this.health = new Health(http);
1896
+ this.agents = new Agents(http);
1897
+ this.tools = new Tools(http);
1898
+ this.tasks = new Tasks(http);
1899
+ this.triggers = new Triggers(http);
1900
+ this.notifications = new Notifications(http);
1901
+ this.sessions = new Sessions(http);
1902
+ this.devices = new Devices(http);
1903
+ this.hosts = new Hosts(http);
1904
+ this.orgs = new Orgs(http);
1905
+ this.projects = new Projects(http);
1906
+ this.environments = new Environments(http);
1907
+ this.account = new Account(http);
1908
+ this.receipt = new Receipts(http);
1909
+ this.phoneModels = new PhoneModels(http);
1910
+ this.chat = callable(
1911
+ new Chat(http),
1912
+ (o) => this.chat.completions.create(this.legacyBody(o), {
1913
+ ext: this.legacyExt(o),
1914
+ signal: o.signal
1915
+ })
1916
+ );
1917
+ this.embeddings = callable(new Embeddings(http), async (o) => {
1918
+ const r = await this.embeddings.create(
1919
+ { input: o.input, model: o.model, dimensions: o.dimensions },
1920
+ { signal: o.signal }
1921
+ );
1922
+ return { embeddings: r.embeddings, raw: r.raw };
1923
+ });
1924
+ this.models = callable(
1925
+ new Models(http),
1926
+ async () => (await this.models.list()).map((m) => ({ id: m.id }))
1927
+ );
1928
+ this.feedback = callable(
1929
+ new Feedback(http),
1930
+ async (requestId, rating, comment) => {
1931
+ await this.feedback.submit({
1932
+ event_id: requestId,
1933
+ rating,
1934
+ ...comment ? { comment } : {}
1935
+ });
1936
+ }
1937
+ );
1938
+ }
1939
+ // ------------------------------------------------- 0.2.x compatibility aliases
1940
+ /** Alias of `chat.completions.stream()` with the 0.2.x option names. */
1941
+ chatStream(opts) {
1942
+ return this.chat.completions.stream(this.legacyBody(opts), {
1943
+ ext: this.legacyExt(opts),
1944
+ signal: opts.signal
1945
+ });
1946
+ }
1947
+ /** Alias of `tokens.create()`. */
1948
+ mintToken(scope, ttlSeconds) {
1949
+ return this.tokens.create(scope, ttlSeconds);
1950
+ }
1951
+ legacyBody(opts) {
1952
+ return {
1953
+ model: opts.model ?? "codai",
1954
+ messages: opts.messages,
1955
+ ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
1956
+ ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {},
1957
+ ...opts.tools ? { tools: opts.tools } : {}
1958
+ };
1959
+ }
1960
+ legacyExt(opts) {
1961
+ return {
1962
+ ...opts.ext,
1963
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
1964
+ ...opts.agentMode ? { mode: "agent" } : {},
1965
+ ...opts.compact ? { compact: opts.compact } : {},
1966
+ ...opts.bestOf !== void 0 ? { bestOf: opts.bestOf } : {},
1967
+ ...opts.agentId ? { agentId: opts.agentId } : {}
1968
+ };
1969
+ }
1970
+ };
1971
+ function callable(target, fn) {
1972
+ const f = ((...args) => fn(...args));
1973
+ for (const key of allKeys(target)) {
1974
+ const value = target[key];
1975
+ Object.defineProperty(f, key, {
1976
+ value: typeof value === "function" ? value.bind(target) : value,
1977
+ enumerable: true,
1978
+ writable: false
1979
+ });
1980
+ }
1981
+ return f;
1982
+ }
1983
+ function allKeys(obj) {
1984
+ const keys = /* @__PURE__ */ new Set();
1985
+ let cur = obj;
1986
+ while (cur && cur !== Object.prototype) {
1987
+ for (const k of Object.getOwnPropertyNames(cur)) if (k !== "constructor") keys.add(k);
1988
+ cur = Object.getPrototypeOf(cur);
1989
+ }
1990
+ return [...keys];
334
1991
  }
335
1992
  var index_default = Codai;
336
1993
 
337
- export { Codai, CodaiError, index_default as default };
1994
+ export { Account, AgentRuns, Agents, Audio, Chat, ChatCompletions, Codai, CodaiError, Devices, Embeddings, EnvironmentMembers, EnvironmentPorts, EnvironmentSecrets, EnvironmentTasks, Environments, Feedback, Health, Hosts, HttpClient, Messages, Models, Notifications, OPERATION_METHODS, OrgMembers, Orgs, PhoneModels, Projects, Receipts, Responses, STREAMING_OPERATIONS, SessionControls, SessionEvents, SessionLease, SessionShares, Sessions, Tasks, Tokens, Tools, Triggers, index_default as default, extensionHeaders, frameJson, mergeToolCall, readSse };
338
1995
  //# sourceMappingURL=index.js.map
339
1996
  //# sourceMappingURL=index.js.map