@every-app/sdk 0.2.0 → 0.3.0-rc.1

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.
Files changed (113) hide show
  1. package/README.md +608 -2
  2. package/dist/cloudflare/index.d.ts +22 -0
  3. package/dist/cloudflare/index.d.ts.map +1 -0
  4. package/dist/cloudflare/index.js +21 -0
  5. package/dist/cloudflare/sentinel.d.ts +69 -0
  6. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  7. package/dist/cloudflare/sentinel.js +50 -0
  8. package/dist/cloudflare/worker.d.ts +511 -0
  9. package/dist/cloudflare/worker.d.ts.map +1 -0
  10. package/dist/cloudflare/worker.js +738 -0
  11. package/dist/dev/index.d.ts +8 -0
  12. package/dist/dev/index.d.ts.map +1 -0
  13. package/dist/dev/index.js +7 -0
  14. package/dist/dev/keypair.d.ts +24 -0
  15. package/dist/dev/keypair.d.ts.map +1 -0
  16. package/dist/dev/keypair.js +60 -0
  17. package/dist/internal/aiProviders.d.ts +67 -0
  18. package/dist/internal/aiProviders.d.ts.map +1 -0
  19. package/dist/internal/aiProviders.js +220 -0
  20. package/dist/internal/appSettings.d.ts +237 -0
  21. package/dist/internal/appSettings.d.ts.map +1 -0
  22. package/dist/internal/appSettings.js +374 -0
  23. package/dist/internal/capabilities.d.ts +1344 -0
  24. package/dist/internal/capabilities.d.ts.map +1 -0
  25. package/dist/internal/capabilities.js +1521 -0
  26. package/dist/internal/devProviders.d.ts +49 -0
  27. package/dist/internal/devProviders.d.ts.map +1 -0
  28. package/dist/internal/devProviders.js +62 -0
  29. package/dist/internal/email.d.ts +280 -0
  30. package/dist/internal/email.d.ts.map +1 -0
  31. package/dist/internal/email.js +613 -0
  32. package/dist/internal/events.d.ts +182 -0
  33. package/dist/internal/events.d.ts.map +1 -0
  34. package/dist/internal/events.js +324 -0
  35. package/dist/internal/gatewaySettings.d.ts +82 -0
  36. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  37. package/dist/internal/gatewaySettings.js +209 -0
  38. package/dist/internal/identity.d.ts +18 -3
  39. package/dist/internal/identity.d.ts.map +1 -1
  40. package/dist/internal/identity.js +53 -14
  41. package/dist/internal/index.d.ts +17 -1
  42. package/dist/internal/index.d.ts.map +1 -1
  43. package/dist/internal/index.js +17 -1
  44. package/dist/internal/members.d.ts +35 -0
  45. package/dist/internal/members.d.ts.map +1 -0
  46. package/dist/internal/members.js +7 -0
  47. package/dist/internal/oauthClient.d.ts +63 -0
  48. package/dist/internal/oauthClient.d.ts.map +1 -0
  49. package/dist/internal/oauthClient.js +194 -0
  50. package/dist/internal/oauthScopes.d.ts +117 -0
  51. package/dist/internal/oauthScopes.d.ts.map +1 -0
  52. package/dist/internal/oauthScopes.js +416 -0
  53. package/dist/internal/organizationName.d.ts +18 -0
  54. package/dist/internal/organizationName.d.ts.map +1 -0
  55. package/dist/internal/organizationName.js +24 -0
  56. package/dist/internal/protocol.d.ts +86 -2
  57. package/dist/internal/protocol.d.ts.map +1 -1
  58. package/dist/internal/protocol.js +112 -1
  59. package/dist/internal/providerApps.d.ts +75 -0
  60. package/dist/internal/providerApps.d.ts.map +1 -0
  61. package/dist/internal/providerApps.js +59 -0
  62. package/dist/internal/providerRegistry.d.ts +147 -0
  63. package/dist/internal/providerRegistry.d.ts.map +1 -0
  64. package/dist/internal/providerRegistry.js +262 -0
  65. package/dist/internal/providerSpec.d.ts +55 -0
  66. package/dist/internal/providerSpec.d.ts.map +1 -0
  67. package/dist/internal/providerSpec.js +746 -0
  68. package/dist/internal/remoteMcp.d.ts +188 -0
  69. package/dist/internal/remoteMcp.d.ts.map +1 -0
  70. package/dist/internal/remoteMcp.js +543 -0
  71. package/dist/internal/tools.d.ts +115 -0
  72. package/dist/internal/tools.d.ts.map +1 -0
  73. package/dist/internal/tools.js +80 -0
  74. package/dist/server/attribution.d.ts +39 -0
  75. package/dist/server/attribution.d.ts.map +1 -0
  76. package/dist/server/attribution.js +67 -0
  77. package/dist/server/authorizeUrl.d.ts +43 -0
  78. package/dist/server/authorizeUrl.d.ts.map +1 -0
  79. package/dist/server/authorizeUrl.js +110 -0
  80. package/dist/server/emit.d.ts +63 -0
  81. package/dist/server/emit.d.ts.map +1 -0
  82. package/dist/server/emit.js +101 -0
  83. package/dist/server/everyApp.d.ts +38 -12
  84. package/dist/server/everyApp.d.ts.map +1 -1
  85. package/dist/server/everyApp.js +92 -28
  86. package/dist/server/gatewayFetch.d.ts +24 -8
  87. package/dist/server/gatewayFetch.d.ts.map +1 -1
  88. package/dist/server/gatewayFetch.js +81 -36
  89. package/dist/server/index.d.ts +10 -1
  90. package/dist/server/index.d.ts.map +1 -1
  91. package/dist/server/index.js +8 -1
  92. package/dist/server/integrationFetch.d.ts +131 -0
  93. package/dist/server/integrationFetch.d.ts.map +1 -0
  94. package/dist/server/integrationFetch.js +526 -0
  95. package/dist/server/listMembers.d.ts +32 -0
  96. package/dist/server/listMembers.d.ts.map +1 -0
  97. package/dist/server/listMembers.js +74 -0
  98. package/dist/server/nodeServer.d.ts +48 -0
  99. package/dist/server/nodeServer.d.ts.map +1 -0
  100. package/dist/server/nodeServer.js +45 -0
  101. package/dist/server/sendEmail.d.ts +39 -0
  102. package/dist/server/sendEmail.d.ts.map +1 -0
  103. package/dist/server/sendEmail.js +114 -0
  104. package/dist/server/tools.d.ts +184 -0
  105. package/dist/server/tools.d.ts.map +1 -0
  106. package/dist/server/tools.js +347 -0
  107. package/dist/server/toolsEndpoint.d.ts +32 -0
  108. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  109. package/dist/server/toolsEndpoint.js +231 -0
  110. package/package.json +30 -17
  111. package/dist/server/mcp.d.ts +0 -66
  112. package/dist/server/mcp.d.ts.map +0 -1
  113. package/dist/server/mcp.js +0 -147
