codai-sdk 0.2.0 → 0.3.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/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,302 @@ 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
  }
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,
533
+ signal: opts.signal,
534
+ timeoutMs: 0,
535
+ noRetry: true
536
+ });
537
+ let resolveFinal;
538
+ let rejectFinal;
539
+ const final = new Promise((resolve, reject) => {
540
+ resolveFinal = resolve;
541
+ rejectFinal = reject;
542
+ });
543
+ final.catch(() => {
544
+ });
545
+ const toolCallsByIndex = /* @__PURE__ */ new Map();
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() {
561
+ try {
562
+ res = await requestPromise;
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);
574
+ }
575
+ }
576
+ if (delta?.content) content += delta.content;
577
+ yield chunk;
578
+ }
579
+ resolveFinal(buildResult());
580
+ } catch (err) {
581
+ rejectFinal(err);
582
+ throw err;
583
+ }
584
+ }
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
+ }
591
+ return {
592
+ [Symbol.asyncIterator]: () => text(),
593
+ chunks: () => ({ [Symbol.asyncIterator]: () => chunks() }),
594
+ final
595
+ };
596
+ }
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
+ });
635
+ }
68
636
  /**
69
- * Streaming chat. Async-iterate the returned object for text deltas, then
70
- * `await stream.final` for metadata (requestId, usage, routedTo, toolCalls).
637
+ * `GET /v1/devices/me/dispatch?limit=` — pending controls dispatched at the
638
+ * calling device (`x-codai-device` required) across all owned sessions.
71
639
  */
