TypeScript
Pachetul codai-sdk pentru Node și runtime-uri edge — configurare, grupuri de resurse cu paritate completă OpenAPI, chat, streaming, agenți, sesiuni, erori și migrarea de la SDK-ul OpenAI.
codai-sdk este clientul oficial TypeScript. Zero dependențe (fetch de platformă), complet tipizat din OpenAPI-ul gateway-ului, doar ESM, Node 18+ și runtime-uri edge moderne. De la 0.3.0 fiecare operație a API-ului gateway are exact o metodă în SDK — un test de paritate din pachet eșuează când cele două diverg.
pnpm add codai-sdkConfigurare
import { Codai } from 'codai-sdk';
const codai = new Codai({
apiKey: process.env.CODAI_API_KEY!, // obligatoriu
baseUrl: 'https://ai.codai.ro', // implicit
sessionId: 'my-project', // opțional — activează memoria de sesiune + sticky routing
device: '<uuid>', // opțional — x-codai-device pentru sesiuni partajate / host-uri
client: 'my-app/1.2.0', // opțional — eticheta x-codai-client a suprafeței
timeoutMs: 120_000, // implicit
maxRetries: 2, // reîncercări pe 429 / 5xx cu backoff exponențial
});Proprietate
Tip
Grupuri de resurse
Fiecare operație a gateway-ului este accesibilă ca metodă pe client, grupată după tag. Metodele de top din 0.2.x (chat(), chatStream(), embeddings(), models(), feedback(), mintToken(), agents.run(), audio.*) funcționează în continuare; chat, embeddings, models și feedback sunt grupuri de resurse apelabile, așa că codai.chat({ messages }) și codai.chat.completions.create({ messages }) sunt același apel.
| Proprietate | Metode | Gateway |
|---|---|---|
chat.completions | create, stream | POST /v1/chat/completions |
messages | create, stream | POST /v1/messages — formatul Anthropic Messages |
responses | create, stream | POST /v1/responses — formatul OpenAI Responses |
embeddings | create | POST /v1/embeddings |
audio | transcribe, transcribeDetailed, speech, speechDetailed | /v1/audio/* |
tokens | create | POST /v1/tokens |
models | list | GET /v1/models |
health | get, ready, status | /health, /health/ready, /status |
agents | run; runs.create, runs.get, runs.steps, runs.stats, runs.cancel, runs.stream | /v1/agents/* |
tools | search, fetch | /v1/tools/* |
tasks | list, pending, stats, get, confirm | /v1/tasks/* |
sessions | create, list, get, update, delete, dispatch, stream; events.*, controls.*, lease.*, shares.* | /v1/sessions/* |
devices | list, update, delete, dispatchInbox | /v1/devices/* |
hosts | list, exec, postResult, stream | /v1/hosts/* |
orgs | create, list; members.list, members.add, members.remove | /v1/orgs/* |
account | get, update | /v1/account |
receipt | get | GET /v1/receipt |
feedback | submit | POST /v1/feedback |
phoneModels | list | GET /v1/phone/models |
Fiecare metodă primește un ultim argument opțional { ext, signal }. ext este un obiect tipizat CodaiRequestExtensions care acoperă toate header-ele de cerere X-Codai-* documentate (effort, thinking, thinkingBudget, cache, noTask, taskId, incognito, mode: 'agent', bestOf, compact, shareToken, …) — vezi header-e.
await codai.chat.completions.create(
{ messages, model: 'codai' },
{ ext: { effort: 'high', thinking: true, thinkingBudget: 8192 } },
);
// stream-urile SSE native codai sunt iteratoare async de frame-uri { event, data }
const { id } = await codai.agents.runs.create({ task: 'Rezumă README-ul repo-ului.' });
for await (const ev of codai.agents.runs.stream(id)) {
if (ev.event === 'done') console.log(ev.data.status, ev.data.result);
}
// sesiunile partajate au nevoie de un id de dispozitiv pe client
const session = await codai.sessions.create({ session_key: 'desktop-main' });
await codai.sessions.lease.acquire(session.id);
for await (const frame of codai.sessions.stream(session.id, { after: 0 })) {
// frame.event ∈ 'event' | 'control' | 'lease' | 'presence'
}Tipurile de cerere și răspuns sunt generate din documentul OpenAPI (src/generated/gateway.ts, pnpm gen) și re-exportate atât brut (paths, components, operations), cât și ca aliasuri prietenoase (ChatCompletionRequest, Task, Session, AccountView, …).
Chat
const res = await codai.chat({
messages: [{ role: 'user', content: 'Explică iteratorii async într-o linie.' }],
});
res.content; // textul asistentului (prima alegere)
res.toolCalls; // tool calls pe prima alegere (array gol dacă nu există)
res.routedTo; // modelul upstream care a servit — din x-codai-routed-to
res.requestId; // din x-codai-trace-id / x-request-id
res.eventId; // din x-codai-event-id — ținta exactă pentru feedback()
res.usage; // { promptTokens, completionTokens } | null
res.execVerify; // 'passed' | 'refined' | … pe codai-labs, altfel null
res.raw; // răspunsul complet de formă OpenAI
res.headers; // toate header-ele de răspuns x-codai-*ChatOptions:
Proprietate
Tip
Tool calls
Trimiți tool-uri OpenAI de tip funcție și citești res.raw.choices[0].message.tool_calls. SDK-ul nu execută niciodată tool-uri pentru tine.
const res = await codai.chat({
messages: [{ role: 'user', content: 'Vremea la Cluj?' }],
tools: [
{
type: 'function',
function: {
name: 'get_weather',
parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
},
},
],
});
const call = (res.raw.choices as any[])[0].message.tool_calls?.[0];
if (call) {
const result = await getWeather(JSON.parse(call.function.arguments).city);
const followUp = await codai.chat({
messages: [
{ role: 'user', content: 'Vremea la Cluj?' },
{ role: 'assistant', content: '', ...(res.raw.choices as any[])[0].message },
{ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) },
],
});
}Streaming
chatStream() returnează un obiect care este atât un iterabil async de delta-uri de text, cât și purtătorul unei promisiuni .final cu metadatele pe care le afli doar la sfârșit.
const stream = codai.chatStream({
messages: [{ role: 'user', content: 'Scrie un haiku despre TypeScript.' }],
});
for await (const delta of stream) {
process.stdout.write(delta);
}
const { content, requestId, usage, routedTo, toolCalls } = await stream.final;
if (requestId) await codai.feedback(requestId, 1);ChatStreamResult:
| Câmp | Semnificație |
|---|---|
content | Textul concatenat. |
toolCalls | Tool calls asamblate din delta-urile pe bucăți — id, function.name, function.arguments deja unite. |
requestId, routedTo, execVerify | La fel ca pe chat(). |
usage | Din chunk-ul final de usage al gateway-ului, cu cachedTokens când e raportat. |
finishReason | finish_reason al ultimului chunk cu conținut. |
headers | Header-ele răspunsului (x-codai-cost-estimate: pending pe stream-urile native). |
.final se respinge dacă stream-ul dă eroare; SDK-ul suprimă un avertisment de unhandled-rejection dacă nu-l aștepți niciodată. Doar frame-urile data: sunt parsate — comentariile keep-alive sunt ignorate. stream.chunks() produce obiectele brute chat.completion.chunk când ai nevoie de mai mult decât delta-uri de text.
Agent server-side
agents.run() rulează o buclă plan-and-execute pe gateway. Procesul tău rămâne subțire; planificarea, folosirea tool-urilor și iterarea se întâmplă server-side și se facturează pe cheia ta.
const run = await codai.agents.run({
task: 'Rezumă punctele cheie ale textului furnizat.',
context: '…input-ul tău…', // opțional, ≤ 256 000 caractere
system: 'Răspunde în română.', // opțional, ≤ 32 000 caractere
model: 'codai', // opțional
});
run.result; // string
run.model; // upstream-ul care a servit
run.eventId; // id-ul rândului de usage — îl trimiți la feedback()
run.usage; // obiect usage brut | nulltask este obligatoriu (≤ 64 000 caractere). Rulările de agent folosesc același plafon de task-uri ca chat-ul.
Rulările persistente stau sub agents.runs: create() returnează 202 cu id-ul rulării, get() o interoghează, steps() listează traseul de pași persistat, stats(days) agregă rulările tale, cancel() trece o rulare aflată în coadă sau în execuție pe cancelled, iar stream() este un iterator async de evenimente step / done / timeout.
Feedback
Voturile pe o cerere finalizată antrenează priorurile de rutare. rating este 1 sau -1; comment e opțional (≤ 500 caractere).
const res = await codai.chat({ messages: [{ role: 'user', content: 'salut' }] });
if (res.requestId) await codai.feedback(res.requestId, 1, 'exact ce trebuia');Forma apelabilă trimite requestId ca event_id. Ca să notezi după session_id sau după propriul client_request_id, folosești codai.feedback.submit({ session_id, rating }) — vezi header-e.
Embeddings
const { embeddings, raw } = await codai.embeddings({
input: ['salut', 'lume'],
model: 'codai-embed', // implicit
dimensions: 512, // opțional
});
embeddings[0]; // number[]Audio
import { readFile, writeFile } from 'node:fs/promises';
// Speech-to-text — încărcare multipart, maximum 25 MB. Returnează string-ul transcris.
const text = await codai.audio.transcribe({
file: await readFile('clip.webm'), // Blob | Uint8Array | ArrayBuffer
filename: 'clip.webm', // extensia determină detecția formatului
model: 'codai-transcribe', // implicit
});
// Text-to-speech — returnează byte-ii audio (mp3 implicit).
const audio = await codai.audio.speech({ input: 'Salut de la codai.', voice: 'alloy' });
await writeFile('salut.mp3', Buffer.from(audio));transcribe() este singura metodă care nu reîncearcă — un body multipart nu poate fi retrimis în siguranță. Trimite mai departe și language, prompt, responseFormat și temperature; transcribeDetailed() / speechDetailed() returnează pe lângă acestea body-ul brut, contentType și header-ele.
Tokeni efemeri
Pentru browsere și webview-uri care nu trebuie să dețină niciodată cheia ta:
const { token, expiresAt, scope } = await codai.mintToken('realtime', 600);
// dai `token` clientului; funcționează doar pe suprafața realtime, cel mult ttl secundeScopuri: 'realtime' | 'audio' | 'embeddings'. TTL 60–3 600 s. Vezi tokeni efemeri.
Modele
const ids = await codai.models(); // Array<{ id: string }> — forma din 0.2.x
const full = await codai.models.list(); // CoreModel[] cu obiectul de capabilități `codai`Returnează array-ul data din GET /v1/models — tot ce poate adresa cheia ta.
Erori
Fiecare răspuns non-2xx după reîncercări aruncă un CodaiError cu statusul HTTP, code-ul stabil al gateway-ului, requestId (x-codai-trace-id), retryAfter (secunde, pe 429) și body-ul parsat (anvelopa de eroare). Eșecurile de rețea după reîncercări aruncă cu status: 0.
import { Codai, CodaiError } from 'codai-sdk';
try {
await codai.chat({ messages: [{ role: 'user', content: 'salut' }] });
} catch (err) {
if (err instanceof CodaiError) {
console.error(err.status, err.code, err.requestId, err.message);
if (err.code === 'quota_exceeded') {
/* alocarea de task-uri a planului consumată — aștepți err.retryAfter secunde */
}
}
}Valorile stabile de code sunt listate la limite și prețuri.
Migrarea de la SDK-ul OpenAI
Payload-ul are formă OpenAI, așa că schimbarea e clientul, nu mesajele.
// înainte
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey, baseURL: 'https://ai.codai.ro/v1' });
const r = await openai.chat.completions.create({ model: 'codai', messages });
r.choices[0].message.content;
// după
import { Codai } from 'codai-sdk';
const codai = new Codai({ apiKey });
const res = await codai.chat({ messages });
res.content;Ce câștigi: routedTo, requestId și usage ca câmpuri de prim rang, .final pe stream-uri cu tool calls asamblate, acces dintr-o opțiune la sesiuni / mod agent / compactare / best-of și reîncercări cu backoff. Ce păstrezi: clientul openai funcționează în continuare pe https://ai.codai.ro/v1 dacă îl preferi — ambii pot împărți aceeași cheie și același id de sesiune.
Export implicit
Codai este și exportul implicit, așa că import Codai from 'codai-sdk' funcționează.