@@ -1,14 +1,22 @@
1
1
  /**
2
2
  * `everyApp()` — the app-side identity boundary.
3
3
  *
4
- * import manifest from "../everyapp.config.js";
5
- * export default everyApp(handler, manifest);
4
+ * export default everyApp(handler, { tools });
6
5
  *
7
6
  * The wrapper verifies the gateway-injected identity before app code runs,
8
7
  * stores the result on the original Request, and then delegates to the
9
- * worker-style handler.
8
+ * worker-style handler. The expected audience is the Worker's own name in
9
+ * `env.EVERYAPP_AUDIENCE`, which `everyAppWorker()` sets at deploy (the
10
+ * gateway stamps every token it mints for the app with it); only in
11
+ * development, where `everyapp dev` mints for the app id, does the app id in
12
+ * `env.EVERYAPP_APP_ID` stand in. The Worker never imports
13
+ * `everyapp.config.ts` (the Cloudflare Vite plugin hides every file
14
+ * `cloudflare.config.ts` imports from the Worker in dev).
15
+ * It also answers the gateway's internal tools contract
16
+ * (`/__everyapp/tools/*`) for the app's `defineTools()` catalog.
10
17
  */
11
- import { APP_ID_ENV, ConfigurationError, DEV_ENV, IdentityError, ISSUER_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, } from "../internal/index.js";
18
+ import { APP_ID_ENV, AUDIENCE_ENV, ConfigurationError, DEV_ENV, IdentityError, ISSUER_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, } from "../internal/index.js";
19
+ import { handleToolsRequest } from "./toolsEndpoint.js";
12
20
  export { ConfigurationError, IdentityError };
13
21
  const requestIdentities = new WeakMap();
