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 +21 -0
- package/README.md +142 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +248 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
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
|
+
[](https://www.npmjs.com/package/codai-sdk)
|
|
4
|
+
[](./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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|