@every-app/sdk 0.3.0-rc.10 → 0.3.0-rc.12

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 (120) hide show
  1. package/README.md +67 -67
  2. package/dist/cloudflare/catalogSettings.generated.d.ts +7 -4
  3. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
  4. package/dist/cloudflare/catalogSettings.generated.js +7 -6
  5. package/dist/cloudflare/index.d.ts +1 -1
  6. package/dist/cloudflare/index.d.ts.map +1 -1
  7. package/dist/cloudflare/worker.d.ts +36 -2
  8. package/dist/cloudflare/worker.d.ts.map +1 -1
  9. package/dist/cloudflare/worker.js +32 -6
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/internal/aiDefault.d.ts +36 -79
  13. package/dist/internal/aiDefault.d.ts.map +1 -1
  14. package/dist/internal/aiDefault.js +47 -149
  15. package/dist/internal/aiProviders.d.ts.map +1 -1
  16. package/dist/internal/aiProviders.js +2 -2
  17. package/dist/internal/appSettings.d.ts +14 -17
  18. package/dist/internal/appSettings.d.ts.map +1 -1
  19. package/dist/internal/appSettings.js +2 -16
  20. package/dist/internal/capabilities.d.ts +106 -489
  21. package/dist/internal/capabilities.d.ts.map +1 -1
  22. package/dist/internal/capabilities.js +49 -550
  23. package/dist/internal/catalog/commerce.js +1 -1
  24. package/dist/internal/catalog/index.d.ts +35 -0
  25. package/dist/internal/catalog/index.d.ts.map +1 -1
  26. package/dist/internal/catalog/infra.d.ts +35 -0
  27. package/dist/internal/catalog/infra.d.ts.map +1 -1
  28. package/dist/internal/catalog/infra.js +65 -0
  29. package/dist/internal/events.d.ts +52 -1
  30. package/dist/internal/events.d.ts.map +1 -1
  31. package/dist/internal/events.js +65 -3
  32. package/dist/internal/gatewaySettings.d.ts +2 -2
  33. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  34. package/dist/internal/gatewaySettings.js +0 -6
  35. package/dist/internal/oauthBuiltIns.d.ts +101 -0
  36. package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
  37. package/dist/internal/oauthBuiltIns.js +122 -0
  38. package/dist/internal/oauthClient.d.ts +7 -47
  39. package/dist/internal/oauthClient.d.ts.map +1 -1
  40. package/dist/internal/oauthClient.js +1 -115
  41. package/dist/internal/oauthPresets.d.ts +14 -15
  42. package/dist/internal/oauthPresets.d.ts.map +1 -1
  43. package/dist/internal/oauthPresets.js +42 -853
  44. package/dist/internal/oauthScopes.d.ts +43 -155
  45. package/dist/internal/oauthScopes.d.ts.map +1 -1
  46. package/dist/internal/oauthScopes.js +82 -475
  47. package/dist/internal/oauthSpec.d.ts +72 -71
  48. package/dist/internal/oauthSpec.d.ts.map +1 -1
  49. package/dist/internal/oauthSpec.js +178 -447
  50. package/dist/internal/protocol.d.ts +24 -14
  51. package/dist/internal/protocol.d.ts.map +1 -1
  52. package/dist/internal/protocol.js +26 -7
  53. package/dist/internal/providerRegistry.d.ts +1 -61
  54. package/dist/internal/providerRegistry.d.ts.map +1 -1
  55. package/dist/internal/providerRegistry.js +8 -115
  56. package/dist/internal/providerSpec.d.ts.map +1 -1
  57. package/dist/internal/providerSpec.js +14 -36
  58. package/dist/internal/remoteMcp.d.ts +4 -6
  59. package/dist/internal/remoteMcp.d.ts.map +1 -1
  60. package/dist/internal/remoteMcp.js +8 -15
  61. package/dist/internal/tools.d.ts +12 -7
  62. package/dist/internal/tools.d.ts.map +1 -1
  63. package/dist/internal/tools.js +9 -8
  64. package/dist/manifest.d.ts +41 -28
  65. package/dist/manifest.d.ts.map +1 -1
  66. package/dist/manifest.js +1 -1
  67. package/dist/server/ai.d.ts +1 -93
  68. package/dist/server/ai.d.ts.map +1 -1
  69. package/dist/server/ai.js +17 -111
  70. package/dist/server/attribution.d.ts +10 -1
  71. package/dist/server/attribution.d.ts.map +1 -1
  72. package/dist/server/attribution.js +7 -1
  73. package/dist/server/connectUrl.d.ts +4 -9
  74. package/dist/server/connectUrl.d.ts.map +1 -1
  75. package/dist/server/connectUrl.js +17 -14
  76. package/dist/server/context.d.ts +12 -14
  77. package/dist/server/context.d.ts.map +1 -1
  78. package/dist/server/context.js +23 -25
  79. package/dist/server/eventsEndpoint.d.ts +48 -0
  80. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  81. package/dist/server/eventsEndpoint.js +145 -0
  82. package/dist/server/everyApp.d.ts +16 -26
  83. package/dist/server/everyApp.d.ts.map +1 -1
  84. package/dist/server/everyApp.js +22 -46
  85. package/dist/server/gatewayFetch.d.ts +1 -1
  86. package/dist/server/gatewayFetch.js +1 -1
  87. package/dist/server/index.d.ts +6 -9
  88. package/dist/server/index.d.ts.map +1 -1
  89. package/dist/server/index.js +4 -8
  90. package/dist/server/integrationFetch.d.ts +60 -72
  91. package/dist/server/integrationFetch.d.ts.map +1 -1
  92. package/dist/server/integrationFetch.js +117 -392
  93. package/dist/server/refusal.d.ts +0 -9
  94. package/dist/server/refusal.d.ts.map +1 -1
  95. package/dist/server/refusal.js +0 -22
  96. package/dist/server/tools.d.ts +13 -5
  97. package/dist/server/tools.d.ts.map +1 -1
  98. package/dist/server/tools.js +10 -3
  99. package/dist/server/toolsEndpoint.d.ts +2 -1
  100. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  101. package/dist/server/toolsEndpoint.js +4 -3
  102. package/dist/server/webhooks.d.ts +56 -0
  103. package/dist/server/webhooks.d.ts.map +1 -0
  104. package/dist/server/webhooks.js +205 -0
  105. package/dist/testing/index.d.ts +2 -3
  106. package/dist/testing/index.d.ts.map +1 -1
  107. package/dist/testing/index.js +2 -6
  108. package/package.json +1 -1
  109. package/dist/server/aiBinding.d.ts +0 -68
  110. package/dist/server/aiBinding.d.ts.map +0 -1
  111. package/dist/server/aiBinding.js +0 -294
  112. package/dist/server/authorizeUrl.d.ts +0 -43
  113. package/dist/server/authorizeUrl.d.ts.map +0 -1
  114. package/dist/server/authorizeUrl.js +0 -106
  115. package/dist/server/emailBinding.d.ts +0 -72
  116. package/dist/server/emailBinding.d.ts.map +0 -1
  117. package/dist/server/emailBinding.js +0 -124
  118. package/dist/server/fromMcpTools.d.ts +0 -89
  119. package/dist/server/fromMcpTools.d.ts.map +0 -1
  120. package/dist/server/fromMcpTools.js +0 -192
