@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 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-release.** Not yet published to npm.
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 typed errors propagate as-is.
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
- /** Raised when a request or stream cannot be represented faithfully. */
30
- declare class AnthropicTranslationError extends Error {
31
- constructor(message: string);
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 typed errors propagate as-is.
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
- declare class OpenAITranslationError extends Error {
66
- constructor(message: string);
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
- declare class OpenRouterTranslationError extends Error {
86
- constructor(message: string);
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 };