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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-06-17
|
|
11
|
+
|
|
12
|
+
Initial release: a plain TypeScript library that unifies the Anthropic, OpenAI,
|
|
13
|
+
and Google (Gemini) APIs behind one provider-agnostic contract, talking to each
|
|
14
|
+
HTTP API directly over `fetch` (no SDK dependencies), and adds strategies for
|
|
15
|
+
combining several providers on one prompt.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Provider-agnostic contract** (`src/types.ts`): `Provider`, `Message`, `Role`,
|
|
20
|
+
`CompletionRequest`, `CompletionResult`. Every provider implements `complete()`
|
|
21
|
+
(full text) and `stream()` (text deltas via SSE).
|
|
22
|
+
- **`ProviderRegistry`** — the package's single point of access, configured with
|
|
23
|
+
`{ anthropic?, openai?, google?, custom? }`. `select(name)` returns a provider
|
|
24
|
+
or throws (listing configured names); plus `has(name)` and `names()`. Concrete
|
|
25
|
+
provider classes are intentionally not exported. The library never reads
|
|
26
|
+
environment variables — keys come from config.
|
|
27
|
+
- **Anthropic, OpenAI, and Google (Gemini) providers.** Anthropic Messages API
|
|
28
|
+
(default `claude-opus-4-8`); OpenAI Chat Completions (default `gpt-4.1`, folds
|
|
29
|
+
`system` into a leading message, cap sent as `max_completion_tokens`); Gemini
|
|
30
|
+
Generative Language API (default `gemini-2.5-pro`, `assistant`→`model`,
|
|
31
|
+
`system`→`systemInstruction`, cap→`maxOutputTokens`). The `google` key is the
|
|
32
|
+
company name (consistent with `anthropic`/`openai`); the API it speaks is Gemini.
|
|
33
|
+
Note: Gemini 2.5 thinking tokens count against `maxTokens`, so a very small cap
|
|
34
|
+
can leave the visible answer empty/truncated.
|
|
35
|
+
- **Custom & gateway providers** via a `custom` map on the registry config. Two
|
|
36
|
+
forms — `{ kind: "openai-compatible", apiKey, baseUrl, model, headers?, retry? }`
|
|
37
|
+
points the OpenAI provider at any Chat Completions–compatible endpoint
|
|
38
|
+
(OpenRouter, Together, Groq, Ollama, a local server, …), and
|
|
39
|
+
`{ kind: "provider", provider }` brings your own `Provider`. Custom providers
|
|
40
|
+
work everywhere a built-in does; a name colliding with a built-in throws. A
|
|
41
|
+
gateway's errors and `provider.name` carry its alias. `ProviderName` accepts any
|
|
42
|
+
custom string while keeping autocomplete for the built-ins. Exported:
|
|
43
|
+
`CustomProviderConfig`, `CustomProviderInstance`, `OpenAICompatibleConfig`,
|
|
44
|
+
`BuiltInProviderName`. `OpenAIProviderOptions.headers?` adds extra headers
|
|
45
|
+
merged into (and able to override) every request.
|
|
46
|
+
- **Combine: cooperate across providers on one prompt** via
|
|
47
|
+
`ProviderRegistry.combine(request)` or the per-strategy methods `consensus()`,
|
|
48
|
+
`pipeline()`, `ensemble()`, and `broadcast()`. Per-strategy methods take that
|
|
49
|
+
strategy's request type (`ConsensusRequest`, `PipelineRequest`, `EnsembleRequest`,
|
|
50
|
+
`BroadcastRequest`) and return its concrete result; `combine()` is generic over
|
|
51
|
+
`strategy`, returning the concrete result for a literal strategy and the
|
|
52
|
+
`CombineResult` union (discriminated on `strategy`) for a dynamic one. Four
|
|
53
|
+
strategies:
|
|
54
|
+
- **consensus** — every participant drafts in parallel, critiques all drafts
|
|
55
|
+
(anonymized by default; `attribution: "attributed"` opts out), then a
|
|
56
|
+
synthesizer adjudicates on correctness over popularity; a final sanitizing
|
|
57
|
+
pass strips process narration. Tolerates partial failure down to
|
|
58
|
+
`minParticipants` (default 2); a single participant degrades to a plain
|
|
59
|
+
completion.
|
|
60
|
+
- **pipeline** — participants refine one answer in sequence (participant order =
|
|
61
|
+
conveyor order); a failed/empty stage carries the previous answer forward.
|
|
62
|
+
- **ensemble** — every participant answers under the same `responseFormat`, then
|
|
63
|
+
the objects are merged **mechanically** by field-wise majority vote (no LLM
|
|
64
|
+
synthesis) with per-field and overall `agreement` scores. `responseFormat` is
|
|
65
|
+
required (object root); rejected for consensus/pipeline/broadcast.
|
|
66
|
+
- **broadcast** — fan the prompt out to every participant and return all raw
|
|
67
|
+
answers, with no critique/synthesis/vote; `BroadcastResult` has no `text`.
|
|
68
|
+
- A participant is a provider name or `{ provider, model?, maxTokens?, label? }`,
|
|
69
|
+
so one combine can mix models or run the same provider twice; each gets a
|
|
70
|
+
unique `id` (results/events/usage are keyed by it). An optional `CombineOptions`
|
|
71
|
+
`onEvent` callback reports phase/per-participant progress (status only).
|
|
72
|
+
When every participant fails, the thrown error is an `AggregateError` carrying
|
|
73
|
+
the participants' own errors. Tools/`toolChoice` are not supported in combine.
|
|
74
|
+
Exported: `CombineRequest`, `CombineResult`, `ConsensusResult`,
|
|
75
|
+
`PipelineResult`, `EnsembleResult`, `EnsembleAgreement`, `BroadcastResult`,
|
|
76
|
+
`ParticipantSpec`, `ParticipantOutcome`, `StrategyName`, `CombineEvent`,
|
|
77
|
+
`StrategyRequest<S>`, `ResultFor<S>`, `CombineRequestBase`.
|
|
78
|
+
- **Structured message content**: `Message.content` is `string | ContentPart[]`
|
|
79
|
+
(a bare string is shorthand for one text part).
|
|
80
|
+
- **Multimodal input**: `ContentPart` is `TextPart | ImagePart | FilePart` (plus
|
|
81
|
+
`MediaSource`). Pass images and documents/PDFs alongside text, with `source` as
|
|
82
|
+
base64 bytes or a URL, mapped to each provider's wire shape. Provider support
|
|
83
|
+
varies — OpenAI's Chat Completions has no URL file source and throws a clear
|
|
84
|
+
error.
|
|
85
|
+
- **Structured output**: `CompletionRequest.responseFormat?` (`{ type: "json_schema",
|
|
86
|
+
schema, name? }`) constrains output to a raw JSON Schema (no Zod), mapped to each
|
|
87
|
+
provider's native mechanism. `complete()` also surfaces the parsed value on
|
|
88
|
+
`CompletionResult.parsed`. For one schema to work across all three, set
|
|
89
|
+
`additionalProperties: false` and list every property in `required`.
|
|
90
|
+
- **Tool / function calling** (single-provider, `complete()` only). Pass `tools`
|
|
91
|
+
and an optional `toolChoice` (`"auto" | "any" | "none" | { name }`); a tool call
|
|
92
|
+
is returned on `toolCalls` with `finishReason: "tool_use"`. Replay results with
|
|
93
|
+
`ToolUsePart`/`ToolResultPart` content parts, mapped to each provider's wire
|
|
94
|
+
shape. Exported: `ToolDefinition`, `ToolChoice`, `ToolCall`, `ToolUsePart`,
|
|
95
|
+
`ToolResultPart`.
|
|
96
|
+
- **Normalized finish reasons and refusals**: `finishReason` (a `FinishReason`
|
|
97
|
+
union) and `rawFinishReason` on `CompletionResult`, so a truncated/refused
|
|
98
|
+
answer is distinguishable from a genuinely empty one. A provider `refusal` is
|
|
99
|
+
surfaced and forces `finishReason: "content_filter"`.
|
|
100
|
+
- **Token usage accounting**: `CompletionResult.usage` (`Usage` of
|
|
101
|
+
`inputTokens`/`outputTokens`/`totalTokens`), or `undefined` when none is
|
|
102
|
+
reported. `CombineResult.usage` (`CombineUsage` with `total` plus a
|
|
103
|
+
per-participant `byParticipant` breakdown) aggregates every model call a combine
|
|
104
|
+
makes, so its true cost is visible.
|
|
105
|
+
- **Cancellation**: `CompletionRequest.signal?` (`AbortSignal`) is forwarded to
|
|
106
|
+
every provider `fetch` and threaded through `combine()`, so one signal cancels
|
|
107
|
+
every participant call. An aborted call rejects with a transport `ProviderError`.
|
|
108
|
+
- **Typed errors**: `ProviderError` (exported) carries `provider`, a `kind`
|
|
109
|
+
discriminant (`"api"` vs `"transport"`), `status` (for `"api"`), and a
|
|
110
|
+
`code`/`type` from the error body. `fetch` rejections are wrapped as
|
|
111
|
+
`"transport"`; an HTTP 200 with an `{ error }` body throws instead of returning a
|
|
112
|
+
silent empty result.
|
|
113
|
+
- **Automatic retry** with bounded exponential backoff on 429/503/529, for both
|
|
114
|
+
`complete()` and `stream()`. Honors `Retry-After`; the backoff respects the
|
|
115
|
+
request's `AbortSignal`. Configurable per provider via `retry`
|
|
116
|
+
(`{ maxRetries?, baseDelayMs? }`, exported as `RetryOptions`; default 2 retries
|
|
117
|
+
from 500ms, `0` disables). Transport failures are not retried.
|
|
118
|
+
- **Robust streaming**: SSE parsing tolerates blank/malformed `data:` frames, and
|
|
119
|
+
`stream()` releases the response body reader on every exit path (normal end,
|
|
120
|
+
error, or consumer `break`) so a long-running server can't leak a socket.
|
|
121
|
+
- Opt-in live integration tests (`*.integration.test.ts`), double-gated on
|
|
122
|
+
`RUN_LIVE_TESTS=1` plus the provider key; combine suites are triple-gated. Run
|
|
123
|
+
with `yarn test:integration` (optional filename pattern narrows to one suite).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anders Jansson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|