72
- chatStream(opts) {
73
- const requestPromise = this.request("/v1/chat/completions", this.chatBody(opts, true), {
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
645
+ });
646
+ }
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();
662
+ return {
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
668
+ };
669
+ }
670
+ };
671
+
672
+ // src/resources/health.ts
673
+ var Health = class {
674
+ constructor(http) {
675
+ this.http = http;
676
+ }
677
+ http;
678
+ /** Liveness probe. */
679
+ get(opts = {}) {
680
+ return this.http.json("/health", { signal: opts.signal, noRetry: true });
681
+ }
682
+ /**
683
+ * Deep readiness probe. A 503 is returned as `{ ok: false, ... }` rather
684
+ * than thrown, so callers can read the failing check.
685
+ */
686
+ async ready(opts = {}) {
687
+ const res = await this.http.request("/health/ready", {
688
+ signal: opts.signal,
689
+ noRetry: true,
690
+ acceptStatus: [503]
691
+ });
692
+ const body = await res.json();
693
+ return { ...body, httpStatus: res.status };
694
+ }
695
+ /** Registry snapshot (aliases + provider health). */
696
+ status(opts = {}) {
697
+ return this.http.json("/status", { signal: opts.signal, noRetry: true });
698
+ }
699
+ };
700
+
701
+ // src/resources/hosts.ts
702
+ var Hosts = class {
703
+ constructor(http) {
704
+ this.http = http;
705
+ }
706
+ http;
707
+ /** `GET /v1/hosts` — your devices currently connected as hosts. */
708
+ async list(opts = {}) {
709
+ const raw = await this.http.json("/v1/hosts", {
710
+ ext: opts.ext,
711
+ signal: opts.signal
712
+ });
713
+ return [...raw.hosts ?? []];
714
+ }
715
+ /**
716
+ * `POST /v1/hosts/{deviceId}/exec` — run one op on a connected host and
717
+ * block until it answers (or `timeout_ms`, default 60 s).
718
+ */
719
+ exec(deviceId, op, args = {}, extra = {}) {
720
+ const body = {
721
+ op,
722
+ args,
723
+ ...extra.timeoutMs !== void 0 ? { timeout_ms: extra.timeoutMs } : {},
724
+ ...extra.label !== void 0 ? { label: extra.label } : {}
725
+ };
726
+ return this.http.json(`/v1/hosts/${encodeURIComponent(deviceId)}/exec`, {
727
+ body,
728
+ ext: extra.ext,
729
+ signal: extra.signal,
730
+ // The gateway itself waits up to timeout_ms + 2 s; give the HTTP layer headroom.
731
+ timeoutMs: (extra.timeoutMs ?? 6e4) + 1e4,
732
+ noRetry: true
733
+ });
734
+ }
735
+ /** `POST /v1/hosts/exec/{reqId}/result` — host side: deliver an exec result. */
736
+ postResult(reqId, body, opts = {}) {
737
+ return this.http.json(
738
+ `/v1/hosts/exec/${encodeURIComponent(reqId)}/result`,
739
+ { body, ext: opts.ext, signal: opts.signal }
740
+ );
741
+ }
742
+ /**
743
+ * `GET /v1/hosts/stream?os=&hostname=&roots=` — host side: announce this
744
+ * device and receive `hello` / `ping` / `exec` events. Closed by the server
745
+ * after 6 h; reconnect to re-announce presence.
746
+ */
747
+ async *stream(query = {}, opts = {}) {
748
+ const frames = this.http.sse("/v1/hosts/stream", {
749
+ query: {
750
+ os: query.os,
751
+ hostname: query.hostname,
752
+ roots: query.roots?.length ? query.roots.join(",") : void 0
753
+ },
754
+ ext: opts.ext,
755
+ signal: opts.signal
756
+ });
757
+ for await (const frame of frames) {
758
+ const data = frameJson(frame);
759
+ if (!data) continue;
760
+ yield { event: frame.event, data };
761
+ }
762
+ }
763
+ };
764
+
765
+ // src/resources/messages.ts
766
+ var Messages = class {
767
+ constructor(http) {
768
+ this.http = http;
769
+ }
770
+ http;
771
+ async create(body, opts = {}) {
772
+ const res = await this.http.request("/v1/messages", {
773
+ body: { ...body, model: body.model ?? "codai", stream: false },
774
+ ext: opts.ext,
775
+ signal: opts.signal
776
+ });
777
+ const raw = await res.json();
778
+ const text = (raw.content ?? []).map((b) => b.type === "text" && typeof b.text === "string" ? b.text : "").join("");
779
+ return {
780
+ text,
781
+ raw,
782
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
783
+ routedTo: res.headers.get("x-codai-routed-to"),
784
+ headers: res.headers
785
+ };
786
+ }
787
+ stream(body, opts = {}) {
788
+ const requestPromise = this.http.request("/v1/messages", {
789
+ body: { ...body, model: body.model ?? "codai", stream: true },
790
+ ext: opts.ext,
74
791
  signal: opts.signal,
75
- headers: this.chatHeaders(opts)
792
+ timeoutMs: 0,
793
+ noRetry: true
76
794
  });
77
795
  let resolveFinal;
78
796
  let rejectFinal;
@@ -82,258 +800,735 @@ var Codai = class {
82
800
  });
83
801
  final.catch(() => {
84
802
  });
85
- const toolCallsByIndex = /* @__PURE__ */ new Map();
86
- async function* iterate() {
87
- let content = "";
88
- let usage = null;
89
- let res;
803
+ let text = "";
804
+ let stopReason = null;
805
+ let inputTokens = null;
806
+ let outputTokens = null;
807
+ let res = null;
808
+ async function* events() {
90
809
  try {
91
810
  res = await requestPromise;
811
+ for await (const frame of readSse(res, opts.signal)) {
812
+ const ev = frameJson(frame);
813
+ if (!ev) continue;
814
+ if (typeof ev.type !== "string") ev.type = frame.event;
815
+ if (ev.type === "message_start") {
816
+ const usage = ev.message?.usage;
817
+ if (usage?.input_tokens !== void 0) inputTokens = usage.input_tokens;
818
+ } else if (ev.type === "content_block_delta") {
819
+ const delta = ev.delta;
820
+ if (delta?.type === "text_delta" && typeof delta.text === "string") text += delta.text;
821
+ } else if (ev.type === "message_delta") {
822
+ const delta = ev.delta;
823
+ if (delta?.stop_reason) stopReason = delta.stop_reason;
824
+ const usage = ev.usage;
825
+ if (usage?.output_tokens !== void 0) outputTokens = usage.output_tokens;
826
+ }
827
+ yield ev;
828
+ if (ev.type === "message_stop") break;
829
+ }
830
+ resolveFinal({
831
+ text,
832
+ stopReason,
833
+ usage: inputTokens !== null || outputTokens !== null ? { inputTokens: inputTokens ?? 0, outputTokens: outputTokens ?? 0 } : null,
834
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id") ?? null,
835
+ routedTo: res.headers.get("x-codai-routed-to"),
836
+ headers: res.headers
837
+ });
92
838
  } catch (err) {
93
839
  rejectFinal(err);
94
840
  throw err;
95
841
  }
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;
842
+ }
843
+ async function* textOnly() {
844
+ for await (const ev of events()) {
845
+ if (ev.type !== "content_block_delta") continue;
846
+ const delta = ev.delta;
847
+ if (delta?.type === "text_delta" && delta.text) yield delta.text;
101
848
  }
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
- });
849
+ }
850
+ return {
851
+ [Symbol.asyncIterator]: () => events(),
852
+ text: () => ({ [Symbol.asyncIterator]: () => textOnly() }),
853
+ final
854
+ };
855
+ }
856
+ };
857
+
858
+ // src/resources/models.ts
859
+ var Models = class {
860
+ constructor(http) {
861
+ this.http = http;
862
+ }
863
+ http;
864
+ /** Every model the calling key may address (OpenAI list shape, `data[]`). */
865
+ async list(opts = {}) {
866
+ const raw = await this.http.json("/v1/models", {
867
+ ext: opts.ext,
868
+ signal: opts.signal
869
+ });
870
+ return [...raw.data ?? []];
871
+ }
872
+ };
873
+
874
+ // src/resources/orgs.ts
875
+ var OrgMembers = class {
876
+ constructor(http) {
877
+ this.http = http;
878
+ }
879
+ http;
880
+ /** `GET /v1/orgs/{id}/members` */
881
+ async list(orgId, opts = {}) {
882
+ const raw = await this.http.json(
883
+ `/v1/orgs/${encodeURIComponent(orgId)}/members`,
884
+ { ext: opts.ext, signal: opts.signal }
885
+ );
886
+ return [...raw.members ?? []];
887
+ }
888
+ /** `POST /v1/orgs/{id}/members` — add or re-role (owner/admin). */
889
+ add(orgId, body, opts = {}) {
890
+ return this.http.json(
891
+ `/v1/orgs/${encodeURIComponent(orgId)}/members`,
892
+ { body, ext: opts.ext, signal: opts.signal }
893
+ );
894
+ }
895
+ /** `DELETE /v1/orgs/{id}/members/{userId}` */
896
+ remove(orgId, userId, opts = {}) {
897
+ return this.http.json(
898
+ `/v1/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`,
899
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
900
+ );
901
+ }
902
+ };
903
+ var Orgs = class {
904
+ constructor(http) {
905
+ this.http = http;
906
+ this.members = new OrgMembers(http);
907
+ }
908
+ http;
909
+ members;
910
+ /** `POST /v1/orgs` */
911
+ create(name, opts = {}) {
912
+ return this.http.json("/v1/orgs", { body: { name }, ext: opts.ext, signal: opts.signal });
913
+ }
914
+ /** `GET /v1/orgs` */
915
+ async list(opts = {}) {
916
+ const raw = await this.http.json("/v1/orgs", {
917
+ ext: opts.ext,
918
+ signal: opts.signal
919
+ });
920
+ return [...raw.orgs ?? []];
921
+ }
922
+ };
923
+
924
+ // src/resources/responses.ts
925
+ var Responses = class {
926
+ constructor(http) {
927
+ this.http = http;
928
+ }
929
+ http;
930
+ async create(body, opts = {}) {
931
+ const res = await this.http.request("/v1/responses", {
932
+ body: { ...body, model: body.model ?? "codai", stream: false },
933
+ ext: opts.ext,
934
+ signal: opts.signal
935
+ });
936
+ const raw = await res.json();
937
+ return {
938
+ outputText: typeof raw.output_text === "string" ? raw.output_text : "",
939
+ raw,
940
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id"),
941
+ routedTo: res.headers.get("x-codai-routed-to"),
942
+ headers: res.headers
943
+ };
944
+ }
945
+ stream(body, opts = {}) {
946
+ const requestPromise = this.http.request("/v1/responses", {
947
+ body: { ...body, model: body.model ?? "codai", stream: true },
948
+ ext: opts.ext,
949
+ signal: opts.signal,
950
+ timeoutMs: 0,
951
+ noRetry: true
952
+ });
953
+ let resolveFinal;
954
+ let rejectFinal;
955
+ const final = new Promise((resolve, reject) => {
956
+ resolveFinal = resolve;
957
+ rejectFinal = reject;
958
+ });
959
+ final.catch(() => {
960
+ });
961
+ let outputText = "";
962
+ let response = null;
963
+ let res = null;
964
+ async function* events() {
113
965
  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
- }
151
- }
152
- if (finished) break;
966
+ res = await requestPromise;
967
+ for await (const frame of readSse(res, opts.signal)) {
968
+ const ev = frameJson(frame);
969
+ if (!ev) continue;
970
+ if (typeof ev.type !== "string") ev.type = frame.event;
971
+ if (ev.type === "response.output_text.delta" && typeof ev.delta === "string") {
972
+ outputText += ev.delta;
973
+ } else if (ev.type === "response.completed" && ev.response) {
974
+ response = ev.response;
153
975
  }
154
- if (finished) break;
976
+ yield ev;
977
+ if (ev.type === "response.completed") break;
155
978
  }
156
- resolveFinal(buildResult());
979
+ resolveFinal({
980
+ outputText,
981
+ response,
982
+ requestId: res.headers.get("x-codai-trace-id") ?? res.headers.get("x-request-id") ?? null,
983
+ routedTo: res.headers.get("x-codai-routed-to"),
984
+ headers: res.headers
985
+ });
157
986
  } catch (err) {
158
987
  rejectFinal(err);
159
988
  throw err;
160
- } finally {
161
- reader.releaseLock?.();
162
989
  }
