# Unelte și sub-agenți (/ro/docs/agents/tools)

Uneltele built-in pe care le poate folosi un run de agent, contractul de unelte web comun tuturor agenților codai, endpoint-urile independente /v1/tools și /v1/research și cum spawn_agent distribuie munca (fan-out) către sub-agenți tipizați.

În interiorul unui run de agent — sau al unui chat completion cu `x-codai-server-tools: 1` — modelul primește un set de unelte executate de gateway. Le apelează ca pe orice unealtă de tip funcție; gateway-ul rulează în paralel fiecare apel dintr-o tură și trimite rezultatele înapoi. Nu vezi niciodată traficul de unelte decât dacă citești pașii run-ului.

## Unelte built-in [#unelte-built-in]

| Tool | Ce face | Disponibil când |
| --- | --- | --- |
| `http_fetch` | Descarcă un URL server-side. | Nivelul de unelte al cheii tale permite HTTP (`l1_http` sau mai sus). |
| `json_query` | Interoghează un document JSON. | La fel ca `http_fetch`. |
| `exec_python` | Rulează Python într-un sandbox — capabilitatea „code interpreter”. | Sandbox-ul e activat pe gateway. |
| `read_artifact` | Citește înapoi un rezultat mare de tool pe care gateway-ul l-a vărsat în storage în loc să îl includă inline. | Vărsarea (spilling) e activată și cel puțin un tool e activ. |
| `memory_recall`, `memory_remember` | Memorie pe termen lung, la nivelul contului tău. | Planul tău include memorie persistentă. |
| `use_skill` | Încarcă unul dintre skill-urile tale în buclă. | Cel puțin un skill vizibil pentru cheie. |
| `web_search`, `web_fetch` | Căutare web verificată și citire de pagini, cu același contract ca pe toate celelalte suprafețe ([mai jos](#unelte-web-un-singur-contract-peste-tot)). | Căutarea web e configurată pe gateway. |
| Unelte MCP | Unelte expuse de serverele MCP atașate cheii tale. | Nivelul de unelte nu e `none`. |
| `delegate_to_model` | Router-ul intern codai pentru un singur completion, peste modelele pe care le poți apela. | Întotdeauna, când sunt permise ≥ 2 modele. |
| `spawn_agent` | Un sub-agent tipizat cu propriul context și propria buclă de unelte. | Vezi mai jos. |

Nivelul de unelte este o proprietate a cheii tale. `none` dezactivează orice unealtă executată; `l1_http` permite citiri din rețea; `l3_sandbox` (sandbox în cloud: shell, Python, fișiere) și `l4_browser` deblochează rolurile mai grele de sub-agent.

## `spawn_agent` [#spawn_agent]

Când uneltele server-side sunt pornite, modelul primește și `spawn_agent`. Spre deosebire de `delegate_to_model` (un singur completion), un copil pornit rulează **propria buclă** cu propriul context. Modelul alege un **rol**; gateway-ul alege tier-ul, nivelul de unelte și bugetul de pași pentru el.

<TypeTable
  type={{
    [`task`]: { type: 'string', description: 'Ce trebuie să facă copilul.', required: true },
    role: { type: "'explore' | 'research' | 'code' | 'verify' | 'summarize'", description: 'Selectează tier-ul, nivelul de tool-uri și bugetul de pași.', default: "'explore'" },
    system: { type: 'string', description: 'Prompt de sistem opțional pentru copil.' },
    max_steps: { type: 'integer', description: '1 – 12, limitat la maximul propriu al rolului.' },
  }}
/>

| Rol | Tier | Pași max | Nivel max de unelte | Scop |
| --- | --- | --- | --- | --- |
| `explore` | small | 6 | `l1_http` | Fapte, doar citire. Implicit. |
| `research` | small | 8 | `l1_http` | `web_search` + `http_fetch`, returnează un rezumat cu surse. |
| `code` | large | 12 | nivelul apelantului (până la `l4_browser`) | Implementează și verifică; returnează diff sau output. |
| `verify` | small | 6 | `l3_sandbox` | Rulează verificări, raportează PASS/FAIL per verificare. |
| `summarize` | small | 3 | `l1_http` | Digeră o singură sursă. |

Semantică:

- **Paralel prin design.** Fiecare apel `spawn_agent` dintr-o tură de asistent rulează concurent, și concurent cu celelalte apeluri de tool ale turei. Fan-out-ul e plafonat per tură (implicit 4); apelurile peste plafon primesc `{"error":"delegation_fanout_capped"}` ca rezultat de tool.
- **Copiii nu pornesc niciodată alți copii.** Un copil are `spawn_agent` eliminat, rulează pe alias-ul părintelui cu `max_tokens: 4096` și se facturează ca o continuare a turei părintelui — nu pornește o [sarcină](./tasks-and-spend.mdx) nouă.
- **Plafon de cost.** Cheltuiala copiilor contează în plafonul de cost al cererii (implicit 5 USD); odată atins, spawn-urile ulterioare returnează `{"error":"request_cost_ceiling"}`.
- **Forma rezultatului.** Răspunsul copilului ajunge la părinte ca un mesaj `tool` obișnuit, prefixat cu `SUB-AGENT[<role>] RESULT:`.
- **Cadre de progres.** Pe un chat completion în stream, gateway-ul emite cadre vendor în delta SSE ca un UI să poată afișa carduri live de sub-agent: `{"codai":{"event":"subagent_started","id","role","task"}}` și `{"codai":{"event":"subagent_done","id","role","ok","preview"}}`. Le ignori dacă vrei doar text.
- Argumente invalide → `{"error":"invalid_arguments"}`; un copil care eșuează → `{"error":"sub_agent_failed"}`.

`spawn_agent` e injectat doar când *cererea* nu poartă unelte client-side proprii — dacă trimiți `tools`, tu deții bucla și gateway-ul stă deoparte.

<a id="tool-uri-web-un-singur-contract-peste-tot"></a>

## Unelte web: un singur contract peste tot [#unelte-web-un-singur-contract-peste-tot]

codai are trei unelte web, iar fiecare suprafață de agent le expune exact pe acestea — aceleași nume, aceleași scheme JSON, aceleași limite, aceleași endpoint-uri de gateway și aceeași redare a textului pentru model. Sursa unică este `packages/agent-core/spec/tools-web.json`; un test de paritate oprește build-ul când o suprafață se abate de la ea.

| Tool | Endpoint | Ce face |
| --- | --- | --- |
| `web_search` | `POST /v1/tools/search` | Căutare verificată: până la 10 rezultate, redate ca linii `[S1]`..`[Sn]` pe care modelul le citează. |
| `web_fetch` | `POST /v1/tools/fetch` | O pagină ca markdown curat, opțional restrânsă cu `focus`; textul e încadrat ca conținut nesigur. |
| `deep_research` | `POST /v1/research`, `GET /v1/research/{id}` | Mai multe căutări + citiri de pagini → un raport în care fiecare afirmație citează `[Sn]`, sau un obiect JSON cu `output_schema`. |

| Suprafață | Cum apare |
| --- | --- |
| [Extensia VS Code](../apps/vscode.mdx) (0.7.0+) | `codai_web_search`, `codai_web_fetch`, `codai_deep_research` în modul agent Copilot |
| [Desktop](../apps/desktop.mdx) și aplicația Android | Unelte de agent built-in cu aceleași nume |
| CLI headless (`codaid run`) | Aceleași unelte în agentul găzduit |
| [MCP găzduit](../gateway/clients.mdx#server-mcp) (`https://ai.codai.ro/mcp`) | `web_search`, `web_fetch`, `deep_research` |
| [SDK-uri](../sdks/index.mdx) | `tools.search`, `tools.fetch`, `research.create` / `retrieve` / `wait` / `run` |
| Bucla de agent pe server (această pagină) | `web_search`, `web_fetch` (fără cercetare imbricată) |

Reguli pe care le respectă fiecare suprafață:

- Gateway-ul este singurul backend de căutare pentru un utilizator autentificat. Un răspuns 4xx (refuz de regulă, argumente greșite, limită de rată) este final: modelul vede o linie `ERROR <code>: <message>` și nimic nu reîncearcă printr-un alt furnizor sau printr-un LLM.
- `lang` și `country` rămân nesetate dacă utilizatorul nu a cerut o anumită piață; gateway-ul le deduce din interogare.
- `published_at` nu este niciodată inventat: când o pagină nu are o dată de publicare cunoscută, rezultatul nu afișează niciuna. `crawled_at` este momentul în care codai a descărcat-o.
- Rândurile fundamentate de Google, când rulează fallback-ul automat, formează un bloc separat redat ca `[G1]`..`[Gn]`, cu linia de atribuire.

### Prețuri [#prețuri]

Uneltele web nu sunt apeluri de model, deci nu scriu rând de usage; fiecare apel este contorizat separat și debitat în EUR din portofel:

| Apel | Preț |
| --- | --- |
| `web_search`, `depth: fast` | 3 $ la 1.000 |
| `web_search`, `depth: deep` | 8 $ la 1.000 |
| `web_fetch` | 0,50 $ la 1.000 |
| Job `deep_research` | 0,20 $ per job, plus tokenii de model ai jobului |

Primele **1.000 de apeluri pe utilizator pe lună calendaristică UTC sunt gratuite**, iar apelurile făcute **în cadrul unei ture de agent codai** (orice suprafață unde `codai` este modelul) nu se taxează niciodată. Erorile și răspunsurile din cache nu se taxează niciodată.

## Search și fetch independente — `/v1/tools` [#search-și-fetch-independente--v1tools]

Aceleași motoare de search și fetch sunt expuse ca endpoint-uri simple, ca un agent client-side să le poată folosi fără să treacă printr-un model. Ambele au nevoie doar de cheia ta `codai_`, contează în limitele tale de cereri per minut / per oră (header-ele `X-RateLimit-*`, `429 rate_limit_exceeded`) și **nu** creează un rând de usage — sunt contorizate ca apeluri de unelte web (vezi [prețurile](#prețuri)).

### `POST /v1/tools/search` [#post-v1toolssearch]

<TypeTable
  type={{
    query: { type: 'string', description: 'Fără spații la capete, 1 – 400 de caractere.', required: true },
    max_results: { type: 'integer', description: '1 – 10.', default: '5' },
    include_domains: { type: 'string[]', description: 'Până la 10 host-uri; doar rezultate de pe ele (inclusiv subdomenii, cu prefix de cale opțional, de ex. `github.com/vercel`).' },
    exclude_domains: { type: 'string[]', description: 'Până la 10 host-uri care nu sunt returnate niciodată.' },
    freshness: { type: "'day' | 'week' | 'month' | 'year'", description: 'Doar pagini publicate în fereastra dată.' },
    published_after: { type: 'string', description: '`YYYY-MM-DD`: doar pagini publicate la sau după această dată.' },
    published_before: { type: 'string', description: '`YYYY-MM-DD`: doar pagini publicate înainte de această dată.' },
    depth: { type: "'fast' | 'deep'", description: '`deep`: mai multe surse și reformulări ale interogării, până la ~12 s.', default: "'fast'" },
    content_chars: { type: 'integer', description: '0 – 2 000: returnează și un extras verificat de această lungime per rezultat, în `content`.', default: '0' },
    country: { type: 'string', description: 'Cod de piață din două litere; dedus din interogare când lipsește.' },
    lang: { type: 'string', description: 'Etichetă de limbă `xx` sau `xx-yy`; dedusă din interogare când lipsește.' },
    mode: { type: "'codai' | 'google'", description: '`google` returnează doar Grounding with Google Search, ca set separat de rezultate.', default: "'codai'" },
    fallback: { type: "'auto' | 'off'", description: '`auto` adaugă un bloc separat fundamentat de Google când rezultatele codai sunt puține.', default: "'auto'" },
  }}
/>

Orice filtru de dată (`freshness`, `published_after`, `published_before`) elimină paginile a căror dată de publicare nu se cunoaște — data descărcării nu este folosită niciodată în locul ei. Merg și operatorii de interogare: `site:`, `-site:`, `filetype:`, `intitle:`, `inurl:`, `"frază exactă"`, `-termen`, `A OR B`, `after:`, `before:`, `lang:xx`.

<Tabs items={['curl', 'TypeScript', 'Python']}>
<Tab value="curl">

```bash
curl https://ai.codai.ro/v1/tools/search \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": "react compiler release", "include_domains": ["react.dev"], "published_after": "2026-01-01" }'
```

</Tab>
<Tab value="TypeScript">

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

const codai = new Codai({ apiKey: process.env.CODAI_API_KEY! });
const { results } = await codai.tools.search({
  query: 'react compiler release',
  include_domains: ['react.dev'],
  published_after: '2026-01-01',
});
```

</Tab>
<Tab value="Python">

```python
from codai import Codai

codai = Codai(api_key=os.environ["CODAI_API_KEY"])
res = codai.tools.search({"query": "react compiler release", "include_domains": ["react.dev"], "published_after": "2026-01-01"})
for hit in res["results"]:
    print(hit["title"], hit["url"], hit["published_at"])
```

</Tab>
</Tabs>

Răspuns — fiecare rezultat este **verificat**: snippet-ul a fost găsit pe pagina live înainte să fie returnat.

```json
{
  "results": [
    {
      "title": "React Compiler v1.0",
      "url": "https://react.dev/blog/…",
      "snippet": "React Compiler is now stable …",
      "source": "docs",
      "verified": true,
      "published_at": "2026-02-10T00:00:00Z",
      "crawled_at": "2026-10-18T07:12:44Z"
    }
  ],
  "provider": "codai",
  "took_ms": 412,
  "cached": false,
  "coverage": 1,
  "sources_tried": ["docs", "index"],
  "degraded": [],
  "intent": "docs"
}
```

`published_at` este `null` când pagina nu are o dată de publicare cunoscută; `page_age` este un alias vechi, păstrat doar când data e cunoscută. `degraded` listează doar eșecurile reale ale surselor; codurile informative ale pipeline-ului ajung în `notes`. Dacă serviciul de căutare nu e accesibil, gateway-ul răspunde `503 search_unavailable` (`type: server_error`) — reîncearcă mai târziu; nu s-a taxat nimic.

### `POST /v1/tools/fetch` [#post-v1toolsfetch]

<TypeTable
  type={{
    url: { type: 'string', description: 'Până la 2 048 de caractere. `https://` este adăugat în față când schema lipsește.', required: true },
    max_chars: { type: 'integer', description: 'Plafon pe conținutul returnat, 200 – 40 000.', default: '8000' },
    focus: { type: 'string', description: 'Până la 300 de caractere. Când e setat, sunt returnate doar paragrafele care i se potrivesc cel mai bine.' },
    format: { type: "'markdown' | 'text'", default: "'markdown'" },
  }}
/>

```bash
curl https://ai.codai.ro/v1/tools/fetch \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://nodejs.org/en/blog/release/v24.0.0", "max_chars": 4000, "focus": "ESM loader hooks" }'
```

```json
{
  "url": "https://nodejs.org/en/blog/release/v24.0.0",
  "final_url": "https://nodejs.org/en/blog/release/v24.0.0",
  "title": "Node.js 24.0.0 (Current)",
  "byline": "The Node.js Project",
  "description": "…",
  "canonical": "https://nodejs.org/en/blog/release/v24.0.0",
  "lang": "en",
  "content_type": "text/html",
  "content": "## Module customization hooks\n\n…",
  "chars": 3811,
  "truncated": true,
  "took_ms": 688,
  "cached": false
}
```

Limite: timeout de 10 s, plafon de 3 MB pe corp, cel mult 3 redirect-uri și un guard SSRF la fiecare hop — URL-urile private, loopback sau cu credențiale, ori schemele non-HTTP, sunt `400 bad_request`. Tipurile de conținut acceptate sunt HTML, text simplu, JSON, XHTML, Markdown și XML; un upstream non-2xx este `502 upstream_error` cu `details: { status, url }`. Fetch-urile sunt puse în cache 15 minute, căutările 10.

Textul descărcat este conținut terț. Suprafețele de agent îl încadrează ca nesigur și îi spun modelului să nu urmeze niciodată instrucțiunile găsite în el; fă la fel dacă îl dai tu unui model.

## Cercetare aprofundată — `/v1/research` [#cercetare-aprofundată--v1research]

`POST /v1/research` pornește un job asincron: codai planifică mai multe căutări, citește cele mai bune pagini și scrie un raport în care fiecare afirmație citează `[Sn]`, urmat de surse. Durează între 20 s și câteva minute. Referința completă: [API-ul de cercetare](../api/gateway/research.mdx).

<TypeTable
  type={{
    question: { type: 'string', description: 'Până la 2 000 de caractere, cu eventualele constrângeri (regiune, perioadă, ce se compară).', required: true },
    time_budget_s: { type: 'integer', description: '20 – 300 de secunde.', default: '90' },
    max_sources: { type: 'integer', description: '3 – 30 de pagini citite.', default: '12' },
    include_domains: { type: 'string[]', description: 'Folosește doar pagini de pe aceste host-uri.' },
    published_after: { type: 'string', description: '`YYYY-MM-DD`: folosește doar pagini publicate la sau după această dată.' },
    output_schema: { type: 'object', description: 'O JSON Schema; rezultatul devine atunci un `answer` JSON în locul unui `report` în proză.' },
    wait_s: { type: 'number', description: '0 – 55: așteaptă atât înainte de a răspunde 202 cu un job în curs.', default: '0' },
  }}
/>

```ts
const job = await codai.research.run({ question: 'Ce țări UE impun facturarea electronică B2B în 2026?', max_sources: 15 });
console.log(job.report, job.sources);
```

<Callout type="warn">
Aceste endpoint-uri descarcă de pe internetul public în numele tău. Nu le da URL-uri furnizate de utilizatori pe care nu le-ai validat tu însuți — guard-ul SSRF protejează rețeaua codai, nu intenția ta.
</Callout>