@@ -1,7 +1,8 @@
1
- import { ACCOUNT_HEADER, CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX, OAUTH_TOKEN_BODY_LIMIT, allowedScopes, apiKeyHosts, authorizationFor, capabilitySettings, carriesToken, declaredScopeSets, describeTokenEndpoints, devProviderFile, grantedScopes, heldTokenCallAllowed, injectClientCredentials, integrationProxyPath, oauthClientCredentials, oauthReadMethods, oauthWriteMethods, providerSettingPrefix, requestedScopes, requestedTokenScopes, resolveDevProvider, rewriteTokenRequestScope, scopeExcess, tokenEndpointFor, tokenGrantType, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
1
+ import { APP_ID_ENV, CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX, apiKeyHosts, authorizationFor, capabilitySettings, devProviderFile, integrationProxyPath, providerSettingPrefix, resolveDevProvider, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
2
2
  import { applyAttribution, attributionFor, } from "./attribution.js";
3
+ import { connectPath } from "./connectUrl.js";
3
4
  import { BINDING_ORIGIN, gatewayBinding, streamingRequest, } from "./gatewayFetch.js";
4
- export function integrationFetch({ env, provider, for: explicitTarget, account, optional = false, }) {
5
+ export function integrationFetch({ env, provider, for: explicitTarget, optional = false, }) {
5
6
  const catalogEntry = fetchableCapability(provider);
6
7
  const what = `integrationFetch("${provider}")`;
7
8
  // Checked now, so a call nobody's fails where the fetch is made; and
@@ -16,26 +17,15 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
16
17
  // or in local dev, the registered row's spec `everyapp dev` hands over.
17
18
  const integration = catalogEntry ??
18
19
  (binding ? undefined : devRegisteredProvider(provider, values));
19
- const local = binding || !integration ? null : localCredentials(integration, values);
20
- // In dev an OAuth provider with a held token in `.dev.vars` serves API
21
- // calls without the client (the token stands in for a connection).
22
- const devToken = !binding && integration?.mode === "oauth_client" && integration.flow
23
- ? stringValue(values[devAccessTokenName(provider)])
24
- : undefined;
25
- // An OAuth provider with a Gateway-owned flow answers an API call
26
- // without the client (`integration_not_connected`, below); a token
27
- // endpoint call without the client throws when it is made.
28
- const decidesLater = integration?.mode === "oauth_client" && integration.flow !== undefined;
29
- if (!binding && !local && !devToken && integration && !decidesLater) {
20
+ // An API-key provider in local dev: the developer's own key. An OAuth
21
+ // provider decides per call (a dev token, or `integration_not_connected`).
22
+ const local = binding || integration?.mode !== "api_key"
23
+ ? null
24
+ : localAuthorization(integration, values);
25
+ if (!binding && integration?.mode === "api_key" && !local) {
30
26
  if (optional)
31
27
  return null;
32
- const names = [
33
- ...capabilitySettings(integration),
34
- ...(integration.mode === "oauth_client" && integration.flow
35
- ? [devAccessTokenName(provider)]
36
- : []),
37
- ];
38
- throw new ConfigurationError(`${integration.label} is unavailable: deploy with the EVERY_APP_GATEWAY binding, or set ${names.join(", ")} in .dev.vars for local development.`);
28
+ throw new ConfigurationError(`${integration.label} is unavailable: deploy with the EVERY_APP_GATEWAY binding, or set ${capabilitySettings(integration).join(", ")} in .dev.vars for local development.`);
39
29
  }
40
30
  return async (input, init) => {
41
31
  const target = attributionFor(env, explicitTarget, what);
@@ -53,48 +43,13 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
53
43
  applyAttribution(headers, target);
54
44
  return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
55
45
  }
56
- if (integration.mode === "oauth_client") {
57
- // A call to the provider's API with the token the gateway holds
58
- // (docs/design/2026-10-oauth-providers.md section 7), for an entry
59
- // that supports the Gateway-owned flow; a token endpoint stays the
60
- // app-driven call it always was.
61
- const toApi = integration.flow !== undefined &&
62
- upstream.protocol === "https:" &&
63
- integration.api.includes(upstream.host) &&
64
- !tokenEndpointFor(integration, upstream);
65
- if (toApi) {
66
- if (binding) {
67
- if (account)
68
- headers.set(ACCOUNT_HEADER, account);
69
- applyAttribution(headers, target);
70
- return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
71
- }
72
- return devHeldTokenCall({
73
- provider,
74
- capability: integration,
75
- outbound,
76
- upstream,
77
- headers,
78
- values,
79
- });
80
- }
81
- return oauthClientCall({
82
- provider: provider,
83
- capability: integration,
84
- outbound,
85
- upstream,
86
- headers,
87
- values,
88
- ...(binding
89
- ? { binding, target }
90
- : { client: local }),
91
- });
92
- }
93
46
  // In local dev a provider's host setting (`.dev.vars`) adds its host,
94
47
  // as the gateway's setting does there.
95
- const hosts = binding
96
- ? [...integration.hosts]
97
- : apiKeyHosts(integration, values);
48
+ const hosts = integration.mode === "oauth_client"
49
+ ? [...integration.api]
50
+ : binding
51
+ ? [...integration.hosts]
52
+ : apiKeyHosts(integration, values);
98
53
  if (upstream.protocol !== "https:" || !hosts.includes(upstream.host)) {
99
54
  throw new TypeError(`integrationFetch("${provider}") only reaches ${hosts.join(", ")}, not ${upstream.host}.`);
100
55
  }
@@ -103,6 +58,16 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
103
58
  return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
104
59
  }
105
60
  applyAttribution(headers, undefined);
61
+ if (integration.mode === "oauth_client") {
62
+ return devHeldTokenCall({
63
+ provider,
64
+ capability: integration,
65
+ outbound,
66
+ upstream,
67
+ headers,
68
+ values,
69
+ });
70
+ }
106
71
  // The developer's own key, put where the provider's entry says, as the
107
72
  // gateway would put its own.
108
73
  const auth = local;
@@ -124,57 +89,30 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
124
89
  export function devAccessTokenName(provider) {
125
90
  return `${providerSettingPrefix(provider)}ACCESS_TOKEN`;
126
91
  }
92
+ /**
93
+ * Where a person connects `provider` in this app:
94
+ * `/connect/<id>?app=<EVERYAPP_APP_ID>`, as the gateway's refusal and
95
+ * status name it (without the app id when the env has none).
96
+ */
97
+ function appConnectPath(provider, values) {
98
+ const app = stringValue(values[APP_ID_ENV]);
99
+ return app
100
+ ? `${connectPath(provider)}?app=${encodeURIComponent(app)}`
101
+ : connectPath(provider);
102
+ }
127
103
  /**
128
104
  * An app's call to an OAuth provider's API in local dev, with no
129
- * gateway: the gateway's held-token checks in its order (the app's
130
- * declared sets, the method, the route), then the developer's own token
131
- * from `.dev.vars` as the bearer, straight to the provider.
105
+ * gateway: the developer's own token from `.dev.vars` as the bearer,
106
+ * straight to the provider (the host was checked already), or the
107
+ * gateway's `integration_not_connected` refusal without one.
132
108
  */
133
109
  async function devHeldTokenCall({ provider, capability, outbound, upstream, headers, values, }) {
134
110
  const token = stringValue(values[devAccessTokenName(provider)]);
135
111
  if (!token) {
136
- return devRefusal(409, "integration_not_connected", `${capability.label} isn't connected here. In local dev, set ${devAccessTokenName(provider)} in .dev.vars to a token you obtained yourself; deployed, the person connects at /connect/${provider}.`, { connectPath: `/connect/${provider}`, reconnect: false });
137
- }
138
- const policy = capability.scopePolicy;
139
- const grant = policy
140
- ? devScopeSets(policy, provider, capability.label, values, "any")
141
- : null;
142
- if (grant && "problem" in grant) {
143
- return devRefusal(403, grant.code, grant.problem);
112
+ const path = appConnectPath(provider, values);
113
+ return devRefusal(409, "integration_not_connected", `${capability.label} isn't connected here. In local dev, set ${devAccessTokenName(provider)} in .dev.vars to a token you obtained yourself; deployed, the person connects in the app at ${path}.`, { connectPath: path, reconnect: false });
144
114
  }
145
115
  const method = outbound.method.toUpperCase();
146
- const reads = oauthReadMethods(capability).includes(method);
147
- const writes = !reads &&
148
- capability.access.includes("write") &&
149
- oauthWriteMethods(capability).includes(method) &&
150
- devDeclaredAccess(provider, values) === "write";
151
- if (policy && grant) {
152
- const allowed = heldTokenCallAllowed({
153
- policy,
154
- declaredSets: grant.sets,
155
- // The developer's token stands for a connection granted every set.
156
- connectionSets: grant.sets,
157
- hosts: capability.api,
158
- method,
159
- upstream,
160
- readMethods: oauthReadMethods(capability),
161
- writes,
162
- });
163
- if (!allowed.ok) {
164
- const status = {
165
- integration_not_connected: 409,
166
- integration_method_not_allowed: 405,
167
- integration_scope_not_allowed: 403,
168
- }[allowed.code];
169
- return devRefusal(status, allowed.code, allowed.code === "integration_not_connected"
170
- ? `${capability.label} is connected without ${allowed.missingSets.join(", ")}.`
171
- : allowed.message);
172
- }
173
- }
174
- else if (!reads && !writes) {
175
- return devRefusal(405, "integration_method_not_allowed", `${capability.label} calls may use ${oauthReadMethods(capability).join(" or ")} with access: "read"; ${method} needs access: "write" in the app's capabilities entry, on a provider that offers it.`);
176
- }
177
- applyAttribution(headers, undefined);
178
116
  headers.set("authorization", `Bearer ${token}`);
179
117
  for (const name of ["access_token", "token"]) {
180
118
  upstream.searchParams.delete(name);
@@ -190,255 +128,53 @@ async function devHeldTokenCall({ provider, capability, outbound, upstream, head
190
128
  ...(method === "GET" || method === "HEAD" ? {} : { duplex: "half" }),
191
129
  }));
192
130
  }
193
- /** The `access` the manifest entry for `provider` declares in dev (EVERYAPP_DEV_CAPABILITIES), or null. */
194
- function devDeclaredAccess(provider, values) {
195
- const raw = values["EVERYAPP_DEV_CAPABILITIES"];
196
- try {
197
- const capabilities = typeof raw === "string" ? jsonRecord(JSON.parse(raw)) : undefined;
198
- const entry = jsonRecord(capabilities?.[provider]);
199
- return typeof entry?.["access"] === "string" ? entry["access"] : null;
200
- }
201
- catch {
202
- return null;
203
- }
204
- }
205
131
  /**
206
- * The developer's own key or client from `.dev.vars` (with EVERYAPP_DEV
207
- * set), under the gateway's names; null until every setting it needs is.
132
+ * The developer's own key from `.dev.vars` (with EVERYAPP_DEV set), under
133
+ * the gateway's names; null until every setting it needs is.
208
134
  */
209
- function localCredentials(integration, values) {
135
+ function localAuthorization(integration, values) {
210
136
  if (!values["EVERYAPP_DEV"])
211
137
  return null;
212
- if (integration.mode === "oauth_client") {
213
- return oauthClientCredentials(integration, values);
214
- }
215
138
  return authorizationFor(integration, values);
216
139
  }
217
- /**
218
- * A call to an OAuth client's token endpoint: over the binding, where the
219
- * gateway puts its client on it, or in local dev straight to the provider
220
- * with the developer's client put on it by the gateway's own rule.
221
- */
222
- async function oauthClientCall({ provider, capability, outbound, upstream, headers, values, binding, target, client, }) {
223
- const endpoint = tokenEndpointFor(capability, upstream);
224
- const method = outbound.method.toUpperCase();
225
- if (!endpoint || !endpoint.methods.includes(method)) {
226
- throw new TypeError(`integrationFetch("${provider}") only reaches ${capability.label}'s token endpoints (${describeTokenEndpoints(capability)}), not ${method} ${upstream.origin}${upstream.pathname}. Call its API with the user's token directly.`);
227
- }
228
- if (binding) {
229
- applyAttribution(headers, target);
230
- return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
231
- }
232
- applyAttribution(headers, undefined);
233
- if (!client) {
234
- throw new ConfigurationError(`${capability.label}'s token endpoints need the client in .dev.vars for local development: set ${capabilitySettings(capability).join(", ")}.`);
235
- }
236
- // The gateway's scope checks, in its order, answering its refusals: an
237
- // app sees in `everyapp dev` what the gateway will say.
238
- const policy = capability.scopePolicy;
239
- const grant = policy
240
- ? devScopeSets(policy, provider, capability.label, values)
241
- : null;
242
- if (grant && "problem" in grant) {
243
- return devRefusal(403, grant.code, grant.problem);
244
- }
245
- const sets = grant?.sets;
246
- const body = method === "GET" ? null : await outbound.text();
247
- if (body !== null &&
248
- new TextEncoder().encode(body).byteLength > OAUTH_TOKEN_BODY_LIMIT) {
249
- throw new TypeError(`A token request's body is at most ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB.`);
250
- }
251
- if (policy &&
252
- ["grant_type", "scope", "user_scope"].some((name) => upstream.searchParams.has(name))) {
253
- return devRefusal(400, "integration_body_invalid", "Put grant_type, scope and user_scope in the token request's body, not its query string; the gateway decides on the body.");
254
- }
255
- let injected = injectClientCredentials({ url: upstream, headers, body }, endpoint, client);
256
- if (!injected.ok)
257
- throw new TypeError(injected.problem);
258
- if (policy && sets) {
259
- const refused = refuseTokenRequest(policy, capability, sets, injected);
260
- if (refused)
261
- return refused;
262
- injected = {
263
- ok: true,
264
- ...rewriteTokenRequestScope(policy, sets, injected),
265
- };
266
- }
267
- const response = await fetch(new Request(injected.url, {
268
- method,
269
- headers: injected.headers,
270
- body: injected.body,
271
- signal: outbound.signal,
272
- // As the gateway: the client never follows a redirect.
273
- redirect: "manual",
274
- }));
275
- if (!policy || !sets || !response.ok)
276
- return response;
277
- return checkTokenResponse(policy, capability, sets, response);
278
- }
279
140
  /**
280
141
  * The gateway's refusal shape (`{ error, code, message }`, the code in
281
142
  * `x-everyapp-error`) from the local-dev twin, so {@link gatewayRefusal}
282
143
  * reads it the same way.
283
144
  */
284
145
  function devRefusal(status, code, message, extra = {}) {
285
- const labels = {
286
- 400: "Bad Request",
287
- 403: "Forbidden",
288
- 405: "Method Not Allowed",
289
- 409: "Conflict",
290
- 502: "Bad Gateway",
291
- };
146
+ const labels = { 409: "Conflict" };
292
147
  return Response.json({ error: labels[status] ?? "Error", code, message, ...extra }, { status, headers: { [GATEWAY_ERROR_HEADER]: code } });
293
148
  }
294
149
  /**
295
- * The scope sets the manifest declares for `id`, read from
150
+ * The manifest's entry for `id` in local dev, from
296
151
  * EVERYAPP_DEV_CAPABILITIES (the manifest's `capabilities` as JSON, which
297
- * `everyAppWorker()` writes in dev), or the gateway's refusal, in its
298
- * order: the client not listed for writing (`capability_not_requested`),
299
- * then a void grant (`integration_scopes_undeclared`: the variable
300
- * missing, the entry without usable `scopes`, or a set this registry
301
- * doesn't offer). Fails closed like the gateway: a hand-rolled dev harness
302
- * that passes no capabilities gets nothing.
152
+ * `everyAppWorker()` writes in dev): the entry, `null` when the manifest
153
+ * doesn't list it, `undefined` when the variable is missing or isn't JSON
154
+ * (a hand-rolled harness), so nothing is known.
303
155
  */
304
- function devScopeSets(policy, id, label, values,
305
- /** The grant the entry needs: `write` for a token-endpoint call (the app-driven flow), `any` for a held-token call. */
306
- grant = "write") {
307
- const offered = Object.keys(policy.sets).join(", ");
156
+ function devManifestEntry(id, values) {
308
157
  const raw = values["EVERYAPP_DEV_CAPABILITIES"];
158
+ if (typeof raw !== "string")
159
+ return undefined;
309
160
  let capabilities;
310
161
  try {
311
- capabilities =
312
- typeof raw === "string" ? jsonRecord(JSON.parse(raw)) : undefined;
162
+ capabilities = jsonRecord(JSON.parse(raw));
313
163
  }
314
164
  catch {
315
- capabilities = undefined;
316
- }
317
- if (!capabilities) {
318
- return {
319
- code: "integration_scopes_undeclared",
320
- problem: `${label}'s scope sets aren't known here: everyapp dev passes the manifest's capabilities to the Worker as EVERYAPP_DEV_CAPABILITIES when everyapp.config.ts builds it with everyAppWorker() from @every-app/sdk/cloudflare.`,
321
- };
322
- }
323
- const entry = Object.hasOwn(capabilities, id)
324
- ? jsonRecord(capabilities[id])
325
- : undefined;
326
- // The gateway's check 2: listing the client for writing is the grant
327
- // of the app-driven flow; a held-token call takes any listing.
328
- const access = entry?.["access"];
329
- if (!entry ||
330
- (grant === "write"
331
- ? access !== "write"
332
- : access !== "read" && access !== "write")) {
333
- return {
334
- code: "capability_not_requested",
335
- problem: `This app doesn't list ${label} in its capabilities. Add capabilities: { ${JSON.stringify(id)}: { access: "${grant === "write" ? "write" : "read"}" } } to everyapp.config.ts and start everyapp dev again.`,
336
- };
337
- }
338
- const sets = declaredScopeSets(policy, entry);
339
- if (sets)
340
- return { sets };
341
- const listed = entry["scopes"];
342
- const unknown = Array.isArray(listed)
343
- ? listed.filter((name) => typeof name === "string" && !Object.hasOwn(policy.sets, name))
344
- : [];
345
- if (unknown.length > 0) {
346
- return {
347
- code: "integration_scopes_undeclared",
348
- problem: `This app's ${label} entry names scope sets this SDK doesn't offer: ${unknown.join(", ")}. It offers: ${offered}. Fix everyapp.config.ts and start everyapp dev again, or update @every-app/sdk if it is older than the set.`,
349
- };
165
+ return undefined;
350
166
  }
351
- return {
352
- code: "integration_scopes_undeclared",
353
- problem: `This app lists ${label} without the scope sets it uses. Add scopes: [...] (one or more of: ${offered}) to its ${JSON.stringify(id)} entry in everyapp.config.ts and start everyapp dev again.`,
354
- };
167
+ if (!capabilities)
168
+ return undefined;
169
+ return Object.hasOwn(capabilities, id)
170
+ ? (jsonRecord(capabilities[id]) ?? null)
171
+ : null;
355
172
  }
356
173
  function jsonRecord(value) {
357
174
  return value && typeof value === "object" && !Array.isArray(value)
358
175
  ? value
359
176
  : undefined;
360
177
  }
361
- /**
362
- * The gateway's checks on a token request before it goes out: the grant
363
- * type must be one the policy completes, and a `scope` the body names must
364
- * stay within the sets. Null when the request may go.
365
- */
366
- function refuseTokenRequest(policy, capability, sets, request) {
367
- const grantType = tokenGrantType(request);
368
- if (!policy.grantTypes.includes(grantType)) {
369
- const completes = policy.grantTypes.map((type) => type === null ? "a code exchange without grant_type" : type);
370
- const asked = grantType === null
371
- ? "a request without grant_type"
372
- : grantType === "other"
373
- ? "this request's grant_type"
374
- : grantType;
375
- return devRefusal(403, "integration_grant_not_allowed", `Through the gateway, ${capability.label} completes ${completes.join(" and ")} grants only; ${asked} isn't one of them. An app never gets app-level tokens with the operator's client.`);
376
- }
377
- const asked = requestedTokenScopes(policy, request);
378
- const excess = asked ? scopeExcess(asked, allowedScopes(policy, sets)) : [];
379
- if (excess.length > 0) {
380
- return devRefusal(403, "integration_scope_not_allowed", `This app may ask ${capability.label} for ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}); the request also asks ${excess.join(", ")}. Build the authorize URL and the token request from integrationStatus().scopes.`);
381
- }
382
- return null;
383
- }
384
- /**
385
- * The gateway's check on a provider's 2xx answer: a token it carries must
386
- * name scopes, and those must stay within the sets; on a refusal the
387
- * answer is dropped, never forwarded, so no app holds a token outside its
388
- * grant. An answer without a token (Slack's `ok: false`) is the app's to
389
- * read. A non-2xx answer never reaches here.
390
- */
391
- async function checkTokenResponse(policy, capability, sets, response) {
392
- const invalid = () => devRefusal(502, "integration_response_invalid", `${capability.label} answered with something that isn't a token response (over ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB or not a JSON object); nothing was passed on.`);
393
- const bytes = await response.arrayBuffer();
394
- if (bytes.byteLength > OAUTH_TOKEN_BODY_LIMIT)
395
- return invalid();
396
- const text = new TextDecoder().decode(bytes);
397
- let json;
398
- try {
399
- json = JSON.parse(text);
400
- }
401
- catch {
402
- return invalid();
403
- }
404
- if (!json || typeof json !== "object" || Array.isArray(json)) {
405
- return invalid();
406
- }
407
- // The body was decoded on read: its length and encoding headers no
408
- // longer describe it.
409
- const headers = new Headers(response.headers);
410
- for (const name of [
411
- "set-cookie",
412
- "content-length",
413
- "content-encoding",
414
- "transfer-encoding",
415
- ]) {
416
- headers.delete(name);
417
- }
418
- const forward = () => new Response(text, {
419
- status: response.status,
420
- statusText: response.statusText,
421
- headers,
422
- });
423
- if (!carriesToken(json))
424
- return forward();
425
- const granted = grantedScopes(policy, json);
426
- if (!granted) {
427
- return devRefusal(502, "integration_scope_unverified", `${capability.label} issued a token without saying which scopes it carries, so the gateway can't hold it to this app's scope sets; nothing was passed on. Ask the person to connect again.`);
428
- }
429
- const excess = scopeExcess(granted, allowedScopes(policy, sets));
430
- if (excess.length > 0) {
431
- const remedy = {
432
- google: "Ask for only the scopes integrationStatus() returns, without include_granted_scopes, and connect again; if the app needs more, add the scope set to everyapp.config.ts and deploy.",
433
- microsoft: "On a shared Microsoft client a token carries everything the person consented to this client: have them remove this app's consent at https://myapplications.microsoft.com and connect again, or add the scope set to everyapp.config.ts and deploy.",
434
- slack: "Slack scopes add up per workspace installation: reinstall the app in the workspace with only the status's scopes, or add the scope set to everyapp.config.ts and deploy.",
435
- space: "Ask for only the scopes integrationStatus() returns and connect again, or add the scope set to everyapp.config.ts and deploy.",
436
- comma: "Ask for only the scopes integrationStatus() returns and connect again, or add the scope set to everyapp.config.ts and deploy.",
437
- }[policy.dialect];
438
- return devRefusal(403, "integration_scope_exceeded", `${capability.label} granted scopes this app doesn't declare: ${excess.join(", ")}. It declares ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}). Nothing was passed on. ${remedy}`);
439
- }
440
- return forward();
441
- }
442
178
  /**
443
179
  * A provider outside the catalog in local dev (no binding): the gateway
444
180
  * row's spec from EVERYAPP_DEV_PROVIDERS (`.everyapp/providers/<id>.json`,
@@ -480,8 +216,9 @@ function stringValue(value) {
480
216
  /**
481
217
  * Whether the app can use an integration right now, without calling the
482
218
  * provider: show "an admin needs to set up DataForSEO" (or "sending isn't
483
- * set up") up front instead of failing mid-task. An OAuth client's status
484
- * carries its public `clientId` for the authorize URL.
219
+ * set up", or "connect Google") up front instead of failing mid-task. An
220
+ * OAuth provider's status carries the person's connection in this app and
221
+ * where to connect.
485
222
  */
486
223
  export async function integrationStatus({ env, provider, for: explicitTarget, }) {
487
224
  const status = await readIntegrationStatus({
@@ -507,6 +244,18 @@ function withSetup(status, provider, values) {
507
244
  };
508
245
  }
509
246
  const integration = CATALOG.get(provider) ?? devRegisteredProvider(provider, values);
247
+ const label = integration?.label ?? provider;
248
+ if (integration?.mode === "oauth_client") {
249
+ // In dev a token stands in for the connection; the client is the
250
+ // deployed gateway's alone.
251
+ const name = devAccessTokenName(provider);
252
+ return {
253
+ ...status,
254
+ settings: [name],
255
+ setup: `${label} isn't connected for local development: set ${name} in .dev.vars to a token you obtained yourself.`,
256
+ command: null,
257
+ };
258
+ }
510
259
  const settings = provider === "email"
511
260
  ? ["EMAIL_FROM"]
512
261
  : integration
@@ -516,8 +265,8 @@ function withSetup(status, provider, values) {
516
265
  ...status,
517
266
  settings,
518
267
  setup: settings.length
519
- ? `${integration?.label ?? provider} isn't set up for local development: set ${settings.join(" and ")} in .dev.vars.`
520
- : `${integration?.label ?? provider} isn't set up for local development.`,
268
+ ? `${label} isn't set up for local development: set ${settings.join(" and ")} in .dev.vars.`
269
+ : `${label} isn't set up for local development.`,
521
270
  command: null,
522
271
  };
523
272
  }
@@ -544,11 +293,20 @@ async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
544
293
  senders: [],
545
294
  provider: null,
546
295
  }
547
- : {
548
- requested: false,
549
- configured: false,
550
- ready: false,
551
- });
296
+ : integration?.mode === "oauth_client"
297
+ ? {
298
+ requested: false,
299
+ configured: false,
300
+ ready: false,
301
+ scopes: [],
302
+ connectPath: null,
303
+ connection: null,
304
+ }
305
+ : {
306
+ requested: false,
307
+ configured: false,
308
+ ready: false,
309
+ });
552
310
  if (binding && typeof binding.fetch === "function") {
553
311
  const statusUrl = `${BINDING_ORIGIN}${INTEGRATION_PROXY_PREFIX}/${provider}`;
554
312
  let response;
@@ -556,8 +314,9 @@ async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
556
314
  response = await binding.fetch(statusUrl);
557
315
  }
558
316
  else {
559
- // Whose status: the person's identity (their OAuth connection) or the
560
- // organization's, as a call would carry it.
317
+ // Whose status: the person's identity (their connection in this
318
+ // app), the member background work names, or the organization's,
319
+ // as a call would carry it.
561
320
  const headers = new Headers();
562
321
  applyAttribution(headers, target);
563
322
  response = await binding.fetch(statusUrl, { headers });
@@ -581,7 +340,7 @@ async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
581
340
  };
582
341
  }
583
342
  if (integration.mode === "oauth_client") {
584
- return devOAuthClientStatus(provider, integration, values);
343
+ return devOAuthStatus(String(provider), values);
585
344
  }
586
345
  const configured = Boolean(values["EVERYAPP_DEV"] && authorizationFor(integration, values));
587
346
  return {
@@ -591,70 +350,36 @@ async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
591
350
  };
592
351
  }
593
352
  /**
594
- * An OAuth client in local dev, with no gateway: the developer's own, from
595
- * `.dev.vars`, and for a client with scope sets the manifest's, from
596
- * EVERYAPP_DEV_CAPABILITIES, shaped as the gateway's status is: an app
597
- * that doesn't list the client is told nothing, and a void grant is not
598
- * ready and hides the client id.
353
+ * An OAuth provider in local dev, with no gateway, shaped as the
354
+ * gateway's status is: the scopes from the manifest's entry
355
+ * (EVERYAPP_DEV_CAPABILITIES), and a token in `.dev.vars` standing in for
356
+ * a connection that holds them all. An app whose manifest doesn't list
357
+ * the provider is told nothing.
599
358
  */
600
- function devOAuthClientStatus(id, capability, values) {
601
- const client = values["EVERYAPP_DEV"]
602
- ? oauthClientCredentials(capability, values)
603
- : null;
604
- const policy = capability.scopePolicy;
605
- // An entry with a Gateway-owned flow is listed for reading or writing;
606
- // the app-driven flow alone needs the write grant.
607
- const grant = policy
608
- ? devScopeSets(policy, id, capability.label, values, capability.flow ? "any" : "write")
609
- : null;
610
- // The Gateway-owned flow's fields, in dev: a token in `.dev.vars`
611
- // stands for a connection granted every declared set.
612
- const flow = capability.flow ? "both" : "app";
613
- const devToken = capability.flow
614
- ? stringValue(values[devAccessTokenName(id)])
615
- : undefined;
616
- const gateway = (sets) => capability.flow
617
- ? {
618
- flow,
619
- connectPath: `/connect/${id}`,
620
- connection: devToken
621
- ? {
622
- account: "user",
623
- label: devAccessTokenName(id),
624
- scopeSets: sets,
625
- ready: true,
626
- }
627
- : null,
628
- }
629
- : { flow };
630
- if (grant && "problem" in grant) {
631
- if (grant.code === "capability_not_requested") {
632
- return { requested: false, configured: false, ready: false };
633
- }
359
+ function devOAuthStatus(id, values) {
360
+ const entry = devManifestEntry(id, values);
361
+ if (entry === null) {
634
362
  return {
635
- requested: true,
636
- configured: Boolean(client),
363
+ requested: false,
364
+ configured: false,
637
365
  ready: false,
638
- scopeSets: [],
639
- ...gateway([]),
366
+ scopes: [],
367
+ connectPath: null,
368
+ connection: null,
640
369
  };
641
370
  }
642
- const asked = policy && grant ? requestedScopes(policy, grant.sets) : null;
371
+ const listed = entry?.["scopes"];
372
+ const scopes = Array.isArray(listed)
373
+ ? listed.filter((scope) => typeof scope === "string")
374
+ : [];
375
+ const token = Boolean(stringValue(values[devAccessTokenName(id)]));
643
376
  return {
644
377
  requested: true,
645
- configured: Boolean(client),
646
- ready: Boolean(client) || Boolean(devToken),
647
- ...(client ? { clientId: client.clientId } : {}),
648
- ...((client || devToken) && grant && asked
649
- ? {
650
- scopeSets: grant.sets,
651
- scopes: asked.scopes,
652
- ...(asked.userScopes.length > 0
653
- ? { userScopes: asked.userScopes }
654
- : {}),
655
- }
656
- : {}),
657
- ...gateway(grant?.sets ?? []),
378
+ configured: token,
379
+ ready: token,
380
+ scopes,
381
+ connectPath: appConnectPath(id, values),
382
+ connection: token ? { label: null, scopes, ready: true } : null,
658
383
  };
659
384
  }
660
385
  /**
@@ -14,8 +14,6 @@ export interface GatewayRefusal extends SetupInfo {
14
14
  code: string;
15
15
  message: string;
16
16
  }
17
- /** The refusal codes that mean no model is set up for the call. */
18
- export declare const AI_NOT_CONFIGURED_CODES: readonly string[];
19
17
  /**
20
18
  * A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
21
19
  * when the gateway won't send (`email_sender_not_allowed`,
@@ -45,11 +43,4 @@ export declare function gatewayRefusal(response: Response): Promise<GatewayRefus
45
43
  * (`ai_provider_not_configured`, status 503, the `.dev.vars` names).
46
44
  */
47
45
  export declare function refusalFromError(error: unknown): GatewayRefusal | null;
48
- /**
49
- * Whether `error` (or a refusal already read) means no model is set up:
50
- * `ai_provider_not_configured` or `ai_default_not_configured`, from the
51
- * Gateway or, in local development, from the SDK with no key in
52
- * `.dev.vars`. Show `refusalFromError(error).setup` to the person.
53
- */
54
- export declare function isAiNotConfigured(error: unknown): boolean;
55
46
  //# sourceMappingURL=refusal.d.ts.map