combined-ai 0.1.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,773 @@
1
+ /**
2
+ * Core, provider-agnostic contract shared by every AI provider in the package.
3
+ *
4
+ * Everything else (additional providers, provider selection, multi-provider
5
+ * combinations) builds on these types.
6
+ */
7
+ type Role = "user" | "assistant";
8
+ /** A text segment of a message's content. */
9
+ type TextPart = {
10
+ type: "text";
11
+ text: string;
12
+ };
13
+ /**
14
+ * Where binary media (an image or document) comes from — either inline
15
+ * base64-encoded bytes (with their MIME type) or a URL the provider fetches.
16
+ * For a `url` source, `mediaType` is optional but some providers need it (e.g.
17
+ * Gemini's file references), so set it when known.
18
+ */
19
+ type MediaSource = {
20
+ kind: "base64";
21
+ mediaType: string;
22
+ data: string;
23
+ } | {
24
+ kind: "url";
25
+ url: string;
26
+ mediaType?: string;
27
+ };
28
+ /** An image input (PNG/JPEG/WebP/GIF, per the provider's support). */
29
+ type ImagePart = {
30
+ type: "image";
31
+ source: MediaSource;
32
+ };
33
+ /** A document input, e.g. a PDF (`source.mediaType` should be `"application/pdf"`). */
34
+ type FilePart = {
35
+ type: "file";
36
+ source: MediaSource;
37
+ /** Optional file name (used by OpenAI's file input). */
38
+ filename?: string;
39
+ };
40
+ /**
41
+ * A tool call the model made, as it appears in an **assistant** message's content
42
+ * when you replay the conversation. Build these from the {@link ToolCall}s a prior
43
+ * `complete()` returned, append them as the assistant turn, then send the matching
44
+ * {@link ToolResultPart}s in the next user message.
45
+ */
46
+ type ToolUsePart = {
47
+ type: "tool_use";
48
+ /** The id the provider assigned to the call; echo it on the matching result. */
49
+ id?: string;
50
+ name: string;
51
+ /** The arguments the model passed (a parsed object). */
52
+ input: Record<string, unknown>;
53
+ };
54
+ /**
55
+ * A tool's result, placed in a **user** message's content to feed it back to the
56
+ * model. Carry both `toolUseId` (Anthropic/OpenAI match the call by id) and `name`
57
+ * (Gemini matches by function name) when you have them.
58
+ */
59
+ type ToolResultPart = {
60
+ type: "tool_result";
61
+ /** The id of the {@link ToolUsePart}/{@link ToolCall} this answers. */
62
+ toolUseId?: string;
63
+ /** The tool's name — required for Gemini, which matches results by name. */
64
+ name?: string;
65
+ /** The tool's output as text. */
66
+ content: string;
67
+ /** Mark the call as having errored (the model is told the tool failed). */
68
+ isError?: boolean;
69
+ };
70
+ /**
71
+ * One part of a structured message content: text, an image, a file/document, or a
72
+ * tool call / tool result (for replaying a tool-use conversation). Widening
73
+ * {@link Message.content} from `string` to `string | ContentPart[]` was the one
74
+ * breaking change; adding members here is additive.
75
+ *
76
+ * Provider support varies (e.g. OpenAI's Chat Completions has no URL file
77
+ * source); each provider's content mapper handles what its API supports and
78
+ * throws a clear error otherwise.
79
+ */
80
+ type ContentPart = TextPart | ImagePart | FilePart | ToolUsePart | ToolResultPart;
81
+ type Message = {
82
+ role: Role;
83
+ /**
84
+ * The message body. A bare `string` is shorthand for a single text part, so
85
+ * existing string callers keep working unchanged; pass `ContentPart[]` for
86
+ * structured (e.g. future multimodal) content.
87
+ */
88
+ content: string | ContentPart[];
89
+ };
90
+ /**
91
+ * Constrain the model's output to a JSON Schema (structured output). The schema
92
+ * is a plain JSON Schema object — no Zod/runtime dependency. Each provider maps
93
+ * it onto its own native mechanism (Anthropic `output_config.format`, OpenAI
94
+ * `response_format`, Gemini `responseSchema`).
95
+ *
96
+ * **For one schema to work across all three providers** (driven by the strictest
97
+ * rules), keep it simple: every `object` sets `additionalProperties: false`, and
98
+ * every property is listed in `required` with a single non-null `type`. Avoid
99
+ * optional and nullable fields — OpenAI's strict mode requires every property in
100
+ * `required`, and Gemini expresses nullability with `nullable: true` rather than a
101
+ * `["string", "null"]` union or an `anyOf` with a null member, so null-unions are
102
+ * **not** portable (they're passed through untranslated and Gemini will reject
103
+ * them). Also avoid recursive schemas, numeric/length constraints
104
+ * (`minimum`/`maxLength`/…), and `$ref` (Gemini ignores most JSON Schema keywords
105
+ * beyond types/enum/format). Provider-specific schemas can of course use more —
106
+ * these constraints are only for a single schema shared across providers.
107
+ */
108
+ type ResponseFormat = {
109
+ type: "json_schema";
110
+ /** A JSON Schema object describing the desired output shape. */
111
+ schema: Record<string, unknown>;
112
+ /**
113
+ * Schema name. OpenAI requires one (must match `^[a-zA-Z0-9_-]+$`); defaulted
114
+ * when omitted. Ignored by Anthropic and Gemini.
115
+ */
116
+ name?: string;
117
+ };
118
+ /**
119
+ * A tool the model may call. `parameters` is a JSON Schema describing the tool's
120
+ * input (the same cross-provider schema guidance as {@link ResponseFormat} applies).
121
+ */
122
+ type ToolDefinition = {
123
+ name: string;
124
+ description?: string;
125
+ parameters: Record<string, unknown>;
126
+ };
127
+ /**
128
+ * How the model should use the supplied tools:
129
+ * - `"auto"` — the model decides whether to call a tool (the default when tools
130
+ * are present);
131
+ * - `"any"` — the model must call some tool;
132
+ * - `"none"` — the model must not call a tool;
133
+ * - `{ name }` — the model must call that specific tool.
134
+ */
135
+ type ToolChoice = "auto" | "any" | "none" | {
136
+ name: string;
137
+ };
138
+ /**
139
+ * A tool call the model requested, surfaced on {@link CompletionResult.toolCalls}.
140
+ * `input` is the parsed arguments object (OpenAI's JSON-string arguments are
141
+ * parsed for you; Anthropic/Gemini already return an object).
142
+ */
143
+ type ToolCall = {
144
+ /** Provider-assigned id (always on Anthropic/OpenAI; newer Gemini models). */
145
+ id?: string;
146
+ name: string;
147
+ input: Record<string, unknown>;
148
+ };
149
+ type CompletionRequest = {
150
+ messages: Message[];
151
+ /** Optional system prompt applied to the whole request. */
152
+ system?: string;
153
+ /** Override the provider's default model. */
154
+ model?: string;
155
+ /** Override the provider's default output-token cap. */
156
+ maxTokens?: number;
157
+ /**
158
+ * Constrain the output to a JSON Schema. The model returns JSON in `text`, and
159
+ * `complete()` also surfaces the parsed value on {@link CompletionResult.parsed}.
160
+ */
161
+ responseFormat?: ResponseFormat;
162
+ /**
163
+ * Tools the model may call. When the model calls one, `complete()` returns the
164
+ * calls on {@link CompletionResult.toolCalls} (and `finishReason: "tool_use"`);
165
+ * you run them and feed results back as {@link ToolResultPart}s in the next
166
+ * message. Surfaced by `complete()` only — `stream()` yields text deltas and
167
+ * does not report tool calls.
168
+ */
169
+ tools?: ToolDefinition[];
170
+ /** Constrain whether/which tool the model calls. Defaults to provider behavior (`"auto"`). */
171
+ toolChoice?: ToolChoice;
172
+ /**
173
+ * Abort the request (and, for `stream()`, the in-flight read) when this signal
174
+ * fires. For a timeout, pass `AbortSignal.timeout(ms)`. An aborted request
175
+ * rejects with a transport `ProviderError` whose `cause` is the abort reason.
176
+ */
177
+ signal?: AbortSignal;
178
+ };
179
+ /**
180
+ * Normalized, provider-agnostic stop reason. The provider's exact value is
181
+ * preserved separately on {@link CompletionResult.rawFinishReason}.
182
+ *
183
+ * - `"stop"` — the model finished on its own.
184
+ * - `"length"` — output was truncated at the token cap (likely an empty `text`
185
+ * when the cap was spent on thinking tokens; see the Gemini note in CLAUDE.md).
186
+ * - `"content_filter"` — the model refused or output was blocked by a safety
187
+ * filter.
188
+ * - `"tool_use"` — the model stopped to call a tool; see
189
+ * {@link CompletionResult.toolCalls}.
190
+ * - `"other"` — any other or unrecognized reason.
191
+ */
192
+ type FinishReason = "stop" | "length" | "content_filter" | "tool_use" | "other";
193
+ /**
194
+ * Token usage for a single completion. `totalTokens` is the provider's own total
195
+ * when it reports one (Gemini's includes thinking tokens, so it can exceed
196
+ * input + output), otherwise `inputTokens + outputTokens`.
197
+ */
198
+ type Usage = {
199
+ inputTokens: number;
200
+ outputTokens: number;
201
+ totalTokens: number;
202
+ };
203
+ type CompletionResult = {
204
+ text: string;
205
+ /** The model that actually produced the response. */
206
+ model: string;
207
+ /**
208
+ * Normalized stop reason, or `undefined` if the provider returned none.
209
+ * Lets callers distinguish a real empty answer from a truncated/refused one
210
+ * instead of seeing a bare `text: ""`.
211
+ */
212
+ finishReason?: FinishReason;
213
+ /** The provider's exact stop-reason string, verbatim (before normalization). */
214
+ rawFinishReason?: string;
215
+ /**
216
+ * The refusal message when the model explicitly declined (currently OpenAI's
217
+ * `message.refusal`). When set, `finishReason` is `"content_filter"`.
218
+ */
219
+ refusal?: string;
220
+ /** Token usage for this completion, or `undefined` if the provider reported none. */
221
+ usage?: Usage;
222
+ /**
223
+ * The parsed structured output, set only when {@link CompletionRequest.responseFormat}
224
+ * was given and `text` parsed as JSON. `undefined` if no schema was requested
225
+ * or the output wasn't valid JSON (e.g. truncated at the token cap). The raw
226
+ * JSON is always in `text`. Typed `unknown` — cast to your schema's type.
227
+ */
228
+ parsed?: unknown;
229
+ /**
230
+ * The tool calls the model requested, set only when it called at least one tool
231
+ * (then `finishReason` is `"tool_use"`). Run them and feed results back as
232
+ * {@link ToolResultPart}s. `complete()`-only.
233
+ */
234
+ toolCalls?: ToolCall[];
235
+ };
236
+ type Provider = {
237
+ readonly name: string;
238
+ /** Run a single completion and return the full text. */
239
+ complete(request: CompletionRequest): Promise<CompletionResult>;
240
+ /** Run a single completion, yielding text deltas as they arrive. */
241
+ stream(request: CompletionRequest): AsyncIterable<string>;
242
+ };
243
+
244
+ /**
245
+ * Public types for the **combine** feature — multiple providers cooperating on
246
+ * one prompt via a selectable strategy. The strategy implementations live in
247
+ * sibling files (e.g. `consensus.ts`); the public entry point is
248
+ * {@link ProviderRegistry.combine}.
249
+ */
250
+
251
+ /** The cooperation strategies the registry knows how to run. */
252
+ declare const STRATEGY_NAMES: readonly ["consensus", "pipeline", "ensemble", "broadcast"];
253
+ type StrategyName = (typeof STRATEGY_NAMES)[number];
254
+ /**
255
+ * One participant in a combine. A bare provider name uses that provider's
256
+ * configured default model; the object form overrides the model and/or maxTokens
257
+ * for this participant only — letting one combine mix cheap drafters with a strong
258
+ * synthesizer, or run the same provider twice with different models.
259
+ */
260
+ type ParticipantSpec = ProviderName | {
261
+ /** Which configured provider to run this participant on (validated by the registry). */
262
+ provider: ProviderName;
263
+ /** Model for this participant. Falls back to `request.model`, then the provider default. */
264
+ model?: string;
265
+ /** maxTokens for this participant. Falls back to `request.maxTokens`. */
266
+ maxTokens?: number;
267
+ /**
268
+ * Unique id for this participant in results/events/usage and for `synthesizer`.
269
+ * Defaults to the provider name, or `<provider>-<model>` when `model` is set.
270
+ * Required (must be set explicitly) only to disambiguate two participants that
271
+ * would otherwise resolve to the same id (e.g. the same provider+model twice).
272
+ */
273
+ label?: string;
274
+ };
275
+ /**
276
+ * The fields every combine strategy's request shares: the underlying
277
+ * {@link CompletionRequest} (messages, model, maxTokens, system, signal, …) plus
278
+ * the roster of participants. The per-strategy request types
279
+ * ({@link ConsensusRequest}, {@link PipelineRequest}, {@link EnsembleRequest},
280
+ * {@link BroadcastRequest}) extend this with only the options that strategy uses.
281
+ */
282
+ type CombineRequestBase = CompletionRequest & {
283
+ /**
284
+ * Who participates. A bare provider name uses its configured default model; the
285
+ * object form ({@link ParticipantSpec}) overrides model/maxTokens per participant.
286
+ * Resolved to a unique id each (see {@link ParticipantSpec.label}); validated by
287
+ * the registry.
288
+ */
289
+ participants: ParticipantSpec[];
290
+ };
291
+ /**
292
+ * Request for the `consensus` strategy (draft → critique → synthesize). Call
293
+ * {@link ProviderRegistry.consensus} directly, or pass `strategy: "consensus"`
294
+ * (the default) to {@link ProviderRegistry.combine}.
295
+ */
296
+ type ConsensusRequest = CombineRequestBase & {
297
+ /**
298
+ * Which participant writes the final synthesized answer, referenced by its
299
+ * **id** (see {@link ParticipantSpec.label}). Defaults to the first participant.
300
+ */
301
+ synthesizer?: string;
302
+ /**
303
+ * Whether drafts are attributed to their provider in the text shown to the
304
+ * other providers. `"anonymized"` (default) shows `Answer A`/`Answer B`/… to
305
+ * neutralize brand and self-preference bias; `"attributed"` shows participant
306
+ * ids. The returned {@link ConsensusResult} always keeps ids regardless.
307
+ */
308
+ attribution?: "attributed" | "anonymized";
309
+ /**
310
+ * Minimum number of participants that must successfully produce a draft for a
311
+ * consensus run to proceed. Defaults to 2. A single-provider combine always
312
+ * degrades to a plain completion regardless of this value.
313
+ */
314
+ minParticipants?: number;
315
+ };
316
+ /**
317
+ * Request for the `pipeline` strategy (sequential refinement). Call
318
+ * {@link ProviderRegistry.pipeline} directly, or pass `strategy: "pipeline"` to
319
+ * {@link ProviderRegistry.combine}. No strategy-specific options — participant
320
+ * order is the conveyor order.
321
+ */
322
+ type PipelineRequest = CombineRequestBase;
323
+ /**
324
+ * Request for the `ensemble` strategy (multi-model vote on structured output).
325
+ * Call {@link ProviderRegistry.ensemble} directly, or pass `strategy: "ensemble"`
326
+ * to {@link ProviderRegistry.combine}. `responseFormat` is **required** (every
327
+ * participant answers under this schema, and the field-wise vote needs an
328
+ * object-root schema).
329
+ */
330
+ type EnsembleRequest = CombineRequestBase & {
331
+ responseFormat: ResponseFormat;
332
+ };
333
+ /**
334
+ * Request for the `broadcast` strategy (fan-out, no combine). Call
335
+ * {@link ProviderRegistry.broadcast} directly, or pass `strategy: "broadcast"` to
336
+ * {@link ProviderRegistry.combine}. No strategy-specific options.
337
+ */
338
+ type BroadcastRequest = CombineRequestBase;
339
+ /**
340
+ * The broad request accepted by the strategy-dispatching
341
+ * {@link ProviderRegistry.combine}: {@link CombineRequestBase} plus every
342
+ * strategy's options and the `strategy` selector. Today only consensus adds
343
+ * options, so this is {@link ConsensusRequest} plus `strategy` (each strategy
344
+ * reads only the options it uses; `responseFormat` stays optional here, inherited
345
+ * from {@link CompletionRequest}). Prefer a per-strategy method
346
+ * ({@link ProviderRegistry.consensus} etc.) when the strategy is known at the
347
+ * call site — they take the precise {@link ConsensusRequest}/… type and return
348
+ * the concrete result without narrowing.
349
+ */
350
+ type CombineRequest = ConsensusRequest & {
351
+ /** Cooperation strategy. Defaults to `"consensus"`. */
352
+ strategy?: StrategyName;
353
+ };
354
+ /**
355
+ * The outcome of one participant in one phase — either its result or its failure.
356
+ * `id` is the participant's unique id (see {@link ParticipantSpec.label}); `provider`
357
+ * is the actual provider it ran on (these differ when a model override gives the
358
+ * participant a `<provider>-<model>` id, or two participants share one provider).
359
+ * A failure's `error` is typically a `ProviderError` (carrying `status`/`kind`/
360
+ * `code`); narrow with `instanceof ProviderError` to read those fields.
361
+ */
362
+ type ParticipantOutcome = {
363
+ id: string;
364
+ provider: ProviderName;
365
+ status: "ok";
366
+ result: CompletionResult;
367
+ } | {
368
+ id: string;
369
+ provider: ProviderName;
370
+ status: "failed";
371
+ error: Error;
372
+ };
373
+ /**
374
+ * Aggregated token usage across all the model calls a combine made — the true
375
+ * cost of a run, which is several times one completion (a default 3-way
376
+ * consensus is ~8 calls: 3 drafts + 3 critiques + synthesis + sanitize).
377
+ * `undefined` if no participating provider reported usage.
378
+ */
379
+ type CombineUsage = {
380
+ /** Total usage summed across every call the combine made. */
381
+ total: Usage;
382
+ /** Usage per participant id, summed across all of that participant's calls. */
383
+ byParticipant: Partial<Record<string, Usage>>;
384
+ };
385
+ /** The result of the `consensus` strategy (draft → critique → synthesize). */
386
+ type ConsensusResult = {
387
+ /** The final synthesized answer. */
388
+ text: string;
389
+ strategy: "consensus";
390
+ /** The id of the participant that wrote the final answer (may be a fallback if the chosen one failed). */
391
+ synthesizer: string;
392
+ /** The model the synthesizer actually used. */
393
+ model: string;
394
+ /** Phase 1 drafts, in participant order (includes any failures). */
395
+ drafts: ParticipantOutcome[];
396
+ /** Phase 2 critiques, in surviving-participant order (includes any failures). */
397
+ critiques: ParticipantOutcome[];
398
+ /** Aggregated token usage across every call, or `undefined` if none was reported. */
399
+ usage?: CombineUsage;
400
+ };
401
+ /** The result of the `pipeline` strategy (sequential refinement). */
402
+ type PipelineResult = {
403
+ /** The final answer — the output of the last stage that produced one. */
404
+ text: string;
405
+ strategy: "pipeline";
406
+ /** The id of the participant that produced the final answer (the last advancing stage). */
407
+ finalParticipant: string;
408
+ /** The model that produced the final answer. */
409
+ model: string;
410
+ /** Every stage in pipeline (participant) order, including any failures. */
411
+ stages: ParticipantOutcome[];
412
+ /** Aggregated token usage across every stage, or `undefined` if none was reported. */
413
+ usage?: CombineUsage;
414
+ };
415
+ /**
416
+ * How strongly the ensemble participants agreed on the merged object — the
417
+ * confidence signal a multi-model vote gives you that a single model can't.
418
+ */
419
+ type EnsembleAgreement = {
420
+ /** Mean of the per-field agreement scores (0–1), or 1 for an empty object. */
421
+ overall: number;
422
+ /**
423
+ * Per-field agreement: the fraction of the merged responses that voted for the
424
+ * field's merged value (0–1). The denominator is **all** the valid responses,
425
+ * not just the ones that returned the field — so a field most models omitted
426
+ * scores low. 1 means every model returned the field and agreed on its value; a
427
+ * low value flags either disagreement or sparse coverage.
428
+ */
429
+ byField: Record<string, number>;
430
+ };
431
+ /**
432
+ * The result of the `ensemble` strategy (each participant returns the same typed
433
+ * object; the objects are merged field-wise by majority vote — with no LLM
434
+ * synthesis — so every merged value is one a model actually returned).
435
+ */
436
+ type EnsembleResult = {
437
+ /** The merged object serialized as JSON (the same content as `merged`). */
438
+ text: string;
439
+ strategy: "ensemble";
440
+ /** The merged typed object. Cast to your schema's type. */
441
+ merged: Record<string, unknown>;
442
+ /** How strongly the participants agreed, overall and per field. */
443
+ agreement: EnsembleAgreement;
444
+ /** Each participant's structured response, in participant order (includes failures). */
445
+ responses: ParticipantOutcome[];
446
+ /** Aggregated token usage across every participant call, or `undefined` if none was reported. */
447
+ usage?: CombineUsage;
448
+ };
449
+ /**
450
+ * The result of the `broadcast` strategy (fan-out to every participant in
451
+ * parallel, no cooperation). There is no single combined answer — hence no
452
+ * `text` field — just every participant's raw response. Narrow on
453
+ * `result.strategy` to reach `responses`.
454
+ */
455
+ type BroadcastResult = {
456
+ strategy: "broadcast";
457
+ /** Each participant's raw completion, in participant order (includes failures). */
458
+ responses: ParticipantOutcome[];
459
+ /** Aggregated token usage across every participant call, or `undefined` if none was reported. */
460
+ usage?: CombineUsage;
461
+ };
462
+ /**
463
+ * The result of a combine, discriminated on `strategy`. Narrow on
464
+ * `result.strategy` to reach the strategy-specific artifacts. Note that
465
+ * `BroadcastResult` has no `text` (it returns every raw response, not one answer).
466
+ */
467
+ type CombineResult = ConsensusResult | PipelineResult | EnsembleResult | BroadcastResult;
468
+ /**
469
+ * Maps a strategy name to its request type — e.g. `StrategyRequest<"ensemble">`
470
+ * is {@link EnsembleRequest}. A utility for callers writing code generic over the
471
+ * strategy.
472
+ */
473
+ type StrategyRequest<S extends StrategyName> = {
474
+ consensus: ConsensusRequest;
475
+ pipeline: PipelineRequest;
476
+ ensemble: EnsembleRequest;
477
+ broadcast: BroadcastRequest;
478
+ }[S];
479
+ /**
480
+ * Maps a strategy name to its concrete result type — e.g.
481
+ * `ResultFor<"ensemble">` is {@link EnsembleResult}. A utility for callers
482
+ * writing code generic over the strategy.
483
+ */
484
+ type ResultFor<S extends StrategyName> = Extract<CombineResult, {
485
+ strategy: S;
486
+ }>;
487
+ /**
488
+ * A progress event emitted while a combine runs. For `consensus`, `phase` marks
489
+ * a phase boundary and `draft`/`critique` fire as each participant's call settles
490
+ * (in completion order, which may differ from participant order). For `pipeline`,
491
+ * a `stage` event fires as each stage settles (in conveyor order). For `ensemble`
492
+ * and `broadcast`, a `response` event fires as each participant settles (in
493
+ * completion order, which may differ from participant order). The final answer is
494
+ * the resolved {@link CombineResult}, so there is no terminal event.
495
+ */
496
+ type CombineEvent = {
497
+ type: "phase";
498
+ phase: "drafting" | "critiquing" | "synthesizing";
499
+ } | {
500
+ type: "draft";
501
+ id: string;
502
+ provider: ProviderName;
503
+ status: "ok" | "failed";
504
+ } | {
505
+ type: "critique";
506
+ id: string;
507
+ provider: ProviderName;
508
+ status: "ok" | "failed";
509
+ } | {
510
+ /** A `pipeline` stage settled. `index` is its 0-based position in the conveyor. */
511
+ type: "stage";
512
+ id: string;
513
+ provider: ProviderName;
514
+ status: "ok" | "failed";
515
+ index: number;
516
+ } | {
517
+ /** A participant settled in an `ensemble` or `broadcast` run. */
518
+ type: "response";
519
+ id: string;
520
+ provider: ProviderName;
521
+ status: "ok" | "failed";
522
+ };
523
+ type CombineOptions = {
524
+ /**
525
+ * Called with progress events as the combine runs. Errors thrown from the
526
+ * handler are swallowed so a progress listener can never break the run.
527
+ */
528
+ onEvent?: (event: CombineEvent) => void;
529
+ };
530
+
531
+ /**
532
+ * HTTP transport for the providers: a `fetch` wrapper that turns network
533
+ * failures into typed errors, plus bounded retry/backoff on routine retryable
534
+ * statuses. The error *vocabulary* (`ProviderError`, `apiError`) lives in
535
+ * `./errors`; this module is about how a request is made.
536
+ */
537
+
538
+ /** Tuning for {@link requestWithRetry}'s bounded exponential backoff. */
539
+ type RetryOptions = {
540
+ /**
541
+ * How many times to retry after the initial attempt on a retryable status
542
+ * (429/503/529). `0` disables retry. Defaults to {@link DEFAULT_MAX_RETRIES}.
543
+ */
544
+ maxRetries?: number;
545
+ /**
546
+ * Base backoff in ms; the nth retry waits `baseDelayMs * 2 ** n` (capped at
547
+ * {@link MAX_BACKOFF_MS}), unless the response carries a `Retry-After` header.
548
+ * Defaults to {@link DEFAULT_BASE_DELAY_MS}.
549
+ */
550
+ baseDelayMs?: number;
551
+ };
552
+
553
+ /**
554
+ * Anthropic (Claude) provider, talking to the Messages API directly over
555
+ * `fetch` — no SDK dependency.
556
+ */
557
+
558
+ type AnthropicProviderOptions = {
559
+ apiKey: string;
560
+ /** Defaults to {@link DEFAULT_MODEL}. */
561
+ model?: string;
562
+ /** Defaults to `https://api.anthropic.com`. */
563
+ baseUrl?: string;
564
+ /** Bounded retry/backoff on 429/503/529. Defaults applied when omitted. */
565
+ retry?: RetryOptions;
566
+ };
567
+
568
+ /**
569
+ * Google Gemini provider, talking to the Generative Language API directly over
570
+ * `fetch` — no SDK dependency.
571
+ */
572
+
573
+ type GoogleProviderOptions = {
574
+ apiKey: string;
575
+ /** Defaults to {@link DEFAULT_MODEL}. */
576
+ model?: string;
577
+ /** Defaults to `https://generativelanguage.googleapis.com`. */
578
+ baseUrl?: string;
579
+ /** Bounded retry/backoff on 429/503/529. Defaults applied when omitted. */
580
+ retry?: RetryOptions;
581
+ };
582
+
583
+ /**
584
+ * OpenAI provider, talking to the Chat Completions API directly over `fetch` —
585
+ * no SDK dependency.
586
+ */
587
+
588
+ type OpenAIProviderOptions = {
589
+ apiKey: string;
590
+ /** Defaults to {@link DEFAULT_MODEL}. */
591
+ model?: string;
592
+ /** Defaults to `https://api.openai.com`. */
593
+ baseUrl?: string;
594
+ /**
595
+ * Extra headers merged into (and able to override) every request's headers —
596
+ * for an OpenAI-compatible gateway's auth/routing (e.g. OpenRouter's
597
+ * `HTTP-Referer`/`X-Title`) or a proxy. Use lowercase header names.
598
+ */
599
+ headers?: Record<string, string>;
600
+ /** Bounded retry/backoff on 429/503/529. Defaults applied when omitted. */
601
+ retry?: RetryOptions;
602
+ };
603
+
604
+ /**
605
+ * Provider registry — the package's single point of access to its providers.
606
+ *
607
+ * You configure the registry with the providers you want (and their API keys);
608
+ * the library constructs the built-in providers by name and hands one back via
609
+ * {@link ProviderRegistry.select}. Consumers never import the provider classes
610
+ * directly. It never reads env vars — keys are always passed in the config.
611
+ */
612
+
613
+ /**
614
+ * An OpenAI Chat Completions–compatible endpoint registered under a custom name.
615
+ * The library reuses its OpenAI provider against your `baseUrl`, so any service
616
+ * that speaks the Chat Completions wire format works — OpenRouter, Together,
617
+ * Groq, Ollama, a local server, etc.
618
+ */
619
+ type OpenAICompatibleConfig = {
620
+ kind: "openai-compatible";
621
+ apiKey: string;
622
+ /**
623
+ * Base URL of the endpoint, **excluding** the `/v1/chat/completions` path the
624
+ * provider appends (e.g. `https://api.groq.com/openai`, `http://localhost:11434`).
625
+ */
626
+ baseUrl: string;
627
+ /**
628
+ * The model id to send. Required — unlike the built-ins there is no sensible
629
+ * default for a third-party endpoint. `request.model` (or combine's `model`)
630
+ * still overrides it per call.
631
+ */
632
+ model: string;
633
+ /** Extra headers merged into every request (e.g. OpenRouter's `HTTP-Referer`/`X-Title`). */
634
+ headers?: Record<string, string>;
635
+ /** Bounded retry/backoff on 429/503/529. Defaults applied when omitted. */
636
+ retry?: RetryOptions;
637
+ };
638
+ /**
639
+ * A provider you implement yourself (anything satisfying {@link Provider}),
640
+ * registered under a custom name. The escape hatch for an API the library
641
+ * doesn't speak natively, or for wrapping a built-in with instrumentation.
642
+ */
643
+ type CustomProviderInstance = {
644
+ kind: "provider";
645
+ provider: Provider;
646
+ };
647
+ /** How a custom (non-built-in) provider is registered. */
648
+ type CustomProviderConfig = OpenAICompatibleConfig | CustomProviderInstance;
649
+ /** The names the library constructs as built-in providers (the single source of truth). */
650
+ declare const BUILT_IN_NAMES: readonly ["anthropic", "openai", "google"];
651
+ /** The provider names the library constructs from its own config. */
652
+ type BuiltInProviderName = (typeof BUILT_IN_NAMES)[number];
653
+ /** Per-provider configuration. Include a provider's key to register it. */
654
+ type ProviderRegistryConfig = {
655
+ anthropic?: AnthropicProviderOptions;
656
+ openai?: OpenAIProviderOptions;
657
+ google?: GoogleProviderOptions;
658
+ /**
659
+ * Extra providers registered under names you choose — an OpenAI-compatible
660
+ * gateway/local endpoint or a {@link Provider} you bring yourself. Each name
661
+ * must not collide with a built-in.
662
+ */
663
+ custom?: Record<string, CustomProviderConfig>;
664
+ };
665
+ /**
666
+ * A configured provider's name: the three built-ins, or any custom name you
667
+ * registered. The `string & Record<never, never>` intersection keeps editor
668
+ * autocomplete for the built-in literals while still accepting an arbitrary
669
+ * custom string.
670
+ */
671
+ type ProviderName = BuiltInProviderName | (string & Record<never, never>);
672
+ declare class ProviderRegistry {
673
+ #private;
674
+ /**
675
+ * Construct the providers present in `config`. A provider is registered only
676
+ * if its entry is supplied; the rest are left out.
677
+ */
678
+ constructor(config: ProviderRegistryConfig);
679
+ /**
680
+ * Return the provider registered under `name`. Throws a clear error listing
681
+ * the configured names if that provider was not configured.
682
+ */
683
+ select(name: ProviderName): Provider;
684
+ /**
685
+ * Combine several configured providers to cooperate on one prompt, dispatching
686
+ * on `request.strategy` (defaults to `"consensus"`).
687
+ *
688
+ * Generic over the strategy: `S` is inferred from the `strategy` field, so a
689
+ * **literal** `strategy` at the call site makes the return that strategy's
690
+ * concrete result (e.g. `strategy: "ensemble"` → `EnsembleResult`) — the caller
691
+ * does **not** narrow a union. When `strategy` is only known at runtime, `S`
692
+ * widens to {@link StrategyName} and the return is the full
693
+ * {@link CombineResult} union to narrow.
694
+ *
695
+ * The request stays the broad {@link CombineRequest} here; for compile-time
696
+ * enforcement of a strategy's specific options (e.g. `responseFormat` required
697
+ * for ensemble) call the per-strategy method ({@link ProviderRegistry.consensus},
698
+ * {@link ProviderRegistry.pipeline}, {@link ProviderRegistry.ensemble},
699
+ * {@link ProviderRegistry.broadcast}), which takes that strategy's request type.
700
+ */
701
+ combine<S extends StrategyName = "consensus">(request: Omit<CombineRequest, "strategy"> & {
702
+ strategy?: S;
703
+ }, options?: CombineOptions): Promise<ResultFor<S>>;
704
+ /**
705
+ * Run the `consensus` strategy (draft → critique → synthesize) over the
706
+ * configured participants. Strategy-specific: `synthesizer` (defaults to the
707
+ * first participant), `attribution`, `minParticipants` (default 2).
708
+ */
709
+ consensus(request: ConsensusRequest, options?: CombineOptions): Promise<ConsensusResult>;
710
+ /**
711
+ * Run the `pipeline` strategy (sequential refinement) — participants refine a
712
+ * running answer in roster order; the last advancing stage wins.
713
+ */
714
+ pipeline(request: PipelineRequest, options?: CombineOptions): Promise<PipelineResult>;
715
+ /**
716
+ * Run the `ensemble` strategy (multi-model vote on structured output) — every
717
+ * participant answers under `request.responseFormat`; the typed objects are
718
+ * merged field-wise by majority vote with a per-field agreement score.
719
+ */
720
+ ensemble(request: EnsembleRequest, options?: CombineOptions): Promise<EnsembleResult>;
721
+ /**
722
+ * Run the `broadcast` strategy (fan-out, no combine) — every participant
723
+ * answers the raw prompt in parallel; all raw responses are returned.
724
+ */
725
+ broadcast(request: BroadcastRequest, options?: CombineOptions): Promise<BroadcastResult>;
726
+ /** Whether a provider is configured under `name`. */
727
+ has(name: string): boolean;
728
+ /** The names of all configured providers. */
729
+ names(): ProviderName[];
730
+ }
731
+
732
+ /**
733
+ * Typed provider errors. Every provider throws a {@link ProviderError} so
734
+ * consumers can branch on `err.status` / `err.kind` / `err.code` instead of
735
+ * regex-matching a message string.
736
+ */
737
+
738
+ /**
739
+ * Whether the failure happened after the provider responded (`"api"` — an
740
+ * HTTP error status, so `status` is set) or before any response arrived
741
+ * (`"transport"` — a network/DNS failure or an aborted request, so `status` is
742
+ * undefined). Branch on this to tell "the provider said no" from "we never
743
+ * reached the provider".
744
+ */
745
+ type ProviderErrorKind = "api" | "transport";
746
+ type ProviderErrorInit = {
747
+ provider: ProviderName;
748
+ kind: ProviderErrorKind;
749
+ status?: number;
750
+ code?: string;
751
+ type?: string;
752
+ body?: string;
753
+ cause?: unknown;
754
+ };
755
+ /** An error from a provider call, carrying enough structure to handle it programmatically. */
756
+ declare class ProviderError extends Error {
757
+ readonly name = "ProviderError";
758
+ /** Which provider produced the failure. */
759
+ readonly provider: ProviderName;
760
+ /** Transport failure (no response) vs API failure (error response). */
761
+ readonly kind: ProviderErrorKind;
762
+ /** HTTP status for `kind: "api"`; `undefined` for transport failures. */
763
+ readonly status?: number;
764
+ /** Machine-readable code parsed from the error body, where the provider sends one. */
765
+ readonly code?: string;
766
+ /** Error category parsed from the body (Anthropic/OpenAI `type`, Gemini `status`). */
767
+ readonly type?: string;
768
+ /** The raw response body, for `kind: "api"`. */
769
+ readonly body?: string;
770
+ constructor(message: string, init: ProviderErrorInit);
771
+ }
772
+
773
+ export { type AnthropicProviderOptions, type BroadcastRequest, type BroadcastResult, type BuiltInProviderName, type CombineEvent, type CombineOptions, type CombineRequest, type CombineRequestBase, type CombineResult, type CombineUsage, type CompletionRequest, type CompletionResult, type ConsensusRequest, type ConsensusResult, type ContentPart, type CustomProviderConfig, type CustomProviderInstance, type EnsembleAgreement, type EnsembleRequest, type EnsembleResult, type FilePart, type FinishReason, type GoogleProviderOptions, type ImagePart, type MediaSource, type Message, type OpenAICompatibleConfig, type OpenAIProviderOptions, type ParticipantOutcome, type ParticipantSpec, type PipelineRequest, type PipelineResult, type Provider, ProviderError, type ProviderErrorKind, type ProviderName, ProviderRegistry, type ProviderRegistryConfig, type ResponseFormat, type ResultFor, type RetryOptions, type Role, type StrategyName, type StrategyRequest, type TextPart, type ToolCall, type ToolChoice, type ToolDefinition, type ToolResultPart, type ToolUsePart, type Usage };