@byte-42/fantastico-sdk 0.0.0-stage → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,734 @@
1
+ /** Messaggio nella conversazione (OpenAI-compat). */
2
+ export interface ChatMessage {
3
+ role: "system" | "user" | "assistant";
4
+ content: string;
5
+ }
6
+ /** Request per chat.completions.create(). */
7
+ export interface ChatCompletionCreateParams {
8
+ /** Modello (opzionale: usa il default del progetto se omesso). */
9
+ model?: string;
10
+ messages: ChatMessage[];
11
+ /** Streaming (futuro). */
12
+ stream?: boolean;
13
+ temperature?: number;
14
+ max_tokens?: number;
15
+ /**
16
+ * Controllo sui thinking token, valido per **ogni turno** dell'orchestrazione. Omesso, non
17
+ * viene mandato nulla. Vedi {@link ReasoningConfig} — in particolare il fatto che il
18
+ * ragionamento scala dal `max_tokens`, che qui l'SDK calcola da sé sul tetto della chiave.
19
+ */
20
+ reasoning?: ReasoningConfig;
21
+ /**
22
+ * Identità del tenant finale (campo `user` dell'API OpenAI). Se presente, il consumo
23
+ * viene attribuito e limitato per quel tenant tramite la prenotazione del budget
24
+ * (`/api/runtime/budget`). Se assente, vale solo il tetto di progetto della runtime key.
25
+ */
26
+ user?: string;
27
+ /**
28
+ * Contesto fidato per QUESTA chiamata, iniettato in ogni tool locale invocato durante il
29
+ * turno (secondo argomento di `execute`) — mai visto dal modello, mai influenzato dai suoi
30
+ * argomenti. Sovrascrive (solo per questa chiamata) il `context` eventuale dell'istanza.
31
+ */
32
+ context?: unknown;
33
+ /**
34
+ * **Quali tool del marketplace offrire al modello in questa chiamata**, per id del tool (lo
35
+ * stesso id di `ai.tools.enabled()` e di `ai.runtimeKeys.ensure().tools`). Dalla 0.18.0.
36
+ *
37
+ * Omesso, il caso ordinario: tutti quelli che il conio ha concesso, come prima. `[]`: nessuno —
38
+ * la chiamata resta orchestrata (tool locali compresi) ma senza tool del marketplace.
39
+ *
40
+ * Restringe e non allarga: un id che il conio non ha concesso non compare, e non è un errore —
41
+ * vale come un tool irraggiungibile, che esce dal catalogo del giro (vedi `auth.ts`). Serve a
42
+ * chi confronta configurazioni diverse (Fantastico Studio: un test è modello + tool) e a chi
43
+ * vuole dare a un passaggio del proprio agente solo gli strumenti che gli servono.
44
+ */
45
+ marketplaceTools?: string[];
46
+ }
47
+ /** Un'azione delegata al client, raccolta durante l'orchestrazione (modalità fire-and-forget:
48
+ * il loop prosegue subito, l'esecuzione reale spetta al chiamante). */
49
+ export interface ClientAction {
50
+ id: string;
51
+ name: string;
52
+ arguments: Record<string, unknown>;
53
+ }
54
+ /** Una tool_call sospesa: il loop si è fermato prima di eseguirla. */
55
+ export interface PendingToolCall {
56
+ id: string;
57
+ name: string;
58
+ arguments: Record<string, unknown>;
59
+ /**
60
+ * Perché è sospesa. Assente = il tool non ha bisogno di una decisione (era solo nello
61
+ * stesso turno di uno che la richiedeva): `resume()` lo esegue in automatico.
62
+ */
63
+ reason?: "confirmation" | "client_action";
64
+ /**
65
+ * Frase umana da mostrare all'operatore per decidere (da `LocalToolDefinition.confirmationSummary`).
66
+ * Assente se il tool non ne definisce una: il chiamante ripiega su nome+argomenti grezzi.
67
+ */
68
+ summary?: string;
69
+ }
70
+ /** Decisione del chiamante su una singola tool_call sospesa, passata a `resume()`. */
71
+ export interface ResumeDecision {
72
+ id: string;
73
+ /** Per reason "confirmation": true = esegui ora, false = nega (il modello riceve un errore). */
74
+ approved?: boolean;
75
+ /** Per reason "client_action": l'esito che il client ha ottenuto compiendo l'azione. */
76
+ result?: unknown;
77
+ }
78
+ /** Esito quando l'orchestrazione si ferma in attesa di conferma umana o di un'azione client. */
79
+ export interface PendingCompletion {
80
+ status: "pending";
81
+ pending: PendingToolCall[];
82
+ /** Stato opaco della conversazione: da ripassare identico a `resume()`. La sua forma non è
83
+ * un contratto stabile — non va ispezionato né modificato dal chiamante. */
84
+ state: unknown;
85
+ /**
86
+ * Testo che il modello ha eventualmente prodotto nello stesso turno delle tool_call sospese
87
+ * (es. "Procedo con i due trasferimenti:"). Assente se il modello non ha scritto nulla.
88
+ * Il chiamante lo mostra come normale messaggio assistente PRIMA del gate di conferma —
89
+ * non è mai eseguito né interpretato, solo testo da visualizzare.
90
+ */
91
+ message?: string;
92
+ }
93
+ /** Usage aggregato (tutti i turni interni sommati). */
94
+ /**
95
+ * Lo sforzo di ragionamento, nei valori che OpenRouter documenta.
96
+ *
97
+ * **L'unione è aperta di proposito** (`| (string & {})`: i valori noti restano suggeriti
98
+ * dall'autocompletamento, uno nuovo non è un errore di compilazione). Due ragioni, e la seconda
99
+ * conta più della prima: le due pagine di OpenRouter non concordano — `use-cases/reasoning-tokens`
100
+ * elenca anche `"max"`, `api-reference/parameters` no — e soprattutto un elenco chiuso qui
101
+ * riprodurrebbe in piccolo il difetto che questo campo esiste per chiudere: un valore valido
102
+ * scartato dall'SDK perché l'SDK non lo conosceva ancora.
103
+ */
104
+ export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {});
105
+ /**
106
+ * Il campo `reasoning` di OpenRouter — il controllo sui "thinking token".
107
+ *
108
+ * **Perché è importante poterlo scrivere.** I token di ragionamento sono fatturati come token di
109
+ * output **e** scalano dal `max_tokens` della richiesta. Un modello capace di ragionare può quindi
110
+ * consumare l'intero budget di output pensando e restituire un contenuto **vuoto o troncato**, con
111
+ * un HTTP 200 e un usage regolarmente misurato. Per chi fa estrazione strutturata il guasto è
112
+ * silenzioso e non rumoroso: un contenuto vuoto si legge come un JSON `[]` valido, cioè "non ho
113
+ * trovato niente" — che è una risposta plausibile e sbagliata.
114
+ *
115
+ * **L'SDK non decide per il chiamante.** Nessun default: omesso, il campo non parte affatto e il
116
+ * comportamento resta quello che il modello e OpenRouter hanno di loro. Spegnere il ragionamento
117
+ * d'ufficio cambierebbe la risposta di ogni chiamante esistente senza che nessuno l'abbia chiesto.
118
+ *
119
+ * Vincoli dichiarati da OpenRouter: `effort` e `max_tokens` non si passano insieme, e per i modelli
120
+ * Anthropic il `max_tokens` della richiesta deve essere strettamente maggiore del budget di
121
+ * ragionamento — altrimenti non resta spazio per la risposta.
122
+ */
123
+ export interface ReasoningConfig {
124
+ /** Accende il ragionamento; senza `effort`, OpenRouter lo attiva a sforzo "medium". */
125
+ enabled?: boolean;
126
+ /** Quanto ragionare. Mutuamente esclusivo con `max_tokens`. */
127
+ effort?: ReasoningEffort;
128
+ /** Budget esplicito di token di ragionamento (stile Anthropic/Gemini). Esclude `effort`. */
129
+ max_tokens?: number;
130
+ /** Ragiona ma non restituire il ragionamento. Supportato da tutti i modelli. */
131
+ exclude?: boolean;
132
+ /** Quali turni portano il ragionamento (GPT-5.6+). Documentato solo sulla pagina use-cases. */
133
+ context?: "auto" | "all_turns" | "current_turn" | (string & {});
134
+ /** Modalità di ragionamento (GPT-5.6+). Documentato solo sulla pagina use-cases. */
135
+ mode?: "standard" | "pro" | (string & {});
136
+ }
137
+ export interface CompletionUsage {
138
+ prompt_tokens: number;
139
+ completion_tokens: number;
140
+ total_tokens: number;
141
+ /** Turni LLM interni (inclusi quelli per MCP). */
142
+ internal_turns: number;
143
+ cache_creation_tokens: number;
144
+ cache_read_tokens: number;
145
+ /**
146
+ * Token spesi a ragionare, da `usage.completion_tokens_details.reasoning_tokens`.
147
+ *
148
+ * **Sono già dentro `completion_tokens`**, non si sommano: la parte visibile della risposta è
149
+ * `completion_tokens - reasoning_tokens`. È l'unico modo che il chiamante ha di vedere quanto è
150
+ * costato il pensiero — e, quando la risposta torna vuota o mozzata, di distinguere "il modello
151
+ * non aveva niente da dire" da "ha speso il budget a pensare". `0` quando il provider non riporta
152
+ * il dettaglio, che è anche il caso di ogni modello che non ragiona.
153
+ */
154
+ reasoning_tokens: number;
155
+ /** Invocazioni MCP avvenute internamente. */
156
+ mcp_calls: number;
157
+ /** Costo reale dell'inferenza in USD, riportato da OpenRouter (non stimato). */
158
+ cost_usd: number;
159
+ }
160
+ export interface ChatCompletionChoice {
161
+ index: number;
162
+ message: {
163
+ role: "assistant";
164
+ content: string;
165
+ };
166
+ finish_reason: "stop" | "length";
167
+ }
168
+ /** Risposta di chat.completions.create() a orchestrazione conclusa. */
169
+ export interface ChatCompletion {
170
+ status: "done";
171
+ id: string;
172
+ object: "chat.completion";
173
+ model: string;
174
+ choices: ChatCompletionChoice[];
175
+ usage: CompletionUsage;
176
+ /** Azioni client raccolte durante il turno (fire-and-forget): il chiamante le esegue
177
+ * lato suo (es. con FantasticoActions). Vuoto se nessuna è stata richiesta. */
178
+ clientActions: ClientAction[];
179
+ }
180
+ /** Esito di create()/resume(): concluso, o fermo in attesa di conferma/azione client. */
181
+ export type ChatCompletionResult = ChatCompletion | PendingCompletion;
182
+ /** Opzioni di configurazione del client FANTASTICO. */
183
+ export interface FantasticoOptions {
184
+ /**
185
+ * La credenziale FANTASTICO (obbligatoria), e **l'unica cosa che decide su quale superficie
186
+ * parla questo client**:
187
+ *
188
+ * - una **project key** `sk-fant-…` vale tutti i conti del progetto e opera su `/api/runtime/*`,
189
+ * dove il conto si nomina (`user` in una create, `userKey` sulle chiavi);
190
+ * - una **chiave di conto** `ck-fant-…` ne vale uno solo e opera su `/api/edge/*`, dove il conto
191
+ * **non si nomina affatto**: è quello della credenziale, e non esiste un campo con cui
192
+ * indicarne un altro.
193
+ *
194
+ * Non c'è un'opzione con cui dichiarare quale delle due: il prefisso lo dice già, e dirlo due
195
+ * volte vuol dire poterlo dire diversamente. Vedi `surface.ts`.
196
+ */
197
+ apiKey: string;
198
+ /** URL del control plane, comprensivo di `/api` (default: https://fantastico.cc/api). */
199
+ baseUrl?: string;
200
+ /** Modello di default (se non specificato nella request). */
201
+ defaultModel?: string;
202
+ /**
203
+ * Nome visibile dell'assistente (es. "GenioAI"). L'SDK non lo usa per sé — non lo inietta
204
+ * da solo nel prompt, non fa branding automatico — è una singola fonte di verità che chi
205
+ * integra può riusare sia per comporre `defaultSystemPrompt` (`` `Ti chiami ${name}...` ``)
206
+ * sia lato UI (letto da `ai.name`), invece di ripetere la stringa in più punti.
207
+ */
208
+ name?: string;
209
+ /**
210
+ * Iniettato come primo messaggio "system" se il chiamante non ne ha già messo uno in
211
+ * `messages`. Comodo per non ripeterlo ad ogni create(); non fa sostituzione di variabili —
212
+ * il testo arriva già pronto da chi integra.
213
+ */
214
+ defaultSystemPrompt?: string;
215
+ /** Tool locali/azione-client registrati fin da subito (in alternativa/oltre a addTool()). */
216
+ tools?: import("./local-tools.js").RegisteredTool[];
217
+ /**
218
+ * Contesto fidato di default per ogni chiamata (secondo argomento di ogni `execute` dei
219
+ * tool locali), sovrascrivibile per singola create() via `ChatCompletionCreateParams.context`.
220
+ * Mai visto dal modello.
221
+ */
222
+ context?: unknown;
223
+ /**
224
+ * La **sessione di attività** da continuare, se se ne sta continuando una.
225
+ *
226
+ * Omessa — il caso normale — il primo conio ne apre una e il client la tiene finché vive. Vale
227
+ * la pena passarla in un caso solo: un processo che riparte e sa di star continuando lo stesso
228
+ * tratto di lavoro. **Non è una credenziale**: non autorizza niente, viene confrontata con il
229
+ * conto dedotto dalla credenziale, e una che non è propria (o che la potatura ha già preso) dà
230
+ * semplicemente una sessione nuova — mai un errore. Quella buona è sempre quella che torna,
231
+ * leggibile da `ai.activitySessionId`.
232
+ */
233
+ activitySessionId?: string;
234
+ /**
235
+ * Il **prodotto del marketplace** che questo client integra, per id (es. `"genioai"`).
236
+ *
237
+ * Omesso, il caso ordinario: si usa il modello e basta. Valorizzato, ogni chiave viene coniata
238
+ * per quel prodotto, alle condizioni con cui il progetto lo ha attivato; se il progetto non lo ha
239
+ * attivato dal marketplace il conio è rifiutato (`TOOL_NOT_ENABLED`). Il prodotto si accende e si
240
+ * spegne dal marketplace, non da questo campo.
241
+ *
242
+ * Va dichiarato da chi integra quel prodotto, e da nessun altro.
243
+ */
244
+ product?: string;
245
+ /** ID progetto FANTASTICO (determina quali MCP sono abilitati). */
246
+ projectId?: string;
247
+ /**
248
+ * USD prenotati a ogni giro di budget per un tenant (default 0.10). Il client scala in
249
+ * locale e regola il consumo reale al giro successivo.
250
+ */
251
+ reserveUsd?: number;
252
+ /** Max turni interni dell'orchestratore (default 8). */
253
+ maxTurns?: number;
254
+ /** Timeout orchestrazione in ms (default 90_000). */
255
+ budgetMs?: number;
256
+ /** Timeout di una chiamata a un tool, in ms (default 180_000). Scaduto, non si riprova: il tool può aver lavorato e addebitato. */
257
+ mcpTimeoutMs?: number;
258
+ /**
259
+ * Origini pubbliche dei tool → origini interne, per chiamarli senza uscire dalla macchina (es.
260
+ * `{ "https://tools.example.com": "http://127.0.0.1:8080" }`). Omessa, si legge
261
+ * `FANTASTICO_TOOL_ORIGIN_MAP` (`pubblica=interna[,…]`). Vedi `tool-origin.ts`.
262
+ */
263
+ toolOriginMap?: Record<string, string>;
264
+ /**
265
+ * Il `reasoning` di default per ogni richiesta che non ne porta uno suo.
266
+ *
267
+ * Esiste per far prendere la decisione **una volta sola**, alla costruzione, dove si vede nel
268
+ * codice e passa in revisione — invece che a ogni call site, dove prima o poi ne resta indietro
269
+ * uno. Omesso, non cambia niente per nessuno: non parte alcun campo.
270
+ *
271
+ * Il `reasoning` di una singola richiesta **sostituisce** questo, non ci si fonde: niente merge
272
+ * profondo, niente campo di qui che sopravvive dentro l'oggetto di lì. Prevedibile batte furbo.
273
+ *
274
+ * ⚠️ Attenzione a `{ enabled: false }` come default di client: alcuni modelli ragionano
275
+ * obbligatoriamente (`reasoning.mandatory` nel catalogo di OpenRouter) e **rifiutano** la
276
+ * richiesta di spegnerlo. Un default di client li renderebbe inutilizzabili da questo client.
277
+ * Vedi {@link ReasoningConfig}.
278
+ */
279
+ reasoning?: ReasoningConfig;
280
+ /**
281
+ * Quanto insistere quando il provider risponde con un errore che passa da solo — un rate limit
282
+ * (429), un provider sovraccarico. Default: due ritentativi, esponenziale con jitter, e
283
+ * `Retry-After` rispettato quando il provider lo manda. `false` li spegne.
284
+ *
285
+ * Vale la pena toccarlo perché la tolleranza alla latenza non è la stessa ovunque: una pagina
286
+ * che risponde a un utente non è un lavoro in coda. Comunque non si dorme mai oltre `budgetMs`:
287
+ * sforarlo farebbe emergere un `TIMEOUT` al posto della causa vera. Vedi `backoff.ts`.
288
+ */
289
+ retry?: import("./backoff.js").RetryOptions | false;
290
+ /** Iniettabile nei test. */
291
+ fetchImpl?: typeof fetch;
292
+ /**
293
+ * @deprecated Non fa niente. Tenuto perché un client scritto contro una versione precedente
294
+ * continui a compilare: valorizzarlo non cambia in alcun modo il comportamento del client.
295
+ *
296
+ * Serviva a saltare il conio e usare una chiave del provider già in mano (BYOK/sviluppo).
297
+ * Quella modalità non esiste più, e non perché il caso d'uso fosse sbagliato: il campo ne
298
+ * spegneva **cinque** alla volta. Senza conio si perdevano anche le concessioni sui tool (cioè
299
+ * il marketplace, per sempre), l'handle da restituire, la sessione di attività, il `max_tokens`
300
+ * calcolato sul tetto della chiave e il ri-conio dopo un 401 del provider.
301
+ *
302
+ * Chi lo passava per **coniare esplicitamente all'avvio** — leggere tetto e scadenza, o
303
+ * restituire la chiave all'uscita invece di lasciarla scadere — usi `ai.runtimeKeys.ensure()`,
304
+ * che dà gli stessi numeri senza spegnere nient'altro. Chi lo passava per **BYOK vero** non ha
305
+ * ancora una strada: quando ci sarà è il conio a restituire la chiave del cliente, non un ramo
306
+ * qui.
307
+ *
308
+ * **Attenzione a cosa compra questo silenzio.** Un client che lo valorizzava non coniava
309
+ * affatto; ora conia, quindi impegna denaro sulla riserva del progetto. La chiamata continua a
310
+ * funzionare, ma non è più gratis per il progetto — ed è il genere di differenza che si legge
311
+ * su un estratto conto invece che in un errore.
312
+ */
313
+ providerApiKey?: string;
314
+ }
315
+ /** Configurazione MCP risolta dal control plane. */
316
+ export interface ToolConfig {
317
+ name: string;
318
+ url: string;
319
+ authToken: string;
320
+ allowedTools: string[];
321
+ toolDefinitions?: ToolDefinition[];
322
+ }
323
+ /** Tool definition (OpenAI function-calling format). */
324
+ export interface ToolDefinition {
325
+ type: "function";
326
+ function: {
327
+ name: string;
328
+ description: string;
329
+ parameters: Record<string, unknown>;
330
+ };
331
+ }
332
+ /** Tool call emesso dal modello (OpenAI function-calling format). */
333
+ export interface ToolCall {
334
+ id: string;
335
+ type: "function";
336
+ function: {
337
+ name: string;
338
+ arguments: string;
339
+ };
340
+ }
341
+ /**
342
+ * Parte di contenuto testuale multi-part (stile OpenAI Chat Completions) con
343
+ * l'estensione `cache_control` che OpenRouter inoltra ai provider che la supportano
344
+ * (Anthropic in primis). FANTASTICO non interpreta mai questo campo: lo passa 1:1.
345
+ *
346
+ * Verificato empiricamente (2026-07): il caching scatta solo
347
+ * se la request forza `provider: {order:["Anthropic"]}` — vedi `pinProvider` sotto —
348
+ * e il blocco supera la soglia minima del modello (1024-4096 token a seconda del modello).
349
+ */
350
+ export interface ChatContentPart {
351
+ type: "text";
352
+ text: string;
353
+ cache_control?: {
354
+ type: "ephemeral";
355
+ ttl?: "5m" | "1h";
356
+ };
357
+ }
358
+ /** Messaggio "raw": stesso vocabolario OpenAI di ChatMessage, ma multi-part + tool. */
359
+ export interface RawChatMessage {
360
+ role: "system" | "user" | "assistant" | "tool";
361
+ content: string | ChatContentPart[] | null;
362
+ /** Presente sui messaggi "assistant" quando il modello ha richiesto tool. */
363
+ tool_calls?: ToolCall[];
364
+ /** Presente sui messaggi "tool": a quale tool_call risponde. */
365
+ tool_call_id?: string;
366
+ }
367
+ /**
368
+ * Request per chat.completions.createRaw() — nessuna orchestrazione interna: una sola
369
+ * chiamata a OpenRouter, il chiamante gestisce il proprio loop di tool-use (per i casi
370
+ * con tool propri che l'orchestrazione opaca di create()
371
+ * non può risolvere).
372
+ */
373
+ export interface RawCompletionCreateParams {
374
+ model?: string;
375
+ messages: RawChatMessage[];
376
+ /** Tool del CHIAMANTE (formato OpenAI function-calling). FANTASTICO non li esegue mai:
377
+ * ritorna i tool_calls così come arrivano dal modello. Per i tool del marketplace
378
+ * FANTASTICO, vedi `ai.tools.list()`/`ai.tools.execute()`. */
379
+ tools?: Array<ToolDefinition | {
380
+ type: "openrouter:web_search";
381
+ parameters?: {
382
+ engine?: "auto" | "native" | "exa" | "firecrawl" | "parallel" | "perplexity";
383
+ max_results?: number;
384
+ max_total_results?: number;
385
+ max_uses?: number;
386
+ search_context_size?: "low" | "medium" | "high";
387
+ };
388
+ }>;
389
+ /** Budget totale di chiamate ai tool server del provider. */
390
+ max_tool_calls?: number;
391
+ /** Formato OpenAI: forza/disabilita l'uso di tool per questo turno. */
392
+ tool_choice?: "auto" | "none" | "required" | {
393
+ type: "function";
394
+ function: {
395
+ name: string;
396
+ };
397
+ };
398
+ temperature?: number;
399
+ max_tokens?: number;
400
+ /** Controllo sui thinking token. Omesso, non viene mandato nulla. Vedi {@link ReasoningConfig}. */
401
+ reasoning?: ReasoningConfig;
402
+ /** Stesso significato di create(): budget per-tenant via /api/runtime/budget. */
403
+ user?: string;
404
+ /**
405
+ * Forza il routing su Anthropic diretto (default: true se un content-block ha
406
+ * `cache_control`, altrimenti non impostato). Senza questo, OpenRouter può instradare
407
+ * su un provider (es. Amazon Bedrock) che ignora `cache_control` senza errore: si paga
408
+ * il prezzo pieno in silenzio. Passa `false` esplicitamente per il routing di default.
409
+ */
410
+ pinProvider?: boolean;
411
+ }
412
+ /** Risposta di chat.completions.createRaw(). */
413
+ export interface RawChatCompletion {
414
+ id: string;
415
+ model: string;
416
+ choices: Array<{
417
+ index: number;
418
+ message: RawChatMessage;
419
+ finish_reason: "stop" | "length" | "tool_calls";
420
+ }>;
421
+ usage: CompletionUsage;
422
+ }
423
+ /** Parametri per la creazione di un conto utente (sub-account). */
424
+ export interface AccountCreateParams {
425
+ /** Identificativo univoco del tenant/utente finale. È l'indirizzo del conto in ogni chiamata. */
426
+ userKey: string;
427
+ /**
428
+ * Nome con cui mostrare il conto (es. nome dello studio). Non è un identificativo: non deve
429
+ * essere unico e nessuna chiamata trova un conto per nome. Torna come `displayName`.
430
+ */
431
+ label?: string;
432
+ /**
433
+ * Da cosa il conto è **finanziato**. Dalla 0.13.0; omesso, `standard` — come prima.
434
+ *
435
+ * - `standard`: riceve la quota periodica del piano del progetto. Se quella quota è zero, il
436
+ * conto nasce **vuoto** e va finanziato con un trasferimento dalla riserva.
437
+ * - `trial`: riceve **una volta sola** la concessione di prova del piano e ha una scadenza; alla
438
+ * scadenza il Cloud lo sospende. È il conto giusto per un cliente in prova gratuita, e si
439
+ * chiude con {@link Fantastico.accounts}`.upgrade()` quando si abbona.
440
+ * - `unlimited`: ogni chiave attinge alla riserva del progetto, senza quota propria.
441
+ *
442
+ * Non è l'ambiente, che è un asse a sé: un conto di sviluppo in prova è una cosa ordinaria.
443
+ * `trial` è rifiutato se il piano del progetto non offre prove (concessione a zero).
444
+ */
445
+ type?: "standard" | "trial" | "unlimited";
446
+ /** Tetto di spesa per periodo in USD; 0 = nessun limite. */
447
+ spendLimit?: number;
448
+ /** Durata del periodo di budget in giorni. */
449
+ periodDays?: number;
450
+ }
451
+ /** Info del conto restituita dal backend. */
452
+ export interface AccountInfo {
453
+ _id: string;
454
+ userKey: string;
455
+ /** Il nome con cui mostrare il conto, se ne ha uno (`null` = nessuno). Dalla 0.12.1. */
456
+ displayName?: string | null;
457
+ /**
458
+ * Da cosa il conto è finanziato ora — vedi {@link AccountCreateParams.type}. Dalla 0.13.0.
459
+ * Una prova lo resta finché non la chiude `accounts.upgrade()`.
460
+ */
461
+ type?: "standard" | "trial" | "unlimited";
462
+ label?: string;
463
+ spendLimit: number;
464
+ periodDays: number;
465
+ spent: number;
466
+ reserved: number;
467
+ status: string;
468
+ createdAt: string;
469
+ }
470
+ /**
471
+ * Campi modificabili di un conto già esistente. Si aggiorna solo ciò che si passa.
472
+ *
473
+ * Oggi l'unico è `label`, il nome con cui mostrare il conto: una stringa lo imposta, la stringa
474
+ * vuota lo toglie. `spendLimit`, `periodDays` e `status` restano dichiarati per compatibilità ma il
475
+ * Cloud li rifiuta (`FIELD_NOT_UPDATABLE`): il credito si trasferisce dalla riserva, il periodo lo
476
+ * dà il piano del conto, la sospensione ha le sue rotte.
477
+ */
478
+ export interface AccountUpdateParams {
479
+ label?: string;
480
+ spendLimit?: number;
481
+ periodDays?: number;
482
+ status?: "active" | "suspended";
483
+ }
484
+ /**
485
+ * Chiave di **conto** (`ck-fant-…`): la credenziale che si consegna a un
486
+ * client che gira **fuori dal proprio perimetro** — un plugin sul server di un cliente.
487
+ * Vale un conto solo, e il tetto della runtime key che conia è il credito di quel conto.
488
+ *
489
+ * Il valore in chiaro non compare qui: esiste solo nella risposta a `issue`/`rotate`.
490
+ */
491
+ export interface AccountKeyInfo {
492
+ id: string;
493
+ prefix: string;
494
+ contoId: string;
495
+ projectId: string;
496
+ env: "dev" | "prod";
497
+ userKey: string;
498
+ label?: string;
499
+ createdAt: string;
500
+ revokedAt?: string;
501
+ revokedReason?: "rotated" | "revoked";
502
+ }
503
+ /** Emissione o rotazione: **l'unica** risposta che contiene la chiave in chiaro. */
504
+ export interface IssuedAccountKey {
505
+ id: string;
506
+ /** Valore in chiaro — esiste solo qui. A riposo il Cloud ne tiene solo l'hash. */
507
+ key: string;
508
+ prefix: string;
509
+ contoId: string;
510
+ userKey: string;
511
+ label?: string;
512
+ createdAt: string;
513
+ }
514
+ /**
515
+ * Una runtime key OpenRouter ancora aperta su un conto — **mai il suo segreto**.
516
+ *
517
+ * Il segreto esiste una volta sola, nella risposta al conio, e da lì in poi vive solo nella
518
+ * memoria di chi l'ha chiesto: il Cloud non lo conserva, quindi non c'è nulla da
519
+ * restituire qui. `recordId` è l'handle del provider — legge e revoca, non compra — ed è lo
520
+ * stesso identificativo che il conio ha già consegnato.
521
+ *
522
+ * **Da non confondere con `AccountKeyInfo`**, che è una credenziale FANTASTICO (`ck-fant-…`):
523
+ * quella autentica verso il Cloud, è conservata come hash e non scade. Questa è una credenziale
524
+ * OpenRouter, non è conservata affatto, e scade.
525
+ */
526
+ export interface RuntimeKeyInfo {
527
+ /**
528
+ * L'handle del provider, o `null` per un'autorizzazione la cui chiave non è mai stata coniata
529
+ * (un crash fra le due scritture). Occupa comunque uno slot e non è revocabile: non c'è nulla
530
+ * da revocare. Il reap la liquida alla scadenza.
531
+ */
532
+ recordId: string | null;
533
+ /** Il tetto della chiave presso il provider. */
534
+ keyLimit: string;
535
+ /** Quanto il provider dice che ha speso finora, contro quel tetto. */
536
+ spent: string;
537
+ /** Quanto ha impegnato del conto: un blocco scalato dal fattore del piano. */
538
+ cost: string;
539
+ createdAt: string;
540
+ /** Quando il reap la prenderà. Anticipata appena la chiave risulta esaurita. */
541
+ expiresAt: string;
542
+ /**
543
+ * Valorizzato quando la chiave è già stata disabilitata sul provider e sta aspettando che la
544
+ * finestra di riporto si chiuda. Tiene ancora il suo slot, ma non c'è più niente da chiederle:
545
+ * revocarla di nuovo risponde `false`.
546
+ */
547
+ disabledAt: string | null;
548
+ }
549
+ /**
550
+ * @deprecated Nessun addebito parte più da qui. Vedi `Fantastico.usage.record`.
551
+ */
552
+ export interface UsageRecordParams {
553
+ /** Identificativo del tenant/utente finale a cui addebitare (conto). */
554
+ userKey: string;
555
+ /** Id del servizio nel registry (es. "anonimizzazione"). */
556
+ service: string;
557
+ /** Unità di consumo del listino (es. "document", "chatTurn"). */
558
+ unit: string;
559
+ /** Quantità di unità consumate. */
560
+ quantity: number;
561
+ /** Id idempotente (es. jobId): la ridelivery non riaddebita. */
562
+ refId?: string;
563
+ }
564
+ /**
565
+ * @deprecated L'esito di un addebito che non avviene più. Vedi `Fantastico.usage.record`.
566
+ */
567
+ export interface UsageRecordResult {
568
+ service: string;
569
+ unit: string;
570
+ quantity: number;
571
+ /** **Sempre `0`**: non è stato addebitato niente. */
572
+ amount: number;
573
+ /** **Sempre `false`**: non c'è stato niente da deduplicare. */
574
+ deduped: boolean;
575
+ }
576
+ /**
577
+ * La runtime key con cui **questo client** sta lavorando — di nuovo, mai il suo segreto.
578
+ *
579
+ * Esiste per una ragione sola: da quando il conio è sempre dell'SDK, chi integra non ha più un
580
+ * posto da cui vedere cosa sta usando. Sono i quattro numeri che servono a chiudere il giro —
581
+ * l'handle per restituirla, il tetto per sapere quanto può chiedere, la scadenza, e la sessione
582
+ * sotto cui questa spesa si ritroverà a valle.
583
+ *
584
+ * Il segreto non è qui e non è altrove: vive nella memoria dell'`AuthClient` per il suo TTL e non
585
+ * attraversa mai la superficie pubblica. Chi ha bisogno di una credenziale del
586
+ * provider in mano non ha bisogno di questo SDK.
587
+ */
588
+ export interface RuntimeKeyHandle {
589
+ /** L'handle presso il provider: lo stesso che `runtimeKeys.revoke` accetta. */
590
+ recordId: string;
591
+ /** Il tetto della chiave in USD, o `null` se il conio non l'ha dichiarato. */
592
+ limitUsd: number | null;
593
+ /** Quanto il conio ha impegnato sul conto, come stringa decimale. `null` se non dichiarato. */
594
+ costUsd: string | null;
595
+ /** Quando scade, ISO. Oltre, il reap la prende. */
596
+ expiresAt: string;
597
+ /** La sessione di attività aperta o continuata da questo conio. */
598
+ activitySessionId: string | null;
599
+ /**
600
+ * I tool che questo conio ha **concesso** — cioè abilitati sul progetto, con un token emesso —
601
+ * per id e nome. Mai il token.
602
+ *
603
+ * Non è l'elenco di `ai.tools.list()`, e la differenza è il punto: quello contiene i tool
604
+ * concessi **e** verificati (documento letto, digest che torna), e scarta in silenzio sia un
605
+ * tool non abilitato sia uno abilitato che non risponde. Chi deve distinguere "spento" da
606
+ * "acceso ma guasto" — una funzione di sistema che, se attiva, non può essere saltata — confronta
607
+ * i due elenchi: concesso e assente da `list()` vuol dire guasto, non spento.
608
+ */
609
+ tools: Array<{
610
+ id: string;
611
+ name: string;
612
+ }>;
613
+ }
614
+ /**
615
+ * Il saldo del **proprio** conto (`ai.balance()`, solo con una chiave di conto).
616
+ *
617
+ * `remainingUsd` è quanto resta da **impegnare**, non il saldo lordo: una parte può essere già
618
+ * bloccata da una chiave viva. `lowBalance` è la soglia di preavviso, e la decide il Cloud e non
619
+ * l'installazione — è una scelta di prodotto, e deve poter cambiare senza toccare i client già
620
+ * distribuiti.
621
+ *
622
+ * **È una sonda, non un blocco.** Il rifiuto vero è il conio, che non emette una chiave quando il
623
+ * conto è a secco; questo serve a dire "stai per finire" prima che succeda.
624
+ */
625
+ export interface ContoBalance {
626
+ remainingUsd: number;
627
+ lowBalance: boolean;
628
+ status: string;
629
+ }
630
+ /**
631
+ * Un modello disponibile su OpenRouter (`ai.models.list()`). Pass-through di quanto
632
+ * risponde il provider: solo `id` è garantito, il resto varia col catalogo — non lo
633
+ * ricopiamo campo per campo per non irrigidirci a ogni loro aggiunta.
634
+ */
635
+ export interface OpenRouterModel {
636
+ id: string;
637
+ name?: string;
638
+ description?: string;
639
+ context_length?: number;
640
+ pricing?: {
641
+ prompt?: string;
642
+ completion?: string;
643
+ request?: string;
644
+ image?: string;
645
+ [key: string]: unknown;
646
+ };
647
+ /** Modalità in ingresso e in uscita (`output_modalities: ["speech"]` per un modello TTS). */
648
+ architecture?: {
649
+ modality?: string;
650
+ input_modalities?: string[];
651
+ output_modalities?: string[];
652
+ [key: string]: unknown;
653
+ };
654
+ /**
655
+ * Le voci che un modello text-to-speech ammette (`ai.audio.speech.models()`). Assente sui
656
+ * modelli di chat, e su quei TTS che la voce la prendono da un riferimento audio
657
+ * (`input_references`) invece che da un nome.
658
+ */
659
+ supported_voices?: string[];
660
+ /**
661
+ * Nomi dei provider che servono QUESTO modello con Zero Data Retention — per-endpoint,
662
+ * non per-modello: lo stesso modello può avere provider ZDR e non-ZDR insieme, e
663
+ * OpenRouter non garantisce di instradare verso uno ZDR a meno di richiederlo
664
+ * esplicitamente (`provider.zdr: true` nella chiamata). Assente sia quando nessun
665
+ * provider è ZDR sia quando il catalogo ZDR non era disponibile — mai da leggere come
666
+ * "non è ZDR" in quest'ultimo caso.
667
+ */
668
+ zdrProviders?: string[];
669
+ [key: string]: unknown;
670
+ }
671
+ /** Errore FANTASTICO strutturato. */
672
+ export declare class FantasticoError extends Error {
673
+ readonly code: string;
674
+ readonly status: number;
675
+ constructor(message: string, code: string, status: number,
676
+ /**
677
+ * Il motivo dato dal provider LLM a monte, quando c'è (es. `Anthropic: This organization has
678
+ * been disabled…`). **Solo per i log**: non sta nel messaggio perché il messaggio arriva
679
+ * spesso davanti all'utente finale, che non deve vedere né il nome del fornitore né la sua
680
+ * prosa. Assente (non `undefined`) quando non c'è, così non sporca l'output di `inspect`.
681
+ */
682
+ providerDetail?: string | null);
683
+ readonly providerDetail?: string;
684
+ }
685
+ /**
686
+ * Il modello ha esaurito il budget di output e non ha restituito **niente**.
687
+ *
688
+ * **Perché è un errore e non una risposta.** L'HTTP è 200, l'usage è misurato e addebitato, e il
689
+ * contenuto è una stringa vuota. È la stessa forma di `errorInBody` (`provider-error.ts`): un 200
690
+ * che non è un successo — lì il fallimento è dichiarato nel corpo, qui è desumibile da
691
+ * `finish_reason` più un contenuto che non c'è. Per chi fa estrazione strutturata la differenza fra
692
+ * le due letture è tutto: `""` si analizza come un `[]` valido, cioè "non ho trovato niente" — una
693
+ * risposta plausibile, misurata, apparentemente riuscita, e falsa.
694
+ *
695
+ * **Non è ritentabile e non fa ri-coniare.** È una `FantasticoError`, quindi `withProviderRetry` la
696
+ * lascia passare intatta: una chiave nuova non cambia nulla, e un ritentativo automatico
697
+ * rispenderebbe i soldi del chiamante per lo stesso esito. Non si ritenta nemmeno "utilmente" con
698
+ * il ragionamento spento: sarebbe la stessa classe di comportamento invisibile che questo errore
699
+ * esiste per rendere visibile.
700
+ *
701
+ * **I numeri sono il rimedio.** Con `reasoningTokens` alto il ragionamento si è mangiato lo spazio
702
+ * della risposta (spegnerlo, o alzare `maxTokens`); a zero, la risposta semplicemente non ci stava
703
+ * (alzare `maxTokens`) — una causa su cui il campo `reasoning` non può niente. Senza questi numeri
704
+ * le due si distinguono solo dopo ore.
705
+ */
706
+ export declare class EmptyCompletionError extends FantasticoError {
707
+ /** Il modello che ha risposto, come lo riporta il provider. */
708
+ readonly model: string;
709
+ /** Il `max_tokens` **effettivamente mandato**, o `null` se non ne è partito nessuno. */
710
+ readonly maxTokens: number | null;
711
+ /** Token di output di QUESTA risposta (comprensivi di quelli di ragionamento). */
712
+ readonly completionTokens: number;
713
+ /**
714
+ * Quanti di quei token sono stati spesi a ragionare.
715
+ *
716
+ * Su `create()` sono i numeri del **turno fallito**, non l'aggregato dell'orchestrazione: a
717
+ * chi deve intervenire serve sapere quale chiamata ha sforato, non quanto è costato il giro.
718
+ */
719
+ readonly reasoningTokens: number;
720
+ constructor(message: string,
721
+ /** Il modello che ha risposto, come lo riporta il provider. */
722
+ model: string,
723
+ /** Il `max_tokens` **effettivamente mandato**, o `null` se non ne è partito nessuno. */
724
+ maxTokens: number | null,
725
+ /** Token di output di QUESTA risposta (comprensivi di quelli di ragionamento). */
726
+ completionTokens: number,
727
+ /**
728
+ * Quanti di quei token sono stati spesi a ragionare.
729
+ *
730
+ * Su `create()` sono i numeri del **turno fallito**, non l'aggregato dell'orchestrazione: a
731
+ * chi deve intervenire serve sapere quale chiamata ha sforato, non quanto è costato il giro.
732
+ */
733
+ reasoningTokens: number);
734
+ }