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.
- package/CHANGELOG.md +123 -0
- package/LICENSE +21 -0
- package/README.md +781 -0
- package/dist/index.cjs +1796 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +773 -0
- package/dist/index.d.ts +773 -0
- package/dist/index.js +1793 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|