flexinference 1.5.1 → 1.5.3
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 +20 -7
- package/README.md +3 -3
- package/dist/index.d.ts +11 -4
- package/dist/index.js +99 -49
- package/dist/types.d.ts +6 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.3
|
|
4
|
+
|
|
5
|
+
Relaxes Messages `max_tokens` on the FlexInference side. The SDK no longer requires or
|
|
6
|
+
runtime-validates it before sending a `/messages` request; FlexInference forwards it when
|
|
7
|
+
set and lets Anthropic return its own request error when it is omitted.
|
|
8
|
+
|
|
9
|
+
## 1.5.2
|
|
10
|
+
|
|
11
|
+
Adds zero-dependency runtime request validation matching the Python SDK: a request is now
|
|
12
|
+
checked for `model` and the per-endpoint required fields before it is sent, with the two SDKs
|
|
13
|
+
held to identical behavior by shared parity fixtures. The generated `Error` type also gains a
|
|
14
|
+
`doc_url` field, so an error now carries a link to its documentation.
|
|
15
|
+
|
|
3
16
|
## 1.5.1
|
|
4
17
|
|
|
5
18
|
Small robustness fixes for the transport, no API changes. A non-streaming request that hits
|
|
@@ -7,8 +20,8 @@ its total `timeout` now throws a clear timeout error instead of a bare abort tha
|
|
|
7
20
|
a caller cancellation. A streamed response flushes its decoder and delivers the final frame
|
|
8
21
|
even when the stream ends without a trailing blank line, so the last event is never dropped,
|
|
9
22
|
and the streaming buffer is capped so a stream that never sends a separator cannot grow
|
|
10
|
-
without bound. The `start_within` type now
|
|
11
|
-
|
|
23
|
+
without bound. The `start_within` type now requires the runtime's two-digit duration
|
|
24
|
+
shape, so values like `0h-5m-0s` no longer type-check.
|
|
12
25
|
|
|
13
26
|
## 1.5.0
|
|
14
27
|
|
|
@@ -82,11 +95,11 @@ required and aligns its values with OpenAI's `service_tier` vocabulary.
|
|
|
82
95
|
|
|
83
96
|
### Breaking changes
|
|
84
97
|
|
|
85
|
-
- **`start_within` is now required on every request.** It is typed as
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
A duration that fits the shape but is malformed or out of range is caught at **runtime**;
|
|
89
|
-
plain-JS callers get every check at **runtime** (a thrown `Error` before any network call).
|
|
98
|
+
- **`start_within` is now required on every request.** It is typed as `"default"`,
|
|
99
|
+
`"priority"`, `"auto"`, or a duration shaped like `"00h-05m-00s"`, so omitting it
|
|
100
|
+
(or passing a value outside those shapes) is a **compile-time** error.
|
|
101
|
+
A duration that fits the shape but is malformed or out of range is caught at **runtime**;
|
|
102
|
+
plain-JS callers get every check at **runtime** (a thrown `Error` before any network call).
|
|
90
103
|
- **Accepted values changed.** `start_within` now takes `"default"`, `"priority"`,
|
|
91
104
|
`"auto"`, or a duration `"HHh-MMm-SSs"` (5s-10m). The old `"standard"` value is
|
|
92
105
|
removed; use `"default"` (it maps to the same OpenAI service tier).
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ FlexInference routes to **OpenAI**, **Google Gemini**, and **Anthropic**. Send t
|
|
|
34
34
|
|
|
35
35
|
- **OpenAI:** `default` (the standard tier), `priority`, `auto`, and flex on flex-capable models. You reach flex by passing a duration.
|
|
36
36
|
- **Gemini:** `default` maps to Gemini's standard tier. You also get `priority` and flex on the Gemini flex models (`gemini-3.5-flash`, `gemini-3.1-flash-lite`, `gemini-3.1-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`). Gemini has no `auto` tier, so `start_within: "auto"` on a Gemini model returns `400`.
|
|
37
|
-
- **Anthropic (Claude):** proxy-only. `default`, `priority`, and `auto` work; there is **no flex tier**, so a duration `start_within` on a `claude-*` model returns `400 flex_unsupported_for_anthropic`. Anthropic requires
|
|
37
|
+
- **Anthropic (Claude):** proxy-only. `default`, `priority`, and `auto` work; there is **no flex tier**, so a duration `start_within` on a `claude-*` model returns `400 flex_unsupported_for_anthropic`. Anthropic requires an output-token field upstream. FlexInference forwards `max_output_tokens` (`max_completion_tokens` on Chat, `max_tokens` on Messages) when set and does not synthesize a default, so omitting it returns Anthropic's own request error. You keep the unified API and tier control, and draw down your own Anthropic credits.
|
|
38
38
|
|
|
39
39
|
Add the provider key you'll use (OpenAI, Gemini, and/or Anthropic) in the [dashboard](https://www.flexinference.com/dashboard). Text, streaming, structured outputs, function calling, image input, and web search work across providers (send a Responses `web_search` tool; we map it to Gemini's `google_search`).
|
|
40
40
|
|
|
@@ -85,7 +85,7 @@ console.log(interactionText(res));
|
|
|
85
85
|
|
|
86
86
|
## Messages (Anthropic shape)
|
|
87
87
|
|
|
88
|
-
Speak Anthropic's Messages shape and reach any model. `max_tokens`
|
|
88
|
+
Speak Anthropic's Messages shape and reach any model. Anthropic requires `max_tokens` upstream. FlexInference forwards it when set and does not synthesize a default, so omitting it returns Anthropic's own request error. `messageText(res)` pulls the assistant text out of the message `content`.
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
91
|
import { messageText } from "flexinference";
|
|
@@ -104,7 +104,7 @@ console.log(messageText(res));
|
|
|
104
104
|
|
|
105
105
|
Streaming and non-streaming requests are timed differently, so a long generation is never cut off yet a hung request never hangs forever:
|
|
106
106
|
|
|
107
|
-
- **Non-streaming** requests have a
|
|
107
|
+
- **Non-streaming** requests have a body-read budget after response headers arrive: `timeout`, default **600000 ms (10 min)**. The worst case is roughly `firstByteTimeout + timeout`.
|
|
108
108
|
- **Streaming** requests have **no total cap** (a healthy stream runs as long as tokens keep arriving). They are bounded by two clocks: `firstByteTimeout` (wait for the first response, default **60000 ms**) and `idleTimeout` (max silence between chunks before the stream is treated as hung, default **60000 ms**).
|
|
109
109
|
|
|
110
110
|
The first-byte wait is **auto-raised for a flex `start_within`** (the router withholds response headers until the flex race resolves), so a long deadline just works. Raise `firstByteTimeout` yourself only for very large non-flex contexts whose first token is slow.
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,12 @@ type Schemas = components["schemas"];
|
|
|
5
5
|
* those OpenAI service tiers and proxy any model; a duration "HHh-MMm-SSs" (5s-10m)
|
|
6
6
|
* runs the flex race on a flex-capable model. An arbitrary string needs `as StartWithin`.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
9
|
+
type Second00To59 = "00" | "01" | "02" | "03" | "04" | "05" | "06" | "07" | "08" | "09" | `1${Digit}` | `2${Digit}` | `3${Digit}` | `4${Digit}` | `5${Digit}`;
|
|
10
|
+
type Second05To59 = Exclude<Second00To59, "00" | "01" | "02" | "03" | "04">;
|
|
11
|
+
type Minute01To09 = `0${Exclude<Digit, "0">}`;
|
|
12
|
+
type Duration = `00h-00m-${Second05To59}s` | `00h-${Minute01To09}m-${Second00To59}s` | "00h-10m-00s";
|
|
13
|
+
export type StartWithin = "default" | "priority" | "auto" | Duration;
|
|
9
14
|
/** Make `start_within` a required, typed field on a generated request body. */
|
|
10
15
|
type WithStartWithin<T> = Omit<T, "start_within"> & {
|
|
11
16
|
start_within: StartWithin;
|
|
@@ -78,6 +83,7 @@ export declare class FlexInferenceError extends Error {
|
|
|
78
83
|
* FlexInferenceError)` handlers keep working; narrow to this to handle 402 specially.
|
|
79
84
|
*/
|
|
80
85
|
export declare class PaymentRequiredError extends FlexInferenceError {
|
|
86
|
+
constructor(status: number, body: unknown, fallback: string);
|
|
81
87
|
}
|
|
82
88
|
export interface ClientOptions {
|
|
83
89
|
apiKey: string;
|
|
@@ -86,9 +92,10 @@ export interface ClientOptions {
|
|
|
86
92
|
/** Override the global fetch (e.g. for tests or non-standard runtimes). */
|
|
87
93
|
fetch?: typeof fetch;
|
|
88
94
|
/**
|
|
89
|
-
* Non-streaming
|
|
90
|
-
* 600000 (10 min).
|
|
91
|
-
*
|
|
95
|
+
* Non-streaming body-read budget in ms, after response headers arrive. Defaults to
|
|
96
|
+
* 600000 (10 min). The worst case is roughly `firstByteTimeout + timeout`.
|
|
97
|
+
* Streaming requests ignore this: they are governed by `firstByteTimeout` and
|
|
98
|
+
* `idleTimeout` and have no total cap, so a healthy long stream is never cut off.
|
|
92
99
|
*/
|
|
93
100
|
timeout?: number;
|
|
94
101
|
/**
|
package/dist/index.js
CHANGED
|
@@ -120,6 +120,10 @@ export class FlexInferenceError extends Error {
|
|
|
120
120
|
* FlexInferenceError)` handlers keep working; narrow to this to handle 402 specially.
|
|
121
121
|
*/
|
|
122
122
|
export class PaymentRequiredError extends FlexInferenceError {
|
|
123
|
+
constructor(status, body, fallback) {
|
|
124
|
+
super(status, body, fallback);
|
|
125
|
+
this.name = "PaymentRequiredError";
|
|
126
|
+
}
|
|
123
127
|
}
|
|
124
128
|
const DEFAULT_BASE_URL = "https://api.flexinference.com/v1";
|
|
125
129
|
// A streaming request is governed by two clocks and no total: how long we wait for the
|
|
@@ -134,43 +138,102 @@ const DEFAULT_TOTAL_MS = 600_000; // non-streaming wall-clock budget (streaming
|
|
|
134
138
|
const ERROR_BODY_READ_MS = 30_000; // bound the read of a non-2xx error body
|
|
135
139
|
// Cap the streaming buffer above the largest legitimate single frame (a terminal
|
|
136
140
|
// response object is well under this). A stream that never sends a blank-line separator
|
|
137
|
-
// would otherwise grow the buffer without bound; error clearly instead.
|
|
141
|
+
// would otherwise grow the buffer without bound; error clearly instead.
|
|
138
142
|
const MAX_SSE_BUFFER_CHARS = 8 * 1024 * 1024;
|
|
139
143
|
const MIN_DEADLINE_SECONDS = 5;
|
|
140
144
|
const MAX_DEADLINE_SECONDS = 600;
|
|
141
145
|
const SECONDS_PER_HOUR = 3600;
|
|
142
146
|
const SECONDS_PER_MINUTE = 60;
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
// count, so a two-digit-only regex would reject values that type-check. See T4.
|
|
146
|
-
const DURATION_RE = /^(\d{1,2})h-(\d{1,2})m-(\d{1,2})s$/;
|
|
147
|
+
// Two digits per field so the runtime matches the router, Python SDK, and StartWithin type.
|
|
148
|
+
const DURATION_RE = /^(\d{2})h-(\d{2})m-(\d{2})s$/;
|
|
147
149
|
const START_WITHIN_VALUES = '"default", "priority", "auto", or a duration "HHh-MMm-SSs"';
|
|
150
|
+
function startWithinValue(body) {
|
|
151
|
+
const value = typeof body === "object" && body !== null
|
|
152
|
+
? body["start_within"]
|
|
153
|
+
: undefined;
|
|
154
|
+
return value === undefined ? { kind: "missing" } : { kind: "present", value };
|
|
155
|
+
}
|
|
156
|
+
function parseStartWithin(value) {
|
|
157
|
+
if (typeof value !== "string")
|
|
158
|
+
return { kind: "invalid", reason: "type", value };
|
|
159
|
+
if (value === "default" || value === "priority" || value === "auto") {
|
|
160
|
+
return { kind: "tier", value };
|
|
161
|
+
}
|
|
162
|
+
const match = DURATION_RE.exec(value);
|
|
163
|
+
if (match === null)
|
|
164
|
+
return { kind: "invalid", reason: "malformed", value };
|
|
165
|
+
const seconds = Number(match[1]) * SECONDS_PER_HOUR + Number(match[2]) * SECONDS_PER_MINUTE + Number(match[3]);
|
|
166
|
+
return { kind: "duration", value, seconds };
|
|
167
|
+
}
|
|
148
168
|
// Runtime guard so plain-JS callers (who get no compile-time check) still fail
|
|
149
169
|
// locally with a clear message instead of a provider round-trip. TypeScript callers
|
|
150
170
|
// are already protected by the required StartWithin field above.
|
|
151
171
|
function assertStartWithin(body) {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
: undefined;
|
|
155
|
-
if (value === undefined) {
|
|
172
|
+
const sw = startWithinValue(body);
|
|
173
|
+
if (sw.kind === "missing") {
|
|
156
174
|
throw new Error(`FlexInference: \`start_within\` is required on every request. Set it to ${START_WITHIN_VALUES}, ` +
|
|
157
175
|
`for example "start_within": "default" for standard-tier behavior or "00h-00m-30s" for a 30-second flex race.`);
|
|
158
176
|
}
|
|
159
|
-
|
|
177
|
+
const parsed = parseStartWithin(sw.value);
|
|
178
|
+
if (parsed.kind === "invalid" && parsed.reason === "type") {
|
|
160
179
|
throw new Error(`FlexInference: \`start_within\` must be a string (${START_WITHIN_VALUES}), for example "default".`);
|
|
161
180
|
}
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (match === null) {
|
|
166
|
-
throw new Error(`FlexInference: \`start_within\` "${value}" is not ${START_WITHIN_VALUES}. ` +
|
|
167
|
-
`A duration is one or two digits each for hours, minutes, and seconds, for example "00h-05m-00s" for a 5-minute deadline.`);
|
|
181
|
+
if (parsed.kind === "invalid") {
|
|
182
|
+
throw new Error(`FlexInference: \`start_within\` "${parsed.value}" is not ${START_WITHIN_VALUES}. ` +
|
|
183
|
+
`A duration is two digits each for hours, minutes, and seconds, for example "00h-05m-00s" for a 5-minute deadline.`);
|
|
168
184
|
}
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
if (parsed.kind === "duration" &&
|
|
186
|
+
(parsed.seconds < MIN_DEADLINE_SECONDS || parsed.seconds > MAX_DEADLINE_SECONDS)) {
|
|
171
187
|
throw new Error("FlexInference: `start_within` duration must be between 5s and 10m.");
|
|
172
188
|
}
|
|
173
189
|
}
|
|
190
|
+
// Per-endpoint required fields beyond the model/start_within envelope. Mirrors the Python
|
|
191
|
+
// _RequestSpec table in _validation.py: responses/interactions require `input` (presence only);
|
|
192
|
+
// chat/messages require `messages` (array).
|
|
193
|
+
const RESPONSES_REQUEST_SPEC = {
|
|
194
|
+
required: [{ name: "input", kind: "present" }],
|
|
195
|
+
};
|
|
196
|
+
const CHAT_REQUEST_SPEC = {
|
|
197
|
+
required: [{ name: "messages", kind: "array" }],
|
|
198
|
+
};
|
|
199
|
+
const INTERACTIONS_REQUEST_SPEC = {
|
|
200
|
+
required: [{ name: "input", kind: "present" }],
|
|
201
|
+
};
|
|
202
|
+
const MESSAGES_REQUEST_SPEC = {
|
|
203
|
+
required: [{ name: "messages", kind: "array" }],
|
|
204
|
+
};
|
|
205
|
+
// Read one field by reference off the raw body without copying it. `undefined` (or a non-object
|
|
206
|
+
// body) is treated as missing; `null` counts as present, matching Python's `name in body`
|
|
207
|
+
// presence check (a JSON `null` is a present key, and JSON serialization drops `undefined`).
|
|
208
|
+
function requestField(body, name) {
|
|
209
|
+
if (!isRecord(body))
|
|
210
|
+
return { kind: "missing" };
|
|
211
|
+
const value = body[name];
|
|
212
|
+
return value === undefined ? { kind: "missing" } : { kind: "present", value };
|
|
213
|
+
}
|
|
214
|
+
// Pre-flight guard: model, then start_within, then the endpoint's required fields in spec order.
|
|
215
|
+
// Pure - it only throws earlier for requests the router/Python already reject; it never mutates,
|
|
216
|
+
// clones, or reshapes the body, so unknown/extra fields pass straight through to `send`.
|
|
217
|
+
function validateRequest(spec, body) {
|
|
218
|
+
const model = requestField(body, "model");
|
|
219
|
+
if (model.kind === "missing" || typeof model.value !== "string" || model.value === "") {
|
|
220
|
+
throw new Error("FlexInference: `model` is required and must be a non-empty string.");
|
|
221
|
+
}
|
|
222
|
+
assertStartWithin(body);
|
|
223
|
+
for (const field of spec.required) {
|
|
224
|
+
const value = requestField(body, field.name);
|
|
225
|
+
if (value.kind === "missing") {
|
|
226
|
+
throw new Error(`FlexInference: \`${field.name}\` is required for this endpoint.`);
|
|
227
|
+
}
|
|
228
|
+
if (field.kind === "array" && !Array.isArray(value.value)) {
|
|
229
|
+
throw new Error(`FlexInference: \`${field.name}\` must be an array.`);
|
|
230
|
+
}
|
|
231
|
+
if (field.kind === "finiteNumber" &&
|
|
232
|
+
(typeof value.value !== "number" || !Number.isFinite(value.value))) {
|
|
233
|
+
throw new Error(`FlexInference: \`${field.name}\` must be a finite number.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
174
237
|
// Distinct abort reasons so an aborted fetch/read can be told apart: a timeout we fired
|
|
175
238
|
// (turned into a clear message) versus the caller's own AbortSignal (propagated as-is).
|
|
176
239
|
const FIRST_BYTE_TIMEOUT = Symbol("first-byte-timeout");
|
|
@@ -183,15 +246,11 @@ const TOTAL_TIMEOUT = Symbol("total-timeout");
|
|
|
183
246
|
* clock must cover the whole deadline plus the fallback path.
|
|
184
247
|
*/
|
|
185
248
|
function flexDeadlineSeconds(body) {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
: undefined;
|
|
189
|
-
if (typeof value !== "string")
|
|
249
|
+
const sw = startWithinValue(body);
|
|
250
|
+
if (sw.kind === "missing")
|
|
190
251
|
return null;
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
return null;
|
|
194
|
-
return (Number(match[1]) * SECONDS_PER_HOUR + Number(match[2]) * SECONDS_PER_MINUTE + Number(match[3]));
|
|
252
|
+
const parsed = parseStartWithin(sw.value);
|
|
253
|
+
return parsed.kind === "duration" ? parsed.seconds : null;
|
|
195
254
|
}
|
|
196
255
|
// Read a non-streamed JSON body under the total wall-clock budget, then release the timer
|
|
197
256
|
// and the abort listener. Non-streaming only: a stream is drained by streamSSE instead.
|
|
@@ -199,7 +258,7 @@ async function readJson(open) {
|
|
|
199
258
|
const { res, controller, cleanup, totalMs } = open;
|
|
200
259
|
// Abort with a dedicated reason so a fired total timeout can be told apart from the
|
|
201
260
|
// caller cancelling on purpose, and translated into a clear message (parity with the
|
|
202
|
-
// first-byte and idle timeouts, and with the Python SDK).
|
|
261
|
+
// first-byte and idle timeouts, and with the Python SDK).
|
|
203
262
|
const totalTimer = setTimeout(() => {
|
|
204
263
|
controller.abort(TOTAL_TIMEOUT);
|
|
205
264
|
}, totalMs);
|
|
@@ -219,6 +278,13 @@ async function readJson(open) {
|
|
|
219
278
|
cleanup();
|
|
220
279
|
}
|
|
221
280
|
}
|
|
281
|
+
async function createEndpoint(send, path, spec, body, options) {
|
|
282
|
+
validateRequest(spec, body);
|
|
283
|
+
const open = await send(path, body, options);
|
|
284
|
+
if (body.stream)
|
|
285
|
+
return streamSSE(open);
|
|
286
|
+
return readJson(open);
|
|
287
|
+
}
|
|
222
288
|
export class FlexInference {
|
|
223
289
|
responses;
|
|
224
290
|
chat;
|
|
@@ -345,11 +411,7 @@ class Responses {
|
|
|
345
411
|
this.send = send;
|
|
346
412
|
}
|
|
347
413
|
async create(body, options) {
|
|
348
|
-
|
|
349
|
-
const open = await this.send("/responses", body, options);
|
|
350
|
-
if (body.stream)
|
|
351
|
-
return streamSSE(open);
|
|
352
|
-
return readJson(open);
|
|
414
|
+
return createEndpoint(this.send, "/responses", RESPONSES_REQUEST_SPEC, body, options);
|
|
353
415
|
}
|
|
354
416
|
}
|
|
355
417
|
class Chat {
|
|
@@ -364,11 +426,7 @@ class ChatCompletions {
|
|
|
364
426
|
this.send = send;
|
|
365
427
|
}
|
|
366
428
|
async create(body, options) {
|
|
367
|
-
|
|
368
|
-
const open = await this.send("/chat/completions", body, options);
|
|
369
|
-
if (body.stream)
|
|
370
|
-
return streamSSE(open);
|
|
371
|
-
return readJson(open);
|
|
429
|
+
return createEndpoint(this.send, "/chat/completions", CHAT_REQUEST_SPEC, body, options);
|
|
372
430
|
}
|
|
373
431
|
}
|
|
374
432
|
class Interactions {
|
|
@@ -377,11 +435,7 @@ class Interactions {
|
|
|
377
435
|
this.send = send;
|
|
378
436
|
}
|
|
379
437
|
async create(body, options) {
|
|
380
|
-
|
|
381
|
-
const open = await this.send("/interactions", body, options);
|
|
382
|
-
if (body.stream)
|
|
383
|
-
return streamSSE(open);
|
|
384
|
-
return readJson(open);
|
|
438
|
+
return createEndpoint(this.send, "/interactions", INTERACTIONS_REQUEST_SPEC, body, options);
|
|
385
439
|
}
|
|
386
440
|
}
|
|
387
441
|
class Messages {
|
|
@@ -390,11 +444,7 @@ class Messages {
|
|
|
390
444
|
this.send = send;
|
|
391
445
|
}
|
|
392
446
|
async create(body, options) {
|
|
393
|
-
|
|
394
|
-
const open = await this.send("/messages", body, options);
|
|
395
|
-
if (body.stream)
|
|
396
|
-
return streamSSE(open);
|
|
397
|
-
return readJson(open);
|
|
447
|
+
return createEndpoint(this.send, "/messages", MESSAGES_REQUEST_SPEC, body, options);
|
|
398
448
|
}
|
|
399
449
|
}
|
|
400
450
|
function parseSSEFrame(frame) {
|
|
@@ -445,7 +495,7 @@ async function* streamSSE(open) {
|
|
|
445
495
|
if (done) {
|
|
446
496
|
// The stream ended. Flush any bytes the decoder held back for an incomplete
|
|
447
497
|
// multibyte sequence, then emit a final frame that arrived without its trailing
|
|
448
|
-
// blank line so the last piece of the answer is not dropped.
|
|
498
|
+
// blank line so the last piece of the answer is not dropped.
|
|
449
499
|
exhausted = true;
|
|
450
500
|
buffer += decoder.decode();
|
|
451
501
|
const frame = buffer;
|
|
@@ -473,7 +523,7 @@ async function* streamSSE(open) {
|
|
|
473
523
|
}
|
|
474
524
|
// With every complete frame drained, whatever remains is a single unterminated
|
|
475
525
|
// frame. If it grows past the cap the stream is never sending a separator; error
|
|
476
|
-
// clearly instead of buffering without bound.
|
|
526
|
+
// clearly instead of buffering without bound.
|
|
477
527
|
if (buffer.length > MAX_SSE_BUFFER_CHARS) {
|
|
478
528
|
throw new Error(`FlexInference: stream buffer exceeded ${String(MAX_SSE_BUFFER_CHARS)} characters ` +
|
|
479
529
|
"without a frame boundary; aborting to avoid unbounded memory.");
|
package/dist/types.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface paths {
|
|
|
90
90
|
};
|
|
91
91
|
get?: never;
|
|
92
92
|
put?: never;
|
|
93
|
-
/** Create a message. The fourth FlexInference caller format (Anthropic Messages shape in and out); works for any model via the canonical Responses path. Send start_within and do not send service_tier. max_tokens
|
|
93
|
+
/** Create a message. The fourth FlexInference caller format (Anthropic Messages shape in and out); works for any model via the canonical Responses path. Send start_within and do not send service_tier. Anthropic requires max_tokens upstream. FlexInference forwards it when set and does not synthesize a default, so omitting it returns Anthropic's own request error. */
|
|
94
94
|
post: operations["createMessage"];
|
|
95
95
|
delete?: never;
|
|
96
96
|
options?: never;
|
|
@@ -5398,6 +5398,8 @@ export interface components {
|
|
|
5398
5398
|
type: string;
|
|
5399
5399
|
code?: string | null;
|
|
5400
5400
|
param?: string | null;
|
|
5401
|
+
/** @description Additive deep link into the docs for this exact code. Present (a URL or null) on FlexInference-layer errors; absent on verbatim upstream errors. */
|
|
5402
|
+
doc_url?: string | null;
|
|
5401
5403
|
};
|
|
5402
5404
|
};
|
|
5403
5405
|
/** Interaction Text Part */
|
|
@@ -5792,11 +5794,11 @@ export interface components {
|
|
|
5792
5794
|
};
|
|
5793
5795
|
/**
|
|
5794
5796
|
* Message Create Params
|
|
5795
|
-
* @description Request body for POST /v1/messages (Anthropic Messages shape). Works for any model via the canonical Responses path. max_tokens
|
|
5797
|
+
* @description Request body for POST /v1/messages (Anthropic Messages shape). Works for any model via the canonical Responses path. Anthropic requires max_tokens upstream. FlexInference forwards it when set and does not synthesize a default, so omitting it returns Anthropic's own request error. Does not accept service_tier (the router owns the tier). start_within is required at runtime but left optional here.
|
|
5796
5798
|
*/
|
|
5797
5799
|
MessageCreateParams: {
|
|
5798
5800
|
model: string;
|
|
5799
|
-
max_tokens
|
|
5801
|
+
max_tokens?: number;
|
|
5800
5802
|
/** @description System prompt as a string or an array of text blocks. */
|
|
5801
5803
|
system?: string | components["schemas"]["MessageTextBlock"][];
|
|
5802
5804
|
messages: components["schemas"]["Message"][];
|
|
@@ -6134,7 +6136,7 @@ export interface operations {
|
|
|
6134
6136
|
"text/event-stream": components["schemas"]["MessageStreamEvent"];
|
|
6135
6137
|
};
|
|
6136
6138
|
};
|
|
6137
|
-
/** @description Bad request. A FlexInference-layer rejection (type flexinference_error, e.g. missing_start_within, invalid_start_within, model_not_flex_capable, flex_unsupported_for_anthropic,
|
|
6139
|
+
/** @description Bad request. A FlexInference-layer rejection (type flexinference_error, e.g. missing_start_within, invalid_start_within, model_not_flex_capable, flex_unsupported_for_anthropic, service_tier_not_allowed) or an upstream provider rejection passed through unchanged. */
|
|
6138
6140
|
400: {
|
|
6139
6141
|
headers: {
|
|
6140
6142
|
[name: string]: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flexinference",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "Official TypeScript SDK for FlexInference - a deadline-aware, OpenAI-compatible inference router.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aditya Perswal <adityaperswal@gmail.com>",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"build": "tsc -p tsconfig.json",
|
|
35
35
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
36
|
"test:types": "tsc -p tsconfig.test.json --noEmit",
|
|
37
|
-
"test": "node test/smoke.mjs && node test/fixes.test.mjs && npm run test:types",
|
|
37
|
+
"test": "node test/smoke.mjs && node test/fixes.test.mjs && node test/parity.test.mjs && npm run test:types",
|
|
38
38
|
"lint": "eslint .",
|
|
39
39
|
"lint:fix": "eslint . --fix",
|
|
40
40
|
"format": "prettier --write .",
|