codai-sdk 0.1.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,248 +1,1534 @@
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]);
13
- var Codai = class {
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 {
14
94
  apiKey;
15
95
  baseUrl;
16
- sessionId;
17
96
  timeoutMs;
18
97
  maxRetries;
98
+ defaults;
99
+ fetchImpl;
19
100
  constructor(opts) {
20
- if (!opts.apiKey) throw new Error("Codai: apiKey is required");
21
101
  this.apiKey = opts.apiKey;
22
- this.baseUrl = (opts.baseUrl ?? "https://ai.codai.ro").replace(/\/$/, "");
23
- this.sessionId = opts.sessionId;
24
- this.timeoutMs = opts.timeoutMs ?? 12e4;
25
- this.maxRetries = opts.maxRetries ?? 2;
26
- }
27
- // ---------------------------------------------------------------- chat
28
- async chat(opts) {
29
- const res = await this.request("/v1/chat/completions", this.chatBody(opts, false), {
30
- signal: opts.signal,
31
- headers: this.chatHeaders(opts)
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
+ }
481
+ function mergeToolCall(acc, index, fragment) {
482
+ const existing = acc.get(index) ?? { index };
483
+ for (const [key, val] of Object.entries(fragment)) {
484
+ if (key === "index") continue;
485
+ if (key === "function" && val && typeof val === "object") {
486
+ const fn = existing.function ?? {};
487
+ for (const [fk, fv] of Object.entries(val)) {
488
+ if (fk === "arguments") fn.arguments = String(fn.arguments ?? "") + String(fv ?? "");
489
+ else fn[fk] = fv;
490
+ }
491
+ existing.function = fn;
492
+ } else {
493
+ existing[key] = val;
494
+ }
495
+ }
496
+ acc.set(index, existing);
497
+ }
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
32
512
  });
33
513
  const raw = await res.json();
34
- const choices = raw.choices;
514
+ const first = raw.choices?.[0];
35
515
  const usage = raw.usage;
36
516
  return {
37
- content: String(choices?.[0]?.message?.content ?? ""),
517
+ content: typeof first?.message?.content === "string" ? first.message.content : "",
518
+ toolCalls: [...first?.message?.tool_calls ?? []],
38
519
  raw,
39
- 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"),
40
522
  routedTo: res.headers.get("x-codai-routed-to"),
41
523
  execVerify: res.headers.get("x-codai-exec-verify"),
42
- usage: usage ? {
43
- promptTokens: usage.prompt_tokens ?? 0,
44
- completionTokens: usage.completion_tokens ?? 0
45
- } : null
524
+ usage: toUsage(usage),
525
+ headers: res.headers
46
526
  };
47
527
  }
48
- /** Streaming chat: async-iterate text deltas; `final` resolves afterwards. */
49
- async *chatStream(opts) {
50
- const res = await 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,
51
533
  signal: opts.signal,
52
- headers: this.chatHeaders(opts)
534
+ timeoutMs: 0,
535
+ noRetry: true
53
536
  });
54
- const reader = res.body?.getReader();
55
- if (!reader) throw new CodaiError("no response body", res.status, null);
56
- const decoder = new TextDecoder();
57
- let buffer = "";
58
- for (; ; ) {
59
- const { done, value } = await reader.read();
60
- if (done) break;
61
- buffer += decoder.decode(value, { stream: true });
62
- const frames = buffer.split("\n\n");
63
- buffer = frames.pop() ?? "";
64
- for (const frame of frames) {
65
- for (const line of frame.split("\n")) {
66
- if (!line.startsWith("data:")) continue;
67
- const data = line.slice(5).trim();
68
- if (data === "[DONE]") return;
69
- try {
70
- const chunk = JSON.parse(data);
71
- const delta = chunk.choices?.[0]?.delta?.content;
72
- if (delta) yield delta;
73
- } catch {
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
+ }
74
575
  }
576
+ if (delta?.content) content += delta.content;
577
+ yield chunk;
75
578
  }
579
+ resolveFinal(buildResult());
580
+ } catch (err) {
581
+ rejectFinal(err);
582
+ throw err;
76
583
  }
77
584
  }
