@inferall/sdk 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 Kindly Robotics
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,134 @@
1
+ # @inferall/sdk
2
+
3
+ Official TypeScript SDK for the [InferAll](https://inferall.ai) AI gateway. One
4
+ API, every model provider — OpenAI, Anthropic, Google Gemini, Replicate, and
5
+ more — behind a single bearer token.
6
+
7
+ This is the TypeScript counterpart to the [`inferall-ai`](https://pypi.org/project/inferall-ai/)
8
+ Python package and mirrors its surface one-to-one.
9
+
10
+ > Zero runtime dependencies. The client uses only the platform `fetch` API, so
11
+ > it ships cleanly into Node, edge functions, and browsers. ESM, CJS, and full
12
+ > type declarations are all bundled.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @inferall/sdk
18
+ ```
19
+
20
+ Requires Node 18+ (or any runtime with a global `fetch`).
21
+
22
+ ## Quick start
23
+
24
+ ```ts
25
+ import { Inferall } from "@inferall/sdk";
26
+
27
+ const ai = new Inferall(); // reads INFERALL_API_KEY from the environment
28
+
29
+ console.log(await ai.text("Summarize the theory of relativity in two sentences."));
30
+ ```
31
+
32
+ ### Explicit credentials
33
+
34
+ ```ts
35
+ const ai = new Inferall({ apiKey: "sk-inferall-..." });
36
+ ```
37
+
38
+ ### System prompt + custom model
39
+
40
+ ```ts
41
+ await ai.text("Translate to French: 'Where is the library?'", {
42
+ system: "You are a careful translator.",
43
+ provider: "openai",
44
+ model: "gpt-4o-mini",
45
+ temperature: 0.2,
46
+ });
47
+ ```
48
+
49
+ ### Multi-turn chat
50
+
51
+ ```ts
52
+ await ai.chat([
53
+ { role: "user", content: "Hello!" },
54
+ { role: "assistant", content: "Hi there — how can I help?" },
55
+ { role: "user", content: "What's 2 + 2?" },
56
+ ]);
57
+ ```
58
+
59
+ ### Vision
60
+
61
+ ```ts
62
+ import { readFile } from "node:fs/promises";
63
+
64
+ const imageBase64 = (await readFile("receipt.jpg")).toString("base64");
65
+
66
+ await ai.vision(imageBase64, "What's the total on this receipt?");
67
+ ```
68
+
69
+ ### Raw responses
70
+
71
+ When you need access to tool calls, automatic-fallback metadata
72
+ (`fallback_used` / `actual_provider`), or any provider-specific field, use
73
+ `generate()` to get the full payload:
74
+
75
+ ```ts
76
+ const raw = await ai.generate({
77
+ provider: "anthropic",
78
+ model: "claude-3-5-sonnet-latest",
79
+ operation: "chat",
80
+ messages: [{ role: "user", content: "..." }],
81
+ });
82
+ ```
83
+
84
+ ## Environment variables
85
+
86
+ | Variable | Purpose |
87
+ | ------------------- | ------------------------------------------------------------------- |
88
+ | `INFERALL_API_KEY` | Bearer token. Used when `apiKey` isn't passed to the constructor. |
89
+ | `INFERALL_BASE_URL` | Override the gateway URL (default `https://api.inferall.ai`). |
90
+ | `AI_GATEWAY_KEY` | Legacy fallback for `INFERALL_API_KEY` (recognised for back-compat).|
91
+ | `AI_GATEWAY_URL` | Legacy fallback for `INFERALL_BASE_URL`. |
92
+
93
+ Environment variables are only read in runtimes that expose `process.env` (Node,
94
+ most edge functions). In the browser, pass `apiKey` / `baseURL` explicitly.
95
+
96
+ ## Errors
97
+
98
+ Gateway failures throw `InferallError`. A `status` of `0` indicates a
99
+ transport-level failure (network error or timeout) rather than an HTTP error
100
+ response.
101
+
102
+ ```ts
103
+ import { Inferall, InferallError } from "@inferall/sdk";
104
+
105
+ const ai = new Inferall();
106
+ try {
107
+ await ai.text("...");
108
+ } catch (err) {
109
+ if (err instanceof InferallError) {
110
+ console.error(err.status, err.body);
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## Python ↔ TypeScript
116
+
117
+ | Python (`inferall-ai`) | TypeScript (`@inferall/sdk`) |
118
+ | ---------------------------------- | ------------------------------------------- |
119
+ | `from inferall import Inferall` | `import { Inferall } from "@inferall/sdk"` |
120
+ | `Inferall(api_key=...)` | `new Inferall({ apiKey: ... })` |
121
+ | `ai.text(prompt, system=...)` | `ai.text(prompt, { system: ... })` |
122
+ | `ai.chat(messages)` | `ai.chat(messages)` |
123
+ | `ai.vision(img_b64, prompt)` | `ai.vision(imgBase64, prompt)` |
124
+ | `ai.generate(provider=..., ...)` | `ai.generate({ provider: ..., ... })` |
125
+ | `InferallError` | `InferallError` |
126
+
127
+ Keyword arguments map to a trailing options object; snake_case parameters become
128
+ camelCase (`max_tokens` → `maxTokens`, `base_url` → `baseURL`). Behaviour
129
+ (defaults, env-var resolution, the `/ai/v1/generate` route, and response
130
+ normalization) is identical.
131
+
132
+ ## License
133
+
134
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
24
+ DEFAULT_MODEL: () => DEFAULT_MODEL,
25
+ DEFAULT_PROVIDER: () => DEFAULT_PROVIDER,
26
+ Inferall: () => Inferall,
27
+ InferallError: () => InferallError,
28
+ VERSION: () => VERSION,
29
+ default: () => index_default
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+ var DEFAULT_BASE_URL = "https://api.inferall.ai";
33
+ var DEFAULT_PROVIDER = "gemini";
34
+ var DEFAULT_MODEL = "gemini-2.5-flash";
35
+ var VERSION = "0.1.0";
36
+ var USER_AGENT = `inferall-typescript/${VERSION}`;
37
+ var InferallError = class _InferallError extends Error {
38
+ constructor(status, body) {
39
+ super(`InferAll gateway returned ${status}: ${body}`);
40
+ this.name = "InferallError";
41
+ this.status = status;
42
+ this.body = body;
43
+ Object.setPrototypeOf(this, _InferallError.prototype);
44
+ }
45
+ };
46
+ function readEnv(name) {
47
+ const proc = globalThis.process;
48
+ return proc?.env?.[name];
49
+ }
50
+ function resolveApiKey(apiKey) {
51
+ if (apiKey) return apiKey;
52
+ return readEnv("INFERALL_API_KEY") || readEnv("AI_GATEWAY_KEY") || "";
53
+ }
54
+ function resolveBaseURL(baseURL) {
55
+ if (baseURL) return baseURL.replace(/\/+$/, "");
56
+ const env = readEnv("INFERALL_BASE_URL") || readEnv("AI_GATEWAY_URL");
57
+ return (env || DEFAULT_BASE_URL).replace(/\/+$/, "");
58
+ }
59
+ var Inferall = class _Inferall {
60
+ constructor(options = {}) {
61
+ this.apiKey = resolveApiKey(options.apiKey);
62
+ this.baseURL = resolveBaseURL(options.baseURL);
63
+ this.defaultProvider = options.defaultProvider ?? DEFAULT_PROVIDER;
64
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
65
+ this.timeout = options.timeout ?? 12e4;
66
+ }
67
+ // ---------------------------------------------------------------- public
68
+ /** Single-turn text completion. Prompt in, string out. */
69
+ async text(prompt, options = {}) {
70
+ const resp = await this.request({
71
+ provider: options.provider || this.defaultProvider,
72
+ model: options.model || this.defaultModel,
73
+ operation: "text",
74
+ messages: [{ role: "user", content: prompt }],
75
+ system: options.system,
76
+ temperature: options.temperature ?? 0.3,
77
+ maxTokens: options.maxTokens ?? 4096
78
+ });
79
+ return _Inferall.extractText(resp);
80
+ }
81
+ /** Multi-turn chat completion. Returns the assistant's response text. */
82
+ async chat(messages, options = {}) {
83
+ const resp = await this.request({
84
+ provider: options.provider || this.defaultProvider,
85
+ model: options.model || this.defaultModel,
86
+ operation: "chat",
87
+ messages,
88
+ system: options.system,
89
+ temperature: options.temperature ?? 0.3,
90
+ maxTokens: options.maxTokens ?? 4096
91
+ });
92
+ return _Inferall.extractText(resp);
93
+ }
94
+ /**
95
+ * Analyze an image (base64-encoded) with a text prompt.
96
+ *
97
+ * Resolves `provider` and `model` from the client's defaults so that
98
+ * `new Inferall({ defaultProvider: "openai" }).vision(...)` actually routes
99
+ * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.
100
+ */
101
+ async vision(imageBase64, prompt, options = {}) {
102
+ const resp = await this.request({
103
+ provider: options.provider || this.defaultProvider || "gemini",
104
+ model: options.model || this.defaultModel || "gemini-2.5-flash",
105
+ operation: "image-analyze",
106
+ messages: [{ role: "user", content: prompt }],
107
+ images: [imageBase64],
108
+ temperature: 0.3,
109
+ maxTokens: options.maxTokens ?? 4096
110
+ });
111
+ return _Inferall.extractText(resp);
112
+ }
113
+ /**
114
+ * Escape hatch — return the raw, provider-shaped response payload.
115
+ *
116
+ * Use this when you need access to tool calls, fallback metadata
117
+ * (`fallback_used` / `actual_provider`), streaming chunks, or any
118
+ * provider-specific field that the convenience helpers ({@link text},
119
+ * {@link chat}, {@link vision}) strip out.
120
+ */
121
+ async generate(options = {}) {
122
+ return this.request({
123
+ provider: options.provider || this.defaultProvider,
124
+ model: options.model || this.defaultModel,
125
+ operation: options.operation ?? "text",
126
+ messages: options.messages,
127
+ system: options.system,
128
+ images: options.images,
129
+ prompt: options.prompt,
130
+ temperature: options.temperature ?? 0.3,
131
+ maxTokens: options.maxTokens ?? 4096
132
+ });
133
+ }
134
+ // -------------------------------------------------------------- internal
135
+ async request(args) {
136
+ if (!this.apiKey) {
137
+ throw new Error(
138
+ "InferAll API key not set. Pass { apiKey } to the constructor or set the INFERALL_API_KEY environment variable (legacy AI_GATEWAY_KEY is also accepted as a fallback)."
139
+ );
140
+ }
141
+ const body = {
142
+ provider: args.provider,
143
+ model: args.model,
144
+ operation: args.operation,
145
+ config: { temperature: args.temperature, maxTokens: args.maxTokens }
146
+ };
147
+ if (args.messages && args.messages.length > 0) body.messages = args.messages;
148
+ if (args.system) body.system = args.system;
149
+ if (args.images && args.images.length > 0) body.images = args.images;
150
+ if (args.prompt) body.prompt = args.prompt;
151
+ const controller = new AbortController();
152
+ const timer = setTimeout(() => controller.abort(), this.timeout);
153
+ let response;
154
+ try {
155
+ response = await fetch(`${this.baseURL}/ai/v1/generate`, {
156
+ method: "POST",
157
+ headers: {
158
+ Authorization: `Bearer ${this.apiKey}`,
159
+ "Content-Type": "application/json",
160
+ "User-Agent": USER_AGENT
161
+ },
162
+ body: JSON.stringify(body),
163
+ signal: controller.signal
164
+ });
165
+ } catch (err) {
166
+ if (err instanceof DOMException && err.name === "AbortError") {
167
+ throw new InferallError(0, `Request timed out after ${this.timeout}ms`);
168
+ }
169
+ const reason = err instanceof Error ? err.message : String(err);
170
+ throw new InferallError(0, `Network error: ${reason}`);
171
+ } finally {
172
+ clearTimeout(timer);
173
+ }
174
+ if (!response.ok) {
175
+ const errorBody = await response.text().catch(() => "");
176
+ throw new InferallError(response.status, errorBody);
177
+ }
178
+ try {
179
+ return await response.json();
180
+ } catch (err) {
181
+ const reason = err instanceof Error ? err.message : String(err);
182
+ throw new InferallError(0, `Failed to parse gateway response: ${reason}`);
183
+ }
184
+ }
185
+ /**
186
+ * Extract text from any supported provider's response format.
187
+ *
188
+ * For providers that return multiple text segments in a single response
189
+ * (Gemini `parts`, Anthropic `content` blocks), all text segments are
190
+ * concatenated with newlines so callers see the full message rather than a
191
+ * silently-truncated first chunk.
192
+ *
193
+ * Throws {@link InferallError} if the response does not match any recognized
194
+ * shape — we never coerce arbitrary JSON into a string.
195
+ */
196
+ static extractText(resp) {
197
+ const candidates = resp["candidates"];
198
+ if (Array.isArray(candidates) && candidates.length > 0) {
199
+ const parts = candidates[0]?.["content"];
200
+ const partList = parts?.["parts"];
201
+ if (Array.isArray(partList)) {
202
+ const texts = partList.filter((p) => isRecord(p) && typeof p["text"] === "string").map((p) => p["text"]);
203
+ if (texts.length > 0) return texts.join("\n");
204
+ }
205
+ }
206
+ const choices = resp["choices"];
207
+ if (Array.isArray(choices) && choices.length > 0) {
208
+ const message = choices[0]?.["message"];
209
+ const content2 = message?.["content"];
210
+ return typeof content2 === "string" ? content2 : "";
211
+ }
212
+ const content = resp["content"];
213
+ if (Array.isArray(content)) {
214
+ const texts = content.filter((block) => isRecord(block) && Boolean(block["text"])).map((block) => block["text"]);
215
+ if (texts.length > 0) return texts.join("\n");
216
+ }
217
+ const output = resp["output"];
218
+ if (typeof output === "string") return output;
219
+ if (Array.isArray(output) && output.length > 0 && typeof output[0] === "string") {
220
+ return output[0];
221
+ }
222
+ throw new InferallError(0, `Unrecognized response shape: keys=${JSON.stringify(Object.keys(resp))}`);
223
+ }
224
+ };
225
+ function isRecord(value) {
226
+ return typeof value === "object" && value !== null;
227
+ }
228
+ var index_default = Inferall;
229
+ // Annotate the CommonJS export names for ESM import in node:
230
+ 0 && (module.exports = {
231
+ DEFAULT_BASE_URL,
232
+ DEFAULT_MODEL,
233
+ DEFAULT_PROVIDER,
234
+ Inferall,
235
+ InferallError,
236
+ VERSION
237
+ });
238
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * InferAll TypeScript client.\n *\n * The {@link Inferall} client speaks to the InferAll gateway over a single HTTP\n * endpoint and normalizes responses across providers (OpenAI, Anthropic, Google\n * Gemini, Replicate, and more).\n *\n * Quick start:\n *\n * ```ts\n * import { Inferall } from \"@inferall/sdk\";\n *\n * const ai = new Inferall(); // picks up INFERALL_API_KEY from the environment\n *\n * console.log(await ai.text(\"Summarize the theory of relativity in two sentences.\"));\n * ```\n *\n * This is a faithful port of the published `inferall-ai` Python package. The\n * client depends only on the platform `fetch` API so it can be dropped into\n * lambdas, edge functions, and browsers without pulling in transitive\n * dependencies.\n */\n\nexport const DEFAULT_BASE_URL = \"https://api.inferall.ai\";\nexport const DEFAULT_PROVIDER = \"gemini\";\nexport const DEFAULT_MODEL = \"gemini-2.5-flash\";\n\n/** Package version, mirrored from package.json. */\nexport const VERSION = \"0.1.0\";\n\nconst USER_AGENT = `inferall-typescript/${VERSION}`;\n\n/**\n * A single chat message. Mirrors the Python SDK's `list[dict]` message shape;\n * arbitrary provider-specific keys are allowed.\n */\nexport interface Message {\n role: string;\n content: unknown;\n [key: string]: unknown;\n}\n\n/** A provider-shaped response payload, as returned by {@link Inferall.generate}. */\nexport type GenerateResponse = Record<string, unknown>;\n\n/** Options for the {@link Inferall} constructor. */\nexport interface InferallOptions {\n /**\n * Bearer token for the gateway. If omitted, the client reads\n * `INFERALL_API_KEY` (preferred) or `AI_GATEWAY_KEY` (legacy) from the\n * environment.\n */\n apiKey?: string;\n /**\n * Override the gateway URL. Defaults to `https://api.inferall.ai`\n * (or `INFERALL_BASE_URL` / `AI_GATEWAY_URL` from the environment).\n */\n baseURL?: string;\n /** Provider used when a call site does not pass `provider`. */\n defaultProvider?: string;\n /** Model used when a call site does not pass `model`. */\n defaultModel?: string;\n /**\n * Per-request timeout, in milliseconds. Defaults to 120000ms (120s) to\n * accommodate large completions.\n */\n timeout?: number;\n}\n\n/** Options for {@link Inferall.text}. */\nexport interface TextOptions {\n system?: string;\n provider?: string;\n model?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.chat}. */\nexport interface ChatOptions {\n system?: string;\n provider?: string;\n model?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.vision}. */\nexport interface VisionOptions {\n provider?: string;\n model?: string;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.generate}. */\nexport interface GenerateOptions {\n provider?: string;\n model?: string;\n /** @default \"text\" */\n operation?: string;\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n provider: string;\n model: string;\n operation: string;\n config: { temperature: number; maxTokens: number };\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n}\n\n/**\n * Raised when the InferAll gateway returns an error or the request fails.\n *\n * Mirrors the Python `InferallError` (a subclass of `RuntimeError`). A `status`\n * of `0` indicates a transport-level failure (network error, timeout) rather\n * than an HTTP error response.\n */\nexport class InferallError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`InferAll gateway returned ${status}: ${body}`);\n this.name = \"InferallError\";\n this.status = status;\n this.body = body;\n // Restore prototype chain for instanceof across transpilation targets.\n Object.setPrototypeOf(this, InferallError.prototype);\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n // Guard against non-Node runtimes (browsers, some edge functions) where\n // `process` is undefined. There the caller must pass apiKey/baseURL explicitly.\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return proc?.env?.[name];\n}\n\n/**\n * Return the API key from the explicit arg or environment.\n *\n * Honors `INFERALL_API_KEY` first, then falls back to `AI_GATEWAY_KEY` for\n * backwards compatibility with the original internal client.\n */\nfunction resolveApiKey(apiKey: string | undefined): string {\n if (apiKey) return apiKey;\n return readEnv(\"INFERALL_API_KEY\") || readEnv(\"AI_GATEWAY_KEY\") || \"\";\n}\n\nfunction resolveBaseURL(baseURL: string | undefined): string {\n if (baseURL) return baseURL.replace(/\\/+$/, \"\");\n const env = readEnv(\"INFERALL_BASE_URL\") || readEnv(\"AI_GATEWAY_URL\");\n return (env || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n}\n\n/**\n * Client for the InferAll AI gateway.\n *\n * @example\n * ```ts\n * const ai = new Inferall(); // env-based key\n * const ai = new Inferall({ apiKey: \"sk-…\" }); // explicit key\n * ```\n */\nexport class Inferall {\n readonly apiKey: string;\n readonly baseURL: string;\n readonly defaultProvider: string;\n readonly defaultModel: string;\n readonly timeout: number;\n\n constructor(options: InferallOptions = {}) {\n this.apiKey = resolveApiKey(options.apiKey);\n this.baseURL = resolveBaseURL(options.baseURL);\n this.defaultProvider = options.defaultProvider ?? DEFAULT_PROVIDER;\n this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;\n this.timeout = options.timeout ?? 120_000;\n }\n\n // ---------------------------------------------------------------- public\n\n /** Single-turn text completion. Prompt in, string out. */\n async text(prompt: string, options: TextOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: \"text\",\n messages: [{ role: \"user\", content: prompt }],\n system: options.system,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /** Multi-turn chat completion. Returns the assistant's response text. */\n async chat(messages: Message[], options: ChatOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: \"chat\",\n messages,\n system: options.system,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /**\n * Analyze an image (base64-encoded) with a text prompt.\n *\n * Resolves `provider` and `model` from the client's defaults so that\n * `new Inferall({ defaultProvider: \"openai\" }).vision(...)` actually routes\n * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.\n */\n async vision(imageBase64: string, prompt: string, options: VisionOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider || \"gemini\",\n model: options.model || this.defaultModel || \"gemini-2.5-flash\",\n operation: \"image-analyze\",\n messages: [{ role: \"user\", content: prompt }],\n images: [imageBase64],\n temperature: 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /**\n * Escape hatch — return the raw, provider-shaped response payload.\n *\n * Use this when you need access to tool calls, fallback metadata\n * (`fallback_used` / `actual_provider`), streaming chunks, or any\n * provider-specific field that the convenience helpers ({@link text},\n * {@link chat}, {@link vision}) strip out.\n */\n async generate(options: GenerateOptions = {}): Promise<GenerateResponse> {\n return this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: options.operation ?? \"text\",\n messages: options.messages,\n system: options.system,\n images: options.images,\n prompt: options.prompt,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n }\n\n // -------------------------------------------------------------- internal\n\n private async request(args: {\n provider: string;\n model: string;\n operation: string;\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n temperature: number;\n maxTokens: number;\n }): Promise<GenerateResponse> {\n if (!this.apiKey) {\n throw new Error(\n \"InferAll API key not set. Pass { apiKey } to the constructor or set \" +\n \"the INFERALL_API_KEY environment variable \" +\n \"(legacy AI_GATEWAY_KEY is also accepted as a fallback).\"\n );\n }\n\n const body: RequestBody = {\n provider: args.provider,\n model: args.model,\n operation: args.operation,\n config: { temperature: args.temperature, maxTokens: args.maxTokens },\n };\n if (args.messages && args.messages.length > 0) body.messages = args.messages;\n if (args.system) body.system = args.system;\n if (args.images && args.images.length > 0) body.images = args.images;\n if (args.prompt) body.prompt = args.prompt;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n let response: Response;\n try {\n response = await fetch(`${this.baseURL}/ai/v1/generate`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new InferallError(0, `Request timed out after ${this.timeout}ms`);\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new InferallError(0, `Network error: ${reason}`);\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new InferallError(response.status, errorBody);\n }\n\n try {\n return (await response.json()) as GenerateResponse;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new InferallError(0, `Failed to parse gateway response: ${reason}`);\n }\n }\n\n /**\n * Extract text from any supported provider's response format.\n *\n * For providers that return multiple text segments in a single response\n * (Gemini `parts`, Anthropic `content` blocks), all text segments are\n * concatenated with newlines so callers see the full message rather than a\n * silently-truncated first chunk.\n *\n * Throws {@link InferallError} if the response does not match any recognized\n * shape — we never coerce arbitrary JSON into a string.\n */\n static extractText(resp: GenerateResponse): string {\n // Gemini\n const candidates = resp[\"candidates\"];\n if (Array.isArray(candidates) && candidates.length > 0) {\n const parts = (candidates[0] as Record<string, unknown> | undefined)?.[\"content\"];\n const partList = (parts as Record<string, unknown> | undefined)?.[\"parts\"];\n if (Array.isArray(partList)) {\n const texts = partList\n .filter((p): p is Record<string, unknown> => isRecord(p) && typeof p[\"text\"] === \"string\")\n .map((p) => p[\"text\"] as string);\n if (texts.length > 0) return texts.join(\"\\n\");\n }\n }\n\n // OpenAI\n const choices = resp[\"choices\"];\n if (Array.isArray(choices) && choices.length > 0) {\n const message = (choices[0] as Record<string, unknown> | undefined)?.[\"message\"];\n const content = (message as Record<string, unknown> | undefined)?.[\"content\"];\n return typeof content === \"string\" ? content : \"\";\n }\n\n // Anthropic\n const content = resp[\"content\"];\n if (Array.isArray(content)) {\n const texts = content\n .filter((block): block is Record<string, unknown> => isRecord(block) && Boolean(block[\"text\"]))\n .map((block) => block[\"text\"] as string);\n if (texts.length > 0) return texts.join(\"\\n\");\n }\n\n // Replicate\n const output = resp[\"output\"];\n if (typeof output === \"string\") return output;\n if (Array.isArray(output) && output.length > 0 && typeof output[0] === \"string\") {\n return output[0];\n }\n\n throw new InferallError(0, `Unrecognized response shape: keys=${JSON.stringify(Object.keys(resp))}`);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport default Inferall;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBO,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AAGtB,IAAM,UAAU;AAEvB,IAAM,aAAa,uBAAuB,OAAO;AAuG1C,IAAM,gBAAN,MAAM,uBAAsB,MAAM;AAAA,EAIvC,YAAY,QAAgB,MAAc;AACxC,UAAM,6BAA6B,MAAM,KAAK,IAAI,EAAE;AACpD,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAEZ,WAAO,eAAe,MAAM,eAAc,SAAS;AAAA,EACrD;AACF;AAEA,SAAS,QAAQ,MAAkC;AAGjD,QAAM,OAAQ,WAA0E;AACxF,SAAO,MAAM,MAAM,IAAI;AACzB;AAQA,SAAS,cAAc,QAAoC;AACzD,MAAI,OAAQ,QAAO;AACnB,SAAO,QAAQ,kBAAkB,KAAK,QAAQ,gBAAgB,KAAK;AACrE;AAEA,SAAS,eAAe,SAAqC;AAC3D,MAAI,QAAS,QAAO,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,QAAM,MAAM,QAAQ,mBAAmB,KAAK,QAAQ,gBAAgB;AACpE,UAAQ,OAAO,kBAAkB,QAAQ,QAAQ,EAAE;AACrD;AAWO,IAAM,WAAN,MAAM,UAAS;AAAA,EAOpB,YAAY,UAA2B,CAAC,GAAG;AACzC,SAAK,SAAS,cAAc,QAAQ,MAAM;AAC1C,SAAK,UAAU,eAAe,QAAQ,OAAO;AAC7C,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,UAAU,QAAQ,WAAW;AAAA,EACpC;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,QAAgB,UAAuB,CAAC,GAAoB;AACrE,UAAM,OAAO,MAAM,KAAK,QAAQ;AAAA,MAC9B,UAAU,QAAQ,YAAY,KAAK;AAAA,MACnC,OAAO,QAAQ,SAAS,KAAK;AAAA,MAC7B,WAAW;AAAA,MACX,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,MAC5C,QAAQ,QAAQ;AAAA,MAChB,aAAa,QAAQ,eAAe;AAAA,MACpC,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AACD,WAAO,UAAS,YAAY,IAAI;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,KAAK,UAAqB,UAAuB,CAAC,GAAoB;AAC1E,UAAM,OAAO,MAAM,KAAK,QAAQ;AAAA,MAC9B,UAAU,QAAQ,YAAY,KAAK;AAAA,MACnC,OAAO,QAAQ,SAAS,KAAK;AAAA,MAC7B,WAAW;AAAA,MACX;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,aAAa,QAAQ,eAAe;AAAA,MACpC,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AACD,WAAO,UAAS,YAAY,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,aAAqB,QAAgB,UAAyB,CAAC,GAAoB;AAC9F,UAAM,OAAO,MAAM,KAAK,QAAQ;AAAA,MAC9B,UAAU,QAAQ,YAAY,KAAK,mBAAmB;AAAA,MACtD,OAAO,QAAQ,SAAS,KAAK,gBAAgB;AAAA,MAC7C,WAAW;AAAA,MACX,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,MAC5C,QAAQ,CAAC,WAAW;AAAA,MACpB,aAAa;AAAA,MACb,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AACD,WAAO,UAAS,YAAY,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,SAAS,UAA2B,CAAC,GAA8B;AACvE,WAAO,KAAK,QAAQ;AAAA,MAClB,UAAU,QAAQ,YAAY,KAAK;AAAA,MACnC,OAAO,QAAQ,SAAS,KAAK;AAAA,MAC7B,WAAW,QAAQ,aAAa;AAAA,MAChC,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,aAAa,QAAQ,eAAe;AAAA,MACpC,WAAW,QAAQ,aAAa;AAAA,IAClC,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAc,QAAQ,MAUQ;AAC5B,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MAGF;AAAA,IACF;AAEA,UAAM,OAAoB;AAAA,MACxB,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,QAAQ,EAAE,aAAa,KAAK,aAAa,WAAW,KAAK,UAAU;AAAA,IACrE;AACA,QAAI,KAAK,YAAY,KAAK,SAAS,SAAS,EAAG,MAAK,WAAW,KAAK;AACpE,QAAI,KAAK,OAAQ,MAAK,SAAS,KAAK;AACpC,QAAI,KAAK,UAAU,KAAK,OAAO,SAAS,EAAG,MAAK,SAAS,KAAK;AAC9D,QAAI,KAAK,OAAQ,MAAK,SAAS,KAAK;AAEpC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,GAAG,KAAK,OAAO,mBAAmB;AAAA,QACvD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,KAAK,MAAM;AAAA,UACpC,gBAAgB;AAAA,UAChB,cAAc;AAAA,QAChB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,cAAM,IAAI,cAAc,GAAG,2BAA2B,KAAK,OAAO,IAAI;AAAA,MACxE;AACA,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,cAAc,GAAG,kBAAkB,MAAM,EAAE;AAAA,IACvD,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,YAAM,IAAI,cAAc,SAAS,QAAQ,SAAS;AAAA,IACpD;AAEA,QAAI;AACF,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,cAAc,GAAG,qCAAqC,MAAM,EAAE;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,YAAY,MAAgC;AAEjD,UAAM,aAAa,KAAK,YAAY;AACpC,QAAI,MAAM,QAAQ,UAAU,KAAK,WAAW,SAAS,GAAG;AACtD,YAAM,QAAS,WAAW,CAAC,IAA4C,SAAS;AAChF,YAAM,WAAY,QAAgD,OAAO;AACzE,UAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,cAAM,QAAQ,SACX,OAAO,CAAC,MAAoC,SAAS,CAAC,KAAK,OAAO,EAAE,MAAM,MAAM,QAAQ,EACxF,IAAI,CAAC,MAAM,EAAE,MAAM,CAAW;AACjC,YAAI,MAAM,SAAS,EAAG,QAAO,MAAM,KAAK,IAAI;AAAA,MAC9C;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,SAAS;AAC9B,QAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GAAG;AAChD,YAAM,UAAW,QAAQ,CAAC,IAA4C,SAAS;AAC/E,YAAMA,WAAW,UAAkD,SAAS;AAC5E,aAAO,OAAOA,aAAY,WAAWA,WAAU;AAAA,IACjD;AAGA,UAAM,UAAU,KAAK,SAAS;AAC9B,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,QAAQ,QACX,OAAO,CAAC,UAA4C,SAAS,KAAK,KAAK,QAAQ,MAAM,MAAM,CAAC,CAAC,EAC7F,IAAI,CAAC,UAAU,MAAM,MAAM,CAAW;AACzC,UAAI,MAAM,SAAS,EAAG,QAAO,MAAM,KAAK,IAAI;AAAA,IAC9C;AAGA,UAAM,SAAS,KAAK,QAAQ;AAC5B,QAAI,OAAO,WAAW,SAAU,QAAO;AACvC,QAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,KAAK,OAAO,OAAO,CAAC,MAAM,UAAU;AAC/E,aAAO,OAAO,CAAC;AAAA,IACjB;AAEA,UAAM,IAAI,cAAc,GAAG,qCAAqC,KAAK,UAAU,OAAO,KAAK,IAAI,CAAC,CAAC,EAAE;AAAA,EACrG;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,IAAO,gBAAQ;","names":["content"]}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * InferAll TypeScript client.
3
+ *
4
+ * The {@link Inferall} client speaks to the InferAll gateway over a single HTTP
5
+ * endpoint and normalizes responses across providers (OpenAI, Anthropic, Google
6
+ * Gemini, Replicate, and more).
7
+ *
8
+ * Quick start:
9
+ *
10
+ * ```ts
11
+ * import { Inferall } from "@inferall/sdk";
12
+ *
13
+ * const ai = new Inferall(); // picks up INFERALL_API_KEY from the environment
14
+ *
15
+ * console.log(await ai.text("Summarize the theory of relativity in two sentences."));
16
+ * ```
17
+ *
18
+ * This is a faithful port of the published `inferall-ai` Python package. The
19
+ * client depends only on the platform `fetch` API so it can be dropped into
20
+ * lambdas, edge functions, and browsers without pulling in transitive
21
+ * dependencies.
22
+ */
23
+ declare const DEFAULT_BASE_URL = "https://api.inferall.ai";
24
+ declare const DEFAULT_PROVIDER = "gemini";
25
+ declare const DEFAULT_MODEL = "gemini-2.5-flash";
26
+ /** Package version, mirrored from package.json. */
27
+ declare const VERSION = "0.1.0";
28
+ /**
29
+ * A single chat message. Mirrors the Python SDK's `list[dict]` message shape;
30
+ * arbitrary provider-specific keys are allowed.
31
+ */
32
+ interface Message {
33
+ role: string;
34
+ content: unknown;
35
+ [key: string]: unknown;
36
+ }
37
+ /** A provider-shaped response payload, as returned by {@link Inferall.generate}. */
38
+ type GenerateResponse = Record<string, unknown>;
39
+ /** Options for the {@link Inferall} constructor. */
40
+ interface InferallOptions {
41
+ /**
42
+ * Bearer token for the gateway. If omitted, the client reads
43
+ * `INFERALL_API_KEY` (preferred) or `AI_GATEWAY_KEY` (legacy) from the
44
+ * environment.
45
+ */
46
+ apiKey?: string;
47
+ /**
48
+ * Override the gateway URL. Defaults to `https://api.inferall.ai`
49
+ * (or `INFERALL_BASE_URL` / `AI_GATEWAY_URL` from the environment).
50
+ */
51
+ baseURL?: string;
52
+ /** Provider used when a call site does not pass `provider`. */
53
+ defaultProvider?: string;
54
+ /** Model used when a call site does not pass `model`. */
55
+ defaultModel?: string;
56
+ /**
57
+ * Per-request timeout, in milliseconds. Defaults to 120000ms (120s) to
58
+ * accommodate large completions.
59
+ */
60
+ timeout?: number;
61
+ }
62
+ /** Options for {@link Inferall.text}. */
63
+ interface TextOptions {
64
+ system?: string;
65
+ provider?: string;
66
+ model?: string;
67
+ /** @default 0.3 */
68
+ temperature?: number;
69
+ /** @default 4096 */
70
+ maxTokens?: number;
71
+ }
72
+ /** Options for {@link Inferall.chat}. */
73
+ interface ChatOptions {
74
+ system?: string;
75
+ provider?: string;
76
+ model?: string;
77
+ /** @default 0.3 */
78
+ temperature?: number;
79
+ /** @default 4096 */
80
+ maxTokens?: number;
81
+ }
82
+ /** Options for {@link Inferall.vision}. */
83
+ interface VisionOptions {
84
+ provider?: string;
85
+ model?: string;
86
+ /** @default 4096 */
87
+ maxTokens?: number;
88
+ }
89
+ /** Options for {@link Inferall.generate}. */
90
+ interface GenerateOptions {
91
+ provider?: string;
92
+ model?: string;
93
+ /** @default "text" */
94
+ operation?: string;
95
+ messages?: Message[];
96
+ system?: string;
97
+ images?: string[];
98
+ prompt?: string;
99
+ /** @default 0.3 */
100
+ temperature?: number;
101
+ /** @default 4096 */
102
+ maxTokens?: number;
103
+ }
104
+ /**
105
+ * Raised when the InferAll gateway returns an error or the request fails.
106
+ *
107
+ * Mirrors the Python `InferallError` (a subclass of `RuntimeError`). A `status`
108
+ * of `0` indicates a transport-level failure (network error, timeout) rather
109
+ * than an HTTP error response.
110
+ */
111
+ declare class InferallError extends Error {
112
+ readonly status: number;
113
+ readonly body: string;
114
+ constructor(status: number, body: string);
115
+ }
116
+ /**
117
+ * Client for the InferAll AI gateway.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const ai = new Inferall(); // env-based key
122
+ * const ai = new Inferall({ apiKey: "sk-…" }); // explicit key
123
+ * ```
124
+ */
125
+ declare class Inferall {
126
+ readonly apiKey: string;
127
+ readonly baseURL: string;
128
+ readonly defaultProvider: string;
129
+ readonly defaultModel: string;
130
+ readonly timeout: number;
131
+ constructor(options?: InferallOptions);
132
+ /** Single-turn text completion. Prompt in, string out. */
133
+ text(prompt: string, options?: TextOptions): Promise<string>;
134
+ /** Multi-turn chat completion. Returns the assistant's response text. */
135
+ chat(messages: Message[], options?: ChatOptions): Promise<string>;
136
+ /**
137
+ * Analyze an image (base64-encoded) with a text prompt.
138
+ *
139
+ * Resolves `provider` and `model` from the client's defaults so that
140
+ * `new Inferall({ defaultProvider: "openai" }).vision(...)` actually routes
141
+ * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.
142
+ */
143
+ vision(imageBase64: string, prompt: string, options?: VisionOptions): Promise<string>;
144
+ /**
145
+ * Escape hatch — return the raw, provider-shaped response payload.
146
+ *
147
+ * Use this when you need access to tool calls, fallback metadata
148
+ * (`fallback_used` / `actual_provider`), streaming chunks, or any
149
+ * provider-specific field that the convenience helpers ({@link text},
150
+ * {@link chat}, {@link vision}) strip out.
151
+ */
152
+ generate(options?: GenerateOptions): Promise<GenerateResponse>;
153
+ private request;
154
+ /**
155
+ * Extract text from any supported provider's response format.
156
+ *
157
+ * For providers that return multiple text segments in a single response
158
+ * (Gemini `parts`, Anthropic `content` blocks), all text segments are
159
+ * concatenated with newlines so callers see the full message rather than a
160
+ * silently-truncated first chunk.
161
+ *
162
+ * Throws {@link InferallError} if the response does not match any recognized
163
+ * shape — we never coerce arbitrary JSON into a string.
164
+ */
165
+ static extractText(resp: GenerateResponse): string;
166
+ }
167
+
168
+ export { type ChatOptions, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PROVIDER, type GenerateOptions, type GenerateResponse, Inferall, InferallError, type InferallOptions, type Message, type TextOptions, VERSION, type VisionOptions, Inferall as default };
@@ -0,0 +1,168 @@
1
+ /**
2
+ * InferAll TypeScript client.
3
+ *
4
+ * The {@link Inferall} client speaks to the InferAll gateway over a single HTTP
5
+ * endpoint and normalizes responses across providers (OpenAI, Anthropic, Google
6
+ * Gemini, Replicate, and more).
7
+ *
8
+ * Quick start:
9
+ *
10
+ * ```ts
11
+ * import { Inferall } from "@inferall/sdk";
12
+ *
13
+ * const ai = new Inferall(); // picks up INFERALL_API_KEY from the environment
14
+ *
15
+ * console.log(await ai.text("Summarize the theory of relativity in two sentences."));
16
+ * ```
17
+ *
18
+ * This is a faithful port of the published `inferall-ai` Python package. The
19
+ * client depends only on the platform `fetch` API so it can be dropped into
20
+ * lambdas, edge functions, and browsers without pulling in transitive
21
+ * dependencies.
22
+ */
23
+ declare const DEFAULT_BASE_URL = "https://api.inferall.ai";
24
+ declare const DEFAULT_PROVIDER = "gemini";
25
+ declare const DEFAULT_MODEL = "gemini-2.5-flash";
26
+ /** Package version, mirrored from package.json. */
27
+ declare const VERSION = "0.1.0";
28
+ /**
29
+ * A single chat message. Mirrors the Python SDK's `list[dict]` message shape;
30
+ * arbitrary provider-specific keys are allowed.
31
+ */
32
+ interface Message {
33
+ role: string;
34
+ content: unknown;
35
+ [key: string]: unknown;
36
+ }
37
+ /** A provider-shaped response payload, as returned by {@link Inferall.generate}. */
38
+ type GenerateResponse = Record<string, unknown>;
39
+ /** Options for the {@link Inferall} constructor. */
40
+ interface InferallOptions {
41
+ /**
42
+ * Bearer token for the gateway. If omitted, the client reads
43
+ * `INFERALL_API_KEY` (preferred) or `AI_GATEWAY_KEY` (legacy) from the
44
+ * environment.
45
+ */
46
+ apiKey?: string;
47
+ /**
48
+ * Override the gateway URL. Defaults to `https://api.inferall.ai`
49
+ * (or `INFERALL_BASE_URL` / `AI_GATEWAY_URL` from the environment).
50
+ */
51
+ baseURL?: string;
52
+ /** Provider used when a call site does not pass `provider`. */
53
+ defaultProvider?: string;
54
+ /** Model used when a call site does not pass `model`. */
55
+ defaultModel?: string;
56
+ /**
57
+ * Per-request timeout, in milliseconds. Defaults to 120000ms (120s) to
58
+ * accommodate large completions.
59
+ */
60
+ timeout?: number;
61
+ }
62
+ /** Options for {@link Inferall.text}. */
63
+ interface TextOptions {
64
+ system?: string;
65
+ provider?: string;
66
+ model?: string;
67
+ /** @default 0.3 */
68
+ temperature?: number;
69
+ /** @default 4096 */
70
+ maxTokens?: number;
71
+ }
72
+ /** Options for {@link Inferall.chat}. */
73
+ interface ChatOptions {
74
+ system?: string;
75
+ provider?: string;
76
+ model?: string;
77
+ /** @default 0.3 */
78
+ temperature?: number;
79
+ /** @default 4096 */
80
+ maxTokens?: number;
81
+ }
82
+ /** Options for {@link Inferall.vision}. */
83
+ interface VisionOptions {
84
+ provider?: string;
85
+ model?: string;
86
+ /** @default 4096 */
87
+ maxTokens?: number;
88
+ }
89
+ /** Options for {@link Inferall.generate}. */
90
+ interface GenerateOptions {
91
+ provider?: string;
92
+ model?: string;
93
+ /** @default "text" */
94
+ operation?: string;
95
+ messages?: Message[];
96
+ system?: string;
97
+ images?: string[];
98
+ prompt?: string;
99
+ /** @default 0.3 */
100
+ temperature?: number;
101
+ /** @default 4096 */
102
+ maxTokens?: number;
103
+ }
104
+ /**
105
+ * Raised when the InferAll gateway returns an error or the request fails.
106
+ *
107
+ * Mirrors the Python `InferallError` (a subclass of `RuntimeError`). A `status`
108
+ * of `0` indicates a transport-level failure (network error, timeout) rather
109
+ * than an HTTP error response.
110
+ */
111
+ declare class InferallError extends Error {
112
+ readonly status: number;
113
+ readonly body: string;
114
+ constructor(status: number, body: string);
115
+ }
116
+ /**
117
+ * Client for the InferAll AI gateway.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const ai = new Inferall(); // env-based key
122
+ * const ai = new Inferall({ apiKey: "sk-…" }); // explicit key
123
+ * ```
124
+ */
125
+ declare class Inferall {
126
+ readonly apiKey: string;
127
+ readonly baseURL: string;
128
+ readonly defaultProvider: string;
129
+ readonly defaultModel: string;
130
+ readonly timeout: number;
131
+ constructor(options?: InferallOptions);
132
+ /** Single-turn text completion. Prompt in, string out. */
133
+ text(prompt: string, options?: TextOptions): Promise<string>;
134
+ /** Multi-turn chat completion. Returns the assistant's response text. */
135
+ chat(messages: Message[], options?: ChatOptions): Promise<string>;
136
+ /**
137
+ * Analyze an image (base64-encoded) with a text prompt.
138
+ *
139
+ * Resolves `provider` and `model` from the client's defaults so that
140
+ * `new Inferall({ defaultProvider: "openai" }).vision(...)` actually routes
141
+ * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.
142
+ */
143
+ vision(imageBase64: string, prompt: string, options?: VisionOptions): Promise<string>;
144
+ /**
145
+ * Escape hatch — return the raw, provider-shaped response payload.
146
+ *
147
+ * Use this when you need access to tool calls, fallback metadata
148
+ * (`fallback_used` / `actual_provider`), streaming chunks, or any
149
+ * provider-specific field that the convenience helpers ({@link text},
150
+ * {@link chat}, {@link vision}) strip out.
151
+ */
152
+ generate(options?: GenerateOptions): Promise<GenerateResponse>;
153
+ private request;
154
+ /**
155
+ * Extract text from any supported provider's response format.
156
+ *
157
+ * For providers that return multiple text segments in a single response
158
+ * (Gemini `parts`, Anthropic `content` blocks), all text segments are
159
+ * concatenated with newlines so callers see the full message rather than a
160
+ * silently-truncated first chunk.
161
+ *
162
+ * Throws {@link InferallError} if the response does not match any recognized
163
+ * shape — we never coerce arbitrary JSON into a string.
164
+ */
165
+ static extractText(resp: GenerateResponse): string;
166
+ }
167
+
168
+ export { type ChatOptions, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PROVIDER, type GenerateOptions, type GenerateResponse, Inferall, InferallError, type InferallOptions, type Message, type TextOptions, VERSION, type VisionOptions, Inferall as default };
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://api.inferall.ai";
3
+ var DEFAULT_PROVIDER = "gemini";
4
+ var DEFAULT_MODEL = "gemini-2.5-flash";
5
+ var VERSION = "0.1.0";
6
+ var USER_AGENT = `inferall-typescript/${VERSION}`;
7
+ var InferallError = class _InferallError extends Error {
8
+ constructor(status, body) {
9
+ super(`InferAll gateway returned ${status}: ${body}`);
10
+ this.name = "InferallError";
11
+ this.status = status;
12
+ this.body = body;
13
+ Object.setPrototypeOf(this, _InferallError.prototype);
14
+ }
15
+ };
16
+ function readEnv(name) {
17
+ const proc = globalThis.process;
18
+ return proc?.env?.[name];
19
+ }
20
+ function resolveApiKey(apiKey) {
21
+ if (apiKey) return apiKey;
22
+ return readEnv("INFERALL_API_KEY") || readEnv("AI_GATEWAY_KEY") || "";
23
+ }
24
+ function resolveBaseURL(baseURL) {
25
+ if (baseURL) return baseURL.replace(/\/+$/, "");
26
+ const env = readEnv("INFERALL_BASE_URL") || readEnv("AI_GATEWAY_URL");
27
+ return (env || DEFAULT_BASE_URL).replace(/\/+$/, "");
28
+ }
29
+ var Inferall = class _Inferall {
30
+ constructor(options = {}) {
31
+ this.apiKey = resolveApiKey(options.apiKey);
32
+ this.baseURL = resolveBaseURL(options.baseURL);
33
+ this.defaultProvider = options.defaultProvider ?? DEFAULT_PROVIDER;
34
+ this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;
35
+ this.timeout = options.timeout ?? 12e4;
36
+ }
37
+ // ---------------------------------------------------------------- public
38
+ /** Single-turn text completion. Prompt in, string out. */
39
+ async text(prompt, options = {}) {
40
+ const resp = await this.request({
41
+ provider: options.provider || this.defaultProvider,
42
+ model: options.model || this.defaultModel,
43
+ operation: "text",
44
+ messages: [{ role: "user", content: prompt }],
45
+ system: options.system,
46
+ temperature: options.temperature ?? 0.3,
47
+ maxTokens: options.maxTokens ?? 4096
48
+ });
49
+ return _Inferall.extractText(resp);
50
+ }
51
+ /** Multi-turn chat completion. Returns the assistant's response text. */
52
+ async chat(messages, options = {}) {
53
+ const resp = await this.request({
54
+ provider: options.provider || this.defaultProvider,
55
+ model: options.model || this.defaultModel,
56
+ operation: "chat",
57
+ messages,
58
+ system: options.system,
59
+ temperature: options.temperature ?? 0.3,
60
+ maxTokens: options.maxTokens ?? 4096
61
+ });
62
+ return _Inferall.extractText(resp);
63
+ }
64
+ /**
65
+ * Analyze an image (base64-encoded) with a text prompt.
66
+ *
67
+ * Resolves `provider` and `model` from the client's defaults so that
68
+ * `new Inferall({ defaultProvider: "openai" }).vision(...)` actually routes
69
+ * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.
70
+ */
71
+ async vision(imageBase64, prompt, options = {}) {
72
+ const resp = await this.request({
73
+ provider: options.provider || this.defaultProvider || "gemini",
74
+ model: options.model || this.defaultModel || "gemini-2.5-flash",
75
+ operation: "image-analyze",
76
+ messages: [{ role: "user", content: prompt }],
77
+ images: [imageBase64],
78
+ temperature: 0.3,
79
+ maxTokens: options.maxTokens ?? 4096
80
+ });
81
+ return _Inferall.extractText(resp);
82
+ }
83
+ /**
84
+ * Escape hatch — return the raw, provider-shaped response payload.
85
+ *
86
+ * Use this when you need access to tool calls, fallback metadata
87
+ * (`fallback_used` / `actual_provider`), streaming chunks, or any
88
+ * provider-specific field that the convenience helpers ({@link text},
89
+ * {@link chat}, {@link vision}) strip out.
90
+ */
91
+ async generate(options = {}) {
92
+ return this.request({
93
+ provider: options.provider || this.defaultProvider,
94
+ model: options.model || this.defaultModel,
95
+ operation: options.operation ?? "text",
96
+ messages: options.messages,
97
+ system: options.system,
98
+ images: options.images,
99
+ prompt: options.prompt,
100
+ temperature: options.temperature ?? 0.3,
101
+ maxTokens: options.maxTokens ?? 4096
102
+ });
103
+ }
104
+ // -------------------------------------------------------------- internal
105
+ async request(args) {
106
+ if (!this.apiKey) {
107
+ throw new Error(
108
+ "InferAll API key not set. Pass { apiKey } to the constructor or set the INFERALL_API_KEY environment variable (legacy AI_GATEWAY_KEY is also accepted as a fallback)."
109
+ );
110
+ }
111
+ const body = {
112
+ provider: args.provider,
113
+ model: args.model,
114
+ operation: args.operation,
115
+ config: { temperature: args.temperature, maxTokens: args.maxTokens }
116
+ };
117
+ if (args.messages && args.messages.length > 0) body.messages = args.messages;
118
+ if (args.system) body.system = args.system;
119
+ if (args.images && args.images.length > 0) body.images = args.images;
120
+ if (args.prompt) body.prompt = args.prompt;
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), this.timeout);
123
+ let response;
124
+ try {
125
+ response = await fetch(`${this.baseURL}/ai/v1/generate`, {
126
+ method: "POST",
127
+ headers: {
128
+ Authorization: `Bearer ${this.apiKey}`,
129
+ "Content-Type": "application/json",
130
+ "User-Agent": USER_AGENT
131
+ },
132
+ body: JSON.stringify(body),
133
+ signal: controller.signal
134
+ });
135
+ } catch (err) {
136
+ if (err instanceof DOMException && err.name === "AbortError") {
137
+ throw new InferallError(0, `Request timed out after ${this.timeout}ms`);
138
+ }
139
+ const reason = err instanceof Error ? err.message : String(err);
140
+ throw new InferallError(0, `Network error: ${reason}`);
141
+ } finally {
142
+ clearTimeout(timer);
143
+ }
144
+ if (!response.ok) {
145
+ const errorBody = await response.text().catch(() => "");
146
+ throw new InferallError(response.status, errorBody);
147
+ }
148
+ try {
149
+ return await response.json();
150
+ } catch (err) {
151
+ const reason = err instanceof Error ? err.message : String(err);
152
+ throw new InferallError(0, `Failed to parse gateway response: ${reason}`);
153
+ }
154
+ }
155
+ /**
156
+ * Extract text from any supported provider's response format.
157
+ *
158
+ * For providers that return multiple text segments in a single response
159
+ * (Gemini `parts`, Anthropic `content` blocks), all text segments are
160
+ * concatenated with newlines so callers see the full message rather than a
161
+ * silently-truncated first chunk.
162
+ *
163
+ * Throws {@link InferallError} if the response does not match any recognized
164
+ * shape — we never coerce arbitrary JSON into a string.
165
+ */
166
+ static extractText(resp) {
167
+ const candidates = resp["candidates"];
168
+ if (Array.isArray(candidates) && candidates.length > 0) {
169
+ const parts = candidates[0]?.["content"];
170
+ const partList = parts?.["parts"];
171
+ if (Array.isArray(partList)) {
172
+ const texts = partList.filter((p) => isRecord(p) && typeof p["text"] === "string").map((p) => p["text"]);
173
+ if (texts.length > 0) return texts.join("\n");
174
+ }
175
+ }
176
+ const choices = resp["choices"];
177
+ if (Array.isArray(choices) && choices.length > 0) {
178
+ const message = choices[0]?.["message"];
179
+ const content2 = message?.["content"];
180
+ return typeof content2 === "string" ? content2 : "";
181
+ }
182
+ const content = resp["content"];
183
+ if (Array.isArray(content)) {
184
+ const texts = content.filter((block) => isRecord(block) && Boolean(block["text"])).map((block) => block["text"]);
185
+ if (texts.length > 0) return texts.join("\n");
186
+ }
187
+ const output = resp["output"];
188
+ if (typeof output === "string") return output;
189
+ if (Array.isArray(output) && output.length > 0 && typeof output[0] === "string") {
190
+ return output[0];
191
+ }
192
+ throw new InferallError(0, `Unrecognized response shape: keys=${JSON.stringify(Object.keys(resp))}`);
193
+ }
194
+ };
195
+ function isRecord(value) {
196
+ return typeof value === "object" && value !== null;
197
+ }
198
+ var index_default = Inferall;
199
+ export {
200
+ DEFAULT_BASE_URL,
201
+ DEFAULT_MODEL,
202
+ DEFAULT_PROVIDER,
203
+ Inferall,
204
+ InferallError,
205
+ VERSION,
206
+ index_default as default
207
+ };
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * InferAll TypeScript client.\n *\n * The {@link Inferall} client speaks to the InferAll gateway over a single HTTP\n * endpoint and normalizes responses across providers (OpenAI, Anthropic, Google\n * Gemini, Replicate, and more).\n *\n * Quick start:\n *\n * ```ts\n * import { Inferall } from \"@inferall/sdk\";\n *\n * const ai = new Inferall(); // picks up INFERALL_API_KEY from the environment\n *\n * console.log(await ai.text(\"Summarize the theory of relativity in two sentences.\"));\n * ```\n *\n * This is a faithful port of the published `inferall-ai` Python package. The\n * client depends only on the platform `fetch` API so it can be dropped into\n * lambdas, edge functions, and browsers without pulling in transitive\n * dependencies.\n */\n\nexport const DEFAULT_BASE_URL = \"https://api.inferall.ai\";\nexport const DEFAULT_PROVIDER = \"gemini\";\nexport const DEFAULT_MODEL = \"gemini-2.5-flash\";\n\n/** Package version, mirrored from package.json. */\nexport const VERSION = \"0.1.0\";\n\nconst USER_AGENT = `inferall-typescript/${VERSION}`;\n\n/**\n * A single chat message. Mirrors the Python SDK's `list[dict]` message shape;\n * arbitrary provider-specific keys are allowed.\n */\nexport interface Message {\n role: string;\n content: unknown;\n [key: string]: unknown;\n}\n\n/** A provider-shaped response payload, as returned by {@link Inferall.generate}. */\nexport type GenerateResponse = Record<string, unknown>;\n\n/** Options for the {@link Inferall} constructor. */\nexport interface InferallOptions {\n /**\n * Bearer token for the gateway. If omitted, the client reads\n * `INFERALL_API_KEY` (preferred) or `AI_GATEWAY_KEY` (legacy) from the\n * environment.\n */\n apiKey?: string;\n /**\n * Override the gateway URL. Defaults to `https://api.inferall.ai`\n * (or `INFERALL_BASE_URL` / `AI_GATEWAY_URL` from the environment).\n */\n baseURL?: string;\n /** Provider used when a call site does not pass `provider`. */\n defaultProvider?: string;\n /** Model used when a call site does not pass `model`. */\n defaultModel?: string;\n /**\n * Per-request timeout, in milliseconds. Defaults to 120000ms (120s) to\n * accommodate large completions.\n */\n timeout?: number;\n}\n\n/** Options for {@link Inferall.text}. */\nexport interface TextOptions {\n system?: string;\n provider?: string;\n model?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.chat}. */\nexport interface ChatOptions {\n system?: string;\n provider?: string;\n model?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.vision}. */\nexport interface VisionOptions {\n provider?: string;\n model?: string;\n /** @default 4096 */\n maxTokens?: number;\n}\n\n/** Options for {@link Inferall.generate}. */\nexport interface GenerateOptions {\n provider?: string;\n model?: string;\n /** @default \"text\" */\n operation?: string;\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n /** @default 0.3 */\n temperature?: number;\n /** @default 4096 */\n maxTokens?: number;\n}\n\ninterface RequestBody {\n provider: string;\n model: string;\n operation: string;\n config: { temperature: number; maxTokens: number };\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n}\n\n/**\n * Raised when the InferAll gateway returns an error or the request fails.\n *\n * Mirrors the Python `InferallError` (a subclass of `RuntimeError`). A `status`\n * of `0` indicates a transport-level failure (network error, timeout) rather\n * than an HTTP error response.\n */\nexport class InferallError extends Error {\n readonly status: number;\n readonly body: string;\n\n constructor(status: number, body: string) {\n super(`InferAll gateway returned ${status}: ${body}`);\n this.name = \"InferallError\";\n this.status = status;\n this.body = body;\n // Restore prototype chain for instanceof across transpilation targets.\n Object.setPrototypeOf(this, InferallError.prototype);\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n // Guard against non-Node runtimes (browsers, some edge functions) where\n // `process` is undefined. There the caller must pass apiKey/baseURL explicitly.\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return proc?.env?.[name];\n}\n\n/**\n * Return the API key from the explicit arg or environment.\n *\n * Honors `INFERALL_API_KEY` first, then falls back to `AI_GATEWAY_KEY` for\n * backwards compatibility with the original internal client.\n */\nfunction resolveApiKey(apiKey: string | undefined): string {\n if (apiKey) return apiKey;\n return readEnv(\"INFERALL_API_KEY\") || readEnv(\"AI_GATEWAY_KEY\") || \"\";\n}\n\nfunction resolveBaseURL(baseURL: string | undefined): string {\n if (baseURL) return baseURL.replace(/\\/+$/, \"\");\n const env = readEnv(\"INFERALL_BASE_URL\") || readEnv(\"AI_GATEWAY_URL\");\n return (env || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n}\n\n/**\n * Client for the InferAll AI gateway.\n *\n * @example\n * ```ts\n * const ai = new Inferall(); // env-based key\n * const ai = new Inferall({ apiKey: \"sk-…\" }); // explicit key\n * ```\n */\nexport class Inferall {\n readonly apiKey: string;\n readonly baseURL: string;\n readonly defaultProvider: string;\n readonly defaultModel: string;\n readonly timeout: number;\n\n constructor(options: InferallOptions = {}) {\n this.apiKey = resolveApiKey(options.apiKey);\n this.baseURL = resolveBaseURL(options.baseURL);\n this.defaultProvider = options.defaultProvider ?? DEFAULT_PROVIDER;\n this.defaultModel = options.defaultModel ?? DEFAULT_MODEL;\n this.timeout = options.timeout ?? 120_000;\n }\n\n // ---------------------------------------------------------------- public\n\n /** Single-turn text completion. Prompt in, string out. */\n async text(prompt: string, options: TextOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: \"text\",\n messages: [{ role: \"user\", content: prompt }],\n system: options.system,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /** Multi-turn chat completion. Returns the assistant's response text. */\n async chat(messages: Message[], options: ChatOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: \"chat\",\n messages,\n system: options.system,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /**\n * Analyze an image (base64-encoded) with a text prompt.\n *\n * Resolves `provider` and `model` from the client's defaults so that\n * `new Inferall({ defaultProvider: \"openai\" }).vision(...)` actually routes\n * to OpenAI. Falls back to gemini-2.5-flash only when no default is set.\n */\n async vision(imageBase64: string, prompt: string, options: VisionOptions = {}): Promise<string> {\n const resp = await this.request({\n provider: options.provider || this.defaultProvider || \"gemini\",\n model: options.model || this.defaultModel || \"gemini-2.5-flash\",\n operation: \"image-analyze\",\n messages: [{ role: \"user\", content: prompt }],\n images: [imageBase64],\n temperature: 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n return Inferall.extractText(resp);\n }\n\n /**\n * Escape hatch — return the raw, provider-shaped response payload.\n *\n * Use this when you need access to tool calls, fallback metadata\n * (`fallback_used` / `actual_provider`), streaming chunks, or any\n * provider-specific field that the convenience helpers ({@link text},\n * {@link chat}, {@link vision}) strip out.\n */\n async generate(options: GenerateOptions = {}): Promise<GenerateResponse> {\n return this.request({\n provider: options.provider || this.defaultProvider,\n model: options.model || this.defaultModel,\n operation: options.operation ?? \"text\",\n messages: options.messages,\n system: options.system,\n images: options.images,\n prompt: options.prompt,\n temperature: options.temperature ?? 0.3,\n maxTokens: options.maxTokens ?? 4096,\n });\n }\n\n // -------------------------------------------------------------- internal\n\n private async request(args: {\n provider: string;\n model: string;\n operation: string;\n messages?: Message[];\n system?: string;\n images?: string[];\n prompt?: string;\n temperature: number;\n maxTokens: number;\n }): Promise<GenerateResponse> {\n if (!this.apiKey) {\n throw new Error(\n \"InferAll API key not set. Pass { apiKey } to the constructor or set \" +\n \"the INFERALL_API_KEY environment variable \" +\n \"(legacy AI_GATEWAY_KEY is also accepted as a fallback).\"\n );\n }\n\n const body: RequestBody = {\n provider: args.provider,\n model: args.model,\n operation: args.operation,\n config: { temperature: args.temperature, maxTokens: args.maxTokens },\n };\n if (args.messages && args.messages.length > 0) body.messages = args.messages;\n if (args.system) body.system = args.system;\n if (args.images && args.images.length > 0) body.images = args.images;\n if (args.prompt) body.prompt = args.prompt;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n let response: Response;\n try {\n response = await fetch(`${this.baseURL}/ai/v1/generate`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new InferallError(0, `Request timed out after ${this.timeout}ms`);\n }\n const reason = err instanceof Error ? err.message : String(err);\n throw new InferallError(0, `Network error: ${reason}`);\n } finally {\n clearTimeout(timer);\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new InferallError(response.status, errorBody);\n }\n\n try {\n return (await response.json()) as GenerateResponse;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new InferallError(0, `Failed to parse gateway response: ${reason}`);\n }\n }\n\n /**\n * Extract text from any supported provider's response format.\n *\n * For providers that return multiple text segments in a single response\n * (Gemini `parts`, Anthropic `content` blocks), all text segments are\n * concatenated with newlines so callers see the full message rather than a\n * silently-truncated first chunk.\n *\n * Throws {@link InferallError} if the response does not match any recognized\n * shape — we never coerce arbitrary JSON into a string.\n */\n static extractText(resp: GenerateResponse): string {\n // Gemini\n const candidates = resp[\"candidates\"];\n if (Array.isArray(candidates) && candidates.length > 0) {\n const parts = (candidates[0] as Record<string, unknown> | undefined)?.[\"content\"];\n const partList = (parts as Record<string, unknown> | undefined)?.[\"parts\"];\n if (Array.isArray(partList)) {\n const texts = partList\n .filter((p): p is Record<string, unknown> => isRecord(p) && typeof p[\"text\"] === \"string\")\n .map((p) => p[\"text\"] as string);\n if (texts.length > 0) return texts.join(\"\\n\");\n }\n }\n\n // OpenAI\n const choices = resp[\"choices\"];\n if (Array.isArray(choices) && choices.length > 0) {\n const message = (choices[0] as Record<string, unknown> | undefined)?.[\"message\"];\n const content = (message as Record<string, unknown> | undefined)?.[\"content\"];\n return typeof content === \"string\" ? content : \"\";\n }\n\n // Anthropic\n const content = resp[\"content\"];\n if (Array.isArray(content)) {\n const texts = content\n .filter((block): block is Record<string, unknown> => isRecord(block) && Boolean(block[\"text\"]))\n .map((block) => block[\"text\"] as string);\n if (texts.length > 0) return texts.join(\"\\n\");\n }\n\n // Replicate\n const output = resp[\"output\"];\n if (typeof output === \"string\") return output;\n if (Array.isArray(output) && output.length > 0 && typeof output[0] === \"string\") {\n return output[0];\n }\n\n throw new InferallError(0, `Unrecognized response shape: keys=${JSON.stringify(Object.keys(resp))}`);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport default Inferall;\n"],"mappings":"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","names":["content"]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@inferall/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official TypeScript SDK for the InferAll AI gateway — one API for every model provider.",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "sideEffects": false,
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "keywords": [
38
+ "ai",
39
+ "inference",
40
+ "gateway",
41
+ "llm",
42
+ "openai",
43
+ "anthropic",
44
+ "gemini",
45
+ "replicate",
46
+ "inferall"
47
+ ],
48
+ "author": "Kindly Robotics <hello@inferall.ai>",
49
+ "license": "MIT",
50
+ "homepage": "https://inferall.ai",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/kindlyrobotics/infra.git",
54
+ "directory": "packages/inferall-ts"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/kindlyrobotics/infra/issues"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^20.19.41",
61
+ "tsup": "^8.0.0",
62
+ "typescript": "^5.7.0",
63
+ "vitest": "^2.1.0"
64
+ }
65
+ }