# TypeScript (/ro/docs/sdks/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](../api/gateway/index.mdx) are exact o metodă în SDK — un test de paritate din pachet eșuează când cele două diverg.

```bash
pnpm add codai-sdk
```

## Configurare [#configurare]

```ts
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
});
```

<TypeTable
  type={{
    apiKey: { type: 'string', description: 'Cheia ta codai_…. Aruncă sincron dacă e goală.', required: true },
    baseUrl: { type: 'string', description: 'Originea gateway-ului, fără slash final.', default: "'https://ai.codai.ro'" },
    sessionId: { type: 'string', description: 'Trimis ca X-Codai-Session-Id la fiecare apel. Se poate suprascrie per apel.' },
    device: { type: 'string', description: 'UUID-ul dispozitivului trimis ca X-Codai-Device — cerut de sessions.* și hosts.*.' },
    deviceName: { type: 'string', description: 'X-Codai-Device-Name, înregistrat la prima folosire.' },
    devicePlatform: { type: "'android' | 'ios' | 'web' | 'desktop' | 'cli' | 'agent'", description: 'X-Codai-Device-Platform.' },
    client: { type: 'string', description: 'Eticheta X-Codai-Client folosită la atribuirea task-urilor.' },
    defaults: { type: 'CodaiRequestExtensions', description: 'Orice alt header X-Codai-* aplicat la fiecare cerere; ext per apel câștigă.' },
    timeoutMs: { type: 'number', description: 'AbortSignal.timeout aplicat când nu trimiți propriul signal. Stream-urile nu sunt niciodată limitate în timp.', default: '120000' },
    maxRetries: { type: 'number', description: 'Reîncercări pe 429, 500, 502, 503, 504 și erori de rețea.', default: '2' },
    fetch: { type: 'typeof fetch', description: 'fetch injectabil pentru teste sau agenți personalizați.' },
  }}
/>

