@niscorp/signal 0.1.1 → 0.1.4
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 +53 -0
- package/DOCS.md +58 -7
- package/dist/index.cjs +11 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -58
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @niscorp/signal
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a8b7133: DOCS.md states thirty-nine facts about the API that were written only in source comments, and describes `resolveTransport`.
|
|
8
|
+
|
|
9
|
+
`step()` and `stepStream()` now have their request and result fields described: the shape of a tool-calling transcript, `output.accept` and `output.outputTool`, what `toolCalls[].args` holds, what `usage.reported: false` means, `outcome`, `wire.rung` and `wire.recovered`, and the `reasoning` and `tool_call_delta` events. A new subsection gives `resolveTransport(spec, capabilities)`: every field of the spec and of the result. Also added: what `UNMEASURED_MODEL` assumes, `chatProviderEntry`, that `describe()` makes no request, that an OpenRouter row lists what the proxy accepts, and that `jev-latest` is an alias.
|
|
10
|
+
|
|
11
|
+
No behaviour changed.
|
|
12
|
+
|
|
13
|
+
**What to change:** nothing.
|
|
14
|
+
|
|
15
|
+
## 0.1.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 63445cb: `complete()` and `stream()` on a decision provider say their own name.
|
|
20
|
+
|
|
21
|
+
Both run through `stepStream`, and the refusal a decision provider gives a chat verb named that instead: `signal.complete('…')` failed with `stepStream() is not available on a decision provider…` and `context.verb: "stepStream"`, for a call the caller never made.
|
|
22
|
+
|
|
23
|
+
The error now names the verb that was called — `complete() is not available on a decision provider — it answers decide() and generates no text`, `context.verb: "complete"`, and the same for `stream`. `step`, `stepStream` and `embed` already named themselves.
|
|
24
|
+
|
|
25
|
+
Only those two strings change. The code is `E_VERB_NOT_SUPPORTED` as before, it is raised at the same moment (for `stream()`, when the stream is first read), and no request is sent.
|
|
26
|
+
|
|
27
|
+
**What to change:** nothing.
|
|
28
|
+
|
|
29
|
+
- 8973c15: DOCS.md says what `embed()` and `meta.provider.raw` do. `embed()` does not check `supportsEmbedding`: the flag is what `describe()` reports, and the request is sent either way; a refused request is `E_PROVIDER_ERROR` with the provider's own error under `context.raw`. `meta.provider.raw` is `null` after `complete()` and `stream()`, which read the reply as a stream and keep no body (`step()` returns the body), and it is the provider's error for a call Signal recovered from a rejection. No code changed.
|
|
30
|
+
|
|
31
|
+
**What to change:** nothing.
|
|
32
|
+
|
|
33
|
+
- ce21cf7: DOCS.md says what a tool's `input` schema may be. The model is sent its JSON Schema, taken from the schema's output side, and the arguments are parsed with it once. So an `input` schema validates and does not convert:
|
|
34
|
+
- One with no JSON Schema (a `.transform()`, a `z.date()`) rejects the call before any request is made, with Zod's own error rather than a `SignalError`.
|
|
35
|
+
- One whose output is another kind than its input (`z.stringbool()`) is described to the model by its output: told `boolean`, a model that sends one gets `input_invalid` back, and it is the string that is accepted.
|
|
36
|
+
|
|
37
|
+
Convert inside `execute`. DOCS.md says this under "Defining Tools", and a test holds the first. No code changed.
|
|
38
|
+
|
|
39
|
+
**What to change:** nothing.
|
|
40
|
+
|
|
41
|
+
## 0.1.2
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- fe30458: Every package exports its `package.json`, so an app can say which version of a package it runs by reading it from the package:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import prism from '@niscorp/prism/package.json' with { type: 'json' };
|
|
49
|
+
prism.version; // the version that is installed, not the range that asked for it
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Until now the `exports` map hid it, and the only way to the version was a path into `node_modules`. `check:packages` installs the tarballs and reads every package's version this way.
|
|
53
|
+
|
|
54
|
+
**What to change:** nothing. An app that read a version by path can read it by name.
|
|
55
|
+
|
|
3
56
|
## 0.1.1
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
package/DOCS.md
CHANGED
|
@@ -37,7 +37,7 @@ Each provider has a capability profile that Signal uses to pick the right strate
|
|
|
37
37
|
- **`nativeJsonMode`** — Can it guarantee JSON output?
|
|
38
38
|
- **`toolsWithStructuredOutput`** — Is `response_format` accepted together with `tools` in one request?
|
|
39
39
|
- **`validatesToolArgs`** — Does the endpoint validate tool-call arguments server-side and reject the request on a mismatch?
|
|
40
|
-
- **`manglesNestedToolArgs`** — Does the model corrupt nested tool-call arguments?
|
|
40
|
+
- **`manglesNestedToolArgs`** — Does the model corrupt nested tool-call arguments? For example, a nested array arrives as a JSON string.
|
|
41
41
|
- **`multimodal`** — Does it accept image content?
|
|
42
42
|
- **`supportsEmbedding`** — Can it convert text to vectors?
|
|
43
43
|
|
|
@@ -152,14 +152,25 @@ does) joined with the model's registry row (what the MODEL does, as
|
|
|
152
152
|
measured by `scripts/probe-model.ts`). There is no override: a fact about
|
|
153
153
|
a model is a row in `src/registry.ts`. A model with no row resolves to the
|
|
154
154
|
conservative `UNMEASURED_MODEL`, and `describe().modelKnown` says so.
|
|
155
|
+
`UNMEASURED_MODEL` assumes the least: `nativeJsonSchema: false`,
|
|
156
|
+
`toolsWithStructuredOutput: false`, `manglesNestedToolArgs: true`,
|
|
157
|
+
`multimodal: false`.
|
|
155
158
|
A custom provider (by `baseUrl`) declares its own `capabilities`.
|
|
159
|
+
`chatProviderEntry(id)` returns a chat provider's registry row, and
|
|
160
|
+
`undefined` for an unknown id and for a decision provider.
|
|
156
161
|
```typescript
|
|
157
162
|
createSignal('groq').describe()
|
|
158
163
|
// { provider: 'groq', model: 'qwen/qwen3.8-27b', kind: 'chat', modelKnown: true, capabilities: {…} }
|
|
159
164
|
```
|
|
165
|
+
`describe()` makes no request and does not need an API key. For a custom
|
|
166
|
+
provider `modelKnown` is `false`, and `model` is `undefined` when none is set.
|
|
167
|
+
|
|
160
168
|
A `reasoningEffort` the model's row does not list is refused when the
|
|
161
169
|
client is built (`SignalError`, `E_VALIDATION_FAILED`), not by a provider 400
|
|
162
|
-
mid-run. An unmeasured model is not checked.
|
|
170
|
+
mid-run. An unmeasured model is not checked. On OpenRouter a row lists what
|
|
171
|
+
the proxy accepts, which is not what the routed model honours: it passes
|
|
172
|
+
`reasoning_effort` through to non-reasoning models too, so an accepted value
|
|
173
|
+
does not mean the model reasons.
|
|
163
174
|
|
|
164
175
|
### `.onRetry(handler)`
|
|
165
176
|
Hook called on each validation failure, including the last one that exhausts the retries.
|
|
@@ -221,6 +232,38 @@ Schema validation happens at the end of the stream; when the retries are spent t
|
|
|
221
232
|
|
|
222
233
|
The low-level primitives `complete()` and `stream()` are built on: one model call, no tool execution, no schema retries. The caller owns the loop. `step()` takes a `StepRequest` (`messages`, `tools`, `toolChoice`, `output`, `responseFormat`, `options`) and returns a `StepResult` (`content`, `toolCalls`, `usage`, `finishReason`, `raw`, and — when the request carried `output.accept` — the routed `outcome` and `wire` report). `stepStream()` yields `text`, `reasoning` and `tool_call_delta` events and a final `done` carrying the `StepResult`.
|
|
223
234
|
|
|
235
|
+
- **`messages`** — an assistant turn carries the calls it made as `toolCalls` (`{ id, name, args }`, `args` a JSON string) and has `content: ''` when it only called tools. Each result goes back as a `tool` message whose `toolCallId` is that call's `id`.
|
|
236
|
+
- **`tools`** — each is `{ name, description, parameters }`; `parameters` is the JSON Schema of the tool's input (`z.toJSONSchema()` of a Zod schema).
|
|
237
|
+
- **`output.accept`** — gates and does not transform: `outcome.value` is the accepted candidate itself, not the schema's parsed output, so a transform or a default in the schema is not applied.
|
|
238
|
+
- **`output.outputTool`** — the name of an exit tool. A call to it is routed as output, never as a tool call.
|
|
239
|
+
- **`responseFormat`** — passed to the provider as given. `step()` does not check it against the capabilities: with `tools` on a model without `toolsWithStructuredOutput`, the provider rejects the request.
|
|
240
|
+
- **`toolCalls`** — a call's `args` is the parsed JSON value, or the provider's string when it does not parse. It is not checked against the tool's `parameters`.
|
|
241
|
+
- **`usage`** — `reported: false` means the provider sent no usage and the numbers are an estimate (about 4 characters per token). A stream does not reliably carry its usage.
|
|
242
|
+
- **`outcome`** — also present, without `output.accept`, on a result recovered from a provider rejection.
|
|
243
|
+
- **`wire`** — `rung` names what produced the accepted output: `parse` for content that was valid JSON as it arrived, `call-args` for a call's arguments, otherwise the repair it took. `recovered` is set when the result was recovered from a provider rejection.
|
|
244
|
+
- **`reasoning` events** — the model's thinking, never part of `content`. Only a provider that streams its reasoning produces them; on Groq and OpenRouter Signal asks for it only when `reasoningEffort` is set.
|
|
245
|
+
- **`tool_call_delta` events** — each carries one fragment of a call's arguments (`argsText`) for the call at `index`. The assembled calls are in the `done` event's `toolCalls`.
|
|
246
|
+
|
|
247
|
+
### `resolveTransport(spec, capabilities)`
|
|
248
|
+
|
|
249
|
+
For a caller that runs its own loop over `step()`: resolves how an output contract travels — `respond` (the arguments of an exit tool), `native` (`response_format`) or `emit` (message content) — and returns what to put on each request. The `spec`:
|
|
250
|
+
|
|
251
|
+
- **`wire`** — the full schema of the output contract. It is the `response_format` under `native` and the exit tool's params under `respond`. An endpoint with `validatesToolArgs` is never sent it as tool params.
|
|
252
|
+
- **`looseWire`** — the same contract with the payload left untyped. It replaces `wire` as the exit tool's params when the contract has `data` and `wire` has no JSON Schema, has a `$ref` in it, or is longer than 8,192 characters as one.
|
|
253
|
+
- **`hasData`** — whether the contract carries a typed payload (a `data` field).
|
|
254
|
+
- **`forceTool`** — every turn must be a tool call: under `respond` the result carries `toolChoice: 'required'`. It makes `auto` resolve to `respond` where it would resolve to `emit`, and throws `E_VALIDATION_FAILED` with an explicit `emit`.
|
|
255
|
+
|
|
256
|
+
The result:
|
|
257
|
+
|
|
258
|
+
- **`respondDetail`** — under `respond`, how much of the contract the exit tool's params carry: `full` (`wire`), `loose` (`looseWire`) or `permissive` (the endpoint validates tool args, so the params name the envelope fields and constrain nothing).
|
|
259
|
+
- **`respondDescriptor`** — under `respond`, the exit tool to append to the request's `tools`.
|
|
260
|
+
- **`outputToolName`** — under `respond`, the exit tool's name (`RESPOND_TOOL_NAME`). Pass it as `output.outputTool`; the `tool_call_delta` events of a call with that name carry the output as it arrives.
|
|
261
|
+
- **`responseFormat`** — under `native`, the `responseFormat` to send on every request.
|
|
262
|
+
- **`toolChoice`** — `'required'` under `respond` with `forceTool`.
|
|
263
|
+
- **`injectSchemaDoc`** — `true` when the contract has `data` and the transport does not carry its full schema (`emit`, and `respond` with `loose` or `permissive` params): the caller adds its own schema documentation to the prompt.
|
|
264
|
+
- **`finishProtocol`** — the text that tells the model how the run ends under this transport. Add it unchanged as a system message, after any schema documentation.
|
|
265
|
+
- **`corrections`** — the messages to send back when a turn did not finish (`termination`) or its output was invalid (`invalidOutput`). The caller replaces `{issues}` in `invalidOutput` with the evidence.
|
|
266
|
+
|
|
224
267
|
### `.count(input)`
|
|
225
268
|
|
|
226
269
|
Rough token count for a string or a message list. A heuristic (about 4 characters per token), not a tokenizer.
|
|
@@ -254,12 +297,14 @@ type SignalMeta = {
|
|
|
254
297
|
retries: number; // validation retries that occurred
|
|
255
298
|
toolCalls: ToolCallRecord[]; // tools called, args, results, timing
|
|
256
299
|
provider: {
|
|
257
|
-
raw: unknown; //
|
|
300
|
+
raw: unknown; // null: these calls read the reply as a stream, which leaves no body to keep (an array, one entry per call, when the run took several)
|
|
258
301
|
errors: ProviderError[]; // currently always empty
|
|
259
302
|
};
|
|
260
303
|
};
|
|
261
304
|
```
|
|
262
305
|
|
|
306
|
+
`step()` returns the provider's response body as its `raw`. Where a provider rejected a call and Signal recovered the attempt from the rejection, that call's `raw` is the provider's error.
|
|
307
|
+
|
|
263
308
|
---
|
|
264
309
|
|
|
265
310
|
## Structured Output
|
|
@@ -284,7 +329,7 @@ const { response } = await createSignal('groq')
|
|
|
284
329
|
|
|
285
330
|
1. Zod schema is converted to JSON Schema (draft-07)
|
|
286
331
|
2. Based on capabilities:
|
|
287
|
-
- `nativeJsonSchema` (and, when tools are set, `toolsWithStructuredOutput`) → sends JSON Schema as `response_format`
|
|
332
|
+
- `nativeJsonSchema` (and, when tools are set, `toolsWithStructuredOutput`) → sends JSON Schema as `response_format`, with `strict: false`
|
|
288
333
|
- otherwise `nativeJsonMode` and no tools → sends `response_format: json_object` + schema in a system message
|
|
289
334
|
- otherwise → schema in a system message only
|
|
290
335
|
3. The response is normalized by the wire layer (JSON extraction and repairs, each counted only if the result passes the schema)
|
|
@@ -318,6 +363,8 @@ const searchTool = defineTool({
|
|
|
318
363
|
|
|
319
364
|
The Zod `input` schema validates the model's arguments before `execute` runs. The return value is stringified and sent back to the model. A call to an unknown tool, arguments that fail the schema, and an `execute` that throws do not fail the run: each goes back to the model as an `error: …` tool result (and is recorded in `meta.toolCalls`).
|
|
320
365
|
|
|
366
|
+
The model is sent the `input` schema as JSON Schema, taken from the schema's output side, so an `input` schema validates and does not convert. One with no JSON Schema (a `.transform()`, a `z.date()`) rejects the call before any request is made, with Zod's own error (`Transforms cannot be represented in JSON Schema`) rather than a `SignalError`. One whose output is another kind than its input (`z.stringbool()`) is described to the model by its output: told `boolean`, a model that sends one gets `input_invalid` back, and it is the string (`"true"`) that is accepted. Convert inside `execute`.
|
|
367
|
+
|
|
321
368
|
### Using Tools
|
|
322
369
|
|
|
323
370
|
```typescript
|
|
@@ -420,7 +467,7 @@ const similarity = cosine(a, b);
|
|
|
420
467
|
|
|
421
468
|
### Provider Support
|
|
422
469
|
|
|
423
|
-
|
|
470
|
+
`embed()` does not check `supportsEmbedding`: the flag is what `describe()` reports, and the request goes to the provider's embeddings endpoint either way. Of the known providers only **OpenAI** is marked as having one (`text-embedding-3-small`, `text-embedding-3-large`). An endpoint that refuses the request surfaces as `E_PROVIDER_ERROR` (`Embedding error: …`, the provider's own error under `context.raw`). A decision provider and the two adapters that are not implemented fail before any request is made.
|
|
424
471
|
|
|
425
472
|
Use a separate Signal client for embedding — embedding models are different from chat models:
|
|
426
473
|
|
|
@@ -462,7 +509,7 @@ result.decisions.urgency.level; // 0 | 1 | 2 — index into criteria
|
|
|
462
509
|
|
|
463
510
|
You declare the questions once and never write an output type. Signal derives both halves from them:
|
|
464
511
|
|
|
465
|
-
- **The result type.** `choice` → the union of the `criteria` keys; `score` → a level index into `criteria`; `noul` → a boolean.
|
|
512
|
+
- **The result type.** `choice` → the union of the `criteria` keys; `score` → a level index into `criteria`; `noul` → a boolean. A `score` question's `criteria` lists its levels lowest first.
|
|
466
513
|
- **The acceptance gate.** The same derivation at runtime: a choice outside the asked options, a question left unanswered, a distribution over other keys, a probability outside [0, 1] — each fails the call with `E_VALIDATION_FAILED` and the provider's body under `context.raw`.
|
|
467
514
|
|
|
468
515
|
There is no repair and no correction retry. A decision model writes no bytes to repair and cannot be told what it got wrong.
|
|
@@ -481,6 +528,8 @@ if (result.calibrated) {
|
|
|
481
528
|
}
|
|
482
529
|
```
|
|
483
530
|
|
|
531
|
+
On a decision provider `level` is the most probable level and `answer` is `noul >= 0.5`.
|
|
532
|
+
|
|
484
533
|
On a chat provider `decide()` still works: the same questions run through structured output (native grammar where it exists, the repair ladder, correction retries) and come back as picks alone with `calibrated: false`. Signal does not invent a `1.0` — it would clear every threshold. Emulation is one to two orders of magnitude slower; a caller on a latency budget checks first:
|
|
485
534
|
|
|
486
535
|
```typescript
|
|
@@ -499,6 +548,8 @@ controller.abort(); // tears down the HTTP request
|
|
|
499
548
|
|
|
500
549
|
A provider is one of two kinds. A **decision provider** (`typesafe`) has `decide()` and nothing else — `complete`, `stream`, `step`, `stepStream` and `embed` fail with `E_VERB_NOT_SUPPORTED` before any request is built. A **chat provider** has every verb, `decide()` included, by emulation.
|
|
501
550
|
|
|
551
|
+
`typesafe`'s default model `jev-latest` is an alias for the provider's current version; `meta.model` is the version the provider reports having answered. A caller that records calibration against a version pins it with `.model('jev-1.13.0')`.
|
|
552
|
+
|
|
502
553
|
A decision provider by base URL — self-hosted, or the fake a check runs — names its protocol:
|
|
503
554
|
|
|
504
555
|
```typescript
|
|
@@ -521,7 +572,7 @@ All errors are `SignalError` instances with a `.code` and optional `.context`:
|
|
|
521
572
|
| `E_MISSING_SDK` | Provider SDK not installed |
|
|
522
573
|
| `E_VALIDATION_FAILED` | Response failed Zod validation after all retries; a `decide()` answer failed its gate; a `reasoningEffort` the model does not take |
|
|
523
574
|
| `E_MAX_ITERATIONS` | The tool loop did not finish within its turn limit |
|
|
524
|
-
| `E_PROVIDER_ERROR` | Provider API error (rate limit, server error, etc.); tools
|
|
575
|
+
| `E_PROVIDER_ERROR` | Provider API error (rate limit, server error, a refused `embed()`, etc.); tools on a provider without `nativeTools`; an adapter that is not implemented |
|
|
525
576
|
| `E_MAX_RETRIES`, `E_TOOL_NOT_FOUND`, `E_TOOL_EXECUTION`, `E_TOOL_VALIDATION` | Declared in `ErrorCode`, not currently thrown — tool failures go back to the model as tool results, and exhausted retries are `E_VALIDATION_FAILED` |
|
|
526
577
|
| `E_VERB_NOT_SUPPORTED` | A chat verb was called on a decision provider |
|
|
527
578
|
|
package/dist/index.cjs
CHANGED
|
@@ -52,17 +52,11 @@ var providerRegistry = {
|
|
|
52
52
|
endpoint: {
|
|
53
53
|
nativeTools: true,
|
|
54
54
|
nativeJsonMode: true,
|
|
55
|
-
// Groq validates tool args server-side (tool_use_failed 400s).
|
|
56
55
|
validatesToolArgs: true,
|
|
57
56
|
supportsEmbedding: false
|
|
58
57
|
},
|
|
59
58
|
adapter: "openai-compatible",
|
|
60
|
-
// tool_use_failed / json_validate_failed 400s carry the model's
|
|
61
|
-
// attempt in failed_generation — recover and route it.
|
|
62
59
|
wire: ["failed-generation"],
|
|
63
|
-
// Reasoning models stream their trace only when asked to parse it out;
|
|
64
|
-
// `reasoning_format` is rejected on the non-reasoning ones, which is why
|
|
65
|
-
// this is opt-in (see reasoningRequest above).
|
|
66
60
|
reasoningRequest: { reasoning_format: "parsed" }
|
|
67
61
|
},
|
|
68
62
|
openai: {
|
|
@@ -77,8 +71,6 @@ var providerRegistry = {
|
|
|
77
71
|
supportsEmbedding: true
|
|
78
72
|
},
|
|
79
73
|
adapter: "openai-compatible",
|
|
80
|
-
// 4o-era models occasionally emit JSONL where one value was asked
|
|
81
|
-
// for; the acceptance gate decides, so this can never misfire.
|
|
82
74
|
wire: ["jsonl-lines"]
|
|
83
75
|
},
|
|
84
76
|
openrouter: {
|
|
@@ -93,8 +85,6 @@ var providerRegistry = {
|
|
|
93
85
|
supportsEmbedding: false
|
|
94
86
|
},
|
|
95
87
|
adapter: "openai-compatible",
|
|
96
|
-
// OpenRouter unifies reasoning across the models it routes; `enabled` turns
|
|
97
|
-
// the trace on for those that have one and is ignored by those that don't.
|
|
98
88
|
reasoningRequest: { reasoning: { enabled: true } }
|
|
99
89
|
},
|
|
100
90
|
anthropic: {
|
|
@@ -127,97 +117,78 @@ var providerRegistry = {
|
|
|
127
117
|
id: "typesafe",
|
|
128
118
|
baseUrl: "https://api.typesafe.ai/v1",
|
|
129
119
|
envKey: "TYPESAFE_API_KEY",
|
|
130
|
-
// The moving alias. A caller that records calibration against a model pins
|
|
131
|
-
// a version (`jev-1.13.0`) with `.model()`.
|
|
132
120
|
defaultModel: "jev-latest",
|
|
133
121
|
adapter: "systemone"
|
|
134
122
|
}
|
|
135
123
|
};
|
|
136
124
|
var modelRegistry = {
|
|
137
|
-
// ── groq ──
|
|
138
125
|
"groq/qwen/qwen3.8-27b": {
|
|
139
126
|
capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: true },
|
|
140
|
-
// nested: 3 of 20 trials failed
|
|
141
127
|
reasoningEfforts: ["none", "default", "low", "medium", "high"],
|
|
142
128
|
verified: "2026-09-24"
|
|
143
129
|
},
|
|
144
130
|
"groq/openai/gpt-oss-120b": {
|
|
145
131
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: false },
|
|
146
|
-
// nested: 1 of 20 trials failed
|
|
147
132
|
reasoningEfforts: ["low", "medium", "high"],
|
|
148
133
|
verified: "2026-09-24"
|
|
149
134
|
},
|
|
150
135
|
"groq/openai/gpt-oss-20b": {
|
|
151
136
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: false },
|
|
152
|
-
// nested: 6 of 20 trials failed
|
|
153
137
|
reasoningEfforts: ["low", "medium", "high"],
|
|
154
138
|
verified: "2026-09-24"
|
|
155
139
|
},
|
|
156
|
-
// ── openai ──
|
|
157
140
|
"openai/gpt-4o": {
|
|
158
141
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
159
|
-
// nested: 0 of 20 trials failed
|
|
160
142
|
reasoningEfforts: [],
|
|
161
143
|
verified: "2026-09-24"
|
|
162
144
|
},
|
|
163
145
|
"openai/gpt-4o-mini": {
|
|
164
146
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
165
|
-
// nested: 0 of 20 trials failed
|
|
166
147
|
reasoningEfforts: [],
|
|
167
148
|
verified: "2026-09-24"
|
|
168
149
|
},
|
|
169
150
|
"openai/gpt-4.1-mini": {
|
|
170
151
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
171
|
-
// nested: 0 of 20 trials failed
|
|
172
152
|
reasoningEfforts: [],
|
|
173
153
|
verified: "2026-09-24"
|
|
174
154
|
},
|
|
175
|
-
// ── openrouter ──
|
|
176
155
|
"openrouter/qwen/qwen3.8-27b": {
|
|
177
156
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
178
|
-
// nested: 0 of 20 trials failed
|
|
179
157
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
180
158
|
verified: "2026-09-24"
|
|
181
159
|
},
|
|
182
160
|
"openrouter/openai/gpt-4o": {
|
|
183
161
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
184
|
-
// nested: 0 of 20 trials failed
|
|
185
162
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
186
163
|
verified: "2026-09-24"
|
|
187
164
|
},
|
|
188
165
|
"openrouter/openai/gpt-4o-mini": {
|
|
189
166
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
190
|
-
// nested: 0 of 20 trials failed
|
|
191
167
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
192
168
|
verified: "2026-09-24"
|
|
193
169
|
},
|
|
194
170
|
"openrouter/openai/gpt-oss-120b": {
|
|
195
171
|
capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: false },
|
|
196
|
-
// nested: 0 of 20 trials failed
|
|
197
172
|
reasoningEfforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
198
173
|
verified: "2026-09-24"
|
|
199
174
|
},
|
|
200
175
|
"openrouter/z-ai/glm-5.2": {
|
|
201
176
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: true, multimodal: false },
|
|
202
|
-
// nested: 1 of 20 trials failed
|
|
203
177
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
204
178
|
verified: "2026-09-24"
|
|
205
179
|
},
|
|
206
180
|
"openrouter/openai/gpt-5.6-luna": {
|
|
207
181
|
capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
|
|
208
|
-
// nested: 0 of 20 trials failed
|
|
209
182
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
210
183
|
verified: "2026-09-24"
|
|
211
184
|
},
|
|
212
185
|
"openrouter/openai/gpt-oss-20b": {
|
|
213
186
|
capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: false },
|
|
214
|
-
// nested: 0 of 20 trials failed
|
|
215
187
|
reasoningEfforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
216
188
|
verified: "2026-09-24"
|
|
217
189
|
},
|
|
218
190
|
"openrouter/meta-llama/llama-3.3-70b-instruct": {
|
|
219
191
|
capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: true, multimodal: false },
|
|
220
|
-
// nested: 9 of 20 trials failed
|
|
221
192
|
reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
222
193
|
verified: "2026-09-24"
|
|
223
194
|
}
|
|
@@ -381,9 +352,8 @@ var createOpenAICompatibleAdapter = async (config) => {
|
|
|
381
352
|
async function* chatStream(request, options) {
|
|
382
353
|
const streamParams = {
|
|
383
354
|
...buildParams(request),
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
// the non-stream `chat` path never sees these, so its content is unmoved.
|
|
355
|
+
// Only when the caller set reasoningEffort: Groq answers `reasoning_format` with a 400
|
|
356
|
+
// on its non-reasoning models.
|
|
387
357
|
...request.options?.reasoningEffort !== void 0 && config.reasoningRequest !== void 0 ? config.reasoningRequest : {},
|
|
388
358
|
stream: true,
|
|
389
359
|
stream_options: { include_usage: true }
|
|
@@ -709,8 +679,6 @@ async function* executeStepStream(config) {
|
|
|
709
679
|
contentBuffer += delta.text;
|
|
710
680
|
yield { type: "text", text: delta.text };
|
|
711
681
|
break;
|
|
712
|
-
// Reasoning is passed straight through and deliberately NOT added to
|
|
713
|
-
// contentBuffer: it is the model thinking, not its answer.
|
|
714
682
|
case "reasoning":
|
|
715
683
|
yield { type: "reasoning", text: delta.text };
|
|
716
684
|
break;
|
|
@@ -1389,10 +1357,7 @@ var buildMessages = (config, input) => {
|
|
|
1389
1357
|
var recoveredStepResult = (recovered, routed, raw, sent) => ({
|
|
1390
1358
|
content: "",
|
|
1391
1359
|
toolCalls: routed.outcome.kind === "tool_calls" ? routed.outcome.calls : [],
|
|
1392
|
-
//
|
|
1393
|
-
// billed for it — and the 400 body carries none. The prompt is known exactly
|
|
1394
|
-
// and the model's attempt is in the rejection, so this is countable rather
|
|
1395
|
-
// than lost. Marked unreported, because it is a reckoning.
|
|
1360
|
+
// The provider bills a rejected generation and its 400 carries no usage.
|
|
1396
1361
|
usage: estimateUsage(sent, recovered.rejection.argsText, routed.outcome.kind === "tool_calls" ? routed.outcome.calls : []),
|
|
1397
1362
|
finishReason: "error_recovered",
|
|
1398
1363
|
raw,
|
|
@@ -1447,10 +1412,6 @@ var createSignalFromConfig = (config) => {
|
|
|
1447
1412
|
...providerTools && { tools: providerTools },
|
|
1448
1413
|
...request.toolChoice !== void 0 && { toolChoice: request.toolChoice },
|
|
1449
1414
|
...request.responseFormat !== void 0 && { responseFormat: request.responseFormat },
|
|
1450
|
-
// The client's own options are the floor; a request may override them
|
|
1451
|
-
// field by field. `complete`/`stream` already run on config.options —
|
|
1452
|
-
// step used to drop them, so a configured client behaved differently
|
|
1453
|
-
// depending on which door the caller came through.
|
|
1454
1415
|
...(config.options !== void 0 || request.options !== void 0) && {
|
|
1455
1416
|
options: { ...config.options, ...request.options }
|
|
1456
1417
|
}
|
|
@@ -1485,8 +1446,7 @@ var createSignalFromConfig = (config) => {
|
|
|
1485
1446
|
const result = {
|
|
1486
1447
|
content: response.content,
|
|
1487
1448
|
toolCalls,
|
|
1488
|
-
//
|
|
1489
|
-
// zero rather than a missing frame.
|
|
1449
|
+
// A non-streaming body always carries usage, so a zero here is a reported zero.
|
|
1490
1450
|
usage: { ...response.usage, reported: true },
|
|
1491
1451
|
finishReason: response.finishReason,
|
|
1492
1452
|
raw: response.raw
|
|
@@ -1502,9 +1462,9 @@ var createSignalFromConfig = (config) => {
|
|
|
1502
1462
|
});
|
|
1503
1463
|
return { ...result, outcome: routed.outcome, wire: routed.wire };
|
|
1504
1464
|
};
|
|
1505
|
-
const
|
|
1465
|
+
const stepStreamAs = (verb) => (request, streamOptions) => {
|
|
1506
1466
|
const run = async function* () {
|
|
1507
|
-
const adapter = await getChatAdapter(
|
|
1467
|
+
const adapter = await getChatAdapter(verb);
|
|
1508
1468
|
const resolved = resolveProvider(config);
|
|
1509
1469
|
const declared = new Set((request.tools ?? []).map((tool) => tool.name));
|
|
1510
1470
|
const merged = config.options !== void 0 || request.options !== void 0 ? { ...request, options: { ...config.options, ...request.options } } : request;
|
|
@@ -1544,6 +1504,7 @@ var createSignalFromConfig = (config) => {
|
|
|
1544
1504
|
};
|
|
1545
1505
|
return run();
|
|
1546
1506
|
};
|
|
1507
|
+
const stepStream = stepStreamAs("stepStream");
|
|
1547
1508
|
const decideCalibrated = async (adapter, model, request, started) => {
|
|
1548
1509
|
const response = await adapter.decide(
|
|
1549
1510
|
{ model, state: request.state, questions: request.questions },
|
|
@@ -1581,8 +1542,7 @@ var createSignalFromConfig = (config) => {
|
|
|
1581
1542
|
decisions: acceptUncalibrated(request.questions, result.response, result.meta.provider.raw),
|
|
1582
1543
|
meta: {
|
|
1583
1544
|
model: result.meta.model,
|
|
1584
|
-
// complete()
|
|
1585
|
-
// and does not say which, so this total cannot be vouched for as measured.
|
|
1545
|
+
// complete() adds reported usage to estimated usage and does not say which.
|
|
1586
1546
|
usage: { ...result.meta.usage, reported: false },
|
|
1587
1547
|
durationMs: Date.now() - started,
|
|
1588
1548
|
provider: { raw: result.meta.provider.raw }
|
|
@@ -1590,7 +1550,6 @@ var createSignalFromConfig = (config) => {
|
|
|
1590
1550
|
};
|
|
1591
1551
|
};
|
|
1592
1552
|
return {
|
|
1593
|
-
// Builder methods
|
|
1594
1553
|
apiKey: (key) => fork({ apiKey: key }),
|
|
1595
1554
|
model: (m) => fork({ model: m }),
|
|
1596
1555
|
systemPrompt: (prompt) => fork({ systemPrompt: prompt }),
|
|
@@ -1620,7 +1579,6 @@ var createSignalFromConfig = (config) => {
|
|
|
1620
1579
|
modelKnown: false
|
|
1621
1580
|
};
|
|
1622
1581
|
},
|
|
1623
|
-
// Execution — thin wrappers over the step core (src/complete.ts).
|
|
1624
1582
|
complete: async (input) => runComplete(
|
|
1625
1583
|
{
|
|
1626
1584
|
messages: buildMessages(config, input),
|
|
@@ -1631,7 +1589,7 @@ var createSignalFromConfig = (config) => {
|
|
|
1631
1589
|
onRetry: config.onRetry,
|
|
1632
1590
|
onToolCall: config.onToolCall
|
|
1633
1591
|
},
|
|
1634
|
-
{ stepStream, model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
|
|
1592
|
+
{ stepStream: stepStreamAs("complete"), model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
|
|
1635
1593
|
),
|
|
1636
1594
|
stream: (input, streamOptions) => runStream(
|
|
1637
1595
|
{
|
|
@@ -1644,12 +1602,10 @@ var createSignalFromConfig = (config) => {
|
|
|
1644
1602
|
onRetry: config.onRetry,
|
|
1645
1603
|
onToolCall: config.onToolCall
|
|
1646
1604
|
},
|
|
1647
|
-
{ stepStream, model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
|
|
1605
|
+
{ stepStream: stepStreamAs("stream"), model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
|
|
1648
1606
|
),
|
|
1649
|
-
// ─── Low-level primitives — the execution core itself ────
|
|
1650
1607
|
step,
|
|
1651
1608
|
stepStream,
|
|
1652
|
-
// ─── Embedding ────────────────────────────────────────────
|
|
1653
1609
|
embed: (async (input, options) => {
|
|
1654
1610
|
const adapter = await getChatAdapter("embed");
|
|
1655
1611
|
if (!adapter.embed) {
|
|
@@ -1669,7 +1625,6 @@ var createSignalFromConfig = (config) => {
|
|
|
1669
1625
|
}
|
|
1670
1626
|
return response.embeddings;
|
|
1671
1627
|
}),
|
|
1672
|
-
// ─── Decisions ────────────────────────────────────────────
|
|
1673
1628
|
decide: async (request) => {
|
|
1674
1629
|
assertQuestions(request.questions);
|
|
1675
1630
|
const started = Date.now();
|
|
@@ -1677,7 +1632,6 @@ var createSignalFromConfig = (config) => {
|
|
|
1677
1632
|
const model = resolveProvider(config).model;
|
|
1678
1633
|
return adapter.kind === "decisions" ? decideCalibrated(adapter, model, request, started) : decideEmulated(model, request, started);
|
|
1679
1634
|
},
|
|
1680
|
-
// ─── Low-level: token counting (heuristic) ───────────────
|
|
1681
1635
|
count: async (input) => {
|
|
1682
1636
|
const CHARS_PER_TOKEN2 = 4;
|
|
1683
1637
|
if (typeof input === "string") {
|
|
@@ -1785,8 +1739,7 @@ var resolveTransport = (spec, caps) => {
|
|
|
1785
1739
|
responseMode: spec.responseMode,
|
|
1786
1740
|
responseFormat: {
|
|
1787
1741
|
type: "json_schema",
|
|
1788
|
-
//
|
|
1789
|
-
// keywords; non-strict still constrains and never hard-fails.
|
|
1742
|
+
// Strict mode rejects open records and several keywords; non-strict still constrains.
|
|
1790
1743
|
jsonSchema: { name: "envelope", strict: false, schema: serialized }
|
|
1791
1744
|
},
|
|
1792
1745
|
injectSchemaDoc: injectSchemaDoc2,
|