@every-app/sdk 0.3.0-rc.2 → 0.3.0-rc.4
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/README.md +1 -1
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/sentinel.d.ts +57 -2
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +27 -2
- package/dist/cloudflare/worker.d.ts +297 -5
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +279 -12
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/dev/keypair.d.ts +2 -0
- package/dist/dev/keypair.d.ts.map +1 -1
- package/dist/dev/keypair.js +2 -0
- package/dist/internal/aiDefault.d.ts +141 -0
- package/dist/internal/aiDefault.d.ts.map +1 -0
- package/dist/internal/aiDefault.js +204 -0
- package/dist/internal/aiProviders.d.ts +4 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +5 -1
- package/dist/internal/appSettings.d.ts +20 -2
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +53 -7
- package/dist/internal/audit.d.ts +108 -0
- package/dist/internal/audit.d.ts.map +1 -0
- package/dist/internal/audit.js +235 -0
- package/dist/internal/capabilities.d.ts +276 -7
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +111 -0
- package/dist/internal/catalog/index.d.ts +39 -2
- package/dist/internal/catalog/index.d.ts.map +1 -1
- package/dist/internal/catalog/inference.d.ts +39 -2
- package/dist/internal/catalog/inference.d.ts.map +1 -1
- package/dist/internal/catalog/inference.js +36 -4
- package/dist/internal/devProviders.d.ts +2 -2
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +19 -1
- package/dist/internal/events.d.ts +13 -2
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +42 -4
- package/dist/internal/identity.d.ts +29 -3
- package/dist/internal/identity.d.ts.map +1 -1
- package/dist/internal/identity.js +37 -9
- package/dist/internal/index.d.ts +5 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +5 -1
- package/dist/internal/members.d.ts +3 -2
- package/dist/internal/members.d.ts.map +1 -1
- package/dist/internal/oauthPresets.d.ts +29 -0
- package/dist/internal/oauthPresets.d.ts.map +1 -0
- package/dist/internal/oauthPresets.js +534 -0
- package/dist/internal/oauthScopes.d.ts +56 -1
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +98 -4
- package/dist/internal/oauthSpec.d.ts +59 -0
- package/dist/internal/oauthSpec.d.ts.map +1 -0
- package/dist/internal/oauthSpec.js +676 -0
- package/dist/internal/protocol.d.ts +36 -1
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +30 -1
- package/dist/internal/providerRegistry.d.ts +17 -6
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +47 -2
- package/dist/internal/providerSpec.d.ts +11 -7
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +134 -37
- package/dist/internal/signInProviders.d.ts.map +1 -1
- package/dist/internal/signInProviders.js +11 -4
- package/dist/server/ai.d.ts +45 -0
- package/dist/server/ai.d.ts.map +1 -0
- package/dist/server/ai.js +40 -0
- package/dist/server/attribution.d.ts +9 -5
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +12 -4
- package/dist/server/connectUrl.d.ts +21 -0
- package/dist/server/connectUrl.d.ts.map +1 -0
- package/dist/server/connectUrl.js +37 -0
- package/dist/server/context.d.ts +68 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +117 -0
- package/dist/server/emit.d.ts +1 -1
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +4 -4
- package/dist/server/everyApp.d.ts +27 -7
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +106 -5
- package/dist/server/fromMcpTools.d.ts +89 -0
- package/dist/server/fromMcpTools.d.ts.map +1 -0
- package/dist/server/fromMcpTools.js +192 -0
- package/dist/server/gatewayFetch.d.ts +8 -6
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +78 -11
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -2
- package/dist/server/integrationFetch.d.ts +55 -8
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +198 -18
- package/dist/server/listMembers.d.ts +1 -1
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +5 -8
- package/dist/server/sendEmail.d.ts +1 -1
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +4 -4
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +11 -5
- package/package.json +13 -14
- package/LICENSE +0 -21
package/dist/server/everyApp.js
CHANGED
|
@@ -14,10 +14,21 @@
|
|
|
14
14
|
* `cloudflare.config.ts` imports from the Worker in dev).
|
|
15
15
|
* It also answers the gateway's internal tools contract
|
|
16
16
|
* (`/__everyapp/tools/*`) for the app's `defineTools()` catalog.
|
|
17
|
+
*
|
|
18
|
+
* Around the handler and around each tool call it opens the request
|
|
19
|
+
* context (context.ts): `currentUser()` reads the user anywhere below, and
|
|
20
|
+
* every call back to the Gateway is attributed to the request without a
|
|
21
|
+
* `for`. A deployed Worker is pinned to its organization
|
|
22
|
+
* (`EVERY_APP_ORG_ID`), and an identity for any other is refused on both
|
|
23
|
+
* paths. The response leaves without `Set-Cookie`: the perimeter strips
|
|
24
|
+
* every `Cookie` before a request reaches the app, so a cookie the app's
|
|
25
|
+
* framework sets could never come back, and one named like the Gateway's
|
|
26
|
+
* session cookie would lock the person out of the app host.
|
|
17
27
|
*/
|
|
18
|
-
import { APP_ID_ENV, AUDIENCE_ENV, ConfigurationError, DEV_ENV, IdentityError, ISSUER_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, } from "../internal/index.js";
|
|
28
|
+
import { APP_ID_ENV, AUDIENCE_ENV, ConfigurationError, DEV_ENV, IdentityError, IDENTITY_HEADER, ISSUER_ENV, ORG_ID_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, isAdmin, } from "../internal/index.js";
|
|
29
|
+
import { currentUser, serveRequest } from "./context.js";
|
|
19
30
|
import { handleToolsRequest } from "./toolsEndpoint.js";
|
|
20
|
-
export { ConfigurationError, IdentityError };
|
|
31
|
+
export { ConfigurationError, IdentityError, isAdmin };
|
|
21
32
|
const requestIdentities = new WeakMap();
|
|
22
33
|
function envValue(env, key) {
|
|
23
34
|
if (!env || typeof env !== "object")
|
|
@@ -68,6 +79,17 @@ function audienceFor(env, config) {
|
|
|
68
79
|
return appId;
|
|
69
80
|
throw new ConfigurationError(`${AUDIENCE_ENV} is not set — the app's cloudflare.config.ts must declare it (everyAppWorker() does at deploy).`);
|
|
70
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The organization a deployed Worker serves ({@link ORG_ID_ENV}), which an
|
|
84
|
+
* identity must name. Under the dev opt-in there is none to enforce: the
|
|
85
|
+
* stub gateway's one organization is the only one, and `everyapp dev
|
|
86
|
+
* --mode mirror` signs real organizations in.
|
|
87
|
+
*/
|
|
88
|
+
function pinnedOrganization(env, config) {
|
|
89
|
+
if (devIdentitiesAllowed(env, config))
|
|
90
|
+
return undefined;
|
|
91
|
+
return envValue(env, ORG_ID_ENV);
|
|
92
|
+
}
|
|
71
93
|
function unauthorized(message) {
|
|
72
94
|
return Response.json({ error: "unauthenticated", message }, { status: 401 });
|
|
73
95
|
}
|
|
@@ -77,6 +99,7 @@ function misconfigured(message) {
|
|
|
77
99
|
function identityOptions(env, config, audience) {
|
|
78
100
|
return {
|
|
79
101
|
audience,
|
|
102
|
+
organizationId: pinnedOrganization(env, config),
|
|
80
103
|
issuer: config.issuer ?? envValue(env, ISSUER_ENV),
|
|
81
104
|
publicKeys: config.publicKeys,
|
|
82
105
|
env: env,
|
|
@@ -91,15 +114,54 @@ async function resolveIdentity(request, env, config) {
|
|
|
91
114
|
function rememberIdentity(request, identity) {
|
|
92
115
|
requestIdentities.set(request, identity);
|
|
93
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The user the request context already verified, when `request` carries
|
|
119
|
+
* the same identity token: a framework that rebuilds the request (TanStack
|
|
120
|
+
* Start's `getRequest()`, a Hono context) still gets the one verification.
|
|
121
|
+
*/
|
|
122
|
+
function contextUserFor(request) {
|
|
123
|
+
const headers = "headers" in request ? request.headers : request;
|
|
124
|
+
const token = headers.get(IDENTITY_HEADER);
|
|
125
|
+
if (!token)
|
|
126
|
+
return null;
|
|
127
|
+
const scoped = servedRequestToken();
|
|
128
|
+
return scoped && scoped.token === token ? scoped.user : null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The served request's token and user, or null outside a request. Reads
|
|
132
|
+
* the context without throwing (currentUser() throws outside one).
|
|
133
|
+
*/
|
|
134
|
+
function servedRequestToken() {
|
|
135
|
+
let user;
|
|
136
|
+
try {
|
|
137
|
+
user = currentUser();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!user)
|
|
143
|
+
return null;
|
|
144
|
+
const served = servedRequests.get(user);
|
|
145
|
+
return served ? { token: served, user } : null;
|
|
146
|
+
}
|
|
147
|
+
// The identity token of each request everyApp() serves, by its user, so a
|
|
148
|
+
// second read of the same token answers from the context.
|
|
149
|
+
const servedRequests = new WeakMap();
|
|
94
150
|
/**
|
|
95
151
|
* Return the verified Every App user for this request, or null for a declared
|
|
96
|
-
* public request.
|
|
97
|
-
*
|
|
152
|
+
* public request. `request` is the inbound `Request` or just its `Headers`
|
|
153
|
+
* (a framework that hands out headers alone needs no made-up Request).
|
|
154
|
+
* Answers from the request context or the per-request cache first, then
|
|
155
|
+
* verifies the identity headers from the audience and public keys in env.
|
|
156
|
+
* Inside a handler `everyApp()` serves, `currentUser()` is the shorter way.
|
|
98
157
|
*/
|
|
99
158
|
export async function getEveryAppUser(request, env) {
|
|
100
159
|
const cached = requestIdentities.get(request);
|
|
101
160
|
if (cached)
|
|
102
161
|
return cached.user;
|
|
162
|
+
const fromContext = contextUserFor(request);
|
|
163
|
+
if (fromContext)
|
|
164
|
+
return fromContext;
|
|
103
165
|
const identity = await resolveIdentity(request, env, {
|
|
104
166
|
keysEnvVar: PUBLIC_KEYS_ENV,
|
|
105
167
|
});
|
|
@@ -125,6 +187,40 @@ export async function requireEveryAppUser(request, env) {
|
|
|
125
187
|
throw error;
|
|
126
188
|
}
|
|
127
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* The user of the request being served (`currentUser()`), or throw the
|
|
192
|
+
* same 401 JSON Response as `requireEveryAppUser()` on a public route.
|
|
193
|
+
* Outside a request (`withOrganization()` work, a handler not under
|
|
194
|
+
* `everyApp()`) it throws `RequestContextError`, as `currentUser()` does.
|
|
195
|
+
*/
|
|
196
|
+
export function requireCurrentUser() {
|
|
197
|
+
const user = currentUser();
|
|
198
|
+
if (!user) {
|
|
199
|
+
throw unauthorized("this request does not have an authenticated user");
|
|
200
|
+
}
|
|
201
|
+
return user;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* `response` without `Set-Cookie`: the perimeter strips every `Cookie`
|
|
205
|
+
* before a request reaches the app, so no cookie the app sets ever comes
|
|
206
|
+
* back, and one named like the Gateway's session cookie locks the person
|
|
207
|
+
* out of the app host (the perimeter refuses a session cookie sent twice).
|
|
208
|
+
* A WebSocket handshake (101) passes as it is: it can't be rebuilt from
|
|
209
|
+
* its body, and the perimeter drops its cookies' `Domain`.
|
|
210
|
+
*/
|
|
211
|
+
function withoutCookies(response) {
|
|
212
|
+
if (response.status === 101 || !response.headers.has("set-cookie")) {
|
|
213
|
+
return response;
|
|
214
|
+
}
|
|
215
|
+
console.warn("[everyapp] Dropped a Set-Cookie header: the Gateway never sends an app's cookies back to it.");
|
|
216
|
+
const headers = new Headers(response.headers);
|
|
217
|
+
headers.delete("set-cookie");
|
|
218
|
+
return new Response(response.body, {
|
|
219
|
+
status: response.status,
|
|
220
|
+
statusText: response.statusText,
|
|
221
|
+
headers,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
128
224
|
export function hasScope(user, scope) {
|
|
129
225
|
return user.scopes.includes("*") || user.scopes.includes(scope);
|
|
130
226
|
}
|
|
@@ -179,7 +275,12 @@ export function everyApp(handler, manifestOrOptions, legacyOptions = {}) {
|
|
|
179
275
|
return unauthorized(err.message);
|
|
180
276
|
throw err;
|
|
181
277
|
}
|
|
182
|
-
|
|
278
|
+
const { user, expiresAt } = identity;
|
|
279
|
+
if (user) {
|
|
280
|
+
servedRequests.set(user, request.headers.get(IDENTITY_HEADER) ?? "");
|
|
281
|
+
}
|
|
282
|
+
const response = await serveRequest({ request, user, expiresAt }, () => handler(request, env, ctx, user));
|
|
283
|
+
return withoutCookies(response);
|
|
183
284
|
},
|
|
184
285
|
};
|
|
185
286
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fromMcpTools()` — an app's existing MCP tools as `defineTools()` entries.
|
|
3
|
+
*
|
|
4
|
+
* const tools: McpToolLike[] = [];
|
|
5
|
+
* registerMyTools((name, config, handler) =>
|
|
6
|
+
* tools.push({ name, ...config, handler }),
|
|
7
|
+
* );
|
|
8
|
+
* export default everyApp(handler, {
|
|
9
|
+
* tools: defineTools(fromMcpTools(tools), { instructions }),
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* Takes the shape `McpServer.registerTool(name, config, handler)` is called
|
|
13
|
+
* with (or what a low-level server's `tools/list` returns, plus a handler),
|
|
14
|
+
* structurally: the SDK takes no dependency on `@modelcontextprotocol/sdk`,
|
|
15
|
+
* and a live `McpServer` keeps its registry private, so the app hands over
|
|
16
|
+
* the list it registers from.
|
|
17
|
+
*/
|
|
18
|
+
import { type ToolRisk } from "../internal/index.js";
|
|
19
|
+
import { type StandardSchemaWithJSON, type ToolContext, type ToolDefinition, type ToolSchema } from "./tools.js";
|
|
20
|
+
/** MCP tool annotations (all hints); only these are read. */
|
|
21
|
+
export interface McpToolAnnotationsLike {
|
|
22
|
+
title?: string;
|
|
23
|
+
readOnlyHint?: boolean;
|
|
24
|
+
destructiveHint?: boolean;
|
|
25
|
+
idempotentHint?: boolean;
|
|
26
|
+
openWorldHint?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** An MCP `CallToolResult`, structurally. */
|
|
29
|
+
export interface McpToolResultLike {
|
|
30
|
+
content?: ReadonlyArray<{
|
|
31
|
+
readonly type: string;
|
|
32
|
+
}>;
|
|
33
|
+
structuredContent?: unknown;
|
|
34
|
+
isError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A tool schema as MCP servers write one: a {@link ToolSchema} (a full zod
|
|
38
|
+
* object, `{ jsonSchema, validate }`), a raw shape of Standard Schemas
|
|
39
|
+
* (`{ query: z.string() }`, what `McpServer.registerTool` takes), or a plain
|
|
40
|
+
* JSON Schema object (a low-level server's `tools/list`), which is described
|
|
41
|
+
* but not validated: the handler checks its own arguments, as it did before.
|
|
42
|
+
*/
|
|
43
|
+
export type McpToolSchemaLike = ToolSchema | Readonly<Record<string, StandardSchemaWithJSON>> | Readonly<Record<string, unknown>>;
|
|
44
|
+
/** One MCP tool: `registerTool`'s name, config and handler, flattened. */
|
|
45
|
+
export interface McpToolLike<TExtra = ToolContext> {
|
|
46
|
+
name: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
/** Omitted: the tool takes no arguments. */
|
|
50
|
+
inputSchema?: McpToolSchemaLike;
|
|
51
|
+
outputSchema?: McpToolSchemaLike;
|
|
52
|
+
annotations?: McpToolAnnotationsLike;
|
|
53
|
+
/**
|
|
54
|
+
* Called with the validated arguments and the value of
|
|
55
|
+
* {@link FromMcpToolsOptions.context} (the call's {@link ToolContext} by
|
|
56
|
+
* default). Return a `CallToolResult`; throw {@link ToolError} for a message
|
|
57
|
+
* the model may read.
|
|
58
|
+
*/
|
|
59
|
+
handler: (args: never, extra: TExtra) => McpToolResultLike | Promise<McpToolResultLike>;
|
|
60
|
+
}
|
|
61
|
+
export interface FromMcpToolsOptions<TExtra = ToolContext> {
|
|
62
|
+
/**
|
|
63
|
+
* What each handler gets as its second argument, built per call from the
|
|
64
|
+
* verified caller (`user`, `env`, `request`, `callId`). Default: the
|
|
65
|
+
* {@link ToolContext} itself.
|
|
66
|
+
*/
|
|
67
|
+
context?: (context: ToolContext) => TExtra | Promise<TExtra>;
|
|
68
|
+
/**
|
|
69
|
+
* A tool's risk, overriding the default for the tools it returns a risk
|
|
70
|
+
* for. Default: `readOnlyHint: true` is `read`, `destructiveHint: true`
|
|
71
|
+
* is `destructive`, anything else `write`.
|
|
72
|
+
*/
|
|
73
|
+
risk?: (tool: McpToolLike<TExtra>) => ToolRisk | undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Adapt MCP tools into `defineTools()` entries, keyed by tool name, to spread
|
|
77
|
+
* beside the app's own: `defineTools({ ...fromMcpTools(list), my_tool })`.
|
|
78
|
+
*
|
|
79
|
+
* - `title`, `description` (the name when absent), `idempotentHint` and
|
|
80
|
+
* `openWorldHint` carry over; risk as {@link FromMcpToolsOptions.risk}.
|
|
81
|
+
* - A result's text blocks, joined by a blank line, are what the model
|
|
82
|
+
* reads; `structuredContent` is sent as structured content (checked
|
|
83
|
+
* against `outputSchema` when there is one). Other blocks are dropped.
|
|
84
|
+
* - `isError: true` becomes a {@link ToolError} with the result's text.
|
|
85
|
+
*
|
|
86
|
+
* Names must already be lowercase snake_case; `defineTools()` checks them.
|
|
87
|
+
*/
|
|
88
|
+
export declare function fromMcpTools<TExtra = ToolContext>(tools: Iterable<McpToolLike<TExtra>>, options?: FromMcpToolsOptions<TExtra>): Record<string, ToolDefinition>;
|
|
89
|
+
//# sourceMappingURL=fromMcpTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fromMcpTools.d.ts","sourceRoot":"","sources":["../../src/server/fromMcpTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAIpB,6DAA6D;AAC7D,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,GAChD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEtC,0EAA0E;AAC1E,MAAM,WAAW,WAAW,CAAC,MAAM,GAAG,WAAW;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC;;;;;OAKG;IACH,OAAO,EAAE,CACP,IAAI,EAAE,KAAK,EACX,KAAK,EAAE,MAAM,KACV,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB,CAAC,MAAM,GAAG,WAAW;IACvD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,QAAQ,GAAG,SAAS,CAAC;CAC5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAG,WAAW,EAC/C,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EACpC,OAAO,GAAE,mBAAmB,CAAC,MAAM,CAAM,GACxC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+ChC"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fromMcpTools()` — an app's existing MCP tools as `defineTools()` entries.
|
|
3
|
+
*
|
|
4
|
+
* const tools: McpToolLike[] = [];
|
|
5
|
+
* registerMyTools((name, config, handler) =>
|
|
6
|
+
* tools.push({ name, ...config, handler }),
|
|
7
|
+
* );
|
|
8
|
+
* export default everyApp(handler, {
|
|
9
|
+
* tools: defineTools(fromMcpTools(tools), { instructions }),
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* Takes the shape `McpServer.registerTool(name, config, handler)` is called
|
|
13
|
+
* with (or what a low-level server's `tools/list` returns, plus a handler),
|
|
14
|
+
* structurally: the SDK takes no dependency on `@modelcontextprotocol/sdk`,
|
|
15
|
+
* and a live `McpServer` keeps its registry private, so the app hands over
|
|
16
|
+
* the list it registers from.
|
|
17
|
+
*/
|
|
18
|
+
import { TOOL_NAME_PATTERN } from "../internal/index.js";
|
|
19
|
+
import { ToolError, toolResult, } from "./tools.js";
|
|
20
|
+
const JSON_SCHEMA_TARGET = "draft-2020-12";
|
|
21
|
+
/**
|
|
22
|
+
* Adapt MCP tools into `defineTools()` entries, keyed by tool name, to spread
|
|
23
|
+
* beside the app's own: `defineTools({ ...fromMcpTools(list), my_tool })`.
|
|
24
|
+
*
|
|
25
|
+
* - `title`, `description` (the name when absent), `idempotentHint` and
|
|
26
|
+
* `openWorldHint` carry over; risk as {@link FromMcpToolsOptions.risk}.
|
|
27
|
+
* - A result's text blocks, joined by a blank line, are what the model
|
|
28
|
+
* reads; `structuredContent` is sent as structured content (checked
|
|
29
|
+
* against `outputSchema` when there is one). Other blocks are dropped.
|
|
30
|
+
* - `isError: true` becomes a {@link ToolError} with the result's text.
|
|
31
|
+
*
|
|
32
|
+
* Names must already be lowercase snake_case; `defineTools()` checks them.
|
|
33
|
+
*/
|
|
34
|
+
export function fromMcpTools(tools, options = {}) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const tool of tools) {
|
|
37
|
+
const problem = (message) => new TypeError(`fromMcpTools: tool "${tool?.name}" ${message}`);
|
|
38
|
+
if (!tool || typeof tool.name !== "string") {
|
|
39
|
+
throw new TypeError("fromMcpTools: every tool needs a name");
|
|
40
|
+
}
|
|
41
|
+
if (!TOOL_NAME_PATTERN.test(tool.name)) {
|
|
42
|
+
throw problem("must be named in lowercase snake_case: a letter, then letters, digits or _ (at most 48 characters); rename it in the list");
|
|
43
|
+
}
|
|
44
|
+
if (Object.prototype.hasOwnProperty.call(out, tool.name)) {
|
|
45
|
+
throw problem("is listed twice");
|
|
46
|
+
}
|
|
47
|
+
if (typeof tool.handler !== "function") {
|
|
48
|
+
throw problem("needs a handler function");
|
|
49
|
+
}
|
|
50
|
+
const output = tool.outputSchema === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: toToolSchema(tool.outputSchema, problem);
|
|
53
|
+
const annotations = tool.annotations ?? {};
|
|
54
|
+
const title = tool.title ?? annotations.title;
|
|
55
|
+
out[tool.name] = {
|
|
56
|
+
...(title !== undefined ? { title } : {}),
|
|
57
|
+
description: tool.description?.trim() ? tool.description : tool.name,
|
|
58
|
+
risk: options.risk?.(tool) ?? defaultRisk(annotations),
|
|
59
|
+
input: toToolSchema(tool.inputSchema ?? {}, problem),
|
|
60
|
+
...(output ? { output } : {}),
|
|
61
|
+
...(annotations.idempotentHint !== undefined
|
|
62
|
+
? { idempotent: annotations.idempotentHint }
|
|
63
|
+
: {}),
|
|
64
|
+
...(annotations.openWorldHint !== undefined
|
|
65
|
+
? { openWorld: annotations.openWorldHint }
|
|
66
|
+
: {}),
|
|
67
|
+
handler: async (args, context) => {
|
|
68
|
+
const extra = options.context
|
|
69
|
+
? await options.context(context)
|
|
70
|
+
: context;
|
|
71
|
+
const result = await tool.handler(args, extra);
|
|
72
|
+
return fromCallToolResult(result);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function defaultRisk(annotations) {
|
|
79
|
+
if (annotations.readOnlyHint === true)
|
|
80
|
+
return "read";
|
|
81
|
+
return annotations.destructiveHint === true ? "destructive" : "write";
|
|
82
|
+
}
|
|
83
|
+
function fromCallToolResult(result) {
|
|
84
|
+
if (!isRecord(result)) {
|
|
85
|
+
throw new Error("the MCP tool did not return a CallToolResult");
|
|
86
|
+
}
|
|
87
|
+
const text = (Array.isArray(result.content) ? result.content : [])
|
|
88
|
+
.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
|
|
89
|
+
? [part.text]
|
|
90
|
+
: [])
|
|
91
|
+
.join("\n\n");
|
|
92
|
+
if (result.isError === true)
|
|
93
|
+
throw new ToolError(text || "The tool failed.");
|
|
94
|
+
const structured = result.structuredContent;
|
|
95
|
+
if (isRecord(structured) && !Array.isArray(structured)) {
|
|
96
|
+
return toolResult({ text, structured });
|
|
97
|
+
}
|
|
98
|
+
return text;
|
|
99
|
+
}
|
|
100
|
+
function isRecord(value) {
|
|
101
|
+
return typeof value === "object" && value !== null;
|
|
102
|
+
}
|
|
103
|
+
function isStandardWithJson(value) {
|
|
104
|
+
if (!isRecord(value))
|
|
105
|
+
return false;
|
|
106
|
+
const standard = value["~standard"];
|
|
107
|
+
return (isRecord(standard) &&
|
|
108
|
+
typeof standard.validate === "function" &&
|
|
109
|
+
isRecord(standard.jsonSchema));
|
|
110
|
+
}
|
|
111
|
+
function isPlainToolSchema(value) {
|
|
112
|
+
return isRecord(value.jsonSchema) && typeof value.validate === "function";
|
|
113
|
+
}
|
|
114
|
+
function toToolSchema(schema, problem) {
|
|
115
|
+
if (!isRecord(schema) || Array.isArray(schema)) {
|
|
116
|
+
throw problem("has a schema that is not an object");
|
|
117
|
+
}
|
|
118
|
+
if (isStandardWithJson(schema) || isPlainToolSchema(schema)) {
|
|
119
|
+
return schema;
|
|
120
|
+
}
|
|
121
|
+
if ("~standard" in schema) {
|
|
122
|
+
throw problem("has a schema that doesn't convert to JSON Schema (zod >= 4.2 does)");
|
|
123
|
+
}
|
|
124
|
+
const values = Object.values(schema);
|
|
125
|
+
if (values.every((value) => isRecord(value) && "~standard" in value)) {
|
|
126
|
+
if (!values.every(isStandardWithJson)) {
|
|
127
|
+
throw problem("has a raw shape whose fields don't convert to JSON Schema (zod >= 4.2 does)");
|
|
128
|
+
}
|
|
129
|
+
return rawShapeSchema(schema);
|
|
130
|
+
}
|
|
131
|
+
return jsonSchemaOnly(schema);
|
|
132
|
+
}
|
|
133
|
+
/** A plain JSON Schema: described as is; any object passes. */
|
|
134
|
+
function jsonSchemaOnly(jsonSchema) {
|
|
135
|
+
return {
|
|
136
|
+
jsonSchema,
|
|
137
|
+
validate: (value) => isRecord(value) && !Array.isArray(value)
|
|
138
|
+
? { value }
|
|
139
|
+
: { issues: [{ message: "Expected an object" }] },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* `{ key: schema }` as an object schema, the way `McpServer` wraps one in
|
|
144
|
+
* `z.object()`: unknown keys are dropped, and a field is optional when its
|
|
145
|
+
* schema accepts `undefined`.
|
|
146
|
+
*/
|
|
147
|
+
function rawShapeSchema(shape) {
|
|
148
|
+
const properties = {};
|
|
149
|
+
const required = [];
|
|
150
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
151
|
+
const { $schema: _, ...property } = field["~standard"].jsonSchema.input({
|
|
152
|
+
target: JSON_SCHEMA_TARGET,
|
|
153
|
+
});
|
|
154
|
+
properties[key] = property;
|
|
155
|
+
const absent = field["~standard"].validate(undefined);
|
|
156
|
+
// An async validator can't be asked here; call the field required.
|
|
157
|
+
if (absent instanceof Promise || absent.issues)
|
|
158
|
+
required.push(key);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
jsonSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties,
|
|
164
|
+
...(required.length > 0 ? { required } : {}),
|
|
165
|
+
},
|
|
166
|
+
validate: async (value) => {
|
|
167
|
+
if (!isRecord(value) || Array.isArray(value)) {
|
|
168
|
+
return { issues: [{ message: "Expected an object" }] };
|
|
169
|
+
}
|
|
170
|
+
const parsed = {};
|
|
171
|
+
const issues = [];
|
|
172
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
173
|
+
const result = await field["~standard"].validate(value[key]);
|
|
174
|
+
if (result.issues) {
|
|
175
|
+
for (const issue of result.issues) {
|
|
176
|
+
issues.push({
|
|
177
|
+
message: issue.message,
|
|
178
|
+
path: [
|
|
179
|
+
key,
|
|
180
|
+
...(issue.path ?? []).map((segment) => typeof segment === "object" ? segment.key : segment),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (key in value || result.value !== undefined) {
|
|
186
|
+
parsed[key] = result.value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return issues.length > 0 ? { issues } : { value: parsed };
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -17,11 +17,13 @@ export interface GatewayFetchEnv {
|
|
|
17
17
|
export interface CreateGatewayFetchOptions {
|
|
18
18
|
env: GatewayFetchEnv;
|
|
19
19
|
/**
|
|
20
|
-
* Whom the call is for
|
|
21
|
-
*
|
|
22
|
-
* (
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Whom the call is for. Left out, the request `everyApp()` is serving
|
|
21
|
+
* (its user, while the identity is good) or the organization
|
|
22
|
+
* `withOrganization()` named; outside both, `RequestContextError`.
|
|
23
|
+
* Explicitly: the inbound request, or `{ background: true,
|
|
24
|
+
* organizationId }` for work no person started (crons, Workflows,
|
|
25
|
+
* queues). A shared app's Worker must end up with one or the other
|
|
26
|
+
* ({@link AttributionError}).
|
|
25
27
|
*/
|
|
26
28
|
for?: GatewayCallFor;
|
|
27
29
|
}
|
|
@@ -31,7 +33,7 @@ export interface CreateGatewayFetchOptions {
|
|
|
31
33
|
* developer's own provider key from `.dev.vars` is a direct-provider
|
|
32
34
|
* fallback when no binding exists.
|
|
33
35
|
*/
|
|
34
|
-
export declare function createGatewayFetch({ env, for:
|
|
36
|
+
export declare function createGatewayFetch({ env, for: explicitTarget, }: CreateGatewayFetchOptions): typeof fetch;
|
|
35
37
|
/** A copy of `source` aimed at `url`, streaming its body rather than buffering it. */
|
|
36
38
|
export declare function streamingRequest(url: string, source: Request, headers: Headers): Request;
|
|
37
39
|
//# sourceMappingURL=gatewayFetch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"AAaA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,eAAO,MAAM,cAAc,sCAAsC,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,eAAe,CAAC;IACrB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,GAAG,EAAE,cAAc,GACpB,EAAE,yBAAyB,GAAG,OAAO,KAAK,CAuE1C;AAwCD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,GACf,OAAO,CAUT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { aiProviderSettings, authorizationOf, ConfigurationError, DEV_AI_PROVIDERS, devAiProvider, joinProviderUrl, } from "../internal/index.js";
|
|
2
|
-
import { applyAttribution,
|
|
1
|
+
import { AI_DEFAULT_MODEL, AI_DEFAULT_PROVIDER, AI_DEFAULT_ROUTES, aiProviderSettings, authorizationOf, ConfigurationError, DEV_AI_PROVIDERS, devAiProvider, joinProviderUrl, resolveAiDefault, } from "../internal/index.js";
|
|
2
|
+
import { applyAttribution, attributionFor, } from "./attribution.js";
|
|
3
3
|
export const BINDING_ORIGIN = "http://every-app-gateway.internal";
|
|
4
4
|
const AI_PROXY_PREFIX = "/api/ai/";
|
|
5
5
|
/**
|
|
@@ -8,8 +8,10 @@ const AI_PROXY_PREFIX = "/api/ai/";
|
|
|
8
8
|
* developer's own provider key from `.dev.vars` is a direct-provider
|
|
9
9
|
* fallback when no binding exists.
|
|
10
10
|
*/
|
|
11
|
-
export function createGatewayFetch({ env, for:
|
|
12
|
-
|
|
11
|
+
export function createGatewayFetch({ env, for: explicitTarget, }) {
|
|
12
|
+
// Checked now, so a call nobody's fails where the fetch is made; and
|
|
13
|
+
// again per call, where an identity may since have neared its expiry.
|
|
14
|
+
attributionFor(env, explicitTarget, "createGatewayFetch");
|
|
13
15
|
const binding = env.EVERY_APP_GATEWAY;
|
|
14
16
|
const devProviders = env.EVERYAPP_DEV ? localProviders(env) : {};
|
|
15
17
|
if (binding && typeof binding.fetch !== "function") {
|
|
@@ -19,6 +21,7 @@ export function createGatewayFetch({ env, for: target, }) {
|
|
|
19
21
|
throw new ConfigurationError(`AI gateway unavailable: deploy with the EVERY_APP_GATEWAY binding, or set the provider's key (${listOf(Object.values(DEV_AI_PROVIDERS).map((spec) => settingsNames(spec, "with")), "or")}) in .dev.vars for direct local development.`);
|
|
20
22
|
}
|
|
21
23
|
return async (input, init) => {
|
|
24
|
+
const target = attributionFor(env, explicitTarget, "createGatewayFetch");
|
|
22
25
|
const outbound = new Request(input, init);
|
|
23
26
|
const headers = new Headers(outbound.headers);
|
|
24
27
|
headers.delete("authorization");
|
|
@@ -28,7 +31,8 @@ export function createGatewayFetch({ env, for: target, }) {
|
|
|
28
31
|
const bindingRequest = streamingRequest(`${BINDING_ORIGIN}${url.pathname}${url.search}`, outbound, headers);
|
|
29
32
|
return binding.fetch(bindingRequest);
|
|
30
33
|
}
|
|
31
|
-
const
|
|
34
|
+
const direct = directProviderPath(outbound.url, env);
|
|
35
|
+
const { provider, spec, path, search } = direct;
|
|
32
36
|
const dev = devProviders[provider];
|
|
33
37
|
if (!dev) {
|
|
34
38
|
throw new ConfigurationError(`Set ${settingsNames(spec)} in .dev.vars to call ${provider} in local development.`);
|
|
@@ -37,6 +41,11 @@ export function createGatewayFetch({ env, for: target, }) {
|
|
|
37
41
|
if (!url)
|
|
38
42
|
throw new ConfigurationError("Invalid provider proxy path.");
|
|
39
43
|
applyAttribution(headers, undefined);
|
|
44
|
+
// The default route in dev: the model filled in as the gateway would.
|
|
45
|
+
let body = outbound;
|
|
46
|
+
if (direct.fillModel !== null) {
|
|
47
|
+
body = await withModel(outbound, direct.fillModel, headers);
|
|
48
|
+
}
|
|
40
49
|
// The developer's key, put where the provider's entry says (a bearer
|
|
41
50
|
// token for the three today), as the gateway would put its own.
|
|
42
51
|
const auth = authorizationOf(spec.auth, { [spec.keyEnv]: dev.key });
|
|
@@ -46,11 +55,44 @@ export function createGatewayFetch({ env, for: target, }) {
|
|
|
46
55
|
for (const [name, value] of Object.entries(auth?.query ?? {})) {
|
|
47
56
|
url.searchParams.set(name, value);
|
|
48
57
|
}
|
|
49
|
-
const response = await fetch(streamingRequest(url.toString(),
|
|
58
|
+
const response = await fetch(streamingRequest(url.toString(), body, headers));
|
|
50
59
|
// What the gateway does to the provider's answer, so dev behaves alike.
|
|
51
60
|
return spec.normalizeResponse?.(path, response) ?? response;
|
|
52
61
|
};
|
|
53
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* `source` with `model` put into its JSON body where the app named none
|
|
65
|
+
* or named `default`, as the gateway's default route does. A body that
|
|
66
|
+
* isn't a JSON object goes through as it is.
|
|
67
|
+
*/
|
|
68
|
+
async function withModel(source, model, headers) {
|
|
69
|
+
if (!canIncludeBody(source.method) || !source.body)
|
|
70
|
+
return source;
|
|
71
|
+
const text = await source.clone().text();
|
|
72
|
+
let payload;
|
|
73
|
+
try {
|
|
74
|
+
payload = JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return source;
|
|
78
|
+
}
|
|
79
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
80
|
+
return source;
|
|
81
|
+
}
|
|
82
|
+
const record = payload;
|
|
83
|
+
if (record["model"] === undefined ||
|
|
84
|
+
record["model"] === null ||
|
|
85
|
+
record["model"] === AI_DEFAULT_MODEL) {
|
|
86
|
+
record["model"] = model;
|
|
87
|
+
}
|
|
88
|
+
headers.delete("content-length");
|
|
89
|
+
return new Request(source.url, {
|
|
90
|
+
method: source.method,
|
|
91
|
+
headers,
|
|
92
|
+
body: JSON.stringify(record),
|
|
93
|
+
signal: source.signal,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
54
96
|
/** A copy of `source` aimed at `url`, streaming its body rather than buffering it. */
|
|
55
97
|
export function streamingRequest(url, source, headers) {
|
|
56
98
|
const body = canIncludeBody(source.method) ? source.body : undefined;
|
|
@@ -82,19 +124,44 @@ function localProviders(env) {
|
|
|
82
124
|
}
|
|
83
125
|
return providers;
|
|
84
126
|
}
|
|
85
|
-
function directProviderPath(input) {
|
|
127
|
+
function directProviderPath(input, env) {
|
|
86
128
|
const incoming = new URL(input);
|
|
87
129
|
const match = incoming.pathname.match(/^\/api\/ai\/([a-z][a-z0-9-]*)(\/.*)?$/);
|
|
88
|
-
|
|
130
|
+
let provider = match?.[1];
|
|
131
|
+
let path = match?.[2] || "/";
|
|
132
|
+
let fillModel = null;
|
|
133
|
+
if (provider === AI_DEFAULT_PROVIDER) {
|
|
134
|
+
// The default in dev, by the gateway's rule on the developer's own
|
|
135
|
+
// `.dev.vars`: the first provider of the order with every setting set.
|
|
136
|
+
const values = env;
|
|
137
|
+
const resolution = resolveAiDefault({
|
|
138
|
+
choice: null,
|
|
139
|
+
isSet: (name) => typeof values[name] === "string" &&
|
|
140
|
+
values[name].trim().length > 0,
|
|
141
|
+
});
|
|
142
|
+
if (!resolution.configured) {
|
|
143
|
+
throw new ConfigurationError(`Set ${resolution.settings.join(" and ")} (or another model provider's key) in .dev.vars to call the default model in local development.`);
|
|
144
|
+
}
|
|
145
|
+
const route = path === AI_DEFAULT_ROUTES.chat
|
|
146
|
+
? resolution.chat
|
|
147
|
+
: path === AI_DEFAULT_ROUTES.embeddings
|
|
148
|
+
? resolution.embeddings
|
|
149
|
+
: undefined;
|
|
150
|
+
if (!route) {
|
|
151
|
+
throw new ConfigurationError(`The default model serves ${AI_DEFAULT_ROUTES.chat} and ${AI_DEFAULT_ROUTES.embeddings} only; call /api/ai/<provider>/… for anything else.`);
|
|
152
|
+
}
|
|
153
|
+
provider = resolution.provider;
|
|
154
|
+
path = route.path;
|
|
155
|
+
fillModel = path === resolution.chat.path ? resolution.model : route.model;
|
|
156
|
+
}
|
|
89
157
|
const spec = provider ? devAiProvider(provider) : undefined;
|
|
90
158
|
if (!provider || !spec) {
|
|
91
|
-
throw new ConfigurationError(`Direct local provider fallback only supports ${listOf(Object.keys(DEV_AI_PROVIDERS).map((name) => `${AI_PROXY_PREFIX}${name}`), "and")} paths.`);
|
|
159
|
+
throw new ConfigurationError(`Direct local provider fallback only supports ${listOf([AI_DEFAULT_PROVIDER, ...Object.keys(DEV_AI_PROVIDERS)].map((name) => `${AI_PROXY_PREFIX}${name}`), "and")} paths.`);
|
|
92
160
|
}
|
|
93
|
-
const path = match?.[2] || "/";
|
|
94
161
|
if (path.startsWith("//") || path.includes("\\")) {
|
|
95
162
|
throw new ConfigurationError("Invalid provider proxy path.");
|
|
96
163
|
}
|
|
97
|
-
return { provider, spec, path, search: incoming.search };
|
|
164
|
+
return { provider, spec, path, search: incoming.search, fillModel };
|
|
98
165
|
}
|
|
99
166
|
/** A provider's settings as a person reads them: `KEY`, or `KEY and SETTING`. */
|
|
100
167
|
function settingsNames(spec, conjunction = "and") {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* There is intentionally no client-side export.
|
|
5
5
|
*/
|
|
6
|
-
export { ConfigurationError, IdentityError, everyApp, getEveryAppUser, hasScope, requireEveryAppUser, type EveryAppOptions, type EveryAppManifestLike, type EveryAppHandler, type EveryAppUser, type IdentityResult, } from "./everyApp.js";
|
|
6
|
+
export { ConfigurationError, IdentityError, everyApp, getEveryAppUser, hasScope, isAdmin, requireCurrentUser, requireEveryAppUser, type EveryAppOptions, type EveryAppManifestLike, type EveryAppHandler, type EveryAppUser, type IdentityResult, type OrgRole, } from "./everyApp.js";
|
|
7
|
+
export { OrganizationMismatchError, RequestContextError, currentUser, withOrganization, } from "./context.js";
|
|
7
8
|
export { AttributionError, type GatewayCallFor } from "./attribution.js";
|
|
8
9
|
export { authorizeUrl, type AuthorizeUrlOptions } from "./authorizeUrl.js";
|
|
10
|
+
export { connectPath, connectUrl, type ConnectUrlOptions, } from "./connectUrl.js";
|
|
9
11
|
export { createGatewayFetch, type CreateGatewayFetchOptions, type GatewayFetchEnv, } from "./gatewayFetch.js";
|
|
10
|
-
export {
|
|
12
|
+
export { ai, AI_DEFAULT_BASE_URL, type AiClient } from "./ai.js";
|
|
13
|
+
export { GatewayRefusalError, gatewayRefusal, devAccessTokenName, integrationFetch, integrationStatus, type EmailIntegrationStatus, type GatewayRefusal, type IntegrationFetchOptions, type IntegrationStatus, type IntegrationStatusOf, type OAuthClientIntegrationStatus, } from "./integrationFetch.js";
|
|
11
14
|
export { nodeServerHandler, type NodeServer, type NodeServerHandlerOptions, } from "./nodeServer.js";
|
|
12
15
|
export { sendEmail, type SendEmailOptions } from "./sendEmail.js";
|
|
13
16
|
export { listMembers, type ListMembersOptions } from "./listMembers.js";
|
|
14
17
|
export { emit, type EmitOptions } from "./emit.js";
|
|
15
|
-
export type { ApiKeyCapabilityId, CapabilityId, OAuthClientCapabilityId, EmailAddressInput, EmailAttachmentInput, SendEmailMessage, SendEmailResult, ListMembersResult, OrganizationMember, OrganizationMemberRole, EmitEventResult, } from "../internal/index.js";
|
|
18
|
+
export type { ApiKeyCapabilityId, CapabilityId, OAuthClientCapabilityId, ConnectionAccount, EmailAddressInput, EmailAttachmentInput, SendEmailMessage, SendEmailResult, ListMembersResult, OrganizationMember, OrganizationMemberRole, EmitEventResult, } from "../internal/index.js";
|
|
16
19
|
export { ToolError, defineTools, toolResult, type DefineToolsOptions, type PlainToolSchema, type StandardSchemaWithJSON, type ToolContext, type ToolDefinition, type ToolResult, type ToolSchema, type ToolSet, } from "./tools.js";
|
|
17
20
|
export type { ToolRisk } from "../internal/index.js";
|
|
21
|
+
export { fromMcpTools, type FromMcpToolsOptions, type McpToolAnnotationsLike, type McpToolLike, type McpToolResultLike, type McpToolSchemaLike, } from "./fromMcpTools.js";
|
|
18
22
|
//# sourceMappingURL=index.d.ts.map
|