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

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -3
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +81 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +174 -0
  6. package/dist/cloudflare/index.d.ts +23 -0
  7. package/dist/cloudflare/index.d.ts.map +1 -0
  8. package/dist/cloudflare/index.js +24 -0
  9. package/dist/cloudflare/sentinel.d.ts +127 -0
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  11. package/dist/cloudflare/sentinel.js +85 -0
  12. package/dist/cloudflare/worker.d.ts +805 -0
  13. package/dist/cloudflare/worker.d.ts.map +1 -0
  14. package/dist/cloudflare/worker.js +997 -0
  15. package/dist/dev/index.d.ts +8 -0
  16. package/dist/dev/index.d.ts.map +1 -0
  17. package/dist/dev/index.js +7 -0
  18. package/dist/dev/keypair.d.ts +26 -0
  19. package/dist/dev/keypair.d.ts.map +1 -0
  20. package/dist/dev/keypair.js +62 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +204 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +334 -0
  27. package/dist/internal/aiProviders.d.ts +72 -0
  28. package/dist/internal/aiProviders.d.ts.map +1 -0
  29. package/dist/internal/aiProviders.js +233 -0
  30. package/dist/internal/appSettings.d.ts +315 -0
  31. package/dist/internal/appSettings.d.ts.map +1 -0
  32. package/dist/internal/appSettings.js +549 -0
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +3174 -0
  37. package/dist/internal/capabilities.d.ts.map +1 -0
  38. package/dist/internal/capabilities.js +1110 -0
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +1973 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +423 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +543 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +49 -0
  58. package/dist/internal/devProviders.d.ts.map +1 -0
  59. package/dist/internal/devProviders.js +80 -0
  60. package/dist/internal/email.d.ts +280 -0
  61. package/dist/internal/email.d.ts.map +1 -0
  62. package/dist/internal/email.js +613 -0
  63. package/dist/internal/events.d.ts +193 -0
  64. package/dist/internal/events.d.ts.map +1 -0
  65. package/dist/internal/events.js +367 -0
  66. package/dist/internal/gatewaySettings.d.ts +83 -0
  67. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  68. package/dist/internal/gatewaySettings.js +215 -0
  69. package/dist/internal/identity.d.ts +57 -5
  70. package/dist/internal/identity.d.ts.map +1 -1
  71. package/dist/internal/identity.js +99 -22
  72. package/dist/internal/index.d.ts +22 -1
  73. package/dist/internal/index.d.ts.map +1 -1
  74. package/dist/internal/index.js +22 -1
  75. package/dist/internal/members.d.ts +36 -0
  76. package/dist/internal/members.d.ts.map +1 -0
  77. package/dist/internal/members.js +7 -0
  78. package/dist/internal/oauthClient.d.ts +61 -0
  79. package/dist/internal/oauthClient.d.ts.map +1 -0
  80. package/dist/internal/oauthClient.js +133 -0
  81. package/dist/internal/oauthPresets.d.ts +33 -0
  82. package/dist/internal/oauthPresets.d.ts.map +1 -0
  83. package/dist/internal/oauthPresets.js +1064 -0
  84. package/dist/internal/oauthScopes.d.ts +172 -0
  85. package/dist/internal/oauthScopes.d.ts.map +1 -0
  86. package/dist/internal/oauthScopes.js +510 -0
  87. package/dist/internal/oauthSpec.d.ts +102 -0
  88. package/dist/internal/oauthSpec.d.ts.map +1 -0
  89. package/dist/internal/oauthSpec.js +789 -0
  90. package/dist/internal/organizationName.d.ts +18 -0
  91. package/dist/internal/organizationName.d.ts.map +1 -0
  92. package/dist/internal/organizationName.js +24 -0
  93. package/dist/internal/protocol.d.ts +150 -3
  94. package/dist/internal/protocol.d.ts.map +1 -1
  95. package/dist/internal/protocol.js +163 -2
  96. package/dist/internal/providerApps.d.ts +75 -0
  97. package/dist/internal/providerApps.d.ts.map +1 -0
  98. package/dist/internal/providerApps.js +59 -0
  99. package/dist/internal/providerRegistry.d.ts +218 -0
  100. package/dist/internal/providerRegistry.d.ts.map +1 -0
  101. package/dist/internal/providerRegistry.js +432 -0
  102. package/dist/internal/providerSpec.d.ts +66 -0
  103. package/dist/internal/providerSpec.d.ts.map +1 -0
  104. package/dist/internal/providerSpec.js +1154 -0
  105. package/dist/internal/remoteMcp.d.ts +188 -0
  106. package/dist/internal/remoteMcp.d.ts.map +1 -0
  107. package/dist/internal/remoteMcp.js +543 -0
  108. package/dist/internal/signInProviders.d.ts +104 -0
  109. package/dist/internal/signInProviders.d.ts.map +1 -0
  110. package/dist/internal/signInProviders.js +245 -0
  111. package/dist/internal/tools.d.ts +115 -0
  112. package/dist/internal/tools.d.ts.map +1 -0
  113. package/dist/internal/tools.js +80 -0
  114. package/dist/manifest.d.ts +238 -0
  115. package/dist/manifest.d.ts.map +1 -0
  116. package/dist/manifest.js +30 -0
  117. package/dist/server/ai.d.ts +111 -0
  118. package/dist/server/ai.d.ts.map +1 -0
  119. package/dist/server/ai.js +145 -0
  120. package/dist/server/aiBinding.d.ts +68 -0
  121. package/dist/server/aiBinding.d.ts.map +1 -0
  122. package/dist/server/aiBinding.js +294 -0
  123. package/dist/server/attribution.d.ts +43 -0
  124. package/dist/server/attribution.d.ts.map +1 -0
  125. package/dist/server/attribution.js +75 -0
  126. package/dist/server/authorizeUrl.d.ts +43 -0
  127. package/dist/server/authorizeUrl.d.ts.map +1 -0
  128. package/dist/server/authorizeUrl.js +106 -0
  129. package/dist/server/connectUrl.d.ts +21 -0
  130. package/dist/server/connectUrl.d.ts.map +1 -0
  131. package/dist/server/connectUrl.js +37 -0
  132. package/dist/server/context.d.ts +68 -0
  133. package/dist/server/context.d.ts.map +1 -0
  134. package/dist/server/context.js +117 -0
  135. package/dist/server/emailBinding.d.ts +72 -0
  136. package/dist/server/emailBinding.d.ts.map +1 -0
  137. package/dist/server/emailBinding.js +124 -0
  138. package/dist/server/emit.d.ts +63 -0
  139. package/dist/server/emit.d.ts.map +1 -0
  140. package/dist/server/emit.js +101 -0
  141. package/dist/server/env.d.ts +44 -0
  142. package/dist/server/env.d.ts.map +1 -0
  143. package/dist/server/env.js +1 -0
  144. package/dist/server/everyApp.d.ts +65 -19
  145. package/dist/server/everyApp.d.ts.map +1 -1
  146. package/dist/server/everyApp.js +195 -30
  147. package/dist/server/fromMcpTools.d.ts +89 -0
  148. package/dist/server/fromMcpTools.d.ts.map +1 -0
  149. package/dist/server/fromMcpTools.js +192 -0
  150. package/dist/server/gatewayFetch.d.ts +54 -14
  151. package/dist/server/gatewayFetch.d.ts.map +1 -1
  152. package/dist/server/gatewayFetch.js +201 -37
  153. package/dist/server/gatewayUrl.d.ts +13 -0
  154. package/dist/server/gatewayUrl.d.ts.map +1 -0
  155. package/dist/server/gatewayUrl.js +34 -0
  156. package/dist/server/index.d.ts +23 -3
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +19 -2
  159. package/dist/server/integrationFetch.d.ts +162 -0
  160. package/dist/server/integrationFetch.d.ts.map +1 -0
  161. package/dist/server/integrationFetch.js +688 -0
  162. package/dist/server/listMembers.d.ts +32 -0
  163. package/dist/server/listMembers.d.ts.map +1 -0
  164. package/dist/server/listMembers.js +71 -0
  165. package/dist/server/nodeServer.d.ts +48 -0
  166. package/dist/server/nodeServer.d.ts.map +1 -0
  167. package/dist/server/nodeServer.js +45 -0
  168. package/dist/server/refusal.d.ts +55 -0
  169. package/dist/server/refusal.d.ts.map +1 -0
  170. package/dist/server/refusal.js +193 -0
  171. package/dist/server/sendEmail.d.ts +39 -0
  172. package/dist/server/sendEmail.d.ts.map +1 -0
  173. package/dist/server/sendEmail.js +114 -0
  174. package/dist/server/tools.d.ts +184 -0
  175. package/dist/server/tools.d.ts.map +1 -0
  176. package/dist/server/tools.js +347 -0
  177. package/dist/server/toolsEndpoint.d.ts +32 -0
  178. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  179. package/dist/server/toolsEndpoint.js +237 -0
  180. package/dist/tanstack-start/index.d.ts +9 -0
  181. package/dist/tanstack-start/index.d.ts.map +1 -0
  182. package/dist/tanstack-start/index.js +29 -0
  183. package/dist/testing/index.d.ts +93 -0
  184. package/dist/testing/index.d.ts.map +1 -0
  185. package/dist/testing/index.js +156 -0
  186. package/package.json +42 -8
  187. package/dist/server/mcp.d.ts +0 -66
  188. package/dist/server/mcp.d.ts.map +0 -1
  189. package/dist/server/mcp.js +0 -147
