---
title: "Сесије"
description: "Покрените сесију, стримујте њене догађаје и обрадите грешке које може да избаци."
---

Сесија је једно извршавање агента, у сопственом изолованом окружењу, на сопственој git грани.
`beeuni.session(projectId, sessionId)` враћа објекат за управљање свиме што
сесија ради: покретањем, слањем упита, стримовањем догађаја и читањем статуса. Ова
страница обухвата тај објекат, потврду спремности, стримовање и типизиране
грешке које SDK позив може да изазове.

```ts
const s = beeuni.session(projectId, sessionId);
```

`s` је објекат за управљање свиме што сесија ради. ID сесије, ID изолованог
окружења и назив гране имају исту вредност. Погледајте
[Сесије](/docs/sr/work/sessions) за објашњење концепта.

<a id="session-lifecycle"></a>

## Животни циклус сесије

| Метод | Обавија | Шта ради |
|---|---|---|
| `s.get(opts?)` | `GET /projects/:pid/sessions/:sid` | Чита детаље сесије |
| `s.update(input)` | `PATCH …/sessions/:sid` | Преименује сесију, замењује њене ознаке или спаја кључеве метаподатака |
| `s.start(waitMs?)` | `POST …/sessions/:sid/start` | Обезбеђује и покреће извршно окружење |
| `s.restart()` | `POST …/sessions/:sid/restart` | Поново покреће извршно окружење; задржава исто изоловано окружење |
| `s.configState()` | `GET …/sessions/:sid/config` | Чита актуелност конфигурације (`stale`) и стање издања конфигурације (`release`) |
| `s.reloadConfig(input?)` | `POST …/sessions/:sid/reload` | Поново компајлира конфигурацију агента и замењује извршно окружење након валидације |
| `s.reloadConfigStream(input, onEvent)` | `POST …/sessions/:sid/reload-stream` | Обавља исто поновно учитавање и емитује фазе напретка које је сервер потврдио |
| `s.stop()` | `POST …/sessions/:sid/stop` | Зауставља извршно окружење; сесија остаје |
| `s.delete()` | `DELETE …/sessions/:sid` | Брише сесију |
| `s.setSharing(intent)` | `PUT …/sharing` | Подешава дељење и видљивост |
| `s.participants()` | `GET …/sessions/:sid/participants` | Чита ко може да отвори сесију и ко је послао сваки упит |
| `s.watch()` | `GET …/sessions/:sid/watch` | Чита да ли добијате [обавештења](/docs/sr/work/notifications) о овој сесији. Захтева `notification_center`; `403` `feature_disabled` док је искључено |
| `s.setWatch(watching)` | `PUT …/sessions/:sid/watch` | Укључује праћење (`true`) или утишава (`false`) обавештења сесије за вас. Захтева `notification_center`; `403` `feature_disabled` док је искључено |
| `s.presence({ tab_id, active, alerts? }, { keepalive? })` | `PUT …/sessions/:sid/presence` | Пријављује отворену картицу. `alerts: true` означава да картица приказује сопствена обавештења, па се push обавештење на телефону задржава. Када је `notification_center` искључено, свака присутна картица га задржава. `keepalive` омогућава да захтев траје и након затварања странице; подразумевано је укључено за пријаву одсуства |
| `s.cost()` | `GET /usage/session-costs/:sid` | Чита коначне трошкове LLM-а и рачунарских ресурса без покретања извршног окружења |
| `s.scope()` | `GET …/sessions/:sid/scope` | Чита сачувано сужење опсега тајни и материјализована везивања конекција |
| `s.rescope(input)` | `PUT …/sessions/:sid/scope` | Замењује наведена поља опсега за следећи упит или позив алата |
| `s.commit(input?)` | — | Прави комит са радом агента |
| `s.providerSecretPool.get(providerId)` | `GET …/provider-secret-pools/:providerId` | Чита ID-ове изабраних тајни налога |
| `s.providerSecretPool.list()` | `GET …/provider-secret-pools` | Наводи конфигурисане групе, укључујући празне, и пријављује `can_edit` |
| `s.providerSecretPool.set(providerId, secretIds)` | `PUT …/provider-secret-pools/:providerId` | Замењује избор; `null` враћа подразумевану вредност пројекта |

:::warning
  `s.delete()` брише сесију и њено извршно окружење. Ово се не може поништити. Да бисте паузирали сесију
  без њеног губитка, уместо тога позовите `s.stop()`.
:::

<a id="labels-and-metadata"></a>

## Ознаке и метаподаци

Свака сесија садржи `labels: string[]` и објекат `metadata`. Користите их да
класификујете сесије и одредите како их ваша апликација приказује.

- **Ознаке** су слободног облика: свакој се уклањају размаци на почетку и крају, има 1–64 знака, највише 20 по
  сесији, а дупликати се уклањају. Поклапање је тачно и разликује велика и мала слова.
- **Метаподаци** су JSON објекат слободног облика за ваше кључеве, са највише 16.384
  знака JSON-а по упису. Сервер ту чува и сопствене кључеве
  (`custom_name`, `source`, …); захтев који наводи кључ којим управља сервер
  одбија се са `400`. Додајте префикс својим кључевима (`app_…`) да бисте избегли сукоб са њима.

```ts
// Set both at create.
const project = beeuni.project(projectId)
const created = await project.sessions.create({
  initial_prompt: 'Triage ticket T-142',
  labels: ['support', 'tier:gold'],
  metadata: { app_ticket: 'T-142', app_priority: 2 },
})

// Update later. `labels` replaces the list; `metadata` merges keys and a
// `null` value removes that key.
const s = beeuni.session(projectId, created.session_id)
await s.update({ labels: ['support', 'resolved'], metadata: { app_priority: null } })

// List by label, server-side: a session must carry EVERY given label.
const { items } = await project.sessions.listPage({ labels: ['support', 'resolved'] })

// Render conditionally.
for (const session of items) {
  const badge = session.labels?.includes('tier:gold') ? 'Gold' : null
  const ticket = session.metadata.app_ticket as string | undefined
}
```

У React-у проследите `labels` у `useProjectSessions(projectId, { labels })`; сваки
скуп ознака добија сопствени унос у кешу. Агенти постављају иста поља из CLI-ја
(`beeuni sessions new --label … --meta k=v`, `beeuni sessions update --label …`)
и са хостованог MCP сервера (`start_session` `labels` / `metadata`,
`list_sessions` `labels`). Верзија која се може покренути је `packages/sdk/examples/12-session-labels.ts`.