78
- }
79
- // -------------------------------------------------------------- agents
80
- agents = {
81
- run: async (opts) => {
82
- const res = await this.request(
83
- "/v1/agents/run",
84
- {
85
- task: opts.task,
86
- ...opts.context ? { context: opts.context } : {},
87
- ...opts.system ? { system: opts.system } : {},
88
- ...opts.model ? { model: opts.model } : {}
89
- },
90
- { signal: opts.signal }
91
- );
92
- const body = await res.json();
93
- return {
94
- result: String(body.result ?? ""),
95
- model: String(body.model ?? ""),
96
- eventId: body.event_id ?? null,
97
- usage: body.usage ?? null
98
- };
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
+ }
99
590
  }
100
- };
101
- // ------------------------------------------------------------ feedback
102
- /** Rate a completed request: +1 (helpful) or -1 (not helpful). */
103
- async feedback(requestId, rating, comment) {
104
- await this.request("/v1/feedback", {
105
- event_id: requestId,
106
- rating,
107
- ...comment ? { comment } : {}
108
- });
109
- }
110
- // -------------------------------------------------------------- models
111
- async models() {
112
- const res = await this.request("/v1/models", null, { method: "GET" });
113
- const body = await res.json();
114
- return body.data ?? [];
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
+ }
636
+ /**
637
+ * `GET /v1/devices/me/dispatch?limit=` — pending controls dispatched at the
638
+ * calling device (`x-codai-device` required) across all owned sessions.
639
+ */
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 });
115
681
  }
116
- // -------------------------------------------------------------- tokens
117
682
  /**
118
- * Mint a short-lived scoped token (60–3600s) for browser/webview clients.
119
- * The token authenticates only its scope's surface; chat is not mintable.
683
+ * Deep readiness probe. A 503 is returned as `{ ok: false, ... }` rather
684
+ * than thrown, so callers can read the failing check.
120
685
  */
121
- async mintToken(scope, ttlSeconds) {
122
- const res = await this.request("/v1/tokens", {
123
- scope,
124
- ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {}
686
+ async ready(opts = {}) {
687
+ const res = await this.http.request("/health/ready", {
688
+ signal: opts.signal,
689
+ noRetry: true,
690
+ acceptStatus: [503]
125
691
  });
126
692
  const body = await res.json();
127
- return { token: body.token, expiresAt: body.expires_at, scope: body.scope };
693
+ return { ...body, httpStatus: res.status };
128
694
  }
129
- // ---------------------------------------------------------- embeddings
130
- async embeddings(opts) {
131
- const res = await this.request(
132
- "/v1/embeddings",
133
- {
134
- model: opts.model ?? "codai-embed",
135
- input: opts.input,
136
- ...opts.dimensions !== void 0 ? { dimensions: opts.dimensions } : {}
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
137
753
  },
138
- { signal: opts.signal }
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,
791
+ signal: opts.signal,
792
+ timeoutMs: 0,
793
+ noRetry: true
794
+ });
795
+ let resolveFinal;
796
+ let rejectFinal;
797
+ const final = new Promise((resolve, reject) => {
798
+ resolveFinal = resolve;
799
+ rejectFinal = reject;
800
+ });
801
+ final.catch(() => {
802
+ });
803
+ let text = "";
804
+ let stopReason = null;
805
+ let inputTokens = null;
806
+ let outputTokens = null;
807
+ let res = null;
808
+ async function* events() {
809
+ try {
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
+ });
838
+ } catch (err) {
839
+ rejectFinal(err);
840
+ throw err;
841
+ }
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;
848
+ }
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 }
139
885
  );
140
- const body = await res.json();
141
- return { embeddings: (body.data ?? []).map((d) => d.embedding), raw: body };
142
- }
143
- // --------------------------------------------------------------- audio
144
- audio = {
145
- /** Speech-to-text (multipart upload, max 25 MB). Returns the transcript. */
146
- transcribe: async (opts) => {
147
- const form = new FormData();
148
- const blob = opts.file instanceof Blob ? opts.file : new Blob([opts.file]);
149
- form.append("file", blob, opts.filename ?? "audio.webm");
150
- form.append("model", opts.model ?? "codai-transcribe");
151
- const res = await fetch(`${this.baseUrl}/v1/audio/transcriptions`, {
152
- method: "POST",
153
- headers: { Authorization: `Bearer ${this.apiKey}` },
154
- body: form,
155
- signal: opts.signal ?? AbortSignal.timeout(this.timeoutMs)
156
- });
157
- if (!res.ok) {
158
- let errBody = null;
159
- try {
160
- errBody = await res.json();
161
- } catch {
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() {
965
+ try {
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;
975
+ }
976
+ yield ev;
977
+ if (ev.type === "response.completed") break;
162
978
  }
163
- throw new CodaiError(`codai /v1/audio/transcriptions \u2192 ${res.status}`, res.status, errBody);
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
+ });
986
+ } catch (err) {
987
+ rejectFinal(err);
988
+ throw err;
989
+ }
990
+ }
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;
164
995
  }