## Grupuri de resurse [#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`, `ground` | `/v1/tools/*` — [unelte web](../agents/tools.mdx#tool-uri-web-un-singur-contract-peste-tot) |
| `research` | `create`, `retrieve`, `wait`, `run` | `/v1/research` — [cercetare aprofundată](../api/gateway/research.mdx) |
| `vault` | `account`, `items`, `putItem`, `decide`, `catalog`, `use`, `httpCall`, …; `devices.challenge`, `devices.enroll`, `devices.list`, `devices.rename`, `devices.revoke`; seifurile organizației `members.list`, `members.candidates`, `members.add`, `members.update`, `members.remove`, `rotateKey`, `setQuorum`, `vote`; `simulatePolicy`, `receiptKeys` | `/v1/vault/*` — [seif](../api/gateway/vault.mdx) |
| `cards` | `weather`, `places`, `geocode`, `staticMap`, `fx`, `unfurl` | `/v1/cards/*` — [carduri](../agents/cards.mdx) |
| `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](../gateway/headers.mdx).

```ts
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 [#chat]

```ts
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`:

<TypeTable
  type={{
    messages: { type: 'ChatMessage[]', description: 'role: system | user | assistant | tool; content: string sau array de părți de conținut.', required: true },
    model: { type: 'string', description: 'Orice alias sau nume upstream.', default: "'codai'" },
    temperature: { type: 'number' },
    maxTokens: { type: 'number', description: 'Trimis ca max_tokens.' },
    tools: { type: 'object[]', description: 'Tool-uri OpenAI de tip funcție. Tu deții bucla de tool-uri.' },
    sessionId: { type: 'string', description: 'Suprascriere per apel a sessionId-ului clientului.' },
    agentMode: { type: 'boolean', description: 'X-Codai-Mode: agent — plan-and-execute (planul Starter sau mai sus).' },
    compact: { type: "'auto'", description: 'X-Codai-Compact: auto — compactare server-side a contextului.' },
    bestOf: { type: '0 | 3', description: 'X-Codai-Best-Of — 3 forțează best-of-N, 0 dezactivează.' },
    agentId: { type: 'string', description: 'X-Codai-Agent-Id — atribuire pentru flote de agenți.' },
    signal: { type: 'AbortSignal', description: 'Înlocuiește signal-ul de timeout implicit.' },
    ext: { type: 'CodaiRequestExtensions', description: 'Orice alt header de extensie X-Codai-* pentru acest apel.' },
  }}
/>

### Tool calls [#tool-calls]

Trimiți unelte OpenAI de tip funcție și citești `res.raw.choices[0].message.tool_calls`. SDK-ul nu execută niciodată unelte pentru tine.

```ts
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 [#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.

```ts
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 [#agent-server-side]

`agents.run()` rulează o buclă plan-and-execute **pe gateway**. Procesul tău rămâne subțire; planificarea, folosirea uneltelor și iterarea se întâmplă server-side și se facturează pe cheia ta.

```ts
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 | null
```

`task` este obligatoriu (≤ 64 000 caractere). Rulările de agent folosesc același plafon de sarcini 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 [#feedback]

Voturile pe o cerere finalizată antrenează priorurile de rutare. `rating` este `1` sau `-1`; `comment` e opțional (≤ 500 caractere).

```ts
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](../gateway/headers.mdx#identitatea-clientului).

## Embeddings [#embeddings]

```ts
const { embeddings, raw } = await codai.embeddings({
  input: ['salut', 'lume'],
  model: 'codai-embed', // implicit
  dimensions: 512,      // opțional
});
embeddings[0]; // number[]
```

## Audio [#audio]

```ts
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 [#tokeni-efemeri]

Pentru browsere și webview-uri care nu trebuie să dețină niciodată cheia ta:

```ts
const { token, expiresAt, scope } = await codai.mintToken('realtime', 600);
// dai `token` clientului; funcționează doar pe suprafața realtime, cel mult ttl secunde
```

Scopuri: `'realtime' | 'audio' | 'embeddings'`. TTL 60–3 600 s. Vezi [tokeni efemeri](../gateway/embeddings-audio.mdx#tokeni-efemeri).

## Modele [#modele]

```ts
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.

## Aplicații gazdă (`CodaiApps`) [#aplicații-gazdă-codaiapps]

Aplicațiile care revând codai propriilor utilizatori (de ex. studiai.ro) gestionează cheile prin API-ul mgmt (`https://api.codai.ro`) cu un **token de conexiune al aplicației** (`codai_app_…`, creat în `codai.ro/console/apps/<app>`), nu cu o cheie de utilizator. Din 0.29.0.

```ts
import { CodaiApps } from 'codai-sdk';

const apps = new CodaiApps({ appToken: process.env.CODAI_APP_TOKEN });
const minted = await apps.keys.mint({ email: 'ana@example.com', endUserExternalId: 'uid:gh1' });
await apps.users.setOverride('uid:gh1', { tasksPerDay: 200 });
await apps.subscriptions.sync({ email: 'ana@example.com', event: stripeEvent });
```

Metode: `keys.mint/list/update/revoke/usage/revokeAllFor`, `users.getOverride/setOverride/clearOverride`, `subscriptions.sync`, `validateKey` (secret separat `validateToken`). Anvelopa `{ ok, data }` e despachetată; erorile sunt `CodaiError` cu `code`-ul mgmt (`missing_scope:keys.mint`, `no_key_for_external_id`). Timeout 20 s, o reîncercare pe 429/5xx, `keys.mint` nu se reîncearcă niciodată.

## Erori [#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`.

```ts
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](../gateway/limits-pricing.mdx#contractul-de-erori).

## Migrarea de la SDK-ul OpenAI [#migrarea-de-la-sdk-ul-openai]

Payload-ul are formă OpenAI, așa că schimbarea e clientul, nu mesajele.

```ts
// î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 [#export-implicit]

`Codai` este și exportul implicit, așa că `import Codai from 'codai-sdk'` funcționează.