163
990
  }
164
- const gen = iterate.call(this);
991
+ async function* textOnly() {
992
+ for await (const ev of events()) {
993
+ if (ev.type === "response.output_text.delta" && typeof ev.delta === "string")
994
+ yield ev.delta;
995
+ }
996
+ }
165
997
  return {
166
- [Symbol.asyncIterator]: () => gen,
998
+ [Symbol.asyncIterator]: () => events(),
999
+ text: () => ({ [Symbol.asyncIterator]: () => textOnly() }),
167
1000
  final
168
1001
  };
169
1002
  }
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
- };
1003
+ };
1004
+
1005
+ // src/resources/sessions.ts
1006
+ var enc = encodeURIComponent;
1007
+ var SessionLease = class {
1008
+ constructor(http) {
1009
+ this.http = http;
1010
+ }
1011
+ http;
1012
+ /** `POST /v1/sessions/{id}/lease` — CAS claim; `force: true` takes over a live lease. */
1013
+ acquire(sessionId, body = {}, opts = {}) {
1014
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1015
+ method: "POST",
1016
+ body,
1017
+ ext: opts.ext,
1018
+ signal: opts.signal
1019
+ });
1020
+ }
1021
+ /** `PUT /v1/sessions/{id}/lease` — heartbeat every 10 s (TTL 30 s). */
1022
+ renew(sessionId, opts = {}) {
1023
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1024
+ method: "PUT",
1025
+ ext: opts.ext,
1026
+ signal: opts.signal
1027
+ });
1028
+ }
1029
+ /** `DELETE /v1/sessions/{id}/lease` */
1030
+ release(sessionId, opts = {}) {
1031
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/lease`, {
1032
+ method: "DELETE",
1033
+ ext: opts.ext,
1034
+ signal: opts.signal
1035
+ });
1036
+ }
1037
+ };
1038
+ var SessionEvents = class {
1039
+ constructor(http) {
1040
+ this.http = http;
1041
+ }
1042
+ http;
1043
+ /** `GET /v1/sessions/{id}/events?after=&limit=` */
1044
+ list(sessionId, query = {}, opts = {}) {
1045
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/events`, {
1046
+ query: { after: query.after, limit: query.limit },
1047
+ ext: opts.ext,
1048
+ signal: opts.signal
1049
+ });
1050
+ }
1051
+ /** `POST /v1/sessions/{id}/events` — executor only (needs the live lease). */
1052
+ append(sessionId, body, opts = {}) {
1053
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/events`, {
1054
+ body,
1055
+ ext: opts.ext,
1056
+ signal: opts.signal
1057
+ });
1058
+ }
1059
+ };
1060
+ var SessionControls = class {
1061
+ constructor(http) {
1062
+ this.http = http;
1063
+ }
1064
+ http;
1065
+ /** `GET /v1/sessions/{id}/controls?applied=&target=` — drain the control queue. */
1066
+ async list(sessionId, query = {}, opts = {}) {
1067
+ const raw = await this.http.json(
1068
+ `/v1/sessions/${enc(sessionId)}/controls`,
1069
+ {
1070
+ query: { applied: query.applied, target: query.target },
1071
+ ext: opts.ext,
1072
+ signal: opts.signal
1073
+ }
1074
+ );
1075
+ return [...raw.controls ?? []];
1076
+ }
1077
+ /** `POST /v1/sessions/{id}/control` — send a control to the executor (editor+). */
1078
+ submit(sessionId, body, opts = {}) {
1079
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/control`, {
1080
+ body,
1081
+ ext: opts.ext,
1082
+ signal: opts.signal
1083
+ });
1084
+ }
1085
+ /** `POST /v1/sessions/{id}/control/{cid}/applied` — executor only. */
1086
+ markApplied(sessionId, controlId, opts = {}) {
1087
+ return this.http.json(
1088
+ `/v1/sessions/${enc(sessionId)}/control/${enc(controlId)}/applied`,
1089
+ { method: "POST", ext: opts.ext, signal: opts.signal }
1090
+ );
1091
+ }
1092
+ };
1093
+ var SessionShares = class {
1094
+ constructor(http) {
1095
+ this.http = http;
1096
+ }
1097
+ http;
1098
+ /** `GET /v1/sessions/{id}/shares` — owner only. */
1099
+ async list(sessionId, opts = {}) {
1100
+ const raw = await this.http.json(
1101
+ `/v1/sessions/${enc(sessionId)}/shares`,
1102
+ { ext: opts.ext, signal: opts.signal }
1103
+ );
1104
+ return [...raw.shares ?? []];
1105
+ }
1106
+ /** `POST /v1/sessions/{id}/shares` — `token` is returned once for link shares. */
1107
+ create(sessionId, body, opts = {}) {
1108
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/shares`, {
1109
+ body,
1110
+ ext: opts.ext,
1111
+ signal: opts.signal
1112
+ });
1113
+ }
1114
+ /** `DELETE /v1/sessions/{id}/shares/{shareId}` */
1115
+ delete(sessionId, shareId, opts = {}) {
1116
+ return this.http.json(
1117
+ `/v1/sessions/${enc(sessionId)}/shares/${enc(shareId)}`,
1118
+ { method: "DELETE", ext: opts.ext, signal: opts.signal }
1119
+ );
1120
+ }
1121
+ /** `GET /v1/sessions/shared-with-me` */
1122
+ async sharedWithMe(query = {}, opts = {}) {
1123
+ const raw = await this.http.json(
1124
+ "/v1/sessions/shared-with-me",
1125
+ {
1126
+ query: { archived: query.archived ? "1" : void 0 },
1127
+ ext: opts.ext,
1128
+ signal: opts.signal
1129
+ }
1130
+ );
1131
+ return [...raw.sessions ?? []];
1132
+ }
1133
+ };
1134
+ var Sessions = class {
1135
+ constructor(http) {
1136
+ this.http = http;
1137
+ this.events = new SessionEvents(http);
1138
+ this.controls = new SessionControls(http);
1139
+ this.lease = new SessionLease(http);
1140
+ this.shares = new SessionShares(http);
1141
+ }
1142
+ http;
1143
+ events;
1144
+ controls;
1145
+ lease;
1146
+ shares;
1147
+ /** `POST /v1/sessions` — idempotent on `session_key` (200 existing / 201 created). */
1148
+ async create(body = {}, opts = {}) {
1149
+ const res = await this.http.request("/v1/sessions", {
1150
+ body,
1151
+ ext: opts.ext,
1152
+ signal: opts.signal
1153
+ });
1154
+ const session = await res.json();
1155
+ return { ...session, created: res.status === 201 };
1156
+ }
1157
+ /** `GET /v1/sessions?limit=&archived=` — own sessions first, then shared. */
1158
+ async list(query = {}, opts = {}) {
1159
+ const raw = await this.http.json("/v1/sessions", {
1160
+ query: { limit: query.limit, archived: query.archived ? "1" : void 0, v: "2" },
1161
+ ext: opts.ext,
1162
+ signal: opts.signal
1163
+ });
1164
+ return [...raw.sessions ?? []];
1165
+ }
1166
+ /** `GET /v1/sessions/{id}` — detail with members, lease and presence. */
1167
+ get(sessionId, query = {}, opts = {}) {
1168
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1169
+ query: { share: query.share, v: "2" },
1170
+ ext: opts.ext,
1171
+ signal: opts.signal
1172
+ });
1173
+ }
1174
+ /** `PATCH /v1/sessions/{id}` — rename / archive (owner). */
1175
+ update(sessionId, body, opts = {}) {
1176
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1177
+ method: "PATCH",
1178
+ body,
1179
+ ext: opts.ext,
1180
+ signal: opts.signal
1181
+ });
1182
+ }
1183
+ /** `DELETE /v1/sessions/{id}` — irreversible (owner). */
1184
+ delete(sessionId, opts = {}) {
1185
+ return this.http.json(`/v1/sessions/${enc(sessionId)}`, {
1186
+ method: "DELETE",
1187
+ ext: opts.ext,
1188
+ signal: opts.signal
1189
+ });
1190
+ }
1191
+ /** `POST /v1/sessions/{id}/dispatch` — queue a `send` for one device and wake it over FCM. */
1192
+ dispatch(sessionId, body, opts = {}) {
1193
+ return this.http.json(`/v1/sessions/${enc(sessionId)}/dispatch`, {
1194
+ body,
1195
+ ext: opts.ext,
1196
+ signal: opts.signal
1197
+ });
1198
+ }
1199
+ /**
1200
+ * `GET /v1/sessions/{id}/stream?after=` — live SSE of `event` / `control` /
1201
+ * `lease` / `presence` frames. `: ping` heartbeats are skipped. The server
1202
+ * closes after 30 min; reconnect with `after` = highest `seq` seen.
1203
+ */
1204
+ async *stream(sessionId, query = {}, opts = {}) {
1205
+ const frames = this.http.sse(`/v1/sessions/${enc(sessionId)}/stream`, {
1206
+ query: { after: query.after, share: query.share },
1207
+ ext: opts.ext,
1208
+ signal: opts.signal
1209
+ });
1210
+ for await (const frame of frames) {
1211
+ const data = frameJson(frame);
1212
+ if (!data) continue;
1213
+ yield { event: frame.event, data };
190
1214
  }
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 ?? [];
206
1215
  }
207
- // -------------------------------------------------------------- tokens
1216
+ };
1217
+
1218
+ // src/resources/tasks.ts
1219
+ var Tasks = class {
1220
+ constructor(http) {
1221
+ this.http = http;
1222
+ }
1223
+ http;
1224
+ /** `GET /v1/tasks` — newest first; page with `cursor` = previous `next_cursor`. */
1225
+ list(query = {}, opts = {}) {
1226
+ return this.http.json("/v1/tasks", {
1227
+ query: { outcome: query.outcome, limit: query.limit, cursor: query.cursor },
1228
+ ext: opts.ext,
1229
+ signal: opts.signal
1230
+ });
1231
+ }
1232
+ /** `GET /v1/tasks/pending` — tasks awaiting your confirmation (max 100). */
1233
+ async pending(opts = {}) {
1234
+ const raw = await this.http.json("/v1/tasks/pending", {
1235
+ ext: opts.ext,
1236
+ signal: opts.signal
1237
+ });
1238
+ return [...raw.tasks ?? []];
1239
+ }
1240
+ /** `GET /v1/tasks/stats?since=` */
1241
+ stats(since, opts = {}) {
1242
+ return this.http.json("/v1/tasks/stats", {
1243
+ query: { since: since instanceof Date ? since.toISOString() : since },
1244
+ ext: opts.ext,
1245
+ signal: opts.signal
1246
+ });
1247
+ }
1248
+ /** `GET /v1/tasks/{id}` */
1249
+ get(id, opts = {}) {
1250
+ return this.http.json(`/v1/tasks/${encodeURIComponent(id)}`, {
1251
+ ext: opts.ext,
1252
+ signal: opts.signal
1253
+ });
1254
+ }
1255
+ /** `POST /v1/tasks/{id}/confirm` — answer "Did codai solve it?". */
1256
+ confirm(id, body, opts = {}) {
1257
+ return this.http.json(`/v1/tasks/${encodeURIComponent(id)}/confirm`, {
1258
+ body,
1259
+ ext: opts.ext,
1260
+ signal: opts.signal
1261
+ });
1262
+ }
1263
+ };
1264
+
1265
+ // src/resources/tokens.ts
1266
+ var Tokens = class {
1267
+ constructor(http) {
1268
+ this.http = http;
1269
+ }
1270
+ http;
208
1271
  /**
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.
1272
+ * Mint a short-lived token (60–3600 s, default 600) bound to one surface.
1273
+ * Only a real `codai_` key may mint.
211
1274
  */
212
- async mintToken(scope, ttlSeconds) {
213
- const res = await this.request("/v1/tokens", {
214
- scope,
215
- ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {}
1275
+ async create(scope, ttlSeconds, opts = {}) {
1276
+ const raw = await this.http.json("/v1/tokens", {
1277
+ body: { scope, ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {} },
1278
+ ext: opts.ext,
1279
+ signal: opts.signal
216
1280
  });
217
- const body = await res.json();
218
- return { token: body.token, expiresAt: body.expires_at, scope: body.scope };
1281
+ return { token: raw.token, expiresAt: raw.expires_at, scope: raw.scope, raw };
219
1282
  }
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);
1283
+ };
1284
+
1285
+ // src/resources/tools.ts
1286
+ var Tools = class {
1287
+ constructor(http) {
1288
+ this.http = http;
1289
+ }
1290
+ http;
1291
+ /** `POST /v1/tools/search` — verified web search (never falls back to an unverified provider). */
1292
+ search(body, opts = {}) {
1293
+ return this.http.json("/v1/tools/search", {
1294
+ body,
1295
+ ext: opts.ext,
1296
+ signal: opts.signal
1297
+ });
1298
+ }
1299
+ /** `POST /v1/tools/fetch` — fetch + extract a public page (SSRF-guarded). */
1300
+ fetch(body, opts = {}) {
1301
+ return this.http.json("/v1/tools/fetch", {
1302
+ body,
1303
+ ext: opts.ext,
1304
+ signal: opts.signal
1305
+ });
1306
+ }
1307
+ };
1308
+
1309
+ // src/operations.ts
1310
+ var OPERATION_METHODS = {
1311
+ // Chat & Completions
1312
+ createChatCompletion: "chat.completions.create",
1313
+ createResponse: "responses.create",
1314
+ createMessage: "messages.create",
1315
+ // Embeddings & Audio
1316
+ createEmbeddings: "embeddings.create",
1317
+ createTranscription: "audio.transcribe",
1318
+ createSpeech: "audio.speech",
1319
+ createEphemeralToken: "tokens.create",
1320
+ // Models
1321
+ listModels: "models.list",
1322
+ // Health
1323
+ getHealth: "health.get",
1324
+ getHealthReady: "health.ready",
1325
+ getStatus: "health.status",
1326
+ // Agents
1327
+ runAgentSync: "agents.run",
1328
+ createAgentRun: "agents.runs.create",
1329
+ getAgentRunStats: "agents.runs.stats",
1330
+ getAgentRun: "agents.runs.get",
1331
+ listAgentRunSteps: "agents.runs.steps",
1332
+ streamAgentRun: "agents.runs.stream",
1333
+ cancelAgentRun: "agents.runs.cancel",
1334
+ // Tools
1335
+ toolsSearch: "tools.search",
1336
+ toolsFetch: "tools.fetch",
1337
+ // Tasks
1338
+ listTasks: "tasks.list",
1339
+ listPendingTasks: "tasks.pending",
1340
+ getTaskStats: "tasks.stats",
1341
+ getTask: "tasks.get",
1342
+ confirmTask: "tasks.confirm",
1343
+ // Sessions
1344
+ createSession: "sessions.create",
1345
+ listSessions: "sessions.list",
1346
+ getSession: "sessions.get",
1347
+ updateSession: "sessions.update",
1348
+ deleteSession: "sessions.delete",
1349
+ listSessionEvents: "sessions.events.list",
1350
+ appendSessionEvents: "sessions.events.append",
1351
+ listSessionControls: "sessions.controls.list",
1352
+ submitSessionControl: "sessions.controls.submit",
1353
+ markSessionControlApplied: "sessions.controls.markApplied",
1354
+ dispatchToDevice: "sessions.dispatch",
1355
+ claimSessionLease: "sessions.lease.acquire",
1356
+ heartbeatSessionLease: "sessions.lease.renew",
1357
+ releaseSessionLease: "sessions.lease.release",
1358
+ streamSession: "sessions.stream",
1359
+ // Devices
1360
+ getMyDispatchInbox: "devices.dispatchInbox",
1361
+ listDevices: "devices.list",
1362
+ updateDevice: "devices.update",
1363
+ deleteDevice: "devices.delete",
1364
+ // Hosts
1365
+ listHosts: "hosts.list",
1366
+ streamHost: "hosts.stream",
1367
+ execOnHost: "hosts.exec",
1368
+ postHostExecResult: "hosts.postResult",
1369
+ // Orgs & Sharing
1370
+ listSessionsSharedWithMe: "sessions.shares.sharedWithMe",
1371
+ listSessionShares: "sessions.shares.list",
1372
+ createSessionShare: "sessions.shares.create",
1373
+ deleteSessionShare: "sessions.shares.delete",
1374
+ createOrg: "orgs.create",
1375
+ listOrgs: "orgs.list",
1376
+ listOrgMembers: "orgs.members.list",
1377
+ addOrgMember: "orgs.members.add",
1378
+ removeOrgMember: "orgs.members.remove",
1379
+ // Account
1380
+ getAccount: "account.get",
1381
+ patchAccount: "account.update",
1382
+ getReceipt: "receipt.get",
1383
+ submitFeedback: "feedback.submit",
1384
+ listPhoneModels: "phoneModels.list"
1385
+ };
1386
+ var STREAMING_OPERATIONS = /* @__PURE__ */ new Set([
1387
+ "streamAgentRun",
1388
+ "streamSession",
1389
+ "streamHost"
1390
+ ]);
1391
+
1392
+ // src/index.ts
1393
+ var Codai = class {
1394
+ http;
1395
+ /** Resource group (`chat.completions.create/stream`) that is also callable with the 0.2.x `chat({ messages })` shape. */
1396
+ chat;
1397
+ messages;
1398
+ responses;
1399
+ embeddings;
1400
+ audio;
1401
+ tokens;
1402
+ models;
1403
+ health;
1404
+ agents;
1405
+ tools;
1406
+ tasks;
1407
+ sessions;
1408
+ devices;
1409
+ hosts;
1410
+ orgs;
1411
+ account;
1412
+ receipt;
1413
+ feedback;
1414
+ phoneModels;
1415
+ constructor(opts) {
1416
+ if (!opts.apiKey) throw new Error("Codai: apiKey is required");
1417
+ this.http = new HttpClient({
1418
+ apiKey: opts.apiKey,
1419
+ baseUrl: opts.baseUrl ?? "https://ai.codai.ro",
1420
+ timeoutMs: opts.timeoutMs ?? 12e4,
1421
+ maxRetries: opts.maxRetries ?? 2,
1422
+ fetch: opts.fetch,
1423
+ defaults: {
1424
+ ...opts.defaults,
1425
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
1426
+ ...opts.device ? { device: opts.device } : {},
1427
+ ...opts.deviceName ? { deviceName: opts.deviceName } : {},
1428
+ ...opts.devicePlatform ? { devicePlatform: opts.devicePlatform } : {},
1429
+ ...opts.client ? { client: opts.client } : {}
255
1430
  }
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 }
1431
+ });
1432
+ const http = this.http;
1433
+ this.messages = new Messages(http);
1434
+ this.responses = new Responses(http);
1435
+ this.audio = new Audio(http);
1436
+ this.tokens = new Tokens(http);
1437
+ this.health = new Health(http);
1438
+ this.agents = new Agents(http);
1439
+ this.tools = new Tools(http);
1440
+ this.tasks = new Tasks(http);
1441
+ this.sessions = new Sessions(http);
1442
+ this.devices = new Devices(http);
1443
+ this.hosts = new Hosts(http);
1444
+ this.orgs = new Orgs(http);
1445
+ this.account = new Account(http);
1446
+ this.receipt = new Receipts(http);
1447
+ this.phoneModels = new PhoneModels(http);
1448
+ this.chat = callable(
1449
+ new Chat(http),
1450
+ (o) => this.chat.completions.create(this.legacyBody(o), {
1451
+ ext: this.legacyExt(o),
1452
+ signal: o.signal
1453
+ })
1454
+ );
1455
+ this.embeddings = callable(new Embeddings(http), async (o) => {
1456
+ const r = await this.embeddings.create(
1457
+ { input: o.input, model: o.model, dimensions: o.dimensions },
1458
+ { signal: o.signal }
269
1459
  );
270
- return res.arrayBuffer();
271
- }
272
- };
273
- // ------------------------------------------------------------ internal
274
- chatBody(opts, stream) {
1460
+ return { embeddings: r.embeddings, raw: r.raw };
1461
+ });
1462
+ this.models = callable(
1463
+ new Models(http),
1464
+ async () => (await this.models.list()).map((m) => ({ id: m.id }))
1465
+ );
1466
+ this.feedback = callable(
1467
+ new Feedback(http),
1468
+ async (requestId, rating, comment) => {
1469
+ await this.feedback.submit({
1470
+ event_id: requestId,
1471
+ rating,
1472
+ ...comment ? { comment } : {}
1473
+ });
1474
+ }
1475
+ );
1476
+ }
1477
+ // ------------------------------------------------- 0.2.x compatibility aliases
1478
+ /** Alias of `chat.completions.stream()` with the 0.2.x option names. */
1479
+ chatStream(opts) {
1480
+ return this.chat.completions.stream(this.legacyBody(opts), {
1481
+ ext: this.legacyExt(opts),
1482
+ signal: opts.signal
1483
+ });
1484
+ }
1485
+ /** Alias of `tokens.create()`. */
1486
+ mintToken(scope, ttlSeconds) {
1487
+ return this.tokens.create(scope, ttlSeconds);
1488
+ }
1489
+ legacyBody(opts) {
275
1490
  return {
276
1491
  model: opts.model ?? "codai",
277
1492
  messages: opts.messages,
278
- stream,
279
1493
  ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
280
1494
  ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {},
281
1495
  ...opts.tools ? { tools: opts.tools } : {}
282
1496
  };
283
1497
  }
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);
1498
+ legacyExt(opts) {
1499
+ return {
1500
+ ...opts.ext,
1501
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
1502
+ ...opts.agentMode ? { mode: "agent" } : {},
1503
+ ...opts.compact ? { compact: opts.compact } : {},
1504
+ ...opts.bestOf !== void 0 ? { bestOf: opts.bestOf } : {},
1505
+ ...opts.agentId ? { agentId: opts.agentId } : {}
1506
+ };
330
1507
  }
331
1508
  };
332
- function sleep(ms) {
333
- return new Promise((r) => setTimeout(r, ms));
1509
+ function callable(target, fn) {
1510
+ const f = ((...args) => fn(...args));
1511
+ for (const key of allKeys(target)) {
1512
+ const value = target[key];
1513
+ Object.defineProperty(f, key, {
1514
+ value: typeof value === "function" ? value.bind(target) : value,
1515
+ enumerable: true,
1516
+ writable: false
1517
+ });
1518
+ }
1519
+ return f;
1520
+ }
1521
+ function allKeys(obj) {
1522
+ const keys = /* @__PURE__ */ new Set();
1523
+ let cur = obj;
1524
+ while (cur && cur !== Object.prototype) {
1525
+ for (const k of Object.getOwnPropertyNames(cur)) if (k !== "constructor") keys.add(k);
1526
+ cur = Object.getPrototypeOf(cur);
1527
+ }
1528
+ return [...keys];
334
1529
  }
335
1530
  var index_default = Codai;
336
1531
 
337
- export { Codai, CodaiError, index_default as default };
1532
+ export { Account, AgentRuns, Agents, Audio, Chat, ChatCompletions, Codai, CodaiError, Devices, Embeddings, Feedback, Health, Hosts, HttpClient, Messages, Models, OPERATION_METHODS, OrgMembers, Orgs, PhoneModels, Receipts, Responses, STREAMING_OPERATIONS, SessionControls, SessionEvents, SessionLease, SessionShares, Sessions, Tasks, Tokens, Tools, index_default as default, extensionHeaders, frameJson, mergeToolCall, readSse };
338
1533
  //# sourceMappingURL=index.js.map
339
1534
  //# sourceMappingURL=index.js.map