@convilyn/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,893 @@
1
+ /**
2
+ * Typed errors raised by the Convilyn SDK — a faithful port of the Python
3
+ * SDK's `exceptions.py`.
4
+ *
5
+ * Hierarchy:
6
+ *
7
+ * ConvilynError base; everything the SDK throws
8
+ * ├── AuthError missing / malformed credentials
9
+ * ├── APIError HTTP 4xx / 5xx (envelope {code, message, details})
10
+ * │ ├── RateLimitError HTTP 429
11
+ * │ ├── PlanRequiredError HTTP 402 + TIER_REQUIRED-family code
12
+ * │ ├── QuotaExceededError HTTP 402 (insufficient_balance / QUOTA_EXCEEDED)
13
+ * │ ├── S3UploadError presigned upload PUT failed
14
+ * │ └── RetryExhaustedError retry policy ran out of attempts
15
+ * ├── JobFailedError conversion job status=failed
16
+ * ├── JobTimeoutError convert.wait() timed out
17
+ * ├── GoalJobFailedError goal job status=failed
18
+ * ├── GoalJobTimeoutError goals.wait() timed out
19
+ * └── WebSocketError goal-event stream failure
20
+ *
21
+ * Catch `ConvilynError` to handle any SDK-originated failure; branch on the
22
+ * `code` / `statusCode` fields for typed handling.
23
+ */
24
+ type ErrorDetails = Record<string, unknown>;
25
+ /** Base class for every error this SDK throws. */
26
+ declare class ConvilynError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ /** Authentication / authorization failed before any HTTP call. */
30
+ declare class AuthError extends ConvilynError {
31
+ constructor(message: string);
32
+ }
33
+ /**
34
+ * Server returned a non-success HTTP response. The Convilyn API uses the
35
+ * envelope `{code, message, details}`; those are exposed as fields.
36
+ *
37
+ * `message` holds the server's raw message (matching the Python `.message`
38
+ * attribute); branch on `code` / `statusCode` for typed handling.
39
+ */
40
+ declare class APIError extends ConvilynError {
41
+ readonly statusCode: number;
42
+ readonly code: string;
43
+ readonly details: ErrorDetails;
44
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails);
45
+ }
46
+ /** HTTP 429 — the SDK or caller exceeded the API rate limit. */
47
+ declare class RateLimitError extends APIError {
48
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails);
49
+ }
50
+ /** HTTP 402 with a TIER_REQUIRED-family code — the caller's plan is too low. */
51
+ declare class PlanRequiredError extends APIError {
52
+ readonly requiredPlan: string;
53
+ readonly upgradeUrl: string | null;
54
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails, opts?: {
55
+ requiredPlan?: string;
56
+ upgradeUrl?: string | null;
57
+ });
58
+ }
59
+ /**
60
+ * HTTP 402 — the run would cost more **credits** than the account's balance.
61
+ *
62
+ * The backend currently emits code `insufficient_balance` (and intends to add
63
+ * `QUOTA_EXCEEDED`); both decode to this error. Fields are in **credits**
64
+ * (1 credit = $0.01) to match the vocabulary users see in the console / web /
65
+ * docs — never internal micro-units.
66
+ */
67
+ declare class QuotaExceededError extends APIError {
68
+ readonly costCredits: number | null;
69
+ readonly balanceCredits: number | null;
70
+ readonly topUpUrl: string | null;
71
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails, opts?: {
72
+ costCredits?: number | null;
73
+ balanceCredits?: number | null;
74
+ topUpUrl?: string | null;
75
+ });
76
+ }
77
+ /** The presigned upload PUT step of a file upload returned a non-success status. */
78
+ declare class S3UploadError extends APIError {
79
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails);
80
+ }
81
+ /** The retry policy ran out of attempts before the request succeeded. */
82
+ declare class RetryExhaustedError extends APIError {
83
+ readonly attemptCount: number;
84
+ constructor(statusCode: number, code: string, message: string, details?: ErrorDetails, opts?: {
85
+ attemptCount?: number;
86
+ });
87
+ }
88
+ /** A conversion job (`convert.wait`) finished with status `failed`. */
89
+ declare class JobFailedError extends ConvilynError {
90
+ readonly jobId: string;
91
+ readonly processorType: string;
92
+ readonly code: string;
93
+ constructor(opts: {
94
+ jobId: string;
95
+ processorType: string;
96
+ code: string;
97
+ message: string;
98
+ });
99
+ }
100
+ /** A polling helper exceeded its `timeout` before the conversion job finished. */
101
+ declare class JobTimeoutError extends ConvilynError {
102
+ readonly jobId: string;
103
+ readonly elapsed: number;
104
+ readonly timeout: number;
105
+ constructor(opts: {
106
+ jobId: string;
107
+ elapsed: number;
108
+ timeout: number;
109
+ });
110
+ }
111
+ /** A goal (agentic) job finished with status `failed`. */
112
+ declare class GoalJobFailedError extends ConvilynError {
113
+ readonly jobSpecId: string;
114
+ readonly code: string;
115
+ constructor(opts: {
116
+ jobSpecId: string;
117
+ code: string | null;
118
+ message: string | null;
119
+ });
120
+ }
121
+ /** A goal polling helper exceeded its `timeout` before a terminal status. */
122
+ declare class GoalJobTimeoutError extends ConvilynError {
123
+ readonly jobSpecId: string;
124
+ readonly elapsed: number;
125
+ readonly timeout: number;
126
+ constructor(opts: {
127
+ jobSpecId: string;
128
+ elapsed: number;
129
+ timeout: number;
130
+ });
131
+ }
132
+ /** Raised when the goal event stream cannot proceed. */
133
+ declare class WebSocketError extends ConvilynError {
134
+ readonly payload: string | null;
135
+ constructor(message: string, opts?: {
136
+ payload?: string | null;
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Public response types — port of `types.py`.
142
+ *
143
+ * Field names are the SDK's **public** names in camelCase (the TS-idiomatic
144
+ * equivalent of the Python SDK's snake_case public surface), which already
145
+ * differ from the raw wire in places (`fileName`→`filename`, `mimeType`→
146
+ * `contentType`). The resource parsers (A2+) map wire → these shapes.
147
+ *
148
+ * Money is expressed in **credits** (1 credit = $0.01) everywhere user-facing,
149
+ * to match the console / web / docs vocabulary — never internal micro-units.
150
+ *
151
+ * Types carry data only; the `is_terminal` / `needs_input` Python *properties*
152
+ * become standalone predicate functions here (tree-shakeable, no class needed).
153
+ */
154
+ /** Lifecycle status of a file-conversion job (`client.convert`). */
155
+ type JobStatus = 'queued' | 'processing' | 'completed' | 'failed';
156
+ /** A file known to the Convilyn platform. */
157
+ interface File {
158
+ readonly fileId: string;
159
+ readonly filename: string;
160
+ readonly size: number;
161
+ readonly contentType: string;
162
+ readonly createdAt: Date;
163
+ readonly jobId: string | null;
164
+ readonly isInput: boolean;
165
+ }
166
+ /** A produced artifact attached to a completed job (presigned `url`, 1h TTL). */
167
+ interface ResultFile {
168
+ readonly filename: string;
169
+ readonly size: number;
170
+ readonly mimetype: string;
171
+ readonly url: string;
172
+ }
173
+ /** Failure detail attached to a `failed` job. */
174
+ interface JobError {
175
+ readonly code: string;
176
+ readonly message: string;
177
+ }
178
+ /** A file-conversion job (data only; actions live on the resource). */
179
+ interface ConvertJob {
180
+ readonly jobId: string;
181
+ readonly status: JobStatus;
182
+ readonly processorType: string;
183
+ readonly progress: number;
184
+ readonly progressMessage: string | null;
185
+ readonly resultFiles: ResultFile[] | null;
186
+ readonly error: JobError | null;
187
+ readonly retryCount: number;
188
+ readonly createdAt: Date;
189
+ readonly updatedAt: Date;
190
+ readonly startedAt: Date | null;
191
+ readonly completedAt: Date | null;
192
+ readonly estimatedDuration: number | null;
193
+ }
194
+ /** Conversion statuses at which `wait()` stops polling. */
195
+ declare const CONVERT_JOB_TERMINAL_STATUSES: readonly JobStatus[];
196
+ /** True when a conversion job has reached `completed` or `failed`. */
197
+ declare function isConvertJobTerminal(job: Pick<ConvertJob, 'status'>): boolean;
198
+ /** Lifecycle status of a goal (agentic) job (`client.goals`). */
199
+ type GoalJobStatus = 'draft' | 'created' | 'analyzing' | 'slots_pending' | 'ready' | 'ready_with_preview' | 'confirmed' | 'queued' | 'executing' | 'completed' | 'partial' | 'failed' | 'cancelled';
200
+ /** Goal-lane statuses at which `wait()` stops polling. */
201
+ declare const GOAL_JOB_TERMINAL_STATUSES: readonly GoalJobStatus[];
202
+ /** A piece of information the agent is asking the user to supply. */
203
+ interface PendingSlot {
204
+ readonly slotId: string;
205
+ readonly slotType: string;
206
+ readonly question: string;
207
+ readonly options: Array<string | Record<string, string>> | null;
208
+ readonly required: boolean;
209
+ readonly isDisambiguation: boolean;
210
+ readonly suggestedValue: unknown | null;
211
+ readonly suggestedConfidence: number | null;
212
+ }
213
+ /** A goal (agentic) job — a "workflow run" in product vocabulary. */
214
+ interface GoalJob {
215
+ readonly jobSpecId: string;
216
+ readonly status: GoalJobStatus;
217
+ readonly progress: number;
218
+ readonly itemVersion: number | null;
219
+ readonly attemptId: string | null;
220
+ readonly goalText: string | null;
221
+ readonly fileIds: string[];
222
+ readonly pendingSlots: PendingSlot[];
223
+ readonly filledSlots: Record<string, unknown>;
224
+ readonly pendingInterrupts: Record<string, unknown>[];
225
+ readonly agentMessage: string | null;
226
+ readonly errorMessage: string | null;
227
+ readonly errorCode: string | null;
228
+ readonly createdAt: Date;
229
+ readonly updatedAt: Date;
230
+ readonly startedAt: Date | null;
231
+ readonly completedAt: Date | null;
232
+ }
233
+ /** True when the workflow run reached a stable end state (incl. `partial`). */
234
+ declare function isGoalJobTerminal(job: Pick<GoalJob, 'status'>): boolean;
235
+ /** True when the agent is blocked waiting on the user (`slots_pending`). */
236
+ declare function goalJobNeedsInput(job: Pick<GoalJob, 'status'>): boolean;
237
+ /** Discriminator for a server-sent event in a goal execution stream. */
238
+ type GoalEventType = 'tool_started' | 'tool_finished' | 'agent_step_started' | 'agent_step_finished' | 'orchestration_transition' | 'status' | 'progress' | 'completed' | 'failed' | 'slot_needed' | 'keepalive' | 'agent_text' | 'agent_text_done';
239
+ /** The known event `type` values (forward-compat: pre-listed so the SDK need
240
+ * not be re-cut the moment the backend starts emitting a reserved one). */
241
+ declare const GOAL_EVENT_TYPES: readonly GoalEventType[];
242
+ /** Type guard: is `value` a recognised {@link GoalEventType}? */
243
+ declare function isGoalEventType(value: string): value is GoalEventType;
244
+ /**
245
+ * Event `type` values that close the WebSocket and stop iteration. `cancelled`
246
+ * is included for a future backend that emits it directly (today it arrives as
247
+ * a `failed` event with `data.code === "CANCELLED"`) — so it is a plain string,
248
+ * not a {@link GoalEventType} member.
249
+ */
250
+ declare const GOAL_EVENT_TERMINAL_TYPES: readonly string[];
251
+ /** One server-sent event in a goal execution stream. */
252
+ interface GoalEvent {
253
+ readonly type: GoalEventType;
254
+ readonly schemaVersion: number;
255
+ readonly jobSpecId: string;
256
+ readonly emittedAt: Date;
257
+ readonly seq: number;
258
+ readonly data: Record<string, unknown>;
259
+ }
260
+ /** True when this event signals the end of the stream. */
261
+ declare function isGoalEventTerminal(event: Pick<GoalEvent, 'type'>): boolean;
262
+ /** Marketplace visibility of a community workflow. */
263
+ type WorkflowVisibility = 'private' | 'public' | 'archived';
264
+ /** Aggregate counters surfaced on every workflow row. */
265
+ interface WorkflowStats {
266
+ readonly runCount: number;
267
+ readonly forkCount: number;
268
+ readonly likeCount: number;
269
+ }
270
+ /** A community / private workflow row. */
271
+ interface Workflow {
272
+ readonly workflowId: string;
273
+ readonly ownerId: string | null;
274
+ readonly specId: string;
275
+ readonly sourceSpecId: string | null;
276
+ readonly sourceType: string | null;
277
+ readonly name: string;
278
+ readonly description: string | null;
279
+ readonly visibility: WorkflowVisibility;
280
+ readonly tags: string[];
281
+ readonly stats: WorkflowStats;
282
+ readonly itemVersion: number;
283
+ }
284
+ /** Light-weight community-list row (no heavy spec/system-prompt fields). */
285
+ interface WorkflowSummary {
286
+ readonly workflowId: string;
287
+ readonly specId: string;
288
+ readonly ownerId: string | null;
289
+ readonly name: string;
290
+ readonly description: string | null;
291
+ readonly visibility: WorkflowVisibility;
292
+ readonly tags: string[];
293
+ readonly stats: WorkflowStats;
294
+ }
295
+ /** One page of community workflow summaries + a pagination cursor. */
296
+ interface WorkflowSearchPage {
297
+ readonly items: WorkflowSummary[];
298
+ readonly cursor: string | null;
299
+ }
300
+ /** Result of `workflows.like` — toggle state + fresh count. */
301
+ interface LikeResponse {
302
+ readonly liked: boolean;
303
+ readonly likeCount: number;
304
+ }
305
+ /** Where an account sits against its quota window. */
306
+ type QuotaState = 'ok' | 'soft_limit' | 'quota_exceeded';
307
+ /** Account plan tier. */
308
+ type PlanTier = 'free' | 'pro';
309
+ /**
310
+ * Caller-plan vs cost-estimate verdict. Amounts in **credits** (the A4 parser
311
+ * converts the wire's micro-units → credits).
312
+ */
313
+ interface QuotaCheck {
314
+ readonly state: QuotaState;
315
+ readonly tier: PlanTier;
316
+ readonly estimatedCredits: number;
317
+ readonly thresholdCredits: number;
318
+ readonly upgradeUrl: string | null;
319
+ }
320
+ /** Per-tool cost breakdown row inside {@link CostEstimate}. */
321
+ interface ToolCostEstimate {
322
+ readonly toolName: string;
323
+ readonly perInvocationCredits: number;
324
+ }
325
+ /**
326
+ * Result of `account.getQuota()` — a cost-range projection (min / total / max)
327
+ * in **credits** plus the quota verdict, in one round-trip.
328
+ */
329
+ interface CostEstimate {
330
+ readonly estimatedCredits: number;
331
+ readonly estimatedUsd: number;
332
+ readonly estimatedTotalCredits: number;
333
+ readonly estimatedMinCredits: number;
334
+ readonly estimatedMaxCredits: number;
335
+ readonly tools: ToolCostEstimate[];
336
+ readonly quotaCheck: QuotaCheck;
337
+ }
338
+ /** The caller's current billing plan. */
339
+ interface Plan {
340
+ readonly tier: PlanTier;
341
+ }
342
+ /** One past usage period for a specific metric. */
343
+ interface UsageHistoryEntry {
344
+ readonly metric: string;
345
+ readonly periodStart: Date;
346
+ readonly periodEnd: Date;
347
+ readonly used: number;
348
+ readonly limit: number | null;
349
+ }
350
+
351
+ /**
352
+ * The minimal response shape a policy needs — so policies never depend on the
353
+ * concrete `fetch` `Response` or on `HttpClient`. `headers.get` is the standard
354
+ * `Headers` accessor (case-insensitive in real `fetch`).
355
+ */
356
+ interface RetryableResponse {
357
+ readonly status: number;
358
+ readonly headers: {
359
+ get(name: string): string | null;
360
+ };
361
+ }
362
+ /** Pluggable retry policy. Mirrors the Python `RetryPolicy` Protocol. */
363
+ interface RetryPolicy {
364
+ /** Decide whether a retry should fire for this outcome. */
365
+ shouldRetry(response: RetryableResponse | null, error: unknown, attempt: number): boolean;
366
+ /** Seconds to wait before the next attempt. */
367
+ nextDelay(response: RetryableResponse | null, attempt: number): number;
368
+ }
369
+ /** Tunables for {@link ExponentialBackoffRetry}; all optional. */
370
+ interface ExponentialBackoffOptions {
371
+ maxAttempts?: number;
372
+ baseDelay?: number;
373
+ maxDelay?: number;
374
+ jitter?: number;
375
+ retryableStatuses?: ReadonlySet<number>;
376
+ /** Injectable RNG in [0, 1) — defaults to `Math.random` (tests pass a stub). */
377
+ rng?: () => number;
378
+ }
379
+ /**
380
+ * Capped exponential backoff with full jitter, honouring `Retry-After`.
381
+ *
382
+ * Defaults match the Python SDK: 5 attempts, 0.2s base, 30s cap, ±25% jitter.
383
+ * Retries only on a retryable **status** — never on a thrown error (mirrors the
384
+ * Python policy, which receives `error=null` from the transport loop).
385
+ */
386
+ declare class ExponentialBackoffRetry implements RetryPolicy {
387
+ #private;
388
+ readonly maxAttempts: number;
389
+ readonly baseDelay: number;
390
+ readonly maxDelay: number;
391
+ readonly jitter: number;
392
+ readonly retryableStatuses: ReadonlySet<number>;
393
+ constructor(options?: ExponentialBackoffOptions);
394
+ shouldRetry(response: RetryableResponse | null, error: unknown, attempt: number): boolean;
395
+ nextDelay(response: RetryableResponse | null, attempt: number): number;
396
+ }
397
+ /** Disables retries entirely — pass to the client to fail fast. */
398
+ declare class NoRetry implements RetryPolicy {
399
+ shouldRetry(): boolean;
400
+ nextDelay(): number;
401
+ }
402
+
403
+ /**
404
+ * Auto-throttle policy — sleep + retry on {@link QuotaExceededError}. Port of
405
+ * the Python SDK's `_internal/throttle.py`.
406
+ *
407
+ * Layered *above* the transport's {@link RetryPolicy}: that policy retries
408
+ * transient transport failures (5xx / 429 / 408), while this module targets the
409
+ * 402 `QUOTA_EXCEEDED` envelope specifically — where the remediation is *waiting
410
+ * for the quota window to reset*, not re-issuing the same request faster.
411
+ *
412
+ * Only {@link AutoThrottleConfig} is part of the public surface (re-exported
413
+ * from the package root); the resolver / sleep math / soft-limit helpers are
414
+ * internal wiring used by `internal/http.ts`.
415
+ *
416
+ * Design choices (mirroring the Python module):
417
+ * - **Bounded retry** — defaults to `maxRetries = 1` so a misconfigured caller
418
+ * cannot get stuck in an infinite sleep+retry loop against a permanently
419
+ * overrun account.
420
+ * - **Bounded sleep** — `maxSleep` caps any server-supplied delay; if the
421
+ * server asks the SDK to wait longer, the SDK gives up immediately and
422
+ * re-raises so the caller's UX path stays predictable.
423
+ * - **Forward-compat reset hints** — when the backend starts emitting them, the
424
+ * SDK reads `details.retry_after_seconds` or `details.reset_at` transparently.
425
+ */
426
+
427
+ /** Tunables for {@link AutoThrottleConfig}; all optional (each defaults above). */
428
+ interface AutoThrottleOptions {
429
+ /** Max number of quota-retry attempts after the first 402 (default 1). */
430
+ maxRetries?: number;
431
+ /** Hard cap (seconds) on any single sleep; over this the SDK re-raises. */
432
+ maxSleep?: number;
433
+ /** Sleep (seconds) used when the 402 carries no reset hint. */
434
+ fallbackSleep?: number;
435
+ }
436
+ /**
437
+ * Knobs for the {@link QuotaExceededError} retry loop. Enable on the client with
438
+ * `new Convilyn({ autoThrottle: true })` (defaults) or
439
+ * `new Convilyn({ autoThrottle: new AutoThrottleConfig({ maxRetries: 2 }) })`.
440
+ */
441
+ declare class AutoThrottleConfig {
442
+ readonly maxRetries: number;
443
+ readonly maxSleep: number;
444
+ readonly fallbackSleep: number;
445
+ constructor(options?: AutoThrottleOptions);
446
+ }
447
+
448
+ /** Pluggable auth strategy. Mirrors the Python `AuthStrategy` Protocol. */
449
+ interface AuthStrategy {
450
+ /** Headers to merge into every request (e.g. `Authorization`). */
451
+ headers(): Record<string, string>;
452
+ /** The raw bearer token — used by non-HTTP transports (WebSocket). */
453
+ bearerToken(): string;
454
+ }
455
+
456
+ /**
457
+ * Public transport contracts — the dependency-injection seams the client
458
+ * surface exposes so callers can swap behaviour without forking the SDK.
459
+ *
460
+ * This module is **public** (re-exported from the package root): it holds the
461
+ * contract, not the implementation. The default WHATWG-`WebSocket` adapter that
462
+ * satisfies {@link WSTransport} lives in `internal/ws.ts` and is NOT part of the
463
+ * public surface — only the interface a custom transport must implement is.
464
+ */
465
+ /**
466
+ * Minimal contract a goal event transport must satisfy. Inject a custom
467
+ * one via `new Convilyn({ wsTransportFactory })` to stream `goals.events()` over
468
+ * a non-default socket (e.g. the `ws` package in older Node, or a test double).
469
+ */
470
+ interface WSTransport {
471
+ connect(url: string): Promise<void>;
472
+ send(payload: string): Promise<void>;
473
+ recv(): Promise<string>;
474
+ close(): Promise<void>;
475
+ }
476
+
477
+ /**
478
+ * HTTP transport — port of `_internal/http.py` (fetch-based; Node 18+ global
479
+ * `fetch` or any browser).
480
+ *
481
+ * Responsibilities (single, per SRP):
482
+ * - inject auth + standard headers (`User-Agent`, `Accept`);
483
+ * - normalise the Convilyn error envelope `{code, message, details}` (flat, or
484
+ * nested under `detail` / `error`) into typed errors;
485
+ * - apply the injected {@link RetryPolicy} on transient 408/429/5xx, reusing one
486
+ * `Idempotency-Key` across retries of a mutating verb;
487
+ * - presigned-URL helpers (`externalPut` / `externalGet`) that send NO auth.
488
+ *
489
+ * The resilience decision lives in {@link RetryPolicy} (DIP seam) — this client
490
+ * owns only the loop. Optionally, an {@link AutoThrottleConfig} adds a higher-
491
+ * level shim that sleeps + re-issues on the 402 `QUOTA_EXCEEDED` envelope
492
+ * (remediation = waiting for the quota window, not retrying faster); it stays
493
+ * OFF unless the caller opts in, so the default path decodes + throws unchanged.
494
+ */
495
+
496
+ /** Request body shapes the transport accepts. */
497
+ type RequestBody = Uint8Array | ArrayBuffer | string | ReadableStream<Uint8Array>;
498
+ /** Narrow, stubbable `fetch` signature (the real global `fetch` satisfies it). */
499
+ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
500
+ /** Per-request options for {@link HttpClient.request} / `rawRequest`. */
501
+ interface RequestOptions {
502
+ headers?: Record<string, string>;
503
+ json?: unknown;
504
+ params?: Record<string, string | number | boolean | null | undefined>;
505
+ content?: RequestBody;
506
+ }
507
+ /** Constructor options for {@link HttpClient}. */
508
+ interface HttpClientOptions {
509
+ auth: AuthStrategy;
510
+ baseUrl?: string;
511
+ /** Per-request timeout in seconds. */
512
+ timeout?: number;
513
+ userAgent?: string;
514
+ retryPolicy?: RetryPolicy;
515
+ idempotencyEnabled?: boolean;
516
+ /** Opt-in 402-quota sleep+retry shim; omitted / null = disabled. */
517
+ autoThrottle?: AutoThrottleConfig | null;
518
+ /** Injectable `fetch` (tests pass a stub). Defaults to the global `fetch`. */
519
+ fetch?: FetchLike;
520
+ /** Injectable sleep in seconds (tests pass a no-op). Defaults to real timer. */
521
+ sleep?: (seconds: number) => Promise<void>;
522
+ }
523
+ /** Async HTTP client with Convilyn-specific header + error semantics. */
524
+ declare class HttpClient {
525
+ #private;
526
+ constructor(options: HttpClientOptions);
527
+ get baseUrl(): string;
528
+ /** Read-only handle to the auth strategy (used by the WS transport). */
529
+ get auth(): AuthStrategy;
530
+ /**
531
+ * Issue a request, applying the retry policy on transient failures and
532
+ * raising a typed error on a non-2xx final response.
533
+ */
534
+ request(method: string, path: string, options?: RequestOptions): Promise<Response>;
535
+ /**
536
+ * Like {@link request} but never raises on HTTP status — returns the final
537
+ * response so callers (the `convilyn api` CLI escape hatch) can inspect it.
538
+ */
539
+ rawRequest(method: string, path: string, options?: RequestOptions): Promise<Response>;
540
+ /**
541
+ * PUT to an external absolute URL (e.g. a presigned upload URL). Sends NO
542
+ * Convilyn auth (the URL carries its own signature) and is NOT retried.
543
+ */
544
+ externalPut(url: string, { content, headers }: {
545
+ content: RequestBody;
546
+ headers?: Record<string, string>;
547
+ }): Promise<Response>;
548
+ /** GET an external absolute URL (e.g. a presigned download). No auth. */
549
+ externalGet(url: string, { headers }?: {
550
+ headers?: Record<string, string>;
551
+ }): Promise<Response>;
552
+ /** No persistent connection pool with `fetch`; provided for API symmetry. */
553
+ close(): Promise<void>;
554
+ }
555
+
556
+ /**
557
+ * Account resource — caller-side billing plan + quota queries. Port of
558
+ * `resources/account.py`.
559
+ *
560
+ * POST /api/v1/workflows/cost-preview — estimate cost (credits) + quota verdict
561
+ * GET /api/v1/payment/usage/history — past usage periods
562
+ *
563
+ * Read-only — actions (upgrade, top-up) live on the website, not the SDK.
564
+ * Money is expressed in **credits** (the µU wire is converted in the parser).
565
+ */
566
+
567
+ /** Options for {@link Account.getQuota}. */
568
+ interface GetQuotaOptions {
569
+ /** Fully-qualified tool ids (e.g. `pdf-mcp:extract_text`). */
570
+ tools?: string[];
571
+ /** Iteration cap from the draft spec. */
572
+ maxIterations?: number;
573
+ }
574
+ /** Asynchronous account-tier resource (`client.account`). */
575
+ declare class Account {
576
+ #private;
577
+ constructor(http: HttpClient);
578
+ /** Estimate a workflow's cost (credits) + check the verdict against the plan. */
579
+ getQuota(options?: GetQuotaOptions): Promise<CostEstimate>;
580
+ /** Return the caller's current billing plan (derived from the cost-preview tier). */
581
+ getPlan(): Promise<Plan>;
582
+ /**
583
+ * List the caller's past usage periods (one row per metric+period). `since`
584
+ * filters client-side to entries whose `periodStart` is on or after it
585
+ * (the backend does not honour a query param today).
586
+ */
587
+ usageHistory(options?: {
588
+ since?: Date;
589
+ }): Promise<UsageHistoryEntry[]>;
590
+ }
591
+
592
+ /**
593
+ * Convert resource — document conversion jobs. Port of
594
+ * `resources/convert.py`.
595
+ *
596
+ * POST /api/v1/jobs — create a conversion job
597
+ * GET /api/v1/jobs/{id} — poll status
598
+ * GET <resultFiles[0].url>— download the produced artifact (presigned, no auth)
599
+ *
600
+ * Data lives on {@link ConvertJob}; behaviour (poll / wait / download) lives
601
+ * here. The poll clock + sleep are injectable so the wait loop is testable
602
+ * without real time.
603
+ */
604
+
605
+ /** Options for {@link Convert.create} — supply exactly one of `file` / `fileId`. */
606
+ interface CreateConvertOptions {
607
+ file?: File;
608
+ fileId?: string;
609
+ targetFormat: string;
610
+ sourceFormat?: string;
611
+ quality?: string;
612
+ pageRange?: string;
613
+ }
614
+ /** Polling controls for {@link Convert.wait}. */
615
+ interface WaitOptions {
616
+ timeout?: number;
617
+ pollInterval?: number;
618
+ }
619
+ /** Test seams: an injectable clock (ms) + sleep (seconds). */
620
+ interface ConvertResourceOptions {
621
+ sleep?: (seconds: number) => Promise<void>;
622
+ now?: () => number;
623
+ }
624
+ /** Asynchronous conversion resource (`client.convert`). */
625
+ declare class Convert {
626
+ #private;
627
+ constructor(http: HttpClient, options?: ConvertResourceOptions);
628
+ /** Create a conversion job and return immediately (queued/processing). */
629
+ create(options: CreateConvertOptions): Promise<ConvertJob>;
630
+ /** Fetch the current state of a job (one-shot poll). */
631
+ retrieve(jobId: string): Promise<ConvertJob>;
632
+ /**
633
+ * Poll until the job reaches a terminal status, the timeout expires, or the
634
+ * backend reports failure.
635
+ *
636
+ * @throws {@link JobFailedError} when the terminal status is `failed`.
637
+ * @throws {@link JobTimeoutError} when `timeout` elapses first.
638
+ */
639
+ wait(jobId: string, options?: WaitOptions): Promise<ConvertJob>;
640
+ /** Shortcut: {@link create} then {@link wait}. */
641
+ createAndWait(options: CreateConvertOptions & WaitOptions): Promise<ConvertJob>;
642
+ /** Presigned download URL for the first result file. */
643
+ downloadUrl(job: ConvertJob | string): Promise<string>;
644
+ /** Download the first result file to `to` (Node) and return that path. */
645
+ downloadTo(job: ConvertJob | string, { to }: {
646
+ to: string;
647
+ }): Promise<string>;
648
+ }
649
+
650
+ /**
651
+ * Files resource — upload local files via Convilyn's presigned-URL flow.
652
+ *
653
+ * Port of `resources/files.py`. Three steps behind one {@link Files.upload}:
654
+ * 1. `POST /api/v1/upload/presign` — Convilyn issues a presigned upload URL
655
+ * 2. `PUT <presigned-url>` — bytes go straight to storage (no Convilyn auth)
656
+ * 3. `POST /api/v1/upload/confirm` — Convilyn registers the file record
657
+ *
658
+ * The steps are private methods so future extensions (multipart, progress) can
659
+ * override one without rewriting the orchestration. The resource depends only
660
+ * on {@link HttpClient} + types (clean DIP — no sibling-resource imports).
661
+ */
662
+
663
+ /** Caller input for {@link Files.upload} — exactly one of `path` / `content`. */
664
+ interface UploadOptions {
665
+ /** Filesystem path (Node only). */
666
+ path?: string;
667
+ /** Raw bytes (works in the browser too). */
668
+ content?: Uint8Array | ArrayBuffer | string;
669
+ /** Override the filename (required when uploading raw `content`). */
670
+ filename?: string;
671
+ /** MIME type; defaults to a best-guess from the filename suffix. */
672
+ contentType?: string;
673
+ }
674
+ /** Asynchronous file-management resource (`client.files`). */
675
+ declare class Files {
676
+ #private;
677
+ constructor(http: HttpClient);
678
+ /**
679
+ * Upload a file and return its {@link File} record. Supply exactly one of
680
+ * `path` (Node) or `content` (bytes/string).
681
+ */
682
+ upload(options: UploadOptions): Promise<File>;
683
+ }
684
+
685
+ /**
686
+ * Goals resource — agentic goal workflow execution. Port of
687
+ * `resources/goals.py`.
688
+ *
689
+ * POST /api/v1/jobs/goal — create a job ("workflow run")
690
+ * GET /api/v1/jobs/goal/{id} — full status (drives polling)
691
+ * PATCH /api/v1/jobs/goal/{id}/slots — answer HITL slots
692
+ * POST /api/v1/jobs/goal/{id}/confirm|cancel|retry
693
+ * WS subscribe → GoalEvent stream — live execution events
694
+ *
695
+ * Same SOLID seams as the convert resource (data on {@link GoalJob}, behaviour
696
+ * here) plus the {@link WSTransport} DIP seam for the event stream.
697
+ */
698
+
699
+ /** Options for {@link Goals.start} — supply exactly one of `workflowId` / `goalText`. */
700
+ interface StartGoalOptions {
701
+ workflowId?: string;
702
+ goalText?: string;
703
+ files?: string[];
704
+ slots?: Record<string, unknown>;
705
+ /**
706
+ * Optional BYO-LLM provider config id (from the console vault) to run this
707
+ * job on your own provider/key. Omit to use your account default config.
708
+ * Honoured only when BYO-LLM is enabled for your account; otherwise the run
709
+ * uses the platform provider.
710
+ */
711
+ llmConfigId?: string;
712
+ }
713
+ /** Polling controls for {@link Goals.wait}. */
714
+ interface GoalWaitOptions {
715
+ timeout?: number;
716
+ pollInterval?: number;
717
+ }
718
+ /** Constructor options + test seams for {@link Goals}. */
719
+ interface GoalsResourceOptions {
720
+ wsUrl?: string | null;
721
+ wsTransportFactory?: () => WSTransport;
722
+ sleep?: (seconds: number) => Promise<void>;
723
+ now?: () => number;
724
+ }
725
+ /** Asynchronous goal workflow resource (`client.goals`). */
726
+ declare class Goals {
727
+ #private;
728
+ constructor(http: HttpClient, options?: GoalsResourceOptions);
729
+ /** Create a goal job and return its initial {@link GoalJob} state. */
730
+ start(options: StartGoalOptions): Promise<GoalJob>;
731
+ /** Fetch the current state of a goal job. */
732
+ retrieve(jobSpecId: string): Promise<GoalJob>;
733
+ /**
734
+ * Poll until the job reaches a terminal state OR stops for HITL
735
+ * (`slots_pending`). `failed` raises {@link GoalJobFailedError}; an elapsed
736
+ * deadline raises {@link GoalJobTimeoutError}.
737
+ */
738
+ wait(jobSpecId: string, options?: GoalWaitOptions): Promise<GoalJob>;
739
+ /** Shortcut: {@link start} then {@link wait}. */
740
+ run(options: StartGoalOptions & GoalWaitOptions): Promise<GoalJob>;
741
+ /** Answer a single slot the agent is waiting on (sugar over {@link fillSlots}). */
742
+ fillSlot(jobSpecId: string, options: {
743
+ slotId: string;
744
+ value: unknown;
745
+ expectedVersion?: number;
746
+ }): Promise<GoalJob>;
747
+ /** Answer one or more slots in a single PATCH (`{slotId: value}` → wire list). */
748
+ fillSlots(jobSpecId: string, answers: Record<string, unknown>, options?: {
749
+ expectedVersion?: number;
750
+ }): Promise<GoalJob>;
751
+ /** Confirm a slots-filled job, queueing it for execution. */
752
+ confirm(jobSpecId: string, options?: {
753
+ expectedVersion?: number;
754
+ }): Promise<GoalJob>;
755
+ /** Cancel a running or queued job. */
756
+ cancel(jobSpecId: string): Promise<GoalJob>;
757
+ /** Retry a failed job (resume the thread or start a fresh rerun). */
758
+ retry(jobSpecId: string, options?: {
759
+ rerunMode?: 'retry_same_thread' | 'fresh_rerun';
760
+ reason?: string;
761
+ }): Promise<GoalJob>;
762
+ /**
763
+ * Stream goal execution events for a job. Opens a WebSocket, subscribes,
764
+ * then yields each {@link GoalEvent} in order; the iterator ends (and the
765
+ * socket closes) on a terminal event. No auto-reconnect — the backend does
766
+ * not replay missed events, so a silent reconnect would hide gaps.
767
+ *
768
+ * **WebSocket streaming is not available with a `ck_` consumer key in v1** —
769
+ * the backend WS gateway does not authenticate `ck_` keys, so the connection
770
+ * is denied. Use {@link wait} polling (standard HTTP auth, works today) for
771
+ * the supported real-time path. This method is wired and ready for when the
772
+ * gateway gains `ck_` support.
773
+ *
774
+ * @throws {@link WebSocketError} on connect/subscribe failure, a dropped
775
+ * stream, or an unparseable/unrecognised envelope.
776
+ */
777
+ events(jobSpecId: string, options?: {
778
+ wsUrl?: string;
779
+ }): AsyncGenerator<GoalEvent, void, void>;
780
+ }
781
+
782
+ /**
783
+ * Workflows resource — community marketplace + private workflow management.
784
+ * Port of `resources/workflows.py`.
785
+ *
786
+ * GET /api/v1/workflows/community — browse the public listing (search)
787
+ * GET /api/v1/workflows/{id} — fetch one row
788
+ * POST /api/v1/workflows/fork — fork a source into a private workflow
789
+ * PATCH /api/v1/workflows/{id} — partial update (publish = visibility)
790
+ * POST /api/v1/workflows/{id}/like — toggle like
791
+ *
792
+ * `fork` / `publish` require Pro tier; the platform's 402/403 surfaces as
793
+ * {@link import('../errors').APIError} so callers branch on the status code.
794
+ */
795
+
796
+ /** Sort order for a marketplace workflow search. */
797
+ type WorkflowSortMode = 'recent' | 'popular';
798
+ /** Options for {@link Workflows.search}. */
799
+ interface WorkflowSearchOptions {
800
+ tag?: string;
801
+ sort?: WorkflowSortMode;
802
+ limit?: number;
803
+ cursor?: string;
804
+ }
805
+ /** Options for {@link Workflows.fork}. */
806
+ interface ForkWorkflowOptions {
807
+ sourceSpecId: string;
808
+ name?: string;
809
+ }
810
+ /** Options for {@link Workflows.patch} (optimistic lock via `itemVersion`). */
811
+ interface PatchWorkflowOptions {
812
+ itemVersion: number;
813
+ name?: string;
814
+ description?: string;
815
+ visibility?: WorkflowVisibility;
816
+ tags?: string[];
817
+ }
818
+ /** Asynchronous community-workflows resource (`client.workflows`). */
819
+ declare class Workflows {
820
+ #private;
821
+ constructor(http: HttpClient);
822
+ /** Browse the public community listing (one page + a `cursor`). */
823
+ search(options?: WorkflowSearchOptions): Promise<WorkflowSearchPage>;
824
+ /** Fetch a single workflow by id. */
825
+ get(workflowId: string): Promise<Workflow>;
826
+ /** Fork a curated/user source into a new private workflow (Pro). */
827
+ fork(options: ForkWorkflowOptions): Promise<Workflow>;
828
+ /** Flip an owned workflow's visibility to `public` (Pro; optimistic lock). */
829
+ publish(workflowId: string, options: {
830
+ itemVersion: number;
831
+ }): Promise<Workflow>;
832
+ /** Partial update of an owned workflow (the generic PATCH escape hatch). */
833
+ patch(workflowId: string, options: PatchWorkflowOptions): Promise<Workflow>;
834
+ /** Toggle the caller's like on a public workflow (idempotent toggle). */
835
+ like(workflowId: string): Promise<LikeResponse>;
836
+ }
837
+
838
+ /**
839
+ * Async root client — port of `client.py`.
840
+ *
841
+ * Wires auth + HTTP transport and exposes the resource accessors. This A2
842
+ * increment exposes `files` + `convert`; `goals` / `workflows` / `account`
843
+ * (and the WebSocket knobs) extend this class in A4. Async-only: Node and the
844
+ * browser are async-native, so there is no synchronous wrapper.
845
+ */
846
+
847
+ /** Constructor options for {@link Convilyn}. */
848
+ interface ConvilynOptions {
849
+ /** API key; falls back to `CONVILYN_API_KEY`. Ignored when `auth` is given. */
850
+ apiKey?: string;
851
+ baseUrl?: string;
852
+ /** Per-request timeout in seconds. */
853
+ timeout?: number;
854
+ /** Custom auth strategy (overrides `apiKey`). */
855
+ auth?: AuthStrategy;
856
+ /** Cap retry attempts (`0` = no retries). Ignored when `retryPolicy` is given. */
857
+ maxRetries?: number;
858
+ /** Custom retry policy (overrides `maxRetries`). */
859
+ retryPolicy?: RetryPolicy;
860
+ /** Disable the automatic `Idempotency-Key` on mutating verbs. */
861
+ disableIdempotency?: boolean;
862
+ /** WebSocket base URL for `goals.events()`; falls back to `CONVILYN_WS_URL`. */
863
+ wsUrl?: string;
864
+ /** Custom WebSocket transport factory (advanced / testing). */
865
+ wsTransportFactory?: () => WSTransport;
866
+ /**
867
+ * Sleep + retry on a 402 quota error, waiting for the quota window to reset
868
+ * (distinct from {@link retryPolicy}, which handles transient 5xx/429/408).
869
+ * `true` enables it with defaults (1 retry, 60s cap); pass
870
+ * `new AutoThrottleConfig({ … })` to tune. Omitted = disabled.
871
+ */
872
+ autoThrottle?: boolean | AutoThrottleConfig;
873
+ }
874
+ /** Asynchronous Convilyn client. */
875
+ declare class Convilyn {
876
+ #private;
877
+ readonly files: Files;
878
+ readonly convert: Convert;
879
+ readonly goals: Goals;
880
+ readonly workflows: Workflows;
881
+ readonly account: Account;
882
+ constructor(options?: ConvilynOptions);
883
+ get baseUrl(): string;
884
+ close(): Promise<void>;
885
+ }
886
+
887
+ /**
888
+ * Single source of truth for the package version. Kept in lockstep with
889
+ * package.json + CHANGELOG.md (guarded by test/packaging/version-changelog.test.ts).
890
+ */
891
+ declare const VERSION = "0.2.0";
892
+
893
+ export { APIError, AuthError, AutoThrottleConfig, CONVERT_JOB_TERMINAL_STATUSES, type ConvertJob, Convilyn, ConvilynError, type ConvilynOptions, type CostEstimate, type CreateConvertOptions, type ErrorDetails, ExponentialBackoffRetry, type File, type ForkWorkflowOptions, GOAL_EVENT_TERMINAL_TYPES, GOAL_EVENT_TYPES, GOAL_JOB_TERMINAL_STATUSES, type GetQuotaOptions, type GoalEvent, type GoalEventType, type GoalJob, GoalJobFailedError, type GoalJobStatus, GoalJobTimeoutError, type GoalWaitOptions, type JobError, JobFailedError, type JobStatus, JobTimeoutError, type LikeResponse, NoRetry, type PatchWorkflowOptions, type PendingSlot, type Plan, PlanRequiredError, type PlanTier, type QuotaCheck, QuotaExceededError, type QuotaState, RateLimitError, type ResultFile, RetryExhaustedError, type RetryPolicy, S3UploadError, type StartGoalOptions, type ToolCostEstimate, type UploadOptions, type UsageHistoryEntry, VERSION, type WSTransport, type WaitOptions, WebSocketError, type Workflow, type WorkflowSearchOptions, type WorkflowSearchPage, type WorkflowSortMode, type WorkflowStats, type WorkflowSummary, type WorkflowVisibility, goalJobNeedsInput, isConvertJobTerminal, isGoalEventTerminal, isGoalEventType, isGoalJobTerminal };