@reinsjs/lowering-fetch 0.2.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 +33 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/index.d.ts +855 -0
- package/dist/index.js +2678 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @reinsjs/lowering-fetch
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f3e3427: New package: a zero-dependency lowering layer on `fetch` with a hand-written SSE parser — no provider SDK, no `node:*`, the same `dist/index.js` on Node ≥ 22 and Cloudflare Workers (verified at the strictest workerd tier, 2023 `compatibility_date`, no `nodejs_compat`, each protocol against a live provider). Three wire protocols, each with a declared `LOSS_MATRIX` comparable cell for cell with `@reinsjs/lowering-pi`:
|
|
8
|
+
|
|
9
|
+
- **OpenAI Chat Completions** — `deepseek()`, `openaiChat()`, generic `chatCompletions()` for any OpenAI-compatible endpoint. Mid-conversation `system` for `system_note`, deferred user messages recorded `lossy(user)`, thinking `dropped` on the official API and replayed as `reasoning_content` under the DeepSeek dialect (always sent there, because DeepSeek requires the field whenever `tools` is present).
|
|
10
|
+
- **Anthropic Messages** — `anthropic()`, generic `anthropicMessages()`. Mid-conversation `system` placed by the encoder (held until the next assistant turn or the end; framed as user text when the provider's placement rule cannot be met), signed thinking replayed and unsigned thinking `dropped` (declared, never turned into text), three cache breakpoints placed by the layer (system / tools / last user; a trailing note becomes a top-level `cache_control`), `anthropic-beta` only when you set `anthropic.betas`.
|
|
11
|
+
- **OpenAI Responses** — `openai()`, generic `openaiResponses()`. `store: false` forced and `previous_response_id` dropped (the timeline is the only state), `developer` / `system` messages at any position, reasoning items stored whole in `replay.thinkingSignature` and replayed verbatim with `include: ["reasoning.encrypted_content"]` requested by default, `call_id` and the `fc_` item id kept apart.
|
|
12
|
+
|
|
13
|
+
`LoweredRequest.payload.body` is exactly the JSON that is POSTed. `auth: "none"` for gateways that carry their own credential header. Non-2xx responses throw `HttpError` (`"<status> <body>"`) so `runLoop`'s retry rule applies unchanged; `timeoutMs` covers the whole stream. The README carries a side-by-side comparison with `@reinsjs/lowering-pi`; both layers stay supported and a timeline written through one replays through the other.
|
|
14
|
+
|
|
15
|
+
Responses line: the `phase` of an assistant message item recorded by the stream decoder (`replay.phase`) is now sent back on replay alongside the bare `msg_` id, matching what the `{"v":1,"id","phase"}` signature from `@reinsjs/lowering-pi` already replayed. Loss-matrix notes are joined with `; `.
|
|
16
|
+
|
|
17
|
+
All five `examples/` (minimal, mcp, team, adrate, eval) now use this package; the eval gate (PRD §7 threshold 2) was re-run on it for both model families before 0.2.
|
|
18
|
+
|
|
19
|
+
Thinking / reasoning replay requires the same provider, API **and model**: a `thinking` signature or an encrypted reasoning item produced by one model is `dropped` (declared in the loss matrix) when the request targets another model of the same provider, instead of being sent and rejected with a 400. Found by re-running the eval gate: the resume fixture's seed history was recorded on one model and replayed to another.
|
|
20
|
+
|
|
21
|
+
- 14345eb: Every user-facing runtime string is now English. This covers thrown error messages (construction-time validation, store and registry errors, HTTP 4xx bodies), `warn()` output from the brain modules and the server, the text the model sees in error tool results (`Unknown tool: …`, `Tool call blocked: …`, `Invalid arguments: …`, `Approval denied…`, `Approval expired…`), the `note` / `when` fields of every lowering loss matrix and landing, the placeholder text for content a wire protocol cannot carry, the conformance suites exported from `@reinsjs/core/testing`, and the `@reinsjs/eval` report and gate output. Previously these were Chinese while the READMEs and model-facing prompts were English, which left a non-Chinese-speaking host with unreadable diagnostics.
|
|
22
|
+
|
|
23
|
+
Nothing changes structurally: same errors, same codes, same warning points, same landing kinds. Hosts that match on the text of a message or a landing note (rather than on its error code or `landing` value) need to update those matches.
|
|
24
|
+
|
|
25
|
+
- a082a0c: Anthropic Messages line: native deferred tool loading. `ToolSpec.deferLoading` becomes `defer_loading: true` (the cache breakpoint moves to the last non-deferred tool; if every tool would be deferred none is), and a system-trusted `tool_result` made of `tool_reference` parts whose tools are in this request's `tools` is sent as `tool_reference` blocks the API expands in place — the tool list stays identical across the run, so the cache prefix survives a `tool_find` (measured on Haiku 4.5: the next request reads 8.5k cached tokens instead of rewriting them). Text parts of such a result are placed after the batch of `tool_result` blocks and recorded `lossy` / `tool-reference` in the loss matrix; references to tools absent from this request, or inside untrusted results, are rendered as text. New dialect flag `anthropic.deferredTools` and capability `deferredTools` (default on for `provider: "anthropic"`, off for third-party Anthropic-protocol endpoints — DeepSeek's ignores `defer_loading`). Chat Completions and Responses lines render `tool_reference` parts as text and do not send deferred tools.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [49d5dea]
|
|
30
|
+
- Updated dependencies [c16e3ea]
|
|
31
|
+
- Updated dependencies [14345eb]
|
|
32
|
+
- Updated dependencies [a082a0c]
|
|
33
|
+
- @reinsjs/core@0.2.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NewRate Limited
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @reinsjs/lowering-fetch
|
|
2
|
+
|
|
3
|
+
A zero-dependency lowering layer for [reins](../../README.md): timeline events → provider wire protocol → event drafts, using nothing but `fetch` and a hand-written SSE parser. No provider SDK, no `node:*`, the same code on Node, Workers, Deno and Bun.
|
|
4
|
+
|
|
5
|
+
Three wire protocols: **OpenAI Chat Completions** (the front door of the OpenAI-compatible ecosystem: DeepSeek, Qwen, vLLM, gateways), **Anthropic Messages** (mid-conversation system placement, signed thinking replay, explicit cache breakpoints) and **OpenAI Responses** (stateless `store: false`, encrypted reasoning replay, developer messages anywhere).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @reinsjs/lowering-fetch
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { anthropic, anthropicMessages, deepseek, openai, openaiChat, openaiResponses, chatCompletions } from "@reinsjs/lowering-fetch"
|
|
13
|
+
import { createAgent } from "@reinsjs/agent"
|
|
14
|
+
|
|
15
|
+
const opus = anthropic("claude-opus-5", { apiKey: process.env.ANTHROPIC_API_KEY! })
|
|
16
|
+
const ds = deepseek("deepseek-flash", { apiKey: process.env.DEEPSEEK_API_KEY! })
|
|
17
|
+
const gpt = openai("gpt-5-mini", { apiKey: process.env.OPENAI_API_KEY!, requestOptions: { reasoning: { effort: "low", summary: "auto" } } })
|
|
18
|
+
const mini = openaiChat("gpt-4o-mini", { apiKey: process.env.OPENAI_API_KEY! })
|
|
19
|
+
// any Anthropic-protocol endpoint (DeepSeek's compatible port, a gateway)
|
|
20
|
+
const dsClaude = anthropicMessages("deepseek-v4-flash", {
|
|
21
|
+
provider: "deepseek",
|
|
22
|
+
baseUrl: "https://api.deepseek.com/anthropic",
|
|
23
|
+
apiKey: process.env.DEEPSEEK_API_KEY!,
|
|
24
|
+
reasoning: true,
|
|
25
|
+
midConversationSystem: true,
|
|
26
|
+
})
|
|
27
|
+
const qwen = chatCompletions("qwen-max", {
|
|
28
|
+
provider: "qwen",
|
|
29
|
+
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
30
|
+
apiKey: process.env.DASHSCOPE_API_KEY!,
|
|
31
|
+
contextWindow: 128_000,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const agent = createAgent({ model: ds, store, tools })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each factory returns a `BoundModel = { model, lowering }`. `apiKey` is required and never read from the environment. For several models on one lowering instance, construct `FetchLowering` yourself and pass `models`.
|
|
38
|
+
|
|
39
|
+
## Choosing between `@reinsjs/lowering-fetch` and `@reinsjs/lowering-pi`
|
|
40
|
+
|
|
41
|
+
Both implement the same `Lowering` interface from `@reinsjs/core` and both declare every landing in a `LOSS_MATRIX`; `createAgent` does not care which one you pass. They coexist on purpose — pick per host, not per project.
|
|
42
|
+
|
|
43
|
+
| | `@reinsjs/lowering-fetch` | `@reinsjs/lowering-pi` |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Protocols | OpenAI Chat Completions, Anthropic Messages, OpenAI Responses | Anthropic Messages, OpenAI Responses |
|
|
46
|
+
| Deferred tools (Anthropic `defer_loading` + `tool_reference`, used by `lazyTools()`) | Yes — `capabilities.deferredTools` on official Anthropic models | No (request shaping is pi-ai's; references are rendered as text) |
|
|
47
|
+
| Dependencies | `@reinsjs/core` only (≈90 KB of ESM) | `pi-ai` and its ten dependencies (≈65 MB installed) |
|
|
48
|
+
| Request body | `payload.body` is what is sent | pre-rewrite shape; the real body is produced in pi-ai's `onPayload` hook |
|
|
49
|
+
| Mid-conversation `system` on Anthropic | placed by the encoder | placed by rewriting pi-ai's payload |
|
|
50
|
+
| Unsigned thinking | `dropped` (declared) | may fall back to visible text |
|
|
51
|
+
| Model table | a minimal built-in table you override per model | pi-ai's table, maintained upstream |
|
|
52
|
+
| Runtime | verified on Cloudflare workerd at the strictest tier (2023 compatibility date, no `nodejs_compat`), Bun, Deno (`--allow-net` only) and Vercel `edge-runtime`, all three protocols against live providers | verified on the same four runtimes, both protocols; Deno needs `--allow-sys=osRelease`, an Edge runtime needs a `process` global for the OpenAI path |
|
|
53
|
+
|
|
54
|
+
**Start with `lowering-fetch`** for a new host: it covers everything `lowering-pi` covers plus the OpenAI-compatible ecosystem, ships nothing but this package, and the request you debug is the request that was sent. Stay on `lowering-pi` when you already run on it (there is nothing to migrate for), or when you want the provider table and new wire-protocol features to arrive through pi-ai rather than through this package. Events written by one layer replay through the other: `replay.thinkingSignature` uses the same shape on both, so switching does not invalidate a stored timeline.
|
|
55
|
+
|
|
56
|
+
## Runtime
|
|
57
|
+
|
|
58
|
+
No `node:*`, no `process`, no `Buffer` — the same `dist/index.js` runs on Node ≥ 22 and on Cloudflare Workers with a 2023 `compatibility_date` and no compatibility flags (`spikes/edge-runtime-check`, tier "最严档": module load, a byte-sliced SSE stream against a local fake endpoint, and one live request per protocol). Bun 1.4, Deno 2.9 (only `--allow-net` needed — the layer reads no system information) and Vercel's `edge-runtime` (bare vm, no `process`, no `eval`) pass the same probes, all three protocols against live providers (`spikes/runtime-matrix`).
|
|
59
|
+
|
|
60
|
+
## Options
|
|
61
|
+
|
|
62
|
+
| option | meaning |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `apiKey` | required; use `auth: "none"` when the credential lives in `headers` (Cloudflare AI Gateway's `cf-aig-authorization`) |
|
|
65
|
+
| `baseUrl` | protocol root; `/chat/completions` (OpenAI: `…/v1`, DeepSeek: the bare host), `/messages` (Anthropic: `…/v1`) or `/responses` (OpenAI: `…/v1`) is appended |
|
|
66
|
+
| `contextWindow`, `maxOutputTokens`, `reasoning`, `images`, `cost`, `midConversationSystem` | override the built-in table; unknown models start from conservative defaults (128k / 16k / no reasoning / no images) |
|
|
67
|
+
| `chat.reasoningContent` | DeepSeek dialect: replay `reasoning_content` (on by default in `deepseek()`, see below) |
|
|
68
|
+
| `anthropic.betas` | values for the `anthropic-beta` header; sent only when set (mid-conversation system needs none) |
|
|
69
|
+
| `anthropic.cacheBreakpoints`, `anthropic.cacheTtl`, `anthropic.midSystemCacheBreakpoint` | explicit cache breakpoints on Anthropic (default on, 5 min); what to do when a note ends the request — `"automatic"` (default, top-level `cache_control`), `"previous-user"`, `"drop"` |
|
|
70
|
+
| `anthropic.deferredTools` | whether `ToolSpec.deferLoading` becomes `defer_loading: true` and `tool_reference` content parts become native `tool_reference` blocks. Default: on for `provider: "anthropic"`, off for third-party Anthropic-protocol endpoints (DeepSeek's compatible endpoint ignores `defer_loading`, so every tool would be visible). When off, deferred tools are simply not sent and references are rendered as text |
|
|
71
|
+
| `responses.systemRole` | role for the system prompt and notes on Responses: default `developer` for reasoning models, `system` otherwise; pin it for an upstream that rejects one of them |
|
|
72
|
+
| `responses.encryptedReasoning` | default on for reasoning models: `include: ["reasoning.encrypted_content"]` is always sent so reasoning items can be replayed under `store: false`; off for upstreams that reject `include` (then `thinkingReplay` reports `false`) |
|
|
73
|
+
| `requestOptions` | spread into the body (`max_tokens`, `temperature`, `thinking`, OpenAI's `parallel_tool_calls`, Responses' `reasoning` / `max_output_tokens` / `prompt_cache_key`…); `messages` / `input` / `tools` / `system` / `model` / `stream` / `store` / `previous_response_id` cannot be overridden. On Anthropic `max_tokens` defaults to the model's `maxOutputTokens` and `thinking` is left to you (Opus 5+ defaults to adaptive server-side; Haiku 4.5 still needs `budget_tokens`); on Responses `reasoning` is likewise yours (gpt-5 defaults to `medium`, gpt-5.1+ to `none`) |
|
|
74
|
+
| `timeoutMs` | whole-request deadline including the stream, default 600 000; a timeout is a retryable error, a host `signal` abort is `aborted` |
|
|
75
|
+
| `trustMarkers` | default on: tool output is wrapped in `<untrusted source="tool:…">` in the request; the log keeps the original |
|
|
76
|
+
|
|
77
|
+
## How events land on Chat Completions
|
|
78
|
+
|
|
79
|
+
Every landing is declared in `LOSS_MATRIX["openai-chat"]` and asserted by tests — nothing is dropped silently.
|
|
80
|
+
|
|
81
|
+
- `system_note` → a mid-conversation `system` message (`exact`; verified on OpenAI and DeepSeek). Set `midConversationSystem: false` for an upstream that rejects it and the note goes out as a framed `user` message (`lossy`).
|
|
82
|
+
- A user message or note that arrives while tool results are still pending is moved after them on the wire (`tool` messages must directly follow the `assistant` that called them); the user message is recorded `lossy(user)`, the log order is untouched.
|
|
83
|
+
- **Thinking**: Chat Completions has no replay slot (no signature, no encrypted item). On the official API thinking events are `dropped`. With `chat.reasoningContent` (DeepSeek) they are replayed as `reasoning_content` — and DeepSeek *requires* the field on every historical assistant message once `tools` is present (400 otherwise), so the layer always sends it, empty when there is nothing to replay.
|
|
84
|
+
- One turn with several text blocks is merged into one `content` string (`lossy(merged-text)`); `tool` messages carry text only, images become a placeholder (`lossy`); `isError` has no flag on this protocol and is expressed as a `[tool error]` prefix (`lossy`).
|
|
85
|
+
- No explicit cache breakpoints exist on this protocol; provider-side automatic prefix caching applies and `cached_tokens` is reported as `cacheRead`.
|
|
86
|
+
- Usage follows core semantics: `input` is the uncached prompt count. `costUsd` uses the built-in price table (DeepSeek: peak rates, an upper bound).
|
|
87
|
+
|
|
88
|
+
## How events land on Anthropic Messages
|
|
89
|
+
|
|
90
|
+
Declared in `LOSS_MATRIX["anthropic-messages"]`, cell for cell comparable with `@reinsjs/lowering-pi`.
|
|
91
|
+
|
|
92
|
+
- `system_note` → a mid-conversation `{ role: "system" }` message on model families that accept it (Fable 5.x / Mythos 5.x / Opus 5 / Opus 4.8, or `midConversationSystem: true`). The provider requires such a message to follow a `user` turn and be followed by an `assistant` turn or end the request, so notes are held and emitted right before the next assistant turn (or at the end); when that slot follows an assistant turn — or the note would be the first message — the note goes out as a framed `user` text (`lossy(user-role)`). Other models always get the framed text.
|
|
93
|
+
- Tool results and anything moved after them (a user message that arrived while results were pending) share one `user` message, so `tool_result` blocks directly follow the `tool_use` turn. `is_error` is carried natively.
|
|
94
|
+
- **Thinking** is replayed only when the event carries a `signature` from the same provider, API **and model** (`thinking` / `redacted_thinking` blocks); unsigned thinking (an interrupted stream), thinking from another provider, or thinking from another model of the same provider is `dropped` and declared — it is not turned into visible text (a signature is only valid for the model that produced it: replaying one across models is a 400). Empty-text thinking blocks with a signature (`display: "omitted"`) are kept and replayed.
|
|
95
|
+
- **Cache breakpoints**: one on the last system block, one on the last tool, one on the last block of the last `user` message. When a note is the last message, the conversation breakpoint becomes a top-level `cache_control` (measured on par with no injection). Never more than four in total.
|
|
96
|
+
- Usage: `input_tokens` is already the uncached count; `cache_read_input_tokens` / `cache_creation_input_tokens` map to `cacheRead` / `cacheWrite`. `stop_reason: "refusal"` becomes a non-retryable `error` carrying `stop_details`.
|
|
97
|
+
|
|
98
|
+
- Deferred tools (`ToolSpec.deferLoading`, set by `lazyTools()` when `capabilities.deferredTools` is true): `defer_loading: true` on the tool, the cache breakpoint moves to the last non-deferred tool (a deferred tool cannot carry `cache_control`), and if every tool would be deferred none is (the API rejects that). A `tool_result` whose content is only `tool_reference` parts, comes from a system-trusted result, and names tools present in this request's `tools` is sent as `tool_reference` blocks the API expands in place — the tool list never changes, so the cache prefix survives (`exact` / `tool-reference`); text parts in the same result are moved after the batch of `tool_result` blocks (`lossy` / `tool-reference`, they cannot be mixed). References to tools not in this request, or inside untrusted results, are rendered as text instead — the API validates every reference in the whole history and answers 400 for an unknown name.
|
|
99
|
+
|
|
100
|
+
## How events land on OpenAI Responses
|
|
101
|
+
|
|
102
|
+
Declared in `LOSS_MATRIX["openai-responses"]`, cell for cell comparable with `@reinsjs/lowering-pi`.
|
|
103
|
+
|
|
104
|
+
- **Stateless by construction.** Every request carries the whole history in `input`; `store: false` is forced and a `previous_response_id` in `requestOptions` is dropped — the timeline is the only source of truth, OpenAI's server-side conversation state is never relied on.
|
|
105
|
+
- The system prompt and `system_note` are `developer` messages (reasoning models) or `system` messages (others) at any position in `input` — no placement rules, always `exact`. `compaction` is a framed `user` text (`lossy`).
|
|
106
|
+
- **Reasoning**: reasoning models always request `include: ["reasoning.encrypted_content"]`; each reasoning item is stored whole in the event's `replay.thinkingSignature` and replayed verbatim (the provider verifies the encrypted payload — a forged one is a 400). Items without `encrypted_content` (an interrupted stream, `include` refused), from another provider, or from another model are `dropped` and declared, never turned into text. Reasoning items with an encrypted payload but no summary are kept as empty-text thinking events so the next turn can replay them.
|
|
107
|
+
- Assistant text becomes a `type: "message"` item per text block, replaying the provider's `msg_` id (`replay.textSignature`, lowering-pi's JSON form is accepted too) or a generated one. Tool calls are `function_call` items: `call_id` is the event's `toolCallId`, the `fc_` item id lives in `replay.itemId` and is replayed only for the same model (OpenAI validates the pairing of `fc_` and `rs_` items); `arguments` is a JSON string, so non-object arguments need no wrapping. Tools are sent with `strict: false`.
|
|
108
|
+
- Tool results are `function_call_output` items matched by `call_id`: a string when text-only, an array of `input_text` / `input_image` blocks when the result has images and the model accepts them; `isError` has no flag on this protocol and is expressed as a `[tool error]` prefix (`lossy`).
|
|
109
|
+
- Usage: `input_tokens` includes cached and cache-write tokens, so `input` is `input_tokens − cached_tokens − cache_write_tokens`, with the two mapped to `cacheRead` / `cacheWrite`. `status: "incomplete"` with `max_output_tokens` is `length`; with `content_filter` a non-retryable `error`; `response.failed` and `error` frames are errors carrying the provider's code and message. Automatic prefix caching is best-effort on OpenAI's side; pass `prompt_cache_key` in `requestOptions` to improve hit rates.
|
|
110
|
+
- Built-in table: the gpt-5 family (5.5 / 5.4 / 5.4-mini / 5.2 / 5.1 / 5 / 5-mini / 5-nano), o3 / o4-mini and gpt-4.1 / 4.1-mini / 4o-mini on Responses; the same OpenAI ids also exist as Chat Completions entries, which `openaiChat()` picks by protocol. A bare `{ provider: "openai", id }` on a `FetchLowering` you construct yourself resolves to Responses.
|
|
111
|
+
|
|
112
|
+
## Errors and retries
|
|
113
|
+
|
|
114
|
+
Non-2xx responses throw `HttpError` with `status`, `headers` and the raw body (`"<status> <body>"`, the same shape the official SDKs use), so `runLoop`'s transient-failure rule applies unchanged: 408 / 409 / 429 / 5xx retry, everything else does not. Network failures propagate from `fetch` untouched.
|
|
115
|
+
|
|
116
|
+
## Documentation
|
|
117
|
+
|
|
118
|
+
`docs/技术方案.md` §11, `docs/模块盘点/lowering-fetch.md`, `spikes/f1-chat-live/`, `spikes/f2-anthropic-live/`, `spikes/f3-responses-live/` (live verification against DeepSeek and, through the Cloudflare AI Gateway, OpenAI and Anthropic) and `spikes/edge-runtime-check/` (Cloudflare workerd) — in Chinese, at the repository root.
|
|
119
|
+
|
|
120
|
+
MIT © 2026 NewRate Limited.
|