@inferencesh/sdk 0.4.4 → 0.4.8

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.
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("./index");
4
+ // Store original fetch
5
+ const originalFetch = global.fetch;
6
+ describe('Proxy Core', () => {
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ });
10
+ afterEach(() => {
11
+ global.fetch = originalFetch;
12
+ });
13
+ /**
14
+ * Helper to create a mock ProxyBehavior for testing
15
+ */
16
+ function createMockBehavior(overrides = {}) {
17
+ const responseHeaders = {};
18
+ const headers = {
19
+ [index_1.TARGET_URL_HEADER]: 'https://api.inference.sh/apps/run',
20
+ 'content-type': 'application/json',
21
+ };
22
+ return {
23
+ id: 'test',
24
+ method: 'POST',
25
+ responseHeaders,
26
+ respondWith: jest.fn((status, data) => new Response(JSON.stringify(data), { status })),
27
+ sendResponse: jest.fn(async (res) => res),
28
+ getHeaders: () => headers,
29
+ getHeader: (name) => headers[name.toLowerCase()],
30
+ sendHeader: (name, value) => {
31
+ responseHeaders[name] = value;
32
+ },
33
+ getRequestBody: async () => JSON.stringify({ test: true }),
34
+ // Always provide resolveApiKey so we don't depend on env at load time
35
+ resolveApiKey: async () => 'test-api-key',
36
+ ...overrides,
37
+ };
38
+ }
39
+ describe('handleRequest', () => {
40
+ it('should return 400 when target URL header is missing', async () => {
41
+ const behavior = createMockBehavior({
42
+ getHeader: () => undefined,
43
+ });
44
+ await (0, index_1.handleRequest)(behavior);
45
+ expect(behavior.respondWith).toHaveBeenCalledWith(400, {
46
+ error: `Missing the ${index_1.TARGET_URL_HEADER} header`,
47
+ });
48
+ });
49
+ it('should return 400 for invalid URL', async () => {
50
+ const headers = {
51
+ [index_1.TARGET_URL_HEADER]: 'not-a-valid-url',
52
+ };
53
+ const behavior = createMockBehavior({
54
+ getHeaders: () => headers,
55
+ getHeader: (name) => headers[name.toLowerCase()],
56
+ });
57
+ await (0, index_1.handleRequest)(behavior);
58
+ expect(behavior.respondWith).toHaveBeenCalledWith(400, {
59
+ error: `Invalid ${index_1.TARGET_URL_HEADER} header: not a valid URL`,
60
+ });
61
+ });
62
+ it('should return 412 for non-inference.sh domains', async () => {
63
+ const headers = {
64
+ [index_1.TARGET_URL_HEADER]: 'https://evil.com/api',
65
+ };
66
+ const behavior = createMockBehavior({
67
+ getHeaders: () => headers,
68
+ getHeader: (name) => headers[name.toLowerCase()],
69
+ });
70
+ await (0, index_1.handleRequest)(behavior);
71
+ expect(behavior.respondWith).toHaveBeenCalledWith(412, {
72
+ error: `Invalid ${index_1.TARGET_URL_HEADER} header: must be an inference.sh domain`,
73
+ });
74
+ });
75
+ it('should return 401 when API key resolver returns undefined', async () => {
76
+ const behavior = createMockBehavior({
77
+ resolveApiKey: async () => undefined,
78
+ });
79
+ await (0, index_1.handleRequest)(behavior);
80
+ expect(behavior.respondWith).toHaveBeenCalledWith(401, {
81
+ error: 'Missing INFERENCE_API_KEY environment variable',
82
+ });
83
+ });
84
+ it('should accept valid inference.sh domains', async () => {
85
+ const validDomains = [
86
+ 'https://api.inference.sh/apps/run',
87
+ 'https://inference.sh/api/test',
88
+ 'https://sub.api.inference.sh/endpoint',
89
+ ];
90
+ global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
91
+ status: 200,
92
+ headers: { 'content-type': 'application/json' },
93
+ }));
94
+ for (const targetUrl of validDomains) {
95
+ jest.clearAllMocks();
96
+ const headers = {
97
+ [index_1.TARGET_URL_HEADER]: targetUrl,
98
+ 'content-type': 'application/json',
99
+ };
100
+ const behavior = createMockBehavior({
101
+ getHeaders: () => headers,
102
+ getHeader: (name) => headers[name.toLowerCase()],
103
+ });
104
+ await (0, index_1.handleRequest)(behavior);
105
+ expect(behavior.sendResponse).toHaveBeenCalled();
106
+ expect(behavior.respondWith).not.toHaveBeenCalled();
107
+ }
108
+ });
109
+ it('should forward x-inf-* headers', async () => {
110
+ global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
111
+ status: 200,
112
+ headers: { 'content-type': 'application/json' },
113
+ }));
114
+ const headers = {
115
+ [index_1.TARGET_URL_HEADER]: 'https://api.inference.sh/test',
116
+ 'content-type': 'application/json',
117
+ 'x-inf-custom-header': 'custom-value',
118
+ 'x-inf-another': 'another-value',
119
+ 'x-other-header': 'should-not-forward', // Not x-inf-*, shouldn't be forwarded
120
+ };
121
+ const behavior = createMockBehavior({
122
+ getHeaders: () => headers,
123
+ getHeader: (name) => headers[name.toLowerCase()],
124
+ });
125
+ await (0, index_1.handleRequest)(behavior);
126
+ expect(global.fetch).toHaveBeenCalledWith('https://api.inference.sh/test', expect.objectContaining({
127
+ headers: expect.objectContaining({
128
+ 'x-inf-custom-header': 'custom-value',
129
+ 'x-inf-another': 'another-value',
130
+ authorization: 'Bearer test-api-key',
131
+ }),
132
+ }));
133
+ // Verify x-other-header was not forwarded
134
+ const fetchCall = global.fetch.mock.calls[0];
135
+ const fetchHeaders = fetchCall[1].headers;
136
+ expect(fetchHeaders['x-other-header']).toBeUndefined();
137
+ });
138
+ it('should use custom resolveApiKey function', async () => {
139
+ global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
140
+ status: 200,
141
+ headers: { 'content-type': 'application/json' },
142
+ }));
143
+ const behavior = createMockBehavior({
144
+ resolveApiKey: async () => 'custom-api-key',
145
+ });
146
+ await (0, index_1.handleRequest)(behavior);
147
+ expect(global.fetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
148
+ headers: expect.objectContaining({
149
+ authorization: 'Bearer custom-api-key',
150
+ }),
151
+ }));
152
+ });
153
+ it('should not send body for GET requests', async () => {
154
+ global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
155
+ status: 200,
156
+ headers: { 'content-type': 'application/json' },
157
+ }));
158
+ const behavior = createMockBehavior({
159
+ method: 'GET',
160
+ });
161
+ await (0, index_1.handleRequest)(behavior);
162
+ expect(global.fetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
163
+ method: 'GET',
164
+ body: undefined,
165
+ }));
166
+ });
167
+ it('should copy response headers (excluding content-length/encoding)', async () => {
168
+ const responseHeaders = new Headers();
169
+ responseHeaders.set('x-custom-response', 'value');
170
+ responseHeaders.set('content-length', '100'); // Should be excluded
171
+ responseHeaders.set('content-encoding', 'gzip'); // Should be excluded
172
+ global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
173
+ status: 200,
174
+ headers: responseHeaders,
175
+ }));
176
+ const behavior = createMockBehavior();
177
+ await (0, index_1.handleRequest)(behavior);
178
+ expect(behavior.responseHeaders['x-custom-response']).toBe('value');
179
+ expect(behavior.responseHeaders['content-length']).toBeUndefined();
180
+ expect(behavior.responseHeaders['content-encoding']).toBeUndefined();
181
+ });
182
+ it('should reject attempts to proxy to non-inference.sh subdomains', async () => {
183
+ const invalidDomains = [
184
+ 'https://notinference.sh/api',
185
+ 'https://inference.sh.evil.com/api',
186
+ 'https://api.inference.io/test',
187
+ ];
188
+ for (const targetUrl of invalidDomains) {
189
+ const headers = {
190
+ [index_1.TARGET_URL_HEADER]: targetUrl,
191
+ };
192
+ const behavior = createMockBehavior({
193
+ getHeaders: () => headers,
194
+ getHeader: (name) => headers[name.toLowerCase()],
195
+ });
196
+ await (0, index_1.handleRequest)(behavior);
197
+ expect(behavior.respondWith).toHaveBeenCalledWith(412, {
198
+ error: `Invalid ${index_1.TARGET_URL_HEADER} header: must be an inference.sh domain`,
199
+ });
200
+ }
201
+ });
202
+ });
203
+ describe('fromHeaders', () => {
204
+ it('should convert Headers to plain object', () => {
205
+ const headers = new Headers();
206
+ headers.set('content-type', 'application/json');
207
+ headers.set('x-custom', 'value');
208
+ const result = (0, index_1.fromHeaders)(headers);
209
+ expect(result).toEqual({
210
+ 'content-type': 'application/json',
211
+ 'x-custom': 'value',
212
+ });
213
+ });
214
+ it('should handle empty headers', () => {
215
+ const headers = new Headers();
216
+ const result = (0, index_1.fromHeaders)(headers);
217
+ expect(result).toEqual({});
218
+ });
219
+ });
220
+ });
@@ -0,0 +1 @@
1
+ export * from "./index.test.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Next.js proxy handlers for Inference.sh API
3
+ *
4
+ * Supports both App Router (route handlers) and Page Router (API handlers).
5
+ *
6
+ * @example App Router (app/api/inference/proxy/route.ts)
7
+ * ```typescript
8
+ * import { route } from "@inferencesh/sdk/proxy/nextjs";
9
+ * export const { GET, POST, PUT } = route;
10
+ * ```
11
+ *
12
+ * @example Page Router (pages/api/inference/proxy.ts)
13
+ * ```typescript
14
+ * export { handler as default } from "@inferencesh/sdk/proxy/nextjs";
15
+ * ```
16
+ */
17
+ import { type NextRequest } from "next/server";
18
+ import type { NextApiHandler } from "next/types";
19
+ /**
20
+ * The default proxy route path.
21
+ */
22
+ export declare const PROXY_ROUTE = "/api/inference/proxy";
23
+ /**
24
+ * Page Router handler for the Inference.sh proxy.
25
+ *
26
+ * Note: Page Router proxy doesn't support streaming responses.
27
+ * For streaming, use the App Router.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // pages/api/inference/proxy.ts
32
+ * export { handler as default } from "@inferencesh/sdk/proxy/nextjs";
33
+ * ```
34
+ */
35
+ export declare const handler: NextApiHandler;
36
+ /**
37
+ * App Router handler for the Inference.sh proxy.
38
+ *
39
+ * Supports full streaming passthrough for SSE responses.
40
+ *
41
+ * @param request - Next.js request object
42
+ * @returns Response object
43
+ */
44
+ declare function routeHandler(request: NextRequest): Promise<Response>;
45
+ /**
46
+ * App Router route exports.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // app/api/inference/proxy/route.ts
51
+ * import { route } from "@inferencesh/sdk/proxy/nextjs";
52
+ * export const { GET, POST, PUT } = route;
53
+ * ```
54
+ */
55
+ export declare const route: {
56
+ handler: typeof routeHandler;
57
+ GET: typeof routeHandler;
58
+ POST: typeof routeHandler;
59
+ PUT: typeof routeHandler;
60
+ };
61
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Next.js proxy handlers for Inference.sh API
4
+ *
5
+ * Supports both App Router (route handlers) and Page Router (API handlers).
6
+ *
7
+ * @example App Router (app/api/inference/proxy/route.ts)
8
+ * ```typescript
9
+ * import { route } from "@inferencesh/sdk/proxy/nextjs";
10
+ * export const { GET, POST, PUT } = route;
11
+ * ```
12
+ *
13
+ * @example Page Router (pages/api/inference/proxy.ts)
14
+ * ```typescript
15
+ * export { handler as default } from "@inferencesh/sdk/proxy/nextjs";
16
+ * ```
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.route = exports.handler = exports.PROXY_ROUTE = void 0;
20
+ const server_1 = require("next/server");
21
+ const index_1 = require("./index");
22
+ /**
23
+ * The default proxy route path.
24
+ */
25
+ exports.PROXY_ROUTE = index_1.DEFAULT_PROXY_ROUTE;
26
+ /**
27
+ * Page Router handler for the Inference.sh proxy.
28
+ *
29
+ * Note: Page Router proxy doesn't support streaming responses.
30
+ * For streaming, use the App Router.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // pages/api/inference/proxy.ts
35
+ * export { handler as default } from "@inferencesh/sdk/proxy/nextjs";
36
+ * ```
37
+ */
38
+ const handler = async (request, response) => {
39
+ return (0, index_1.handleRequest)({
40
+ id: "nextjs-page-router",
41
+ method: request.method || "POST",
42
+ getRequestBody: async () => JSON.stringify(request.body),
43
+ getHeaders: () => request.headers,
44
+ getHeader: (name) => request.headers[name],
45
+ sendHeader: (name, value) => response.setHeader(name, value),
46
+ respondWith: (status, data) => response.status(status).json(data),
47
+ sendResponse: async (res) => {
48
+ if (res.headers.get("content-type")?.includes("application/json")) {
49
+ return response.status(res.status).json(await res.json());
50
+ }
51
+ return response.status(res.status).send(await res.text());
52
+ },
53
+ });
54
+ };
55
+ exports.handler = handler;
56
+ /**
57
+ * App Router handler for the Inference.sh proxy.
58
+ *
59
+ * Supports full streaming passthrough for SSE responses.
60
+ *
61
+ * @param request - Next.js request object
62
+ * @returns Response object
63
+ */
64
+ async function routeHandler(request) {
65
+ const responseHeaders = new Headers();
66
+ return await (0, index_1.handleRequest)({
67
+ id: "nextjs-app-router",
68
+ method: request.method,
69
+ getRequestBody: async () => request.text(),
70
+ getHeaders: () => (0, index_1.fromHeaders)(request.headers),
71
+ getHeader: (name) => request.headers.get(name),
72
+ sendHeader: (name, value) => responseHeaders.set(name, value),
73
+ respondWith: (status, data) => server_1.NextResponse.json(data, {
74
+ status,
75
+ headers: responseHeaders,
76
+ }),
77
+ sendResponse: index_1.responsePassthrough,
78
+ });
79
+ }
80
+ /**
81
+ * App Router route exports.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * // app/api/inference/proxy/route.ts
86
+ * import { route } from "@inferencesh/sdk/proxy/nextjs";
87
+ * export const { GET, POST, PUT } = route;
88
+ * ```
89
+ */
90
+ exports.route = {
91
+ handler: routeHandler,
92
+ GET: routeHandler,
93
+ POST: routeHandler,
94
+ PUT: routeHandler,
95
+ };
@@ -0,0 +1 @@
1
+ export * from "./nextjs.js";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Remix proxy handler for Inference.sh API
3
+ *
4
+ * Works with Remix's loader and action functions.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // app/routes/api.inference.proxy.ts
9
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/remix";
10
+ *
11
+ * const handler = createRequestHandler();
12
+ *
13
+ * export const loader = handler;
14
+ * export const action = handler;
15
+ * ```
16
+ */
17
+ export interface RemixProxyOptions {
18
+ /**
19
+ * Custom function to resolve the API key.
20
+ * Defaults to reading from INFERENCE_API_KEY environment variable.
21
+ */
22
+ resolveApiKey?: () => Promise<string | undefined>;
23
+ }
24
+ type RemixRequestHandler = (args: {
25
+ request: Request;
26
+ }) => Promise<Response>;
27
+ /**
28
+ * Creates a Remix request handler that proxies requests to the Inference.sh API.
29
+ *
30
+ * Use this as both a loader and action in your Remix route.
31
+ *
32
+ * @param options - Proxy options
33
+ * @returns A Remix request handler function
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // app/routes/api.inference.proxy.ts
38
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/remix";
39
+ *
40
+ * const handler = createRequestHandler();
41
+ *
42
+ * export const loader = handler;
43
+ * export const action = handler;
44
+ * ```
45
+ */
46
+ export declare function createRequestHandler({ resolveApiKey, }?: RemixProxyOptions): RemixRequestHandler;
47
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * Remix proxy handler for Inference.sh API
4
+ *
5
+ * Works with Remix's loader and action functions.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // app/routes/api.inference.proxy.ts
10
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/remix";
11
+ *
12
+ * const handler = createRequestHandler();
13
+ *
14
+ * export const loader = handler;
15
+ * export const action = handler;
16
+ * ```
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.createRequestHandler = createRequestHandler;
20
+ const index_1 = require("./index");
21
+ /**
22
+ * Creates a Remix request handler that proxies requests to the Inference.sh API.
23
+ *
24
+ * Use this as both a loader and action in your Remix route.
25
+ *
26
+ * @param options - Proxy options
27
+ * @returns A Remix request handler function
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // app/routes/api.inference.proxy.ts
32
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/remix";
33
+ *
34
+ * const handler = createRequestHandler();
35
+ *
36
+ * export const loader = handler;
37
+ * export const action = handler;
38
+ * ```
39
+ */
40
+ function createRequestHandler({ resolveApiKey = index_1.resolveApiKeyFromEnv, } = {}) {
41
+ return async ({ request }) => {
42
+ const responseHeaders = new Headers();
43
+ return (0, index_1.handleRequest)({
44
+ id: "remix",
45
+ method: request.method,
46
+ getRequestBody: async () => request.text(),
47
+ getHeaders: () => (0, index_1.fromHeaders)(request.headers),
48
+ getHeader: (name) => request.headers.get(name),
49
+ sendHeader: (name, value) => responseHeaders.set(name, value),
50
+ respondWith: (status, data) => new Response(JSON.stringify(data), {
51
+ status,
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ ...Object.fromEntries(responseHeaders.entries()),
55
+ },
56
+ }),
57
+ sendResponse: index_1.responsePassthrough,
58
+ resolveApiKey,
59
+ });
60
+ };
61
+ }
@@ -0,0 +1 @@
1
+ export * from "./remix.js";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SvelteKit proxy handler for Inference.sh API
3
+ *
4
+ * Works with SvelteKit's +server.ts route handlers.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * // src/routes/api/inference/proxy/+server.ts
9
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/svelte";
10
+ *
11
+ * const handler = createRequestHandler();
12
+ *
13
+ * export const GET = handler;
14
+ * export const POST = handler;
15
+ * export const PUT = handler;
16
+ * ```
17
+ */
18
+ import type { RequestEvent } from "@sveltejs/kit";
19
+ export interface SvelteProxyOptions {
20
+ /**
21
+ * Custom function to resolve the API key.
22
+ * Defaults to reading from INFERENCE_API_KEY environment variable.
23
+ */
24
+ resolveApiKey?: () => Promise<string | undefined>;
25
+ }
26
+ type SvelteRequestHandler = (event: RequestEvent) => Promise<Response>;
27
+ /**
28
+ * Creates a SvelteKit request handler that proxies requests to the Inference.sh API.
29
+ *
30
+ * @param options - Proxy options
31
+ * @returns A SvelteKit request handler function
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // src/routes/api/inference/proxy/+server.ts
36
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/svelte";
37
+ *
38
+ * const handler = createRequestHandler();
39
+ *
40
+ * export const GET = handler;
41
+ * export const POST = handler;
42
+ * export const PUT = handler;
43
+ * ```
44
+ */
45
+ export declare function createRequestHandler({ resolveApiKey, }?: SvelteProxyOptions): SvelteRequestHandler;
46
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * SvelteKit proxy handler for Inference.sh API
4
+ *
5
+ * Works with SvelteKit's +server.ts route handlers.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // src/routes/api/inference/proxy/+server.ts
10
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/svelte";
11
+ *
12
+ * const handler = createRequestHandler();
13
+ *
14
+ * export const GET = handler;
15
+ * export const POST = handler;
16
+ * export const PUT = handler;
17
+ * ```
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.createRequestHandler = createRequestHandler;
21
+ const index_1 = require("./index");
22
+ /**
23
+ * Creates a SvelteKit request handler that proxies requests to the Inference.sh API.
24
+ *
25
+ * @param options - Proxy options
26
+ * @returns A SvelteKit request handler function
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // src/routes/api/inference/proxy/+server.ts
31
+ * import { createRequestHandler } from "@inferencesh/sdk/proxy/svelte";
32
+ *
33
+ * const handler = createRequestHandler();
34
+ *
35
+ * export const GET = handler;
36
+ * export const POST = handler;
37
+ * export const PUT = handler;
38
+ * ```
39
+ */
40
+ function createRequestHandler({ resolveApiKey = index_1.resolveApiKeyFromEnv, } = {}) {
41
+ return async (event) => {
42
+ const request = event.request;
43
+ const responseHeaders = new Headers();
44
+ return (0, index_1.handleRequest)({
45
+ id: "svelte",
46
+ method: request.method,
47
+ getRequestBody: async () => request.text(),
48
+ getHeaders: () => (0, index_1.fromHeaders)(request.headers),
49
+ getHeader: (name) => request.headers.get(name),
50
+ sendHeader: (name, value) => responseHeaders.set(name, value),
51
+ respondWith: (status, data) => new Response(JSON.stringify(data), {
52
+ status,
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ ...Object.fromEntries(responseHeaders.entries()),
56
+ },
57
+ }),
58
+ sendResponse: index_1.responsePassthrough,
59
+ resolveApiKey,
60
+ });
61
+ };
62
+ }
@@ -0,0 +1 @@
1
+ export * from "./svelte.js";