@wrenautomation/models 0.0.0-stage → 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/LICENSE.md ADDED
@@ -0,0 +1,91 @@
1
+ # PolyForm Shield License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/shield/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
12
+
13
+ ## Distribution License
14
+
15
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
16
+
17
+ ## Notices
18
+
19
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
20
+
21
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
22
+
23
+ ## Changes and New Works License
24
+
25
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
26
+
27
+ ## Patent License
28
+
29
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
30
+
31
+ ## Noncompete
32
+
33
+ Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
34
+
35
+ ## Competition
36
+
37
+ Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge. If you market a product as a practical substitute for the software or another product, it definitely competes.
38
+
39
+ ## New Products
40
+
41
+ If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
42
+
43
+ ## Discontinued Products
44
+
45
+ You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
46
+
47
+ > Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
48
+
49
+ ## Sales of Business
50
+
51
+ If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce [Noncompete](#noncompete) for that product.
52
+
53
+ ## Fair Use
54
+
55
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
56
+
57
+ ## No Other Rights
58
+
59
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
60
+
61
+ ## Patent Defense
62
+
63
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
64
+
65
+ ## Violations
66
+
67
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
68
+
69
+ ## No Liability
70
+
71
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
76
+
77
+ A **product** can be a good or service, or a combination of them.
78
+
79
+ **You** refers to the individual or entity agreeing to these terms.
80
+
81
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
82
+
83
+ **Affiliates** means the other organizations than an organization has control over, is under the control of, or is under common control with.
84
+
85
+ **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
86
+
87
+ **Your licenses** are all the licenses granted to you for the software under these terms.
88
+
89
+ **Use** means anything you do with the software requiring one of your licenses.
90
+
91
+ Required Notice: Copyright 2026 William Jin (https://wrenautomation.com)
package/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
1
+ # models
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ One door to every model call in Wren's repos. A `Model` takes text, image, video, audio and PDF
4
+ parts. It answers with text, tokens, USD and why it stopped. `ask` parses the answer to a zod
5
+ shape and asks again once when the answer does not fit.
6
+
7
+ The rule: code that calls a model calls it through this package. A new vendor, a new part kind or
8
+ a new price goes here, never in the caller. No guard enforces this; this file and wren's
9
+ `designs/2026-10-26-research-engine.md` record it.
10
+
11
+ | Import | Does | Runs in |
12
+ |---|---|---|
13
+ | `@wrenautomation/models` | `Model`, `Part`, `Usage`; `anthropic`, `gemini`, `cohere`, `compatible` (Groq, OpenRouter, Mistral, Cerebras, OpenAI), `gateway`; `rotating` key rings; `fakeModel`, `scriptedModel`; the errors | Node, Workers |
14
+ | `@wrenautomation/models/hear` | speech to text: `groqEar`, `cohereEar`, `compatibleEar` (any `/audio/transcriptions` server) | Node, Workers |
15
+ | `@wrenautomation/models/prices` | one price table: `MODEL_PRICES`, `SPEECH_PRICES`, `priceUsd`, `priced`, `speechUsd`, `addUsage`, `pricesText` | Node, Workers |
16
+ | `@wrenautomation/models/command` | a shell command as a model (`commandModel`), Claude Code on the subscription (`claudeCode`) | Node |
17
+ | `@wrenautomation/models/spec` | a model from a setting string: `modelFromSpec("anthropic:sonnet", env)` | Node |
18
+
19
+ ## Use
20
+
21
+ ```ts
22
+ import { anthropic, gemini } from "@wrenautomation/models";
23
+ import { z } from "zod";
24
+
25
+ const claude = anthropic({ key: [key1, key2], model: "sonnet" });
26
+ const { value, usage } = await claude.ask("Name three colours.", z.object({ colours: z.array(z.string()) }));
27
+ // usage: { input, output, cacheRead?, cacheWrite?, usd }
28
+
29
+ const eyes = gemini({ key: geminiKeys });
30
+ const reply = await eyes.complete({
31
+ prompt: [
32
+ { type: "text", text: "What is on this slide?" },
33
+ { type: "image", data: pngBytes, mediaType: "image/png" },
34
+ ],
35
+ effort: "low",
36
+ });
37
+ ```
38
+
39
+ Spec strings: `fake`, `claude-code[:model]`, `anthropic[:haiku|sonnet|opus|id]`, `gateway[:alias]`,
40
+ `gemini[:id]`, `cohere[:id]`, `groq|openrouter|mistral|cerebras|openai[:id]`, `command:<shell command>`.
41
+ Keys come from `<VENDOR>_API_KEY`, or a ring `NUM_<VENDOR>` + `<VENDOR>_API_KEY_1..n`.
42
+
43
+ ## Errors
44
+
45
+ Each error says what the caller does next. Each check (`isTransient`, `isCut`...) also matches by
46
+ name, so a host that holds two copies of this package still sorts them.
47
+
48
+ | Error | When | Next |
49
+ |---|---|---|
50
+ | `Transient` | 408, 429, 5xx, a dropped connection, a timeout | wait `retryAfter`, try again |
51
+ | `Refused` | 401, 402, 403 | the key ring moves to the next key |
52
+ | `InputRejected` | 400, 413, 422 | change the request |
53
+ | `Unsupported` | a part kind the model does not take | pick another model; parts are never dropped |
54
+ | `Cut` | the output cap stopped the answer | `partial` holds the text so far, `usage` its cost |
55
+ | `NoAnswer` | `ask` found no object of the shape after its repairs | `issues` says why |
56
+
57
+ A key ring moves past a refused or busy key at once. When every key is busy, it waits 2 s, then
58
+ doubles up to 30 s, for `passes` passes (default 1). Retries inside the SDK are off.
59
+
60
+ ## Prices
61
+
62
+ USD per million tokens, as of `PRICES_AS_OF`. A call on a free key ring (Cohere, Gemini, Groq,
63
+ OpenRouter `:free`, the gateway's `free`, Claude Code on the subscription) records `usd: 0`,
64
+ `free: true`, and `listUsd` (what it would have cost). Anthropic cache writes are 1.25x input
65
+ (5 minutes) or 2x (1 hour); reads are 0.1x (0.05x on Opus 5.5). Haiku 5.5 has two tiers: a
66
+ prompt over 100K tokens costs 5x. A model with no row keeps its tokens and has no `usd`.
67
+
68
+ Sources: Anthropic's pricing page; Cohere from activepieces.com and pricepertoken.com; Groq from
69
+ groq.com/pricing; Gemini from anotherwrapper.com and benchlm.ai.
70
+
71
+ ## Why the AI SDK
72
+
73
+ The adapters sit on the [AI SDK](https://ai-sdk.dev) 7 (Apache 2.0): one call shape for Anthropic,
74
+ Google, Cohere and every OpenAI-compatible server, with media parts and token details per vendor.
75
+ The other choices were each vendor's own SDK (four call shapes and four usage formats to keep) and
76
+ plain `fetch` (every wire format kept by hand). This package adds only what the SDK leaves out:
77
+ errors sorted by what to do next, key rings, prices, the zod parse with repair, and speech.
78
+
79
+ ## Develop
80
+
81
+ ```sh
82
+ pnpm install
83
+ pnpm lint && pnpm typecheck && pnpm test
84
+ ```
85
+
86
+ A `v*` tag publishes to npm (`.github/workflows/release.yml`, trusted publishing: no stored token).
87
+
88
+ License: PolyForm Shield 1.0.0 (`LICENSE.md`).
@@ -0,0 +1,44 @@
1
+ import type { Model, Usage } from "./types.js";
2
+ /** What `claude -p --output-format json` prints; only what is read. */
3
+ interface Envelope {
4
+ type?: string;
5
+ subtype?: string;
6
+ is_error?: boolean;
7
+ result?: string;
8
+ total_cost_usd?: number;
9
+ usage?: {
10
+ input_tokens?: number;
11
+ output_tokens?: number;
12
+ cache_read_input_tokens?: number;
13
+ cache_creation_input_tokens?: number;
14
+ };
15
+ }
16
+ /** The answer and usage from a command's stdout: Claude Code's envelope, else the whole text. */
17
+ export declare function readOutput(stdout: string): {
18
+ text: string;
19
+ usage?: Usage;
20
+ envelope?: Envelope;
21
+ };
22
+ /**
23
+ * Any shell command as a model. A command that says nothing for `timeoutMs` (3 minutes) is
24
+ * killed and the call throws `Transient`, so a hung model never hangs the run.
25
+ */
26
+ export declare function commandModel(command: string, o?: {
27
+ id?: string;
28
+ timeoutMs?: number;
29
+ }): Model;
30
+ export type ClaudeRun = (args: string[], stdin: string, timeoutMs: number) => Promise<string>;
31
+ /** The subscription pays: an `ANTHROPIC_API_KEY` would take over from the login, so the child never sees one. */
32
+ export declare function claudeChildEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
33
+ /**
34
+ * Claude Code headless (`claude -p`), one process per call, on the person's subscription: its
35
+ * cost is 0 and its list price kept. Tools, MCP servers, skills and settings are off, and it runs
36
+ * in a scratch folder, so nothing on disk is touched. About 2 s to start: right for a few big
37
+ * calls, wrong for thousands of rows. `maxTokens` has no flag here; the prompt bounds the answer.
38
+ */
39
+ export declare function claudeCode(model?: string, o?: {
40
+ bin?: string;
41
+ timeoutMs?: number;
42
+ run?: ClaudeRun;
43
+ }): Model;
44
+ export {};
@@ -0,0 +1,151 @@
1
+ /**
2
+ * A model that is a local process (Node only, its own door): any shell command with the prompt on
3
+ * stdin and the answer on stdout, or Claude Code headless on the person's own subscription.
4
+ */
5
+ import { execFile, spawn } from "node:child_process";
6
+ import { tmpdir } from "node:os";
7
+ import { Transient } from "./errors.js";
8
+ import { modelOf, promptText, TEXT_ONLY } from "./model.js";
9
+ const isEnvelope = (v) => typeof v === "object" && v !== null && typeof v.result === "string";
10
+ /** The answer and usage from a command's stdout: Claude Code's envelope, else the whole text. */
11
+ export function readOutput(stdout) {
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(stdout);
15
+ }
16
+ catch {
17
+ return { text: stdout };
18
+ }
19
+ if (!isEnvelope(parsed))
20
+ return { text: stdout };
21
+ const u = parsed.usage;
22
+ const usage = {};
23
+ const read = u?.cache_read_input_tokens ?? 0;
24
+ const write = u?.cache_creation_input_tokens ?? 0;
25
+ if (u?.input_tokens !== undefined)
26
+ usage.input = u.input_tokens + read + write;
27
+ if (u?.output_tokens !== undefined)
28
+ usage.output = u.output_tokens;
29
+ if (read)
30
+ usage.cacheRead = read;
31
+ if (write)
32
+ usage.cacheWrite = write;
33
+ if (parsed.total_cost_usd !== undefined)
34
+ usage.usd = parsed.total_cost_usd;
35
+ return { text: parsed.result, usage, envelope: parsed };
36
+ }
37
+ const textOf = (id, req) => {
38
+ const text = promptText(req.prompt);
39
+ if (text === null)
40
+ throw new Error(`${id} takes text only`);
41
+ return req.system ? `${req.system}\n\n${text}` : text;
42
+ };
43
+ const reply = (id, stdout, ms, free) => {
44
+ const { text, usage = {}, envelope } = readOutput(stdout);
45
+ if (envelope?.is_error || !text.trim())
46
+ throw new Error(`${id}: ${envelope?.subtype ?? "empty answer"}: ${text.slice(0, 300)}`);
47
+ const cost = free && usage.usd !== undefined ? { ...usage, usd: 0, free: true, listUsd: usage.usd } : usage;
48
+ return {
49
+ text,
50
+ usage: cost,
51
+ model: id,
52
+ stop: envelope?.subtype,
53
+ ms,
54
+ raw: envelope ? { ...envelope, result: undefined } : {},
55
+ };
56
+ };
57
+ /**
58
+ * Any shell command as a model. A command that says nothing for `timeoutMs` (3 minutes) is
59
+ * killed and the call throws `Transient`, so a hung model never hangs the run.
60
+ */
61
+ export function commandModel(command, o = {}) {
62
+ const id = o.id ?? command;
63
+ const timeoutMs = o.timeoutMs ?? 180_000;
64
+ return modelOf({
65
+ id,
66
+ provider: "command",
67
+ takes: TEXT_ONLY,
68
+ complete: (req) => new Promise((resolve, reject) => {
69
+ const started = performance.now();
70
+ const child = spawn(command, { shell: true, stdio: ["pipe", "pipe", "inherit"] });
71
+ const out = [];
72
+ let timedOut = false;
73
+ const timer = setTimeout(() => {
74
+ timedOut = true;
75
+ child.kill();
76
+ }, timeoutMs);
77
+ child.stdout.on("data", (b) => out.push(b));
78
+ child.on("error", (e) => {
79
+ clearTimeout(timer);
80
+ reject(e);
81
+ });
82
+ child.on("close", (code) => {
83
+ clearTimeout(timer);
84
+ const stdout = Buffer.concat(out).toString("utf8");
85
+ if (timedOut)
86
+ return reject(new Transient(`${id}: no answer in ${Math.round(timeoutMs / 1000)}s`));
87
+ if (code !== 0)
88
+ return reject(new Error(`${id} exited ${code}: ${stdout.slice(0, 400)}`));
89
+ try {
90
+ resolve(reply(id, stdout, Math.round(performance.now() - started), false));
91
+ }
92
+ catch (e) {
93
+ reject(e);
94
+ }
95
+ });
96
+ child.stdin.end(textOf(id, req));
97
+ }),
98
+ });
99
+ }
100
+ /** The subscription pays: an `ANTHROPIC_API_KEY` would take over from the login, so the child never sees one. */
101
+ export function claudeChildEnv(env = process.env) {
102
+ const { ANTHROPIC_API_KEY: _drop, ...rest } = env;
103
+ return rest;
104
+ }
105
+ function spawnClaude(bin) {
106
+ return (args, stdin, timeoutMs) => new Promise((resolve, reject) => {
107
+ const child = execFile(bin, args, { cwd: tmpdir(), timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, env: claudeChildEnv() }, (err, stdout, stderr) => {
108
+ // A failed call still prints its JSON (is_error, why) on stdout: the reader says it.
109
+ if (!err || (!err.killed && stdout.trim().startsWith("{")))
110
+ return resolve(stdout);
111
+ if (err.killed)
112
+ return reject(new Transient(`claude -p: killed after ${timeoutMs}ms`));
113
+ reject(new Error(`claude -p exit ${err.code ?? "?"}: ${(stderr || stdout).slice(0, 400)}`));
114
+ });
115
+ child.stdin?.end(stdin);
116
+ });
117
+ }
118
+ /** Kept short: the prompt carries the task and the output shape. */
119
+ const SYSTEM = "Do exactly what the message asks. Reply with only the answer, no preamble.";
120
+ /**
121
+ * Claude Code headless (`claude -p`), one process per call, on the person's subscription: its
122
+ * cost is 0 and its list price kept. Tools, MCP servers, skills and settings are off, and it runs
123
+ * in a scratch folder, so nothing on disk is touched. About 2 s to start: right for a few big
124
+ * calls, wrong for thousands of rows. `maxTokens` has no flag here; the prompt bounds the answer.
125
+ */
126
+ export function claudeCode(model = "sonnet", o = {}) {
127
+ const id = `claude-code:${model}`;
128
+ const run = o.run ?? spawnClaude(o.bin ?? "claude");
129
+ const timeoutMs = o.timeoutMs ?? 300_000;
130
+ return modelOf({
131
+ id,
132
+ provider: "claude-code",
133
+ takes: TEXT_ONLY,
134
+ async complete(req) {
135
+ const text = promptText(req.prompt);
136
+ if (text === null)
137
+ throw new Error(`${id} takes text only`);
138
+ const args = [
139
+ "-p",
140
+ ...["--output-format", "json", "--model", model],
141
+ ...["--system-prompt", req.system ?? SYSTEM],
142
+ ...["--tools", "", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}'],
143
+ ...["--disable-slash-commands", "--setting-sources", "", "--no-session-persistence"],
144
+ ];
145
+ const started = performance.now();
146
+ const out = await run(args, text, timeoutMs);
147
+ return reply(id, out, Math.round(performance.now() - started), true);
148
+ },
149
+ });
150
+ }
151
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What a model call throws, by what the caller does next. `Transient`: try again later (429, 5xx,
3
+ * a dropped connection). `Refused`: this key is bad (401, 402, 403); the key ring moves on.
4
+ * `InputRejected`: the vendor refused this input (400, 413, 422); record it and go on.
5
+ * `Unsupported`: the model cannot take a part it was given. `Cut`: the output cap stopped the
6
+ * answer; the text so far and its tokens come with it. Each is checked by shape too (`isCut`):
7
+ * a host may hold two copies of this package while its pins catch up.
8
+ */
9
+ import type { PartKind, Usage } from "./types.js";
10
+ export declare class Transient extends Error {
11
+ /** ISO time, from the server's `retry-after` when it sent one. */
12
+ readonly retryAfter: string | undefined;
13
+ readonly status: number | undefined;
14
+ constructor(message: string, o?: {
15
+ retryAfter?: string | number | null | undefined;
16
+ status?: number;
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ export declare class Refused extends Error {
21
+ readonly status: number;
22
+ constructor(message: string, status: number);
23
+ }
24
+ export declare class InputRejected extends Error {
25
+ readonly status: number | undefined;
26
+ constructor(message: string, status?: number);
27
+ }
28
+ export declare class Unsupported extends Error {
29
+ readonly kind: PartKind;
30
+ constructor(model: string, kind: PartKind);
31
+ }
32
+ export declare class Cut extends Error {
33
+ readonly partial: string;
34
+ readonly usage: Usage | undefined;
35
+ constructor(message: string, partial: string, usage?: Usage);
36
+ }
37
+ export declare const isCut: (err: unknown) => err is Cut;
38
+ export declare const isTransient: (err: unknown) => err is Transient;
39
+ export declare const isRefused: (err: unknown) => err is Refused;
40
+ export declare const isInputRejected: (err: unknown) => err is InputRejected;
41
+ /** A `retry-after` value (seconds, an HTTP date or ISO time) as ISO time; undefined when unreadable. */
42
+ export declare function retryAfterIso(value: string | number | null | undefined, now?: () => Date): string | undefined;
43
+ /** A status worth another try: 408, 429 and every 5xx. */
44
+ export declare const transientStatus: (status: number) => boolean;
45
+ /** The error for an HTTP status, by what the caller does next. */
46
+ export declare function statusError(message: string, status: number, retryAfter?: string | number | null): Error;
47
+ /** The error for a response that is not ok. */
48
+ export declare const httpError: (what: string, res: Response, detail?: string) => Error;
49
+ /** A fetch that threw (the network, a timeout): always transient. */
50
+ export declare const transport: (what: string, err: unknown) => Transient;
package/dist/errors.js ADDED
@@ -0,0 +1,79 @@
1
+ export class Transient extends Error {
2
+ /** ISO time, from the server's `retry-after` when it sent one. */
3
+ retryAfter;
4
+ status;
5
+ constructor(message, o = {}) {
6
+ super(message, o.cause === undefined ? undefined : { cause: o.cause });
7
+ this.name = "Transient";
8
+ this.retryAfter = retryAfterIso(o.retryAfter);
9
+ this.status = o.status;
10
+ }
11
+ }
12
+ export class Refused extends Error {
13
+ status;
14
+ constructor(message, status) {
15
+ super(message);
16
+ this.name = "Refused";
17
+ this.status = status;
18
+ }
19
+ }
20
+ export class InputRejected extends Error {
21
+ status;
22
+ constructor(message, status) {
23
+ super(message);
24
+ this.name = "InputRejected";
25
+ this.status = status;
26
+ }
27
+ }
28
+ export class Unsupported extends Error {
29
+ kind;
30
+ constructor(model, kind) {
31
+ super(`${model} takes no ${kind} part`);
32
+ this.name = "Unsupported";
33
+ this.kind = kind;
34
+ }
35
+ }
36
+ export class Cut extends Error {
37
+ partial;
38
+ usage;
39
+ constructor(message, partial, usage) {
40
+ super(message);
41
+ this.name = "Cut";
42
+ this.partial = partial;
43
+ this.usage = usage;
44
+ }
45
+ }
46
+ const named = (err, name) => err instanceof Error && err.name === name;
47
+ export const isCut = (err) => named(err, "Cut") && typeof err.partial === "string";
48
+ export const isTransient = (err) => named(err, "Transient");
49
+ export const isRefused = (err) => named(err, "Refused");
50
+ export const isInputRejected = (err) => named(err, "InputRejected");
51
+ /** A `retry-after` value (seconds, an HTTP date or ISO time) as ISO time; undefined when unreadable. */
52
+ export function retryAfterIso(value, now = () => new Date()) {
53
+ if (value === null || value === undefined || value === "")
54
+ return undefined;
55
+ const seconds = typeof value === "number" ? value : Number(value);
56
+ if (Number.isFinite(seconds))
57
+ return new Date(now().getTime() + seconds * 1000).toISOString();
58
+ const t = Date.parse(String(value));
59
+ return Number.isNaN(t) ? undefined : new Date(t).toISOString();
60
+ }
61
+ /** A status worth another try: 408, 429 and every 5xx. */
62
+ export const transientStatus = (status) => status === 408 || status === 429 || status >= 500;
63
+ const REFUSED = new Set([401, 402, 403]);
64
+ const REJECTED = new Set([400, 413, 422]);
65
+ /** The error for an HTTP status, by what the caller does next. */
66
+ export function statusError(message, status, retryAfter) {
67
+ if (transientStatus(status))
68
+ return new Transient(message, { retryAfter, status });
69
+ if (REFUSED.has(status))
70
+ return new Refused(message, status);
71
+ if (REJECTED.has(status))
72
+ return new InputRejected(message, status);
73
+ return new Error(message);
74
+ }
75
+ /** The error for a response that is not ok. */
76
+ export const httpError = (what, res, detail) => statusError(`${what}: HTTP ${res.status}${detail ? ` ${detail}` : ""}`, res.status, res.headers.get("retry-after"));
77
+ /** A fetch that threw (the network, a timeout): always transient. */
78
+ export const transport = (what, err) => new Transient(`${what}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
79
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/fake.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { Model, Request } from "./types.js";
2
+ /** Answers each call from `respond` (the prompt's text and the system), or a fixed string. */
3
+ export declare function fakeModel(respond?: string | ((prompt: string, req: Request) => string | Promise<string>), id?: string): Model;
4
+ /** Answers from a fixed list, in order; an object answer is sent as its JSON. Throws past the end. */
5
+ export declare function scriptedModel(answers: readonly unknown[], id?: string): Model;
package/dist/fake.js ADDED
@@ -0,0 +1,33 @@
1
+ /** Models for tests and dry runs: no network, no keys. */
2
+ import { modelOf, promptText } from "./model.js";
3
+ const reply = (text, model) => ({
4
+ text,
5
+ usage: {},
6
+ model,
7
+ stop: "end_turn",
8
+ ms: 0,
9
+ raw: { fake: true },
10
+ });
11
+ /** Answers each call from `respond` (the prompt's text and the system), or a fixed string. */
12
+ export function fakeModel(respond = "{}", id = "fake") {
13
+ return modelOf({
14
+ id,
15
+ provider: "fake",
16
+ takes: new Set(["text", "image", "video", "audio", "pdf"]),
17
+ async complete(req) {
18
+ const text = typeof respond === "string" ? respond : await respond(promptText(req.prompt) ?? "", req);
19
+ return reply(text, id);
20
+ },
21
+ });
22
+ }
23
+ /** Answers from a fixed list, in order; an object answer is sent as its JSON. Throws past the end. */
24
+ export function scriptedModel(answers, id = "scripted") {
25
+ let i = 0;
26
+ return fakeModel(() => {
27
+ const a = answers[i++];
28
+ if (a === undefined)
29
+ throw new Error(`scripted model has no answer ${i}`);
30
+ return typeof a === "string" ? a : JSON.stringify(a);
31
+ }, id);
32
+ }
33
+ //# sourceMappingURL=fake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake.js","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGjD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,KAAa,EAAS,EAAE,CAAC,CAAC;IACrD,IAAI;IACJ,KAAK,EAAE,EAAE;IACT,KAAK;IACL,IAAI,EAAE,UAAU;IAChB,EAAE,EAAE,CAAC;IACL,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;CACpB,CAAC,CAAC;AAEH,8FAA8F;AAC9F,MAAM,UAAU,SAAS,CACvB,UAAiF,IAAI,EACrF,EAAE,GAAG,MAAM;IAEX,OAAO,OAAO,CAAC;QACb,EAAE;QACF,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,KAAK,CAAC,QAAQ,CAAC,GAAG;YAChB,MAAM,IAAI,GACR,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;YAC3F,OAAO,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzB,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,aAAa,CAAC,OAA2B,EAAE,EAAE,GAAG,UAAU;IACxE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,SAAS,CAAC,GAAG,EAAE;QACpB,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;QAC1E,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC"}
package/dist/hear.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { type RotateOptions } from "./keys.js";
2
+ export interface Audio {
3
+ data: Uint8Array;
4
+ /** `audio/wav`, `audio/mpeg`. */
5
+ mediaType: string;
6
+ filename?: string;
7
+ }
8
+ export interface HearOptions {
9
+ /** ISO 639-1. Default `en`. */
10
+ language?: string;
11
+ /** The audio's length, when known: it prices the call. */
12
+ seconds?: number;
13
+ signal?: AbortSignal;
14
+ }
15
+ export interface Heard {
16
+ text: string;
17
+ /** The model that heard it. */
18
+ model: string;
19
+ ms: number;
20
+ /** 0 on a free key; absent when the model or the length has no price. */
21
+ usd?: number;
22
+ free?: boolean;
23
+ listUsd?: number;
24
+ }
25
+ export interface Ear {
26
+ /** As spends name it (`groq:whisper-large-v3-turbo`). */
27
+ readonly id: string;
28
+ readonly model: string;
29
+ hear(audio: Audio, o?: HearOptions): Promise<Heard>;
30
+ }
31
+ export interface EarOptions {
32
+ /** The full `/audio/transcriptions` URL. */
33
+ url: string;
34
+ model: string;
35
+ /** None for a server with no auth. */
36
+ key?: string | readonly string[];
37
+ /** The vendor, for the id. Default: the URL's host. */
38
+ vendor?: string;
39
+ free?: boolean;
40
+ /** Send `response_format=json` (OpenAI's field). Default true; Cohere's route takes no such field. */
41
+ jsonFormat?: boolean;
42
+ /** Bytes the vendor takes at most; a larger file throws before the call. */
43
+ maxBytes?: number;
44
+ limitMs?: number;
45
+ rotate?: RotateOptions;
46
+ fetch?: typeof fetch;
47
+ }
48
+ /** Any OpenAI-compatible transcription endpoint. Keys travel in a header, never in an error. */
49
+ export declare function compatibleEar(o: EarOptions): Ear;
50
+ export declare const COHERE_SPEECH_MODEL = "cohere-transcribe-03-2026";
51
+ /** Cohere's transcription on the free key ring. It takes 25 MB at most. */
52
+ export declare const cohereEar: (o: {
53
+ key: string | readonly string[];
54
+ rotate?: RotateOptions;
55
+ fetch?: typeof fetch;
56
+ }) => Ear;
57
+ /** Whisper on Groq's free key ring. */
58
+ export declare const groqEar: (o: {
59
+ key: string | readonly string[];
60
+ model?: string;
61
+ rotate?: RotateOptions;
62
+ fetch?: typeof fetch;
63
+ }) => Ear;