flexinference 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aditya Perswal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # FlexInference (TypeScript)
2
+
3
+ The official TypeScript SDK for [FlexInference](https://flexinference.com) - a deadline-aware, OpenAI-compatible inference router. Send the OpenAI requests you already send, bring your own OpenAI key, and add one field - `start_within` - to trade latency for cost.
4
+
5
+ ```bash
6
+ npm install flexinference
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```ts
12
+ import { FlexInference } from "flexinference";
13
+
14
+ const client = new FlexInference({ apiKey: "flex_live_..." });
15
+
16
+ const res = await client.responses.create({
17
+ model: "gpt-5.5",
18
+ input: "Write a haiku about cheap GPUs.",
19
+ start_within: "00h-00m-30s", // typed - no cast needed
20
+ });
21
+
22
+ console.log(res.output_text);
23
+ ```
24
+
25
+ `start_within` takes `"priority"`, `"standard"`, or a duration `"HHh-MMm-SSs"` (5s-10m) that races OpenAI's flex tier and falls back to standard if it can't start in time. See the [docs](https://flexinference.com/docs/deadline-routing).
26
+
27
+ ## Streaming
28
+
29
+ ```ts
30
+ const stream = await client.responses.create({
31
+ model: "gpt-5-nano",
32
+ input: "Count to ten.",
33
+ stream: true,
34
+ start_within: "00h-00m-20s",
35
+ });
36
+
37
+ for await (const event of stream) {
38
+ if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
39
+ }
40
+ ```
41
+
42
+ ## Chat Completions
43
+
44
+ The Chat Completions endpoint works identically:
45
+
46
+ ```ts
47
+ const res = await client.chat.completions.create({
48
+ model: "gpt-5.5",
49
+ messages: [{ role: "user", content: "Hello!" }],
50
+ start_within: "standard",
51
+ });
52
+ ```
53
+
54
+ ## Cancellation
55
+
56
+ Every call accepts an `AbortSignal` as a second argument, so you can cancel a request (or wire up your own timeout). Cancelling a stream stops it mid-flight:
57
+
58
+ ```ts
59
+ const controller = new AbortController();
60
+ const timeout = setTimeout(() => controller.abort(), 5_000);
61
+
62
+ try {
63
+ const res = await client.responses.create(
64
+ { model: "gpt-5.5", input: "Write a haiku about cheap GPUs.", start_within: "priority" },
65
+ { signal: controller.signal },
66
+ );
67
+ console.log(res.output_text);
68
+ } finally {
69
+ clearTimeout(timeout);
70
+ }
71
+ ```
72
+
73
+ ## Errors
74
+
75
+ Non-2xx responses throw `FlexInferenceError`, carrying the OpenAI-shaped `status`, `type`, `code`, and `param`:
76
+
77
+ ```ts
78
+ import { FlexInferenceError } from "flexinference";
79
+
80
+ try {
81
+ await client.responses.create({ model: "gpt-5.5", input: "hi", start_within: "priority" });
82
+ } catch (err) {
83
+ if (err instanceof FlexInferenceError && err.code === "no_byok_key") {
84
+ console.log("Add your OpenAI key in the dashboard.");
85
+ } else {
86
+ throw err;
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## Configuration
92
+
93
+ | Option | Default | Description |
94
+ | --------- | ---------------------------------- | -------------------------------------- |
95
+ | `apiKey` | - | Your `flex_live_` key (required). |
96
+ | `baseURL` | `https://api.flexinference.com/v1` | Override the router endpoint. |
97
+ | `fetch` | global `fetch` | Provide a custom fetch implementation. |
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,68 @@
1
+ import type { components } from "./types.js";
2
+ type Schemas = components["schemas"];
3
+ export type ResponseCreateParams = Schemas["CreateResponse"];
4
+ export type ResponseObject = Schemas["Response"];
5
+ export type ResponseStreamEvent = Schemas["ResponseStreamEvent"];
6
+ export type ChatCompletionCreateParams = Schemas["CreateChatCompletionRequest"];
7
+ export type ChatCompletion = Schemas["CreateChatCompletionResponse"];
8
+ export type ChatCompletionChunk = Schemas["CreateChatCompletionStreamResponse"];
9
+ export interface FlexErrorBody {
10
+ error: {
11
+ message: string;
12
+ type: string;
13
+ code?: string | null;
14
+ param?: string | null;
15
+ };
16
+ }
17
+ export declare class FlexInferenceError extends Error {
18
+ readonly status: number;
19
+ readonly type: string | undefined;
20
+ readonly code: string | null | undefined;
21
+ readonly param: string | null | undefined;
22
+ constructor(status: number, body: Partial<FlexErrorBody> | undefined, fallback: string);
23
+ }
24
+ export interface ClientOptions {
25
+ apiKey: string;
26
+ /** Base URL of the FlexInference router. Defaults to the hosted endpoint. */
27
+ baseURL?: string;
28
+ /** Override the global fetch (e.g. for tests or non-standard runtimes). */
29
+ fetch?: typeof fetch;
30
+ }
31
+ export interface RequestOptions {
32
+ signal?: AbortSignal;
33
+ }
34
+ type Post = (path: string, body: unknown, signal: AbortSignal | undefined) => Promise<Response>;
35
+ export declare class FlexInference {
36
+ readonly responses: Responses;
37
+ readonly chat: Chat;
38
+ private readonly apiKey;
39
+ private readonly baseURL;
40
+ private readonly fetchImpl;
41
+ constructor(options: ClientOptions);
42
+ private post;
43
+ }
44
+ declare class Responses {
45
+ private readonly post;
46
+ constructor(post: Post);
47
+ create(body: ResponseCreateParams & {
48
+ stream?: false | null;
49
+ }, options?: RequestOptions): Promise<ResponseObject>;
50
+ create(body: ResponseCreateParams & {
51
+ stream: true;
52
+ }, options?: RequestOptions): Promise<AsyncIterable<ResponseStreamEvent>>;
53
+ }
54
+ declare class Chat {
55
+ readonly completions: ChatCompletions;
56
+ constructor(post: Post);
57
+ }
58
+ declare class ChatCompletions {
59
+ private readonly post;
60
+ constructor(post: Post);
61
+ create(body: ChatCompletionCreateParams & {
62
+ stream?: false | null;
63
+ }, options?: RequestOptions): Promise<ChatCompletion>;
64
+ create(body: ChatCompletionCreateParams & {
65
+ stream: true;
66
+ }, options?: RequestOptions): Promise<AsyncIterable<ChatCompletionChunk>>;
67
+ }
68
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ export class FlexInferenceError extends Error {
2
+ status;
3
+ type;
4
+ code;
5
+ param;
6
+ constructor(status, body, fallback) {
7
+ const err = body?.error;
8
+ super(err?.message ?? fallback);
9
+ this.name = "FlexInferenceError";
10
+ this.status = status;
11
+ this.type = err?.type;
12
+ this.code = err?.code;
13
+ this.param = err?.param;
14
+ }
15
+ }
16
+ const DEFAULT_BASE_URL = "https://api.flexinference.com/v1";
17
+ export class FlexInference {
18
+ responses;
19
+ chat;
20
+ apiKey;
21
+ baseURL;
22
+ fetchImpl;
23
+ constructor(options) {
24
+ if (!options.apiKey)
25
+ throw new Error("FlexInference: `apiKey` is required.");
26
+ this.apiKey = options.apiKey;
27
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
28
+ const f = options.fetch ?? globalThis.fetch;
29
+ if (typeof f !== "function") {
30
+ throw new Error("FlexInference: no global fetch found; pass `fetch` in options.");
31
+ }
32
+ this.fetchImpl = f;
33
+ const post = (path, body, signal) => this.post(path, body, signal);
34
+ this.responses = new Responses(post);
35
+ this.chat = new Chat(post);
36
+ }
37
+ async post(path, body, signal) {
38
+ const res = await this.fetchImpl(`${this.baseURL}${path}`, {
39
+ method: "POST",
40
+ headers: {
41
+ Authorization: `Bearer ${this.apiKey}`,
42
+ "Content-Type": "application/json",
43
+ Accept: "text/event-stream, application/json",
44
+ },
45
+ body: JSON.stringify(body),
46
+ signal,
47
+ });
48
+ if (!res.ok) {
49
+ let parsed;
50
+ try {
51
+ parsed = (await res.json());
52
+ }
53
+ catch {
54
+ parsed = undefined;
55
+ }
56
+ throw new FlexInferenceError(res.status, parsed, `HTTP ${String(res.status)} ${res.statusText}`);
57
+ }
58
+ return res;
59
+ }
60
+ }
61
+ class Responses {
62
+ post;
63
+ constructor(post) {
64
+ this.post = post;
65
+ }
66
+ async create(body, options) {
67
+ const res = await this.post("/responses", body, options?.signal);
68
+ if (body.stream) {
69
+ return streamSSE(res);
70
+ }
71
+ return (await res.json());
72
+ }
73
+ }
74
+ class Chat {
75
+ completions;
76
+ constructor(post) {
77
+ this.completions = new ChatCompletions(post);
78
+ }
79
+ }
80
+ class ChatCompletions {
81
+ post;
82
+ constructor(post) {
83
+ this.post = post;
84
+ }
85
+ async create(body, options) {
86
+ const res = await this.post("/chat/completions", body, options?.signal);
87
+ if (body.stream) {
88
+ return streamSSE(res);
89
+ }
90
+ return (await res.json());
91
+ }
92
+ }
93
+ async function* streamSSE(res) {
94
+ if (!res.body)
95
+ throw new Error("FlexInference: streaming response has no body.");
96
+ const reader = res.body.getReader();
97
+ const decoder = new TextDecoder();
98
+ let buffer = "";
99
+ try {
100
+ for (;;) {
101
+ const { done, value } = await reader.read();
102
+ if (done)
103
+ break;
104
+ buffer += decoder.decode(value, { stream: true }).replace(/\r/g, "");
105
+ let sep;
106
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
107
+ const frame = buffer.slice(0, sep);
108
+ buffer = buffer.slice(sep + 2);
109
+ const dataLines = [];
110
+ for (const line of frame.split("\n")) {
111
+ if (line.startsWith("data:"))
112
+ dataLines.push(line.slice(5).replace(/^ /, ""));
113
+ }
114
+ if (dataLines.length === 0)
115
+ continue;
116
+ const data = dataLines.join("\n");
117
+ if (data === "[DONE]")
118
+ return;
119
+ yield JSON.parse(data);
120
+ }
121
+ }
122
+ }
123
+ finally {
124
+ reader.releaseLock();
125
+ }
126
+ }