@@ -0,0 +1,432 @@
1
+ /**
2
+ * The provider registry: the built-in catalog (./capabilities.ts) and the
3
+ * providers one gateway registered as rows (`everyapp provider add`,
4
+ * `everyapp_provider_add`), resolved as one lookup
5
+ * (docs/design/2026-10-provider-registry.md). The catalog comes first and
6
+ * a row can never shadow it. Every row is parsed and checked again each
7
+ * time a registry is built, so a row written by hand into D1, or one that
8
+ * stopped passing the rules when a later catalog took its name, is left
9
+ * out and named in `rejected` rather than served.
10
+ *
11
+ * The rules a row must pass (the register's `registry.*` invariants):
12
+ *
13
+ * - `registry.builtin-precedence`: not a catalog id, and never `everyapp`;
14
+ * - `registry.row-secret-is-its-own`: each Gateway setting it names starts
15
+ * with its own id (`STRIPE_` for `stripe`) and is nobody else's: no
16
+ * install-owned, dev-only or reserved name, no settable Gateway setting
17
+ * (the catalog's keys and clients included), no other row's;
18
+ * - `registry.row-hosts-public-https`: each host is a public DNS name (not
19
+ * an IP literal, localhost, or a `.local`, `.internal` or `.arpa` name)
20
+ * and not one the gateway serves itself (`refuseHost`, which knows the
21
+ * gateway's own host and `APP_BASE_DOMAIN`).
22
+ *
23
+ * A second kind of row, a scope-set overlay (`provider_scope_sets`,
24
+ * `everyapp provider scope-set add`), adds a named scope set to a
25
+ * built-in OAuth client that has scope sets (Google, Microsoft, Slack):
26
+ * its scopes, and the routes on the client's own API hosts it unlocks,
27
+ * checked by the rules a built-in set is checked by
28
+ * (`providers.scope-set-overlay-within-client`). An overlay never replaces
29
+ * one of the client's own sets, never names a host outside the client's
30
+ * `api`, and never applies to a registered row (an operator replaces the
31
+ * row's spec instead). Apps list an overlay set under `scopes` as they
32
+ * list a built-in one.
33
+ */
34
+ import { CAPABILITIES, CAPABILITY_IDS, capabilitySettings, } from "./capabilities.js";
35
+ import { isDevOnlyGatewaySetting, isInstallOwnedGatewaySetting, isReservedAppSecretName, isSettableGatewaySetting, } from "./gatewaySettings.js";
36
+ import { parseScopeSetOverlay, withScopeSets } from "./oauthSpec.js";
37
+ import { isSignInSettingName } from "./signInProviders.js";
38
+ import { describeSpecProblems, parseProviderSpec, PROVIDER_ID, } from "./providerSpec.js";
39
+ /**
40
+ * The part of a row a key is set for: its hosts, its host setting and its
41
+ * auth, as one canonical string (sorted keys, sorted hosts), so that
42
+ * replacing a row's hosts or auth changes the key and the value it held
43
+ * is no longer read until the operator sets it again. For an OAuth
44
+ * provider the client is bound to its authorize, token and revoke URLs,
45
+ * its API hosts, where the client goes on the token request and its
46
+ * client setting names (`oauth.client-bound-to-endpoints`): an admin who
47
+ * re-points `tokenUrl` has to paste the secret again.
48
+ */
49
+ export function providerBindingKey(capability) {
50
+ if (capability.mode === "ai") {
51
+ // An `ai` row's key is bound to where the proxy sends it: its base URL and the key's placement.
52
+ return JSON.stringify({
53
+ kind: "ai",
54
+ auth: sortedAuth(capability.auth),
55
+ origin: capability.origin,
56
+ basePath: capability.basePath ?? null,
57
+ });
58
+ }
59
+ if (capability.mode === "oauth_client") {
60
+ return JSON.stringify({
61
+ kind: "oauth2-user",
62
+ authorizeUrl: capability.authorize.url,
63
+ tokenUrl: capability.flow?.tokenUrl ?? null,
64
+ revokeUrl: capability.flow?.revokeUrl ?? null,
65
+ inject: capability.token[0]?.inject ?? "form",
66
+ clientId: capability.secrets.clientId,
67
+ clientSecret: capability.secrets.clientSecret ?? null,
68
+ hosts: [...capability.api].sort(),
69
+ });
70
+ }
71
+ return JSON.stringify({
72
+ auth: sortedAuth(capability.auth),
73
+ hostSetting: capability.hostSetting ?? null,
74
+ hosts: [...capability.hosts].sort(),
75
+ });
76
+ }
77
+ /** An auth block with its keys sorted, one level down too, for a canonical key. */
78
+ function sortedAuth(auth) {
79
+ return Object.fromEntries(Object.entries(auth)
80
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
81
+ .map(([field, value]) => [
82
+ field,
83
+ typeof value === "object" && value !== null
84
+ ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
85
+ : value,
86
+ ]));
87
+ }
88
+ /** The reserved app id no provider may take. */
89
+ const RESERVED_IDS = new Set(["everyapp"]);
90
+ const IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
91
+ const LOCAL_SUFFIXES = [".localhost", ".local", ".internal", ".arpa"];
92
+ /** The prefix a provider's own Gateway settings carry: `stripe` → `STRIPE_`, `tiktok-business` → `TIKTOK_BUSINESS_`. */
93
+ export function providerSettingPrefix(id) {
94
+ return `${id.toUpperCase().replace(/-/g, "_")}_`;
95
+ }
96
+ /** Why `host` can't be a row's, by the pure rules alone; null when it can. */
97
+ export function publicHostProblem(host) {
98
+ if (IPV4.test(host))
99
+ return `${host} is an IP address, not a host name`;
100
+ if (host === "localhost" ||
101
+ LOCAL_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
102
+ return `${host} is not a public host`;
103
+ }
104
+ return null;
105
+ }
106
+ /** A bare lowercase host from a host or a URL; null when neither. */
107
+ function bareHost(value) {
108
+ const text = value?.trim().toLowerCase().replace(/\.$/, "");
109
+ if (!text)
110
+ return null;
111
+ if (!text.includes("/"))
112
+ return text;
113
+ try {
114
+ return new URL(text).hostname.toLowerCase();
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ /**
121
+ * Why a row can't send its key to `host` on this gateway: the gateway's
122
+ * own host, an app host under its base domain, or a host under its
123
+ * third-party domain. Null when none applies. The gateway applies it on
124
+ * every read (`refusedGatewayHost`, with its env) and the CLI at `provider
125
+ * add` (with the install's gateway host and base domain), in the same words.
126
+ */
127
+ export function gatewayHostRefusal(host, hosts) {
128
+ const own = bareHost(hosts.gatewayHost);
129
+ if (own && host === own)
130
+ return `${host} is this gateway's own host`;
131
+ const base = bareHost(hosts.appBaseDomain);
132
+ if (base && (host === base || host.endsWith(`.${base}`))) {
133
+ return `${host} is under this gateway's app domain ${base}`;
134
+ }
135
+ const third = bareHost(hosts.thirdPartyDomain);
136
+ if (third && (host === third || host.endsWith(`.${third}`))) {
137
+ return `${host} is under this gateway's third-party domain ${third}`;
138
+ }
139
+ return null;
140
+ }
141
+ /**
142
+ * Why a setting name can't be a row's: a name the Gateway, the catalog or
143
+ * another provider already reads, or one outside the row's own prefix.
144
+ * Null when the row may hold it.
145
+ */
146
+ export function rowSecretProblem(id, name, taken) {
147
+ const prefix = providerSettingPrefix(id);
148
+ if (!name.startsWith(prefix)) {
149
+ return `${name} doesn't start with ${prefix}: a provider's settings carry its own id`;
150
+ }
151
+ if (isInstallOwnedGatewaySetting(name)) {
152
+ return `${name} is one of the Gateway's own keys`;
153
+ }
154
+ if (isDevOnlyGatewaySetting(name)) {
155
+ return `${name} is a local-development setting`;
156
+ }
157
+ if (isReservedAppSecretName(name)) {
158
+ return `${name} is a name everyapp sets itself`;
159
+ }
160
+ if (isSignInSettingName(name)) {
161
+ return `${name} is a sign-in provider's setting name (every <ID>_SIGNIN_<FIELD> name is the Gateway's, set with \`everyapp signin add\`)`;
162
+ }
163
+ if (isSettableGatewaySetting(name)) {
164
+ return `${name} is a Gateway setting another provider or the Gateway reads`;
165
+ }
166
+ if (taken(name)) {
167
+ return `${name} is another registered provider's`;
168
+ }
169
+ return null;
170
+ }
171
+ /**
172
+ * Check one parsed row against the catalog and the other rows. Pure: the
173
+ * CLI runs it before writing D1, the MCP tool before writing, and the
174
+ * gateway again on every read.
175
+ */
176
+ export function checkProviderRow(id, capability, context) {
177
+ if (RESERVED_IDS.has(id) || CAPABILITY_IDS.includes(id)) {
178
+ return {
179
+ id,
180
+ code: "PROVIDER_BUILTIN",
181
+ message: `${id} is a built-in provider; a registered one can't take its id`,
182
+ };
183
+ }
184
+ for (const name of capabilitySettings(capability)) {
185
+ const problem = rowSecretProblem(id, name, (each) => context.rowSettings.has(each));
186
+ if (problem) {
187
+ return { id, code: "PROVIDER_SECRET_TAKEN", message: problem };
188
+ }
189
+ }
190
+ for (const host of registrableHosts(capability)) {
191
+ const problem = publicHostProblem(host) ?? context.refuseHost?.(host);
192
+ if (problem) {
193
+ return { id, code: "PROVIDER_HOST_REFUSED", message: problem };
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+ /**
199
+ * Every host a row sends a secret to: an API-key provider's `hosts`; an
200
+ * OAuth provider's API hosts (the token), and its authorize, token and
201
+ * revoke hosts (the client). Each passes the row host rules.
202
+ */
203
+ export function registrableHosts(capability) {
204
+ if (capability.mode === "api_key")
205
+ return [...capability.hosts];
206
+ if (capability.mode === "ai")
207
+ return [new URL(capability.origin).hostname.toLowerCase()];
208
+ const hosts = [...capability.api];
209
+ const urls = [
210
+ capability.authorize.url,
211
+ capability.flow?.tokenUrl,
212
+ capability.flow?.revokeUrl,
213
+ capability.flow?.identity?.url,
214
+ ];
215
+ for (const url of urls) {
216
+ if (!url)
217
+ continue;
218
+ try {
219
+ const host = new URL(url).hostname.toLowerCase();
220
+ if (!hosts.includes(host))
221
+ hosts.push(host);
222
+ }
223
+ catch {
224
+ // The parser refused it already.
225
+ }
226
+ }
227
+ return hosts;
228
+ }
229
+ /** A row's spec parsed and checked: the capability, or the refusal. */
230
+ export function resolveProviderRow(row, context) {
231
+ let spec = row.spec;
232
+ if (typeof spec === "string") {
233
+ try {
234
+ spec = JSON.parse(spec);
235
+ }
236
+ catch {
237
+ return {
238
+ ok: false,
239
+ refusal: {
240
+ id: row.id,
241
+ code: "PROVIDER_SPEC_INVALID",
242
+ message: "the stored spec isn't JSON",
243
+ },
244
+ };
245
+ }
246
+ }
247
+ const parsed = parseProviderSpec(spec);
248
+ if (!parsed.ok) {
249
+ return {
250
+ ok: false,
251
+ refusal: {
252
+ id: row.id,
253
+ code: "PROVIDER_SPEC_INVALID",
254
+ message: describeSpecProblems(parsed.problems),
255
+ },
256
+ };
257
+ }
258
+ if (parsed.id !== row.id || !PROVIDER_ID.test(row.id)) {
259
+ return {
260
+ ok: false,
261
+ refusal: {
262
+ id: row.id,
263
+ code: "PROVIDER_SPEC_INVALID",
264
+ message: `the row is ${row.id} but its spec says ${parsed.id}`,
265
+ },
266
+ };
267
+ }
268
+ const refusal = checkProviderRow(parsed.id, parsed.capability, context);
269
+ return refusal
270
+ ? { ok: false, refusal }
271
+ : { ok: true, capability: parsed.capability };
272
+ }
273
+ /**
274
+ * The built-in OAuth client an overlay may extend: a catalog entry of mode
275
+ * `oauth_client` with a scope policy. Null for anything else (an API-key
276
+ * entry, a client without sets, a registered row, an unknown id).
277
+ */
278
+ export function overlayableClient(providerId) {
279
+ if (!CAPABILITY_IDS.includes(providerId))
280
+ return null;
281
+ const capability = CAPABILITIES[providerId];
282
+ return capability.mode === "oauth_client" && capability.scopePolicy
283
+ ? capability
284
+ : null;
285
+ }
286
+ /** An overlay row's spec parsed and checked against its client: the set, or the refusal. */
287
+ export function resolveScopeSetRow(row) {
288
+ const refuse = (code, message) => ({
289
+ ok: false,
290
+ refusal: { providerId: row.providerId, name: row.name, code, message },
291
+ });
292
+ const client = overlayableClient(row.providerId);
293
+ if (!client) {
294
+ return refuse("SCOPE_SET_PROVIDER_UNKNOWN", `${row.providerId} isn't a built-in OAuth client with scope sets (google-workspace, microsoft-365, slack); a registered provider's sets are in its spec`);
295
+ }
296
+ if (Object.hasOwn(client.scopePolicy.sets, row.name)) {
297
+ return refuse("SCOPE_SET_BUILTIN", `${row.name} is one of ${row.providerId}'s own sets; an overlay adds a set, never replaces one`);
298
+ }
299
+ let spec = row.spec;
300
+ if (typeof spec === "string") {
301
+ try {
302
+ spec = JSON.parse(spec);
303
+ }
304
+ catch {
305
+ return refuse("SCOPE_SET_INVALID", "the stored spec isn't JSON");
306
+ }
307
+ }
308
+ const parsed = parseScopeSetOverlay(typeof spec === "object" && spec !== null && !Array.isArray(spec)
309
+ ? { ...spec, name: row.name }
310
+ : spec, { id: row.providerId, capability: client });
311
+ if (!parsed.ok) {
312
+ return refuse("SCOPE_SET_INVALID", describeSpecProblems(parsed.problems));
313
+ }
314
+ return { ok: true, set: parsed.set };
315
+ }
316
+ /**
317
+ * The catalog and `rows` as one registry. Rows are taken in order; a row
318
+ * whose settings another earlier row holds is the one refused.
319
+ */
320
+ export function providerRegistry(rows = [], options = {}) {
321
+ const catalog = new Map(CAPABILITY_IDS.map((id) => [id, CAPABILITIES[id]]));
322
+ // Scope-set overlays on the catalog's OAuth clients, before any row is
323
+ // looked at: a row never shadows the catalog, with or without overlays.
324
+ const overlays = [];
325
+ const rejectedScopeSets = [];
326
+ const overlaySets = new Map();
327
+ for (const row of options.scopeSets ?? []) {
328
+ if (overlays.some((each) => each.providerId === row.providerId && each.name === row.name)) {
329
+ rejectedScopeSets.push({
330
+ providerId: row.providerId,
331
+ name: row.name,
332
+ code: "SCOPE_SET_INVALID",
333
+ message: "listed twice",
334
+ });
335
+ continue;
336
+ }
337
+ const resolved = resolveScopeSetRow(row);
338
+ if (!resolved.ok) {
339
+ rejectedScopeSets.push(resolved.refusal);
340
+ continue;
341
+ }
342
+ overlays.push({
343
+ providerId: row.providerId,
344
+ name: row.name,
345
+ set: resolved.set,
346
+ addedBy: row.addedBy ?? null,
347
+ addedAt: row.addedAt ?? null,
348
+ });
349
+ const sets = overlaySets.get(row.providerId) ?? {};
350
+ sets[row.name] = resolved.set;
351
+ overlaySets.set(row.providerId, sets);
352
+ }
353
+ for (const [id, sets] of overlaySets) {
354
+ const client = catalog.get(id);
355
+ if (client?.mode === "oauth_client") {
356
+ catalog.set(id, withScopeSets(client, sets));
357
+ }
358
+ }
359
+ const accepted = new Map();
360
+ const rejected = [];
361
+ const rowSettings = new Set();
362
+ for (const row of rows) {
363
+ if (accepted.has(row.id)) {
364
+ rejected.push({
365
+ id: row.id,
366
+ code: "PROVIDER_SPEC_INVALID",
367
+ message: "listed twice",
368
+ });
369
+ continue;
370
+ }
371
+ const resolved = resolveProviderRow(row, {
372
+ rowSettings,
373
+ ...(options.refuseHost ? { refuseHost: options.refuseHost } : {}),
374
+ });
375
+ if (!resolved.ok) {
376
+ rejected.push(resolved.refusal);
377
+ continue;
378
+ }
379
+ accepted.set(row.id, {
380
+ id: row.id,
381
+ capability: resolved.capability,
382
+ bound: row.boundTo === providerBindingKey(resolved.capability),
383
+ addedBy: row.addedBy ?? null,
384
+ addedAt: row.addedAt ?? null,
385
+ });
386
+ for (const name of capabilitySettings(resolved.capability)) {
387
+ rowSettings.add(name);
388
+ }
389
+ }
390
+ const all = new Map([
391
+ ...catalog,
392
+ ...[...accepted.values()].map((row) => [row.id, row.capability]),
393
+ ]);
394
+ let names;
395
+ let unbound;
396
+ return {
397
+ get: (id) => all.get(id),
398
+ ids: () => [...all.keys()],
399
+ entries: () => [...all.entries()],
400
+ sourceOf: (id) => catalog.has(id) ? "catalog" : accepted.has(id) ? "row" : undefined,
401
+ settingNames: () => {
402
+ names ??= new Set([...all.values()].flatMap((capability) => capabilitySettings(capability)));
403
+ return names;
404
+ },
405
+ bound: (id) => catalog.has(id) || (accepted.get(id)?.bound ?? false),
406
+ unboundSettings: () => {
407
+ unbound ??= new Set([...accepted.values()]
408
+ .filter((row) => !row.bound)
409
+ .flatMap((row) => capabilitySettings(row.capability)));
410
+ return unbound;
411
+ },
412
+ rows: () => [...accepted.values()],
413
+ rejected,
414
+ scopeSets: () => [...overlays],
415
+ scopeSetSource: (providerId, name) => {
416
+ if (overlays.some((each) => each.providerId === providerId && each.name === name)) {
417
+ return "row";
418
+ }
419
+ const capability = CAPABILITY_IDS.includes(providerId)
420
+ ? CAPABILITIES[providerId]
421
+ : undefined;
422
+ return capability?.mode === "oauth_client" &&
423
+ capability.scopePolicy &&
424
+ Object.hasOwn(capability.scopePolicy.sets, name)
425
+ ? "catalog"
426
+ : undefined;
427
+ },
428
+ rejectedScopeSets,
429
+ };
430
+ }
431
+ /** The catalog alone, as a registry: what every surface reads without a gateway's rows. */
432
+ export const BUILTIN_PROVIDERS = providerRegistry();
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A provider spec: the shape `everyapp provider add` takes as JSON (and
3
+ * `everyapp_provider_add` as a tool argument), and the shape every
4
+ * built-in catalog entry has in TypeScript (./capabilities.ts), so a
5
+ * catalog entry is nothing a row couldn't be. `parseProviderSpec` turns
6
+ * JSON into an {@link ApiKeyCapability} or names every problem; nothing
7
+ * here reads a value: a spec names Gateway settings, never holds one.
8
+ *
9
+ * Three kinds parse: API-key providers (`auth.kind` bearer, header, query
10
+ * or basic), OAuth providers (`auth.kind` oauth2-user, the half in
11
+ * ./oauthSpec.ts: the Gateway runs the flow and holds the tokens,
12
+ * docs/design/2026-10-oauth-providers.md), and OpenAI-compatible model
13
+ * providers (`mode: "ai"`: a `baseUrl`, the key's placement, the models
14
+ * it serves and the default, its inference routes; the AI proxy serves a
15
+ * row exactly as it serves a catalog `ai` entry, inference POSTs alone,
16
+ * `store: false` on chat, the usage tap, so a new model vendor is a
17
+ * registered spec, not a catalog release).
18
+ *
19
+ * The rules that make a row safe on a particular gateway (its secret names
20
+ * are its own, its hosts are public, it shadows no catalog entry) are the
21
+ * registry's (./providerRegistry.ts), run after this parse.
22
+ */
23
+ import { type AiProviderCapability, type ApiKeyCapability, type OAuthClientCapability } from "./capabilities.js";
24
+ /** A provider id: lowercase, a letter first, 2 to 40 characters. */
25
+ export declare const PROVIDER_ID: RegExp;
26
+ /** A Gateway setting name a provider may hold: upper case, 3 to 64 characters. */
27
+ export declare const PROVIDER_SECRET_NAME: RegExp;
28
+ /** A bare lowercase host with at least one dot: labels of letters, digits and hyphens. */
29
+ export declare const PROVIDER_HOST: RegExp;
30
+ /** What an `ai` row serves by default: chat, and embeddings when it names an embeddings model. */
31
+ export declare const AI_SPEC_CHAT_ROUTE = "/v1/chat/completions";
32
+ export declare const AI_SPEC_EMBEDDINGS_ROUTE = "/v1/embeddings";
33
+ export interface ProviderSpecProblem {
34
+ /** Dotted: `auth.secret`, `hosts.1`, or `(root)`. */
35
+ path: string;
36
+ message: string;
37
+ }
38
+ /** What a row or a preset may be: an API key, an OAuth provider with a `flow`, or an OpenAI-compatible model provider. */
39
+ export type RegistrableCapability = ApiKeyCapability | OAuthClientCapability | AiProviderCapability;
40
+ export type ParsedProviderSpec = {
41
+ ok: true;
42
+ id: string;
43
+ capability: RegistrableCapability;
44
+ } | {
45
+ ok: false;
46
+ problems: ProviderSpecProblem[];
47
+ };
48
+ /**
49
+ * Parse a provider spec. Every problem is reported, with its path, so a
50
+ * person fixes the file once. On success the id and the catalog-shaped
51
+ * capability, defaults filled in (`access` read, `readMethods` GET and
52
+ * HEAD, no account-wide paths).
53
+ */
54
+ export declare function parseProviderSpec(input: unknown): ParsedProviderSpec;
55
+ /** The problems as one line: `auth.secret: must name a Gateway setting; hosts.0: …`. */
56
+ export declare function describeSpecProblems(problems: readonly ProviderSpecProblem[]): string;
57
+ /**
58
+ * The spec of an API-key capability as JSON (`provider show`, the
59
+ * catalog's round trip): the entry's fields in the spec's names, with the
60
+ * defaults the parser fills in spelled out, so `parseProviderSpec` of it
61
+ * gives the entry back.
62
+ */
63
+ export declare function providerSpecOf(id: string, capability: RegistrableCapability): Record<string, unknown>;
64
+ /** The methods an app with `access: "write"` may use on `capability` besides its read methods. */
65
+ export declare function writeMethodsOf(capability: RegistrableCapability): readonly string[];
66
+ //# sourceMappingURL=providerSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerSpec.d.ts","sourceRoot":"","sources":["../../src/internal/providerSpec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAM3B,MAAM,mBAAmB,CAAC;AAG3B,oEAAoE;AACpE,eAAO,MAAM,WAAW,QAA2B,CAAC;AACpD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAA2B,CAAC;AAC7D,0FAA0F;AAC1F,eAAO,MAAM,aAAa,QACoD,CAAC;AA4C/E,kGAAkG;AAClG,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AACzD,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AA+EzD,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,0HAA0H;AAC1H,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,qBAAqB,GACrB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,qBAAqB,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AA+iBnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CA2NpE;AAkOD,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuGzB;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAC5B,UAAU,EAAE,qBAAqB,GAChC,SAAS,MAAM,EAAE,CAKnB"}