@volund-ia/sdk 0.2.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.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/index.cjs +537 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +356 -0
- package/dist/index.d.mts +356 -0
- package/dist/index.mjs +524 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { createParser } from "eventsource-parser";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
/** Erro base de todo o SDK. */
|
|
4
|
+
var VolundError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
status;
|
|
7
|
+
constructor(message, opts) {
|
|
8
|
+
super(message, { cause: opts.cause });
|
|
9
|
+
this.name = "VolundError";
|
|
10
|
+
this.code = opts.code;
|
|
11
|
+
this.status = opts.status;
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** 401 — chave ausente ou inválida. */
|
|
16
|
+
var VolundAuthError = class extends VolundError {
|
|
17
|
+
constructor(message, code = "invalid_api_key", cause) {
|
|
18
|
+
super(message, {
|
|
19
|
+
code,
|
|
20
|
+
status: 401,
|
|
21
|
+
cause
|
|
22
|
+
});
|
|
23
|
+
this.name = "VolundAuthError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/** 403 — a chave não tem acesso a este agente/run. */
|
|
27
|
+
var VolundForbiddenError = class extends VolundError {
|
|
28
|
+
constructor(message, cause) {
|
|
29
|
+
super(message, {
|
|
30
|
+
code: "forbidden",
|
|
31
|
+
status: 403,
|
|
32
|
+
cause
|
|
33
|
+
});
|
|
34
|
+
this.name = "VolundForbiddenError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** 404 — agente ou run inexistente. */
|
|
38
|
+
var VolundNotFoundError = class extends VolundError {
|
|
39
|
+
constructor(message, code = "agent_not_found", cause) {
|
|
40
|
+
super(message, {
|
|
41
|
+
code,
|
|
42
|
+
status: 404,
|
|
43
|
+
cause
|
|
44
|
+
});
|
|
45
|
+
this.name = "VolundNotFoundError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** 409 — já existe um run ativo na thread (só na continuação). */
|
|
49
|
+
var VolundRunBusyError = class extends VolundError {
|
|
50
|
+
constructor(message, cause) {
|
|
51
|
+
super(message, {
|
|
52
|
+
code: "run_busy",
|
|
53
|
+
status: 409,
|
|
54
|
+
cause
|
|
55
|
+
});
|
|
56
|
+
this.name = "VolundRunBusyError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/** O run terminou com `status: "failed"`. Lançado por `run.result()`. */
|
|
60
|
+
var VolundRunFailedError = class extends VolundError {
|
|
61
|
+
constructor(message, cause) {
|
|
62
|
+
super(message, {
|
|
63
|
+
code: "run_failed",
|
|
64
|
+
cause
|
|
65
|
+
});
|
|
66
|
+
this.name = "VolundRunFailedError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* O run pausou esperando ação humana (HITL — na V1, preenchimento de cofre).
|
|
71
|
+
* `run.result()` lança isto porque o stream termina sem `run_finished`. Quem
|
|
72
|
+
* usa `run.stream()` recebe o evento `awaiting_input` normalmente, sem exceção.
|
|
73
|
+
*/
|
|
74
|
+
var VolundAwaitingInputError = class extends VolundError {
|
|
75
|
+
requestId;
|
|
76
|
+
kind;
|
|
77
|
+
constructor(requestId, kind) {
|
|
78
|
+
super(`Run pausou aguardando entrada do tipo "${kind}" (request ${requestId}).`, { code: "awaiting_input" });
|
|
79
|
+
this.name = "VolundAwaitingInputError";
|
|
80
|
+
this.requestId = requestId;
|
|
81
|
+
this.kind = kind;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
/** Constrói a subclasse certa a partir de uma resposta de erro da API. */
|
|
85
|
+
function errorFromApiResponse(status, body) {
|
|
86
|
+
const code = body.error ?? "internal_error";
|
|
87
|
+
const message = body.message ?? `Requisição falhou (HTTP ${status}).`;
|
|
88
|
+
switch (code) {
|
|
89
|
+
case "missing_api_key":
|
|
90
|
+
case "invalid_api_key": return new VolundAuthError(message, code);
|
|
91
|
+
case "forbidden": return new VolundForbiddenError(message);
|
|
92
|
+
case "agent_not_found":
|
|
93
|
+
case "run_not_found": return new VolundNotFoundError(message, code);
|
|
94
|
+
case "run_busy": return new VolundRunBusyError(message);
|
|
95
|
+
default: return new VolundError(message, {
|
|
96
|
+
code,
|
|
97
|
+
status
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Base do backoff exponencial (ms): 300, 600, 1200... */
|
|
102
|
+
const RETRY_BASE_MS = 300;
|
|
103
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
104
|
+
/**
|
|
105
|
+
* Combina o sinal externo (cancel do usuário) com um timeout. O fetch recebe o
|
|
106
|
+
* sinal combinado. `clearTimer()` desarma o timeout (chame ao receber a resposta,
|
|
107
|
+
* p/ o timer não abortar o stream em andamento); a ligação com o sinal externo
|
|
108
|
+
* permanece, então `run.cancel()` segue funcionando durante todo o stream.
|
|
109
|
+
*/
|
|
110
|
+
function linkAbort(external, timeoutMs) {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
let timedOut = false;
|
|
113
|
+
const onExternalAbort = () => controller.abort();
|
|
114
|
+
if (external) if (external.aborted) controller.abort();
|
|
115
|
+
else external.addEventListener("abort", onExternalAbort, { once: true });
|
|
116
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
117
|
+
timedOut = true;
|
|
118
|
+
controller.abort();
|
|
119
|
+
}, timeoutMs) : void 0;
|
|
120
|
+
return {
|
|
121
|
+
signal: controller.signal,
|
|
122
|
+
/** Foi o timeout (e não o cancel do usuário) que abortou? */
|
|
123
|
+
timedOut: () => timedOut,
|
|
124
|
+
/** Desarma o timeout MANTENDO a ligação com o sinal externo. Use no sucesso,
|
|
125
|
+
* p/ `run.cancel()` seguir abortando o stream em andamento. */
|
|
126
|
+
clearTimer: () => {
|
|
127
|
+
if (timer) clearTimeout(timer);
|
|
128
|
+
},
|
|
129
|
+
/** Desarma o timer E solta o listener do sinal externo. Use em tentativas
|
|
130
|
+
* abandonadas (erro/retry) p/ não vazar listeners no signal do usuário. */
|
|
131
|
+
dispose: () => {
|
|
132
|
+
if (timer) clearTimeout(timer);
|
|
133
|
+
if (external) external.removeEventListener("abort", onExternalAbort);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Faz POST num endpoint `/stream` e devolve a `Response` SSE crua. Lança a
|
|
139
|
+
* subclasse de `VolundError` apropriada se a resposta for um erro. Aplica timeout
|
|
140
|
+
* pré-stream e retry (rede/5xx) conforme a config.
|
|
141
|
+
*/
|
|
142
|
+
async function postStream(cfg, path, body, signal) {
|
|
143
|
+
const url = `${cfg.baseUrl.replace(/\/+$/, "")}${path}`;
|
|
144
|
+
const timeoutMs = cfg.timeoutMs ?? 6e4;
|
|
145
|
+
const maxRetries = cfg.maxRetries ?? 0;
|
|
146
|
+
const sleep = cfg.sleep ?? defaultSleep;
|
|
147
|
+
const payload = JSON.stringify(body);
|
|
148
|
+
let lastError;
|
|
149
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
150
|
+
if (signal?.aborted) throw new VolundError(`Requisição a ${path} cancelada.`, {
|
|
151
|
+
code: "network_error",
|
|
152
|
+
cause: signal.reason
|
|
153
|
+
});
|
|
154
|
+
const link = linkAbort(signal, timeoutMs);
|
|
155
|
+
let res;
|
|
156
|
+
try {
|
|
157
|
+
res = await cfg.fetch(url, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: {
|
|
160
|
+
...cfg.defaultHeaders,
|
|
161
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
Accept: "text/event-stream"
|
|
164
|
+
},
|
|
165
|
+
body: payload,
|
|
166
|
+
signal: link.signal
|
|
167
|
+
});
|
|
168
|
+
} catch (cause) {
|
|
169
|
+
link.dispose();
|
|
170
|
+
if (link.timedOut()) lastError = new VolundError(`Timeout (${timeoutMs}ms) esperando resposta de ${path}.`, {
|
|
171
|
+
code: "timeout",
|
|
172
|
+
cause
|
|
173
|
+
});
|
|
174
|
+
else if (signal?.aborted) throw new VolundError(`Requisição a ${path} cancelada.`, {
|
|
175
|
+
code: "network_error",
|
|
176
|
+
cause
|
|
177
|
+
});
|
|
178
|
+
else lastError = new VolundError(`Falha de rede ao chamar ${path}.`, {
|
|
179
|
+
code: "network_error",
|
|
180
|
+
cause
|
|
181
|
+
});
|
|
182
|
+
if (attempt < maxRetries) {
|
|
183
|
+
await sleep(RETRY_BASE_MS * 2 ** attempt);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
throw lastError;
|
|
187
|
+
}
|
|
188
|
+
link.clearTimer();
|
|
189
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
190
|
+
if (res.ok && contentType.includes("text/event-stream")) return res;
|
|
191
|
+
link.dispose();
|
|
192
|
+
let errBody = {};
|
|
193
|
+
try {
|
|
194
|
+
errBody = await res.json();
|
|
195
|
+
} catch {
|
|
196
|
+
errBody = { message: `Resposta inesperada (HTTP ${res.status}).` };
|
|
197
|
+
}
|
|
198
|
+
const mapped = errorFromApiResponse(res.status, errBody);
|
|
199
|
+
if (res.status >= 500 && attempt < maxRetries) {
|
|
200
|
+
lastError = mapped;
|
|
201
|
+
await sleep(RETRY_BASE_MS * 2 ** attempt);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw mapped;
|
|
205
|
+
}
|
|
206
|
+
throw lastError ?? new VolundError(`Falha ao chamar ${path}.`, { code: "network_error" });
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/sse.ts
|
|
210
|
+
/**
|
|
211
|
+
* Parser SSE → `VolundEvent`. Camada fina sobre `eventsource-parser`, o mesmo
|
|
212
|
+
* parser usado pelo Vercel AI SDK e pelo SDK da OpenAI. Ele resolve, de graça,
|
|
213
|
+
* as três armadilhas do wire do Volund OS (ver `sse-adapter.ts` no servidor):
|
|
214
|
+
*
|
|
215
|
+
* 1. Heartbeat `: ping\n\n` — linhas de comentário são ignoradas pelo parser.
|
|
216
|
+
* 2. Campo `id: <n>` por frame — exposto como `event.id` (reservado p/
|
|
217
|
+
* reconexão na V2); a V1 não o usa.
|
|
218
|
+
* 3. `data:` multi-linha / partido entre chunks — o parser bufferiza e
|
|
219
|
+
* concatena conforme a spec SSE.
|
|
220
|
+
*
|
|
221
|
+
* Cada `event.data` é um JSON de um único `VolundEvent`. JSON inválido ou tipo
|
|
222
|
+
* desconhecido é IGNORADO (regra de ouro [D4]: clientes ignoram o que não
|
|
223
|
+
* conhecem — permite minor bumps sem quebrar).
|
|
224
|
+
*
|
|
225
|
+
* Usamos a API de callback (`createParser`) + um reader manual em vez do
|
|
226
|
+
* `EventSourceParserStream` para evitar o atrito de variância de tipos entre
|
|
227
|
+
* `TextDecoderStream` e `ReadableStream<Uint8Array>` no `lib.dom`.
|
|
228
|
+
*/
|
|
229
|
+
const KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
230
|
+
"run_started",
|
|
231
|
+
"thinking_delta",
|
|
232
|
+
"assistant_text_delta",
|
|
233
|
+
"tool_call",
|
|
234
|
+
"tool_result",
|
|
235
|
+
"awaiting_input",
|
|
236
|
+
"run_finished"
|
|
237
|
+
]);
|
|
238
|
+
function isVolundEvent(value) {
|
|
239
|
+
return typeof value === "object" && value !== null && typeof value.type === "string" && KNOWN_TYPES.has(value.type);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Transforma o corpo de uma resposta `text/event-stream` numa sequência de
|
|
243
|
+
* `VolundEvent`. Web-standard: roda em Node ≥18, Deno, Bun, Workers e browser.
|
|
244
|
+
*/
|
|
245
|
+
async function* parseVolundSSE(body) {
|
|
246
|
+
const queue = [];
|
|
247
|
+
const parser = createParser({ onEvent(event) {
|
|
248
|
+
if (!event.data) return;
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(event.data);
|
|
252
|
+
} catch {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (isVolundEvent(parsed)) queue.push(parsed);
|
|
256
|
+
} });
|
|
257
|
+
const reader = body.getReader();
|
|
258
|
+
const decoder = new TextDecoder();
|
|
259
|
+
try {
|
|
260
|
+
while (true) {
|
|
261
|
+
const { done, value } = await reader.read();
|
|
262
|
+
if (done) break;
|
|
263
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
264
|
+
while (queue.length > 0) yield queue.shift();
|
|
265
|
+
}
|
|
266
|
+
const tail = decoder.decode();
|
|
267
|
+
if (tail) {
|
|
268
|
+
parser.feed(tail);
|
|
269
|
+
while (queue.length > 0) yield queue.shift();
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
reader.releaseLock();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/run.ts
|
|
277
|
+
/**
|
|
278
|
+
* `Run` — uma execução de agente em andamento. Espelha o `run` do Cursor SDK:
|
|
279
|
+
* `.stream()` (eventos ao vivo), `.result()` (atalho p/ o texto final) e
|
|
280
|
+
* `.cancel()` (fecha a conexão; o servidor mata a sandbox).
|
|
281
|
+
*/
|
|
282
|
+
var Run = class {
|
|
283
|
+
#id;
|
|
284
|
+
#response;
|
|
285
|
+
#abort;
|
|
286
|
+
#consumed = false;
|
|
287
|
+
constructor(response, id, abort) {
|
|
288
|
+
this.#response = response;
|
|
289
|
+
this.#id = id;
|
|
290
|
+
this.#abort = abort;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* `run_id` (== `thread_id` no Volund OS). Use em `agents.continue`.
|
|
294
|
+
* Para um run NOVO, só fica disponível depois que o primeiro evento
|
|
295
|
+
* (`run_started`) é consumido via `stream()`/`result()` — antes disso é "".
|
|
296
|
+
* Em `agents.continue`, já vem preenchido (você passou o `runId`).
|
|
297
|
+
*/
|
|
298
|
+
get id() {
|
|
299
|
+
return this.#id;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Itera os `VolundEvent` conforme chegam. ⚠️ Consumível UMA vez (é um stream
|
|
303
|
+
* de rede) — não chame `stream()` e `result()` no mesmo `Run`.
|
|
304
|
+
*
|
|
305
|
+
* Se você ABANDONAR o stream no meio (um `break`/`throw` antes de um evento
|
|
306
|
+
* terminal), a conexão é fechada automaticamente — o servidor mata o sandbox e
|
|
307
|
+
* não vaza recurso (§3.6/§4.2 da proposta). Já em `run_finished`/`awaiting_input`
|
|
308
|
+
* o servidor encerra sozinho, então NÃO abortamos (abortar no `awaiting_input`
|
|
309
|
+
* mataria um run parqueado p/ vault e quebraria o resume — §3.5).
|
|
310
|
+
*/
|
|
311
|
+
async *stream() {
|
|
312
|
+
if (this.#consumed) throw new VolundError("Este run já foi consumido (stream/result só uma vez).", { code: "stream_error" });
|
|
313
|
+
this.#consumed = true;
|
|
314
|
+
const body = this.#response.body;
|
|
315
|
+
if (!body) throw new VolundError("Resposta de streaming sem corpo legível.", { code: "stream_error" });
|
|
316
|
+
let serverClosing = false;
|
|
317
|
+
try {
|
|
318
|
+
for await (const event of parseVolundSSE(body)) {
|
|
319
|
+
if (event.type === "run_started" && event.run_id) this.#id = event.run_id;
|
|
320
|
+
if (event.type === "run_finished" || event.type === "awaiting_input") serverClosing = true;
|
|
321
|
+
yield event;
|
|
322
|
+
}
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (this.#abort.signal.aborted) return;
|
|
325
|
+
throw err;
|
|
326
|
+
} finally {
|
|
327
|
+
if (!serverClosing) this.#abort.abort();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Espera o run terminar e devolve o texto final + uso de tokens.
|
|
332
|
+
* Lança `VolundRunFailedError` se o run falhar e `VolundAwaitingInputError`
|
|
333
|
+
* se ele pausar para HITL (vault). Para esses casos, prefira `stream()`.
|
|
334
|
+
*/
|
|
335
|
+
async result() {
|
|
336
|
+
let text = "";
|
|
337
|
+
for await (const event of this.stream()) switch (event.type) {
|
|
338
|
+
case "assistant_text_delta":
|
|
339
|
+
text += event.delta;
|
|
340
|
+
break;
|
|
341
|
+
case "awaiting_input": throw new VolundAwaitingInputError(event.request_id, event.kind);
|
|
342
|
+
case "run_finished":
|
|
343
|
+
if (event.status === "failed") throw new VolundRunFailedError(event.error ?? "Run falhou sem motivo informado.");
|
|
344
|
+
return {
|
|
345
|
+
output: event.output ?? text,
|
|
346
|
+
usage: event.usage
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
output: text,
|
|
351
|
+
usage: null
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** Cancela o run: aborta a conexão → o servidor mata a sandbox. */
|
|
355
|
+
cancel() {
|
|
356
|
+
this.#abort.abort();
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/agents.ts
|
|
361
|
+
/**
|
|
362
|
+
* `volund.agents` — dispara (`run`) e continua (`continue`) execuções de agente.
|
|
363
|
+
* DX espelha o Cursor SDK: `agents.run(...)` → `Run` com `.stream()/.result()`.
|
|
364
|
+
*/
|
|
365
|
+
/** Combina o sinal do usuário com o sinal interno de `run.cancel()`. */
|
|
366
|
+
function linkSignals(controller, external) {
|
|
367
|
+
if (!external) return;
|
|
368
|
+
if (external.aborted) {
|
|
369
|
+
controller.abort();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
external.addEventListener("abort", () => controller.abort(), { once: true });
|
|
373
|
+
}
|
|
374
|
+
/** V1 só roda na nuvem; o gancho `execution` já existe p/ a V2 (local.cwd). */
|
|
375
|
+
function assertCloud(execution) {
|
|
376
|
+
if (execution && execution !== "cloud") throw new VolundError("execution: \"local\" ainda não é suportado (chega na V2). Use \"cloud\" ou omita.", { code: "unsupported" });
|
|
377
|
+
}
|
|
378
|
+
var Agents = class {
|
|
379
|
+
#http;
|
|
380
|
+
constructor(http) {
|
|
381
|
+
this.#http = http;
|
|
382
|
+
}
|
|
383
|
+
/** Dispara um run novo (cria uma thread) e devolve um `Run` em streaming. */
|
|
384
|
+
async run(options) {
|
|
385
|
+
assertCloud(options.execution);
|
|
386
|
+
const controller = new AbortController();
|
|
387
|
+
linkSignals(controller, options.signal);
|
|
388
|
+
const body = { input: options.input };
|
|
389
|
+
if (options.files?.length) body.files = options.files;
|
|
390
|
+
const res = await postStream(this.#http, `/api/v1/agents/${encodeURIComponent(options.agentId)}/stream`, body, controller.signal);
|
|
391
|
+
return new Run(res, runIdFromResponse(res), controller);
|
|
392
|
+
}
|
|
393
|
+
/** Continua uma conversa existente (mesma thread). */
|
|
394
|
+
async continue(options) {
|
|
395
|
+
assertCloud(options.execution);
|
|
396
|
+
const controller = new AbortController();
|
|
397
|
+
linkSignals(controller, options.signal);
|
|
398
|
+
const body = { input: options.input };
|
|
399
|
+
if (options.files?.length) body.files = options.files;
|
|
400
|
+
return new Run(await postStream(this.#http, `/api/v1/runs/${encodeURIComponent(options.runId)}/stream`, body, controller.signal), options.runId, controller);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Para um run NOVO o `run_id` só existe no primeiro evento (`run_started`) — o
|
|
405
|
+
* servidor não o devolve em header. Então iniciamos o `Run` com "" e ele faz o
|
|
406
|
+
* backfill do id ao consumir o `run_started` (ver `Run.stream`). Mantemos um
|
|
407
|
+
* fast-path opcional por header caso o servidor passe a ecoá-lo no futuro.
|
|
408
|
+
*/
|
|
409
|
+
function runIdFromResponse(res) {
|
|
410
|
+
return res.headers.get("x-volund-run-id") ?? "";
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/client.ts
|
|
414
|
+
/**
|
|
415
|
+
* `VolundOS` — ponto de entrada do SDK. Configuração mínima: uma API key.
|
|
416
|
+
*
|
|
417
|
+
* const volund = new VolundOS({ apiKey: process.env.VOLUND_API_KEY! });
|
|
418
|
+
* const run = await volund.agents.run({ agentId, input });
|
|
419
|
+
*/
|
|
420
|
+
/** Endpoint de produção do Volund OS (decisão de proposta §6). */
|
|
421
|
+
const DEFAULT_BASE_URL = "https://os.volund.com.br";
|
|
422
|
+
var VolundOS = class {
|
|
423
|
+
/** Disparo e continuação de runs de agente. */
|
|
424
|
+
agents;
|
|
425
|
+
constructor(config) {
|
|
426
|
+
if (!config?.apiKey || typeof config.apiKey !== "string") throw new VolundError("apiKey é obrigatória.", { code: "missing_api_key" });
|
|
427
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
428
|
+
if (typeof fetchImpl !== "function") throw new VolundError("fetch global indisponível. Use Node ≥18 ou injete `fetch` no config.", { code: "unsupported" });
|
|
429
|
+
const http = {
|
|
430
|
+
apiKey: config.apiKey,
|
|
431
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
432
|
+
fetch: (...args) => fetchImpl(...args),
|
|
433
|
+
...config.defaultHeaders ? { defaultHeaders: config.defaultHeaders } : {},
|
|
434
|
+
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
435
|
+
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {}
|
|
436
|
+
};
|
|
437
|
+
this.agents = new Agents(http);
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/protocol/events.ts
|
|
442
|
+
/**
|
|
443
|
+
* ⚠️ ARQUIVO VENDORADO — NÃO EDITE À MÃO ABAIXO DO SENTINEL.
|
|
444
|
+
*
|
|
445
|
+
* Cópia fiel de `lib/agent/connectors/api/events.ts` do repo `volund-os`
|
|
446
|
+
* (a FONTE ÚNICA do contrato VolundEvent v1). O servidor emite estes eventos;
|
|
447
|
+
* este pacote os entrega tipados. Manter os dois em sincronia é invariante do
|
|
448
|
+
* projeto — o CI roda `scripts/check-protocol-drift.mjs`, que falha se o
|
|
449
|
+
* conteúdo abaixo do sentinel divergir do upstream.
|
|
450
|
+
*
|
|
451
|
+
* Para atualizar: rode `npm run sync:protocol` (copia do volund-os) — nunca
|
|
452
|
+
* edite o corpo manualmente. Promover para um pacote `@volund/protocol`
|
|
453
|
+
* publicado no futuro é trivial: este diretório não importa nada do SDK.
|
|
454
|
+
*/
|
|
455
|
+
/**
|
|
456
|
+
* Contrato público de eventos do Volund OS SDK — VERSÃO 1.
|
|
457
|
+
*
|
|
458
|
+
* Fonte única (graduada de `docs/prototypes/sse-adapter/events.ts`). É o que o
|
|
459
|
+
* servidor (Parte A) emite via SSE e o que o SDK (Parte B) entregará como
|
|
460
|
+
* AsyncIterable<VolundEvent>. Clientes externos dependem disso por anos —
|
|
461
|
+
* NÃO altere os tipos públicos sem bump de SCHEMA_VERSION.
|
|
462
|
+
*
|
|
463
|
+
* DECISÕES DESTA VERSÃO (aprovadas pelo time em 22/06):
|
|
464
|
+
* [D1] Naming = snake_case no wire. Consistente com a API pública que JÁ
|
|
465
|
+
* existe (GET /api/v1/runs/{id} e webhook) E com o padrão do ecossistema:
|
|
466
|
+
* Anthropic é 100% snake_case no fio; Cursor espelha em snake_case os
|
|
467
|
+
* campos estilo Claude Code. camelCase fica reservado p/ ergonomia futura
|
|
468
|
+
* na borda da linguagem (mapeado no SDK), não no contrato.
|
|
469
|
+
* [D2] Status reusa o vocabulário do GET /runs: "completed" | "failed".
|
|
470
|
+
* Pausa (HITL) = UM evento genérico `awaiting_input { kind }`. Na V1 o
|
|
471
|
+
* único `kind` é "vault": runs via API rodam com
|
|
472
|
+
* `--permission-mode bypassPermissions` (lib/agent/v2/run.ts), então NÃO
|
|
473
|
+
* pausam por aprovação de ferramenta. "approval" foi descopado da V1
|
|
474
|
+
* (decisão do time, 22/06) — reintroduzir quando/se a API suportar.
|
|
475
|
+
* [D3] tool_result.is_error CONFIRMADO real no nível stream-json: vem como
|
|
476
|
+
* `is_error: true` dentro do tool_result (no evento cru `user`). O
|
|
477
|
+
* types.ts da Volund não o tipa → o adapter lê do cru via cast. Mantido
|
|
478
|
+
* opcional (só presente/true em erro de ferramenta).
|
|
479
|
+
* [D4] Versionamento (modelo combinado): campo `protocol` no run_started
|
|
480
|
+
* (portátil p/ HTTP e CLI) p/ MAJOR; minor/patch via política
|
|
481
|
+
* "ignore unknown fields" (clientes ignoram campos desconhecidos).
|
|
482
|
+
* [D5] SSE `id:` por evento é emitido pelo ADAPTER (não é campo de payload),
|
|
483
|
+
* RESERVADO p/ reconexão futura (V2). A V1 NÃO promete retomada.
|
|
484
|
+
*
|
|
485
|
+
* ROTEAMENTO DE ERROS (por `type`, nunca por posição):
|
|
486
|
+
* - erro de ferramenta → tool_result.is_error (este arquivo, ToolResultEvent)
|
|
487
|
+
* - falha do run inteiro → run_finished status:"failed" + error
|
|
488
|
+
* - erro de transporte → cru `system/api_retry`: NÃO exposto na V1 (interno;
|
|
489
|
+
* retries são tratados dentro da nuvem).
|
|
490
|
+
*
|
|
491
|
+
* REGRAS DE OURO:
|
|
492
|
+
* 1. NÃO vazar interno: nada de session_id, sandbox, scratchDir, nome do
|
|
493
|
+
* executor (claude-code/cursor), api_retry, nem formatos do AI SDK.
|
|
494
|
+
* 2. Versionar (SCHEMA_VERSION). Mudança incompatível => bump MAJOR.
|
|
495
|
+
* 3. input/output são `unknown` mas DEVEM ser JSON-serializáveis.
|
|
496
|
+
* 4. Fonte única: servidor importa daqui; o pacote @volund/sdk publica daqui.
|
|
497
|
+
*
|
|
498
|
+
* INVARIANTES DO ADAPTER (blindagem contra inconsistências):
|
|
499
|
+
* I1. tool_call.input é emitido COMPLETO. O input chega vazio no 1º snapshot e
|
|
500
|
+
* completo depois. O adapter acumula input_json_delta e só emite no
|
|
501
|
+
* content_block_stop — nunca um input meio-vazio.
|
|
502
|
+
* I2. tool_result.output é NORMALIZADO: bloco MCP [{type:"text",text}] vira
|
|
503
|
+
* string; imagem `data:image/...` vira placeholder (não trafega binário);
|
|
504
|
+
* tamanho limitado. Saída sempre JSON-serializável e enxuta.
|
|
505
|
+
* I3. Sentinel de vault (__vault_request_pending__:<id>) NUNCA vaza como
|
|
506
|
+
* tool_result — o adapter detecta e emite awaiting_input{kind:"vault"},
|
|
507
|
+
* suprimindo o sentinel e o run_finished subsequente.
|
|
508
|
+
* I4. Texto duplicado: o adapter faz diff entre partials e snapshot cumulativo
|
|
509
|
+
* (gate hasSeenPartials), nunca reemite texto já enviado.
|
|
510
|
+
* I5. Stream drenado por inteiro (dispara persister + hooks via o tap em
|
|
511
|
+
* runAgentV2); abort do cliente → kill(). RESSALVA HITL: no vault o run
|
|
512
|
+
* termina sozinho (emite `result`); o adapter NÃO dá kill — só suprime a
|
|
513
|
+
* saída ao cliente e continua drenando até o `result` natural, pra o
|
|
514
|
+
* persister flipar a thread pra `awaiting_vault` e `handle.finished`
|
|
515
|
+
* resolver. Ver sse-adapter.ts.
|
|
516
|
+
* I6. V1 achata turnos: um único stream ordenado de deltas, sem id de bloco/
|
|
517
|
+
* turno no payload.
|
|
518
|
+
*/
|
|
519
|
+
/** Versão do schema. Bump em qualquer mudança incompatível. */
|
|
520
|
+
const SCHEMA_VERSION = "v1";
|
|
521
|
+
//#endregion
|
|
522
|
+
export { Agents, Run, SCHEMA_VERSION, VolundAuthError, VolundAwaitingInputError, VolundError, VolundForbiddenError, VolundNotFoundError, VolundOS, VolundRunBusyError, VolundRunFailedError, errorFromApiResponse, parseVolundSSE };
|
|
523
|
+
|
|
524
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#response","#id","#abort","#consumed","#http"],"sources":["../src/errors.ts","../src/http.ts","../src/sse.ts","../src/run.ts","../src/agents.ts","../src/client.ts","../src/protocol/events.ts"],"sourcesContent":["/**\r\n * Hierarquia de erros do SDK. Espelha os códigos que a API já devolve em\r\n * `{ error, message }` (ver `VolundErrorCode` no contrato) e adiciona erros\r\n * locais do cliente (rede, stream, modo não suportado).\r\n *\r\n * Roteie SEMPRE por `instanceof` ou por `err.code` — nunca por `err.message`.\r\n */\r\n\r\nimport type { VolundErrorCode } from \"./protocol/events\";\r\n\r\n/** Códigos locais do SDK que não vêm do servidor. */\r\nexport type VolundClientErrorCode =\r\n | \"network_error\" // falha de transporte (fetch rejeitou, DNS, offline)\r\n | \"timeout\" // resposta não chegou dentro de timeoutMs (fase pré-stream)\r\n | \"stream_error\" // corpo SSE ausente/ilegível, ou stream consumido 2x\r\n | \"run_failed\" // run_finished status:\"failed\"\r\n | \"awaiting_input\" // run pausou p/ HITL (vault) — ver AwaitingInputError\r\n | \"unsupported\"; // ex.: execution:\"local\" na V1\r\n\r\nexport type AnyVolundErrorCode = VolundErrorCode | VolundClientErrorCode;\r\n\r\nexport interface VolundErrorOptions {\r\n code: AnyVolundErrorCode;\r\n /** Status HTTP, quando o erro veio de uma resposta da API. */\r\n status?: number;\r\n cause?: unknown;\r\n}\r\n\r\n/** Erro base de todo o SDK. */\r\nexport class VolundError extends Error {\r\n readonly code: AnyVolundErrorCode;\r\n readonly status?: number;\r\n\r\n constructor(message: string, opts: VolundErrorOptions) {\r\n super(message, { cause: opts.cause });\r\n this.name = \"VolundError\";\r\n this.code = opts.code;\r\n this.status = opts.status;\r\n // Mantém a cadeia de protótipo correta após transpile p/ ES5/CJS.\r\n Object.setPrototypeOf(this, new.target.prototype);\r\n }\r\n}\r\n\r\n/** 401 — chave ausente ou inválida. */\r\nexport class VolundAuthError extends VolundError {\r\n constructor(message: string, code: VolundErrorCode = \"invalid_api_key\", cause?: unknown) {\r\n super(message, { code, status: 401, cause });\r\n this.name = \"VolundAuthError\";\r\n }\r\n}\r\n\r\n/** 403 — a chave não tem acesso a este agente/run. */\r\nexport class VolundForbiddenError extends VolundError {\r\n constructor(message: string, cause?: unknown) {\r\n super(message, { code: \"forbidden\", status: 403, cause });\r\n this.name = \"VolundForbiddenError\";\r\n }\r\n}\r\n\r\n/** 404 — agente ou run inexistente. */\r\nexport class VolundNotFoundError extends VolundError {\r\n constructor(message: string, code: VolundErrorCode = \"agent_not_found\", cause?: unknown) {\r\n super(message, { code, status: 404, cause });\r\n this.name = \"VolundNotFoundError\";\r\n }\r\n}\r\n\r\n/** 409 — já existe um run ativo na thread (só na continuação). */\r\nexport class VolundRunBusyError extends VolundError {\r\n constructor(message: string, cause?: unknown) {\r\n super(message, { code: \"run_busy\", status: 409, cause });\r\n this.name = \"VolundRunBusyError\";\r\n }\r\n}\r\n\r\n/** O run terminou com `status: \"failed\"`. Lançado por `run.result()`. */\r\nexport class VolundRunFailedError extends VolundError {\r\n constructor(message: string, cause?: unknown) {\r\n super(message, { code: \"run_failed\", cause });\r\n this.name = \"VolundRunFailedError\";\r\n }\r\n}\r\n\r\n/**\r\n * O run pausou esperando ação humana (HITL — na V1, preenchimento de cofre).\r\n * `run.result()` lança isto porque o stream termina sem `run_finished`. Quem\r\n * usa `run.stream()` recebe o evento `awaiting_input` normalmente, sem exceção.\r\n */\r\nexport class VolundAwaitingInputError extends VolundError {\r\n readonly requestId: string;\r\n readonly kind: \"vault\";\r\n\r\n constructor(requestId: string, kind: \"vault\") {\r\n super(`Run pausou aguardando entrada do tipo \"${kind}\" (request ${requestId}).`, {\r\n code: \"awaiting_input\",\r\n });\r\n this.name = \"VolundAwaitingInputError\";\r\n this.requestId = requestId;\r\n this.kind = kind;\r\n }\r\n}\r\n\r\n/** Constrói a subclasse certa a partir de uma resposta de erro da API. */\r\nexport function errorFromApiResponse(\r\n status: number,\r\n body: { error?: string; message?: string }\r\n): VolundError {\r\n const code = (body.error ?? \"internal_error\") as VolundErrorCode;\r\n const message = body.message ?? `Requisição falhou (HTTP ${status}).`;\r\n switch (code) {\r\n case \"missing_api_key\":\r\n case \"invalid_api_key\":\r\n return new VolundAuthError(message, code);\r\n case \"forbidden\":\r\n return new VolundForbiddenError(message);\r\n case \"agent_not_found\":\r\n case \"run_not_found\":\r\n return new VolundNotFoundError(message, code);\r\n case \"run_busy\":\r\n return new VolundRunBusyError(message);\r\n default:\r\n return new VolundError(message, { code, status });\r\n }\r\n}\r\n","/**\r\n * Transporte HTTP: monta a requisição, injeta auth, e — crucialmente —\r\n * distingue resposta de STREAM de resposta de ERRO antes de qualquer parsing.\r\n *\r\n * Erros pré-stream (401/403/404/409/400/5xx) NÃO vêm como SSE: voltam como JSON\r\n * `{ error, message }` com status normal (ver as rotas `/stream` do volund-os).\r\n * Por isso checamos `ok` + `content-type` aqui; só uma resposta\r\n * `text/event-stream` de fato é devolvida para virar um `Run`.\r\n *\r\n * Robustez (v0.2):\r\n * - TIMEOUT cobre só a fase PRÉ-STREAM (até a resposta/headers chegarem). NÃO\r\n * limita a duração do stream — um run pode durar minutos. Assim que a resposta\r\n * chega, o timer é cancelado; o sinal de cancelamento do usuário continua\r\n * ligado ao fetch p/ `run.cancel()` abortar o stream a qualquer momento.\r\n * - RETRY com backoff exponencial só na fase pré-stream e só p/ erro de rede e\r\n * 5xx (nunca 4xx, que são determinísticos). ⚠️ `run`/`continue` NÃO são\r\n * idempotentes: um 5xx (ou queda de conexão) pode ter criado o run mesmo\r\n * assim. Por isso o DEFAULT é 0 (SEM retry automático) — habilite via\r\n * `maxRetries` só se a duplicidade de runs for aceitável no seu caso.\r\n */\r\n\r\nimport { errorFromApiResponse, VolundError } from \"./errors\";\r\nimport type { VolundFileInput } from \"./protocol/events\";\r\n\r\n/** Timeout default (ms) p/ receber a resposta. 0 desliga. */\r\nexport const DEFAULT_TIMEOUT_MS = 60_000;\r\n/**\r\n * Tentativas extras default. 0 = SEM retry automático, porque `run`/`continue`\r\n * NÃO são idempotentes (retentar pode criar runs duplicados). Habilite por config\r\n * apenas se a duplicidade for aceitável.\r\n */\r\nexport const DEFAULT_MAX_RETRIES = 0;\r\n/** Base do backoff exponencial (ms): 300, 600, 1200... */\r\nconst RETRY_BASE_MS = 300;\r\n\r\nexport interface HttpConfig {\r\n apiKey: string;\r\n baseUrl: string;\r\n fetch: typeof fetch;\r\n /** Headers extra em toda requisição (ex.: bypass de proteção da Vercel). */\r\n defaultHeaders?: Record<string, string>;\r\n /** Timeout (ms) p/ receber a resposta. Default 60s. 0 desliga. */\r\n timeoutMs?: number;\r\n /** Tentativas extras em erro de rede/5xx. Default 2. 0 desliga. */\r\n maxRetries?: number;\r\n /** Sleep injetável (testes). Default: setTimeout real. */\r\n sleep?: (ms: number) => Promise<void>;\r\n}\r\n\r\nexport interface StreamRequestBody {\r\n input: string;\r\n files?: VolundFileInput[];\r\n}\r\n\r\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\r\n\r\n/**\r\n * Combina o sinal externo (cancel do usuário) com um timeout. O fetch recebe o\r\n * sinal combinado. `clearTimer()` desarma o timeout (chame ao receber a resposta,\r\n * p/ o timer não abortar o stream em andamento); a ligação com o sinal externo\r\n * permanece, então `run.cancel()` segue funcionando durante todo o stream.\r\n */\r\nfunction linkAbort(external: AbortSignal | undefined, timeoutMs: number) {\r\n const controller = new AbortController();\r\n let timedOut = false;\r\n\r\n const onExternalAbort = () => controller.abort();\r\n if (external) {\r\n if (external.aborted) controller.abort();\r\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\r\n }\r\n\r\n const timer =\r\n timeoutMs > 0\r\n ? setTimeout(() => {\r\n timedOut = true;\r\n controller.abort();\r\n }, timeoutMs)\r\n : undefined;\r\n\r\n return {\r\n signal: controller.signal,\r\n /** Foi o timeout (e não o cancel do usuário) que abortou? */\r\n timedOut: () => timedOut,\r\n /** Desarma o timeout MANTENDO a ligação com o sinal externo. Use no sucesso,\r\n * p/ `run.cancel()` seguir abortando o stream em andamento. */\r\n clearTimer: () => {\r\n if (timer) clearTimeout(timer);\r\n },\r\n /** Desarma o timer E solta o listener do sinal externo. Use em tentativas\r\n * abandonadas (erro/retry) p/ não vazar listeners no signal do usuário. */\r\n dispose: () => {\r\n if (timer) clearTimeout(timer);\r\n if (external) external.removeEventListener(\"abort\", onExternalAbort);\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Faz POST num endpoint `/stream` e devolve a `Response` SSE crua. Lança a\r\n * subclasse de `VolundError` apropriada se a resposta for um erro. Aplica timeout\r\n * pré-stream e retry (rede/5xx) conforme a config.\r\n */\r\nexport async function postStream(\r\n cfg: HttpConfig,\r\n path: string,\r\n body: StreamRequestBody,\r\n signal?: AbortSignal\r\n): Promise<Response> {\r\n const url = `${cfg.baseUrl.replace(/\\/+$/, \"\")}${path}`;\r\n const timeoutMs = cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n const maxRetries = cfg.maxRetries ?? DEFAULT_MAX_RETRIES;\r\n const sleep = cfg.sleep ?? defaultSleep;\r\n const payload = JSON.stringify(body);\r\n\r\n let lastError: VolundError | undefined;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n // Cancelamento já pedido antes de tentar: respeita na hora.\r\n if (signal?.aborted) {\r\n throw new VolundError(`Requisição a ${path} cancelada.`, {\r\n code: \"network_error\",\r\n cause: signal.reason,\r\n });\r\n }\r\n\r\n const link = linkAbort(signal, timeoutMs);\r\n let res: Response;\r\n try {\r\n res = await cfg.fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n // defaultHeaders primeiro: os obrigatórios sempre vencem (não dá p/\r\n // quebrar auth/streaming por engano), mas extras como bypass passam.\r\n ...cfg.defaultHeaders,\r\n Authorization: `Bearer ${cfg.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n Accept: \"text/event-stream\",\r\n },\r\n body: payload,\r\n signal: link.signal,\r\n });\r\n } catch (cause) {\r\n // Resposta nunca chegou. Timeout do usuário? cancel? falha de transporte?\r\n // Tentativa abandonada → dispose (solta o listener do signal externo).\r\n link.dispose();\r\n if (link.timedOut()) {\r\n lastError = new VolundError(\r\n `Timeout (${timeoutMs}ms) esperando resposta de ${path}.`,\r\n { code: \"timeout\", cause }\r\n );\r\n } else if (signal?.aborted) {\r\n // Cancel do usuário — não é retentável.\r\n throw new VolundError(`Requisição a ${path} cancelada.`, {\r\n code: \"network_error\",\r\n cause,\r\n });\r\n } else {\r\n lastError = new VolundError(`Falha de rede ao chamar ${path}.`, {\r\n code: \"network_error\",\r\n cause,\r\n });\r\n }\r\n if (attempt < maxRetries) {\r\n await sleep(RETRY_BASE_MS * 2 ** attempt);\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n // Resposta chegou: desarma o timeout para não matar o stream que vem a seguir.\r\n link.clearTimer();\r\n\r\n const contentType = res.headers.get(\"content-type\") ?? \"\";\r\n if (res.ok && contentType.includes(\"text/event-stream\")) {\r\n return res;\r\n }\r\n\r\n // Resposta não-stream = erro: esta tentativa acabou, solta o link (listener).\r\n link.dispose();\r\n\r\n // Caminho de erro: tenta ler `{ error, message }`; se não for JSON, sintetiza.\r\n let errBody: { error?: string; message?: string } = {};\r\n try {\r\n errBody = (await res.json()) as { error?: string; message?: string };\r\n } catch {\r\n errBody = { message: `Resposta inesperada (HTTP ${res.status}).` };\r\n }\r\n const mapped = errorFromApiResponse(res.status, errBody);\r\n\r\n // Só 5xx é retentável. 4xx é determinístico — falha na hora.\r\n if (res.status >= 500 && attempt < maxRetries) {\r\n lastError = mapped;\r\n await sleep(RETRY_BASE_MS * 2 ** attempt);\r\n continue;\r\n }\r\n throw mapped;\r\n }\r\n\r\n // Inalcançável na prática (o loop sempre retorna ou lança), mas satisfaz o tipo.\r\n throw lastError ?? new VolundError(`Falha ao chamar ${path}.`, { code: \"network_error\" });\r\n}\r\n","/**\r\n * Parser SSE → `VolundEvent`. Camada fina sobre `eventsource-parser`, o mesmo\r\n * parser usado pelo Vercel AI SDK e pelo SDK da OpenAI. Ele resolve, de graça,\r\n * as três armadilhas do wire do Volund OS (ver `sse-adapter.ts` no servidor):\r\n *\r\n * 1. Heartbeat `: ping\\n\\n` — linhas de comentário são ignoradas pelo parser.\r\n * 2. Campo `id: <n>` por frame — exposto como `event.id` (reservado p/\r\n * reconexão na V2); a V1 não o usa.\r\n * 3. `data:` multi-linha / partido entre chunks — o parser bufferiza e\r\n * concatena conforme a spec SSE.\r\n *\r\n * Cada `event.data` é um JSON de um único `VolundEvent`. JSON inválido ou tipo\r\n * desconhecido é IGNORADO (regra de ouro [D4]: clientes ignoram o que não\r\n * conhecem — permite minor bumps sem quebrar).\r\n *\r\n * Usamos a API de callback (`createParser`) + um reader manual em vez do\r\n * `EventSourceParserStream` para evitar o atrito de variância de tipos entre\r\n * `TextDecoderStream` e `ReadableStream<Uint8Array>` no `lib.dom`.\r\n */\r\n\r\nimport { createParser } from \"eventsource-parser\";\r\n\r\nimport type { VolundEvent, VolundEventType } from \"./protocol/events\";\r\n\r\nconst KNOWN_TYPES: ReadonlySet<VolundEventType> = new Set<VolundEventType>([\r\n \"run_started\",\r\n \"thinking_delta\",\r\n \"assistant_text_delta\",\r\n \"tool_call\",\r\n \"tool_result\",\r\n \"awaiting_input\",\r\n \"run_finished\",\r\n]);\r\n\r\nfunction isVolundEvent(value: unknown): value is VolundEvent {\r\n return (\r\n typeof value === \"object\" &&\r\n value !== null &&\r\n typeof (value as { type?: unknown }).type === \"string\" &&\r\n KNOWN_TYPES.has((value as { type: string }).type as VolundEventType)\r\n );\r\n}\r\n\r\n/**\r\n * Transforma o corpo de uma resposta `text/event-stream` numa sequência de\r\n * `VolundEvent`. Web-standard: roda em Node ≥18, Deno, Bun, Workers e browser.\r\n */\r\nexport async function* parseVolundSSE(\r\n body: ReadableStream<Uint8Array>\r\n): AsyncGenerator<VolundEvent> {\r\n const queue: VolundEvent[] = [];\r\n const parser = createParser({\r\n onEvent(event) {\r\n if (!event.data) return; // comentário/heartbeat ou frame vazio\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(event.data);\r\n } catch {\r\n return; // frame malformado — ignora\r\n }\r\n if (isVolundEvent(parsed)) queue.push(parsed);\r\n },\r\n });\r\n\r\n const reader = body.getReader();\r\n const decoder = new TextDecoder();\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n parser.feed(decoder.decode(value, { stream: true }));\r\n // onEvent já rodou (síncrono) durante feed() — drena o que acumulou.\r\n while (queue.length > 0) yield queue.shift() as VolundEvent;\r\n }\r\n // Flush final do decoder (bytes multibyte pendentes no fim do stream).\r\n const tail = decoder.decode();\r\n if (tail) {\r\n parser.feed(tail);\r\n while (queue.length > 0) yield queue.shift() as VolundEvent;\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n}\r\n","/**\r\n * `Run` — uma execução de agente em andamento. Espelha o `run` do Cursor SDK:\r\n * `.stream()` (eventos ao vivo), `.result()` (atalho p/ o texto final) e\r\n * `.cancel()` (fecha a conexão; o servidor mata a sandbox).\r\n */\r\n\r\nimport {\r\n VolundAwaitingInputError,\r\n VolundError,\r\n VolundRunFailedError,\r\n} from \"./errors\";\r\nimport { parseVolundSSE } from \"./sse\";\r\nimport type { VolundEvent } from \"./protocol/events\";\r\n\r\nexport interface RunResult {\r\n /** Texto final do agente. */\r\n output: string;\r\n usage: { input_tokens?: number; output_tokens?: number } | null;\r\n}\r\n\r\nexport class Run {\r\n #id: string;\r\n #response: Response;\r\n #abort: AbortController;\r\n #consumed = false;\r\n\r\n constructor(response: Response, id: string, abort: AbortController) {\r\n this.#response = response;\r\n this.#id = id;\r\n this.#abort = abort;\r\n }\r\n\r\n /**\r\n * `run_id` (== `thread_id` no Volund OS). Use em `agents.continue`.\r\n * Para um run NOVO, só fica disponível depois que o primeiro evento\r\n * (`run_started`) é consumido via `stream()`/`result()` — antes disso é \"\".\r\n * Em `agents.continue`, já vem preenchido (você passou o `runId`).\r\n */\r\n get id(): string {\r\n return this.#id;\r\n }\r\n\r\n /**\r\n * Itera os `VolundEvent` conforme chegam. ⚠️ Consumível UMA vez (é um stream\r\n * de rede) — não chame `stream()` e `result()` no mesmo `Run`.\r\n *\r\n * Se você ABANDONAR o stream no meio (um `break`/`throw` antes de um evento\r\n * terminal), a conexão é fechada automaticamente — o servidor mata o sandbox e\r\n * não vaza recurso (§3.6/§4.2 da proposta). Já em `run_finished`/`awaiting_input`\r\n * o servidor encerra sozinho, então NÃO abortamos (abortar no `awaiting_input`\r\n * mataria um run parqueado p/ vault e quebraria o resume — §3.5).\r\n */\r\n async *stream(): AsyncIterable<VolundEvent> {\r\n if (this.#consumed) {\r\n throw new VolundError(\"Este run já foi consumido (stream/result só uma vez).\", {\r\n code: \"stream_error\",\r\n });\r\n }\r\n this.#consumed = true;\r\n\r\n const body = this.#response.body;\r\n if (!body) {\r\n throw new VolundError(\"Resposta de streaming sem corpo legível.\", {\r\n code: \"stream_error\",\r\n });\r\n }\r\n\r\n // O servidor já está encerrando por conta própria? (terminal ou parqueado)\r\n let serverClosing = false;\r\n try {\r\n for await (const event of parseVolundSSE(body)) {\r\n // Para run novo, o id real chega aqui (run_started). Backfill barato.\r\n if (event.type === \"run_started\" && event.run_id) this.#id = event.run_id;\r\n if (event.type === \"run_finished\" || event.type === \"awaiting_input\") {\r\n serverClosing = true;\r\n }\r\n yield event;\r\n }\r\n } catch (err) {\r\n // Se o abort foi nosso (run.cancel()), encerra gracioso em vez de propagar\r\n // o AbortError do fetch para o consumidor.\r\n if (this.#abort.signal.aborted) return;\r\n throw err;\r\n } finally {\r\n // Consumidor abandonou no meio (sem evento terminal): fecha a conexão p/ o\r\n // servidor matar o sandbox. Em estado terminal/parqueado, não tocamos.\r\n if (!serverClosing) this.#abort.abort();\r\n }\r\n }\r\n\r\n /**\r\n * Espera o run terminar e devolve o texto final + uso de tokens.\r\n * Lança `VolundRunFailedError` se o run falhar e `VolundAwaitingInputError`\r\n * se ele pausar para HITL (vault). Para esses casos, prefira `stream()`.\r\n */\r\n async result(): Promise<RunResult> {\r\n let text = \"\";\r\n for await (const event of this.stream()) {\r\n switch (event.type) {\r\n case \"assistant_text_delta\":\r\n text += event.delta;\r\n break;\r\n case \"awaiting_input\":\r\n throw new VolundAwaitingInputError(event.request_id, event.kind);\r\n case \"run_finished\":\r\n if (event.status === \"failed\") {\r\n throw new VolundRunFailedError(\r\n event.error ?? \"Run falhou sem motivo informado.\"\r\n );\r\n }\r\n return { output: event.output ?? text, usage: event.usage };\r\n }\r\n }\r\n // Stream terminou sem `run_finished` (ex.: conexão cortada). Devolve o que veio.\r\n return { output: text, usage: null };\r\n }\r\n\r\n /** Cancela o run: aborta a conexão → o servidor mata a sandbox. */\r\n cancel(): void {\r\n this.#abort.abort();\r\n }\r\n}\r\n","/**\n * `volund.agents` — dispara (`run`) e continua (`continue`) execuções de agente.\n * DX espelha o Cursor SDK: `agents.run(...)` → `Run` com `.stream()/.result()`.\n */\n\nimport { VolundError } from \"./errors\";\nimport { postStream, type HttpConfig, type StreamRequestBody } from \"./http\";\nimport { Run } from \"./run\";\nimport type { ContinueInput, ExecutionMode, RunInput } from \"./protocol/events\";\n\n/** Opções de `agents.run`. Estende o contrato do wire com `signal` (runtime). */\nexport type RunOptions = RunInput & { signal?: AbortSignal };\n/** Opções de `agents.continue`. */\nexport type ContinueOptions = ContinueInput & { signal?: AbortSignal };\n\n/** Combina o sinal do usuário com o sinal interno de `run.cancel()`. */\nfunction linkSignals(controller: AbortController, external?: AbortSignal): void {\n if (!external) return;\n if (external.aborted) {\n controller.abort();\n return;\n }\n external.addEventListener(\"abort\", () => controller.abort(), { once: true });\n}\n\n/** V1 só roda na nuvem; o gancho `execution` já existe p/ a V2 (local.cwd). */\nfunction assertCloud(execution: ExecutionMode | undefined): void {\n if (execution && execution !== \"cloud\") {\n throw new VolundError(\n 'execution: \"local\" ainda não é suportado (chega na V2). Use \"cloud\" ou omita.',\n { code: \"unsupported\" }\n );\n }\n}\n\nexport class Agents {\n #http: HttpConfig;\n\n constructor(http: HttpConfig) {\n this.#http = http;\n }\n\n /** Dispara um run novo (cria uma thread) e devolve um `Run` em streaming. */\n async run(options: RunOptions): Promise<Run> {\n assertCloud(options.execution);\n const controller = new AbortController();\n linkSignals(controller, options.signal);\n\n const body: StreamRequestBody = { input: options.input };\n if (options.files?.length) body.files = options.files;\n\n const res = await postStream(\n this.#http,\n `/api/v1/agents/${encodeURIComponent(options.agentId)}/stream`,\n body,\n controller.signal\n );\n return new Run(res, runIdFromResponse(res), controller);\n }\n\n /** Continua uma conversa existente (mesma thread). */\n async continue(options: ContinueOptions): Promise<Run> {\n assertCloud(options.execution);\n const controller = new AbortController();\n linkSignals(controller, options.signal);\n\n const body: StreamRequestBody = { input: options.input };\n if (options.files?.length) body.files = options.files;\n\n const res = await postStream(\n this.#http,\n `/api/v1/runs/${encodeURIComponent(options.runId)}/stream`,\n body,\n controller.signal\n );\n return new Run(res, options.runId, controller);\n }\n}\n\n/**\n * Para um run NOVO o `run_id` só existe no primeiro evento (`run_started`) — o\n * servidor não o devolve em header. Então iniciamos o `Run` com \"\" e ele faz o\n * backfill do id ao consumir o `run_started` (ver `Run.stream`). Mantemos um\n * fast-path opcional por header caso o servidor passe a ecoá-lo no futuro.\n */\nfunction runIdFromResponse(res: Response): string {\n return res.headers.get(\"x-volund-run-id\") ?? \"\";\n}\n","/**\r\n * `VolundOS` — ponto de entrada do SDK. Configuração mínima: uma API key.\r\n *\r\n * const volund = new VolundOS({ apiKey: process.env.VOLUND_API_KEY! });\r\n * const run = await volund.agents.run({ agentId, input });\r\n */\r\n\r\nimport { Agents } from \"./agents\";\r\nimport { VolundError } from \"./errors\";\r\nimport type { HttpConfig } from \"./http\";\r\n\r\n/** Endpoint de produção do Volund OS (decisão de proposta §6). */\r\nconst DEFAULT_BASE_URL = \"https://os.volund.com.br\";\r\n\r\nexport interface VolundOSConfig {\r\n /** Chave de API \"vos_live_...\". Obrigatória. */\r\n apiKey: string;\r\n /** Sobrescreve a URL base (staging/local). Default: produção. */\r\n baseUrl?: string;\r\n /** Injeta um `fetch` (testes ou runtimes sem fetch global). */\r\n fetch?: typeof fetch;\r\n /**\r\n * Headers extra em toda requisição. Útil p/ o Protection Bypass da Vercel ao\r\n * testar contra um preview deployment:\r\n * `{ \"x-vercel-protection-bypass\": process.env.VERCEL_BYPASS! }`.\r\n * Os headers obrigatórios (Authorization/Content-Type/Accept) não podem ser\r\n * sobrescritos.\r\n */\r\n defaultHeaders?: Record<string, string>;\r\n /**\r\n * Timeout (ms) p/ RECEBER a resposta (headers). NÃO limita a duração do stream\r\n * — um run pode durar minutos. Default: 60000. Use 0 para desligar.\r\n */\r\n timeoutMs?: number;\r\n /**\r\n * Tentativas extras em erro de rede/5xx (só na fase pré-stream). **Default: 0\r\n * (sem retry).** ⚠️ `run`/`continue` não são idempotentes — um 5xx ou queda de\r\n * conexão pode ter criado o run mesmo assim, então retentar pode DUPLICAR runs.\r\n * Só aumente se a duplicidade for aceitável no seu caso de uso.\r\n */\r\n maxRetries?: number;\r\n}\r\n\r\nexport class VolundOS {\r\n /** Disparo e continuação de runs de agente. */\r\n readonly agents: Agents;\r\n\r\n constructor(config: VolundOSConfig) {\r\n if (!config?.apiKey || typeof config.apiKey !== \"string\") {\r\n throw new VolundError(\"apiKey é obrigatória.\", { code: \"missing_api_key\" });\r\n }\r\n\r\n const fetchImpl = config.fetch ?? globalThis.fetch;\r\n if (typeof fetchImpl !== \"function\") {\r\n throw new VolundError(\r\n \"fetch global indisponível. Use Node ≥18 ou injete `fetch` no config.\",\r\n { code: \"unsupported\" }\r\n );\r\n }\r\n\r\n const http: HttpConfig = {\r\n apiKey: config.apiKey,\r\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\r\n // Liga o `fetch` ao globalThis p/ não perder o `this` em algumas impls.\r\n fetch: (...args) => fetchImpl(...args),\r\n ...(config.defaultHeaders ? { defaultHeaders: config.defaultHeaders } : {}),\r\n ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),\r\n ...(config.maxRetries !== undefined ? { maxRetries: config.maxRetries } : {}),\r\n };\r\n\r\n this.agents = new Agents(http);\r\n }\r\n}\r\n","/**\n * ⚠️ ARQUIVO VENDORADO — NÃO EDITE À MÃO ABAIXO DO SENTINEL.\n *\n * Cópia fiel de `lib/agent/connectors/api/events.ts` do repo `volund-os`\n * (a FONTE ÚNICA do contrato VolundEvent v1). O servidor emite estes eventos;\n * este pacote os entrega tipados. Manter os dois em sincronia é invariante do\n * projeto — o CI roda `scripts/check-protocol-drift.mjs`, que falha se o\n * conteúdo abaixo do sentinel divergir do upstream.\n *\n * Para atualizar: rode `npm run sync:protocol` (copia do volund-os) — nunca\n * edite o corpo manualmente. Promover para um pacote `@volund/protocol`\n * publicado no futuro é trivial: este diretório não importa nada do SDK.\n */\n/* ---- BEGIN VENDORED CONTRACT (gerado; não edite abaixo) ---- */\n/**\r\n * Contrato público de eventos do Volund OS SDK — VERSÃO 1.\r\n *\r\n * Fonte única (graduada de `docs/prototypes/sse-adapter/events.ts`). É o que o\r\n * servidor (Parte A) emite via SSE e o que o SDK (Parte B) entregará como\r\n * AsyncIterable<VolundEvent>. Clientes externos dependem disso por anos —\r\n * NÃO altere os tipos públicos sem bump de SCHEMA_VERSION.\r\n *\r\n * DECISÕES DESTA VERSÃO (aprovadas pelo time em 22/06):\r\n * [D1] Naming = snake_case no wire. Consistente com a API pública que JÁ\r\n * existe (GET /api/v1/runs/{id} e webhook) E com o padrão do ecossistema:\r\n * Anthropic é 100% snake_case no fio; Cursor espelha em snake_case os\r\n * campos estilo Claude Code. camelCase fica reservado p/ ergonomia futura\r\n * na borda da linguagem (mapeado no SDK), não no contrato.\r\n * [D2] Status reusa o vocabulário do GET /runs: \"completed\" | \"failed\".\r\n * Pausa (HITL) = UM evento genérico `awaiting_input { kind }`. Na V1 o\r\n * único `kind` é \"vault\": runs via API rodam com\r\n * `--permission-mode bypassPermissions` (lib/agent/v2/run.ts), então NÃO\r\n * pausam por aprovação de ferramenta. \"approval\" foi descopado da V1\r\n * (decisão do time, 22/06) — reintroduzir quando/se a API suportar.\r\n * [D3] tool_result.is_error CONFIRMADO real no nível stream-json: vem como\r\n * `is_error: true` dentro do tool_result (no evento cru `user`). O\r\n * types.ts da Volund não o tipa → o adapter lê do cru via cast. Mantido\r\n * opcional (só presente/true em erro de ferramenta).\r\n * [D4] Versionamento (modelo combinado): campo `protocol` no run_started\r\n * (portátil p/ HTTP e CLI) p/ MAJOR; minor/patch via política\r\n * \"ignore unknown fields\" (clientes ignoram campos desconhecidos).\r\n * [D5] SSE `id:` por evento é emitido pelo ADAPTER (não é campo de payload),\r\n * RESERVADO p/ reconexão futura (V2). A V1 NÃO promete retomada.\r\n *\r\n * ROTEAMENTO DE ERROS (por `type`, nunca por posição):\r\n * - erro de ferramenta → tool_result.is_error (este arquivo, ToolResultEvent)\r\n * - falha do run inteiro → run_finished status:\"failed\" + error\r\n * - erro de transporte → cru `system/api_retry`: NÃO exposto na V1 (interno;\r\n * retries são tratados dentro da nuvem).\r\n *\r\n * REGRAS DE OURO:\r\n * 1. NÃO vazar interno: nada de session_id, sandbox, scratchDir, nome do\r\n * executor (claude-code/cursor), api_retry, nem formatos do AI SDK.\r\n * 2. Versionar (SCHEMA_VERSION). Mudança incompatível => bump MAJOR.\r\n * 3. input/output são `unknown` mas DEVEM ser JSON-serializáveis.\r\n * 4. Fonte única: servidor importa daqui; o pacote @volund/sdk publica daqui.\r\n *\r\n * INVARIANTES DO ADAPTER (blindagem contra inconsistências):\r\n * I1. tool_call.input é emitido COMPLETO. O input chega vazio no 1º snapshot e\r\n * completo depois. O adapter acumula input_json_delta e só emite no\r\n * content_block_stop — nunca um input meio-vazio.\r\n * I2. tool_result.output é NORMALIZADO: bloco MCP [{type:\"text\",text}] vira\r\n * string; imagem `data:image/...` vira placeholder (não trafega binário);\r\n * tamanho limitado. Saída sempre JSON-serializável e enxuta.\r\n * I3. Sentinel de vault (__vault_request_pending__:<id>) NUNCA vaza como\r\n * tool_result — o adapter detecta e emite awaiting_input{kind:\"vault\"},\r\n * suprimindo o sentinel e o run_finished subsequente.\r\n * I4. Texto duplicado: o adapter faz diff entre partials e snapshot cumulativo\r\n * (gate hasSeenPartials), nunca reemite texto já enviado.\r\n * I5. Stream drenado por inteiro (dispara persister + hooks via o tap em\r\n * runAgentV2); abort do cliente → kill(). RESSALVA HITL: no vault o run\r\n * termina sozinho (emite `result`); o adapter NÃO dá kill — só suprime a\r\n * saída ao cliente e continua drenando até o `result` natural, pra o\r\n * persister flipar a thread pra `awaiting_vault` e `handle.finished`\r\n * resolver. Ver sse-adapter.ts.\r\n * I6. V1 achata turnos: um único stream ordenado de deltas, sem id de bloco/\r\n * turno no payload.\r\n */\r\n\r\n/** Versão do schema. Bump em qualquer mudança incompatível. */\r\nexport const SCHEMA_VERSION = \"v1\" as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Eventos públicos — wire SSE: cada um é uma linha `data: <json>\\n\\n`\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Primeiro evento do stream. run_id === thread_id (detalhe opaco p/ o cliente).\r\n * Carrega `protocol` p/ o cliente validar a versão do contrato logo no começo\r\n * (modelo Anthropic system/init), sem depender de inspecionar headers. [D4]\r\n */\r\nexport interface RunStartedEvent {\r\n type: \"run_started\";\r\n protocol: typeof SCHEMA_VERSION;\r\n run_id: string;\r\n agent_id: string;\r\n}\r\n\r\n/** Raciocínio do agente, em streaming (origem: partials stream_event). */\r\nexport interface ThinkingDeltaEvent {\r\n type: \"thinking_delta\";\r\n delta: string;\r\n}\r\n\r\n/** Resposta do agente, em streaming token a token (origem: partials stream_event). */\r\nexport interface AssistantTextDeltaEvent {\r\n type: \"assistant_text_delta\";\r\n delta: string;\r\n}\r\n\r\n/** O agente chamou uma ferramenta (origem: assistant → content[].tool_use). */\r\nexport interface ToolCallEvent {\r\n type: \"tool_call\";\r\n tool_call_id: string;\r\n tool_name: string;\r\n /** JSON-serializável e COMPLETO (invariante I1). */\r\n input: unknown;\r\n}\r\n\r\n/** Uma ferramenta retornou (origem: user → content[].tool_result). */\r\nexport interface ToolResultEvent {\r\n type: \"tool_result\";\r\n tool_call_id: string;\r\n /** JSON-serializável e NORMALIZADO (invariante I2): texto desembrulhado,\r\n * imagem vira placeholder, tamanho limitado. */\r\n output: unknown;\r\n /** [D3] Presente (true) quando a ferramenta falhou. Vem do `is_error` dentro\r\n * do tool_result cru (evento `user`), que o types.ts da Volund não tipa. */\r\n is_error?: boolean;\r\n}\r\n\r\n/**\r\n * HITL: o run pausou esperando ação humana. Na V1 o único caso é \"vault\"\r\n * (preencher uma credencial no cofre) — o servidor emite este evento, SUPRIME o\r\n * resto do stream e o cliente retoma pelos endpoints existentes. [D2] Modelo\r\n * genérico (decisão do time, 22/06): `kind` fica como união pra ser extensível.\r\n *\r\n * \"approval\" foi descopado da V1: runs via API usam\r\n * `--permission-mode bypassPermissions` (lib/agent/v2/run.ts), logo não pausam\r\n * por aprovação de ferramenta. Reintroduzir `kind:\"approval\"` quando/se a API\r\n * passar a suportar aprovação (provável na V2).\r\n */\r\nexport interface AwaitingInputEvent {\r\n type: \"awaiting_input\";\r\n request_id: string;\r\n kind: \"vault\";\r\n}\r\n\r\n/** Último evento de um stream que termina normalmente (origem: result). */\r\nexport interface RunFinishedEvent {\r\n type: \"run_finished\";\r\n /** [D2] Mesmo vocabulário do GET /runs e do webhook. */\r\n status: \"completed\" | \"failed\";\r\n /** Texto final (origem: result.result). Pode ser null. */\r\n output: string | null;\r\n /** Origem: result.usage (snake_case já usado na API). Pode ser null. */\r\n usage: { input_tokens?: number; output_tokens?: number } | null;\r\n /** Preenchido quando status === \"failed\". */\r\n error?: string;\r\n}\r\n\r\n/** União discriminada pública. Faça narrowing por `event.type`. */\r\nexport type VolundEvent =\r\n | RunStartedEvent\r\n | ThinkingDeltaEvent\r\n | AssistantTextDeltaEvent\r\n | ToolCallEvent\r\n | ToolResultEvent\r\n | AwaitingInputEvent\r\n | RunFinishedEvent;\r\n\r\n/** Todos os literais de `type` (útil p/ exhaustiveness/validação). */\r\nexport type VolundEventType = VolundEvent[\"type\"];\r\n\r\n// ---------------------------------------------------------------------------\r\n// Entradas do SDK (request) — args dos métodos run()/continue().\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Mesmo shape do { url } | { data } que a API atual aceita. */\r\nexport type VolundFileInput =\r\n | { url: string; name?: string }\r\n | { data: string; name?: string; mime?: string };\r\n\r\n/**\r\n * Gancho p/ V2 (inferência na nuvem + execução local). Na V1 só \"cloud\"\r\n * existe; o tipo já antecipa a V2 sem fechar a porta. NÃO implementar \"local\".\r\n */\r\nexport type ExecutionMode = \"cloud\" | { local: { cwd: string } };\r\n\r\nexport interface RunInput {\r\n agentId: string;\r\n input: string;\r\n files?: VolundFileInput[];\r\n execution?: ExecutionMode;\r\n}\r\n\r\nexport interface ContinueInput {\r\n runId: string;\r\n input: string;\r\n files?: VolundFileInput[];\r\n execution?: ExecutionMode;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Códigos de erro — espelham os que a API já devolve em { error, message }.\r\n// ---------------------------------------------------------------------------\r\n\r\nexport type VolundErrorCode =\r\n | \"missing_api_key\" // 401\r\n | \"invalid_api_key\" // 401\r\n | \"forbidden\" // 403\r\n | \"agent_not_found\" // 404\r\n | \"run_not_found\" // 404\r\n | \"run_busy\" // 409 — já há um run ativo na thread (só na continuação)\r\n | \"internal_error\"; // 5xx\r\n"],"mappings":"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"}
|