@volter/twin-cohere 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.
Files changed (40) hide show
  1. package/README.md +224 -0
  2. package/defaults/handlers.json +26 -0
  3. package/dist/defaults/handlers.json +26 -0
  4. package/dist/src/cli.d.ts +2 -0
  5. package/dist/src/cli.js +31 -0
  6. package/dist/src/cohere-budget.d.ts +55 -0
  7. package/dist/src/cohere-budget.js +171 -0
  8. package/dist/src/cohere-capabilities.d.ts +14 -0
  9. package/dist/src/cohere-capabilities.js +1852 -0
  10. package/dist/src/cohere-conformance.d.ts +17 -0
  11. package/dist/src/cohere-conformance.js +464 -0
  12. package/dist/src/cohere-connector.d.ts +150 -0
  13. package/dist/src/cohere-connector.js +625 -0
  14. package/dist/src/cohere-models.d.ts +21 -0
  15. package/dist/src/cohere-models.js +73 -0
  16. package/dist/src/cohere-scenario.d.ts +57 -0
  17. package/dist/src/cohere-scenario.js +176 -0
  18. package/dist/src/cohere-server.d.ts +16 -0
  19. package/dist/src/cohere-server.js +184 -0
  20. package/dist/src/cohere-stub.d.ts +119 -0
  21. package/dist/src/cohere-stub.js +321 -0
  22. package/dist/src/cohere-twin.d.ts +82 -0
  23. package/dist/src/cohere-twin.js +1243 -0
  24. package/dist/src/cohere-types.d.ts +226 -0
  25. package/dist/src/cohere-types.js +40 -0
  26. package/dist/src/index.d.ts +15 -0
  27. package/dist/src/index.js +84 -0
  28. package/package.json +71 -0
  29. package/src/cli.ts +30 -0
  30. package/src/cohere-budget.ts +197 -0
  31. package/src/cohere-capabilities.ts +1855 -0
  32. package/src/cohere-conformance.ts +489 -0
  33. package/src/cohere-connector.ts +709 -0
  34. package/src/cohere-models.ts +79 -0
  35. package/src/cohere-scenario.ts +194 -0
  36. package/src/cohere-server.ts +195 -0
  37. package/src/cohere-stub.ts +337 -0
  38. package/src/cohere-twin.ts +1290 -0
  39. package/src/cohere-types.ts +231 -0
  40. package/src/index.ts +159 -0
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @volter/twin-cohere
2
+
3
+ > **Legacy connector helpers:** this package still has callable helpers using the retired v1
4
+ > `syncPull` API. Those paths require migration before use on the current kernel;
5
+ > older helper descriptions below do not establish current compatibility. Check the
6
+ > [generated index](../../../generated/INDEX.md) for protocol standing and use the
7
+ > [shared model](../../../docs/concepts/the-model.md) for current state semantics.
8
+
9
+ A local, faithful **Cohere** twin your real `cohere-ai` client and your real `@ai-sdk/cohere`
10
+ provider talk to **unmodified** — point either at the twin and chat (v1 *and* v2), streaming, tool
11
+ calls, embeddings, rerank, classify, tokenize, the model catalog, datasets, connectors and embed
12
+ jobs all work. Built on the shared `@volter/world-core` kernel; see [the model](../../../docs/concepts/the-model.md) for storage and branching;
13
+ no parallel side store.
14
+
15
+ ```ts
16
+ import { CohereClientV2 } from 'cohere-ai';
17
+ import { createCohereTwinServer } from '@volter/twin-cohere';
18
+
19
+ const { port } = createCohereTwinServer({});
20
+ const client = new CohereClientV2({ token: 'cohere-twin', environment: `http://127.0.0.1:${port}` });
21
+
22
+ const res = await client.chat({
23
+ model: 'command-a-03-2025',
24
+ messages: [{ role: 'user', content: 'hello' }],
25
+ });
26
+ // res.message.content[0].text starts with "[twin-stub:…]" — deterministic, clearly labeled.
27
+ // res.finishReason === 'COMPLETE' — Cohere's UPPER-CASE reason, not OpenAI's 'stop'.
28
+ ```
29
+
30
+ …and the Vercel AI SDK provider, through its own `baseURL` option:
31
+
32
+ ```ts
33
+ import { createCohere } from '@ai-sdk/cohere';
34
+ const cohere = createCohere({ apiKey: 'cohere-twin', baseURL: `http://127.0.0.1:${port}/v2` });
35
+ ```
36
+
37
+ ## Cohere is its OWN dialect — the refusals are the point
38
+
39
+ Cohere is **not** OpenAI-compatible, and a twin built by copying an OpenAI-shaped pack's
40
+ permissiveness would be wrong in exactly the places that matter. What distinguishes it, all of it
41
+ modeled here and asserted by a named capability:
42
+
43
+ | | Cohere | the OpenAI-shaped mistake |
44
+ |---|---|---|
45
+ | error body | a **bare** `{ "message": "…" }` — one key | `{ error: { message, type, code } }` |
46
+ | finish reason | `COMPLETE` / `TOOL_CALL` / `MAX_TOKENS` / `STOP_SEQUENCE` / `ERROR` / `TIMEOUT` | `stop` / `tool_calls` |
47
+ | chat response | one `message`, one `finish_reason`, no `choices`, no `object`, no `created` | a `choices[]` array |
48
+ | usage | `usage.billed_units.*` **and** `usage.tokens.*` (v2); `meta.billed_units.*` (v1) | a flat `prompt_tokens` trio |
49
+ | v1 vs v2 | **two different protocols** on one host — v1 chat takes a `message` STRING and answers flat `text`; v2 takes `messages[]` | "v2 is v1 with a prefix" |
50
+ | embed | v2 **requires** `input_type`; v1 does not, but still refuses a v3/v4 model without one | one shape for both |
51
+ | embed response | v1 defaults to `embeddings_floats` (a flat `number[][]`); v2 always answers `embeddings_by_type` | always by-type |
52
+ | rerank | v2 `documents` must be **strings**; v1 accepts objects and has `return_documents` | one shape for both |
53
+ | enums | UPPER-CASE — `truncate: NONE\|START\|END`, `tool_choice: REQUIRED\|NONE`, `safety_mode: CONTEXTUAL\|STRICT\|OFF` | lower-case |
54
+ | `check-api-key` | a **POST**, despite the name | a GET |
55
+ | streaming | v2 is SSE terminated by `data: [DONE]`; v1 is newline-delimited JSON tagged `event_type` | one wire |
56
+
57
+ ## The generative-stub design (be honest)
58
+
59
+ The twin **cannot run the model** — there are no weights here. So every generative endpoint returns
60
+ a **deterministic STUB** that is unmistakably a twin stub (it carries a `[twin-stub:<model>]` marker
61
+ and echoes your prompt). It **never** pretends to be real model output, and that is an *invariant*
62
+ rather than a gap: a realism upgrade that reached for a real model, local or remote, would cost
63
+ serve-path determinism, which is the property the whole pack rests on.
64
+
65
+ What **is** faithful is the **entire protocol envelope**:
66
+
67
+ - the v2 response shape — `{ id, finish_reason, message:{role, content?, tool_plan?, tool_calls?}, usage:{billed_units,tokens} }`
68
+ - **v2 streaming** — `message-start` → `content-start` → `content-delta`× → `content-end` →
69
+ `message-end` → the literal `data: [DONE]`, plus the `tool-plan-delta` /
70
+ `tool-call-start` / `tool-call-delta` / `tool-call-end` sequence when tools fire. Note the
71
+ shapes DIFFER between `content-start` (a full content block) and `content-delta` (just `{text}`),
72
+ which is exactly what `@ai-sdk/cohere`'s discriminated union rejects if you get it wrong. Driven
73
+ through an injected sink — no real sockets in the handler.
74
+ - **v1 streaming** — the different wire: NDJSON objects tagged `event_type`
75
+ (`stream-start` / `text-generation` / `stream-end`), with no `[DONE]` sentinel.
76
+ - **tool calls** — `tool_calls[{id, type:'function', function:{name, arguments}}]` with a
77
+ `tool_plan` alongside, `finish_reason: 'TOOL_CALL'`, and `tool_choice: 'NONE'` genuinely
78
+ forbidding a call
79
+ - **all six `embedding_types`** — `float`, `int8`, `uint8`, `binary`, `ubinary`, `base64`, every one
80
+ a real view of the SAME vector (binary packs one BIT per dimension, 8 to a byte)
81
+ - **deterministic token counts**, and byte-identical responses on replay
82
+
83
+ The genuinely **stateful + static** surface is real, not a stub:
84
+
85
+ - `GET /v1/models` (+ `/v1/models/{name}`) — the real catalog (ids, context lengths and
86
+ `endpoints` read from Cohere's own models page), filterable by `endpoint`
87
+ - **Datasets** — create (name + type as query parameters, the file as multipart) / list (filtered by
88
+ `datasetType`, `validationStatus`, `limit`, `offset`) / retrieve / delete / `usage`, with Cohere's
89
+ closed 12-member `DatasetType` set enforced
90
+ - **Connectors** — create / list (with `total_count`) / retrieve / **partial** PATCH / delete, plus
91
+ `oauth/authorize`, which builds the redirect from the connector's OWN stored configuration and
92
+ **refuses** a connector that has none
93
+ - **Embed jobs** — create against a dataset that must actually exist, list, retrieve, cancel, with
94
+ the `processing → cancelling` state machine and a terminal job refusing a second cancel
95
+ - **The tokenizer vocabulary** — `POST /v1/tokenize` OBSERVES each (id → segment) pair into the
96
+ event log and `POST /v1/detokenize` folds that projection. This is what makes detokenize honest
97
+ rather than a fabrication: the twin can only detokenize ids it has genuinely issued, and refuses
98
+ the rest with a 400 instead of inventing text.
99
+
100
+ ## Coverage
101
+
102
+ **Partial and honest.** The capability manifest
103
+ (`src/cohere-capabilities.ts`) is the vendor's **real** surface as the denominator, enumerated
104
+ top-down from `cohere-ai@8.1.0`'s own operation inventory — every `client.<sub>.<method>` in its
105
+ `reference.md`, cross-checked against the `core.url.join(..., "<path>")` literal in each generated
106
+ client module.
107
+
108
+ **One unit, throughout:** those **45 SDK methods** resolve to **42 distinct (method, path)
109
+ operations** over 32 paths. The twin serves **27** of the 42 — the same 27 pairs that make up
110
+ `COHERE_ROUTER_SURFACE` and the conformance snapshot. The **15** it does not: `POST /v1/generate`,
111
+ `POST /v1/summarize`, `POST /v2/parse`, `POST /v2/audio/transcriptions`, the four `/v2/batches`
112
+ operations and the seven `/v1/finetuning/*` operations — every one enumerated as a `todo`, because a
113
+ denominator that omitted them would be a self-portrait rather than a measurement. Run
114
+ `bun scripts/manifest-baseline-one.ts cohere` for the current numbers.
115
+
116
+ **Wire-shape provenance.** Every response shape comes from the SDKs' own transcriptions, read from
117
+ their published npm tarballs during the build: `cohere-ai@8.1.0`'s
118
+ `serialization/**/*.d.ts` `interface Raw` blocks (which carry the **snake_case wire keys** — the TS
119
+ types are camelCase and would mislead), cross-checked against `@ai-sdk/cohere@4.0.35`'s zod schemas,
120
+ the stricter second transcription for chat/embed/rerank. Where the two disagree in strictness the
121
+ twin satisfies **both**, and `src/cohere-sdk.integration.test.ts` drives both clients unmodified to
122
+ prove it — including streaming through `cohere-ai`'s **own** SSE decoder and its typed error classes.
123
+
124
+ ### The stub is the answer, not a shortfall
125
+
126
+ Chat returns a deterministic, clearly-labeled stub; embed returns deterministic pseudo-vectors
127
+ seeded from the input hash; rerank scores with a deterministic lexical token-overlap heuristic; and
128
+ classify uses a deterministic per-(input, label) hash normalized so the confidences sum to 1. Those
129
+ **are** the twin's answers — the protocol envelope around each (shape, dimensionality, per-type
130
+ quantization, descending order, `[0,1]` range, search-unit billing, determinism) is faithful, and
131
+ only the learned meaning is absent. Every capability is `done` or `todo`.
132
+
133
+ Two filed `todo`s worth naming:
134
+
135
+ - **`cohere.tokenize.bpe_vocabulary`** — tokenize with Cohere's real published BPE vocabulary
136
+ (vendored tokenizer JSON) instead of the learned word-segment map. The tokenizers are published,
137
+ downloadable, static JSON, and this pack already serves their URLs.
138
+ - **`cohere.deployments.aws_planes`** — `cohere-ai` also ships `BedrockClient` / `SagemakerClient` /
139
+ `AwsClient`, which SigV4-sign requests to **AWS** hosts rather than `api.cohere.com`. The pack does
140
+ not claim those hosts today, so a regionally-deployed client fails closed rather than being
141
+ half-served.
142
+
143
+ ### No UI mirror
144
+
145
+ **The API is the product.** When someone does Cohere's core job they write code — chat, embed,
146
+ rerank and classify calls from an application — not open a browser. Cohere's dashboard is incidental
147
+ tooling for API keys, billing and usage graphs, the same shape as Anthropic's and OpenAI's consoles,
148
+ and its playground is a REPL over the same API this twin already serves rather than a place work is
149
+ stored. So this pack ships **no mirror** and declares **no UI capabilities**; coverage is API +
150
+ connector. (`ui-scope.json` records `needsUi: false` with this reasoning.)
151
+
152
+ ## Scripting behaviour (scenario handlers)
153
+
154
+ The default stub is generic. To script the exact deterministic answers a flow under test needs, put
155
+ MSW-shaped rules in the world directory's `handlers/cohere.json` — run by the **kernel's** one
156
+ scenario engine (`@volter/world-core` `scenario.ts`) with this pack's adapter, never a bespoke engine:
157
+
158
+ ```jsonc
159
+ {
160
+ "handlers": [
161
+ { "on": { "userTextIncludes": "refund", "hasTool": "create_job" },
162
+ "respond": { "toolPlan": "I will file it.", "toolCalls": { "name": "create_job", "arguments": { "title": "Refund order" } } },
163
+ "once": true },
164
+ { "on": { "userTextIncludes": "throttle" }, "respond": { "error": { "type": "rate_limit", "retryAfter": 5 } } }
165
+ ]
166
+ }
167
+ ```
168
+
169
+ `on` accepts `modelEquals`, `userTextIncludes`, `anyTextIncludes`, `hasTool`, `toolResultFor`,
170
+ `lastMessageIsToolResult` and the kernel's `nthCall`. `respond` accepts `text`, `thinking`,
171
+ `toolPlan`, `toolCalls`, `finishReason` (Cohere's UPPER-CASE set), or an `error` that becomes a
172
+ **real HTTP status** — a scripted `rate_limit` is a genuine 429 with a `retry-after` header, so
173
+ `cohere-ai` raises `Cohere.TooManyRequestsError` rather than handing you a 200 with an error-shaped
174
+ body. A malformed handlers file fails server startup **loudly**; it never silently falls back.
175
+
176
+ **Mind the SDK's own retries when scripting an error.** `cohere-ai` auto-retries `408`/`429`/`5xx`
177
+ **twice** (`requestWithRetries.js`, `DEFAULT_MAX_RETRIES = 2`) and *sleeps* for `retry-after`, so the
178
+ engine fires three times per caller-visible call and a large `retryAfter` stalls the caller for real
179
+ wall-clock seconds. Two corollaries: keep `retryAfter` small (the shipped defaults use `1`), and
180
+ treat `once: true` beside an `error` as a **footgun** — the SDK's retry gets a normal 200, silently
181
+ turning your scripted failure into a success.
182
+
183
+ The read doors are `GET /twin` (what this twin is and how to script it) and `GET /twin/scenario`
184
+ (active handlers, match counts, and the features of recent misses). Both are read-only: the handler
185
+ **file** is the only write surface.
186
+
187
+ ## CLI
188
+
189
+ ```bash
190
+ bun packages/twin/cohere/src/cli.ts serve [--port N] [--root DIR] [--read-only] [--scenario FILE]
191
+ bun packages/twin/cohere/src/cli.ts conformance [--root DIR]
192
+ ```
193
+
194
+ `--read-only` refuses every **POST/PATCH/DELETE** with a 405 while reads keep serving. The refusal is method-based, so a read-only Cohere world cannot chat or tokenize either — a defensible reading of "no POSTs", but not the same as "every mutation". `conformance` runs the
195
+ offline check: one real request per claimed endpoint asserting the outcome a live handler produces,
196
+ a two-way probe⇄claim bijection, and a router census pass that fails on served-but-unclaimed
197
+ surface.
198
+
199
+ ## Interception and world wiring
200
+
201
+ - **Hosts:** exactly one — `api.cohere.com`. That is the only entry in `cohere-ai`'s
202
+ `CohereEnvironment`, and `@ai-sdk/cohere`'s default `baseURL` is that host plus `/v2`. Declared on
203
+ the pack descriptor, so `volter-world` routes an unmodified SDK's traffic here with zero edits.
204
+ - **Endpoint env: none, and that is a ruling rather than an omission.** Neither SDK reads a base-URL
205
+ environment variable — `cohere-ai` takes the override as the `baseUrl`/`environment` **constructor**
206
+ option and reads only `CO_API_KEY` from the environment; `@ai-sdk/cohere` takes `options.baseURL`
207
+ and reads only `COHERE_API_KEY`. Inventing a `COHERE_BASE_URL` nothing reads would make
208
+ `volter-world covers` report a world covered while the app still talked to the real vendor.
209
+ - **Adoption:** both official clients (`cohere-ai`, `@ai-sdk/cohere`) and **both** credential stems
210
+ (`COHERE`, `CO`), because the two SDKs read different variables. No npm scope is claimed —
211
+ Cohere owns none, and `@ai-sdk/` belongs to Vercel and is shared by every provider.
212
+
213
+ ## Rate budget
214
+
215
+ Live calls (the connector only) go through one guarded client with a durable, fail-closed spend
216
+ ledger. Cohere publishes scalar per-minute limits **per endpoint and per key class**
217
+ (<https://docs.cohere.com/docs/rate-limits>, read 2026-08-31): chat 20/min on a trial key and 500 on
218
+ production, rerank 10/1,000, EmbedJob 5/50, tokenize 100/2,000, "default (other)" 500. The **trial**
219
+ numbers bind, and they are *below* the kernel's undeclared fallback — so this declaration is
220
+ deliberately **tighter** than the fallback rather than more permissive: 20 weighted units per 60s at
221
+ a default weight of 2, i.e. 10 calls a minute. The per-endpoint weights reproduce the vendor's own
222
+ scarcity (embed-jobs 8, rerank 4, chat/default 2, tokenize 1). The window bounds the 60-second
223
+ average and does **not** pace; the vendor's first `429`/`retry-after` becomes a persisted cooldown
224
+ that survives a restart.
@@ -0,0 +1,26 @@
1
+ {
2
+ "$comment": "Starter handlers copied into your world by `volter-world init` — YOURS now: edit or delete (regenerate a fresh world for fresh defaults). Grammar: {on, respond, once?, scope?, phase?}; `GET <twin-url>/twin` explains this twin; `GET <twin-url>/twin/scenario` shows active handlers + matches/misses (unmatched asks also ledger their features — `volter-world tail`). These match narrowly on the [twin-demo] tag so they never shadow your app's real asks. They script `POST /v2/chat` only; datasets, connectors and embed jobs are STATE — create those through the vendor's own API instead.",
3
+ "handlers": [
4
+ {
5
+ "id": "demo-answer",
6
+ "on": { "userTextIncludes": "[twin-demo] hello" },
7
+ "respond": { "text": "Hello from a scripted handler. This twin's chat completions are yours to script: edit handlers/cohere.json in this world directory." }
8
+ },
9
+ {
10
+ "id": "demo-tool-call",
11
+ "$comment": "A scripted tool call: fires once, only when the caller offers a create_job tool. Cohere sends a `tool_plan` alongside its tool calls, so the handler scripts one.",
12
+ "on": { "userTextIncludes": "[twin-demo] tool", "hasTool": "create_job" },
13
+ "once": true,
14
+ "respond": {
15
+ "toolPlan": "I will call create_job to handle this request.",
16
+ "toolCalls": { "name": "create_job", "arguments": { "title": "Demo job from a scripted handler", "instructions": "Replace me: edit handlers/cohere.json" } }
17
+ }
18
+ },
19
+ {
20
+ "id": "demo-rate-limit",
21
+ "$comment": "A scripted API FAILURE, so retry/backoff paths can be exercised without waiting for a real 429. This is a genuine HTTP 429 with a retry-after header \u2014 cohere-ai raises Cohere.TooManyRequestsError, not a 200 carrying an error body. MIND THE SDK'S OWN RETRIES: cohere-ai auto-retries 429/408/5xx twice (requestWithRetries.js, DEFAULT_MAX_RETRIES = 2) and SLEEPS for retry-after, so this handler costs the caller ~2s of real wall clock before the typed error surfaces \u2014 retryAfter is 1 rather than 5 for exactly that reason. Corollary: `once: true` beside an `error` is a FOOTGUN, because the SDK's retry gets a normal 200 and your scripted failure silently becomes a success.",
22
+ "on": { "userTextIncludes": "[twin-demo] throttle" },
23
+ "respond": { "error": { "type": "rate_limit", "retryAfter": 1 } }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$comment": "Starter handlers copied into your world by `volter-world init` — YOURS now: edit or delete (regenerate a fresh world for fresh defaults). Grammar: {on, respond, once?, scope?, phase?}; `GET <twin-url>/twin` explains this twin; `GET <twin-url>/twin/scenario` shows active handlers + matches/misses (unmatched asks also ledger their features — `volter-world tail`). These match narrowly on the [twin-demo] tag so they never shadow your app's real asks. They script `POST /v2/chat` only; datasets, connectors and embed jobs are STATE — create those through the vendor's own API instead.",
3
+ "handlers": [
4
+ {
5
+ "id": "demo-answer",
6
+ "on": { "userTextIncludes": "[twin-demo] hello" },
7
+ "respond": { "text": "Hello from a scripted handler. This twin's chat completions are yours to script: edit handlers/cohere.json in this world directory." }
8
+ },
9
+ {
10
+ "id": "demo-tool-call",
11
+ "$comment": "A scripted tool call: fires once, only when the caller offers a create_job tool. Cohere sends a `tool_plan` alongside its tool calls, so the handler scripts one.",
12
+ "on": { "userTextIncludes": "[twin-demo] tool", "hasTool": "create_job" },
13
+ "once": true,
14
+ "respond": {
15
+ "toolPlan": "I will call create_job to handle this request.",
16
+ "toolCalls": { "name": "create_job", "arguments": { "title": "Demo job from a scripted handler", "instructions": "Replace me: edit handlers/cohere.json" } }
17
+ }
18
+ },
19
+ {
20
+ "id": "demo-rate-limit",
21
+ "$comment": "A scripted API FAILURE, so retry/backoff paths can be exercised without waiting for a real 429. This is a genuine HTTP 429 with a retry-after header \u2014 cohere-ai raises Cohere.TooManyRequestsError, not a 200 carrying an error body. MIND THE SDK'S OWN RETRIES: cohere-ai auto-retries 429/408/5xx twice (requestWithRetries.js, DEFAULT_MAX_RETRIES = 2) and SLEEPS for retry-after, so this handler costs the caller ~2s of real wall clock before the typed error surfaces \u2014 retryAfter is 1 rather than 5 for exactly that reason. Corollary: `once: true` beside an `error` is a FOOTGUN, because the SDK's retry gets a normal 200 and your scripted failure silently becomes a success.",
22
+ "on": { "userTextIncludes": "[twin-demo] throttle" },
23
+ "respond": { "error": { "type": "rate_limit", "retryAfter": 1 } }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
3
+ // world-cohere CLI: serve the Cohere API twin or run conformance. Cohere is an API-first vendor —
4
+ // its dashboard is incidental tooling for keys, billing and usage, not where the work happens
5
+ // (docs/contributing/architecture.md C1b) — so this pack ships no mirror and there is no `mirror` command.
6
+ import { hasFlag, optionValue } from '@volter/world-core/args';
7
+ import { createCohereTwinServer } from "./cohere-server.js";
8
+ const [cmd, ...rest] = process.argv.slice(2);
9
+ const port = Number(optionValue(rest, '--port', '0')) || undefined;
10
+ const root = optionValue(rest, '--root') || undefined;
11
+ const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
12
+ const scenario = optionValue(rest, '--scenario') || undefined; // scripted chat turns (JSON handlers file)
13
+ if (cmd === 'serve') {
14
+ const s = await createCohereTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(scenario ? { scenarioPath: scenario } : {}) });
15
+ process.stdout.write(`cohere twin (v1 + v2; model output is a deterministic stub)${readOnly ? ' [read-only]' : ''}${scenario ? ` [scenario: ${scenario}]` : ''} at http://127.0.0.1:${s.port}\n`);
16
+ await keepProcessAlive();
17
+ }
18
+ else if (cmd === 'conformance') {
19
+ // dev-only; lazy so the bin runs without @volter/world-tooling in the runtime graph
20
+ const { checkCohereConformance } = await import("./cohere-conformance.js");
21
+ const report = await checkCohereConformance({ ...(root ? { root } : {}) });
22
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
23
+ if (!report.ok)
24
+ process.exitCode = 1;
25
+ }
26
+ else {
27
+ // NON-ZERO on an unknown command. `world-cohere bogus` exiting 0 makes a typo in a world's boot
28
+ // script look like a successful start (§9 round two, MINOR 10).
29
+ process.stdout.write('Usage: world-cohere serve|conformance [--port N] [--root DIR] [--read-only] [--scenario FILE]\n');
30
+ process.exitCode = 1;
31
+ }
@@ -0,0 +1,55 @@
1
+ import { RateBudget, type RateBudgetDeclaration, type RateBudgetOptions, type RateBudgetReservation, type RateBudgetSnapshot } from '@volter/world-core';
2
+ /** Rolling window, in ms. Spend older than this is pruned. */
3
+ export declare const COHERE_BUDGET_WINDOW_MS = 60000;
4
+ /**
5
+ * Weighted units allowed inside one window. 20/60s at the default weight of 2 is 10 calls a
6
+ * minute — HALF Cohere's tightest broadly-applicable published limit (chat, 20 req/min on a trial
7
+ * key) and tighter than the kernel's undeclared fallback, because Cohere's trial limits are below
8
+ * that fallback.
9
+ */
10
+ export declare const COHERE_BUDGET_CEILING = 20;
11
+ /** Seconds. A `retry-after` above this means the org is throttled hard — fail loudly, don't sleep. */
12
+ export declare const COHERE_BUDGET_MAX_RETRY_AFTER_S = 300;
13
+ /** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for the arithmetic. */
14
+ export declare const COHERE_CALL_WEIGHTS: {
15
+ /** `POST /v1/embed-jobs` — a 5 req/min trial allowance, the scarcest endpoint this pack drives. */
16
+ readonly embedJob: 8;
17
+ /** `POST /v2/rerank`, `POST /v1/rerank` — a 10 req/min trial allowance. */
18
+ readonly rerank: 4;
19
+ /** Chat and everything unclassified — the 20 req/min trial anchor. */
20
+ readonly other: 2;
21
+ /** `POST /v1/tokenize` — a 100 req/min trial allowance, the most generous endpoint modeled. */
22
+ readonly tokenize: 1;
23
+ };
24
+ /** THE PACK'S DECLARATION — pure data, the only Cohere-specific thing in the whole budget. */
25
+ export declare const COHERE_RATE_BUDGET: RateBudgetDeclaration;
26
+ /**
27
+ * Price one call. The key is `"<METHOD> <path>"` with the query string split off, so a rule can
28
+ * price by method (a write is not a read) without the kernel knowing anything about Cohere.
29
+ * An unclassified endpoint still costs `defaultWeight` — nothing is ever free.
30
+ */
31
+ export declare function cohereCallWeight(method: string, path: string): number;
32
+ /** Where Cohere's ledger lives. Token-keyed and cwd-independent by default (the limit is per API
33
+ * key, so a cwd-scoped ledger would hand the same key a fresh allowance in every checkout,
34
+ * worktree and CI matrix leg); pass `root` for world-scoped accounting. */
35
+ export declare function cohereBudgetPath(opts?: {
36
+ root?: string;
37
+ token?: string;
38
+ } | string): string;
39
+ /** Construction options for Cohere's budget. The vendor is fixed; everything else may only TIGHTEN. */
40
+ export type CohereBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
41
+ /**
42
+ * Cohere's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
43
+ * not an alias, so `budget instanceof CohereBudget` in `liveCohereExecute` means "a budget that
44
+ * accounts against COHERE's ledger under COHERE's ceiling": another vendor's `RateBudget` (with
45
+ * its own, possibly larger, ceiling) is NOT assignable there.
46
+ */
47
+ export declare class CohereBudget extends RateBudget {
48
+ constructor(opts?: CohereBudgetOptions);
49
+ }
50
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
51
+ * which one refused, and `err.kind` says why. */
52
+ export { RateBudgetError as CohereBudgetError } from '@volter/world-core';
53
+ export type { RateBudgetErrorKind as CohereBudgetErrorKind } from '@volter/world-core';
54
+ export type CohereBudgetReservation = RateBudgetReservation;
55
+ export type CohereBudgetSnapshot = RateBudgetSnapshot;
@@ -0,0 +1,171 @@
1
+ // Cohere's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
2
+ // bindings `liveCohereExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
3
+ // window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
4
+ // lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's
5
+ // header for the full rationale AND for the honest list of what the guard does not guarantee (an
6
+ // injected clock or ledger path still defeats it — it guards carelessness, not malice).
7
+ //
8
+ // ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────
9
+ // A real ~4.5-DAY vendor lockout (Figma, 2026-07-25) happened because raw API calls were made
10
+ // outside the pack's connector — no cache, no batching, no ceiling. Discipline only binds the code
11
+ // that follows it; a BUDGET binds the code that does not.
12
+ //
13
+ // ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
14
+ // Cohere DOES publish scalar per-minute limits, per endpoint AND per key class. Live-read from
15
+ // https://docs.cohere.com/docs/rate-limits on 2026-08-31:
16
+ // • Chat: TRIAL key 20 req/min across every Command model; PRODUCTION key 500 req/min for
17
+ // Command A / R+ / R / R7B (the newer A-variants are sales-gated).
18
+ // • Rerank: TRIAL 10 req/min; production 1,000 req/min.
19
+ // • EmbedJob: TRIAL 5 req/min; production 50 req/min.
20
+ // • Audio Transcriptions: TRIAL 5 req/min.
21
+ // • Tokenize: TRIAL 100 req/min; production 2,000 req/min.
22
+ // • Embed (text): 2,000 INPUTS/min on both key classes; Embed (images) 5 inputs/min trial.
23
+ // • Parse and "Default (other)": 500 req/min on both classes.
24
+ //
25
+ // THE TRIAL NUMBERS BIND, and they are LOWER than the kernel's undeclared fallback. That fallback
26
+ // (`DEFAULT_RATE_BUDGET`) is 60 units / 60s at defaultWeight 2 — 30 calls a minute — which already
27
+ // exceeds Cohere's 20/min trial chat limit and is 6x its 5/min EmbedJob limit. A pack must only be
28
+ // MORE PERMISSIVE than the fallback when the live first-party source justifies it; here the source
29
+ // justifies being TIGHTER, so this declaration is tighter.
30
+ //
31
+ // The ceiling is 20 weighted units per 60s. At the default weight of 2 that is 10 calls a minute:
32
+ // half of the tightest limit that applies to a plain call (chat, 20/min trial) and comfortably
33
+ // under the 5/min EmbedJob floor once that endpoint's own weight is applied.
34
+ //
35
+ // The window bounds the 60-second AVERAGE; it does NOT pace (the kernel refuses, it never sleeps).
36
+ // So the honest claim is "bounds the minute and converts the vendor's first 429/`retry-after` into
37
+ // a hard stop", not "refuses before the vendor ever 429s".
38
+ //
39
+ // ── HOW THE WEIGHTS WERE CHOSEN ─────────────────────────────────────────────────────────────
40
+ // Cohere counts REQUESTS per endpoint, and the per-endpoint limits differ by two orders of
41
+ // magnitude, so pricing by endpoint is reproducing the vendor's own scheme rather than proxying
42
+ // it. Each weight below is the fallback weight scaled by how much scarcer that endpoint's trial
43
+ // allowance is than chat's:
44
+ // • `POST /v1/embed-jobs` costs 8 — a 5/min trial allowance, four times scarcer than chat's 20.
45
+ // • `POST /v2/rerank` / `POST /v1/rerank` cost 4 — a 10/min trial allowance, twice as scarce.
46
+ // • `POST /v2/chat` / `POST /v1/chat` cost 2 (the default) — the 20/min anchor.
47
+ // • `POST /v1/tokenize` costs 1 — a 100/min trial allowance, five times more generous.
48
+ //
49
+ // Everything unclassified costs `defaultWeight`; nothing is ever free. WHICH published row binds an
50
+ // unclassified call is a deliberate choice worth stating, because the arithmetic is otherwise
51
+ // uncheckable against the source it cites (§9 round 1, m5): the endpoints this connector actually
52
+ // drives — `GET /v1/{datasets,connectors,embed-jobs}` and the connector CRUD — fall under Cohere's
53
+ // "Default (other)" row at 500/min, NOT under chat's 20/min. They are nonetheless priced at the
54
+ // CHAT anchor, i.e. 25x tighter than the vendor allows. That is deliberate: the per-endpoint
55
+ // figures for this set are not individually published, and the cost asymmetry is stark — a
56
+ // too-tight default costs a refused call plus a one-line declaration, a too-loose one costs days of
57
+ // lockout.
58
+ import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
59
+ const VENDOR = 'cohere';
60
+ /** Rolling window, in ms. Spend older than this is pruned. */
61
+ export const COHERE_BUDGET_WINDOW_MS = 60_000;
62
+ /**
63
+ * Weighted units allowed inside one window. 20/60s at the default weight of 2 is 10 calls a
64
+ * minute — HALF Cohere's tightest broadly-applicable published limit (chat, 20 req/min on a trial
65
+ * key) and tighter than the kernel's undeclared fallback, because Cohere's trial limits are below
66
+ * that fallback.
67
+ */
68
+ export const COHERE_BUDGET_CEILING = 20;
69
+ /** Seconds. A `retry-after` above this means the org is throttled hard — fail loudly, don't sleep. */
70
+ export const COHERE_BUDGET_MAX_RETRY_AFTER_S = 300;
71
+ /** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for the arithmetic. */
72
+ export const COHERE_CALL_WEIGHTS = {
73
+ /** `POST /v1/embed-jobs` — a 5 req/min trial allowance, the scarcest endpoint this pack drives. */
74
+ embedJob: 8,
75
+ /** `POST /v2/rerank`, `POST /v1/rerank` — a 10 req/min trial allowance. */
76
+ rerank: 4,
77
+ /** Chat and everything unclassified — the 20 req/min trial anchor. */
78
+ other: 2,
79
+ /** `POST /v1/tokenize` — a 100 req/min trial allowance, the most generous endpoint modeled. */
80
+ tokenize: 1,
81
+ };
82
+ /** THE PACK'S DECLARATION — pure data, the only Cohere-specific thing in the whole budget. */
83
+ export const COHERE_RATE_BUDGET = {
84
+ windowMs: COHERE_BUDGET_WINDOW_MS,
85
+ ceiling: COHERE_BUDGET_CEILING,
86
+ defaultWeight: COHERE_CALL_WEIGHTS.other,
87
+ maxRetryAfterSeconds: COHERE_BUDGET_MAX_RETRY_AFTER_S,
88
+ // Ordered, first match wins. Every pattern is ANCHORED at both ends so a longer path cannot
89
+ // borrow a cheaper endpoint's price.
90
+ rules: [
91
+ { match: '^POST /v1/embed-jobs$', weight: COHERE_CALL_WEIGHTS.embedJob },
92
+ { match: '^POST /v2/rerank$', weight: COHERE_CALL_WEIGHTS.rerank },
93
+ { match: '^POST /v1/rerank$', weight: COHERE_CALL_WEIGHTS.rerank },
94
+ { match: '^POST /v1/tokenize$', weight: COHERE_CALL_WEIGHTS.tokenize },
95
+ ],
96
+ reason: 'Cohere publishes scalar per-minute limits per endpoint AND per key class ' +
97
+ '(https://docs.cohere.com/docs/rate-limits, read 2026-08-31): chat 20 req/min on a TRIAL key ' +
98
+ 'and 500 on production; rerank 10 trial / 1,000 production; EmbedJob 5 trial / 50 production; ' +
99
+ 'tokenize 100 trial / 2,000 production; embed metered in inputs (2,000/min); "default (other)" ' +
100
+ '500. The TRIAL numbers bind, and they are BELOW the kernel fallback (30 calls/min), so this ' +
101
+ 'declaration is deliberately TIGHTER than the fallback rather than more permissive: 20 weighted ' +
102
+ 'units / 60s at defaultWeight 2 is 10 calls a minute, half the 20/min trial chat limit. Weights ' +
103
+ 'reproduce the vendor\'s own per-endpoint scarcity — embed-jobs 8 (5/min), rerank 4 (10/min), ' +
104
+ 'chat/default 2 (20/min), tokenize 1 (100/min). UNCLASSIFIED calls take the chat anchor rather ' +
105
+ 'than the "default (other)" row that actually governs them (the connector\'s own endpoints are ' +
106
+ 'in that 500/min bucket), i.e. 25x tighter than the vendor allows — deliberate, because those ' +
107
+ 'per-endpoint figures are not individually published and a too-tight default costs a refused ' +
108
+ 'call while a too-loose one costs days of lockout. The window bounds the 60s AVERAGE and does ' +
109
+ 'not pace; the 429 / retry-after cooldown is the backstop for a shorter enforcement horizon.',
110
+ };
111
+ // Declared at module load, so merely importing this module (which `cohere-connector.ts` does) is
112
+ // enough to arm the real ceiling. A budget constructed BEFORE this runs falls back to the kernel's
113
+ // DEFAULT_RATE_BUDGET, which here is strictly LOOSER (30 calls/min vs 10), so the ordering genuinely
114
+ // matters — constructing through the subclass below (which imports this module) is what makes it a
115
+ // non-issue in practice.
116
+ declareRateBudget(VENDOR, COHERE_RATE_BUDGET);
117
+ /**
118
+ * Price one call. The key is `"<METHOD> <path>"` with the query string split off, so a rule can
119
+ * price by method (a write is not a read) without the kernel knowing anything about Cohere.
120
+ * An unclassified endpoint still costs `defaultWeight` — nothing is ever free.
121
+ */
122
+ export function cohereCallWeight(method, path) {
123
+ const { bare, query } = splitQuery(path);
124
+ // UPPER-CASE the method: `fetch` normalizes a known lowercase method before sending, so
125
+ // `execute('post', …)` really does issue a POST and must be priced as one.
126
+ return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
127
+ }
128
+ /**
129
+ * `/v1/x?a=1` -> `{ bare: '/v1/x', query: { a: '1' } }`. Rules match the path; `whenQuery*` the
130
+ * query.
131
+ *
132
+ * NORMALIZED, because the anchored rules are otherwise trivially evaded: `fetch` upper-cases a
133
+ * known method before sending, so `execute('post', …)` issues a real WRITE that a `^POST ` rule
134
+ * would otherwise price as a read; and a trailing slash makes a path miss a `$` anchor while most
135
+ * routers treat it as the same endpoint. Both are input variations, not attacks, and either one
136
+ * silently voids the "scarce endpoints are priced up" claim the ceiling rests on.
137
+ */
138
+ function splitQuery(path) {
139
+ const at = path.indexOf('?');
140
+ const query = {};
141
+ if (at !== -1)
142
+ for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
143
+ query[k] = v;
144
+ const raw = at === -1 ? path : path.slice(0, at);
145
+ const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
146
+ return { bare, query };
147
+ }
148
+ /** Where Cohere's ledger lives. Token-keyed and cwd-independent by default (the limit is per API
149
+ * key, so a cwd-scoped ledger would hand the same key a fresh allowance in every checkout,
150
+ * worktree and CI matrix leg); pass `root` for world-scoped accounting. */
151
+ export function cohereBudgetPath(opts = {}) {
152
+ const o = typeof opts === 'string' ? { root: opts } : opts;
153
+ // VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
154
+ // excess-property check only catches object literals) must not redirect this pack's ledger to
155
+ // another vendor's file.
156
+ return rateBudgetPath({ ...o, vendor: VENDOR });
157
+ }
158
+ /**
159
+ * Cohere's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
160
+ * not an alias, so `budget instanceof CohereBudget` in `liveCohereExecute` means "a budget that
161
+ * accounts against COHERE's ledger under COHERE's ceiling": another vendor's `RateBudget` (with
162
+ * its own, possibly larger, ceiling) is NOT assignable there.
163
+ */
164
+ export class CohereBudget extends RateBudget {
165
+ constructor(opts = {}) {
166
+ super({ ...opts, vendor: VENDOR });
167
+ }
168
+ }
169
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
170
+ * which one refused, and `err.kind` says why. */
171
+ export { RateBudgetError as CohereBudgetError } from '@volter/world-core';
@@ -0,0 +1,14 @@
1
+ import { type CapabilityReport, type CapabilitySpec } from '@volter/world-tooling';
2
+ /**
3
+ * THE AREAS CENSUS (TWIN-87). Enumerated TOP-DOWN from cohere-ai@8.1.0's own client surface — the
4
+ * root client's method groups plus every sub-client directory under `api/resources/`
5
+ * (v2, batches, connectors, datasets, embedJobs, finetuning, models, audio) and the three
6
+ * deployment clients the package also ships (`AwsClient`, `BedrockClient`, `SagemakerClient`) —
7
+ * NOT from this manifest's own `area` values, which would make the bijection a tautology.
8
+ *
9
+ * `connectors` is Cohere's PRODUCT (its RAG connector registry); `connector` is this twin's
10
+ * pull/push plane. Two different things that unfortunately share a word; both are real areas.
11
+ */
12
+ export declare const COHERE_AREAS: readonly ["audio", "auth", "batches", "chat", "chat_v1", "classify", "conformance", "connector", "connectors", "datasets", "deployments", "determinism", "embed", "embed_jobs", "errors", "finetuning", "generate", "models", "parse", "rerank", "summarize", "tokenize"];
13
+ export declare const COHERE_CAPABILITIES: CapabilitySpec[];
14
+ export declare function cohereCapabilities(): Promise<CapabilityReport>;