Када су `pooled_provider_secrets` и `llm_gateway` омогућени, сесија може да изабере
више доступних тајни налога за једног добављача. Направите ресурс са опсегом пројекта путем
`beeuni.accounts.secretResources.create(accountId, { project_id: projectId, ...input })`.
Сваки члан пројекта подразумевано може да га користи. Ограничите га помоћу
`.setAccess(accountId, secretId, 'members', userIds)`. Читања враћају метаподатке и додељене дозволе, никада
вредност тајне. Након `429` добављача, пре почетка излаза, мрежни пролаз
покушава са другим изабраним кључем.

```ts
await s.providerSecretPool.set('anthropic', [primarySecretId, backupSecretId]);
const selected = await s.providerSecretPool.get('anthropic');
await s.providerSecretPool.set('anthropic', null); // inherit project default
```

Користите метод са стримовањем када позивалац приказује напредак поновног учитавања:

```ts
await s.reloadConfigStream({ refresh_repo: false }, (event) => {
  if (event.type === 'phase') console.log(event.phase);
});
```

Фазе су `checking-session`, `refreshing-workspace`, `compiling-config`,
`applying-config` и `confirming-config`. Сервер изоставља
`refreshing-workspace` када је `refresh_repo` `false`.
Фаза `applying-config` укључује замену извршног окружења коју је демон валидирао.

`s.configState()` враћа `release` када API подржава издања конфигурације.
Његов тип је `SessionConfigRelease`. Резултати поновног учитавања садрже исти објекат.

```ts
const { stale, release } = await s.configState();
if (release?.fallback_reason) {
  // The desired config failed on the box. An earlier config runs the session.
  console.warn(release.fallback_reason, release.running_release_id);
} else if (stale === true) {
  // Moves the session onto the base branch's current config AND
  // fast-forwards its /workspace checkout. `workspace_checkout` says what
  // happened to the checkout; `detail` words both halves.
  const result = await s.reloadConfig();
  console.log(result.workspace_checkout, result.detail);
}
```

