@alma-harness/providers 0.1.0 → 0.3.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/README.md +60 -1
- package/dist/index.d.ts +86 -12
- package/dist/index.js +601 -80
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
Anthropic and OpenAI first-party, plus the OpenRouter gateway bridge, over one
|
|
5
5
|
neutral message format.
|
|
6
6
|
|
|
7
|
-
> **Status: pre-
|
|
7
|
+
> **Status: 0.2.0 on npm, pre-1.0.** The API is still moving; see the
|
|
8
|
+
> [roadmap](../../docs/architecture.md#12-adoption-roadmap) for where it stands.
|
|
8
9
|
|
|
9
10
|
## What it owns
|
|
10
11
|
|
|
@@ -44,6 +45,64 @@ const client = new AnthropicModelClient(); // reads ANTHROPIC_API_KEY
|
|
|
44
45
|
const explicit = new AnthropicModelClient({ apiKey }); // or inject it
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
## Jobs
|
|
49
|
+
|
|
50
|
+
`AnthropicJobClient` (Message Batches) and `OpenAIJobClient` (the Batch API,
|
|
51
|
+
over a JSONL file of `/v1/responses` requests) implement the `ModelJobClient`
|
|
52
|
+
seam (spec: model-jobs): the same request translations, minus streaming, and
|
|
53
|
+
a non-streaming translation of the complete answer. OpenRouter has no batch
|
|
54
|
+
API.
|
|
55
|
+
|
|
56
|
+
## Reasoning
|
|
57
|
+
|
|
58
|
+
When the policy sets `ModelChoice.reasoning` (spec: reasoning-blocks), each
|
|
59
|
+
adapter asks for it and hands it back as one complete block per step:
|
|
60
|
+
|
|
61
|
+
| effort | Anthropic | OpenAI | OpenRouter |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| absent | nothing sent; default output dropped | nothing sent; default items dropped | nothing sent |
|
|
64
|
+
| `none` | `thinking: disabled` | `reasoning.effort: none` | `reasoning.enabled: false` |
|
|
65
|
+
| others | `thinking: adaptive` + `output_config.effort` | `reasoning.effort` + encrypted content | `reasoning.effort` |
|
|
66
|
+
|
|
67
|
+
An adapter replays only its own blocks, and only when reasoning is on for the
|
|
68
|
+
request; every other reasoning block in the history is skipped.
|
|
69
|
+
|
|
70
|
+
## Provider-executed web search
|
|
71
|
+
|
|
72
|
+
`ModelRequest.providerTools` (spec: provider-tools) declares the provider's
|
|
73
|
+
own search beside the registered tools; what comes back is two neutral
|
|
74
|
+
blocks, `provider_tool_call` and `provider_tool_result`, the provider's
|
|
75
|
+
payload kept `opaque` for replay.
|
|
76
|
+
|
|
77
|
+
| | Anthropic | OpenAI | OpenRouter |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| declaration | `web_search_20250305` (the direct search; the agentic 2026-03-18 version drives `code_execution` and is another kind) with `max_uses`, `allowed_domains`, `blocked_domains` | `{ type: "web_search", filters: { allowed_domains } }` + `include: ["web_search_call.action.sources"]`; `blockedDomains` is REFUSED before the network; `maxUses` is the loop's to enforce | refused before the network |
|
|
80
|
+
| on the stream | `server_tool_use` → call; `web_search_tool_result` → result (url, title, page age; an error code as `error`); `usage.server_tool_use.web_search_requests` | a completed `web_search_call` item → call (`input: action`) and result (the sources; `failed` as an error), counted | — |
|
|
81
|
+
| replay | its own two blocks as `server_tool_use` + `web_search_tool_result` with the encrypted content, only while the request declares the kind (a capped step replays the text and skips the search); another provider's skipped | its own result's `opaque` as the item, only while the request declares the kind; the call block skipped | skipped |
|
|
82
|
+
| `pause_turn` | → the neutral `pause`: the loop re-sends | — | — |
|
|
83
|
+
|
|
84
|
+
## Arguments the wire cut
|
|
85
|
+
|
|
86
|
+
A tool call whose arguments do not parse — a response cut by `max_tokens`
|
|
87
|
+
mid-arguments, or a model that emitted broken JSON — is still a `tool_call`
|
|
88
|
+
(spec: what-the-wire-cuts): `input: {}` and the raw text as `malformed`,
|
|
89
|
+
followed by the wire's own stop reason. The loop answers a malformed call
|
|
90
|
+
with an invalid-input result on `tool_use`, and closes it on `max_tokens`.
|
|
91
|
+
The translators never throw on it.
|
|
92
|
+
|
|
93
|
+
## Errors
|
|
94
|
+
|
|
95
|
+
What an SDK throws leaves every client as a `ProviderError` from
|
|
96
|
+
`@alma-harness/core` (spec: error-taxonomy), classified through one
|
|
97
|
+
duck-typed table over the fields the SDKs share: 429 → `rate_limited`
|
|
98
|
+
(OpenAI's `insufficient_quota` → `rejected`); 529, 503 or an overloaded body
|
|
99
|
+
→ `overloaded`; no status or 5xx → `unavailable`; a 400 that says the prompt
|
|
100
|
+
does not fit → `context_window`; the other 4xx → `rejected`. A user abort
|
|
101
|
+
passes through untouched. The translation error classes are
|
|
102
|
+
`ProviderError`s too — `rejected` before the network, `provider_drift`
|
|
103
|
+
mid-stream — and `toProviderError` / `classifyFailure` are exported for a
|
|
104
|
+
product wrapping its own client.
|
|
105
|
+
|
|
47
106
|
## What it must never do
|
|
48
107
|
|
|
49
108
|
- Decide routing, spend, or capability. Those are core, not adapter.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ModelClient, ModelRequest, ModelEvent } from '@alma-harness/core';
|
|
2
|
-
export { ModelClient, ModelEvent, ModelRef, ModelRequest, ProviderId } from '@alma-harness/core';
|
|
1
|
+
import { ModelClient, ModelRequest, ModelEvent, ProviderError, ProviderFailureKind, ModelJobClient, JobItem, JobHandle, JobProgress, JobResult, JobOutput, ProviderId } from '@alma-harness/core';
|
|
2
|
+
export { ModelClient, ModelEvent, ModelJobClient, ModelRef, ModelRequest, ProviderId } from '@alma-harness/core';
|
|
3
3
|
import Anthropic from '@anthropic-ai/sdk';
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
5
|
|
|
@@ -11,7 +11,8 @@ interface AnthropicModelClientOptions {
|
|
|
11
11
|
/**
|
|
12
12
|
* `ModelClient` adapter for the Anthropic Messages API — §6.2, spec 002.
|
|
13
13
|
* A thin shell: request/stream translation lives in ./translate (pure);
|
|
14
|
-
* this class only owns the SDK client. SDK
|
|
14
|
+
* this class only owns the SDK client. What the SDK throws leaves as a
|
|
15
|
+
* `ProviderError` (spec: error-taxonomy); a user abort passes through.
|
|
15
16
|
*/
|
|
16
17
|
declare class AnthropicModelClient implements ModelClient {
|
|
17
18
|
#private;
|
|
@@ -26,9 +27,14 @@ declare class AnthropicModelClient implements ModelClient {
|
|
|
26
27
|
* Messages API — spec 002. Everything here is side-effect-free so the wire
|
|
27
28
|
* mapping is testable without a network.
|
|
28
29
|
*/
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Raised when a request or stream cannot be represented faithfully. A
|
|
32
|
+
* `ProviderError` since spec: error-taxonomy — `rejected` before the network
|
|
33
|
+
* (the request as built cannot be sent), `provider_drift` mid-stream (the
|
|
34
|
+
* wire sent something this adapter does not know).
|
|
35
|
+
*/
|
|
36
|
+
declare class AnthropicTranslationError extends ProviderError {
|
|
37
|
+
constructor(message: string, kind?: ProviderFailureKind);
|
|
32
38
|
}
|
|
33
39
|
declare function toAnthropicParams(req: ModelRequest): Anthropic.MessageCreateParamsStreaming;
|
|
34
40
|
/**
|
|
@@ -40,6 +46,27 @@ declare function toAnthropicParams(req: ModelRequest): Anthropic.MessageCreatePa
|
|
|
40
46
|
*/
|
|
41
47
|
declare function translateStream(events: AsyncIterable<Anthropic.RawMessageStreamEvent>): AsyncGenerator<ModelEvent>;
|
|
42
48
|
|
|
49
|
+
/**
|
|
50
|
+
* `ModelJobClient` over the Anthropic Message Batches API — spec: model-jobs.
|
|
51
|
+
* Each item's request is translated with the same function the stream uses,
|
|
52
|
+
* minus `stream`; a succeeded result is a complete `Message`, translated
|
|
53
|
+
* here into the neutral output the runner prices and returns.
|
|
54
|
+
*/
|
|
55
|
+
/** A complete message → neutral blocks, usage and stop — the non-streaming half of spec 002. */
|
|
56
|
+
declare function translateMessage(message: Anthropic.Message): JobOutput;
|
|
57
|
+
interface AnthropicJobClientOptions {
|
|
58
|
+
apiKey?: string;
|
|
59
|
+
baseURL?: string;
|
|
60
|
+
}
|
|
61
|
+
declare class AnthropicJobClient implements ModelJobClient {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(opts?: AnthropicJobClientOptions);
|
|
64
|
+
submit(items: readonly JobItem[]): Promise<JobHandle>;
|
|
65
|
+
status(handle: JobHandle): Promise<JobProgress>;
|
|
66
|
+
results(handle: JobHandle): AsyncIterable<JobResult>;
|
|
67
|
+
cancel(handle: JobHandle): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
interface OpenAIModelClientOptions {
|
|
44
71
|
/** Omit to use the SDK's environment resolution (OPENAI_API_KEY). */
|
|
45
72
|
apiKey?: string;
|
|
@@ -48,7 +75,8 @@ interface OpenAIModelClientOptions {
|
|
|
48
75
|
/**
|
|
49
76
|
* `ModelClient` adapter for the OpenAI Responses API — §6.2, spec 003.
|
|
50
77
|
* A thin shell: request/stream translation lives in ./translate (pure);
|
|
51
|
-
* this class only owns the SDK client. SDK
|
|
78
|
+
* this class only owns the SDK client. What the SDK throws leaves as a
|
|
79
|
+
* `ProviderError` (spec: error-taxonomy); a user abort passes through.
|
|
52
80
|
*/
|
|
53
81
|
declare class OpenAIModelClient implements ModelClient {
|
|
54
82
|
#private;
|
|
@@ -62,8 +90,9 @@ declare class OpenAIModelClient implements ModelClient {
|
|
|
62
90
|
* Pure translation between Alma's neutral vocabulary (§6.2) and the OpenAI
|
|
63
91
|
* Responses API — spec 003. Side-effect-free; testable without a network.
|
|
64
92
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
/** A `ProviderError` since spec: error-taxonomy — see `AnthropicTranslationError`. */
|
|
94
|
+
declare class OpenAITranslationError extends ProviderError {
|
|
95
|
+
constructor(message: string, kind?: ProviderFailureKind);
|
|
67
96
|
}
|
|
68
97
|
declare function toOpenAIParams(req: ModelRequest): OpenAI.Responses.ResponseCreateParamsStreaming;
|
|
69
98
|
/**
|
|
@@ -75,6 +104,35 @@ declare function toOpenAIParams(req: ModelRequest): OpenAI.Responses.ResponseCre
|
|
|
75
104
|
*/
|
|
76
105
|
declare function translateOpenAIStream(events: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>): AsyncGenerator<ModelEvent>;
|
|
77
106
|
|
|
107
|
+
/**
|
|
108
|
+
* `ModelJobClient` over the OpenAI Batch API — spec: model-jobs. Items become
|
|
109
|
+
* one JSONL file of `/v1/responses` requests, uploaded with purpose `batch`;
|
|
110
|
+
* results come back as a JSONL file of complete `Response` objects, each
|
|
111
|
+
* translated here into the neutral output the runner prices and returns.
|
|
112
|
+
*/
|
|
113
|
+
/** One line of the batch input file. */
|
|
114
|
+
interface BatchLine {
|
|
115
|
+
custom_id: string;
|
|
116
|
+
method: "POST";
|
|
117
|
+
url: "/v1/responses";
|
|
118
|
+
body: OpenAI.Responses.ResponseCreateParamsNonStreaming;
|
|
119
|
+
}
|
|
120
|
+
declare function toBatchLines(items: readonly JobItem[]): BatchLine[];
|
|
121
|
+
/** A complete response → neutral blocks, usage and stop — the non-streaming half of spec 003. */
|
|
122
|
+
declare function translateResponse(response: OpenAI.Responses.Response): JobOutput;
|
|
123
|
+
interface OpenAIJobClientOptions {
|
|
124
|
+
apiKey?: string;
|
|
125
|
+
baseURL?: string;
|
|
126
|
+
}
|
|
127
|
+
declare class OpenAIJobClient implements ModelJobClient {
|
|
128
|
+
#private;
|
|
129
|
+
constructor(opts?: OpenAIJobClientOptions);
|
|
130
|
+
submit(items: readonly JobItem[]): Promise<JobHandle>;
|
|
131
|
+
status(handle: JobHandle): Promise<JobProgress>;
|
|
132
|
+
results(handle: JobHandle): AsyncIterable<JobResult>;
|
|
133
|
+
cancel(handle: JobHandle): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
|
|
78
136
|
/**
|
|
79
137
|
* Pure translation between Alma's neutral vocabulary (§6.2) and OpenRouter's
|
|
80
138
|
* chat-completions wire — spec 014. A SECOND translator over the neutral
|
|
@@ -82,8 +140,9 @@ declare function translateOpenAIStream(events: AsyncIterable<OpenAI.Responses.Re
|
|
|
82
140
|
* Responses API, and nothing in its translation is reusable here.
|
|
83
141
|
* Side-effect-free; testable without a network.
|
|
84
142
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
143
|
+
/** A `ProviderError` since spec: error-taxonomy — see `AnthropicTranslationError`. */
|
|
144
|
+
declare class OpenRouterTranslationError extends ProviderError {
|
|
145
|
+
constructor(message: string, kind?: ProviderFailureKind);
|
|
87
146
|
}
|
|
88
147
|
/**
|
|
89
148
|
* The upstream routing policy — spec 014's core constraint: for a gateway,
|
|
@@ -118,8 +177,14 @@ interface OpenRouterProviderBlock {
|
|
|
118
177
|
zdr?: boolean;
|
|
119
178
|
require_parameters?: boolean;
|
|
120
179
|
}
|
|
180
|
+
/** OpenRouter's reasoning block — a gateway extension the OpenAI SDK does not type. */
|
|
181
|
+
interface OpenRouterReasoningBlock {
|
|
182
|
+
effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
183
|
+
enabled?: boolean;
|
|
184
|
+
}
|
|
121
185
|
type OpenRouterParams = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming & {
|
|
122
186
|
provider: OpenRouterProviderBlock;
|
|
187
|
+
reasoning?: OpenRouterReasoningBlock;
|
|
123
188
|
};
|
|
124
189
|
declare function toOpenRouterParams(req: ModelRequest, routing: OpenRouterRouting): OpenRouterParams;
|
|
125
190
|
/**
|
|
@@ -155,6 +220,15 @@ declare class OpenRouterModelClient implements ModelClient {
|
|
|
155
220
|
}): AsyncIterable<ModelEvent>;
|
|
156
221
|
}
|
|
157
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Wraps anything but a user abort. The abort passes through untouched: the
|
|
225
|
+
* loop already reads its own signal, and a wrapped abort would classify a
|
|
226
|
+
* cancellation as a provider failure.
|
|
227
|
+
*/
|
|
228
|
+
declare function toProviderError(provider: ProviderId, err: unknown): unknown;
|
|
229
|
+
/** The table. Exported for the one caller that has a code and no thrown error: a failure the wire REPORTED. */
|
|
230
|
+
declare function classifyFailure(name: string, status: number | undefined, hints: string): ProviderFailureKind;
|
|
231
|
+
|
|
158
232
|
/**
|
|
159
233
|
* @alma-harness/providers — `ModelClient` adapters (§6.2).
|
|
160
234
|
* Bi-provider from birth — `AnthropicModelClient` (spec 002) and
|
|
@@ -169,4 +243,4 @@ declare class OpenRouterModelClient implements ModelClient {
|
|
|
169
243
|
*/
|
|
170
244
|
declare const SUPPORTED_PROVIDERS: readonly ["anthropic", "openai", "openrouter"];
|
|
171
245
|
|
|
172
|
-
export { AnthropicModelClient, type AnthropicModelClientOptions, AnthropicTranslationError, OpenAIModelClient, type OpenAIModelClientOptions, OpenAITranslationError, OpenRouterModelClient, type OpenRouterModelClientOptions, type OpenRouterParams, type OpenRouterRouting, OpenRouterTranslationError, SUPPORTED_PROVIDERS, toAnthropicParams, toOpenAIParams, toOpenRouterParams, translateOpenAIStream, translateOpenRouterStream, translateStream };
|
|
246
|
+
export { AnthropicJobClient, type AnthropicJobClientOptions, AnthropicModelClient, type AnthropicModelClientOptions, AnthropicTranslationError, type BatchLine, OpenAIJobClient, type OpenAIJobClientOptions, OpenAIModelClient, type OpenAIModelClientOptions, OpenAITranslationError, OpenRouterModelClient, type OpenRouterModelClientOptions, type OpenRouterParams, type OpenRouterRouting, OpenRouterTranslationError, SUPPORTED_PROVIDERS, classifyFailure, toAnthropicParams, toBatchLines, toOpenAIParams, toOpenRouterParams, toProviderError, translateMessage, translateOpenAIStream, translateOpenRouterStream, translateResponse, translateStream };
|