@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,410 @@
1
+ import type { FantasticoOptions, ChatCompletionCreateParams, ChatCompletionResult, ToolDefinition, RawCompletionCreateParams, RawChatCompletion, AccountCreateParams, AccountUpdateParams, AccountInfo, AccountKeyInfo, IssuedAccountKey, RuntimeKeyInfo, RuntimeKeyHandle, ContoBalance, UsageRecordParams, UsageRecordResult, ResumeDecision, OpenRouterModel } from "./types.js";
2
+ import { type Surface } from "./surface.js";
3
+ import type { ChatActivityOptions } from "./activity.js";
4
+ import { type RegisteredTool, type LocalToolDefinition } from "./local-tools.js";
5
+ import { type EvaluateDecisionParams, type DecisionResult, type DecisionToolConfig } from "./decisions.js";
6
+ import { type SpeechCreateParams, type SpeechResult, type SpeechModel } from "./speech.js";
7
+ export declare class Fantastico {
8
+ private readonly opts;
9
+ private readonly auth;
10
+ private readonly fetchImpl;
11
+ private readonly mcpExecutor;
12
+ private readonly localTools;
13
+ /** Costo non ancora regolato per tenant: si salda alla chiamata successiva (delta). */
14
+ private readonly unsettled;
15
+ /** Nome visibile dell'assistente, se impostato — un solo posto da cui prompt e UI
16
+ * lo leggono, invece di ripetere la stringa (vedi FantasticoOptions.name). */
17
+ readonly name?: string;
18
+ /**
19
+ * Su quale delle due superfici parla questo client, dedotto dal prefisso della credenziale
20
+ * (`surface.ts`). Leggibile perché chi integra con l'una o con l'altra ha a disposizione cose
21
+ * diverse — `accounts` di qua, `balance` di là — e un `if` sulla stringa della chiave, scritto
22
+ * a valle, sarebbe la seconda copia della stessa deduzione.
23
+ */
24
+ readonly surface: Surface;
25
+ readonly chat: {
26
+ completions: {
27
+ /** `options.onEvent` riceve gli eventi del turno mentre accadono (`activity.ts`). */
28
+ create: (params: ChatCompletionCreateParams, options?: ChatActivityOptions) => Promise<ChatCompletionResult>;
29
+ /**
30
+ * Riprende un'orchestrazione fermata con `status: "pending"` (conferma umana o esito
31
+ * di un'azione client). `state` è quello ricevuto in `PendingCompletion.state`, passato
32
+ * identico — non va ispezionato né modificato.
33
+ */
34
+ resume: (state: unknown, decisions: ResumeDecision[], context?: unknown, options?: ChatActivityOptions) => Promise<ChatCompletionResult>;
35
+ /** Nessuna orchestrazione: una chiamata, tool del chiamante, il loop resta a lui. */
36
+ createRaw: (params: RawCompletionCreateParams) => Promise<RawChatCompletion>;
37
+ };
38
+ };
39
+ /**
40
+ * Tool del marketplace FANTASTICO, per chi gestisce il proprio loop via createRaw().
41
+ *
42
+ * **`user` nomina il conto**, come in `create()`: i tool token arrivano col conio e sono la
43
+ * credenziale con cui quel tool verrà pagato, quindi chiedere l'elenco per un tenant significa
44
+ * coniare — e far pagare — sul conto giusto. Omesso, è il conto del proprietario.
45
+ */
46
+ readonly tools: {
47
+ list: (user?: string) => Promise<ToolDefinition[]>;
48
+ execute: (name: string, args: Record<string, unknown>, user?: string) => Promise<string>;
49
+ /**
50
+ * **Cosa il progetto ha abilitato**: id e nome, senza coniare. Dalla 0.14.0.
51
+ *
52
+ * È la domanda di chi **configura**, non di chi esegue: un pannello che deve offrire le
53
+ * skill disponibili, un controllo di installazione. Per questo non porta endpoint, token né
54
+ * digest — quelli arrivano col conio, uno per tool, ed è lì che restano.
55
+ *
56
+ * La differenza con `list()` è **quanto costa chiederlo**: `list()` conia (e quindi impegna
57
+ * un blocco sul conto) perché deve restituire i tool *pronti all'uso*, verificati contro il
58
+ * documento che serviranno. Questa no: non impegna niente e non apre nessuna chiave, quindi
59
+ * è quella giusta ogni volta che la risposta serve a mostrare un elenco e non a chiamarlo.
60
+ */
61
+ enabled: () => Promise<Array<{
62
+ id: string;
63
+ name: string;
64
+ }>>;
65
+ };
66
+ /** Catalogo modelli disponibili sul provider (OpenRouter), per chi vuole scegliere
67
+ * `model` senza conoscerlo a priori — es. un selettore in UI. Risposta cachata dal
68
+ * Cloud per qualche minuto: chiamarlo spesso non genera traffico verso il provider. */
69
+ readonly models: {
70
+ list: () => Promise<OpenRouterModel[]>;
71
+ };
72
+ /**
73
+ * Anagrafica dei conti del progetto — i tenant finali a cui è attribuito il consumo.
74
+ *
75
+ * Progetto e ambiente **non si passano**: sono quelli della project key con cui è stato
76
+ * costruito il client. Non esiste un parametro con cui operare sul progetto di qualcun
77
+ * altro, e quindi non c'è la classe di errore corrispondente.
78
+ */
79
+ readonly accounts: {
80
+ create: (params: AccountCreateParams) => Promise<AccountInfo>;
81
+ list: () => Promise<AccountInfo[]>;
82
+ get: (userKey: string) => Promise<AccountInfo>;
83
+ update: (userKey: string, params: AccountUpdateParams) => Promise<AccountInfo>;
84
+ /**
85
+ * Chiude la prova di un conto aperto con `type: "trial"`: da qui è un conto a pagamento.
86
+ * Dalla 0.13.0.
87
+ *
88
+ * **La chiama il verticale che ha incassato l'abbonamento**, perché è l'unico a sapere che è
89
+ * stato pagato. Senza, il conto resta una prova e il Cloud lo sospende alla scadenza anche se
90
+ * il cliente nel frattempo si è abbonato.
91
+ *
92
+ * Cosa cambia per quel conto: la parte non spesa della concessione di prova torna alla riserva
93
+ * del progetto (era un prestito per provare), cominciano la quota periodica del piano e la fee
94
+ * per conto, e se era sospeso **dalla propria scadenza** torna attivo — uno sospeso da un
95
+ * operatore resta sospeso, di proposito.
96
+ *
97
+ * Una volta sola e senza ritorno: su un conto che non è una prova risponde 422.
98
+ */
99
+ upgrade: (userKey: string) => Promise<AccountInfo>;
100
+ /**
101
+ * La **chiave di conto**: si emette quando l'operatività di quel conto va delegata a un
102
+ * perimetro che non controlli — il plugin di un cliente, un dispositivo, un'installazione.
103
+ * Chi la riceve può coniare runtime key **solo** entro il credito di
104
+ * quel conto, e non può nominarne un altro.
105
+ *
106
+ * È opzionale: un conto senza chiave resta operato da questo stesso client, con la
107
+ * project key, esattamente come oggi. Emetterla è un atto in più e volontario.
108
+ *
109
+ * **Una sola chiave viva per conto**: se ne esiste già una, `issue` risponde 409 —
110
+ * per sostituirla si usa `rotate`, che invalida la precedente nello stesso istante.
111
+ */
112
+ keys: {
113
+ /** Le chiavi attive del progetto+ambiente: quali conti hanno una credenziale in giro. */
114
+ list: () => Promise<AccountKeyInfo[]>;
115
+ /** Emette la chiave. **L'unico momento in cui il valore in chiaro esiste.** */
116
+ issue: (userKey: string, label?: string) => Promise<IssuedAccountKey>;
117
+ /** Ne emette una nuova e invalida la precedente. Da usare dopo un sospetto, o su un clone. */
118
+ rotate: (userKey: string) => Promise<IssuedAccountKey>;
119
+ /** Revoca senza sostituire: il conto resta, e torna operabile da qui. */
120
+ revoke: (userKey: string) => Promise<{
121
+ revoked: number;
122
+ }>;
123
+ };
124
+ };
125
+ /**
126
+ * Le runtime key OpenRouter che questo progetto ha ancora aperte.
127
+ *
128
+ * **Esiste perché il segreto non si conserva.** La credenziale vive solo nella memoria di chi
129
+ * l'ha coniata: un processo riavviato, o una seconda istanza dello stesso client, non sa
130
+ * più nominare le chiavi che ha in giro — mentre ognuna di esse occupa uno slot del piano
131
+ * (`maxOpenKeys`) finché non liquida. Questa lettura le rende nominabili, e quindi
132
+ * restituibili, invece di lasciarle scadere.
133
+ *
134
+ * Senza `userKey` è il conto del proprietario, lo stesso che il conio userebbe.
135
+ */
136
+ readonly runtimeKeys: {
137
+ /**
138
+ * La chiave con cui questo client sta lavorando, **coniandone una se non ne ha**.
139
+ *
140
+ * È la sostituzione onesta del vecchio "conia tu e passami la key": chi vuole vedere handle,
141
+ * tetto, impegno e scadenza all'avvio — o vuole poter **restituire** la chiave all'uscita,
142
+ * invece di lasciare uno slot di `maxOpenKeys` occupato per il TTL residuo — la chiama qui e
143
+ * continua a usare il client normalmente. Il segreto resta dentro, dove può scadere e
144
+ * rinnovarsi senza che nessuno se ne accorga.
145
+ *
146
+ * Chiamarla non è obbligatorio: la prima `create()` conia da sé.
147
+ *
148
+ * **`user` nomina il conto che paga**, ed è lo stesso campo di `create()`: una chiave è di un
149
+ * conto, quindi chiedere "la chiave di questo client" senza dire quale conto significa quella
150
+ * del proprietario del progetto. Chi chiama per conto di un tenant — per leggere i tool che il
151
+ * conio gli concede, per esempio — deve nominarlo, o aprirà un hold sul conto sbagliato.
152
+ */
153
+ ensure: (user?: string) => Promise<RuntimeKeyHandle>;
154
+ /**
155
+ * La chiave in mano **adesso**, senza coniarne una: `null` se non se n'è ancora coniata
156
+ * nessuna, o se l'ultima è stata scartata dopo un errore del provider.
157
+ *
158
+ * È la metà in sola lettura di `ensure`, e serve quando restituire una chiave è l'ultima cosa
159
+ * che si fa: fra l'avvio e l'uscita l'SDK può averne coniata un'altra — una chiave si esaurisce
160
+ * o scade a metà lavoro — e restituire l'handle di partenza lascerebbe viva quella vera,
161
+ * occupando uno slot di `maxOpenKeys` per il TTL residuo. Con `ensure` non si potrebbe: a
162
+ * processo in chiusura conierebbe la chiave che si sta cercando di rendere.
163
+ *
164
+ * `user` come in `ensure`: la chiave in mano **di quel conto**.
165
+ */
166
+ current: (user?: string) => RuntimeKeyHandle | null;
167
+ list: (userKey?: string) => Promise<RuntimeKeyInfo[]>;
168
+ /**
169
+ * Restituisce una chiave prima della scadenza: smette di funzionare sul provider subito, e
170
+ * il suo slot torna libero appena lo scheduler la liquida — un paio di minuti invece del
171
+ * TTL residuo. `revoked: false` se era già stata revocata.
172
+ *
173
+ * **L'handle la nomina, il conto la autorizza**: come per il conio, senza `userKey` è il
174
+ * conto del proprietario. Una chiave di un altro conto viene rifiutata con le stesse parole
175
+ * di una che non esiste.
176
+ */
177
+ revoke: (recordId: string, userKey?: string) => Promise<{
178
+ revoked: boolean;
179
+ }>;
180
+ };
181
+ /**
182
+ * Modelli decisionali "System One" (Jev di TypeSafe e affini) — non chat, non `/chat/completions`:
183
+ * uno stato in ingresso, un verdetto tipizzato con probabilità in uscita, sullo stesso conto e
184
+ * la stessa runtime key di ogni altra chiamata (`decisions.ts`).
185
+ *
186
+ * **`evaluate` è la chiamata diretta**, con la stessa contabilità di `createRaw()` — prenota il
187
+ * budget del tenant, conia/ri-conia se il provider rifiuta, regola il costo reale al giro
188
+ * successivo. **`tool` è l'integrazione nel loop di tool-use**: costruisce un
189
+ * `LocalToolDefinition` che un LLM conversazionale (avviato con `chat.completions.create()`)
190
+ * può chiamare a metà conversazione per ottenere una decisione veloce senza uscire dal loop —
191
+ * es. instradare un ticket, o segnalare un contenuto, mentre il modello principale continua a
192
+ * scrivere la risposta. Registralo con `ai.addTool(ai.decisions.tool({...}))`.
193
+ */
194
+ readonly decisions: {
195
+ evaluate: (params: EvaluateDecisionParams) => Promise<DecisionResult>;
196
+ tool: <TContext = unknown>(config: DecisionToolConfig<TContext>) => LocalToolDefinition<TContext>;
197
+ };
198
+ /**
199
+ * Voce sintetica (text-to-speech), drop-in di `audio.speech.create()` dell'SDK OpenAI — sullo
200
+ * stesso conto e la stessa runtime key di ogni altra chiamata (`speech.ts`). Dalla 0.20.0.
201
+ *
202
+ * **`create`** ha la stessa contabilità di `createRaw()`: prenota il budget del tenant, conia o
203
+ * ri-conia se il provider rifiuta la chiave, e **non dichiara nessun costo** — la spesa entra
204
+ * nell'`usage` della runtime key presso il provider come quella di una chat, e il ledger la
205
+ * liquida sull'hold del conio. Verificato sul campo il 2026-10-17.
206
+ *
207
+ * **`models`** è il catalogo dei modelli TTS con le voci che ciascuno ammette
208
+ * (`supported_voices`) e il listino, per un selettore che faccia scegliere la voce anche in
209
+ * base al costo come già fa per i modelli di chat. È un catalogo a sé: `ai.models.list()`
210
+ * **non li contiene** — OpenRouter li espone solo filtrando per modalità in uscita.
211
+ */
212
+ readonly audio: {
213
+ speech: {
214
+ create: (params: SpeechCreateParams) => Promise<SpeechResult>;
215
+ models: () => Promise<SpeechModel[]>;
216
+ };
217
+ };
218
+ /**
219
+ * Il saldo del **proprio** conto — solo con una chiave di conto (`/api/edge/balance`).
220
+ *
221
+ * Con una project key non esiste, e non è una lacuna da colmare: lì i conti sono molti e si
222
+ * nominano, quindi la stessa domanda è `ai.accounts.get(userKey)`. Le due superfici non offrono
223
+ * le stesse cose perché non hanno la stessa identità, e fingere il contrario significherebbe
224
+ * rispondere con il saldo di qualcun altro.
225
+ */
226
+ readonly balance: () => Promise<ContoBalance>;
227
+ /**
228
+ * @deprecated Non addebita niente e non manda nessuna richiesta: risolve con `amount: 0`.
229
+ * Tenuto perché un client scritto contro una versione precedente continui a compilare **e a
230
+ * girare**.
231
+ *
232
+ * Dichiarava il consumo di un servizio marketplace a esecuzione locale (`POST /api/usage`), ed
233
+ * era l'unico addebito del marketplace deciso da **chi chiama** invece che dal tool. Non c'è
234
+ * più perché quel modello non c'è più: ogni tool si registra a un endpoint, ci viene chiamato
235
+ * con un tool token e dichiara da sé quanto è costata la chiamata, contro un tetto che il conio
236
+ * gli ha dato. Prezzare a unità non è morto — è aritmetica che il
237
+ * tool fa; è morto il *client* che dichiara le unità.
238
+ *
239
+ * **Un servizio che si faceva pagare così, oggi, non si fa pagare.** Questo ramo tiene in piedi
240
+ * il codice, non il ricavo: se questa chiamata era l'unica contabilità di un servizio, quel
241
+ * servizio va registrato nel marketplace e deve fatturarsi contro il proprio tool token.
242
+ */
243
+ readonly usage: {
244
+ record: (params: UsageRecordParams) => Promise<UsageRecordResult>;
245
+ };
246
+ /**
247
+ * La sessione di attività in corso, o `null` prima del primo conio.
248
+ *
249
+ * **Non è una credenziale**: non autorizza niente, si può stampare, loggare e mettere in un
250
+ * ticket — ed è esattamente ciò per cui esiste, visto che la query di supporto a valle è
251
+ * `WHERE activity_session_id = '…'`. Prima non usciva da `AuthClient`, quindi chi integrava non
252
+ * aveva modo di scrivere nei propri log l'unico id con cui quella spesa si ritrova.
253
+ *
254
+ * Può **cambiare da sola**: il ledger ne sostituisce in silenzio una che non riconosce più
255
+ * (potata, o mai stata di questo conto), quindi va riletta dopo un conio e non memorizzata una
256
+ * volta per tutte.
257
+ */
258
+ get activitySessionId(): string | null;
259
+ constructor(options: FantasticoOptions);
260
+ /**
261
+ * Il tenant a cui attribuire questa chiamata — **e il rifiuto di nominarne uno sull'edge**.
262
+ *
263
+ * `user` è il campo OpenAI-compat con cui una project key dice a quale conto del progetto va il
264
+ * consumo. Con una chiave di conto quel conto è già deciso: è quello della credenziale, e
265
+ * nessuna rotta edge ne legge uno dal corpo. Ignorarlo in silenzio sarebbe peggio che
266
+ * rifiutarlo — chi lo passa crede di star separando due tenant, e vedrebbe invece la spesa di
267
+ * tutti e due finire sullo stesso conto, senza niente che lo dica.
268
+ */
269
+ private tenantOf;
270
+ /**
271
+ * L'anagrafica dei conti, e il posto unico in cui la sua superficie si verifica.
272
+ *
273
+ * `/api/conti/*` autentica con la project key e descrive **tutti** i conti di un progetto: non
274
+ * esiste su edge, e non è una rotta che manca — una chiave di conto ne vale uno solo, quindi
275
+ * "elenca i conti" lì è una domanda che non ha un soggetto.
276
+ */
277
+ private conti;
278
+ /** Registra un tool locale o un'azione-client dopo la costruzione (in alternativa/oltre a
279
+ * passarli nel costruttore via `tools`). */
280
+ addTool(tool: RegisteredTool): void;
281
+ /**
282
+ * Conia la runtime key e ci esegue `run`. Se il provider risponde `401`/`402` — o, meglio, se
283
+ * classifica lui stesso l'errore come `authentication`/`payment_required` — NON è un verdetto
284
+ * affidabile che "sono finiti i fondi del cliente": può essere il tetto di QUESTA key esaurito
285
+ * prima della sua scadenza per tempo, oppure un problema del tutto estraneo al cliente
286
+ * (una key già ritirata, o un problema del provider). L'ambiguità è **documentata dal
287
+ * provider**: il suo 402 vale "your account **or API key** has insufficient credits", e la doc
288
+ * dice apertamente di non distinguere i due casi. Quindi il verdetto non si dà, si prova: un solo
289
+ * retry a chiave nuova; se sparisce da solo, il chiamante non vede nulla. Se persiste, l'unico
290
+ * messaggio onesto è un errore di comunicazione col provider — MAI "sono finiti i tuoi fondi",
291
+ * perché a questo punto non lo sappiamo.
292
+ *
293
+ * **Su cosa si decide.** Su `ProviderError` (`provider-error.ts`), cioè sullo stato HTTP e su
294
+ * `metadata.error_type`, mai sul testo del messaggio. La regex che c'era prima leggeva la prosa
295
+ * del provider perché lo stato non sopravviveva alla pila: quel file racconta i due modi in cui
296
+ * sbagliava. Conseguenza per chi inietta un `LlmClient` proprio: per partecipare al ri-conio deve
297
+ * lanciare un `ProviderError` (esportato dall'indice); un `Error` qualunque è un errore e basta.
298
+ *
299
+ * L'UNICO caso in cui "fondi insufficienti" è un'affermazione vera è quando è
300
+ * FANTASTICO STESSO a rifiutarsi di coniare (`FantasticoError` da `getRuntimeKey()`,
301
+ * es. `INSUFFICIENT_RESERVE`/`ALLOWANCE_EXHAUSTED`): lì il verdetto è nostro, autorevole,
302
+ * e si propaga così com'è — non va mai reinterpretato.
303
+ */
304
+ /**
305
+ * Il `max_tokens` da mandare quando il chiamante non ne ha scritto uno.
306
+ *
307
+ * **Va calcolato dopo il conio e dentro il retry**, non una volta per chiamata: la chiave del
308
+ * secondo tentativo e un'altra chiave, con un tetto suo, e riusare il numero della prima e
309
+ * esattamente il caso che il retry esiste per riparare.
310
+ *
311
+ * `undefined` quando non c'e niente da calcolare — catalogo muto, o chiave capiente. In
312
+ * entrambi i casi non si scrive nulla sul filo. Vedi `max-tokens.ts` per il perche del conto.
313
+ */
314
+ private defaultMaxTokens;
315
+ /**
316
+ * Come un errore del provider arriva al chiamante.
317
+ *
318
+ * Se il provider l'ha classificato, si passa il suo stato e un codice `PROVIDER_*` che dice **chi
319
+ * ha parlato**: `status` in questo SDK porta già, verbatim, lo stato HTTP del Cloud (`auth.ts`,
320
+ * `api()`), quindi il numero da solo non basta più a distinguere i due mittenti — e il `403` lo
321
+ * usano entrambi, per cose con rimedi opposti. Vedi `providerFailure()`.
322
+ *
323
+ * Quel che resta `INTERNAL_ERROR` 500 è solo ciò che davvero non sappiamo attribuire.
324
+ */
325
+ private asFantasticoError;
326
+ /** La configurazione dei ritentativi transitori, con la scadenza entro cui è lecito dormire. */
327
+ private retryConfig;
328
+ /**
329
+ * Un `LlmClient` che si ri-conia da solo, **per singola chiamata**.
330
+ *
331
+ * È qui che sta la differenza con la versione precedente, e non è una rifinitura. Prima il
332
+ * ri-conio avvolgeva l'intera `orchestrate()`, e l'orchestratore riparte da `[...messages]`: un
333
+ * 402 al quarto turno non riprendeva il lavoro, lo **rigiocava da capo** — i turni 1-3 rimandati
334
+ * al modello e ripagati sulla chiave nuova, e i loro tool **rieseguiti**, quelli remoti con un
335
+ * altro addebito e quelli locali con i loro effetti. Il primo tentativo non veniva rimborsato:
336
+ * era semplicemente perso. E il momento in cui un tetto di chiave si esaurisce è, per
337
+ * costruzione, **a metà conversazione**: il ri-conio scattava quindi proprio dove rigiocare
338
+ * costava di più.
339
+ *
340
+ * Spostato qui, l'unica cosa rifatta è la richiesta HTTP che è fallita. Lo stato accumulato resta
341
+ * dov'è sempre stato, dentro l'orchestratore, che non si accorge di niente.
342
+ *
343
+ * **E il `max_tokens` si ricalcola da sé**, che è l'invariante che il vecchio commento
344
+ * raccomandava a voce: si calcola dentro `run`, cioè dopo il conio, quindi la chiave nuova porta
345
+ * il proprio tetto invece di ereditare il numero di quella scartata — che è esattamente il caso
346
+ * che il ri-conio esiste per riparare.
347
+ */
348
+ private remintingLlm;
349
+ private withProviderRetry;
350
+ private createCompletion;
351
+ /**
352
+ * I tool remoti di questo giro: conia se serve, poi chiede a ciascun tool le proprie funzioni.
353
+ *
354
+ * **Il conio viene prima, ed e' l'unico ordine possibile** da quando la discovery non ha un
355
+ * ripiego: le concessioni — endpoint, token, digest — arrivano *con* la
356
+ * runtime key, quindi senza chiave non c'e' niente da scoprire. Prima l'ordine era comodo e non
357
+ * necessario, perche' senza token si ricadeva sul catalogo aggregato del Cloud; ora chiedere
358
+ * prima di coniare significa un elenco vuoto.
359
+ *
360
+ * **Qui e non in tre posti.** Lo facevano `create`, `resume` e `ai.tools`, e il terzo non coniava:
361
+ * `ai.tools.list()` su un client appena costruito rispondeva quindi sempre `[]` — la superficie
362
+ * che esiste apposta per chi orchestra da se, muta senza dire perche'.
363
+ */
364
+ private remoteTools;
365
+ /**
366
+ * Riprende un'orchestrazione sospesa da create() (`status: "pending"`). Rifà lo stesso
367
+ * bootstrap di create() (MCP abilitati, runtime key) perché in un backend a richieste può
368
+ * arrivare come chiamata HTTP separata, anche minuti dopo — mai supporre che sia lo stesso
369
+ * processo che l'ha messa in pausa.
370
+ */
371
+ private resumeCompletion;
372
+ /**
373
+ * Una sola chiamata a OpenRouter — nessuna orchestrazione interna, nessun tool eseguito
374
+ * da FANTASTICO. Riusa la stessa runtime key + prenotazione budget di create(): il
375
+ * chiamante ottiene lo stesso pattern "prenota-e-salda ad ogni giro"
376
+ * semplicemente richiamando createRaw() a ogni passo del proprio loop di tool-use.
377
+ */
378
+ private createRawCompletion;
379
+ /**
380
+ * Una chiamata all'endpoint Decisions (`decisions.ts`) — stessa contabilità di `createRawCompletion`:
381
+ * prenota il budget del tenant prima, regola il costo reale dopo. È il punto unico da cui passano
382
+ * sia `ai.decisions.evaluate()` sia i tool costruiti con `ai.decisions.tool()`, cosicché una
383
+ * decisione chiamata da dentro un loop di tool-use paghi sullo stesso conto delle chiamate dirette.
384
+ */
385
+ private evaluateDecision;
386
+ /**
387
+ * Una chiamata all'endpoint TTS (`speech.ts`) — la stessa contabilità di `createRawCompletion`
388
+ * **meno la regolazione del costo**, e la differenza va detta: la risposta è audio e non porta
389
+ * un `usage`; il costo si legge da `GET /generation` solo dopo circa cinque secondi. Aspettarli
390
+ * dentro la chiamata la rallenterebbe per un numero che serve soltanto al preavviso di
391
+ * `lowBalance` — l'addebito vero lo misura il ledger dall'usage della chiave, qui come per una
392
+ * chat. Quindi `unsettled` non si tocca, e il risultato porta l'id della generazione e non una
393
+ * stima.
394
+ */
395
+ private createSpeech;
396
+ /**
397
+ * Chiamata autenticata al control plane con la project key.
398
+ *
399
+ * Il codice d'errore arriva dal Cloud e si propaga così com'è (`ALLOWANCE_EXHAUSTED`,
400
+ * `CONTO_KEY_EXISTS`, `SCOPE_FORBIDDEN`…): tradurlo in un codice generico farebbe perdere
401
+ * al chiamante l'unica informazione che gli dice cosa fare.
402
+ */
403
+ private api;
404
+ /**
405
+ * Salda subito il consumo residuo di un tenant senza avviare una nuova chiamata.
406
+ * Da invocare a fine sessione, così l'ultimo costo non resta in sospeso fino al giro
407
+ * successivo (la misura del provider resta comunque la fonte di verità).
408
+ */
409
+ flushBudget(userKey: string): Promise<void>;
410
+ }