codai-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dragos Catalin Vladulescu (codai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # codai-sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/codai-sdk.svg)](https://www.npmjs.com/package/codai-sdk)
4
+ [![license](https://img.shields.io/npm/l/codai-sdk.svg)](./LICENSE)
5
+
6
+ Official TypeScript SDK for the [codai](https://codai.ro) AI gateway — a single
7
+ OpenAI-compatible endpoint with smart routing, sessions, server-side agents,
8
+ streaming, embeddings, audio, and feedback.
9
+
10
+ - **Zero dependencies** — uses the platform `fetch`.
11
+ - Works in **Node 18+** and modern edge runtimes.
12
+ - **OpenAI-compatible** chat surface with codai extensions.
13
+ - Fully typed.
14
+
15
+ ```bash
16
+ npm install codai-sdk
17
+ # or
18
+ pnpm add codai-sdk
19
+ ```
20
+
21
+ > You need a codai API key. Get one at **[codai.ro](https://codai.ro)**.
22
+
23
+ ## Quickstart
24
+
25
+ ```ts
26
+ import { Codai } from 'codai-sdk';
27
+
28
+ const codai = new Codai({ apiKey: process.env.CODAI_API_KEY! });
29
+
30
+ const res = await codai.chat({
31
+ messages: [{ role: 'user', content: 'Explain async iterators in one line.' }],
32
+ });
33
+
34
+ console.log(res.content);
35
+ console.log(res.routedTo); // which upstream model actually served
36
+ ```
37
+
38
+ ## Streaming
39
+
40
+ ```ts
41
+ for await (const delta of codai.chatStream({
42
+ messages: [{ role: 'user', content: 'Write a haiku about TypeScript.' }],
43
+ })) {
44
+ process.stdout.write(delta);
45
+ }
46
+ ```
47
+
48
+ ## Server-side agent
49
+
50
+ Run a plan-and-execute loop on the gateway — the heavy lifting (planning, tool
51
+ use, iteration) happens server-side; your client stays thin.
52
+
53
+ ```ts
54
+ const run = await codai.agents.run({
55
+ task: 'Summarize the key points of the provided text.',
56
+ context: '…your input…',
57
+ });
58
+
59
+ console.log(run.result);
60
+ ```
61
+
62
+ ## Feedback
63
+
64
+ ```ts
65
+ const res = await codai.chat({ messages: [{ role: 'user', content: 'hi' }] });
66
+ if (res.requestId) {
67
+ await codai.feedback(res.requestId, 1); // 1 = 👍, -1 = 👎
68
+ }
69
+ ```
70
+
71
+ ## Embeddings
72
+
73
+ ```ts
74
+ const { embeddings } = await codai.embeddings({ input: ['hello', 'world'] });
75
+ ```
76
+
77
+ ## Audio
78
+
79
+ ```ts
80
+ // Speech-to-text
81
+ const text = await codai.audio.transcribe({ file: audioBytes, filename: 'clip.webm' });
82
+
83
+ // Text-to-speech
84
+ const wav = await codai.audio.speech({ input: 'Hello from codai.' });
85
+ ```
86
+
87
+ ## List models
88
+
89
+ ```ts
90
+ const models = await codai.models();
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ ```ts
96
+ const codai = new Codai({
97
+ apiKey: process.env.CODAI_API_KEY!,
98
+ baseUrl: 'https://ai.codai.ro', // default
99
+ sessionId: 'my-project', // enables session memory + stickiness
100
+ timeoutMs: 120_000,
101
+ maxRetries: 2,
102
+ });
103
+ ```
104
+
105
+ ## codai extensions
106
+
107
+ The chat surface is OpenAI-compatible, with a few opt-in extensions:
108
+
109
+ | Option | Description |
110
+ | ----------------- | ----------------------------------------------------------------------- |
111
+ | `sessionId` | Stable conversation id — enables session memory and routing stickiness. |
112
+ | `agentMode` | Plan-and-execute agent mode (Pro+). |
113
+ | `compact: "auto"` | Server-side context compaction. |
114
+ | `bestOf` | Best-of-N sampling override (`0` disables, `3` forces). |
115
+
116
+ ## Migrating from the OpenAI SDK
117
+
118
+ The chat payload is OpenAI-shaped, so migration is mostly swapping the client:
119
+
120
+ ```ts
121
+ // before: openai.chat.completions.create({ model, messages })
122
+ // after:
123
+ const res = await codai.chat({ messages });
124
+ ```
125
+
126
+ ## Error handling
127
+
128
+ ```ts
129
+ import { Codai, CodaiError } from 'codai-sdk';
130
+
131
+ try {
132
+ await codai.chat({ messages: [{ role: 'user', content: 'hi' }] });
133
+ } catch (err) {
134
+ if (err instanceof CodaiError) {
135
+ console.error(err.status, err.message, err.body);
136
+ }
137
+ }
138
+ ```
139
+
140
+ ## License
141
+
142
+ [MIT](./LICENSE) © codai
@@ -0,0 +1,150 @@
1
+ /**
2
+ * codai-sdk — official TypeScript client for the codai AI gateway.
3
+ *
4
+ * Full-surface client:
5
+ * - chat(): OpenAI-compatible completions with codai extensions
6
+ * (session, agent mode, compaction, best-of)
7
+ * - chatStream(): SSE streaming with an async-iterator interface
8
+ * - agents.run(): server-side agent loop (POST /v1/agents/run)
9
+ * - feedback(): thumbs rating on a completed request
10
+ * - models(): list models available to the key
11
+ *
12
+ * Zero dependencies; works in Node 18+ (global fetch) and modern edge
13
+ * runtimes. Memory tools (memory_recall / memory_remember) and skills
14
+ * (use_skill) are GATEWAY-NATIVE — the model invokes them server-side;
15
+ * nothing to call from the SDK beyond enabling a session.
16
+ */
17
+ interface CodaiClientOptions {
18
+ apiKey: string;
19
+ /** Defaults to https://ai.codai.ro */
20
+ baseUrl?: string;
21
+ /** Stable conversation/session id — enables session memory + stickiness. */
22
+ sessionId?: string;
23
+ /** Default request timeout (ms). Default 120_000. */
24
+ timeoutMs?: number;
25
+ /** Retries on 429/5xx with exponential backoff. Default 2. */
26
+ maxRetries?: number;
27
+ }
28
+ interface ChatMessage {
29
+ role: 'system' | 'user' | 'assistant' | 'tool';
30
+ content: string | Array<Record<string, unknown>>;
31
+ tool_call_id?: string;
32
+ name?: string;
33
+ }
34
+ interface ChatOptions {
35
+ model?: string;
36
+ messages: ChatMessage[];
37
+ temperature?: number;
38
+ maxTokens?: number;
39
+ tools?: Array<Record<string, unknown>>;
40
+ /** codai extension: plan-and-execute agent mode (Pro+). */
41
+ agentMode?: boolean;
42
+ /** codai extension: server-side context compaction ('auto'). */
43
+ compact?: 'auto';
44
+ /** codai extension: best-of-N override (0 disables, 3 forces). */
45
+ bestOf?: 0 | 3;
46
+ /** Attribution id recorded on tool calls (agent fleets). */
47
+ agentId?: string;
48
+ /** Per-call session override. */
49
+ sessionId?: string;
50
+ signal?: AbortSignal;
51
+ }
52
+ interface ChatResult {
53
+ /** Assistant message content (first choice). */
54
+ content: string;
55
+ /** Raw OpenAI-shaped response. */
56
+ raw: Record<string, unknown>;
57
+ /** Request id — pass to feedback(). */
58
+ requestId: string | null;
59
+ /** Which upstream model actually served (x-codai-routed-to). */
60
+ routedTo: string | null;
61
+ /** Verification status for verified-execution responses ('passed'|'refined'|…), if present. */
62
+ execVerify: string | null;
63
+ usage: {
64
+ promptTokens: number;
65
+ completionTokens: number;
66
+ } | null;
67
+ }
68
+ interface AgentRunOptions {
69
+ task: string;
70
+ context?: string;
71
+ system?: string;
72
+ model?: string;
73
+ signal?: AbortSignal;
74
+ }
75
+ interface AgentRunResult {
76
+ result: string;
77
+ model: string;
78
+ eventId: string | null;
79
+ usage: Record<string, unknown> | null;
80
+ }
81
+ type EphemeralScope = 'realtime' | 'audio' | 'embeddings';
82
+ interface EphemeralToken {
83
+ token: string;
84
+ expiresAt: string;
85
+ scope: EphemeralScope;
86
+ }
87
+ interface TranscriptionOptions {
88
+ /** Audio file contents. */
89
+ file: Blob | Uint8Array | ArrayBuffer;
90
+ /** Filename hint (extension drives format detection). Default audio.webm */
91
+ filename?: string;
92
+ model?: string;
93
+ signal?: AbortSignal;
94
+ }
95
+ interface SpeechOptions {
96
+ input: string;
97
+ model?: string;
98
+ voice?: string;
99
+ signal?: AbortSignal;
100
+ }
101
+ interface EmbeddingsOptions {
102
+ input: string | string[];
103
+ model?: string;
104
+ dimensions?: number;
105
+ signal?: AbortSignal;
106
+ }
107
+ declare class CodaiError extends Error {
108
+ readonly status: number;
109
+ readonly body: unknown;
110
+ constructor(message: string, status: number, body: unknown);
111
+ }
112
+ declare class Codai {
113
+ private readonly apiKey;
114
+ private readonly baseUrl;
115
+ private readonly sessionId?;
116
+ private readonly timeoutMs;
117
+ private readonly maxRetries;
118
+ constructor(opts: CodaiClientOptions);
119
+ chat(opts: ChatOptions): Promise<ChatResult>;
120
+ /** Streaming chat: async-iterate text deltas; `final` resolves afterwards. */
121
+ chatStream(opts: ChatOptions): AsyncGenerator<string, void, undefined>;
122
+ readonly agents: {
123
+ run: (opts: AgentRunOptions) => Promise<AgentRunResult>;
124
+ };
125
+ /** Rate a completed request: +1 (helpful) or -1 (not helpful). */
126
+ feedback(requestId: string, rating: 1 | -1, comment?: string): Promise<void>;
127
+ models(): Promise<Array<{
128
+ id: string;
129
+ }>>;
130
+ /**
131
+ * Mint a short-lived scoped token (60–3600s) for browser/webview clients.
132
+ * The token authenticates only its scope's surface; chat is not mintable.
133
+ */
134
+ mintToken(scope: EphemeralScope, ttlSeconds?: number): Promise<EphemeralToken>;
135
+ embeddings(opts: EmbeddingsOptions): Promise<{
136
+ embeddings: number[][];
137
+ raw: unknown;
138
+ }>;
139
+ readonly audio: {
140
+ /** Speech-to-text (multipart upload, max 25 MB). Returns the transcript. */
141
+ transcribe: (opts: TranscriptionOptions) => Promise<string>;
142
+ /** Text-to-speech. Returns the audio bytes (default mp3). */
143
+ speech: (opts: SpeechOptions) => Promise<ArrayBuffer>;
144
+ };
145
+ private chatBody;
146
+ private chatHeaders;
147
+ private request;
148
+ }
149
+
150
+ export { type AgentRunOptions, type AgentRunResult, type ChatMessage, type ChatOptions, type ChatResult, Codai, type CodaiClientOptions, CodaiError, type EmbeddingsOptions, type EphemeralScope, type EphemeralToken, type SpeechOptions, type TranscriptionOptions, Codai as default };
package/dist/index.js ADDED
@@ -0,0 +1,248 @@
1
+ // src/index.ts
2
+ var CodaiError = class extends Error {
3
+ constructor(message, status, body) {
4
+ super(message);
5
+ this.status = status;
6
+ this.body = body;
7
+ this.name = "CodaiError";
8
+ }
9
+ status;
10
+ body;
11
+ };
12
+ var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
13
+ var Codai = class {
14
+ apiKey;
15
+ baseUrl;
16
+ sessionId;
17
+ timeoutMs;
18
+ maxRetries;
19
+ constructor(opts) {
20
+ if (!opts.apiKey) throw new Error("Codai: apiKey is required");
21
+ 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)
32
+ });
33
+ const raw = await res.json();
34
+ const choices = raw.choices;
35
+ const usage = raw.usage;
36
+ return {
37
+ content: String(choices?.[0]?.message?.content ?? ""),
38
+ raw,
39
+ requestId: res.headers.get("x-request-id"),
40
+ routedTo: res.headers.get("x-codai-routed-to"),
41
+ 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
46
+ };
47
+ }
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), {
51
+ signal: opts.signal,
52
+ headers: this.chatHeaders(opts)
53
+ });
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 {
74
+ }
75
+ }
76
+ }
77
+ }
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
+ };
99
+ }
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 ?? [];
115
+ }
116
+ // -------------------------------------------------------------- tokens
117
+ /**
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.
120
+ */
121
+ async mintToken(scope, ttlSeconds) {
122
+ const res = await this.request("/v1/tokens", {
123
+ scope,
124
+ ...ttlSeconds !== void 0 ? { ttl_seconds: ttlSeconds } : {}
125
+ });
126
+ const body = await res.json();
127
+ return { token: body.token, expiresAt: body.expires_at, scope: body.scope };
128
+ }
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 } : {}
137
+ },
138
+ { signal: opts.signal }
139
+ );
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 {
162
+ }
163
+ throw new CodaiError(`codai /v1/audio/transcriptions \u2192 ${res.status}`, res.status, errBody);
164
+ }
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
+ }
181
+ };
182
+ // ------------------------------------------------------------ internal
183
+ chatBody(opts, stream) {
184
+ return {
185
+ model: opts.model ?? "codai",
186
+ messages: opts.messages,
187
+ stream,
188
+ ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
189
+ ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {},
190
+ ...opts.tools ? { tools: opts.tools } : {}
191
+ };
192
+ }
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);
239
+ }
240
+ };
241
+ function sleep(ms) {
242
+ return new Promise((r) => setTimeout(r, ms));
243
+ }
244
+ var index_default = Codai;
245
+
246
+ export { Codai, CodaiError, index_default as default };
247
+ //# sourceMappingURL=index.js.map
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * codai-sdk — official TypeScript client for the codai AI gateway.\n *\n * Full-surface client:\n * - chat(): OpenAI-compatible completions with codai extensions\n * (session, agent mode, compaction, best-of)\n * - chatStream(): SSE streaming with an async-iterator interface\n * - agents.run(): server-side agent loop (POST /v1/agents/run)\n * - feedback(): thumbs rating on a completed request\n * - models(): list models available to the key\n *\n * Zero dependencies; works in Node 18+ (global fetch) and modern edge\n * runtimes. Memory tools (memory_recall / memory_remember) and skills\n * (use_skill) are GATEWAY-NATIVE — the model invokes them server-side;\n * nothing to call from the SDK beyond enabling a session.\n */\n\nexport interface CodaiClientOptions {\n apiKey: string;\n /** Defaults to https://ai.codai.ro */\n baseUrl?: string;\n /** Stable conversation/session id — enables session memory + stickiness. */\n sessionId?: string;\n /** Default request timeout (ms). Default 120_000. */\n timeoutMs?: number;\n /** Retries on 429/5xx with exponential backoff. Default 2. */\n maxRetries?: number;\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string | Array<Record<string, unknown>>;\n tool_call_id?: string;\n name?: string;\n}\n\nexport interface ChatOptions {\n model?: string;\n messages: ChatMessage[];\n temperature?: number;\n maxTokens?: number;\n tools?: Array<Record<string, unknown>>;\n /** codai extension: plan-and-execute agent mode (Pro+). */\n agentMode?: boolean;\n /** codai extension: server-side context compaction ('auto'). */\n compact?: 'auto';\n /** codai extension: best-of-N override (0 disables, 3 forces). */\n bestOf?: 0 | 3;\n /** Attribution id recorded on tool calls (agent fleets). */\n agentId?: string;\n /** Per-call session override. */\n sessionId?: string;\n signal?: AbortSignal;\n}\n\nexport interface ChatResult {\n /** Assistant message content (first choice). */\n content: string;\n /** Raw OpenAI-shaped response. */\n raw: Record<string, unknown>;\n /** Request id — pass to feedback(). */\n requestId: string | null;\n /** Which upstream model actually served (x-codai-routed-to). */\n routedTo: string | null;\n /** Verification status for verified-execution responses ('passed'|'refined'|…), if present. */\n execVerify: string | null;\n usage: { promptTokens: number; completionTokens: number } | null;\n}\n\nexport interface AgentRunOptions {\n task: string;\n context?: string;\n system?: string;\n model?: string;\n signal?: AbortSignal;\n}\n\nexport interface AgentRunResult {\n result: string;\n model: string;\n eventId: string | null;\n usage: Record<string, unknown> | null;\n}\n\nexport type EphemeralScope = 'realtime' | 'audio' | 'embeddings';\n\nexport interface EphemeralToken {\n token: string;\n expiresAt: string;\n scope: EphemeralScope;\n}\n\nexport interface TranscriptionOptions {\n /** Audio file contents. */\n file: Blob | Uint8Array | ArrayBuffer;\n /** Filename hint (extension drives format detection). Default audio.webm */\n filename?: string;\n model?: string;\n signal?: AbortSignal;\n}\n\nexport interface SpeechOptions {\n input: string;\n model?: string;\n voice?: string;\n signal?: AbortSignal;\n}\n\nexport interface EmbeddingsOptions {\n input: string | string[];\n model?: string;\n dimensions?: number;\n signal?: AbortSignal;\n}\n\nexport class CodaiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = 'CodaiError';\n }\n}\n\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\n\nexport class Codai {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly sessionId?: string;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n\n constructor(opts: CodaiClientOptions) {\n if (!opts.apiKey) throw new Error('Codai: apiKey is required');\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? 'https://ai.codai.ro').replace(/\\/$/, '');\n this.sessionId = opts.sessionId;\n this.timeoutMs = opts.timeoutMs ?? 120_000;\n this.maxRetries = opts.maxRetries ?? 2;\n }\n\n // ---------------------------------------------------------------- chat\n async chat(opts: ChatOptions): Promise<ChatResult> {\n const res = await this.request('/v1/chat/completions', this.chatBody(opts, false), {\n signal: opts.signal,\n headers: this.chatHeaders(opts),\n });\n const raw = (await res.json()) as Record<string, unknown>;\n const choices = raw.choices as Array<{ message?: { content?: string } }> | undefined;\n const usage = raw.usage as { prompt_tokens?: number; completion_tokens?: number } | undefined;\n return {\n content: String(choices?.[0]?.message?.content ?? ''),\n raw,\n requestId: res.headers.get('x-request-id'),\n routedTo: res.headers.get('x-codai-routed-to'),\n execVerify: res.headers.get('x-codai-exec-verify'),\n usage: usage\n ? {\n promptTokens: usage.prompt_tokens ?? 0,\n completionTokens: usage.completion_tokens ?? 0,\n }\n : null,\n };\n }\n\n /** Streaming chat: async-iterate text deltas; `final` resolves afterwards. */\n async *chatStream(opts: ChatOptions): AsyncGenerator<string, void, undefined> {\n const res = await this.request('/v1/chat/completions', this.chatBody(opts, true), {\n signal: opts.signal,\n headers: this.chatHeaders(opts),\n });\n const reader = res.body?.getReader();\n if (!reader) throw new CodaiError('no response body', res.status, null);\n const decoder = new TextDecoder();\n let buffer = '';\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n // SSE frames are \\n\\n separated; keep the trailing partial in buffer.\n const frames = buffer.split('\\n\\n');\n buffer = frames.pop() ?? '';\n for (const frame of frames) {\n for (const line of frame.split('\\n')) {\n if (!line.startsWith('data:')) continue;\n const data = line.slice(5).trim();\n if (data === '[DONE]') return;\n try {\n const chunk = JSON.parse(data) as {\n choices?: Array<{ delta?: { content?: string } }>;\n };\n const delta = chunk.choices?.[0]?.delta?.content;\n if (delta) yield delta;\n } catch {\n /* keep-alives / comments */\n }\n }\n }\n }\n }\n\n // -------------------------------------------------------------- agents\n readonly agents = {\n run: async (opts: AgentRunOptions): Promise<AgentRunResult> => {\n const res = await this.request(\n '/v1/agents/run',\n {\n task: opts.task,\n ...(opts.context ? { context: opts.context } : {}),\n ...(opts.system ? { system: opts.system } : {}),\n ...(opts.model ? { model: opts.model } : {}),\n },\n { signal: opts.signal },\n );\n const body = (await res.json()) as Record<string, unknown>;\n return {\n result: String(body.result ?? ''),\n model: String(body.model ?? ''),\n eventId: (body.event_id as string) ?? null,\n usage: (body.usage as Record<string, unknown>) ?? null,\n };\n },\n };\n\n // ------------------------------------------------------------ feedback\n /** Rate a completed request: +1 (helpful) or -1 (not helpful). */\n async feedback(requestId: string, rating: 1 | -1, comment?: string): Promise<void> {\n await this.request('/v1/feedback', {\n event_id: requestId,\n rating,\n ...(comment ? { comment } : {}),\n });\n }\n\n // -------------------------------------------------------------- models\n async models(): Promise<Array<{ id: string }>> {\n const res = await this.request('/v1/models', null, { method: 'GET' });\n const body = (await res.json()) as { data?: Array<{ id: string }> };\n return body.data ?? [];\n }\n\n // -------------------------------------------------------------- tokens\n /**\n * Mint a short-lived scoped token (60–3600s) for browser/webview clients.\n * The token authenticates only its scope's surface; chat is not mintable.\n */\n async mintToken(scope: EphemeralScope, ttlSeconds?: number): Promise<EphemeralToken> {\n const res = await this.request('/v1/tokens', {\n scope,\n ...(ttlSeconds !== undefined ? { ttl_seconds: ttlSeconds } : {}),\n });\n const body = (await res.json()) as {\n token: string;\n expires_at: string;\n scope: EphemeralScope;\n };\n return { token: body.token, expiresAt: body.expires_at, scope: body.scope };\n }\n\n // ---------------------------------------------------------- embeddings\n async embeddings(opts: EmbeddingsOptions): Promise<{ embeddings: number[][]; raw: unknown }> {\n const res = await this.request(\n '/v1/embeddings',\n {\n model: opts.model ?? 'codai-embed',\n input: opts.input,\n ...(opts.dimensions !== undefined ? { dimensions: opts.dimensions } : {}),\n },\n { signal: opts.signal },\n );\n const body = (await res.json()) as { data?: Array<{ embedding: number[] }> };\n return { embeddings: (body.data ?? []).map((d) => d.embedding), raw: body };\n }\n\n // --------------------------------------------------------------- audio\n readonly audio = {\n /** Speech-to-text (multipart upload, max 25 MB). Returns the transcript. */\n transcribe: async (opts: TranscriptionOptions): Promise<string> => {\n const form = new FormData();\n const blob = opts.file instanceof Blob ? opts.file : new Blob([opts.file as BlobPart]);\n form.append('file', blob, opts.filename ?? 'audio.webm');\n form.append('model', opts.model ?? 'codai-transcribe');\n const res = await fetch(`${this.baseUrl}/v1/audio/transcriptions`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.apiKey}` },\n body: form,\n signal: opts.signal ?? AbortSignal.timeout(this.timeoutMs),\n });\n if (!res.ok) {\n let errBody: unknown = null;\n try {\n errBody = await res.json();\n } catch {\n /* non-JSON */\n }\n throw new CodaiError(`codai /v1/audio/transcriptions → ${res.status}`, res.status, errBody);\n }\n const body = (await res.json()) as { text?: string };\n return body.text ?? '';\n },\n\n /** Text-to-speech. Returns the audio bytes (default mp3). */\n speech: async (opts: SpeechOptions): Promise<ArrayBuffer> => {\n const res = await this.request(\n '/v1/audio/speech',\n {\n model: opts.model ?? 'codai-tts',\n input: opts.input,\n voice: opts.voice ?? 'alloy',\n },\n { signal: opts.signal },\n );\n return res.arrayBuffer();\n },\n };\n\n // ------------------------------------------------------------ internal\n private chatBody(opts: ChatOptions, stream: boolean): Record<string, unknown> {\n return {\n model: opts.model ?? 'codai',\n messages: opts.messages,\n stream,\n ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),\n ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}),\n ...(opts.tools ? { tools: opts.tools } : {}),\n };\n }\n\n private chatHeaders(opts: ChatOptions): Record<string, string> {\n const h: Record<string, string> = {};\n const session = opts.sessionId ?? this.sessionId;\n if (session) h['x-codai-session-id'] = session;\n if (opts.agentMode) h['x-codai-mode'] = 'agent';\n if (opts.compact) h['x-codai-compact'] = opts.compact;\n if (opts.bestOf !== undefined) h['x-codai-best-of'] = String(opts.bestOf);\n if (opts.agentId) h['x-codai-agent-id'] = opts.agentId;\n return h;\n }\n\n private async request(\n path: string,\n body: unknown,\n init: { signal?: AbortSignal; headers?: Record<string, string>; method?: string } = {},\n ): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n let lastErr: unknown;\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const res = await fetch(url, {\n method: init.method ?? 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n ...(body !== null ? { 'Content-Type': 'application/json' } : {}),\n ...init.headers,\n },\n ...(body !== null ? { body: JSON.stringify(body) } : {}),\n signal: init.signal ?? AbortSignal.timeout(this.timeoutMs),\n });\n if (res.ok) return res;\n if (RETRYABLE.has(res.status) && attempt < this.maxRetries) {\n await sleep(500 * 2 ** attempt + Math.random() * 250);\n continue;\n }\n let errBody: unknown = null;\n try {\n errBody = await res.json();\n } catch {\n /* non-JSON error */\n }\n throw new CodaiError(`codai ${path} → ${res.status}`, res.status, errBody);\n } catch (err) {\n if (err instanceof CodaiError) throw err;\n lastErr = err;\n if (attempt < this.maxRetries) {\n await sleep(500 * 2 ** attempt);\n continue;\n }\n }\n }\n throw new CodaiError(`codai ${path} failed after retries: ${String(lastErr)}`, 0, null);\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nexport default Codai;\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "codai-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official TypeScript SDK for the codai AI gateway - chat, streaming, agents, feedback, models.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "publishConfig": {
26
+ "provenance": true
27
+ },
28
+ "keywords": [
29
+ "codai",
30
+ "ai",
31
+ "llm",
32
+ "openai-compatible",
33
+ "gateway",
34
+ "agents"
35
+ ],
36
+ "homepage": "https://codai.ro",
37
+ "bugs": "https://github.com/dragoscv/codai-sdk/issues",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/dragoscv/codai-sdk.git"
41
+ },
42
+ "scripts": {
43
+ "lint": "echo 'lint runs in the codai monorepo'",
44
+ "typecheck": "tsc --noEmit",
45
+ "build": "tsup",
46
+ "test": "vitest run",
47
+ "clean": "rm -rf .turbo dist"
48
+ },
49
+ "devDependencies": {
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.9.3",
52
+ "vitest": "^4.1.7"
53
+ }
54
+ }