@every-app/sdk 0.3.0-rc.4 → 0.3.0-rc.6

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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cloudflare/catalogSettings.generated.d.ts +80 -0
  3. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  4. package/dist/cloudflare/catalogSettings.generated.js +172 -0
  5. package/dist/cloudflare/index.d.ts +1 -0
  6. package/dist/cloudflare/index.d.ts.map +1 -1
  7. package/dist/cloudflare/index.js +3 -0
  8. package/dist/cloudflare/worker.d.ts +29 -98
  9. package/dist/cloudflare/worker.d.ts.map +1 -1
  10. package/dist/cloudflare/worker.js +45 -132
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +5 -0
  14. package/dist/internal/aiDefault.d.ts +70 -7
  15. package/dist/internal/aiDefault.d.ts.map +1 -1
  16. package/dist/internal/aiDefault.js +145 -15
  17. package/dist/internal/aiProviders.d.ts +3 -1
  18. package/dist/internal/aiProviders.d.ts.map +1 -1
  19. package/dist/internal/aiProviders.js +12 -3
  20. package/dist/internal/appSettings.d.ts +65 -5
  21. package/dist/internal/appSettings.d.ts.map +1 -1
  22. package/dist/internal/appSettings.js +141 -23
  23. package/dist/internal/capabilities.d.ts +52 -433
  24. package/dist/internal/capabilities.d.ts.map +1 -1
  25. package/dist/internal/capabilities.js +25 -558
  26. package/dist/internal/catalog/index.d.ts +6 -6
  27. package/dist/internal/catalog/inference.d.ts +6 -6
  28. package/dist/internal/catalog/inference.d.ts.map +1 -1
  29. package/dist/internal/catalog/inference.js +9 -6
  30. package/dist/internal/events.d.ts.map +1 -1
  31. package/dist/internal/events.js +1 -0
  32. package/dist/internal/gatewaySettings.d.ts +1 -1
  33. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  34. package/dist/internal/gatewaySettings.js +1 -3
  35. package/dist/internal/identity.d.ts +12 -1
  36. package/dist/internal/identity.d.ts.map +1 -1
  37. package/dist/internal/identity.js +11 -1
  38. package/dist/internal/oauthClient.d.ts +10 -12
  39. package/dist/internal/oauthClient.d.ts.map +1 -1
  40. package/dist/internal/oauthClient.js +20 -81
  41. package/dist/internal/oauthPresets.d.ts +7 -3
  42. package/dist/internal/oauthPresets.d.ts.map +1 -1
  43. package/dist/internal/oauthPresets.js +533 -3
  44. package/dist/internal/oauthSpec.d.ts +44 -1
  45. package/dist/internal/oauthSpec.d.ts.map +1 -1
  46. package/dist/internal/oauthSpec.js +147 -34
  47. package/dist/internal/protocol.d.ts +14 -0
  48. package/dist/internal/protocol.d.ts.map +1 -1
  49. package/dist/internal/protocol.js +7 -0
  50. package/dist/internal/providerRegistry.d.ts +61 -1
  51. package/dist/internal/providerRegistry.d.ts.map +1 -1
  52. package/dist/internal/providerRegistry.js +127 -6
  53. package/dist/internal/providerSpec.d.ts +14 -7
  54. package/dist/internal/providerSpec.d.ts.map +1 -1
  55. package/dist/internal/providerSpec.js +306 -7
  56. package/dist/manifest.d.ts +226 -0
  57. package/dist/manifest.d.ts.map +1 -0
  58. package/dist/manifest.js +30 -0
  59. package/dist/server/ai.d.ts +66 -0
  60. package/dist/server/ai.d.ts.map +1 -1
  61. package/dist/server/ai.js +113 -8
  62. package/dist/server/aiBinding.d.ts +68 -0
  63. package/dist/server/aiBinding.d.ts.map +1 -0
  64. package/dist/server/aiBinding.js +294 -0
  65. package/dist/server/authorizeUrl.d.ts +1 -1
  66. package/dist/server/authorizeUrl.d.ts.map +1 -1
  67. package/dist/server/authorizeUrl.js +2 -6
  68. package/dist/server/emailBinding.d.ts +72 -0
  69. package/dist/server/emailBinding.d.ts.map +1 -0
  70. package/dist/server/emailBinding.js +124 -0
  71. package/dist/server/emit.js +1 -1
  72. package/dist/server/env.d.ts +41 -0
  73. package/dist/server/env.d.ts.map +1 -0
  74. package/dist/server/env.js +1 -0
  75. package/dist/server/gatewayFetch.d.ts +17 -13
  76. package/dist/server/gatewayFetch.d.ts.map +1 -1
  77. package/dist/server/gatewayFetch.js +24 -14
  78. package/dist/server/gatewayUrl.d.ts +13 -0
  79. package/dist/server/gatewayUrl.d.ts.map +1 -0
  80. package/dist/server/gatewayUrl.js +34 -0
  81. package/dist/server/index.d.ts +10 -3
  82. package/dist/server/index.d.ts.map +1 -1
  83. package/dist/server/index.js +8 -2
  84. package/dist/server/integrationFetch.d.ts +16 -32
  85. package/dist/server/integrationFetch.d.ts.map +1 -1
  86. package/dist/server/integrationFetch.js +47 -65
  87. package/dist/server/listMembers.js +1 -1
  88. package/dist/server/refusal.d.ts +55 -0
  89. package/dist/server/refusal.d.ts.map +1 -0
  90. package/dist/server/refusal.js +193 -0
  91. package/dist/server/sendEmail.js +1 -1
  92. package/dist/tanstack-start/index.d.ts +9 -0
  93. package/dist/tanstack-start/index.d.ts.map +1 -0
  94. package/dist/tanstack-start/index.js +29 -0
  95. package/dist/testing/index.d.ts +93 -0
  96. package/dist/testing/index.d.ts.map +1 -0
  97. package/dist/testing/index.js +156 -0
  98. package/package.json +34 -12
