@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.
@@ -0,0 +1,356 @@
1
+ //#region src/protocol/events.d.ts
2
+ /**
3
+ * ⚠️ ARQUIVO VENDORADO — NÃO EDITE À MÃO ABAIXO DO SENTINEL.
4
+ *
5
+ * Cópia fiel de `lib/agent/connectors/api/events.ts` do repo `volund-os`
6
+ * (a FONTE ÚNICA do contrato VolundEvent v1). O servidor emite estes eventos;
7
+ * este pacote os entrega tipados. Manter os dois em sincronia é invariante do
8
+ * projeto — o CI roda `scripts/check-protocol-drift.mjs`, que falha se o
9
+ * conteúdo abaixo do sentinel divergir do upstream.
10
+ *
11
+ * Para atualizar: rode `npm run sync:protocol` (copia do volund-os) — nunca
12
+ * edite o corpo manualmente. Promover para um pacote `@volund/protocol`
13
+ * publicado no futuro é trivial: este diretório não importa nada do SDK.
14
+ */
15
+ /**
16
+ * Contrato público de eventos do Volund OS SDK — VERSÃO 1.
17
+ *
18
+ * Fonte única (graduada de `docs/prototypes/sse-adapter/events.ts`). É o que o
19
+ * servidor (Parte A) emite via SSE e o que o SDK (Parte B) entregará como
20
+ * AsyncIterable<VolundEvent>. Clientes externos dependem disso por anos —
21
+ * NÃO altere os tipos públicos sem bump de SCHEMA_VERSION.
22
+ *
23
+ * DECISÕES DESTA VERSÃO (aprovadas pelo time em 22/06):
24
+ * [D1] Naming = snake_case no wire. Consistente com a API pública que JÁ
25
+ * existe (GET /api/v1/runs/{id} e webhook) E com o padrão do ecossistema:
26
+ * Anthropic é 100% snake_case no fio; Cursor espelha em snake_case os
27
+ * campos estilo Claude Code. camelCase fica reservado p/ ergonomia futura
28
+ * na borda da linguagem (mapeado no SDK), não no contrato.
29
+ * [D2] Status reusa o vocabulário do GET /runs: "completed" | "failed".
30
+ * Pausa (HITL) = UM evento genérico `awaiting_input { kind }`. Na V1 o
31
+ * único `kind` é "vault": runs via API rodam com
32
+ * `--permission-mode bypassPermissions` (lib/agent/v2/run.ts), então NÃO
33
+ * pausam por aprovação de ferramenta. "approval" foi descopado da V1
34
+ * (decisão do time, 22/06) — reintroduzir quando/se a API suportar.
35
+ * [D3] tool_result.is_error CONFIRMADO real no nível stream-json: vem como
36
+ * `is_error: true` dentro do tool_result (no evento cru `user`). O
37
+ * types.ts da Volund não o tipa → o adapter lê do cru via cast. Mantido
38
+ * opcional (só presente/true em erro de ferramenta).
39
+ * [D4] Versionamento (modelo combinado): campo `protocol` no run_started
40
+ * (portátil p/ HTTP e CLI) p/ MAJOR; minor/patch via política
41
+ * "ignore unknown fields" (clientes ignoram campos desconhecidos).
42
+ * [D5] SSE `id:` por evento é emitido pelo ADAPTER (não é campo de payload),
43
+ * RESERVADO p/ reconexão futura (V2). A V1 NÃO promete retomada.
44
+ *
45
+ * ROTEAMENTO DE ERROS (por `type`, nunca por posição):
46
+ * - erro de ferramenta → tool_result.is_error (este arquivo, ToolResultEvent)
47
+ * - falha do run inteiro → run_finished status:"failed" + error
48
+ * - erro de transporte → cru `system/api_retry`: NÃO exposto na V1 (interno;
49
+ * retries são tratados dentro da nuvem).
50
+ *
51
+ * REGRAS DE OURO:
52
+ * 1. NÃO vazar interno: nada de session_id, sandbox, scratchDir, nome do
53
+ * executor (claude-code/cursor), api_retry, nem formatos do AI SDK.
54
+ * 2. Versionar (SCHEMA_VERSION). Mudança incompatível => bump MAJOR.
55
+ * 3. input/output são `unknown` mas DEVEM ser JSON-serializáveis.
56
+ * 4. Fonte única: servidor importa daqui; o pacote @volund/sdk publica daqui.
57
+ *
58
+ * INVARIANTES DO ADAPTER (blindagem contra inconsistências):
59
+ * I1. tool_call.input é emitido COMPLETO. O input chega vazio no 1º snapshot e
60
+ * completo depois. O adapter acumula input_json_delta e só emite no
61
+ * content_block_stop — nunca um input meio-vazio.
62
+ * I2. tool_result.output é NORMALIZADO: bloco MCP [{type:"text",text}] vira
63
+ * string; imagem `data:image/...` vira placeholder (não trafega binário);
64
+ * tamanho limitado. Saída sempre JSON-serializável e enxuta.
65
+ * I3. Sentinel de vault (__vault_request_pending__:<id>) NUNCA vaza como
66
+ * tool_result — o adapter detecta e emite awaiting_input{kind:"vault"},
67
+ * suprimindo o sentinel e o run_finished subsequente.
68
+ * I4. Texto duplicado: o adapter faz diff entre partials e snapshot cumulativo
69
+ * (gate hasSeenPartials), nunca reemite texto já enviado.
70
+ * I5. Stream drenado por inteiro (dispara persister + hooks via o tap em
71
+ * runAgentV2); abort do cliente → kill(). RESSALVA HITL: no vault o run
72
+ * termina sozinho (emite `result`); o adapter NÃO dá kill — só suprime a
73
+ * saída ao cliente e continua drenando até o `result` natural, pra o
74
+ * persister flipar a thread pra `awaiting_vault` e `handle.finished`
75
+ * resolver. Ver sse-adapter.ts.
76
+ * I6. V1 achata turnos: um único stream ordenado de deltas, sem id de bloco/
77
+ * turno no payload.
78
+ */
79
+ /** Versão do schema. Bump em qualquer mudança incompatível. */
80
+ declare const SCHEMA_VERSION: "v1";
81
+ /**
82
+ * Primeiro evento do stream. run_id === thread_id (detalhe opaco p/ o cliente).
83
+ * Carrega `protocol` p/ o cliente validar a versão do contrato logo no começo
84
+ * (modelo Anthropic system/init), sem depender de inspecionar headers. [D4]
85
+ */
86
+ interface RunStartedEvent {
87
+ type: "run_started";
88
+ protocol: typeof SCHEMA_VERSION;
89
+ run_id: string;
90
+ agent_id: string;
91
+ }
92
+ /** Raciocínio do agente, em streaming (origem: partials stream_event). */
93
+ interface ThinkingDeltaEvent {
94
+ type: "thinking_delta";
95
+ delta: string;
96
+ }
97
+ /** Resposta do agente, em streaming token a token (origem: partials stream_event). */
98
+ interface AssistantTextDeltaEvent {
99
+ type: "assistant_text_delta";
100
+ delta: string;
101
+ }
102
+ /** O agente chamou uma ferramenta (origem: assistant → content[].tool_use). */
103
+ interface ToolCallEvent {
104
+ type: "tool_call";
105
+ tool_call_id: string;
106
+ tool_name: string;
107
+ /** JSON-serializável e COMPLETO (invariante I1). */
108
+ input: unknown;
109
+ }
110
+ /** Uma ferramenta retornou (origem: user → content[].tool_result). */
111
+ interface ToolResultEvent {
112
+ type: "tool_result";
113
+ tool_call_id: string;
114
+ /** JSON-serializável e NORMALIZADO (invariante I2): texto desembrulhado,
115
+ * imagem vira placeholder, tamanho limitado. */
116
+ output: unknown;
117
+ /** [D3] Presente (true) quando a ferramenta falhou. Vem do `is_error` dentro
118
+ * do tool_result cru (evento `user`), que o types.ts da Volund não tipa. */
119
+ is_error?: boolean;
120
+ }
121
+ /**
122
+ * HITL: o run pausou esperando ação humana. Na V1 o único caso é "vault"
123
+ * (preencher uma credencial no cofre) — o servidor emite este evento, SUPRIME o
124
+ * resto do stream e o cliente retoma pelos endpoints existentes. [D2] Modelo
125
+ * genérico (decisão do time, 22/06): `kind` fica como união pra ser extensível.
126
+ *
127
+ * "approval" foi descopado da V1: runs via API usam
128
+ * `--permission-mode bypassPermissions` (lib/agent/v2/run.ts), logo não pausam
129
+ * por aprovação de ferramenta. Reintroduzir `kind:"approval"` quando/se a API
130
+ * passar a suportar aprovação (provável na V2).
131
+ */
132
+ interface AwaitingInputEvent {
133
+ type: "awaiting_input";
134
+ request_id: string;
135
+ kind: "vault";
136
+ }
137
+ /** Último evento de um stream que termina normalmente (origem: result). */
138
+ interface RunFinishedEvent {
139
+ type: "run_finished";
140
+ /** [D2] Mesmo vocabulário do GET /runs e do webhook. */
141
+ status: "completed" | "failed";
142
+ /** Texto final (origem: result.result). Pode ser null. */
143
+ output: string | null;
144
+ /** Origem: result.usage (snake_case já usado na API). Pode ser null. */
145
+ usage: {
146
+ input_tokens?: number;
147
+ output_tokens?: number;
148
+ } | null;
149
+ /** Preenchido quando status === "failed". */
150
+ error?: string;
151
+ }
152
+ /** União discriminada pública. Faça narrowing por `event.type`. */
153
+ type VolundEvent = RunStartedEvent | ThinkingDeltaEvent | AssistantTextDeltaEvent | ToolCallEvent | ToolResultEvent | AwaitingInputEvent | RunFinishedEvent;
154
+ /** Todos os literais de `type` (útil p/ exhaustiveness/validação). */
155
+ type VolundEventType = VolundEvent["type"];
156
+ /** Mesmo shape do { url } | { data } que a API atual aceita. */
157
+ type VolundFileInput = {
158
+ url: string;
159
+ name?: string;
160
+ } | {
161
+ data: string;
162
+ name?: string;
163
+ mime?: string;
164
+ };
165
+ /**
166
+ * Gancho p/ V2 (inferência na nuvem + execução local). Na V1 só "cloud"
167
+ * existe; o tipo já antecipa a V2 sem fechar a porta. NÃO implementar "local".
168
+ */
169
+ type ExecutionMode = "cloud" | {
170
+ local: {
171
+ cwd: string;
172
+ };
173
+ };
174
+ interface RunInput {
175
+ agentId: string;
176
+ input: string;
177
+ files?: VolundFileInput[];
178
+ execution?: ExecutionMode;
179
+ }
180
+ interface ContinueInput {
181
+ runId: string;
182
+ input: string;
183
+ files?: VolundFileInput[];
184
+ execution?: ExecutionMode;
185
+ }
186
+ type VolundErrorCode = "missing_api_key" | "invalid_api_key" | "forbidden" | "agent_not_found" | "run_not_found" | "run_busy" | "internal_error";
187
+ //#endregion
188
+ //#region src/http.d.ts
189
+ interface HttpConfig {
190
+ apiKey: string;
191
+ baseUrl: string;
192
+ fetch: typeof fetch;
193
+ /** Headers extra em toda requisição (ex.: bypass de proteção da Vercel). */
194
+ defaultHeaders?: Record<string, string>;
195
+ /** Timeout (ms) p/ receber a resposta. Default 60s. 0 desliga. */
196
+ timeoutMs?: number;
197
+ /** Tentativas extras em erro de rede/5xx. Default 2. 0 desliga. */
198
+ maxRetries?: number;
199
+ /** Sleep injetável (testes). Default: setTimeout real. */
200
+ sleep?: (ms: number) => Promise<void>;
201
+ }
202
+ //#endregion
203
+ //#region src/run.d.ts
204
+ interface RunResult {
205
+ /** Texto final do agente. */
206
+ output: string;
207
+ usage: {
208
+ input_tokens?: number;
209
+ output_tokens?: number;
210
+ } | null;
211
+ }
212
+ declare class Run {
213
+ #private;
214
+ constructor(response: Response, id: string, abort: AbortController);
215
+ /**
216
+ * `run_id` (== `thread_id` no Volund OS). Use em `agents.continue`.
217
+ * Para um run NOVO, só fica disponível depois que o primeiro evento
218
+ * (`run_started`) é consumido via `stream()`/`result()` — antes disso é "".
219
+ * Em `agents.continue`, já vem preenchido (você passou o `runId`).
220
+ */
221
+ get id(): string;
222
+ /**
223
+ * Itera os `VolundEvent` conforme chegam. ⚠️ Consumível UMA vez (é um stream
224
+ * de rede) — não chame `stream()` e `result()` no mesmo `Run`.
225
+ *
226
+ * Se você ABANDONAR o stream no meio (um `break`/`throw` antes de um evento
227
+ * terminal), a conexão é fechada automaticamente — o servidor mata o sandbox e
228
+ * não vaza recurso (§3.6/§4.2 da proposta). Já em `run_finished`/`awaiting_input`
229
+ * o servidor encerra sozinho, então NÃO abortamos (abortar no `awaiting_input`
230
+ * mataria um run parqueado p/ vault e quebraria o resume — §3.5).
231
+ */
232
+ stream(): AsyncIterable<VolundEvent>;
233
+ /**
234
+ * Espera o run terminar e devolve o texto final + uso de tokens.
235
+ * Lança `VolundRunFailedError` se o run falhar e `VolundAwaitingInputError`
236
+ * se ele pausar para HITL (vault). Para esses casos, prefira `stream()`.
237
+ */
238
+ result(): Promise<RunResult>;
239
+ /** Cancela o run: aborta a conexão → o servidor mata a sandbox. */
240
+ cancel(): void;
241
+ }
242
+ //#endregion
243
+ //#region src/agents.d.ts
244
+ /** Opções de `agents.run`. Estende o contrato do wire com `signal` (runtime). */
245
+ type RunOptions = RunInput & {
246
+ signal?: AbortSignal;
247
+ };
248
+ /** Opções de `agents.continue`. */
249
+ type ContinueOptions = ContinueInput & {
250
+ signal?: AbortSignal;
251
+ };
252
+ declare class Agents {
253
+ #private;
254
+ constructor(http: HttpConfig);
255
+ /** Dispara um run novo (cria uma thread) e devolve um `Run` em streaming. */
256
+ run(options: RunOptions): Promise<Run>;
257
+ /** Continua uma conversa existente (mesma thread). */
258
+ continue(options: ContinueOptions): Promise<Run>;
259
+ }
260
+ //#endregion
261
+ //#region src/client.d.ts
262
+ interface VolundOSConfig {
263
+ /** Chave de API "vos_live_...". Obrigatória. */
264
+ apiKey: string;
265
+ /** Sobrescreve a URL base (staging/local). Default: produção. */
266
+ baseUrl?: string;
267
+ /** Injeta um `fetch` (testes ou runtimes sem fetch global). */
268
+ fetch?: typeof fetch;
269
+ /**
270
+ * Headers extra em toda requisição. Útil p/ o Protection Bypass da Vercel ao
271
+ * testar contra um preview deployment:
272
+ * `{ "x-vercel-protection-bypass": process.env.VERCEL_BYPASS! }`.
273
+ * Os headers obrigatórios (Authorization/Content-Type/Accept) não podem ser
274
+ * sobrescritos.
275
+ */
276
+ defaultHeaders?: Record<string, string>;
277
+ /**
278
+ * Timeout (ms) p/ RECEBER a resposta (headers). NÃO limita a duração do stream
279
+ * — um run pode durar minutos. Default: 60000. Use 0 para desligar.
280
+ */
281
+ timeoutMs?: number;
282
+ /**
283
+ * Tentativas extras em erro de rede/5xx (só na fase pré-stream). **Default: 0
284
+ * (sem retry).** ⚠️ `run`/`continue` não são idempotentes — um 5xx ou queda de
285
+ * conexão pode ter criado o run mesmo assim, então retentar pode DUPLICAR runs.
286
+ * Só aumente se a duplicidade for aceitável no seu caso de uso.
287
+ */
288
+ maxRetries?: number;
289
+ }
290
+ declare class VolundOS {
291
+ /** Disparo e continuação de runs de agente. */
292
+ readonly agents: Agents;
293
+ constructor(config: VolundOSConfig);
294
+ }
295
+ //#endregion
296
+ //#region src/sse.d.ts
297
+ /**
298
+ * Transforma o corpo de uma resposta `text/event-stream` numa sequência de
299
+ * `VolundEvent`. Web-standard: roda em Node ≥18, Deno, Bun, Workers e browser.
300
+ */
301
+ declare function parseVolundSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<VolundEvent>;
302
+ //#endregion
303
+ //#region src/errors.d.ts
304
+ /** Códigos locais do SDK que não vêm do servidor. */
305
+ type VolundClientErrorCode = "network_error" | "timeout" | "stream_error" | "run_failed" | "awaiting_input" | "unsupported";
306
+ type AnyVolundErrorCode = VolundErrorCode | VolundClientErrorCode;
307
+ interface VolundErrorOptions {
308
+ code: AnyVolundErrorCode;
309
+ /** Status HTTP, quando o erro veio de uma resposta da API. */
310
+ status?: number;
311
+ cause?: unknown;
312
+ }
313
+ /** Erro base de todo o SDK. */
314
+ declare class VolundError extends Error {
315
+ readonly code: AnyVolundErrorCode;
316
+ readonly status?: number;
317
+ constructor(message: string, opts: VolundErrorOptions);
318
+ }
319
+ /** 401 — chave ausente ou inválida. */
320
+ declare class VolundAuthError extends VolundError {
321
+ constructor(message: string, code?: VolundErrorCode, cause?: unknown);
322
+ }
323
+ /** 403 — a chave não tem acesso a este agente/run. */
324
+ declare class VolundForbiddenError extends VolundError {
325
+ constructor(message: string, cause?: unknown);
326
+ }
327
+ /** 404 — agente ou run inexistente. */
328
+ declare class VolundNotFoundError extends VolundError {
329
+ constructor(message: string, code?: VolundErrorCode, cause?: unknown);
330
+ }
331
+ /** 409 — já existe um run ativo na thread (só na continuação). */
332
+ declare class VolundRunBusyError extends VolundError {
333
+ constructor(message: string, cause?: unknown);
334
+ }
335
+ /** O run terminou com `status: "failed"`. Lançado por `run.result()`. */
336
+ declare class VolundRunFailedError extends VolundError {
337
+ constructor(message: string, cause?: unknown);
338
+ }
339
+ /**
340
+ * O run pausou esperando ação humana (HITL — na V1, preenchimento de cofre).
341
+ * `run.result()` lança isto porque o stream termina sem `run_finished`. Quem
342
+ * usa `run.stream()` recebe o evento `awaiting_input` normalmente, sem exceção.
343
+ */
344
+ declare class VolundAwaitingInputError extends VolundError {
345
+ readonly requestId: string;
346
+ readonly kind: "vault";
347
+ constructor(requestId: string, kind: "vault");
348
+ }
349
+ /** Constrói a subclasse certa a partir de uma resposta de erro da API. */
350
+ declare function errorFromApiResponse(status: number, body: {
351
+ error?: string;
352
+ message?: string;
353
+ }): VolundError;
354
+ //#endregion
355
+ export { Agents, type AnyVolundErrorCode, type AssistantTextDeltaEvent, type AwaitingInputEvent, type ContinueInput, type ContinueOptions, type ExecutionMode, Run, type RunFinishedEvent, type RunInput, type RunOptions, type RunResult, type RunStartedEvent, SCHEMA_VERSION, type ThinkingDeltaEvent, type ToolCallEvent, type ToolResultEvent, VolundAuthError, VolundAwaitingInputError, type VolundClientErrorCode, VolundError, type VolundErrorCode, type VolundEvent, type VolundEventType, type VolundFileInput, VolundForbiddenError, VolundNotFoundError, VolundOS, type VolundOSConfig, VolundRunBusyError, VolundRunFailedError, errorFromApiResponse, parseVolundSSE };
356
+ //# sourceMappingURL=index.d.mts.map