14
22
  function envValue(env, key) {
@@ -19,7 +27,7 @@ function envValue(env, key) {
19
27
  }
20
28
  function configFrom(manifest, options = {}) {
21
29
  return {
22
- audience: manifest.id,
30
+ manifestAppId: manifest?.id,
23
31
  issuer: options.issuer,
24
32
  keysEnvVar: options.keysEnvVar ?? PUBLIC_KEYS_ENV,
25
33
  publicKeys: options.publicKeys,
@@ -27,41 +35,62 @@ function configFrom(manifest, options = {}) {
27
35
  now: options.now,
28
36
  };
29
37
  }
38
+ /**
39
+ * The app id: EVERYAPP_APP_ID, or a manifest passed to everyApp() for
40
+ * Workers without it; the two must agree when both are present.
41
+ */
42
+ function appIdFor(env, config) {
43
+ const fromEnv = envValue(env, APP_ID_ENV);
44
+ if (fromEnv && config.manifestAppId && fromEnv !== config.manifestAppId) {
45
+ throw new ConfigurationError(`${APP_ID_ENV} ("${fromEnv}") does not match the manifest id ("${config.manifestAppId}").`);
46
+ }
47
+ return fromEnv ?? config.manifestAppId;
48
+ }
49
+ function devIdentitiesAllowed(env, config) {
50
+ const devEnv = envValue(env, DEV_ENV);
51
+ return (config.allowDevIdentities ??
52
+ (devEnv !== undefined &&
53
+ (devEnv === "1" || devEnv.toLowerCase() === "true")));
54
+ }
55
+ /**
56
+ * The audience identity tokens must carry: this Worker's name, from
57
+ * EVERYAPP_AUDIENCE. The gateway mints for the Worker, not the app id, so a
58
+ * token for one organization's copy of an app never opens another's. Only
59
+ * under the dev opt-in, where `everyapp dev` mints for the app id, does the
60
+ * app id stand in when EVERYAPP_AUDIENCE is unset.
61
+ */
62
+ function audienceFor(env, config) {
63
+ const appId = appIdFor(env, config);
64
+ const audience = envValue(env, AUDIENCE_ENV);
65
+ if (audience)
66
+ return audience;
67
+ if (appId && devIdentitiesAllowed(env, config))
68
+ return appId;
69
+ throw new ConfigurationError(`${AUDIENCE_ENV} is not set — the app's cloudflare.config.ts must declare it (everyAppWorker() does at deploy).`);
70
+ }
30
71
  function unauthorized(message) {
31
72
  return Response.json({ error: "unauthenticated", message }, { status: 401 });
32
73
  }
33
74
  function misconfigured(message) {
34
75
  return Response.json({ error: "misconfigured", message }, { status: 500 });
35
76
  }
36
- async function resolveIdentity(request, env, config) {
37
- const devEnv = env && typeof env === "object"
38
- ? env[DEV_ENV]
39
- : undefined;
40
- return getIdentityFromRequest(request, {
41
- audience: config.audience,
77
+ function identityOptions(env, config, audience) {
78
+ return {
79
+ audience,
42
80
  issuer: config.issuer ?? envValue(env, ISSUER_ENV),
43
81
  publicKeys: config.publicKeys,
44
82
  env: env,
45
83
  keysEnvVar: config.keysEnvVar,
46
- allowDevIdentities: config.allowDevIdentities ??
47
- (typeof devEnv === "string" &&
48
- (devEnv === "1" || devEnv.toLowerCase() === "true")),
84
+ allowDevIdentities: devIdentitiesAllowed(env, config),
49
85
  now: config.now,
50
- });
86
+ };
87
+ }
88
+ async function resolveIdentity(request, env, config) {
89
+ return getIdentityFromRequest(request, identityOptions(env, config, audienceFor(env, config)));
51
90
  }
52
91
  function rememberIdentity(request, identity) {
53
92
  requestIdentities.set(request, identity);
54
93
  }
55
- function helperConfigFromEnv(env) {
56
- const audience = envValue(env, APP_ID_ENV);
57
- if (!audience) {
58
- throw new ConfigurationError(`${APP_ID_ENV} is not set — the CLI must inject the app id for identity verification.`);
59
- }
60
- return {
61
- audience,
62
- keysEnvVar: PUBLIC_KEYS_ENV,
63
- };
64
- }
65
94
  /**
66
95
  * Return the verified Every App user for this request, or null for a declared
67
96
  * public request. Uses the request cache first, then verifies the identity
@@ -71,7 +100,9 @@ export async function getEveryAppUser(request, env) {
71
100
  const cached = requestIdentities.get(request);
72
101
  if (cached)
73
102
  return cached.user;
74
- const identity = await resolveIdentity(request, env, helperConfigFromEnv(env));
103
+ const identity = await resolveIdentity(request, env, {
104
+ keysEnvVar: PUBLIC_KEYS_ENV,
105
+ });
75
106
  rememberIdentity(request, identity);
76
107
  return identity.user;
77
108
  }
@@ -97,13 +128,46 @@ export async function requireEveryAppUser(request, env) {
97
128
  export function hasScope(user, scope) {
98
129
  return user.scopes.includes("*") || user.scopes.includes(scope);
99
130
  }
100
- export function everyApp(handler, manifest, options = {}) {
131
+ function isManifest(value) {
132
+ return (typeof value === "object" &&
133
+ value !== null &&
134
+ typeof value.id === "string");
135
+ }
136
+ export function everyApp(handler, manifestOrOptions, legacyOptions = {}) {
137
+ const manifest = isManifest(manifestOrOptions)
138
+ ? manifestOrOptions
139
+ : undefined;
140
+ const options = manifest || manifestOrOptions === undefined
141
+ ? legacyOptions
142
+ : manifestOrOptions;
101
143
  const config = configFrom(manifest, options);
102
144
  return {
103
145
  async fetch(request, env, ctx) {
146
+ let audience;
147
+ let appId;
148
+ try {
149
+ audience = audienceFor(env, config);
150
+ appId = appIdFor(env, config) ?? audience;
151
+ }
152
+ catch (err) {
153
+ if (err instanceof ConfigurationError) {
154
+ console.error(err);
155
+ return misconfigured(err.message);
156
+ }
157
+ throw err;
158
+ }
159
+ // The reserved tools contract carries its own gateway-minted tokens and
160
+ // never reaches the app's handler.
161
+ const toolsResponse = await handleToolsRequest(request, env, ctx, {
162
+ toolSet: options.tools,
163
+ appId,
164
+ identity: identityOptions(env, config, audience),
165
+ });
166
+ if (toolsResponse)
167
+ return toolsResponse;
104
168
  let identity;
105
169
  try {
106
- identity = await resolveIdentity(request, env, config);
170
+ identity = await getIdentityFromRequest(request, identityOptions(env, config, audience));
107
171
  rememberIdentity(request, identity);
108
172
  }
109
173
  catch (err) {
@@ -1,21 +1,37 @@
1
- interface GatewayServiceBinding {
1
+ import { type GatewayCallFor } from "./attribution.js";
2
+ export declare const BINDING_ORIGIN = "http://every-app-gateway.internal";
3
+ export interface GatewayServiceBinding {
2
4
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
3
5
  }
4
6
  export interface GatewayFetchEnv {
5
7
  EVERY_APP_GATEWAY?: GatewayServiceBinding;
6
8
  EVERYAPP_DEV?: string | boolean;
9
+ /** `1` on a shared app's Workers (`everyAppWorker()` sets it). */
10
+ EVERYAPP_SHARED?: string;
7
11
  OPENAI_API_KEY?: string;
12
+ OPENROUTER_API_KEY?: string;
13
+ /** Workers AI: an API token (Workers AI Read and Edit) and the account it bills. */
14
+ WORKERS_AI_API_TOKEN?: string;
15
+ CLOUDFLARE_ACCOUNT_ID?: string;
8
16
  }
9
17
  export interface CreateGatewayFetchOptions {
10
18
  env: GatewayFetchEnv;
11
- /** The inbound app request whose verified identity may be forwarded. */
12
- request: Request;
19
+ /**
20
+ * Whom the call is for: the inbound request (its user), or
21
+ * `{ background: true, organizationId }` for work no person started
22
+ * (crons, Workflows, queues). An organization's own app may omit it for
23
+ * background work, which is then the app's; a shared app's Worker must
24
+ * pass one or the other ({@link AttributionError}).
25
+ */
26
+ for?: GatewayCallFor;
13
27
  }
14
28
  /**
15
- * Create an OpenAI-compatible fetch function backed by the private gateway
16
- * service binding. In local dev only, the developer's own OPENAI_API_KEY is a
17
- * direct-provider fallback when no binding exists.
29
+ * Create a fetch for the gateway's AI providers (`/api/ai/<provider>/…`),
30
+ * backed by the private gateway service binding. In local dev only, the
31
+ * developer's own provider key from `.dev.vars` is a direct-provider
32
+ * fallback when no binding exists.
18
33
  */
19
- export declare function createGatewayFetch({ env, request: inboundRequest, }: CreateGatewayFetchOptions): typeof fetch;
20
- export {};
34
+ export declare function createGatewayFetch({ env, for: target, }: CreateGatewayFetchOptions): typeof fetch;
35
+ /** A copy of `source` aimed at `url`, streaming its body rather than buffering it. */
36
+ export declare function streamingRequest(url: string, source: Request, headers: Headers): Request;
21
37
  //# sourceMappingURL=gatewayFetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"AAMA,UAAU,qBAAqB;IAC7B,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,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,eAAe,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,OAAO,EAAE,cAAc,GACxB,EAAE,yBAAyB,GAAG,OAAO,KAAK,CAyC1C"}
1
+ {"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"AASA,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;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,GAAG,EAAE,MAAM,GACZ,EAAE,yBAAyB,GAAG,OAAO,KAAK,CA8D1C;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,GACf,OAAO,CAUT"}
@@ -1,44 +1,58 @@
1
- import { ConfigurationError, IDENTITY_HEADER } from "../internal/index.js";
2
- const BINDING_ORIGIN = "http://every-app-gateway.internal";
3
- const OPENAI_UPSTREAM_ORIGIN = "https://api.openai.com";
4
- const OPENAI_PROXY_PREFIX = "/api/ai/openai";
1
+ import { aiProviderSettings, authorizationOf, ConfigurationError, DEV_AI_PROVIDERS, devAiProvider, joinProviderUrl, } from "../internal/index.js";
2
+ import { applyAttribution, checkAttribution, } from "./attribution.js";
3
+ export const BINDING_ORIGIN = "http://every-app-gateway.internal";
4
+ const AI_PROXY_PREFIX = "/api/ai/";
5
5
  /**
6
- * Create an OpenAI-compatible fetch function backed by the private gateway
7
- * service binding. In local dev only, the developer's own OPENAI_API_KEY is a
8
- * direct-provider fallback when no binding exists.
6
+ * Create a fetch for the gateway's AI providers (`/api/ai/<provider>/…`),
7
+ * backed by the private gateway service binding. In local dev only, the
8
+ * developer's own provider key from `.dev.vars` is a direct-provider
9
+ * fallback when no binding exists.
9
10
  */
10
- export function createGatewayFetch({ env, request: inboundRequest, }) {
11
+ export function createGatewayFetch({ env, for: target, }) {
12
+ checkAttribution(env, target, "createGatewayFetch");
11
13
  const binding = env.EVERY_APP_GATEWAY;
12
- const devProviderKey = env.EVERYAPP_DEV
13
- ? env.OPENAI_API_KEY?.trim()
14
- : undefined;
14
+ const devProviders = env.EVERYAPP_DEV ? localProviders(env) : {};
15
15
  if (binding && typeof binding.fetch !== "function") {
16
16
  throw new ConfigurationError("EVERY_APP_GATEWAY is configured but is not a valid service binding.");
17
17
  }
18
- if (!binding && !devProviderKey) {
19
- throw new ConfigurationError("AI gateway unavailable: deploy with the EVERY_APP_GATEWAY binding, or set OPENAI_API_KEY in .dev.vars for direct local development.");
18
+ if (!binding && Object.keys(devProviders).length === 0) {
19
+ 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
20
  }
21
21
  return async (input, init) => {
22
22
  const outbound = new Request(input, init);
23
23
  const headers = new Headers(outbound.headers);
24
24
  headers.delete("authorization");
25
- const identity = inboundRequest.headers.get(IDENTITY_HEADER);
26
- if (identity)
27
- headers.set(IDENTITY_HEADER, identity);
28
- else
29
- headers.delete(IDENTITY_HEADER);
30
25
  if (binding) {
31
- const target = new URL(outbound.url);
32
- const bindingRequest = streamingRequest(`${BINDING_ORIGIN}${target.pathname}${target.search}`, outbound, headers);
26
+ applyAttribution(headers, target);
27
+ const url = new URL(outbound.url);
28
+ const bindingRequest = streamingRequest(`${BINDING_ORIGIN}${url.pathname}${url.search}`, outbound, headers);
33
29
  return binding.fetch(bindingRequest);
34
30
  }
35
- const providerUrl = directOpenAiUrl(outbound.url);
36
- headers.delete(IDENTITY_HEADER);
37
- headers.set("authorization", `Bearer ${devProviderKey}`);
38
- return fetch(streamingRequest(providerUrl, outbound, headers));
31
+ const { provider, spec, path, search } = directProviderPath(outbound.url);
32
+ const dev = devProviders[provider];
33
+ if (!dev) {
34
+ throw new ConfigurationError(`Set ${settingsNames(spec)} in .dev.vars to call ${provider} in local development.`);
35
+ }
36
+ const url = joinProviderUrl(`${spec.origin}${dev.basePath}`, path, search);
37
+ if (!url)
38
+ throw new ConfigurationError("Invalid provider proxy path.");
39
+ applyAttribution(headers, undefined);
40
+ // The developer's key, put where the provider's entry says (a bearer
41
+ // token for the three today), as the gateway would put its own.
42
+ const auth = authorizationOf(spec.auth, { [spec.keyEnv]: dev.key });
43
+ for (const [name, value] of Object.entries(auth?.headers ?? {})) {
44
+ headers.set(name, value);
45
+ }
46
+ for (const [name, value] of Object.entries(auth?.query ?? {})) {
47
+ url.searchParams.set(name, value);
48
+ }
49
+ const response = await fetch(streamingRequest(url.toString(), outbound, headers));
50
+ // What the gateway does to the provider's answer, so dev behaves alike.
51
+ return spec.normalizeResponse?.(path, response) ?? response;
39
52
  };
40
53
  }
41
- function streamingRequest(url, source, headers) {
54
+ /** A copy of `source` aimed at `url`, streaming its body rather than buffering it. */
55
+ export function streamingRequest(url, source, headers) {
42
56
  const body = canIncludeBody(source.method) ? source.body : undefined;
43
57
  const init = {
44
58
  method: source.method,
@@ -49,20 +63,51 @@ function streamingRequest(url, source, headers) {
49
63
  };
50
64
  return new Request(url, init);
51
65
  }
52
- function directOpenAiUrl(input) {
66
+ /** The providers `.dev.vars` sets every setting of, with their key and base path. */
67
+ function localProviders(env) {
68
+ const providers = {};
69
+ for (const [provider, spec] of Object.entries(DEV_AI_PROVIDERS)) {
70
+ const settings = {};
71
+ for (const name of aiProviderSettings(spec)) {
72
+ const value = env[name];
73
+ if (typeof value === "string" && value.trim()) {
74
+ settings[name] = value.trim();
75
+ }
76
+ }
77
+ const key = settings[spec.keyEnv];
78
+ // A provider is reachable only with every setting it needs.
79
+ if (!key || Object.keys(settings).length < aiProviderSettings(spec).length)
80
+ continue;
81
+ providers[provider] = { key, basePath: spec.basePath?.(settings) ?? "" };
82
+ }
83
+ return providers;
84
+ }
85
+ function directProviderPath(input) {
53
86
  const incoming = new URL(input);
54
- if (incoming.pathname !== OPENAI_PROXY_PREFIX &&
55
- !incoming.pathname.startsWith(`${OPENAI_PROXY_PREFIX}/`)) {
56
- throw new ConfigurationError(`Direct local provider fallback only supports ${OPENAI_PROXY_PREFIX} paths.`);
87
+ const match = incoming.pathname.match(/^\/api\/ai\/([a-z][a-z0-9-]*)(\/.*)?$/);
88
+ const provider = match?.[1];
89
+ const spec = provider ? devAiProvider(provider) : undefined;
90
+ 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.`);
57
92
  }
58
- const upstreamPath = incoming.pathname.slice(OPENAI_PROXY_PREFIX.length) || "/";
59
- if (!upstreamPath.startsWith("/") || upstreamPath.startsWith("//")) {
60
- throw new ConfigurationError("Invalid OpenAI proxy path.");
93
+ const path = match?.[2] || "/";
94
+ if (path.startsWith("//") || path.includes("\\")) {
95
+ throw new ConfigurationError("Invalid provider proxy path.");
61
96
  }
62
- const upstream = new URL(OPENAI_UPSTREAM_ORIGIN);
63
- upstream.pathname = upstreamPath;
64
- upstream.search = incoming.search;
65
- return upstream.toString();
97
+ return { provider, spec, path, search: incoming.search };
98
+ }
99
+ /** A provider's settings as a person reads them: `KEY`, or `KEY and SETTING`. */
100
+ function settingsNames(spec, conjunction = "and") {
101
+ const [key, ...others] = aiProviderSettings(spec);
102
+ return others.length
103
+ ? `${key} ${conjunction} ${listOf(others, "and")}`
104
+ : key;
105
+ }
106
+ /** `a`, `a and b`, `a, b and c`. */
107
+ function listOf(items, conjunction) {
108
+ return items.length < 2
109
+ ? (items[0] ?? "")
110
+ : `${items.slice(0, -1).join(", ")} ${conjunction} ${items.at(-1)}`;
66
111
  }
67
112
  function canIncludeBody(method) {
68
113
  const normalized = method.toUpperCase();
@@ -4,6 +4,15 @@
4
4
  * There is intentionally no client-side export.
5
5
  */
6
6
  export { ConfigurationError, IdentityError, everyApp, getEveryAppUser, hasScope, requireEveryAppUser, type EveryAppOptions, type EveryAppManifestLike, type EveryAppHandler, type EveryAppUser, type IdentityResult, } from "./everyApp.js";
7
+ export { AttributionError, type GatewayCallFor } from "./attribution.js";
8
+ export { authorizeUrl, type AuthorizeUrlOptions } from "./authorizeUrl.js";
7
9
  export { createGatewayFetch, type CreateGatewayFetchOptions, type GatewayFetchEnv, } from "./gatewayFetch.js";
8
- export { createMcpHandler, type CreateMcpHandlerOptions, type McpToolContext, type McpToolDefinition, type McpRequestHandler, } from "./mcp.js";
10
+ export { GatewayRefusalError, gatewayRefusal, integrationFetch, integrationStatus, type EmailIntegrationStatus, type GatewayRefusal, type IntegrationFetchOptions, type IntegrationStatus, type IntegrationStatusOf, type OAuthClientIntegrationStatus, } from "./integrationFetch.js";
11
+ export { nodeServerHandler, type NodeServer, type NodeServerHandlerOptions, } from "./nodeServer.js";
12
+ export { sendEmail, type SendEmailOptions } from "./sendEmail.js";
13
+ export { listMembers, type ListMembersOptions } from "./listMembers.js";
14
+ 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";
16
+ export { ToolError, defineTools, toolResult, type DefineToolsOptions, type PlainToolSchema, type StandardSchemaWithJSON, type ToolContext, type ToolDefinition, type ToolResult, type ToolSchema, type ToolSet, } from "./tools.js";
17
+ export type { ToolRisk } from "../internal/index.js";
9
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,GAClC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,wBAAwB,GAC9B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
@@ -4,5 +4,12 @@
4
4
  * There is intentionally no client-side export.
5
5
  */
6
6
  export { ConfigurationError, IdentityError, everyApp, getEveryAppUser, hasScope, requireEveryAppUser, } from "./everyApp.js";
7
+ export { AttributionError } from "./attribution.js";
8
+ export { authorizeUrl } from "./authorizeUrl.js";
7
9
  export { createGatewayFetch, } from "./gatewayFetch.js";
8
- export { createMcpHandler, } from "./mcp.js";
10
+ export { GatewayRefusalError, gatewayRefusal, integrationFetch, integrationStatus, } from "./integrationFetch.js";
11
+ export { nodeServerHandler, } from "./nodeServer.js";
12
+ export { sendEmail } from "./sendEmail.js";
13
+ export { listMembers } from "./listMembers.js";
14
+ export { emit } from "./emit.js";
15
+ export { ToolError, defineTools, toolResult, } from "./tools.js";
@@ -0,0 +1,131 @@
1
+ import { type ApiKeyCapabilityId, type CapabilityId, type EmailCapabilityStatus, type OAuthClientCapabilityId } from "../internal/index.js";
2
+ import { type GatewayCallFor } from "./attribution.js";
3
+ export interface IntegrationFetchOptions {
4
+ /**
5
+ * The Worker env: the EVERY_APP_GATEWAY binding when deployed, or in local
6
+ * dev EVERYAPP_DEV and the provider's key (e.g. DATAFORSEO_API_KEY in
7
+ * `.dev.vars`).
8
+ */
9
+ env: object;
10
+ /**
11
+ * A provider the app lists in its manifest's `capabilities`: one whose
12
+ * key the gateway holds, or one whose OAuth client it holds (its token
13
+ * endpoints only). The built-in catalog's ids complete; a provider
14
+ * registered on the gateway (`everyapp provider add`) is any other id,
15
+ * reached through the binding, which checks it. The `email` capability
16
+ * is sent with `sendEmail()` instead.
17
+ */
18
+ provider: ApiKeyCapabilityId | OAuthClientCapabilityId | (string & {});
19
+ /**
20
+ * Whom the call is for: the inbound request (its user), or
21
+ * `{ background: true, organizationId }` for work no person started
22
+ * (crons, Workflows, queues). An organization's own app may omit it for
23
+ * background work, which is then the app's; a shared app's Worker must
24
+ * pass one or the other ({@link AttributionError}).
25
+ */
26
+ for?: GatewayCallFor;
27
+ }
28
+ /**
29
+ * A `fetch` for one of the gateway's capabilities. Call the provider's real
30
+ * URLs (`https://api.dataforseo.com/v3/…`); this sends them over the
31
+ * AppGateway binding, which checks the app's manifest lists the capability,
32
+ * injects the key the gateway holds and forwards the call. The app never
33
+ * sees the key. A refusal the gateway makes itself comes back as a response
34
+ * {@link gatewayRefusal} recognizes; anything else is the provider's answer.
35
+ *
36
+ * For an OAuth client (`linkedin`, `mastodon`, ...) it reaches the
37
+ * provider's token endpoints only, and the gateway puts its client on the
38
+ * request: send the code (or refresh token) without `client_id` or
39
+ * `client_secret`. Build the authorize URL with {@link authorizeUrl} from
40
+ * {@link integrationStatus}, and call the provider's API with the user's
41
+ * token directly.
42
+ *
43
+ * In local dev only, with no binding, the developer's own key or client
44
+ * from `.dev.vars` reaches the provider directly, under the gateway's names:
45
+ * a catalog provider's from the catalog, a provider registered on the
46
+ * gateway from its spec in `.everyapp/providers/<id>.json` (`everyapp
47
+ * provider show <id> --save` writes it), put on the call as the gateway's
48
+ * spec says.
49
+ */
50
+ export declare function integrationFetch({ env, provider, for: target, }: IntegrationFetchOptions): typeof fetch;
51
+ export interface IntegrationStatus {
52
+ /** The app's manifest lists the capability. */
53
+ requested: boolean;
54
+ /** The gateway holds a key for it (in local dev: `.dev.vars` sets one). */
55
+ configured: boolean;
56
+ /** Calls would go through. */
57
+ ready: boolean;
58
+ }
59
+ /**
60
+ * The email capability's status: as {@link IntegrationStatus}, plus the
61
+ * From a message without `from` gets, the `from` patterns the gateway
62
+ * accepts from this app (its installation's senders, `everyapp email set
63
+ * --sender`) and the provider mail goes out with (`cloudflare` or `resend`).
64
+ */
65
+ export type EmailIntegrationStatus = IntegrationStatus & Pick<EmailCapabilityStatus, "sender" | "senders" | "provider">;
66
+ /**
67
+ * An OAuth client's status: as {@link IntegrationStatus}, plus what the app
68
+ * needs to send people to the provider's authorize page, present only when
69
+ * the app lists the capability and the gateway holds the client.
70
+ */
71
+ export interface OAuthClientIntegrationStatus extends IntegrationStatus {
72
+ /** The client's public id, for the authorize URL (and Twitch's `Client-Id`). */
73
+ clientId?: string;
74
+ /**
75
+ * The provider's origin when the operator chose it (Mastodon's instance,
76
+ * `MASTODON_URL`): the authorize page, token endpoint and API are on it.
77
+ */
78
+ origin?: string;
79
+ /**
80
+ * For a client with scope sets (`google-workspace`, `microsoft-365`): the
81
+ * set names the app's manifest entry declares, `[]` when the entry voids
82
+ * its grant (no `scopes`, or a set this gateway doesn't offer), in which
83
+ * case `ready` is false and no token call can complete.
84
+ */
85
+ scopeSets?: readonly string[];
86
+ /**
87
+ * The provider's scope strings those sets ask for, in the provider's
88
+ * spelling and the registry's order, for the authorize URL
89
+ * ({@link authorizeUrl} puts them on). The gateway refuses a token that
90
+ * carries more.
91
+ */
92
+ scopes?: readonly string[];
93
+ /** Slack's `user_scope` strings, when the sets name any. */
94
+ userScopes?: readonly string[];
95
+ }
96
+ /** The status of a capability: email's carries its senders, an OAuth client's its id. */
97
+ export type IntegrationStatusOf<P extends string> = P extends "email" ? EmailIntegrationStatus : P extends OAuthClientCapabilityId ? OAuthClientIntegrationStatus : IntegrationStatus;
98
+ /**
99
+ * Whether the app can use an integration right now, without calling the
100
+ * provider: show "an admin needs to set up DataForSEO" (or "sending isn't
101
+ * set up") up front instead of failing mid-task. An OAuth client's status
102
+ * carries its public `clientId` for the authorize URL.
103
+ */
104
+ export declare function integrationStatus<P extends CapabilityId | (string & {})>({ env, provider, }: {
105
+ env: object;
106
+ provider: P;
107
+ }): Promise<IntegrationStatusOf<P>>;
108
+ /** A refusal the AppGateway binding made itself, not the provider. */
109
+ export interface GatewayRefusal {
110
+ status: number;
111
+ /** e.g. `capability_not_requested`, `integration_not_configured`. */
112
+ code: string;
113
+ message: string;
114
+ }
115
+ /**
116
+ * A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
117
+ * when the gateway won't send (`email_sender_not_allowed`,
118
+ * `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
119
+ */
120
+ export declare class GatewayRefusalError extends Error implements GatewayRefusal {
121
+ readonly status: number;
122
+ readonly code: string;
123
+ constructor({ status, code, message }: GatewayRefusal);
124
+ }
125
+ /**
126
+ * The gateway's own refusal in `response`, or null when the response is the
127
+ * provider's answer (which may be an error of its own). Reads the body only
128
+ * for a refusal, so call it before reading the body yourself.
129
+ */
130
+ export declare function gatewayRefusal(response: Response): Promise<GatewayRefusal | null>;
131
+ //# sourceMappingURL=integrationFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrationFetch.d.ts","sourceRoot":"","sources":["../../src/server/integrationFetch.ts"],"names":[],"mappings":"AAAA,OAAO,EA2BL,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAE1B,KAAK,uBAAuB,EAO7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,QAAQ,EAAE,kBAAkB,GAAG,uBAAuB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACvE;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,QAAQ,EACR,GAAG,EAAE,MAAM,GACZ,EAAE,uBAAuB,GAAG,OAAO,KAAK,CAkGxC;AA+XD,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,UAAU,EAAE,OAAO,CAAC;IACpB,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GACpD,IAAI,CAAC,qBAAqB,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;AAEjE;;;;GAIG;AACH,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IACrE,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,4DAA4D;IAC5D,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,yFAAyF;AACzF,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,OAAO,GACjE,sBAAsB,GACtB,CAAC,SAAS,uBAAuB,GAC/B,4BAA4B,GAC5B,iBAAiB,CAAC;AAExB;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,CAAC,SAAS,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,EACtC,EACA,GAAG,EACH,QAAQ,GACT,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,CAAC,CAAC;CACb,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAoElC;AA2FD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAM,YAAW,cAAc;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,cAAc;CAMtD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAWhC"}