165
- const body = await res.json();
166
- return body.text ?? "";
167
- },
168
- /** Text-to-speech. Returns the audio bytes (default mp3). */
169
- speech: async (opts) => {
170
- const res = await this.request(
171
- "/v1/audio/speech",
172
- {
173
- model: opts.model ?? "codai-tts",
174
- input: opts.input,
175
- voice: opts.voice ?? "alloy"
176
- },
177
- { signal: opts.signal }
178
- );
179
- return res.arrayBuffer();
180
996
  }
181
- };
182
- // ------------------------------------------------------------ internal
183
- chatBody(opts, stream) {
997
+ return {
998
+ [Symbol.asyncIterator]: () => events(),
999
+ text: () => ({ [Symbol.asyncIterator]: () => textOnly() }),
1000
+ final
1001
+ };
1002
+ }
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 };
1214
+ }
1215
+ }
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;
1271
+ /**
1272
+ * Mint a short-lived token (60–3600 s, default 600) bound to one surface.
1273
+ * Only a real `codai_` key may mint.
1274
+ */
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
1280
+ });
1281
+ return { token: raw.token, expiresAt: raw.expires_at, scope: raw.scope, raw };
1282
+ }
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 } : {}
1430
+ }
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 }
1459
+ );
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) {
184
1490
  return {
185
1491
  model: opts.model ?? "codai",
186
1492
  messages: opts.messages,
187
- stream,
188
1493
  ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
189
1494
  ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {},
190
1495
  ...opts.tools ? { tools: opts.tools } : {}
191
1496
  };
192
1497
  }
193
- chatHeaders(opts) {
194
- const h = {};
195
- const session = opts.sessionId ?? this.sessionId;
196
- if (session) h["x-codai-session-id"] = session;
197
- if (opts.agentMode) h["x-codai-mode"] = "agent";
198
- if (opts.compact) h["x-codai-compact"] = opts.compact;
199
- if (opts.bestOf !== void 0) h["x-codai-best-of"] = String(opts.bestOf);
200
- if (opts.agentId) h["x-codai-agent-id"] = opts.agentId;
201
- return h;
202
- }
203
- async request(path, body, init = {}) {
204
- const url = `${this.baseUrl}${path}`;
205
- let lastErr;
206
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
207
- try {
208
- const res = await fetch(url, {
209
- method: init.method ?? "POST",
210
- headers: {
211
- Authorization: `Bearer ${this.apiKey}`,
212
- ...body !== null ? { "Content-Type": "application/json" } : {},
213
- ...init.headers
214
- },
215
- ...body !== null ? { body: JSON.stringify(body) } : {},
216
- signal: init.signal ?? AbortSignal.timeout(this.timeoutMs)
217
- });
218
- if (res.ok) return res;
219
- if (RETRYABLE.has(res.status) && attempt < this.maxRetries) {
220
- await sleep(500 * 2 ** attempt + Math.random() * 250);
221
- continue;
222
- }
223
- let errBody = null;
224
- try {
225
- errBody = await res.json();
226
- } catch {
227
- }
228
- throw new CodaiError(`codai ${path} \u2192 ${res.status}`, res.status, errBody);
229
- } catch (err) {
230
- if (err instanceof CodaiError) throw err;
231
- lastErr = err;
232
- if (attempt < this.maxRetries) {
233
- await sleep(500 * 2 ** attempt);
234
- continue;
235
- }
236
- }
237
- }
238
- 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
+ };
239
1507
  }
240
1508
  };
241
- function sleep(ms) {
242
- 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];
243
1529
  }
244
1530
  var index_default = Codai;
245
1531
 
246
- 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 };
247
1533
  //# sourceMappingURL=index.js.map
248
1534
  //# sourceMappingURL=index.js.map