@@ -1,29 +1,7 @@
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, oauthClientOrigin, oauthReadMethods, oauthWriteMethods, providerSettingPrefix, requestedScopes, requestedTokenScopes, resolveDevProvider, rewriteTokenRequestScope, scopeExcess, tokenEndpointFor, tokenGrantType, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
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";
2
2
  import { applyAttribution, attributionFor, } from "./attribution.js";
3
3
  import { BINDING_ORIGIN, streamingRequest, } from "./gatewayFetch.js";
4
- /**
5
- * A `fetch` for one of the gateway's capabilities. Call the provider's real
6
- * URLs (`https://api.dataforseo.com/v3/…`); this sends them over the
7
- * AppGateway binding, which checks the app's manifest lists the capability,
8
- * injects the key the gateway holds and forwards the call. The app never
9
- * sees the key. A refusal the gateway makes itself comes back as a response
10
- * {@link gatewayRefusal} recognizes; anything else is the provider's answer.
11
- *
12
- * For an OAuth client (`linkedin`, `mastodon`, ...) it reaches the
13
- * provider's token endpoints only, and the gateway puts its client on the
14
- * request: send the code (or refresh token) without `client_id` or
15
- * `client_secret`. Build the authorize URL with {@link authorizeUrl} from
16
- * {@link integrationStatus}, and call the provider's API with the user's
17
- * token directly.
18
- *
19
- * In local dev only, with no binding, the developer's own key or client
20
- * from `.dev.vars` reaches the provider directly, under the gateway's names:
21
- * a catalog provider's from the catalog, a provider registered on the
22
- * gateway from its spec in `.everyapp/providers/<id>.json` (`everyapp
23
- * provider show <id> --save` writes it), put on the call as the gateway's
24
- * spec says.
25
- */
26
- export function integrationFetch({ env, provider, for: explicitTarget, account, }) {
4
+ export function integrationFetch({ env, provider, for: explicitTarget, account, optional = false, }) {
27
5
  const catalogEntry = fetchableCapability(provider);
28
6
  const what = `integrationFetch("${provider}")`;
29
7
  // Checked now, so a call nobody's fails where the fetch is made; and
@@ -49,6 +27,8 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
49
27
  // endpoint call without the client throws when it is made.
50
28
  const decidesLater = integration?.mode === "oauth_client" && integration.flow !== undefined;
51
29
  if (!binding && !local && !devToken && integration && !decidesLater) {
30
+ if (optional)
31
+ return null;
52
32
  const names = [
53
33
  ...capabilitySettings(integration),
54
34
  ...(integration.mode === "oauth_client" && integration.flow
@@ -81,7 +61,7 @@ export function integrationFetch({ env, provider, for: explicitTarget, account,
81
61
  const toApi = integration.flow !== undefined &&
82
62
  upstream.protocol === "https:" &&
83
63
  integration.api.includes(upstream.host) &&
84
- !tokenEndpointFor(integration, upstream, binding && integration.hostSetting ? undefined : values);
64
+ !tokenEndpointFor(integration, upstream);
85
65
  if (toApi) {
86
66
  if (binding) {
87
67
  if (account)
@@ -240,12 +220,7 @@ function localCredentials(integration, values) {
240
220
  * with the developer's client put on it by the gateway's own rule.
241
221
  */
242
222
  async function oauthClientCall({ provider, capability, outbound, upstream, headers, values, binding, target, client, }) {
243
- // With the binding the gateway resolves the provider's origin (Mastodon's
244
- // instance) and refuses any other, so any origin stands in for it here.
245
- const settings = binding && capability.hostSetting
246
- ? { [capability.hostSetting]: upstream.origin }
247
- : values;
248
- const endpoint = tokenEndpointFor(capability, upstream, settings);
223
+ const endpoint = tokenEndpointFor(capability, upstream);
249
224
  const method = outbound.method.toUpperCase();
250
225
  if (!endpoint || !endpoint.methods.includes(method)) {
251
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.`);
@@ -477,6 +452,9 @@ function devRegisteredProvider(provider, values) {
477
452
  const resolved = resolveDevProvider(values, provider);
478
453
  if (!resolved.found)
479
454
  throw new ConfigurationError(resolved.problem);
455
+ if (resolved.capability.mode === "ai") {
456
+ throw new ConfigurationError(`${provider} is an AI provider: list it under providers and call it with createGatewayFetch(), not integrationFetch().`);
457
+ }
480
458
  return resolved.capability;
481
459
  }
482
460
  /**
@@ -506,6 +484,44 @@ function stringValue(value) {
506
484
  * carries its public `clientId` for the authorize URL.
507
485
  */
508
486
  export async function integrationStatus({ env, provider, for: explicitTarget, }) {
487
+ const status = await readIntegrationStatus({
488
+ env,
489
+ provider,
490
+ ...(explicitTarget === undefined ? {} : { for: explicitTarget }),
491
+ });
492
+ return withSetup(status, String(provider), env);
493
+ }
494
+ /**
495
+ * `status` with its setup fields: the Gateway's as it sent them, or in
496
+ * local development (no binding) the `.dev.vars` names a status that
497
+ * isn't configured lacks.
498
+ */
499
+ function withSetup(status, provider, values) {
500
+ const binding = values["EVERY_APP_GATEWAY"];
501
+ if (binding || !status.requested || status.configured) {
502
+ return {
503
+ ...status,
504
+ settings: Array.isArray(status.settings) ? status.settings : [],
505
+ setup: typeof status.setup === "string" ? status.setup : null,
506
+ command: typeof status.command === "string" ? status.command : null,
507
+ };
508
+ }
509
+ const integration = CATALOG.get(provider) ?? devRegisteredProvider(provider, values);
510
+ const settings = provider === "email"
511
+ ? ["EMAIL_FROM"]
512
+ : integration
513
+ ? [...capabilitySettings(integration)]
514
+ : [];
515
+ return {
516
+ ...status,
517
+ settings,
518
+ 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.`,
521
+ command: null,
522
+ };
523
+ }
524
+ async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
509
525
  const target = attributionFor(env, explicitTarget, `integrationStatus("${String(provider)}")`);
510
526
  const values = env;
511
527
  const binding = values["EVERY_APP_GATEWAY"];
@@ -585,7 +601,6 @@ function devOAuthClientStatus(id, capability, values) {
585
601
  const client = values["EVERYAPP_DEV"]
586
602
  ? oauthClientCredentials(capability, values)
587
603
  : null;
588
- const origin = oauthClientOrigin(capability, values);
589
604
  const policy = capability.scopePolicy;
590
605
  // An entry with a Gateway-owned flow is listed for reading or writing;
591
606
  // the app-driven flow alone needs the write grant.
@@ -630,7 +645,6 @@ function devOAuthClientStatus(id, capability, values) {
630
645
  configured: Boolean(client),
631
646
  ready: Boolean(client) || Boolean(devToken),
632
647
  ...(client ? { clientId: client.clientId } : {}),
633
- ...(client && origin ? { origin } : {}),
634
648
  ...((client || devToken) && grant && asked
635
649
  ? {
636
650
  scopeSets: grant.sets,
@@ -672,35 +686,3 @@ function devEmailStatus(values) {
672
686
  provider: configured && choice.ok ? choice.provider : null,
673
687
  };
674
688
  }
675
- /**
676
- * A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
677
- * when the gateway won't send (`email_sender_not_allowed`,
678
- * `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
679
- */
680
- export class GatewayRefusalError extends Error {
681
- status;
682
- code;
683
- constructor({ status, code, message }) {
684
- super(message);
685
- this.name = "GatewayRefusalError";
686
- this.status = status;
687
- this.code = code;
688
- }
689
- }
690
- /**
691
- * The gateway's own refusal in `response`, or null when the response is the
692
- * provider's answer (which may be an error of its own). Reads the body only
693
- * for a refusal, so call it before reading the body yourself.
694
- */
695
- export async function gatewayRefusal(response) {
696
- const code = response.headers.get(GATEWAY_ERROR_HEADER);
697
- if (!code)
698
- return null;
699
- const body = await response.json().catch(() => null);
700
- const message = body &&
701
- typeof body === "object" &&
702
- typeof body.message === "string"
703
- ? body.message
704
- : `The EveryApp gateway refused the call (${code}).`;
705
- return { status: response.status, code, message };
706
- }
@@ -2,7 +2,7 @@ import { ConfigurationError, serviceRoutePath, } from "../internal/index.js";
2
2
  import { applyAttribution, attributionFor, isRequest, } from "./attribution.js";
3
3
  import { getEveryAppUser } from "./everyApp.js";
4
4
  import { BINDING_ORIGIN } from "./gatewayFetch.js";
5
- import { GatewayRefusalError, gatewayRefusal } from "./integrationFetch.js";
5
+ import { GatewayRefusalError, gatewayRefusal } from "./refusal.js";
6
6
  /**
7
7
  * The organization's members who can open the app now, through the
8
8
  * gateway's `members` capability: people deactivated, removed from the
@@ -0,0 +1,55 @@
1
+ /** What would make it work, as the Gateway words it. */
2
+ export interface SetupInfo {
3
+ /** The Gateway settings it needs (`["DATAFORSEO_API_KEY"]`); `[]` when ready or not a setting. */
4
+ settings: readonly string[];
5
+ /** One sentence for a person, naming what to set and how; null when ready. */
6
+ setup: string | null;
7
+ /** The command an admin runs (`pnpm everyapp configure crm`), or null. */
8
+ command: string | null;
9
+ }
10
+ /** A refusal the AppGateway binding made itself, not the provider. */
11
+ export interface GatewayRefusal extends SetupInfo {
12
+ status: number;
13
+ /** e.g. `capability_not_requested`, `integration_not_configured`. */
14
+ code: string;
15
+ message: string;
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
+ /**
20
+ * A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
21
+ * when the gateway won't send (`email_sender_not_allowed`,
22
+ * `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
23
+ */
24
+ export declare class GatewayRefusalError extends Error implements GatewayRefusal {
25
+ readonly status: number;
26
+ readonly code: string;
27
+ readonly settings: readonly string[];
28
+ readonly setup: string | null;
29
+ readonly command: string | null;
30
+ constructor({ status, code, message, settings, setup, command, }: Pick<GatewayRefusal, "status" | "code" | "message"> & Partial<SetupInfo>);
31
+ }
32
+ /**
33
+ * The gateway's own refusal in `response`, or null when the response is the
34
+ * provider's answer (which may be an error of its own). Reads the body only
35
+ * for a refusal, so call it before reading the body yourself.
36
+ */
37
+ export declare function gatewayRefusal(response: Response): Promise<GatewayRefusal | null>;
38
+ /**
39
+ * The Gateway's refusal behind a thrown error, or null when it isn't one:
40
+ * a {@link GatewayRefusalError} (`sendEmail()`, `listMembers()`, `emit()`),
41
+ * an OpenAI SDK `APIError` or an ai-sdk `APICallError` for a call the
42
+ * Gateway refused (also inside ai-sdk's `RetryError` or an error's
43
+ * `cause`), or the SDK's local-development `ConfigurationError` for a
44
+ * missing key, which answers as the deployed refusal would
45
+ * (`ai_provider_not_configured`, status 503, the `.dev.vars` names).
46
+ */
47
+ 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
+ //# sourceMappingURL=refusal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refusal.d.ts","sourceRoot":"","sources":["../../src/server/refusal.ts"],"names":[],"mappings":"AAgBA,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACxB,kGAAkG;IAClG,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAGpD,CAAC;AAEF;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAM,YAAW,cAAc;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEpB,EACV,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAAa,EACb,KAAY,EACZ,OAAc,GACf,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;CAS5E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAKhC;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAWtE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The Gateway's own refusals, read the same way wherever they surface: a
3
+ * `Response` from a gateway fetch ({@link gatewayRefusal}), the error an
4
+ * SDK helper throws ({@link GatewayRefusalError}), or the error an AI
5
+ * client throws for a refused call (an OpenAI SDK's `APIError`, ai-sdk's
6
+ * `APICallError`) ({@link refusalFromError}).
7
+ *
8
+ * A refusal that means "not set up" (`integration_not_configured`,
9
+ * `ai_provider_not_configured`, `ai_default_not_configured`) carries what
10
+ * to do in the Gateway's words: `settings` (the setting names it needs),
11
+ * `setup` (one sentence for a person) and `command` (what an admin runs,
12
+ * `pnpm everyapp configure <app>`, or null). Show them as they are; never
13
+ * match the message text or hardcode a setting name or a command.
14
+ */
15
+ import { ConfigurationError, GATEWAY_ERROR_HEADER } from "../internal/index.js";
16
+ /** The refusal codes that mean no model is set up for the call. */
17
+ export const AI_NOT_CONFIGURED_CODES = [
18
+ "ai_provider_not_configured",
19
+ "ai_default_not_configured",
20
+ ];
21
+ /**
22
+ * A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
23
+ * when the gateway won't send (`email_sender_not_allowed`,
24
+ * `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
25
+ */
26
+ export class GatewayRefusalError extends Error {
27
+ status;
28
+ code;
29
+ settings;
30
+ setup;
31
+ command;
32
+ constructor({ status, code, message, settings = [], setup = null, command = null, }) {
33
+ super(message);
34
+ this.name = "GatewayRefusalError";
35
+ this.status = status;
36
+ this.code = code;
37
+ this.settings = settings;
38
+ this.setup = setup;
39
+ this.command = command;
40
+ }
41
+ }
42
+ /**
43
+ * The gateway's own refusal in `response`, or null when the response is the
44
+ * provider's answer (which may be an error of its own). Reads the body only
45
+ * for a refusal, so call it before reading the body yourself.
46
+ */
47
+ export async function gatewayRefusal(response) {
48
+ const code = response.headers.get(GATEWAY_ERROR_HEADER);
49
+ if (!code)
50
+ return null;
51
+ const body = await response.json().catch(() => null);
52
+ return refusalOf(response.status, code, body);
53
+ }
54
+ /**
55
+ * The Gateway's refusal behind a thrown error, or null when it isn't one:
56
+ * a {@link GatewayRefusalError} (`sendEmail()`, `listMembers()`, `emit()`),
57
+ * an OpenAI SDK `APIError` or an ai-sdk `APICallError` for a call the
58
+ * Gateway refused (also inside ai-sdk's `RetryError` or an error's
59
+ * `cause`), or the SDK's local-development `ConfigurationError` for a
60
+ * missing key, which answers as the deployed refusal would
61
+ * (`ai_provider_not_configured`, status 503, the `.dev.vars` names).
62
+ */
63
+ export function refusalFromError(error) {
64
+ const seen = new Set();
65
+ let current = error;
66
+ while (current && typeof current === "object" && !seen.has(current)) {
67
+ seen.add(current);
68
+ const found = refusalHere(current);
69
+ if (found)
70
+ return found;
71
+ const record = current;
72
+ current = record["lastError"] ?? record["cause"];
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Whether `error` (or a refusal already read) means no model is set up:
78
+ * `ai_provider_not_configured` or `ai_default_not_configured`, from the
79
+ * Gateway or, in local development, from the SDK with no key in
80
+ * `.dev.vars`. Show `refusalFromError(error).setup` to the person.
81
+ */
82
+ export function isAiNotConfigured(error) {
83
+ const refusal = isRefusal(error) ? error : refusalFromError(error);
84
+ return refusal !== null && AI_NOT_CONFIGURED_CODES.includes(refusal.code);
85
+ }
86
+ function isRefusal(value) {
87
+ return (!!value &&
88
+ typeof value === "object" &&
89
+ !(value instanceof Error) &&
90
+ typeof value.code === "string" &&
91
+ typeof value.status === "number");
92
+ }
93
+ function refusalHere(error) {
94
+ // A Response is read with gatewayRefusal(), which can read its body.
95
+ if (error instanceof Response)
96
+ return null;
97
+ if (error instanceof GatewayRefusalError) {
98
+ return {
99
+ status: error.status,
100
+ code: error.code,
101
+ message: error.message,
102
+ settings: error.settings,
103
+ setup: error.setup,
104
+ command: error.command,
105
+ };
106
+ }
107
+ if (error instanceof ConfigurationError) {
108
+ if (!error.code)
109
+ return null;
110
+ return {
111
+ status: 503,
112
+ code: error.code,
113
+ message: error.message,
114
+ settings: error.settings,
115
+ setup: error.message,
116
+ command: null,
117
+ };
118
+ }
119
+ const record = error;
120
+ // ai-sdk's APICallError: lowercase header names, the body as text.
121
+ const responseHeaders = record["responseHeaders"];
122
+ if (responseHeaders && typeof responseHeaders === "object") {
123
+ const code = headerOf(responseHeaders, GATEWAY_ERROR_HEADER);
124
+ if (!code)
125
+ return null;
126
+ return refusalOf(numberOf(record["statusCode"]), code, parseJson(record["responseBody"]), messageOf(error));
127
+ }
128
+ // An OpenAI SDK APIError: `headers` (Headers or a record), `status`,
129
+ // and `error`, the body's `error` object (which carries the same fields).
130
+ const headers = record["headers"];
131
+ if (headers && typeof headers === "object" && "status" in record) {
132
+ const code = headerOf(headers, GATEWAY_ERROR_HEADER);
133
+ if (!code)
134
+ return null;
135
+ return refusalOf(numberOf(record["status"]), code, record["error"], messageOf(error));
136
+ }
137
+ return null;
138
+ }
139
+ /** A refusal from its status, code and body (the Gateway's `{ code, message, … }`, or OpenAI's `{ error: {…} }`). */
140
+ function refusalOf(status, code, body, fallback) {
141
+ const top = recordOf(body);
142
+ const inner = recordOf(top?.["error"]);
143
+ const field = (name) => top?.[name] ?? inner?.[name];
144
+ const message = stringOf(top?.["message"]) ??
145
+ stringOf(inner?.["message"]) ??
146
+ fallback ??
147
+ `The EveryApp gateway refused the call (${code}).`;
148
+ const settings = field("settings");
149
+ return {
150
+ status,
151
+ code,
152
+ message,
153
+ settings: Array.isArray(settings)
154
+ ? settings.filter((name) => typeof name === "string")
155
+ : [],
156
+ setup: stringOf(field("setup")) ?? null,
157
+ command: stringOf(field("command")) ?? null,
158
+ };
159
+ }
160
+ function headerOf(headers, name) {
161
+ if (typeof headers.get === "function") {
162
+ return headers.get(name);
163
+ }
164
+ for (const [key, value] of Object.entries(headers)) {
165
+ if (key.toLowerCase() === name && typeof value === "string")
166
+ return value;
167
+ }
168
+ return null;
169
+ }
170
+ function recordOf(value) {
171
+ return value && typeof value === "object" && !Array.isArray(value)
172
+ ? value
173
+ : null;
174
+ }
175
+ function stringOf(value) {
176
+ return typeof value === "string" && value ? value : undefined;
177
+ }
178
+ function numberOf(value) {
179
+ return typeof value === "number" ? value : 0;
180
+ }
181
+ function parseJson(value) {
182
+ if (typeof value !== "string")
183
+ return null;
184
+ try {
185
+ return JSON.parse(value);
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ function messageOf(error) {
192
+ return error instanceof Error && error.message ? error.message : undefined;
193
+ }
@@ -1,7 +1,7 @@
1
1
  import { ConfigurationError, EMAIL_SENDERS_ENV, emailDomain, emailProviderRefusal, emailRestBody, emailRestUrl, emailSenderRefusal, parseEmailList, parseEmailSender, parseSendEmailMessage, readEmailRestResponse, senderAllowed, serviceRoutePath, } from "../internal/index.js";
2
2
  import { applyAttribution, attributionFor, } from "./attribution.js";
3
3
  import { BINDING_ORIGIN } from "./gatewayFetch.js";
4
- import { GatewayRefusalError, gatewayRefusal } from "./integrationFetch.js";
4
+ import { GatewayRefusalError, gatewayRefusal } from "./refusal.js";
5
5
  /**
6
6
  * Send an email through the gateway's `email` capability: its sender
7
7
  * (EMAIL_FROM) unless `message.from` names an address the installation's
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Puts the person the Gateway signed in on `context.user` (an
3
+ * `EveryAppUser`), verified from the identity the Gateway sent; throws the
4
+ * 401 Response when there is none (a public route, anonymous).
5
+ */
6
+ export declare const ensureUserMiddleware: import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
7
+ user: import("../internal/protocol.js").EveryAppUser;
8
+ }, undefined, undefined, undefined>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tanstack-start/index.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;mCAK/B,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @every-app/sdk/tanstack-start — identity for a TanStack Start app's server
3
+ * functions:
4
+ *
5
+ * import { ensureUserMiddleware } from "@every-app/sdk/tanstack-start";
6
+ *
7
+ * export const getTodos = createServerFn()
8
+ * .middleware([ensureUserMiddleware])
9
+ * .handler(({ context: { user } }) => listTodos(user.id));
10
+ *
11
+ * The SDK lists `@tanstack/react-start` as a peer dependency, so the Start
12
+ * plugin compiles this module like the app's own: the `.server()` part never
13
+ * reaches the browser.
14
+ */
15
+ import { createMiddleware } from "@tanstack/react-start";
16
+ import { getRequest } from "@tanstack/react-start/server";
17
+ import { env } from "cloudflare:workers";
18
+ import { requireEveryAppUser } from "../server/everyApp.js";
19
+ /**
20
+ * Puts the person the Gateway signed in on `context.user` (an
21
+ * `EveryAppUser`), verified from the identity the Gateway sent; throws the
22
+ * 401 Response when there is none (a public route, anonymous).
23
+ */
24
+ export const ensureUserMiddleware = createMiddleware({
25
+ type: "function",
26
+ }).server(async ({ next }) => {
27
+ const user = await requireEveryAppUser(getRequest(), env);
28
+ return next({ context: { user } });
29
+ });
@@ -0,0 +1,93 @@
1
+ import { IDENTITY_HEADER, type IdentityChannel, type ToolCallResultV1, type ToolsCallResponseV1, type ToolsListResponseV1 } from "../internal/index.js";
2
+ import type { ToolSet } from "../server/tools.js";
3
+ export { IDENTITY_HEADER };
4
+ export type { ToolCallResultV1 as ToolCallResult, ToolsCallResponseV1 as ToolCallResponse, ToolsListResponseV1 as ToolsListResponse, };
5
+ /** The person a test signs in; each field left out takes {@link TEST_USER}'s. */
6
+ export interface TestUser {
7
+ id?: string;
8
+ email?: string;
9
+ orgId?: string;
10
+ /** `owner`, `admin` or `member`, as the Gateway signs it. */
11
+ orgRole?: string;
12
+ }
13
+ /** The person a test signs in by default: the owner of `org_test`. */
14
+ export declare const TEST_USER: Readonly<Required<TestUser>>;
15
+ /** The app id {@link testEnv} uses when none is given. */
16
+ export declare const TEST_APP_ID = "test-app";
17
+ /** What an identity is minted for, beside the person. */
18
+ export interface TestIdentityOptions {
19
+ user?: TestUser;
20
+ /** The app the identity is for (the token's audience). Default: {@link TEST_APP_ID}. */
21
+ appId?: string;
22
+ /** Default `["*"]`: a browser session, which holds every scope. */
23
+ scopes?: string[];
24
+ /** Default `"web"`. */
25
+ channel?: IdentityChannel;
26
+ }
27
+ /**
28
+ * The Worker env `everyApp()` needs to trust test identities: the app id,
29
+ * the dev issuer and key, `EVERYAPP_DEV`, and a `GATEWAY_URL`. `overrides`
30
+ * adds the app's own bindings (a fake `DB`, a secret) or replaces any of
31
+ * these.
32
+ */
33
+ export declare function testEnv<T extends object = Record<never, never>>(overrides?: {
34
+ appId?: string;
35
+ } & T): {
36
+ EVERYAPP_APP_ID: string;
37
+ EVERYAPP_DEV: string;
38
+ EVERYAPP_IDENTITY_ISSUER: string;
39
+ EVERYAPP_IDENTITY_PUBLIC_KEYS: string;
40
+ GATEWAY_URL: string;
41
+ } & Omit<T, "appId">;
42
+ /**
43
+ * An `ExecutionContext` for calling a Worker's `fetch` in a test:
44
+ * `waitUntil` keeps each promise in `waited`, so a test can await them.
45
+ */
46
+ export declare function testContext(): ExecutionContext & {
47
+ waited: Promise<unknown>[];
48
+ };
49
+ /** An identity token for `user`, as the Gateway sends in the identity header. */
50
+ export declare function mintTestIdentity(options?: TestIdentityOptions & {
51
+ toolCall?: {
52
+ id: string;
53
+ name: string;
54
+ };
55
+ }): Promise<string>;
56
+ /**
57
+ * A request as the Gateway forwards it: `user` signed in (the default
58
+ * person when left out), or nobody with `user: null`, as on a public route.
59
+ */
60
+ export declare function testRequest(input: string | URL, init?: RequestInit & Omit<TestIdentityOptions, "user"> & {
61
+ user?: TestUser | null;
62
+ }): Promise<Request>;
63
+ /** A Worker as `everyApp()` returns it. */
64
+ export interface TestWorker {
65
+ fetch(request: Request, env: never, ctx: ExecutionContext): Response | Promise<Response>;
66
+ }
67
+ export interface CallToolOptions {
68
+ user?: TestUser;
69
+ /**
70
+ * The caller's scopes. Default: `mcp:read`, `mcp:write` and
71
+ * `mcp:destructive`, so every tool may be called; pass fewer to test a
72
+ * refusal.
73
+ */
74
+ scopes?: string[];
75
+ /** Default: {@link testEnv}() for the worker's app. */
76
+ env?: object;
77
+ ctx?: ExecutionContext;
78
+ }
79
+ /**
80
+ * Call one tool as the Gateway does for an AI client: a `chan: "mcp"`
81
+ * identity minted for this call, through the app's tools endpoint, so the
82
+ * SDK checks the scope, validates input and output and maps errors as it
83
+ * does deployed. `app` is the Worker `everyApp()` returned, or the
84
+ * `defineTools()` set alone. The answer is `{ result }`, or `{ error }`
85
+ * when the SDK refused the call.
86
+ */
87
+ export declare function callTool(app: TestWorker | ToolSet, name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<ToolsCallResponseV1>;
88
+ /** The tool catalog, read as the Gateway reads it when the app deploys. */
89
+ export declare function listTools(app: TestWorker | ToolSet, options?: {
90
+ env?: object;
91
+ ctx?: ExecutionContext;
92
+ }): Promise<ToolsListResponseV1>;
93
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}