@inferencesh/sdk 0.20.1 → 0.22.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/CHANGELOG.md +104 -0
- package/dist/agent/actions.js +1 -4
- package/dist/agent/api.d.ts +37 -6
- package/dist/agent/api.js +37 -3
- package/dist/agent/reducer.js +23 -7
- package/dist/agent/types.d.ts +30 -5
- package/dist/api/agents.d.ts +9 -3
- package/dist/api/agents.js +87 -65
- package/dist/api/api-keys.d.ts +2 -5
- package/dist/api/apps.d.ts +9 -2
- package/dist/api/apps.js +9 -1
- package/dist/api/chats.d.ts +13 -3
- package/dist/api/chats.js +13 -2
- package/dist/api/engines.d.ts +4 -2
- package/dist/api/engines.js +3 -2
- package/dist/api/flow-runs.d.ts +7 -2
- package/dist/api/flow-runs.js +7 -2
- package/dist/api/flows.d.ts +13 -2
- package/dist/api/flows.js +15 -1
- package/dist/api/knowledge.d.ts +7 -4
- package/dist/api/knowledge.js +6 -4
- package/dist/api/mcp-servers.d.ts +2 -1
- package/dist/api/projects.d.ts +5 -3
- package/dist/api/projects.js +3 -2
- package/dist/api/secrets.d.ts +2 -2
- package/dist/api/tasks.js +39 -60
- package/dist/api/teams.d.ts +4 -2
- package/dist/api/teams.js +3 -2
- package/dist/api/write-bodies.d.ts +110 -0
- package/dist/api/write-bodies.js +1 -0
- package/dist/http/client.d.ts +68 -7
- package/dist/http/client.js +138 -97
- package/dist/http/index.d.ts +2 -2
- package/dist/http/loopback.d.ts +8 -0
- package/dist/http/loopback.js +18 -0
- package/dist/http/stream-until-terminal.d.ts +55 -0
- package/dist/http/stream-until-terminal.js +88 -0
- package/dist/http/stream.d.ts +1 -0
- package/dist/http/stream.js +7 -3
- package/dist/http/streamable.d.ts +25 -5
- package/dist/http/streamable.js +28 -40
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/proxy/endpoints.json +156 -0
- package/dist/proxy/express.d.ts +5 -6
- package/dist/proxy/express.js +18 -2
- package/dist/proxy/hono.d.ts +5 -3
- package/dist/proxy/hono.js +4 -3
- package/dist/proxy/index.d.ts +165 -6
- package/dist/proxy/index.js +409 -25
- package/dist/proxy/nextjs.d.ts +29 -17
- package/dist/proxy/nextjs.js +72 -62
- package/dist/proxy/remix.d.ts +5 -3
- package/dist/proxy/remix.js +4 -3
- package/dist/proxy/svelte.d.ts +5 -4
- package/dist/proxy/svelte.js +4 -4
- package/dist/types.d.ts +591 -271
- package/dist/types.js +117 -252
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/package.json +2 -1
package/dist/http/streamable.js
CHANGED
|
@@ -3,12 +3,8 @@
|
|
|
3
3
|
* This is the NDJSON alternative to SSE/EventSource that avoids
|
|
4
4
|
* the browser's ~6 connection limit per domain.
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
* Yields parsed JSON objects, automatically filtering heartbeats.
|
|
9
|
-
*/
|
|
10
|
-
export async function* streamable(url, options = {}) {
|
|
11
|
-
const { headers = {}, body, method = body ? 'POST' : 'GET', signal, skipHeartbeats = true, credentials, } = options;
|
|
6
|
+
/** Send the stream's request and hand back the body to read. */
|
|
7
|
+
async function openStream(source, { headers = {}, body, method = body ? 'POST' : 'GET', signal, credentials }) {
|
|
12
8
|
const requestHeaders = {
|
|
13
9
|
Accept: 'application/x-ndjson',
|
|
14
10
|
...headers,
|
|
@@ -16,13 +12,13 @@ export async function* streamable(url, options = {}) {
|
|
|
16
12
|
if (body) {
|
|
17
13
|
requestHeaders['Content-Type'] = 'application/json';
|
|
18
14
|
}
|
|
19
|
-
const
|
|
15
|
+
const init = {
|
|
20
16
|
method,
|
|
21
17
|
headers: requestHeaders,
|
|
22
18
|
body: body ? JSON.stringify(body) : undefined,
|
|
23
19
|
signal,
|
|
24
|
-
|
|
25
|
-
});
|
|
20
|
+
};
|
|
21
|
+
const res = typeof source === 'string' ? await fetch(source, { ...init, credentials }) : await source(init);
|
|
26
22
|
if (!res.ok) {
|
|
27
23
|
const text = await res.text();
|
|
28
24
|
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
@@ -30,7 +26,16 @@ export async function* streamable(url, options = {}) {
|
|
|
30
26
|
if (!res.body) {
|
|
31
27
|
throw new Error('No response body');
|
|
32
28
|
}
|
|
33
|
-
|
|
29
|
+
return res.body;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Stream NDJSON from an HTTP endpoint using fetch + ReadableStream.
|
|
33
|
+
* Yields parsed JSON objects, automatically filtering heartbeats.
|
|
34
|
+
* `source` is the URL to fetch, or a StreamRequest that sends the request.
|
|
35
|
+
*/
|
|
36
|
+
export async function* streamable(source, options = {}) {
|
|
37
|
+
const { skipHeartbeats = true } = options;
|
|
38
|
+
const reader = (await openStream(source, options)).getReader();
|
|
34
39
|
const decoder = new TextDecoder();
|
|
35
40
|
let buffer = '';
|
|
36
41
|
try {
|
|
@@ -109,12 +114,16 @@ export class StreamableManager {
|
|
|
109
114
|
this.abortController = new AbortController();
|
|
110
115
|
try {
|
|
111
116
|
this.options.onStart?.();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
body:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
const { options } = this;
|
|
118
|
+
const stream = 'request' in options
|
|
119
|
+
? streamableRaw(options.request, { body: options.body, signal: this.abortController.signal })
|
|
120
|
+
: streamableRaw(options.url, {
|
|
121
|
+
headers: options.headers,
|
|
122
|
+
body: options.body,
|
|
123
|
+
signal: this.abortController.signal,
|
|
124
|
+
credentials: options.credentials,
|
|
125
|
+
});
|
|
126
|
+
for await (const message of stream) {
|
|
118
127
|
if (!this.isRunning)
|
|
119
128
|
break;
|
|
120
129
|
const wrapper = message;
|
|
@@ -179,30 +188,9 @@ export class StreamableManager {
|
|
|
179
188
|
* Raw streamable that yields the full message including wrapper.
|
|
180
189
|
* Use this when you need access to event type or fields.
|
|
181
190
|
*/
|
|
182
|
-
export async function* streamableRaw(
|
|
183
|
-
const {
|
|
184
|
-
const
|
|
185
|
-
Accept: 'application/x-ndjson',
|
|
186
|
-
...headers,
|
|
187
|
-
};
|
|
188
|
-
if (body) {
|
|
189
|
-
requestHeaders['Content-Type'] = 'application/json';
|
|
190
|
-
}
|
|
191
|
-
const res = await fetch(url, {
|
|
192
|
-
method,
|
|
193
|
-
headers: requestHeaders,
|
|
194
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
195
|
-
signal,
|
|
196
|
-
credentials,
|
|
197
|
-
});
|
|
198
|
-
if (!res.ok) {
|
|
199
|
-
const text = await res.text();
|
|
200
|
-
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
201
|
-
}
|
|
202
|
-
if (!res.body) {
|
|
203
|
-
throw new Error('No response body');
|
|
204
|
-
}
|
|
205
|
-
const reader = res.body.getReader();
|
|
191
|
+
export async function* streamableRaw(source, options = {}) {
|
|
192
|
+
const { skipHeartbeats = true } = options;
|
|
193
|
+
const reader = (await openStream(source, options)).getReader();
|
|
206
194
|
const decoder = new TextDecoder();
|
|
207
195
|
let buffer = '';
|
|
208
196
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { HttpClient, type HttpClientConfig, type ErrorHandler, type MessageHandler, createHttpClient } from './http/client';
|
|
1
|
+
export { HttpClient, type HttpClientConfig, type ErrorHandler, type FailedRequest, type HttpFetchInit, type MessageHandler, createHttpClient } from './http/client';
|
|
2
2
|
export type { Response } from './http/response';
|
|
3
3
|
import type { Response } from './http/response';
|
|
4
4
|
export { StreamManager, type StreamManagerOptions, type PartialDataWrapper } from './http/stream';
|
|
5
|
-
export { StreamableManager, type StreamableManagerOptions, type StreamableMessage, streamable, streamableRaw } from './http/streamable';
|
|
5
|
+
export { StreamableManager, type StreamableManagerOptions, type StreamableSource, type StreamRequest, type StreamRequestInit, type StreamableMessage, streamable, streamableRaw } from './http/streamable';
|
|
6
6
|
export { PollManager, type PollManagerOptions } from './http/poll';
|
|
7
|
+
export { isLoopbackHost, isHttpsOrLoopback } from './http/loopback';
|
|
7
8
|
export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError, isRequirementsNotMetException, isInferenceError, isSessionError, } from './http/errors';
|
|
8
9
|
export { TasksAPI, type RunOptions, type WatchOptions, type TaskWatch } from './api/tasks';
|
|
9
10
|
export { SocketsAPI, type SocketTarget, type OpenSocketOptions } from './api/sockets';
|
|
@@ -17,6 +18,7 @@ export { FlowRunsAPI } from './api/flow-runs';
|
|
|
17
18
|
export { EnginesAPI } from './api/engines';
|
|
18
19
|
export { KnowledgeAPI, SkillsAPI } from './api/knowledge';
|
|
19
20
|
export { ArtifactsAPI } from './api/artifacts';
|
|
21
|
+
export type { AgentUpdateBody, AppPatchBody, AppUpdateBody, ChatUpdateBody, EngineUpdateBody, FlowUpdateBody, FlowPatchBody, FlowViewportBody, ProjectWriteBody, KnowledgeUpdateBody, TeamUpdateBody, MCPServerUpdateBody, } from './api/write-bodies';
|
|
20
22
|
export { TeamsAPI } from './api/teams';
|
|
21
23
|
export { SecretsAPI } from './api/secrets';
|
|
22
24
|
export { ApiKeysAPI } from './api/api-keys';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { HttpClient, createHttpClient } from './http/client';
|
|
|
3
3
|
export { StreamManager } from './http/stream';
|
|
4
4
|
export { StreamableManager, streamable, streamableRaw } from './http/streamable';
|
|
5
5
|
export { PollManager } from './http/poll';
|
|
6
|
+
export { isLoopbackHost, isHttpsOrLoopback } from './http/loopback';
|
|
6
7
|
export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError,
|
|
7
8
|
// Type guards (use these instead of instanceof for reliability)
|
|
8
9
|
isRequirementsNotMetException, isInferenceError, isSessionError, } from './http/errors';
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Generated by `make proxy-endpoints` from the route table (internal/api/proxy_endpoints_test.go). Do not edit.",
|
|
3
|
+
"preset": "run-only",
|
|
4
|
+
"endpoints": [
|
|
5
|
+
{
|
|
6
|
+
"method": "POST",
|
|
7
|
+
"path": "/apps/run",
|
|
8
|
+
"purpose": "run an app",
|
|
9
|
+
"scopes": [
|
|
10
|
+
"apps:execute",
|
|
11
|
+
"apps:read",
|
|
12
|
+
"conversations:read"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"method": "POST",
|
|
17
|
+
"path": "/files",
|
|
18
|
+
"purpose": "upload a file input of a run or message",
|
|
19
|
+
"scopes": [
|
|
20
|
+
"files:write"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"method": "GET",
|
|
25
|
+
"path": "/tasks/{id}",
|
|
26
|
+
"purpose": "read a task",
|
|
27
|
+
"scopes": [
|
|
28
|
+
"apps:read"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"method": "GET",
|
|
33
|
+
"path": "/tasks/{id}/status",
|
|
34
|
+
"purpose": "poll a task",
|
|
35
|
+
"scopes": [
|
|
36
|
+
"apps:read"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"method": "GET",
|
|
41
|
+
"path": "/tasks/{id}/stream",
|
|
42
|
+
"purpose": "stream a task",
|
|
43
|
+
"scopes": [
|
|
44
|
+
"apps:read",
|
|
45
|
+
"conversations:read",
|
|
46
|
+
"flows:read"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"method": "POST",
|
|
51
|
+
"path": "/tasks/{id}/cancel",
|
|
52
|
+
"purpose": "cancel a task",
|
|
53
|
+
"scopes": [
|
|
54
|
+
"apps:execute"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"method": "POST",
|
|
59
|
+
"path": "/agents/run",
|
|
60
|
+
"purpose": "send a message to an agent",
|
|
61
|
+
"scopes": [
|
|
62
|
+
"agents:execute",
|
|
63
|
+
"conversations:read"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"method": "POST",
|
|
68
|
+
"path": "/chats",
|
|
69
|
+
"purpose": "start a chat with an agent",
|
|
70
|
+
"scopes": [
|
|
71
|
+
"conversations:write"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"method": "POST",
|
|
76
|
+
"path": "/chats/{id}/messages",
|
|
77
|
+
"purpose": "send a message in a chat",
|
|
78
|
+
"scopes": [
|
|
79
|
+
"agents:execute"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"method": "POST",
|
|
84
|
+
"path": "/chats/{id}/agent",
|
|
85
|
+
"purpose": "hand a chat to another agent",
|
|
86
|
+
"scopes": [
|
|
87
|
+
"conversations:write"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"method": "GET",
|
|
92
|
+
"path": "/agents/{namespace}/{name}",
|
|
93
|
+
"purpose": "read an agent's info",
|
|
94
|
+
"scopes": [
|
|
95
|
+
"agents:read"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"method": "GET",
|
|
100
|
+
"path": "/chats/{id}",
|
|
101
|
+
"purpose": "read a chat",
|
|
102
|
+
"scopes": [
|
|
103
|
+
"conversations:read"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"method": "GET",
|
|
108
|
+
"path": "/chats/{id}/status",
|
|
109
|
+
"purpose": "poll a chat",
|
|
110
|
+
"scopes": [
|
|
111
|
+
"conversations:read"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"method": "GET",
|
|
116
|
+
"path": "/chats/{id}/messages",
|
|
117
|
+
"purpose": "read a chat's messages",
|
|
118
|
+
"scopes": [
|
|
119
|
+
"conversations:read"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"method": "GET",
|
|
124
|
+
"path": "/chats/{id}/stream",
|
|
125
|
+
"purpose": "stream a chat",
|
|
126
|
+
"scopes": [
|
|
127
|
+
"conversations:read"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"method": "POST",
|
|
132
|
+
"path": "/chats/{id}/stop",
|
|
133
|
+
"purpose": "stop a chat",
|
|
134
|
+
"scopes": [
|
|
135
|
+
"conversations:write"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"method": "POST",
|
|
140
|
+
"path": "/chats/messages/{messageId}/cancel",
|
|
141
|
+
"purpose": "cancel a message",
|
|
142
|
+
"scopes": [
|
|
143
|
+
"conversations:read",
|
|
144
|
+
"conversations:write"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"method": "POST",
|
|
149
|
+
"path": "/tools/{toolId}",
|
|
150
|
+
"purpose": "answer a client tool call",
|
|
151
|
+
"scopes": [
|
|
152
|
+
"conversations:write"
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
package/dist/proxy/express.d.ts
CHANGED
|
@@ -8,18 +8,17 @@
|
|
|
8
8
|
*
|
|
9
9
|
* const app = express();
|
|
10
10
|
* app.use(express.json());
|
|
11
|
-
* app.all(PROXY_ROUTE, createHandler());
|
|
11
|
+
* app.all(PROXY_ROUTE, createHandler({ allowedEndpoints: ["my-team/*"] }));
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
import type { RequestHandler } from "express";
|
|
14
|
+
import type { Request, RequestHandler } from "express";
|
|
15
|
+
import { type ProxyOptions } from "./index";
|
|
15
16
|
/** Default proxy route path */
|
|
16
17
|
export declare const PROXY_ROUTE = "/api/inference/proxy";
|
|
17
18
|
/** @deprecated Use PROXY_ROUTE */
|
|
18
19
|
export declare const route = "/api/inference/proxy";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
apiKey?: string;
|
|
22
|
-
}
|
|
20
|
+
/** Proxy options; `isAuthenticated` receives the Express request. */
|
|
21
|
+
export type ExpressProxyOptions = ProxyOptions<Request>;
|
|
23
22
|
/**
|
|
24
23
|
* Creates an Express middleware handler for the Inference.sh proxy.
|
|
25
24
|
*
|
package/dist/proxy/express.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* const app = express();
|
|
10
10
|
* app.use(express.json());
|
|
11
|
-
* app.all(PROXY_ROUTE, createHandler());
|
|
11
|
+
* app.all(PROXY_ROUTE, createHandler({ allowedEndpoints: ["my-team/*"] }));
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
import { PROXY_PATH, processProxyRequest, } from "./index";
|
|
@@ -23,8 +23,19 @@ export const route = PROXY_ROUTE;
|
|
|
23
23
|
*/
|
|
24
24
|
export function createHandler(options) {
|
|
25
25
|
return async (req, res, _next) => {
|
|
26
|
+
// The client hung up before the response finished: stop the upstream
|
|
27
|
+
// request too. Chat and task streams never end on their own, so
|
|
28
|
+
// without this the proxy keeps reading them for nobody and the open
|
|
29
|
+
// upstream connection keeps the server process alive.
|
|
30
|
+
const upstream = new AbortController();
|
|
31
|
+
res.on("close", () => {
|
|
32
|
+
if (!res.writableFinished)
|
|
33
|
+
upstream.abort();
|
|
34
|
+
});
|
|
26
35
|
return processProxyRequest({
|
|
27
36
|
framework: "express",
|
|
37
|
+
signal: upstream.signal,
|
|
38
|
+
request: req,
|
|
28
39
|
method: req.method,
|
|
29
40
|
body: async () => JSON.stringify(req.body),
|
|
30
41
|
headers: () => req.headers,
|
|
@@ -61,7 +72,12 @@ export function createHandler(options) {
|
|
|
61
72
|
// Handle text responses
|
|
62
73
|
return res.send(await response.text());
|
|
63
74
|
},
|
|
64
|
-
}, options)
|
|
75
|
+
}, options).catch((error) => {
|
|
76
|
+
// Aborted because the client left; there is nobody to answer.
|
|
77
|
+
if (upstream.signal.aborted)
|
|
78
|
+
return res;
|
|
79
|
+
throw error;
|
|
80
|
+
});
|
|
65
81
|
};
|
|
66
82
|
}
|
|
67
83
|
/** @deprecated Use createHandler() */
|
package/dist/proxy/hono.d.ts
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* import { createHandler } from "@inferencesh/sdk/proxy/hono";
|
|
10
10
|
*
|
|
11
11
|
* const app = new Hono();
|
|
12
|
-
* app.all("/api/inference/proxy", createHandler());
|
|
12
|
+
* app.all("/api/inference/proxy", createHandler({ allowedEndpoints: ["my-team/*"] }));
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from "hono";
|
|
16
|
-
|
|
16
|
+
import { type ProxyOptions } from "./index";
|
|
17
|
+
/** Proxy options; `isAuthenticated` receives the Context. */
|
|
18
|
+
export interface HonoProxyOptions extends ProxyOptions<Context> {
|
|
17
19
|
/** Custom function to resolve the API key */
|
|
18
20
|
resolveApiKey?: () => Promise<string | undefined>;
|
|
19
21
|
}
|
|
@@ -21,7 +23,7 @@ type HonoHandler = (context: Context) => Promise<Response>;
|
|
|
21
23
|
/**
|
|
22
24
|
* Creates a Hono route handler for the Inference.sh proxy.
|
|
23
25
|
*/
|
|
24
|
-
export declare function createHandler({ resolveApiKey, }?: HonoProxyOptions): HonoHandler;
|
|
26
|
+
export declare function createHandler({ resolveApiKey, ...options }?: HonoProxyOptions): HonoHandler;
|
|
25
27
|
/** @deprecated Use createHandler */
|
|
26
28
|
export declare const createRouteHandler: typeof createHandler;
|
|
27
29
|
export {};
|
package/dist/proxy/hono.js
CHANGED
|
@@ -9,19 +9,20 @@
|
|
|
9
9
|
* import { createHandler } from "@inferencesh/sdk/proxy/hono";
|
|
10
10
|
*
|
|
11
11
|
* const app = new Hono();
|
|
12
|
-
* app.all("/api/inference/proxy", createHandler());
|
|
12
|
+
* app.all("/api/inference/proxy", createHandler({ allowedEndpoints: ["my-team/*"] }));
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
import { processProxyRequest, getEnvApiKey, passthrough, headersToRecord, } from "./index";
|
|
16
16
|
/**
|
|
17
17
|
* Creates a Hono route handler for the Inference.sh proxy.
|
|
18
18
|
*/
|
|
19
|
-
export function createHandler({ resolveApiKey = getEnvApiKey, } = {}) {
|
|
19
|
+
export function createHandler({ resolveApiKey = getEnvApiKey, ...options } = {}) {
|
|
20
20
|
return async (context) => {
|
|
21
21
|
const responseHeaders = new Headers();
|
|
22
22
|
const url = new URL(context.req.url);
|
|
23
23
|
return processProxyRequest({
|
|
24
24
|
framework: "hono",
|
|
25
|
+
request: context,
|
|
25
26
|
method: context.req.method,
|
|
26
27
|
body: async () => {
|
|
27
28
|
try {
|
|
@@ -44,7 +45,7 @@ export function createHandler({ resolveApiKey = getEnvApiKey, } = {}) {
|
|
|
44
45
|
}),
|
|
45
46
|
respond: passthrough,
|
|
46
47
|
apiKey: resolveApiKey,
|
|
47
|
-
});
|
|
48
|
+
}, options);
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
/** @deprecated Use createHandler */
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
* Protects API keys by proxying requests from frontend apps through your server.
|
|
5
5
|
* Supports Next.js (App & Page Router), Express, Hono, Remix, and SvelteKit.
|
|
6
6
|
*
|
|
7
|
+
* The proxy forwards only what a frontend needs to run things (app runs,
|
|
8
|
+
* agent chats, and reading or streaming their task or chat; see
|
|
9
|
+
* PROXY_ENDPOINTS) and refuses every other API call with 403.
|
|
10
|
+
*
|
|
7
11
|
* @example Next.js App Router
|
|
8
12
|
* ```typescript
|
|
9
13
|
* // app/api/inference/proxy/route.ts
|
|
10
14
|
* import { createHandler } from "@inferencesh/sdk/proxy/nextjs";
|
|
11
|
-
* export const { GET, POST
|
|
15
|
+
* export const { GET, POST } = createHandler({
|
|
16
|
+
* allowedEndpoints: ["my-team/support-agent"],
|
|
17
|
+
* isAuthenticated: async (req) => Boolean(req.cookies.get("session")),
|
|
18
|
+
* });
|
|
12
19
|
* ```
|
|
13
20
|
*/
|
|
14
21
|
/** Header name for target URL */
|
|
@@ -17,15 +24,24 @@ export declare const INF_TARGET_HEADER = "x-inf-target-url";
|
|
|
17
24
|
export declare const INF_TARGET_PARAM = "__inf_target";
|
|
18
25
|
/** Default proxy route path */
|
|
19
26
|
export declare const PROXY_PATH = "/api/inference/proxy";
|
|
27
|
+
/** The API the proxy forwards to unless `apiUrl` or `apiBaseUrl` says otherwise. */
|
|
28
|
+
export declare const DEFAULT_API_URL = "https://api.inference.sh";
|
|
29
|
+
/**
|
|
30
|
+
* Default `maxUploadBytes`: 100 MiB, well under the API's own per-file limit
|
|
31
|
+
* (storage.max_upload_bytes, 5 GiB by default).
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
|
|
20
34
|
/** Header value from various HTTP libraries */
|
|
21
35
|
export type HttpHeaderValue = string | string[] | undefined | null;
|
|
22
36
|
/**
|
|
23
37
|
* Framework adapter interface for proxy handlers.
|
|
24
38
|
* Implement this to support a new framework.
|
|
25
39
|
*/
|
|
26
|
-
export interface ProxyAdapter<T> {
|
|
40
|
+
export interface ProxyAdapter<T, R = unknown> {
|
|
27
41
|
/** Framework name for user-agent */
|
|
28
42
|
framework: string;
|
|
43
|
+
/** The framework's request, handed to `isAuthenticated` */
|
|
44
|
+
request?: R;
|
|
29
45
|
/** HTTP method of the request */
|
|
30
46
|
method: string;
|
|
31
47
|
/** Get request body as string */
|
|
@@ -44,17 +60,159 @@ export interface ProxyAdapter<T> {
|
|
|
44
60
|
respond: (response: Response) => Promise<T>;
|
|
45
61
|
/** Custom API key resolver (optional) */
|
|
46
62
|
apiKey?: () => Promise<string | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Aborts the upstream request (optional). Adapters that copy the
|
|
65
|
+
* upstream body themselves abort it when their client disconnects, so a
|
|
66
|
+
* stream nobody reads any more (GET /chats/{id}/stream never ends on its
|
|
67
|
+
* own) does not stay open.
|
|
68
|
+
*/
|
|
69
|
+
signal?: AbortSignal;
|
|
47
70
|
}
|
|
48
71
|
/**
|
|
49
72
|
* Proxy handler options.
|
|
50
73
|
*/
|
|
51
|
-
export interface ProxyOptions {
|
|
74
|
+
export interface ProxyOptions<R = unknown> {
|
|
52
75
|
/** Custom API key (defaults to INFERENCE_API_KEY env var) */
|
|
53
76
|
apiKey?: string;
|
|
54
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* The API the frontend's SDK client calls (its `baseUrl`), default
|
|
79
|
+
* https://api.inference.sh. The proxy attaches the key only to requests
|
|
80
|
+
* for this origin (or an `allowedDomains` host); a target on any other
|
|
81
|
+
* host, other inference.sh hosts included, is refused with 412.
|
|
82
|
+
*/
|
|
83
|
+
apiUrl?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Rewrite every target to this API base (defaults to the
|
|
86
|
+
* INFERENCE_API_BASE_URL env var). When set, it is also the only origin
|
|
87
|
+
* the proxy forwards to.
|
|
88
|
+
*/
|
|
55
89
|
apiBaseUrl?: string;
|
|
56
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Extra target hosts, besides the API's. Each pattern must match the
|
|
92
|
+
* whole host (`host:port` when the target names a port).
|
|
93
|
+
*/
|
|
57
94
|
allowedDomains?: RegExp[];
|
|
95
|
+
/**
|
|
96
|
+
* Largest file, in bytes, a visitor may upload through the proxy
|
|
97
|
+
* (default 100 MiB). `POST /files` declares each file's size and the API
|
|
98
|
+
* binds the upload URL to it; the bytes then go straight to storage and
|
|
99
|
+
* count against the API key owner's storage quota. One file per request.
|
|
100
|
+
*/
|
|
101
|
+
maxUploadBytes?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Apps and agents visitors may run, as `namespace/name` globs: "ana/helper",
|
|
104
|
+
* or "ana/*" for all of a namespace. Unset or empty allows every app and
|
|
105
|
+
* agent the API key can run.
|
|
106
|
+
*
|
|
107
|
+
* Checked where a request names the app or agent: a new app run, a new
|
|
108
|
+
* chat or agent message, a chat handed to another agent, an agent's info.
|
|
109
|
+
* Requests about an existing task or chat name it by id, and the proxy
|
|
110
|
+
* cannot tell which app or agent that is: whoever holds a task or chat id
|
|
111
|
+
* can read and stream it, cancel or stop it, and send messages in the
|
|
112
|
+
* chat (which run the chat's agent). All visitors share one key, so the
|
|
113
|
+
* id is the only boundary between them. When set, ad-hoc agent configs
|
|
114
|
+
* (`agent_config`) and runs by `app_id`/`version_id` are refused.
|
|
115
|
+
*/
|
|
116
|
+
allowedEndpoints?: string[];
|
|
117
|
+
/**
|
|
118
|
+
* Match `allowedEndpoints` entries against the whole ref, version and
|
|
119
|
+
* function included, instead of as `namespace/name` globs: the entry
|
|
120
|
+
* "ana/helper@v2" lets only that exact ref through, and "ana/helper"
|
|
121
|
+
* only the unversioned ref. Entries are refs, without `*`.
|
|
122
|
+
*/
|
|
123
|
+
exactRefs?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* The calls to forward, a subset of PROXY_ENDPOINTS (default: all of
|
|
126
|
+
* them). Entries must come from PROXY_ENDPOINTS or `pickProxyEndpoint`,
|
|
127
|
+
* which can narrow one further but never widen it; anything else
|
|
128
|
+
* refuses every request with 500.
|
|
129
|
+
*/
|
|
130
|
+
endpoints?: readonly ProxyEndpoint[];
|
|
131
|
+
/**
|
|
132
|
+
* Decides whether a request may use the API key, e.g. by checking your
|
|
133
|
+
* session. Requests it answers false for are refused with 401. Receives
|
|
134
|
+
* the framework's request (NextRequest, Express Request, Hono Context, ...).
|
|
135
|
+
*/
|
|
136
|
+
isAuthenticated?: (request: R) => boolean | Promise<boolean>;
|
|
137
|
+
/**
|
|
138
|
+
* Without `isAuthenticated`, whether anyone may use the proxy (default
|
|
139
|
+
* true, for compatibility). Set it to false to require `isAuthenticated`:
|
|
140
|
+
* with neither, every request is refused.
|
|
141
|
+
*/
|
|
142
|
+
allowUnauthorizedRequests?: boolean;
|
|
143
|
+
}
|
|
144
|
+
/** Where a request names the app or agent it runs. */
|
|
145
|
+
type RefSource =
|
|
146
|
+
/** Body field naming the app or agent, and fields that would name one another way. */
|
|
147
|
+
{
|
|
148
|
+
body: string;
|
|
149
|
+
refuse: string[];
|
|
150
|
+
}
|
|
151
|
+
/** `/agents/{namespace}/{name}` in the path. */
|
|
152
|
+
| {
|
|
153
|
+
path: true;
|
|
154
|
+
};
|
|
155
|
+
/** One call of the API's proxy manifest (src/proxy/endpoints.json). */
|
|
156
|
+
export interface ProxyManifestEndpoint {
|
|
157
|
+
method: string;
|
|
158
|
+
/** API path template: `{namespace}`/`{name}` are an agent ref, every other `{…}` an id. */
|
|
159
|
+
path: string;
|
|
160
|
+
purpose: string;
|
|
161
|
+
/** Scopes the call takes: what the site's API key must hold (the manifest's preset grants them all). */
|
|
162
|
+
scopes: string[];
|
|
163
|
+
}
|
|
164
|
+
export interface ProxyEndpoint {
|
|
165
|
+
method: "GET" | "POST";
|
|
166
|
+
/** The manifest's route, "METHOD /path/{id}" (what pickProxyEndpoint takes). */
|
|
167
|
+
route: string;
|
|
168
|
+
/** API path, matched whole. */
|
|
169
|
+
path: RegExp;
|
|
170
|
+
/** What the endpoint is for (used in docs and tests). */
|
|
171
|
+
purpose: string;
|
|
172
|
+
/** Scopes the call takes, from the manifest. */
|
|
173
|
+
scopes: readonly string[];
|
|
174
|
+
/** Set when the request names an app or agent: checked against allowedEndpoints. */
|
|
175
|
+
ref?: RefSource;
|
|
176
|
+
/** Set when only these body fields may be sent. */
|
|
177
|
+
onlyFields?: string[];
|
|
178
|
+
/** Further checks of the body (a JSON object), given the proxy's limits. */
|
|
179
|
+
validate?: (body: Record<string, unknown>, limits: ProxyLimits) => PolicyRefusal | undefined;
|
|
180
|
+
}
|
|
181
|
+
/** Limits from the proxy options, handed to endpoint checks. */
|
|
182
|
+
export interface ProxyLimits {
|
|
183
|
+
maxUploadBytes: number;
|
|
184
|
+
}
|
|
185
|
+
/** What the proxy checks of a call's request, beyond the manifest. */
|
|
186
|
+
type EndpointRules = Pick<ProxyEndpoint, "ref" | "onlyFields" | "validate">;
|
|
187
|
+
/**
|
|
188
|
+
* The proxy's own checks, by manifest route. A route here that the manifest
|
|
189
|
+
* does not list is never forwarded (a test keeps the two in step).
|
|
190
|
+
*/
|
|
191
|
+
export declare const PROXY_ENDPOINT_RULES: Readonly<Record<string, EndpointRules>>;
|
|
192
|
+
/** The API's proxy manifest as shipped in this package. */
|
|
193
|
+
export declare const PROXY_MANIFEST: {
|
|
194
|
+
readonly preset: string;
|
|
195
|
+
readonly endpoints: readonly ProxyManifestEndpoint[];
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* The calls the proxy forwards: exactly the manifest's, with the proxy's
|
|
199
|
+
* request checks. Everything else is refused with 403.
|
|
200
|
+
*/
|
|
201
|
+
export declare const PROXY_ENDPOINTS: readonly ProxyEndpoint[];
|
|
202
|
+
/** The PROXY_ENDPOINTS entry for a request, or undefined when the proxy does not forward it. */
|
|
203
|
+
export declare function matchProxyEndpoint(method: string, pathname: string, endpoints?: readonly ProxyEndpoint[]): ProxyEndpoint | undefined;
|
|
204
|
+
/**
|
|
205
|
+
* The PROXY_ENDPOINTS entry for `route` ("METHOD /path", with `{id}`,
|
|
206
|
+
* `{namespace}` and `{name}` placeholders, e.g. "POST /chats/{id}/messages"),
|
|
207
|
+
* optionally narrowed: `onlyFields` must be a subset of the entry's own,
|
|
208
|
+
* and `validate` runs after the entry's own check. For the `endpoints`
|
|
209
|
+
* option. Throws when the proxy does not forward the route.
|
|
210
|
+
*/
|
|
211
|
+
export declare function pickProxyEndpoint(route: string, narrow?: Pick<ProxyEndpoint, "onlyFields" | "validate">): ProxyEndpoint;
|
|
212
|
+
/** A refusal: the HTTP status and the message sent back. */
|
|
213
|
+
export interface PolicyRefusal {
|
|
214
|
+
status: number;
|
|
215
|
+
error: string;
|
|
58
216
|
}
|
|
59
217
|
/**
|
|
60
218
|
* Process a proxied request to the Inference.sh API.
|
|
@@ -66,7 +224,7 @@ export interface ProxyOptions {
|
|
|
66
224
|
* @param options - Optional configuration
|
|
67
225
|
* @returns Promise resolving to the framework's response type
|
|
68
226
|
*/
|
|
69
|
-
export declare function processProxyRequest<T>(adapter: ProxyAdapter<T>, options?: ProxyOptions): Promise<T>;
|
|
227
|
+
export declare function processProxyRequest<T, R = unknown>(adapter: ProxyAdapter<T, R>, options?: ProxyOptions<R>): Promise<T>;
|
|
70
228
|
/**
|
|
71
229
|
* Convert Headers object to plain record.
|
|
72
230
|
*/
|
|
@@ -95,3 +253,4 @@ export declare const fromHeaders: typeof headersToRecord;
|
|
|
95
253
|
export declare const responsePassthrough: (res: Response) => Promise<Response>;
|
|
96
254
|
/** @deprecated Use getEnvApiKey */
|
|
97
255
|
export declare const resolveApiKeyFromEnv: () => Promise<string | undefined>;
|
|
256
|
+
export {};
|