`stale` има три стања. `null` значи да Beeuni није могао да утврди стање. Проверавајте `stale === true`,
никада `!stale`. Старији API изоставља `release`. Погледајте
[Усаглашавање конфигурације](/docs/sr/work/runtime#config-convergence).

`s.participants()` враћа `{ participants, total, multi_user }`. `participants`
наводи људе који сада могу да отворе сесију, прво власника, највише 20, сваки
`{ user_id, name, email, avatar_url, is_viewer }`; `total` је укупан број.
`multi_user` је `true` када две или више особа могу да отворе сесију. У React-у,
`useSessionParticipants(projectId, sessionId)` чита исте податке. Ко је написао
сваку поруку означено је са `s.messageAuthors()`.

Још три метода за читање употпуњују објекат за управљање:

- `s.previews()` — потенцијални портови за преглед које извршно окружење излаже.
- `s.publicShares.list()` / `.create(input)` / `.revoke(shareId)` — јавни линкови за дељење.
- `s.reminders.list()` / `.create(input)` / `.update(reminderId, { enabled })` / `.remove(reminderId)` — заказани упити за ову сесију. Погледајте [Подсетнике](/docs/sr/connect/reminders).
  `.create({ transcript: true })` прави линк за читање разговора (један активан линк по сесији; други позив враћа исти линк). Читајте га анонимно помоћу `getPublicSessionShareMessages(token)`; `findActiveTranscriptShare(shares)` бира активни из `.list()`.
- `s.audit(limit?)` — ревизорски траг радњи агента у сесији.
- `s.attachments.upload(file, options?)` — сачувајте приватну датотеку пре покретања. Највише 50 MiB. Враћа стабилан URL за део упита који садржи датотеку.
- `s.attachments.read(attachmentId, signal?)` — прочитајте сачуване бајтове као `Blob` без покретања изолованог окружења.
- `s.transcript(options?)` — сажет транскрипт на серверу (текст и позиви алата, без улаза или излаза алата). Ово ради са токеном сесије ограниченим на пројекат.
- `s.transcriptSync(options?)` — транскрипт који сервер чува на крају сваког потеза, у облику runtime омотача порука. Сваки део се чува 1:1 — текст, резоновање и сваки позив алата са улазом, излазом, грешком и метаподацима — осим бајтова прилога. Одговара док је изоловано окружење заустављено или се још буди, без његовог покретања. Проследите `limit` (највише 500) за најновији прозор, а `next_cursor` тог прозора као `before` да прочитате прозор старији од њега. Прозор се такође завршава на 1.000.000 знакова JSON-а: задржава најновије поруке, а `next_cursor` тада означава најстарију задржану поруку. Проследите `child` (`ses_…` id подагента, сесију коју отвара ред подагента) да прочитате сачувани транскрипт тог подагента; чува се сваки подагент који је разговор покренуо. `source` је `mirror`; `complete` је true само када прозор садржи све сачуване поруке и сачувана копија досеже прву поруку сесије. Сесија чије је извршно окружење прочитано у целости и није садржало поруке враћа `available: true`, `complete: true`, `total: 0` и ниједну поруку. Позив алата сачуван пре 2026-09-28, када је сачувана историја изостављала улазе и излазе алата, враћа се са улазом који потврђују његов наслов или метаподаци, а команда и са својим излазом.
- `s.voiceTranscript(options?)` — транскрипт активног гласовног позива ове сесије (изговорени потези и `ask_beeuni`/`run_command` позиви алата радника). Враћа празну листу када сесија нема активан позив, а не 404.

<a id="files-a-session-stored"></a>

### Датотеке које је сесија сачувала

`findSessionAttachments(messages)` наводи сваку сачувану датотеку на коју транскрипт
упућује, редом и сваку само једном: датотеку коју је корисник отпремио, `<file>` референцу у
упиту или копију датотеке коју је агент приказао, а коју чува сачувана историја. Чита
и активни и сачувани транскрипт и никада не баца изузетак због неисправног транскрипта.

```ts
import { findSessionAttachments } from '@beeuni/sdk'

const saved = await s.transcriptSync({ limit: 500 })
for (const file of findSessionAttachments(saved.messages)) {
  const blob = await s.attachments.read(file.attachment_id) // no sandbox needed
  console.log(file.filename, file.role, blob.size)
}
```

Сваки резултат је `{ url, attachment_id, filename, mime, message_id, role }`.
`role` је `user` за отпремљену датотеку и `assistant` за датотеку коју је агент приказао;
`mime` је null за потоњу, а бајтови носе сопствени тип.

<a id="who-wrote-each-message"></a>

## Ко је написао сваку поруку

Свака порука има аутора. Сервер га чита из акредитива којим је послат
упит: члан пројекта или агент друге сесије. Транскрипт извршног окружења
га не садржи.

```ts
const { authors, initial_author } = await beeuni.session(projectId, sessionId).messageAuthors()
// getSessionMessageAuthors(projectId, sessionId) is the same call as a function.

const author = authors[runtimeMessageId] // undefined when the sender is unknown
```

`authors` користи id поруке извршног окружења као кључ. `initial_author` је родитељска сесија за
прву поруку покренуте сесије. Вредност је `null` када сесија није имала први
упит. Сваки аутор је `SessionMessageAuthor`:

| `kind` | Поља |
| --- | --- |
| `'member'` | `user_id`, `name`, `email` (`string \| null`) |
| `'session'` | `session_id`, `name` (наслов сесије), `agent` (агент који покреће, када је именован) |

<a id="which-model-answered-a-turn"></a>

## Који модел је одговорио у потезу

Порука транскрипта наводи модел затражен за њен потез. Када [ланац резервних модела](/docs/sr/project/models)
пројекта одговори помоћу другог модела, само запис захтева на мрежном пролазу
садржи модел који је одговорио и оно што је Beeuni наплатио.

```ts
const { latest, billed_cost, turns } = await beeuni.session(projectId, sessionId).modelUsage()
// getSessionModelUsage(projectId, sessionId) is the same call as a function.

latest?.served_model   // 'glm-5.3-flash': the model that answered the newest request
latest?.fallback_from  // 'codex/gpt-6.1-sol' when a fallback model answered, else null
turns[userMessageId]   // { served_models, fallback_from, billed_cost } for one turn
```

`latest` је `null` пре првог одговора. `billed_cost` је износ у USD који је Beeuni наплатио за
позиве модела у сесији. `turns` користи као кључ id поруке извршног окружења за упит који је
започео сваки потез. `served_models` наводи моделе који су одговорили у том потезу, прво оне са
највише захтева. У React-у, `useSessionModelUsage(projectId, sessionId)` чита исти
запис; позовите његов `refetch()` када се потез заврши.

У React-у, `useSessionMessageAuthors(projectId, sessionId, messageCount)` чита
исте податке. Проследите број порука у транскрипту. Нова порука је једини догађај који
додаје аутора, па упит поново преузима податке када се број промени.

<a id="readiness-is-a-handshake"></a>

## Спремност захтева руковање

Пре него што пошаљете упит, позовите `ensureReady()`. Он обезбеђује изоловано окружење ако је
потребно, чека да се извршно окружење покрене и враћа разрешено извршно окружење.

```ts
const { runtimeSessionId, runtimeUrl, sandboxId } = await s.ensureReady();
```

При хладном покретању, `ensureReady()` може да баци `RUNTIME_UNAVAILABLE`. Погледајте
[Поновни покушај при хладном покретању](#retry-on-a-cold-boot) да сазнате шта то значи и како да
покушате поново.

`s.send()` и `s.abort()` позивају `ensureReady()` уместо вас.

<a id="seed-a-server-authorized-runtime-session-pin"></a>

### Поставите почетну, серверски одобрену везу са сесијом извршног окружења

React хост који се рендерује на серверу може да достави везу са сесијом извршног окружења која је већ трајно сачувана за
исту Beeuni сесију:

```tsx
const session = useSession(projectId, sessionId, {
  initialRuntimeSessionId: persistedSession.runtime_session_id ?? persistedSession.opencode_session_id,
});
```

Почетна вредност само хидрира кеширани садржај транскрипта док `/start` ради. Она
не замењује идентитет извршног окружења. Веза коју враћа `/start` је
меродавна и замењује застарелу почетну вредност.

`opencode_session_id` је застарели назив за `runtime_session_id`. Оба
носе исту вредност; читајте стари назив само као резервну опцију приказану изнад.

Не прихватајте ову вредност из непоузданог селектора закупца. Не правите
сесију извршног окружења у хосту. Beeuni прави и трајно чува коренску сесију.
Кешеви упита извршног окружења и контролери транскрипта ограничени су на извршно окружење
изолованог окружења, па једнаки id-ови сесија извршног окружења из различитих изолованих окружења не деле
ставке кеша.

Поновни покушаји порука на чекању задржавају URL изворног изолованог окружења када промените
сесију. Одговор на поруку `404` или `410` зауставља аутоматске поновне покушаје без
брисања транскрипта. Експлицитно усаглашавање може поново да покуша читање и
врати нормалну синхронизацију након успешног одговора.

<a id="send-a-prompt"></a>

## Пошаљите упит

```ts
s.setModel({ providerID, modelID }); // sticky for later send() calls
s.setAgent('build'); // sticky for later send() calls

await s.send('Refactor the auth module');
await s.send('One-off task', { model, agent }); // overrides for this call only
await s.abort(); // stop the current run
```

`send()` ставља упит у трајно пријемно сандуче упита сесије
(`POST .../prompts`), путању коју користе веб уређивач порука и CLI. Завршава се
када је упит сачуван, са редом пријемног сандучета (`prompt_id`, `state`,
`message_id`), а не када се потез заврши. Прочитајте одговор из `s.stream()` или
транскрипта.

За runtime REST сесије, први `send()` на објекту сесије чита модел и
агента трајно сачуване у Beeuni сесији. То спречава runtime сесију наслеђену из снимка
да поново користи застареле подразумеване вредности снимка.

Приоритет избора за упит је:

1. Позив `send()`.
2. Вредност `setModel()` или `setAgent()` објекта сесије.
3. Трајно сачувана подразумевана вредност Beeuni сесије.

`setModel` само бира шта ће следећи локални `send` затражити — никада не напушта
објекат сесије. Да бисте **трајно сачували** нови модел за активну сесију на серверу, користите
`changeModel`:

```ts
const { applied_live } = await s.changeModel('anthropic/claude-opus-4-8');
```

Промена ступа на снагу поновним покретањем извршног окружења, па се текући потез завршава.
`applied_live` је `true` када је активна сесија одмах применила промену, а `false` када се
примењује при следећем покретању. Само власник сесије или позивалац са дозволама менаџера
пројекта може да промени модел; сви остали добијају `403`.

`send()` разрешава извршно окружење, а затим му шаље упит. `abort()` зауставља текуће
извршавање без брисања сесије.

<a id="session-scope-and-cost"></a>

## Опсег сесије и трошак

Прочитајте сачувано сужење приступа тајнама и материјализоване везе конекција.
`secrets_allowlist: null` значи да важи одобрење приступа тајнама за агента:

```ts
const scope = await s.scope();
scope.connector_bindings_configured; // false = inherits the project defaults
```

`connector_bindings` је РАЗРЕШЕНА мапа, па изгледа исто за сесију
која је заменила своје конекторе и за ону која наслеђује подразумеване вредности пројекта. Прочитајте
`connector_bindings_configured` да бисте их разликовали пре приказивања опсега или
његовог слања назад.

Замените једно или оба поља опсега:

```ts
await s.rescope({
  secrets: ['DATABASE_URL'],
  connector_bindings: {
    github: { connection_id: connectionId },
  },
});
```

Свако наведено поље замењује целу своју претходну вредност. Изоставите поље да бисте
га оставили непромењеним. Промене конекција важе од следећег позива алата.
Уклањање тајне зауставља будућу испоруку, али не може да уклони већ откривену
вредност из контекста модела или постојећег процеса.

Обе осе имају изричит начин враћања на подразумевану вредност. То није исто што и
празна вредност:

```ts
await s.rescope({
  secrets: null, // inherit the agent's secret grant
  connector_bindings: null, // drop the override; inherit the project defaults
});
```

`secrets: []` и `connector_bindings: {}` су супротна упутства: изричито
„без тајни пројекта“ и „без иједног конектора“, укључујући подразумеване вредности
пројекта. Сесија која пошаље `{}` уместо намераваног `null` подразумевано одбија приступ сваком
алијасу који није навела.

Прочитајте обједињени запис о трошковима:

```ts
const cost = await s.cost();
```

Запис обједињује коначни трошак LLM-а, обрачунати трошак рачунарских ресурса изолованог окружења, употребу
модела, укупан број токена, трајање обраде и ставке у књизи евиденције. `s.cost()` не
позива `ensureReady()`.

<a id="runtime-status-and-previews"></a>

## Статус извршног окружења и прегледи

| Метод                      | Враћа                        | Употреба                                          |
| --------------------------- | ------------------------------ | -------------------------------------------- |
| `s.health(init?)`           | `{ status, ok, health, body }` | Проверите да ли је извршно окружење активно           |
| `s.previewUrl(port, path?)` | `string`                       | Преузмите прокси URL за порт који је агент изложио |
| `s.proxyUrl(url?)`          | `string \| undefined`          | Препишите localhost URL који је агент исписао    |

```ts
const { ok, health } = await s.health();
const url = s.previewUrl(3000, '/docs');
```

`s.health()` никада не баца изузетак. Позовите га било када, чак и пре него што сесија добије
извршно окружење. `s.previewUrl()` и `s.proxyUrl()` захтевају разрешено извршно окружење — прво позовите
`s.ensureReady()`, иначе бацају `SessionNotReadyError`. Погледајте
[Грешке спремности сесије](#session-readiness-errors).

<a id="what-the-runtime-supports"></a>

### Шта извршно окружење подржава

`health.capabilities` наводи функције сесије које извршно окружење пружа. Прочитајте једну
помоћу `runtimeSupports(capabilities, capability)` и сакријте контролу када
врати `false`. runtime сесија пружа свих десет; pi сесија пружа
само `session.subagents`, а за остале одговара са `501 feature_not_supported`
(`404` за конфигурациони документ). [Извршни оквири](/docs/sr/work/harnesses) садржи
пуну матрицу за runtime и pi.

| Могућност | Функција |
| --- | --- |
| `session.rewind` | Враћање на поруку и њено обнављање |
| `session.compact` | Сажимање разговора на захтев |
| `session.commands` | Команде пројекта са косом цртом |
| `session.fork` | Гранање сесије код поруке |
| `session.subagents` | Подређене сесије подагената |
| `session.mcp` | MCP сервери на које се извршно окружење само повезује |
| `session.todo` | Листа задатака извршног окружења |
| `session.shell` | Команда љуске покренута као потез |
| `session.attach` | Прикључивање сопственог терминалског клијента извршног оквира |
| `session.config` | Конфигурациони документ извршног окружења (`/global/config`); агенти и модели у сваком случају долазе из пројекта |

```ts
import { runtimeSupports } from '@beeuni/sdk';

const { health } = await s.health();
if (!runtimeSupports(health?.capabilities, 'session.compact')) hideCompact();
```

`runtimeSupports` враћа `true` пре него што стигне одговор о исправности, као и за
извршно окружење које не наводи могућност `session.*`: демон направљен пре увођења могућности
покреће runtime, који пружа све функције. У React-у користите
[`useRuntimeSupports`](/docs/sr/sdk/react).

Одговор `start` такође садржи листу: `SessionStartResult.capabilities` се
поставља помоћу `stage: 'ready'`. `useSession` је бележи, па провера могућности
ради исправно пре него што прва провера исправности врати одговор.

<a id="status-words"></a>

## Називи статуса

Сваки хост користи исти речник, па једно стање има исти назив на сваком екрану.

| Извоз                                     | Употреба                                                                                          |
| ------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `sessionListStatus(session, reviewCount?)` | Сесија на листи: `needs-you`, `starting`, `running`, `done`, `stopped`, `failed`, `legacy` |
| `SESSION_LIST_STATUS[status]`              | `{ label, tone, description }` за тај статус                                               |
| `sessionConnectionLabel(connection)`       | Кратка ознака рачунара сесије или `null` за `unknown` и `live`                  |
| `SESSION_NOTICE`                           | Реченица у уређивачу поруке док рачунар није спреман                                      |
| `turnRetryLabel(secondsLeft)`              | `Retrying in 5s`, `Retrying now` или `Waiting to retry` када одбројавање није познато           |

Тонови су `actionable`, `live`, `progress`, `muted` и `danger`. Зелена је само за
`live` и `actionable`; завршена сесија је `muted`. Статус који новији
API пошаље, а ова верзија не познаје, приказује се као `stopped`, никада као `failed`.

<a id="streaming"></a>

## Стримовање

Користите `s.stream()` за пријем догађаја уживо у скрипти или на серверу. У React
апликацији уместо тога користите [`useSession`](/docs/sr/sdk/react) — он управља целим
животним циклусом сесије уместо вас.

`s.stream()` је ток догађаја за компатибилност са runtime REST-ом. Beeuni API
га прослеђује из изолованог окружења. Не постоји засебна WebSocket крајња тачка.
Транспорт је `fetch` са телом одговора у току, које се чита преко
`ReadableStream` и `TextDecoderStream`. SDK управља поновним повезивањем,
повећавањем интервала између покушаја и провером сигнала активности.

Стримујте сесију:

1. Прво позовите `ensureReady()`. Извршно окружење не постоји док се изоловано окружење
   не покрене.
2. Отворите ток пре слања поруке да не бисте пропустили ране
   догађаје.
3. Пошаљите поруку.
4. Затворите ток када видите `session.idle`.

```ts
const session = beeuni.session(projectId, sessionId);
const { runtimeSessionId } = await session.ensureReady();

const stream = await session.stream({
  onEvent: (event) => {
    if (event.type === 'session.idle' && event.properties.sessionID === runtimeSessionId) {
      onTurnDone();
      stream.close();
    }
  },
});

await session.send('Refactor the auth module');
```

Ток испоручује догађаје у групама, у размацима од 16 ms. Узастопни
догађаји `message.part.delta` за једно поље дела у групи стижу као један догађај.
Његов `properties.delta` је њихов текст, спојен по редоследу. Његов `id` је идентификатор
последњег догађаја. Његов низ `coalesced` наводи догађаје које је заменио. Додајте `delta`
на крај поља дела, као за појединачну делту.

Стримовање захтева `fetch` са стварним телом `ReadableStream` и
`TextDecoderStream`. Прегледачи, Node 18 и новији, Bun и Cloudflare Workers
га подржавају.

Хост чији `fetch` не може да стримује обезбеђује сопствени транспорт. SDK га позива
једном по конекцији и сам задржава управљање поновним повезивањем, наставком преко `Last-Event-ID`, повећавањем интервала између покушаја и
сигналом активности:

```ts
import { configureBeeuni, notifyHostSignal, type RuntimeEventTransport } from '@beeuni/sdk';

const transport: RuntimeEventTransport = async function* ({ url, headers, signal }) {
  // Yield { data, id?, retry? } for each server-sent message of ONE connection.
  // Return when the server ends it. Throw (with `status` for a non-2xx answer)
  // when it fails.
};

configureBeeuni({ backendUrl, getToken, eventStreamTransport: transport });

// A host without `visibilitychange` / `online` events reports them itself.
notifyHostSignal('visible'); // the app is back in the foreground
notifyHostSignal('online');  // the network is back
notifyHostSignal('retry');   // a person asked to reconnect now
```

React Native подразумевано нема `fetch` са подршком за стримовање. Мобилна апликација Beeuni користи
ову тачку интеграције са `react-native-sse`. `createHttpSessionSyncController` остаје
доступан за хост коме треба само ограничена историја и синхронизација
статуса.

Контролер прво учитава најновијих 10 порука. `loadOlder()` прати
серверски курсор. `loadHttpSessionHistory()` прати сваки курсор за изричите
извозе.

<a id="event-types"></a>

### Типови догађаја

Сваки догађај има `type` и објекат `properties` који садржи његове податке, на
пример `event.properties.sessionID`.

| `type`                                          | Када се активира                                    |
| ----------------------------------------------- | --------------------------------------------------- |
| `message.updated` / `message.removed`           | Порука је промењена или обрисана.                    |
| `message.part.updated` / `message.part.removed` | Део (текст, позив алата, датотека) је проширен или уклоњен. |
| `session.status`                                | Промењено је стање заузетости сесије.                |
| `session.idle`                                  | Потез је завршен.                                   |
| `session.error`                                 | Потез није успео. Догађај садржи грешку.              |
| `question.asked`                                | Агент је затражио унос.                             |
| `question.replied` / `question.rejected`        | Стигао је одговор на питање.                        |

Претворите необрађене поруке и делове у излаз који се може приказати помоћу `classifyTurn`. Погледајте
[Референцу за SDK](/docs/sr/sdk/reference).

<a id="retry-on-a-cold-boot"></a>

## Поновни покушај при хладном покретању

`ensureReady()` проверава крајњу тачку сесије `/start` — сваки позив чека одговор до
30 s — док извршно окружење не достигне завршну фазу `ready`/`failed`/`stopped`
или не истекне рок (`readyTimeoutMs`, подразумевано ~180 s). У већ покренутој сесији
прва провера одмах враћа `ready`. При хладном покретању провере се настављају
док изоловано окружење пријављује `retriable: true`, па споро покретање само траје дуже,
уместо да изазове изузетак. Изузетак `ApiError` са `code:
'RUNTIME_UNAVAILABLE'` if the runtime is still not `ready` избацује се само када рок
истекне.

`ensureReady()` је идемпотентан, па истовремени позиви за исту сесију деле
један захтев `/start` уместо да шаљу више њих. Помоћна функција `retryUntilReady`
испод сада је опциона — `ensureReady()` већ интерно понавља покушаје — али је и даље
корисна ако желите дуже укупно време од подразумеваног `readyTimeoutMs`.

```ts
async function retryUntilReady<T>(ensure: () => Promise<T>): Promise<T> {
  const deadline = Date.now() + 300_000;
  for (;;) {
    try {
      return await ensure();
    } catch (error) {
      const provisioning = error instanceof ApiError && error.code === 'RUNTIME_UNAVAILABLE';
      if (!provisioning || Date.now() > deadline) throw error;
      await new Promise((r) => setTimeout(r, 3_000));
    }
  }
}
```

Погледајте [Класе грешака](#error-classes) за пуну структуру `ApiError`. У React-у,
[`useSession`](/docs/sr/sdk/react) понавља `/start` уместо вас, па вам овај
образац није потребан.

<a id="what-start-tells-you"></a>

## Шта вам говори `/start`

Сваки одговор `/start` описује **тај позив**, а не акумулирану
историју реда. То преносе четири поља.

| Поље | Значење |
| --- | --- |
| `observed_at` | Једно време за цео одговор. |
| `action` | Шта је сервер урадио: `inspected`, `checked_provider`, `resumed`, `provisioned`, `restored`, `reconciled`, `awaited_wake`, `cooling_down`. |
| `observation` | Шта је сервер проверио. `known: false` значи **није проверено у овом позиву** — никада „проверено и ништа није пронађено“. |
| `boot` | `phase` (`provisioning` / `resuming` / `booting` / `ready` / `parked` / `failed`), `since` и `actively_starting`. |

`boot.actively_starting` одговара на питање „да ли је операција добављача тренутно у току за ову
сесију?“. Садржај `starting` са `actively_starting: false` значи
да сервер чека да истекне пауза пре поновног покушаја, а не да се рачунар покреће.

```jsonc
{
  "stage": "starting",
  "retriable": true,
  "reason": "runtime_wake_cooldown",
  "observed_at": "2026-08-26T14:00:00.000Z",
  "action": "cooling_down",
  "boot": { "phase": "resuming", "since": "2026-08-26T13:58:00.000Z", "actively_starting": false },
  "observation": {
    "provider": { "known": false, "status": null, "checked_at": null },
    "runtime": { "known": false, "state": null, "boot_phase": null, "checked_at": null }
  },
  "failure": {
    "category": "sandbox-provider",
    "message": "The runtime did not start (attempt 2). Retrying automatically.",
    "retryable": true,
    "evidence": {
      "check": "provider_not_running",
      "observed_at": "2026-08-26T13:58:00.000Z",
      "error": null,
      "attempts": 2,
      "next_retry_at": "2026-08-26T14:03:00.000Z"
    }
  }
}
```

<a id="a-failed-start-is-retried-for-you"></a>

### Неуспело покретање се аутоматски понавља

Неуспело покретање бележи **паузу пре поновног покушаја**, а не трајну одлуку. Следећи
`/start` након паузе сам поново покушава буђење. Пауза се продужава
са узастопним неуспесима (2 min, 5 min, 10 min). После пет узастопних
неуспеха `/start` враћа `stage: "failed"` са бројем покушаја у
`failure.message`; та одлука истиче 30 минута након последњег неуспеха, а
`POST …/restart` је одмах поништава.

`retriable` се изводи при сваком позиву. Стање које сервер још може поново да покуша
никада не садржи `retriable: false`.

`failure.evidence` наводи проверу која је дала негативан резултат, када је извршена и
када сервер поново покушава. Сваки неуспех `/start` га садржи.

<a id="prompt-attachments"></a>

## Прилози упита

Отпремите локалне датотеке када их корисници додају у уређивач поруке. Приватне отпремљене датотеке припадају
пројекту и не захтевају покренуту сесију.

```tsx
import { usePromptAttachments } from '@beeuni/sdk/react';

const uploads = usePromptAttachments(projectId);
// File picker, paste, and drop handlers call uploads.add(file) or uploads.addMany(files).
// Render uploads.attachments. Refuse Send only while an attachment is `error` or `aborted`.

async function submit() {
  const ids = uploads.attachments.map((item) => item.id);
  uploads.submit(ids); // Send never waits: the composer clears and uploads continue.
  paintMessage(text, ids);
  try {
    const parts = await uploads.whenReady(ids);
    await beeuni.session(projectId, sessionId).prompts.create({
      clientMessageId,
      messageId,
      parts: [{ type: 'text', text }, ...parts],
    });
    uploads.forget(ids);
  } catch {
    uploads.reclaim(ids);
  }
}
```

`whenReady(ids, { signal })` се разрешава када све отпремљене датотеке буду спремне, редоследом `ids`.
Одбија се када отпремање не успе, буде прекинуто или уклоњено, или када `signal` прекине операцију.
`submit(ids)` предаје ставке том слању: оне напуштају `attachments`, престају
да се рачунају у ограничења и настављају да се отпремају након демонтирања уређивача поруке.
Позовите `forget(ids)` након успешног POST захтева или `reclaim(ids)` да их вратите у
уређивач поруке након неуспелог слања.
Разрешени део датотеке садржи `attachment_id`, `filename` и `mime`.
Не садржи URL нити бајтове датотеке. Сервер повезује и материјализује отпремљену датотеку.
Свака датотека стиже на рачунар сесије, укључујући слике и PDF-ове: пре
извршавања упита, сервер је записује у `/workspace/uploads/.beeuni-inbox/`, а
упит се на њу позива преко путање. Сам упит не преноси бајтове датотеке. Када је
чување историје укључено, сервер чува и копију за транскрипт. Неуспело копирање
никада не спречава да датотека стигне на рачунар. Датотека коју сервер не може да запише задржава
упит, који се поново покушава.
Користите ове делове при креирању сесије платформе, покретању већ припремљене сесије или на рутама
пријемног сандучета упита. Не прослеђујте приватне идентификаторе директно извршном окружењу runtime `sendParts`.
Стари делови датотека платформе са `url` и даље раде.

Свака ставка има стабилан `id`, оригиналну референцу `file`, `receivedBytes` и
статус: `pending`, `uploading`, `processing`, `ready`, `error` или `aborted`.
Послати бајтови одређују напредак. Контролер објављује снимак напретка само
при промени целог процента, највише десет пута у секунди по отпремању. `processing` може
остати на 100% док сервер не заврши отпремање. Ограничења су 50 MiB по
датотеци, 100 MiB по поруци и 20 датотека. Датотеке не смеју бити празне. Подразумевано се две датотеке
отпремају истовремено.

`retry(id)` наставља са истим идентификатором. После `attachment_size_mismatch` или
`attachment_failed` сервер не задржава употребљив идентификатор, па `retry(id)` поново отпрема
File као нов прилог. Истекло отпремање не може поново да се покуша: `retry(id)`
избацује изузетак, а грешка ставке садржи код `attachment_expired`. `abort(id)` отказује
незавршени рад.
`remove(id)` уклања ставку, прекида њено отпремање и одмах се разрешава.
Серверски DELETE се извршава по принципу најбољег напора и никада не одбија; отпремљена датотека коју не може да обрише
истиче након 24 сата.
`forget(ids)` ослобађа ставке без брисања серверских објеката.
Хук прекида наведени рад при демонтирању или промени пројекта и брише своје отпремљене датотеке
по принципу најбољег напора: ниједно слање их не задржава, а нацрти не чувају идентификаторе. Предата отпремања
се настављају.

Избори постоје само у меморији. Никада не чувајте File објекте, blob URL-ове, потписане
URL-ове или идентификаторе отпремања у нацрту. Напуштене отпремљене датотеке истичу након 24 сата.

Окружења без радног оквира користе `beeuni.project(projectId).attachments.createController()`
са истим методама. Претплатите се преко `subscribe(listener)` и читајте
`getSnapshot()`. Позовите `dispose()` када се уређивач поруке затвори.
Директни позиваоци могу да користе `attachments.upload(file, { signal, onProgress, onUpload,
resume })` and `attachments.delete(attachmentId)`.
Задржите метаподатке `onUpload` за ручно настављање са истим ID-јем.

Сервер бира транспорт у пољу идентификатора `upload`:

- `kind: 'direct'` је подразумевано. SDK шаље целу датотеку у једном `PUT` на
  `upload.url` са `upload.headers` и без заглавља Authorization. Уз
  `XMLHttpRequest` (прегледачи, React Native), напредак приказује послате бајтове. Уз
  `fetch`, напредак приказује 0, а затим пуну величину. Истекли URL или онај који
  Storage одбије са 400, 401 или 403 поново се потписује једном за исти
  `attachment_id`. `409` из Storage значи да је претходни покушај сачувао датотеку.
- `kind: 'chunked'` шаље узастопне аутентификоване `PUT` захтеве од `upload.chunk_size`
  бајтова. SDK прихвата сваки позитиван `chunk_size`. Овај режим бира само окружење чији edge
  одбацује велика тела захтева.

Завршетак проверава сачувану величину и SHA-256. Ако завршетак одговори са `409
attachment_not_uploaded`, `onUpload` пријављује директну референцу са
`received_bytes: 0`, па `retry(id)` или наставак поново шаље датотеку.
Покретање, отпремање и завршетак понављају покушаје при истеку времена, мрежним грешкама, 429 и
5xx, уз експоненцијално повећање паузе са насумичним одступањем. Временски оквир је 60 секунди од првог
неуспеха, па се и дуго отпремање које касно не успе поново покушава. Завршетак такође понавља покушаје за
`attachment_processing`, у оквиру пет минута. Покретање никада не понавља покушај за 402
(`BillingError`: налог не може да се покрене) или 429 `attachment_budget_exceeded` (40
незавршених отпремања или 500 MiB непослатих отпремања по кориснику; неискоришћена отпремања
истичу у року од 24 сата). Сервер одговара на један захтев за завршетак или га одбија у року од 105
секунди. Сваки захтев за завршетак има рок од 120 секунди. Прекиди које иницира позивалац никада се не понављају.

Референца послатог прилога ослобађа се 1 сат након испоруке његовог упита,
као и када се избрише његова сесија или пројекат. Следећи циклус одржавања затим
уклања датотеку и њен `attachment_id` више не може да се пошаље.

<a id="files"></a>

## Датотеке

`s.files` чита и пише у изолованом окружењу сесије: `list`, `read`, `readBlob`,
`status`, `findFiles`, `findText`, `upload`, `create`, `copy`, `remove`,
`mkdir`, `rename`. Сваки позив прво разрешава извршно окружење и увек је усмерен на
сопствено изоловано окружење ове сесије. Погледајте [SDK референцу](/docs/sr/sdk/reference) за
пун списак метода.

<a id="session-verbs"></a>

## Операције сесије

Референца за вас чита из извршног окружења и шаље му одговоре. Свака операција прво припрема
сесију и усмерена је на сопствено изоловано окружење ове сесије, без обзира на то који га harness покреће.

```ts
// A page of the transcript, oldest first: { info, parts } messages (beeuni.transcript.v1).
const { messages, hasMore } = await s.messages({ limit: 50 });
// What the agent waits on.
const { statuses, permissions, questions } = await s.pending();
await s.answerPermission(permissions[0].id, 'once'); // 'once' | 'always' | 'reject'
await s.answerQuestion(questions[0].id, [['Yes']]); // null dismisses the question
// Summarize the conversation. Needs the `session.compact` capability (see health()).
await s.compact();
```

`s.runtime`, клијент за компатибилност са сировим REST-ом, застарео је. Користите горенаведене
операције са `send`, `abort`, `rewind` и `stream`.

<a id="warm-a-project-session"></a>

## Унапред припремите сесију пројекта

Позовите `ensureWarm()` када је одредишној страници пројекта потребно једно спремно извршно окружење пре
првог упита.

```ts
const project = beeuni.project(projectId);
const warm = await project.sessions.ensureWarm();

// An ORDINARY session. Prompt it like any other.
await beeuni.session(projectId, warm.session.session_id).send("Build me a widget");
```

`ensureWarm()` прави или враћа једну неискоришћену сесију за тренутног корисника. То
је исто креирање које извршава `sessions.create()`, са подразумеваним подешавањима пројекта: истим
условом наплате и истим захтевима за конекторе. Једина
разлика је `metadata.warm`, који скрива сесију од
`sessions.list()` док не стигне њен први упит.

Сматрајте то спекулативном припремом. `409 WARM_SESSION_UNAVAILABLE` значи да пројекат
тренутно не може да се припреми — пређите на `sessions.create()`, који пријављује
стварни разлог.

Унапред припремљена сесија користи DEFAULT агента и изоловано окружење пројекта. Ако корисник
изабере друго, напустите је и позовите `sessions.create()`: неискоришћена унапред припремљена
сесија је скривена и аутоматски се уклања.

:::warning
`claimWarm()` је застарео. Унапред припремљена сесија је обична сесија, па нема
шта да се преузме — отворите је и пошаљите јој упит. Позив и даље ради за
кориснике везане за старији облик и биће уклоњен у следећој главној верзији.
:::

<a id="handling-errors"></a>

## Обрада грешака

Сваки позив преко `createBeeuni` одбија се типизираном поткласом `Error`,
никада обичним објектом. Ухватите грешку, проверите `instanceof` и гранајте логику према
`.status` или `.code`.

```ts
import { ApiError, BillingError } from '@beeuni/sdk';

try {
  await beeuni.project(projectId).sessions.create();
} catch (err) {
  if (err instanceof BillingError) {
    // 402 — out of credits or over a plan limit
  } else if (err instanceof ApiError) {
    // any other failed request — err.status, err.code, err.detail
  } else {
    throw err;
  }
}
```

<a id="error-classes"></a>

### Класе грешака

| Класа                  | Наслеђује    | Када се баца                                                                 | Кључна поља                                                           |
| ---------------------- | ---------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `ApiError`             | `Error`    | Подразумевана за сваки неуспешан захтев: лош статус, мрежни неуспех, истек времена или прекид | `status`, `code`, `detail`, `response`, `url`, `endpoint`, `timeout` |
| `AuthError`            | `ApiError` | `getToken` је вратио `null`. Beeuni није послао захтев                      | `code` је увек `'NO_SESSION'`                                      |
| `BillingError`         | `ApiError` | HTTP `402`. Једина класа грешака наплате                                       | `status` (`402`), `code`, `detail.message`                           |
| `RequestTooLargeError` | `ApiError` | HTTP `431`. Обично превише датотека у једном захтеву                              | `detail.suggestion`                                                  |
| `SessionNotReadyError` | `Error`    | Приступник извршном окружењу покренут је пре `ensureReady()`                                  | `name` је `'SessionNotReadyError'`                                   |

`ApiError.name` је подразумевано `'ApiError'`. Два случаја то мењају:

- `name: 'AbortError'`, `code: 'ABORTED'` — захтев је отказан, на пример због навигације. То није неуспех. Занемарите га.
- `code: 'TIMEOUT'` — истекао је сопствени рок захтева. `url`, `endpoint` и `timeout` показују шта је прекорачило рок.

За сваки други неуспех, `status` садржи HTTP статусни код. `code` долази из `error_code` позадинског система или користи статус као стринг. `message` је сопствено набројиво својство на `ApiError`, па се задржава при `JSON.stringify` и распакивању објекта.

Beeuni још једном шаље захтев који добије `401` са свежим токеном из `getToken()`, када се тај токен разликује од послатог. Захтев чије је тело једнократни ток не шаље се поново.

Beeuni понавља неке захтеве пре него што ваш код види грешку. Ако захтев `GET` или `HEAD` врати `502`, `503` или `504`, Beeuni га понавља до 2 пута, са паузом од 250ms, а затим 500ms. Привремени транспортни неуспех за `GET` или `HEAD` — мрежна грешка, не статусни код — понавља се на исти начин. Успешан поновни покушај никада не стиже до `onError`. Beeuni никада не понавља захтеве `POST`, `PUT`, `PATCH` или `DELETE`, нити одговор `500`.

Beeuni баца `AuthError` на клијенту, пре слања захтева, када `getToken()` врати `null`. `AuthError` наслеђује `ApiError`, па `err instanceof ApiError` и даље одговара. Проверите `err instanceof AuthError` или `err.code === 'NO_SESSION'` да бисте „није пријављен“ обрадили као засебан случај у односу на неуспех позадинског система.

Beeuni баца `BillingError` за сваки HTTP `402` одговор: потрошени кредити, прекорачено ограничење пакета или други услов наплате. `detail.message` садржи разлог из позадинског система, а `code` садржи његов машински код (на пример `app_account_unfunded`). `BillingError` наслеђује `ApiError`, па прво проверите `BillingError` када обрађујете оба.

Beeuni баца `RequestTooLargeError` за HTTP `431`. То обично значи да је захтев садржао превише датотека. `detail.suggestion` садржи савет спреман за приказ кориснику.

<a id="session-readiness-errors"></a>

### Грешке спремности сесије

Две грешке значе да изоловано окружење сесије још није спремно. Обрадите сваку другачије.

`SessionNotReadyError` синхроно баца грешку када позовете приступник за runtime — `session.previewUrl()` или `session.proxyUrl()` — пре него што овај handle сесије разреши своје sandbox окружење. Handle сесије разрешава само сопствено sandbox окружење; никада се не враћа на sandbox окружење друге сесије.

```ts
import { SessionNotReadyError } from '@beeuni/sdk';

const s = beeuni.session(projectId, sessionId);
try {
  const url = s.previewUrl(3000); // throws: not resolved yet
} catch (err) {
  if (err instanceof SessionNotReadyError) {
    await s.ensureReady();
  }
}
```

Прво позовите `await session.ensureReady()` или позовите `send()`, који интерно припрема сесију. `session.health()` је једини приступник који никада не баца ову грешку, па можете да га испитујете пре покретања сесије.

`RUNTIME_UNAVAILABLE` је друга грешка — то значи да је сам `ensureReady()` истекао чекајући хладно покретање. Погледајте [Поновни покушај при хладном покретању](#retry-on-a-cold-boot) за цео образац. У React-у, `useSession` поново покушава уместо вас и излаже резултат преко вредности `phase`, уместо да баци грешку.

<a id="helpers"></a>

### Помоћне функције

| Помоћна функција                | Потпис                              | Шта ради                                                                                                      |
| -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `parseBillingError(error)`       | `(error) => Error`                  | Обмотава одговор `402` у `BillingError`. Остале грешке враћа непромењене                                  |
| `isBillingError(error)`          | `(error) => boolean`                | Враћа `error instanceof BillingError`                                                                       |
| `formatBillingErrorForUI(error)` | `(error) => BillingErrorUI \| null` | Враћа `null` за грешке које нису повезане са наплатом. У супротном враћа `{ alertTitle, alertSubtitle }` за модал за надоградњу |
| `isRuntimeNotReadyResponse(error)` | `(error) => boolean` | Тачно за `503` sandbox daemon-а док runtime сесије још не може да прими захтев. Захтев није прослеђен, па је поновни покушај безбедан. Исти резултат у runtime-у и pi-ју |
| `isSandboxNotReadyError(error)` | `(error) => boolean` | Тачно за оба претходна одговора и за sandbox који је заустављен, покреће се или је паузиран. Прикажите га као „буђење“ и наставите са испитивањем |
| `isRuntimeStartingError(error)` | `(error) => boolean` | Тачно за оба претходна одговора и за runtime URL који још није закачен. Error boundary поново покушава у том случају, уместо да прикаже пад |

```ts
import { formatBillingErrorForUI } from '@beeuni/sdk';

try {
  await beeuni.session(projectId, sessionId).start();
} catch (err) {
  const ui = formatBillingErrorForUI(err);
  if (ui) showUpgradeModal(ui.alertTitle, ui.alertSubtitle);
}
```

<a id="in-beeunisdkreact"></a>

### У `@beeuni/sdk/react`

`@beeuni/sdk/react` поново извози `BillingError`, `RequestTooLargeError`, `parseBillingError`, `isBillingError` и `formatBillingErrorForUI`. Не извози поново `ApiError` или `AuthError` — њих увезите из `@beeuni/sdk`.

`useSession` класификује сваки неуспех `send`, `answerQuestion`, `answerPermission` и `rejectQuestion` у један објекат `sendError`, па не морате ручно да пишете провере `instanceof`:

```ts
interface BeeuniSendError {
  kind: 'billing' | 'runtime-not-ready' | 'runtime-error';
  message: string;
  billing?: BillingError; // set when kind is 'billing'
  cause: unknown;
}
```

```tsx
const s = useSession(projectId, sessionId);

if (s.sendError?.kind === 'billing') {
  const ui = formatBillingErrorForUI(s.sendError.billing);
}
```

Погледајте [React hooks](/docs/sr/sdk/react) за остатак теме `useSession`.
