@jaypie/mcp 0.8.179 → 0.8.181
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/dist/assets.js +3 -3
- package/package.json +1 -1
- package/release-notes/dynamodb/0.6.14.md +21 -0
- package/release-notes/jaypie/1.2.116.md +9 -0
- package/release-notes/llm/1.3.57.md +47 -0
- package/release-notes/mcp/0.8.180.md +10 -0
- package/release-notes/mcp/0.8.181.md +10 -0
- package/release-notes/testkit/1.2.80.md +9 -0
- package/release-notes/testkit/1.2.81.md +9 -0
- package/skills/dynamodb.md +50 -3
- package/skills/llm.md +40 -3
package/dist/assets.js
CHANGED
|
@@ -55,11 +55,11 @@ function getMcpAssetPaths() {
|
|
|
55
55
|
*/
|
|
56
56
|
function getMcpBuildInfo() {
|
|
57
57
|
return {
|
|
58
|
-
commit: "
|
|
58
|
+
commit: "9b837db4"
|
|
59
59
|
,
|
|
60
|
-
version: "0.8.
|
|
60
|
+
version: "0.8.181"
|
|
61
61
|
,
|
|
62
|
-
versionString: "@jaypie/mcp@0.8.
|
|
62
|
+
versionString: "@jaypie/mcp@0.8.181#9b837db4"
|
|
63
63
|
,
|
|
64
64
|
};
|
|
65
65
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.6.14
|
|
3
|
+
date: 2026-10-02
|
|
4
|
+
summary: Adds patchEntity, a targeted UpdateItem that sets or removes listed fields without moving GSI entries or overwriting other fields
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **`patchEntity({ id, set?, remove?, condition?, names?, values? })`** (#606).
|
|
10
|
+
Sends a targeted `UpdateItem` that sets and removes only the listed
|
|
11
|
+
attributes. `updatedAt` and every GSI key stay unchanged, so the entity
|
|
12
|
+
keeps its place in `queryByScope` and other index queries, and concurrent
|
|
13
|
+
writes to other fields are never overwritten with a stale copy. Returns the
|
|
14
|
+
entity as stored after the write.
|
|
15
|
+
- Protected attributes (`id`, `model`, `updatedAt`, `archivedAt`, `deletedAt`,
|
|
16
|
+
`index*`, and every pk/sk field of a registered index) throw
|
|
17
|
+
`BadRequestError`; change those with `updateEntity`. A missing entity throws
|
|
18
|
+
`NotFoundError`; a failed `condition` throws `ConflictError`.
|
|
19
|
+
- **Docs:** `updateEntity` and `queryByScope` documentation explains that each
|
|
20
|
+
`updateEntity` moves the entity's `indexModel` entry, and recommends
|
|
21
|
+
`patchEntity` for counters, heartbeats, and "last X" fields.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.2.116
|
|
3
|
+
date: 2026-09-29
|
|
4
|
+
summary: Pulls @jaypie/llm 1.3.57
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **`@jaypie/llm` 1.3.57**: a fallback chain advances past an incomplete (truncated) response; new `incomplete: "return" | "throw"` option with `LlmIncompleteError`; `response.stopReason` normalized as `LlmStopReason`; Anthropic spending-cap and bundled 4xx errors no longer classify as retryable.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.3.57
|
|
3
|
+
date: 2026-09-29
|
|
4
|
+
summary: A fallback chain advances past an incomplete (truncated) response; new incomplete option, LlmIncompleteError, and a standard stopReason; Anthropic spending-cap and bundled 4xx errors classify correctly
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
### Fallback on incomplete (issue #602)
|
|
10
|
+
|
|
11
|
+
A response the provider cut short (`status: "incomplete"`, `error.reason: "incomplete"`: an output token ceiling or a content filter) was returned as success, so a configured chain never advanced.
|
|
12
|
+
|
|
13
|
+
- `operate` now moves to the next chain entry on an incomplete response, as it does on a thrown error.
|
|
14
|
+
- When no entry finishes, the latest incomplete response is returned rather than an error. `fallbackAttempts` counts every attempt made.
|
|
15
|
+
- A primary that cut off skips the linger pass: the same model would cut off the same way.
|
|
16
|
+
- Loop stops (`max_turns`, `tool_errors`) never fall over.
|
|
17
|
+
- The hand-off logs `Provider X returned incomplete; failing over` at `debug` and tallies under the `incomplete` kind.
|
|
18
|
+
|
|
19
|
+
### `incomplete` option
|
|
20
|
+
|
|
21
|
+
`incomplete: "return" | "throw"` decides what reaches the caller once the chain is spent. `"return"` (default) keeps the partial with `status: "incomplete"`; `"throw"` raises `LlmIncompleteError` (status 502, category `incomplete`) carrying the settled response on `.response`. Jaypie 2 will default to `"throw"`.
|
|
22
|
+
|
|
23
|
+
### Standard stop reason
|
|
24
|
+
|
|
25
|
+
`LlmOperateResponse.stopReason` (`LlmStopReason`) normalizes the final model call's stop across providers: `end_turn`, `max_tokens`, `content_filter`, `refusal`, `tool_use`, or `other`. Gemini `MAX_TOKENS`, Chat Completions `length`, Anthropic `max_tokens`, and OpenAI `max_output_tokens` all read `max_tokens`.
|
|
26
|
+
|
|
27
|
+
### Error classification under bundling (issue #604)
|
|
28
|
+
|
|
29
|
+
An Anthropic spending-cap 400 surfaced as a retryable `LlmTransientError` (504) and tallied under `retryable`.
|
|
30
|
+
|
|
31
|
+
- `You have reached your specified API usage limits` classifies as `Quota` with `reason: "billing"`: no retries, immediate failover, tallied under `quota`.
|
|
32
|
+
- The Anthropic adapter classifies by HTTP `status` first: 429 rate limit, 408 and 5xx retryable, every other 4xx unrecoverable. An esbuild-renamed class (`BadRequestError3`) previously fell through to `Unknown`, so every bundled Anthropic 4xx was retried.
|
|
33
|
+
- A class-name fallback strips a bundler's numeric suffix.
|
|
34
|
+
- Bedrock matches on the AWS SDK's literal `error.name` instead of the class name.
|
|
35
|
+
- Anthropic and OpenAI client error classes carry a literal `name`.
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
- An incomplete `content` is the raw partial string even when `format` was requested.
|
|
40
|
+
- Mixed chains mix output budgets: Anthropic, Google, and Mistral cap non-streaming at 16,384 while OpenAI, xAI, and Meta send no ceiling.
|
|
41
|
+
|
|
42
|
+
## Testing
|
|
43
|
+
|
|
44
|
+
- `runWithFallback` specs cover soft failover, lone-model return, linger skip, partial over a later error, `settle`, and a `settle` throw that does not fail over.
|
|
45
|
+
- `Llm` class specs cover failover on incomplete, whole-chain cut-off, fallback error after a primary cut-off, loop stops not failing over, and `incomplete: "throw"`.
|
|
46
|
+
- Adapter specs cover the spending-cap message, renamed Anthropic classes by status (400, 401, 403, 404, 429, 500, 529) and without one, and renamed Bedrock exceptions.
|
|
47
|
+
- `standardStopReason` and `OperateLoop` specs cover the normalized stop reason, including a `structured_output` tool answer as `end_turn`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.180
|
|
3
|
+
date: 2026-09-29
|
|
4
|
+
summary: Release notes for @jaypie/llm 1.3.57, @jaypie/testkit 1.2.80, and jaypie 1.2.116; llm skill documents incomplete failover and stopReason
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **Release notes**: `@jaypie/llm` 1.3.57, `@jaypie/testkit` 1.2.80, and `jaypie` 1.2.116
|
|
10
|
+
- **`llm` skill**: documents failover on incomplete responses, the `incomplete` option and `LlmIncompleteError`, `response.stopReason`, raw partial `content` under `format`, and the output budget asymmetry across providers
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 0.8.181
|
|
3
|
+
date: 2026-10-02
|
|
4
|
+
summary: Release notes for @jaypie/dynamodb 0.6.14 and @jaypie/testkit 1.2.81; dynamodb skill documents patchEntity
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
- **Release notes**: `@jaypie/dynamodb` 0.6.14 and `@jaypie/testkit` 1.2.81
|
|
10
|
+
- **`dynamodb` skill**: documents `patchEntity`, the protected attributes, and why each `updateEntity` moves an entity's `indexModel` entry
|
package/skills/dynamodb.md
CHANGED
|
@@ -127,7 +127,7 @@ interface StorableEntity {
|
|
|
127
127
|
### Entity Operations
|
|
128
128
|
|
|
129
129
|
```typescript
|
|
130
|
-
import { APEX, createEntity, getEntity, updateEntity, deleteEntity, archiveEntity, destroyEntity } from "@jaypie/dynamodb";
|
|
130
|
+
import { APEX, createEntity, getEntity, patchEntity, updateEntity, deleteEntity, archiveEntity, destroyEntity } from "@jaypie/dynamodb";
|
|
131
131
|
|
|
132
132
|
// Create entity — indexEntity auto-populates GSI keys, createdAt, updatedAt
|
|
133
133
|
const record = await createEntity({
|
|
@@ -148,9 +148,12 @@ const record = await createEntity({
|
|
|
148
148
|
// Get by primary key (id only)
|
|
149
149
|
const item = await getEntity({ id: "abc-123" });
|
|
150
150
|
|
|
151
|
-
// Update — sets updatedAt, re-indexes
|
|
151
|
+
// Update — writes the whole item, sets updatedAt, re-indexes (moves GSI entries)
|
|
152
152
|
await updateEntity({ entity: { ...item, name: "Updated Name" } });
|
|
153
153
|
|
|
154
|
+
// Patch — sets only the listed fields; updatedAt and GSI keys unchanged
|
|
155
|
+
await patchEntity({ id: "abc-123", set: { lastUsedAt: new Date() } });
|
|
156
|
+
|
|
154
157
|
// Conditional update — write-time guard; throws ConflictError if it fails
|
|
155
158
|
await updateEntity({
|
|
156
159
|
entity: { ...item, status: "complete" },
|
|
@@ -176,13 +179,55 @@ await destroyEntity({ id: "abc-123" });
|
|
|
176
179
|
|----------|-------------|
|
|
177
180
|
| `createEntity({ entity, preserveTimestamps? })` | Create entity; returns `null` if `id` exists (conditional `attribute_not_exists(id)`) |
|
|
178
181
|
| `getEntity({ id })` | Get by primary key (id only) |
|
|
179
|
-
| `updateEntity({ entity, condition?, names?, preserveTimestamps?, values? })` | Update (sets `updatedAt`, re-indexes). Pass `condition` (a ConditionExpression, with `names`/`values` bindings) to guard the write; throws `ConflictError` (409) and leaves the item unchanged when the condition fails |
|
|
182
|
+
| `updateEntity({ entity, condition?, names?, preserveTimestamps?, values? })` | Update (writes the whole item, sets `updatedAt`, re-indexes; each call moves the entity's GSI entries). Pass `condition` (a ConditionExpression, with `names`/`values` bindings) to guard the write; throws `ConflictError` (409) and leaves the item unchanged when the condition fails |
|
|
183
|
+
| `patchEntity({ id, set?, remove?, condition?, names?, values? })` | Targeted `UpdateItem`: sets/removes only the listed attributes; leaves `updatedAt` and every GSI key unchanged. Throws `BadRequestError` (400) for index keys and timestamps, `NotFoundError` (404) when absent, `ConflictError` (409) when `condition` fails. See [Patching Fields Without Re-indexing](#patching-fields-without-re-indexing) |
|
|
180
184
|
| `transitionEntity({ id, from?, set })` | Conditionally update by status: reads the entity, merges `set`, writes guarded by `#status = from`; throws `ConflictError` (409) on a race, `NotFoundError` (404) when absent. Validates `from`/`set.status` against the model's `status` vocabulary |
|
|
181
185
|
| `deleteEntity({ id })` | Soft delete (`deletedAt`, `#deleted` suffix on GSI pk) |
|
|
182
186
|
| `archiveEntity({ id })` | Archive (`archivedAt`, `#archived` suffix on GSI pk) |
|
|
183
187
|
| `destroyEntity({ id })` | Hard delete (permanent) |
|
|
184
188
|
| `transactWriteEntities({ entities, conditionalCreate?, condition?, preserveTimestamps? })` | Write many entities atomically; `conditionalCreate: true` guards every `Put` with `attribute_not_exists(id)` (`condition` for a custom expression), throwing `ConflictError` (409) when a conditional check fails |
|
|
185
189
|
|
|
190
|
+
### Patching Fields Without Re-indexing
|
|
191
|
+
|
|
192
|
+
`updateEntity` writes the whole item with `PutItem` and sets `updatedAt` to the write time. For entities that are written often (counters, heartbeats, "last X" fields), that has two consequences:
|
|
193
|
+
|
|
194
|
+
1. **The index entry moves.** `updatedAt` is part of every GSI sort key (`scope#updatedAt`), so each `updateEntity` moves the entity's `indexModel` entry (and every other index entry). DynamoDB moves a GSI entry by deleting it and inserting it again, asynchronously, and GSI reads are always eventually consistent. A `queryByScope` (or any other index query) that runs between the delete and the insert does not return the entity.
|
|
195
|
+
2. **Stale fields overwrite newer ones.** The `Put` writes every attribute as the caller read it. When two read-modify-write calls overlap, the later one undoes the earlier one (for example, a status change or a credential rotation).
|
|
196
|
+
|
|
197
|
+
`patchEntity` sends a targeted `UpdateItem` that sets and removes only the listed attributes. It does not change `updatedAt` or any index key, so the entity stays in place in every index, and it never writes attributes the caller did not list.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { patchEntity } from "@jaypie/dynamodb";
|
|
201
|
+
|
|
202
|
+
// Record a heartbeat without moving the entity in queryByScope
|
|
203
|
+
await patchEntity({
|
|
204
|
+
id: endpoint.id,
|
|
205
|
+
set: { lastDeliveryAt: new Date() },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Set and remove in one write, guarded by a condition
|
|
209
|
+
const patched = await patchEntity({
|
|
210
|
+
id: endpoint.id,
|
|
211
|
+
set: { lastDeliveryAt: new Date(), lastStatus: 200 },
|
|
212
|
+
remove: ["lastError"],
|
|
213
|
+
condition: "#status = :active",
|
|
214
|
+
names: { "#status": "status" },
|
|
215
|
+
values: { ":active": "active" },
|
|
216
|
+
});
|
|
217
|
+
// patched: the full entity as stored after the write
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
| Use | When |
|
|
221
|
+
|-----|------|
|
|
222
|
+
| `patchEntity` | Counters, heartbeats, "last X" timestamps, and any field that is not an index key and should not reorder listings |
|
|
223
|
+
| `updateEntity` | Changes to index keys (`scope`, `alias`, `category`, `type`, `xid`, custom index fields) or changes that should advance `updatedAt` |
|
|
224
|
+
|
|
225
|
+
- **Protected attributes.** `set` and `remove` cannot name `id`, `model`, `updatedAt`, `archivedAt`, `deletedAt`, any `index*` attribute, or any `pk`/`sk` field of a registered index (`scope`, `alias`, `category`, `type`, `xid`, and custom index fields). These throw `BadRequestError` (400) before any write, because changing them requires re-indexing through `updateEntity`. An empty patch also throws `BadRequestError`.
|
|
226
|
+
- **Existence.** Every patch is guarded by `attribute_exists(id)`, so it never creates an item. A missing entity throws `NotFoundError` (404).
|
|
227
|
+
- **Conditions.** `condition` is combined with the existence guard as `attribute_exists(id) AND (condition)`. When it fails, the item is left unchanged and `ConflictError` (409) is thrown. Placeholders `#s<n>`, `:s<n>`, and `#r<n>` are reserved for the generated expression; passing them in `names`/`values` throws `BadRequestError`.
|
|
228
|
+
- **Ordering.** `updatedAt` does not change, so listings sorted by `updatedAt` do not reflect a patch. Use `updateEntity` when a change should move the entity to the top of a listing.
|
|
229
|
+
- **Values.** `Date` values are stored as ISO 8601 strings. TTL is not changed.
|
|
230
|
+
|
|
186
231
|
### Preserving Timestamps (Imports)
|
|
187
232
|
|
|
188
233
|
Every write stamps `updatedAt` with the write time, and every `scope#updatedAt` sort key follows it. Migrations, restores, and replays pass `preserveTimestamps: true` to `createEntity`, `updateEntity`, `transactWriteEntities`, or `seedEntities` (as an option) to keep the entity's own `createdAt`/`updatedAt`. Missing values fall back to now. TTL handling, date serialization, and conditional-write error mapping still apply, so raw `PutCommand` workarounds are unnecessary.
|
|
@@ -329,6 +374,8 @@ All queries return `{ items, lastEvaluatedKey }` and support pagination. `scope`
|
|
|
329
374
|
import { APEX, queryByScope, queryByAlias, queryByCategory, queryByType, queryByXid } from "@jaypie/dynamodb";
|
|
330
375
|
|
|
331
376
|
// List by model (scope optional)
|
|
377
|
+
// Each updateEntity moves the entity's indexModel entry; patch frequently
|
|
378
|
+
// written fields with patchEntity so listings stay stable
|
|
332
379
|
const { items } = await queryByScope({ model: "record", scope: APEX });
|
|
333
380
|
|
|
334
381
|
// List across all scopes
|
package/skills/llm.md
CHANGED
|
@@ -130,8 +130,14 @@ response.history; // LlmHistory - for follow-up calls
|
|
|
130
130
|
response.usage; // Token usage per turn: { input, output, reasoning, total, cacheRead?, cacheWrite?, cacheWriteTtl? }; reasoning ⊂ output, cache ⊂ input; price with tokenCost()
|
|
131
131
|
response.reasoning; // Extended thinking (if available)
|
|
132
132
|
response.status; // "completed" | "incomplete" | "in_progress"
|
|
133
|
+
response.stopReason; // LlmStopReason: "end_turn" | "max_tokens" | "content_filter" | "refusal" | "tool_use" | "other"
|
|
133
134
|
```
|
|
134
135
|
|
|
136
|
+
`stopReason` is normalized across providers (Gemini `MAX_TOKENS`, Chat
|
|
137
|
+
Completions `length`, Anthropic `max_tokens`, and OpenAI `max_output_tokens`
|
|
138
|
+
all read `max_tokens`). The provider's own value rides
|
|
139
|
+
`exchange.response.stopReason`.
|
|
140
|
+
|
|
135
141
|
### send() - Simple Completions
|
|
136
142
|
|
|
137
143
|
For single-shot text completions:
|
|
@@ -800,7 +806,9 @@ for await (const chunk of Llm.stream(input, {
|
|
|
800
806
|
|
|
801
807
|
## Fallback Providers
|
|
802
808
|
|
|
803
|
-
Configure a chain of fallback providers. Any error (rate limit, 5xx, network flake, bad request) moves to the next entry at once, with no retry or wait. When every entry has failed, the primary runs once more with its full retry policy; if that fails, the call throws. A caller abort (`LlmAbortError`) never falls over
|
|
809
|
+
Configure a chain of fallback providers. Any error (rate limit, 5xx, network flake, bad request) moves to the next entry at once, with no retry or wait. When every entry has failed, the primary runs once more with its full retry policy; if that fails, the call throws. A caller abort (`LlmAbortError`) never falls over.
|
|
810
|
+
|
|
811
|
+
A response the provider cut short (`status: "incomplete"` with `error.reason: "incomplete"`: an output token ceiling or a content filter) also moves to the next entry. When no entry finishes, the latest incomplete response is returned rather than an error, and a primary that cut off does not linger (the same model would cut off the same way). Loop stops (`max_turns`, `tool_errors`) never fall over:
|
|
804
812
|
|
|
805
813
|
```typescript
|
|
806
814
|
// Instance-level configuration
|
|
@@ -827,6 +835,28 @@ const response = await Llm.operate(input, {
|
|
|
827
835
|
});
|
|
828
836
|
```
|
|
829
837
|
|
|
838
|
+
### Incomplete Responses
|
|
839
|
+
|
|
840
|
+
`incomplete` decides what reaches the caller once the chain is spent. `"return"` (the default) returns the partial with `status: "incomplete"`; `"throw"` raises `LlmIncompleteError` (status 502) carrying the settled response on `.response`. Either way a chain moves past an incomplete first. Jaypie 2 will default to `"throw"`.
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
import { LlmIncompleteError, LlmStopReason } from "@jaypie/llm";
|
|
844
|
+
|
|
845
|
+
try {
|
|
846
|
+
const response = await Llm.operate(input, {
|
|
847
|
+
format: Extraction,
|
|
848
|
+
incomplete: "throw",
|
|
849
|
+
model: [LLM.MODEL.GEMINI_FLASH, LLM.MODEL.SOL],
|
|
850
|
+
});
|
|
851
|
+
} catch (error) {
|
|
852
|
+
if (error instanceof LlmIncompleteError) {
|
|
853
|
+
error.response.stopReason; // LlmStopReason.MaxTokens
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
An incomplete `content` is the raw partial text, a string even when `format` was requested: a truncated answer is not the object the format promises. Check `status` (or use `incomplete: "throw"`) before reading `content` as the formatted object.
|
|
859
|
+
|
|
830
860
|
### Provider Options in a Chain
|
|
831
861
|
|
|
832
862
|
`providerOptions` belongs to the model it was written for. Per-call `providerOptions` reach the primary only (including its linger pass); a fallback receives only the `providerOptions` on its own entry, or none. Options shaped for one provider (OpenAI `reasoning`, Gemini `thinkingConfig`) would otherwise break every other provider in the chain. Prefer first-class `effort` and `temperature`, which translate per provider.
|
|
@@ -1038,7 +1068,7 @@ exactly like a provider rate limit.
|
|
|
1038
1068
|
| ------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1039
1069
|
| `max_turns` | 429 | The model asked for another tool call after `turns` ran out. Nothing failed; the run did not converge. |
|
|
1040
1070
|
| `tool_errors` | 502 | Tool execution failed six times in a row and the loop stopped. |
|
|
1041
|
-
| `incomplete` | 502 | The provider cut the model off before it finished (an output token ceiling, a content filter). `content` holds the partial text; `error.detail` names the provider's reason. |
|
|
1071
|
+
| `incomplete` | 502 | The provider cut the model off before it finished (an output token ceiling, a content filter). `content` holds the partial text; `error.detail` names the provider's reason and `stopReason` normalizes it. A fallback chain moves past it. |
|
|
1042
1072
|
|
|
1043
1073
|
```typescript
|
|
1044
1074
|
import { LlmResponseErrorReason } from "@jaypie/llm";
|
|
@@ -1254,12 +1284,19 @@ await Llm.operate(input, {
|
|
|
1254
1284
|
```
|
|
1255
1285
|
|
|
1256
1286
|
OpenAI, xAI, and Meta leave the limit unset (their defaults do not truncate early).
|
|
1287
|
+
A chain that mixes them with Anthropic, Google, or Mistral therefore mixes
|
|
1288
|
+
output budgets: a Gemini primary resolves to 16,384 non-streaming while an
|
|
1289
|
+
OpenAI fallback sends no ceiling, so swapping the primary moves the effective
|
|
1290
|
+
budget with nothing at the call site changing. Reasoning tokens count against
|
|
1291
|
+
the ceiling. Set `providerOptions.maxOutputTokens` (Google) or `max_tokens`
|
|
1292
|
+
(Anthropic, Mistral) on the call or chain entry to pin it.
|
|
1257
1293
|
Mistral is capped (32,768 streaming / 16,384 non-streaming) despite publishing
|
|
1258
1294
|
no low ceiling: a Mistral model can degenerate into restating its answer when
|
|
1259
1295
|
`format` and tools are combined, and an uncapped completion turns that into a
|
|
1260
1296
|
multi-minute request. Override with `providerOptions: { max_tokens }`.
|
|
1261
1297
|
OpenRouter varies by routed model; pass `max_tokens` via `providerOptions`
|
|
1262
|
-
when needed. A truncated response
|
|
1298
|
+
when needed. A truncated response settles `status: "incomplete"` with
|
|
1299
|
+
`stopReason: "max_tokens"` and moves a fallback chain to its next entry;
|
|
1263
1300
|
raise the limit or switch to `stream()`.
|
|
1264
1301
|
|
|
1265
1302
|
## See Also
|