@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
@@ -3,6 +3,10 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { parseEnv } from "node:util";
5
5
  import { DEV_ISSUER, DEV_ORG_ID, DEV_PUBLIC_KEY_PEM } from "../dev/keypair.js";
6
+ // The catalog's settings, generated from ../internal/capabilities.ts, which
7
+ // this file must not import (see ./index.ts: the Vite plugin hides whatever
8
+ // cloudflare.config.ts imports from the Worker).
9
+ import { DEV_GATEWAY_KEYS } from "./catalogSettings.generated.js";
6
10
  import { sentinelDeployTarget, } from "./sentinel.js";
7
11
  /** The names of the manifest's `secrets`, whichever form each took. */
8
12
  function manifestSecretNames(manifest) {
@@ -12,131 +16,6 @@ function manifestSecretNames(manifest) {
12
16
  function manifestProviderIds(manifest) {
13
17
  return (manifest.providers ?? []).map((entry) => typeof entry === "string" ? entry : entry.id);
14
18
  }
15
- /**
16
- * The gateway secrets holding the keys of the AI providers and capabilities
17
- * an app may ask for, with the settings a provider needs besides its key
18
- * (Workers AI's account, a Basic username held as a setting, a host
19
- * setting). With no gateway in development, the SDK reaches them with the
20
- * developer's own values under the same names. A copy of the catalog's
21
- * settings (`capabilitySettings()` over every entry of
22
- * ../internal/capabilities.ts), which the Worker loads and this file must
23
- * not import (see ./index.ts: the Vite plugin hides whatever
24
- * cloudflare.config.ts imports from the Worker). worker.test.ts keeps the
25
- * copy in step: a provider added to the catalog is added here.
26
- */
27
- const DEV_GATEWAY_KEYS = {
28
- providers: {
29
- openai: ["OPENAI_API_KEY"],
30
- openrouter: ["OPENROUTER_API_KEY"],
31
- "workers-ai": ["WORKERS_AI_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
32
- // Not Cloudflare AI Gateway (the proxy entry): dev goes to the
33
- // providers directly, so its settings are the Gateway's alone.
34
- // The AI providers of ../internal/catalog/inference.ts.
35
- anthropic: ["ANTHROPIC_API_KEY"],
36
- gemini: ["GEMINI_API_KEY"],
37
- mistral: ["MISTRAL_API_KEY"],
38
- groq: ["GROQ_API_KEY"],
39
- cohere: ["COHERE_API_KEY"],
40
- },
41
- capabilities: {
42
- dataforseo: ["DATAFORSEO_API_KEY"],
43
- context: ["CONTEXT_API_KEY"],
44
- // The sender, and the developer's own Email Sending account: set all
45
- // but the name and sendEmail() really sends; otherwise it prints.
46
- // Resend's key and EMAIL_PROVIDER pick the provider integrationStatus()
47
- // names, as the gateway would.
48
- // TODO(integration): the last two added by the email-providers lane
49
- // (this file is outside its ownership list).
50
- email: [
51
- "EMAIL_FROM",
52
- "EMAIL_FROM_NAME",
53
- "EMAIL_REST_API_TOKEN",
54
- "CLOUDFLARE_ACCOUNT_ID",
55
- "RESEND_API_KEY",
56
- "EMAIL_PROVIDER",
57
- ],
58
- // The organization's members: nothing to set; listMembers() answers
59
- // from the dev identity.
60
- members: [],
61
- // OAuth clients: the id, the secret, and the instance a provider with
62
- // no fixed host is on.
63
- linkedin: ["LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET"],
64
- facebook: ["FACEBOOK_APP_ID", "FACEBOOK_APP_SECRET"],
65
- instagram: ["INSTAGRAM_APP_ID", "INSTAGRAM_APP_SECRET"],
66
- threads: ["THREADS_APP_ID", "THREADS_APP_SECRET"],
67
- youtube: ["YOUTUBE_CLIENT_ID", "YOUTUBE_CLIENT_SECRET"],
68
- tiktok: ["TIKTOK_CLIENT_ID", "TIKTOK_CLIENT_SECRET"],
69
- "tiktok-business": [
70
- "TIKTOK_BUSINESS_CLIENT_ID",
71
- "TIKTOK_BUSINESS_CLIENT_SECRET",
72
- ],
73
- reddit: ["REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET"],
74
- pinterest: ["PINTEREST_CLIENT_ID", "PINTEREST_CLIENT_SECRET"],
75
- discord: ["DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET"],
76
- slack: ["SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET"],
77
- mastodon: ["MASTODON_CLIENT_ID", "MASTODON_CLIENT_SECRET", "MASTODON_URL"],
78
- kick: ["KICK_CLIENT_ID", "KICK_CLIENT_SECRET"],
79
- twitch: ["TWITCH_CLIENT_ID", "TWITCH_CLIENT_SECRET"],
80
- tumblr: ["TUMBLR_CLIENT_ID", "TUMBLR_CLIENT_SECRET"],
81
- dribbble: ["DRIBBBLE_CLIENT_ID", "DRIBBBLE_CLIENT_SECRET"],
82
- "google-workspace": [
83
- "GOOGLE_WORKSPACE_CLIENT_ID",
84
- "GOOGLE_WORKSPACE_CLIENT_SECRET",
85
- ],
86
- "microsoft-365": ["MICROSOFT_365_CLIENT_ID", "MICROSOFT_365_CLIENT_SECRET"],
87
- // The API-key providers of ../internal/catalog/*.ts, in catalog order:
88
- // the key, then a Basic username held as a setting, then the host
89
- // setting of a provider whose host is the operator's.
90
- stripe: ["STRIPE_API_KEY"],
91
- lemonsqueezy: ["LEMONSQUEEZY_API_KEY"],
92
- paddle: ["PADDLE_API_KEY"],
93
- shopify: ["SHOPIFY_API_TOKEN", "SHOPIFY_HOST"],
94
- plaid: ["PLAID_SECRET"],
95
- attio: ["ATTIO_API_KEY"],
96
- hubspot: ["HUBSPOT_API_TOKEN"],
97
- pipedrive: ["PIPEDRIVE_API_TOKEN"],
98
- airtable: ["AIRTABLE_API_TOKEN"],
99
- notion: ["NOTION_API_TOKEN"],
100
- postmark: ["POSTMARK_API_TOKEN"],
101
- sendgrid: ["SENDGRID_API_KEY"],
102
- twilio: ["TWILIO_API_SECRET", "TWILIO_API_KEY"],
103
- intercom: ["INTERCOM_API_TOKEN"],
104
- crisp: ["CRISP_API_KEY", "CRISP_API_IDENTIFIER"],
105
- linear: ["LINEAR_API_KEY"],
106
- github: ["GITHUB_API_TOKEN"],
107
- gitlab: ["GITLAB_API_TOKEN"],
108
- vercel: ["VERCEL_API_TOKEN"],
109
- cloudflare: ["CLOUDFLARE_PROVIDER_API_TOKEN"],
110
- posthog: ["POSTHOG_PERSONAL_API_KEY"],
111
- mixpanel: [
112
- "MIXPANEL_SERVICE_ACCOUNT_SECRET",
113
- "MIXPANEL_SERVICE_ACCOUNT_USERNAME",
114
- ],
115
- segment: ["SEGMENT_WRITE_KEY"],
116
- supabase: ["SUPABASE_SECRET_KEY", "SUPABASE_HOST"],
117
- turso: ["TURSO_API_TOKEN"],
118
- "turso-db": ["TURSO_DB_AUTH_TOKEN", "TURSO_DB_HOST"],
119
- neon: ["NEON_API_KEY"],
120
- pinecone: ["PINECONE_API_KEY", "PINECONE_HOST"],
121
- mapbox: ["MAPBOX_ACCESS_TOKEN"],
122
- openweather: ["OPENWEATHER_API_KEY"],
123
- voyage: ["VOYAGE_API_KEY"],
124
- perplexity: ["PERPLEXITY_API_KEY"],
125
- jina: ["JINA_API_KEY"],
126
- replicate: ["REPLICATE_API_TOKEN"],
127
- huggingface: ["HUGGINGFACE_API_TOKEN"],
128
- firecrawl: ["FIRECRAWL_API_KEY"],
129
- exa: ["EXA_API_KEY"],
130
- tavily: ["TAVILY_API_KEY"],
131
- serper: ["SERPER_API_KEY"],
132
- "brave-search": ["BRAVE_SEARCH_API_KEY"],
133
- apify: ["APIFY_API_TOKEN"],
134
- deepgram: ["DEEPGRAM_API_KEY"],
135
- elevenlabs: ["ELEVENLABS_API_KEY"],
136
- assemblyai: ["ASSEMBLYAI_API_KEY"],
137
- newsapi: ["NEWSAPI_API_KEY"],
138
- },
139
- };
140
19
  /**
141
20
  * Observability every Every App Worker ships with: logs (with invocation
142
21
  * logs), sampled traces and Workers Issues, with query strings redacted so
@@ -166,6 +45,19 @@ const BUILD_ENV = "EVERYAPP_BUILD";
166
45
  const RESERVED_SECRET_NAME = /^(GATEWAY_URL|CF_VERSION_METADATA|EVERYAPP_\w*|EVERY_APP_\w*|APP__\w*)$/i;
167
46
  const GATEWAY_WORKER_NAME = "every-app-gateway";
168
47
  const GATEWAY_ENTRYPOINT = "AppGateway";
48
+ /**
49
+ * Where `everyapp dev` serves its launcher, which it passes to the app's dev
50
+ * server in EVERYAPP_DEV_GATEWAY_URL; its default address otherwise. The
51
+ * deployed Worker gets the Gateway's URL from the deploy instead.
52
+ */
53
+ const DEV_GATEWAY_URL_ENV = "EVERYAPP_DEV_GATEWAY_URL";
54
+ const DEFAULT_DEV_GATEWAY_URL = "http://localhost:8787";
55
+ function devGatewayUrl() {
56
+ return {
57
+ type: "text",
58
+ value: process.env[DEV_GATEWAY_URL_ENV] || DEFAULT_DEV_GATEWAY_URL,
59
+ };
60
+ }
169
61
  /**
170
62
  * The manifest's `capabilities` as the SDK's local-dev twin of the gateway
171
63
  * reads them: with no gateway to hold an OAuth client's calls to the scope
@@ -177,7 +69,7 @@ function devCapabilities(manifest) {
177
69
  /**
178
70
  * The binding the dev Worker gets the specs of providers registered on a
179
71
  * gateway in (`DEV_PROVIDERS_ENV` in ../internal/devProviders.ts; a copy,
180
- * kept in step by a test, for the reason {@link DEV_GATEWAY_KEYS} is one).
72
+ * kept in step by a test, since this file may not import ../internal).
181
73
  */
182
74
  const DEV_PROVIDERS_ENV = "EVERYAPP_DEV_PROVIDERS";
183
75
  /**
@@ -291,6 +183,12 @@ export function everyAppWorker(options) {
291
183
  const { deploy, name } = appWorkerName(options, "everyAppWorker");
292
184
  const env = {
293
185
  EVERYAPP_APP_ID: { type: "text", value: manifest.id },
186
+ // The manifest itself, for a page that shows what the app asked for:
187
+ // the Worker can't import everyapp.config.ts (see ./index.ts).
188
+ EVERYAPP_MANIFEST: {
189
+ type: "json",
190
+ value: manifest,
191
+ },
294
192
  CF_VERSION_METADATA: { type: "version-metadata" },
295
193
  };
296
194
  if (deploy) {
@@ -317,6 +215,7 @@ export function everyAppWorker(options) {
317
215
  value: JSON.stringify([DEV_PUBLIC_KEY_PEM]),
318
216
  };
319
217
  env["EVERYAPP_IDENTITY_ISSUER"] = { type: "text", value: DEV_ISSUER };
218
+ env["GATEWAY_URL"] = devGatewayUrl();
320
219
  // The stub gateway's organization, as the deploy's secret would be, so
321
220
  // background work's withOrganization(env.EVERY_APP_ORG_ID, …) runs
322
221
  // here too. Not a pin: under the dev opt-in everyApp() enforces none,
@@ -436,6 +335,7 @@ export function everyAppAuxiliaryWorker(options) {
436
335
  else {
437
336
  env["EVERYAPP_DEV"] = { type: "text", value: "1" };
438
337
  env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
338
+ env["GATEWAY_URL"] = devGatewayUrl();
439
339
  env["EVERY_APP_ORG_ID"] = { type: "text", value: DEV_ORG_ID };
440
340
  addDevProviders(env, options.root);
441
341
  }
@@ -905,18 +805,25 @@ taken) {
905
805
  return { bindings, exports };
906
806
  }
907
807
  /**
908
- * Check the names of the manifest's `secrets`, and in dev declare the ones
909
- * set locally, with the developer's own keys for the manifest's providers
910
- * and capabilities.
808
+ * Check the names of the manifest's `secrets` and `settings`, and in dev
809
+ * declare the ones set locally (a setting that isn't, with its default),
810
+ * with the developer's own keys for the manifest's providers and
811
+ * capabilities.
911
812
  */
912
813
  function addSecrets(env, options, deploy) {
913
- const secrets = manifestSecretNames(options.manifest);
814
+ const settings = options.manifest.settings ?? [];
815
+ const secrets = [
816
+ ...manifestSecretNames(options.manifest),
817
+ ...settings.map((setting) => setting.name),
818
+ ];
819
+ const settingNames = new Set(settings.map((setting) => setting.name));
914
820
  for (const secret of secrets) {
821
+ const field = settingNames.has(secret) ? "settings" : "secrets";
915
822
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(secret)) {
916
- throw new Error(`secrets: "${secret}" isn't a valid binding name.`);
823
+ throw new Error(`${field}: "${secret}" isn't a valid binding name.`);
917
824
  }
918
825
  if (env[secret] || RESERVED_SECRET_NAME.test(secret)) {
919
- throw new Error(`secrets: ${secret} is a name the deploy sets or a binding it builds.`);
826
+ throw new Error(`${field}: ${secret} is a name the deploy sets or a binding it builds.`);
920
827
  }
921
828
  }
922
829
  if (!deploy) {
@@ -933,6 +840,12 @@ function addSecrets(env, options, deploy) {
933
840
  for (const secret of locallySetSecrets(options.root ?? process.cwd(), options.ctx.mode ?? "development", [...names].filter((name) => !env[name]))) {
934
841
  env[secret] = { type: "secret" };
935
842
  }
843
+ // A setting nobody set locally reads its default, as a deployed one does.
844
+ for (const setting of settings) {
845
+ if (!env[setting.name] && setting.default !== undefined) {
846
+ env[setting.name] = { type: "text", value: setting.default };
847
+ }
848
+ }
936
849
  }
937
850
  }
938
851
  /** The {@link DEV_GATEWAY_KEYS} of the manifest's providers and capabilities. */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @every-app/sdk — what `everyapp.config.ts` imports: `defineEveryApp` and
3
+ * the manifest's types. A leaf, like the config itself (see ./manifest.ts).
4
+ */
5
+ export { defineEveryApp, type EveryAppCapabilityEntry, type EveryAppClassResource, type EveryAppManifest, type EveryAppPublicRoute, type EveryAppQueueConsumer, type EveryAppResources, type EveryAppSecretEntry, type EveryAppSettingEntry, type EveryAppSettingNeed, type EveryAppWorkerLimits, } from "./manifest.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,cAAc,EACd,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @every-app/sdk — what `everyapp.config.ts` imports: `defineEveryApp` and
3
+ * the manifest's types. A leaf, like the config itself (see ./manifest.ts).
4
+ */
5
+ export { defineEveryApp, } from "./manifest.js";
@@ -15,6 +15,11 @@
15
15
  * and fronts whichever provider the default resolves to when
16
16
  * `AI_GATEWAY_ID` is set (./capabilities.ts, `aig`).
17
17
  *
18
+ * The models resolve the same way: the operator's model setting for the
19
+ * provider (`everyapp ai model <provider> --chat … --embeddings …`,
20
+ * `org_settings.ai_models`), else the catalog entry's seed (`chat.seedModel`,
21
+ * `embeddings.seedModel`). Nothing reads a seed except through here.
22
+ *
18
23
  * Everything here is pure: what is set is asked of the caller (`isSet`),
19
24
  * which reads the Gateway's env, the Worker's secret names, or `.dev.vars`.
20
25
  */
@@ -38,19 +43,54 @@ export declare const AI_DEFAULT_ROUTES: {
38
43
  readonly embeddings: "/v1/embeddings";
39
44
  };
40
45
  export type AiDefaultRoute = keyof typeof AI_DEFAULT_ROUTES;
41
- /** What the operator stored: a provider, and the model to fill in when the app names none. */
46
+ /**
47
+ * A choice as typed (`everyapp ai default openrouter/anthropic/claude-sonnet-5.5`,
48
+ * the Usage page's dialog, `everyapp_ai_default`): the provider, and the
49
+ * chat model to store for it. The provider goes to `org_settings.ai_default`,
50
+ * the model to that provider's `chat` in `org_settings.ai_models`.
51
+ */
42
52
  export interface AiDefaultChoice {
43
53
  provider: string;
44
54
  model?: string;
45
55
  }
56
+ /** The operator's models for one provider; a field left out serves the seed. */
57
+ export interface AiModelSetting {
58
+ chat?: string;
59
+ embeddings?: string;
60
+ }
61
+ /** The operator's models by provider id (`org_settings.ai_models`, JSON). */
62
+ export type AiModelSettings = Readonly<Record<string, AiModelSetting>>;
63
+ /** A change to one provider's models: a name sets it, null puts the seed back, absent leaves it. */
64
+ export interface AiModelPatch {
65
+ chat?: string | null;
66
+ embeddings?: string | null;
67
+ }
68
+ /** Whether `value` can be a model id: 1 to 200 characters, no surrounding or control whitespace. */
69
+ export declare function isAiModelName(value: string): boolean;
46
70
  /**
47
- * `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as a choice:
71
+ * `org_settings.ai_models` as stored: a JSON object of provider id →
72
+ * `{ chat?, embeddings? }`. Anything else (null, malformed JSON, a field
73
+ * that isn't a model name) reads as no setting, so the seed serves.
74
+ */
75
+ export declare function parseAiModelSettings(value: string | null | undefined): AiModelSettings;
76
+ /** The stored text of the settings, providers sorted; null when nothing is set. */
77
+ export declare function formatAiModelSettings(settings: AiModelSettings): string | null;
78
+ /** `settings` with one provider's models changed by `patch`. */
79
+ export declare function withAiModelSetting(settings: AiModelSettings, provider: string, patch: AiModelPatch): AiModelSettings;
80
+ /**
81
+ * `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as typed:
48
82
  * the provider id up to the first slash, the model after it (a model id
49
83
  * may hold slashes of its own). Null for blank or malformed text.
50
84
  */
51
85
  export declare function parseAiDefaultChoice(value: string | null | undefined): AiDefaultChoice | null;
52
- /** The stored text of a choice: `provider` or `provider/model`. */
86
+ /** The text of a choice: `provider` or `provider/model`. */
53
87
  export declare function formatAiDefaultChoice(choice: AiDefaultChoice): string;
88
+ /**
89
+ * The provider id `org_settings.ai_default` holds, or null. The column
90
+ * holds the provider alone (its models are in `ai_models`); text after a
91
+ * first slash is not read.
92
+ */
93
+ export declare function parseAiDefaultProvider(value: string | null | undefined): string | null;
54
94
  /** Whether `name` holds a value, as the Gateway's env or a secret list says. */
55
95
  export type IsSet = (name: string) => boolean;
56
96
  /** An `ai` entry the default may resolve to: not the proxy, with a chat route. */
@@ -77,8 +117,15 @@ export interface AiDefaultCandidate {
77
117
  /** The Gateway settings it needs; what to set when it isn't configured. */
78
118
  settings: string[];
79
119
  configured: boolean;
80
- /** The model the Gateway fills in when the app names none. */
120
+ /** The chat model the Gateway fills in when the app names none: the operator's setting, else the seed. */
81
121
  model: string;
122
+ /** The embeddings model, the same way; absent when the entry has no embeddings route. */
123
+ embeddingsModel?: string;
124
+ /** The catalog's seeds: what serves where the operator set no model. */
125
+ seed: {
126
+ chat: string;
127
+ embeddings?: string;
128
+ };
82
129
  /** Through Cloudflare AI Gateway, as things are set now. */
83
130
  viaAiGateway: boolean;
84
131
  embeddings: boolean;
@@ -94,8 +141,15 @@ export type AiDefaultSource =
94
141
  export interface AiDefaultResolution {
95
142
  provider: string;
96
143
  label: string;
97
- /** The model the default serves: the choice's, else the entry's. */
144
+ /** The chat model the default serves: the operator's setting for the provider, else the seed. */
98
145
  model: string;
146
+ /** The embeddings model, the same way; absent when the provider has no embeddings route. */
147
+ embeddingsModel?: string;
148
+ /** The provider's seeds. */
149
+ seed: {
150
+ chat: string;
151
+ embeddings?: string;
152
+ };
99
153
  source: AiDefaultSource;
100
154
  /** Every setting the provider needs is set, so the default serves. */
101
155
  configured: boolean;
@@ -118,9 +172,15 @@ export interface AiDefaultResolution {
118
172
  * then any other `ai` entry of `lookup` with a chat route (a registered
119
173
  * row, one day), each with its state.
120
174
  */
121
- export declare function aiDefaultCandidates(isSet: IsSet, lookup?: CapabilityLookup): AiDefaultCandidate[];
175
+ export declare function aiDefaultCandidates(isSet: IsSet, lookup?: CapabilityLookup, models?: AiModelSettings): AiDefaultCandidate[];
122
176
  /** Why `choice.provider` can't be the default, or null when it can. */
123
177
  export declare function aiDefaultChoiceProblem(choice: AiDefaultChoice, lookup?: CapabilityLookup): string | null;
178
+ /**
179
+ * Why `patch` can't be stored for `provider`, or null when it can: the
180
+ * provider must be one the default can be, a model name must be a model
181
+ * name, and an embeddings model needs the entry's embeddings route.
182
+ */
183
+ export declare function aiModelPatchProblem(provider: string, patch: AiModelPatch, lookup?: CapabilityLookup): string | null;
124
184
  /**
125
185
  * The Gateway's effective default, from the stored choice (or none) and
126
186
  * what is set. A choice that names a provider which isn't configured
@@ -129,7 +189,10 @@ export declare function aiDefaultChoiceProblem(choice: AiDefaultChoice, lookup?:
129
189
  * `problem` and the order decides.
130
190
  */
131
191
  export declare function resolveAiDefault(input: {
132
- choice: AiDefaultChoice | null;
192
+ /** The stored provider (`org_settings.ai_default`), or null for the shipped order. */
193
+ provider: string | null;
194
+ /** The operator's models by provider (`org_settings.ai_models`); none: the seeds serve. */
195
+ models?: AiModelSettings;
133
196
  isSet: IsSet;
134
197
  lookup?: CapabilityLookup;
135
198
  }): AiDefaultResolution;
@@ -1 +1 @@
1
- {"version":3,"file":"aiDefault.d.ts","sourceRoot":"","sources":["../../src/internal/aiDefault.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiDefault.d.ts","sourceRoot":"","sources":["../../src/internal/aiDefault.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,11 @@
15
15
  * and fronts whichever provider the default resolves to when
16
16
  * `AI_GATEWAY_ID` is set (./capabilities.ts, `aig`).
17
17
  *
18
+ * The models resolve the same way: the operator's model setting for the
19
+ * provider (`everyapp ai model <provider> --chat … --embeddings …`,
20
+ * `org_settings.ai_models`), else the catalog entry's seed (`chat.seedModel`,
21
+ * `embeddings.seedModel`). Nothing reads a seed except through here.
22
+ *
18
23
  * Everything here is pure: what is set is asked of the caller (`isSet`),
19
24
  * which reads the Gateway's env, the Worker's secret names, or `.dev.vars`.
20
25
  */
@@ -48,10 +53,85 @@ export const AI_DEFAULT_ROUTES = {
48
53
  chat: "/v1/chat/completions",
49
54
  embeddings: "/v1/embeddings",
50
55
  };
51
- /** A stored choice must name a provider id; the model is free text after the first slash. */
56
+ /** Whether `value` can be a model id: 1 to 200 characters, no surrounding or control whitespace. */
57
+ export function isAiModelName(value) {
58
+ return (value.length >= 1 &&
59
+ value.length <= 200 &&
60
+ value.trim() === value &&
61
+ !/[\u0000-\u001f\u007f]/.test(value));
62
+ }
63
+ /**
64
+ * `org_settings.ai_models` as stored: a JSON object of provider id →
65
+ * `{ chat?, embeddings? }`. Anything else (null, malformed JSON, a field
66
+ * that isn't a model name) reads as no setting, so the seed serves.
67
+ */
68
+ export function parseAiModelSettings(value) {
69
+ if (typeof value !== "string" || value.trim() === "")
70
+ return {};
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(value);
74
+ }
75
+ catch {
76
+ return {};
77
+ }
78
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
79
+ return {};
80
+ const settings = {};
81
+ for (const [provider, entry] of Object.entries(parsed)) {
82
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
83
+ continue;
84
+ const setting = {};
85
+ for (const kind of ["chat", "embeddings"]) {
86
+ const model = entry[kind];
87
+ if (typeof model === "string" && isAiModelName(model)) {
88
+ setting[kind] = model;
89
+ }
90
+ }
91
+ if (setting.chat !== undefined || setting.embeddings !== undefined) {
92
+ settings[provider] = setting;
93
+ }
94
+ }
95
+ return settings;
96
+ }
97
+ /** The stored text of the settings, providers sorted; null when nothing is set. */
98
+ export function formatAiModelSettings(settings) {
99
+ const providers = Object.keys(settings)
100
+ .filter((id) => settings[id]?.chat !== undefined ||
101
+ settings[id]?.embeddings !== undefined)
102
+ .sort();
103
+ if (providers.length === 0)
104
+ return null;
105
+ return JSON.stringify(Object.fromEntries(providers.map((id) => {
106
+ const { chat, embeddings } = settings[id];
107
+ return [
108
+ id,
109
+ {
110
+ ...(chat === undefined ? {} : { chat }),
111
+ ...(embeddings === undefined ? {} : { embeddings }),
112
+ },
113
+ ];
114
+ })));
115
+ }
116
+ /** `settings` with one provider's models changed by `patch`. */
117
+ export function withAiModelSetting(settings, provider, patch) {
118
+ const next = { ...settings[provider] };
119
+ for (const kind of ["chat", "embeddings"]) {
120
+ const value = patch[kind];
121
+ if (value === null)
122
+ delete next[kind];
123
+ else if (value !== undefined)
124
+ next[kind] = value;
125
+ }
126
+ const rest = Object.fromEntries(Object.entries(settings).filter(([id]) => id !== provider));
127
+ return next.chat === undefined && next.embeddings === undefined
128
+ ? rest
129
+ : { ...rest, [provider]: next };
130
+ }
131
+ /** A choice must name a provider id; the model is free text after the first slash. */
52
132
  const CHOICE = /^([a-z][a-z0-9-]{1,39})(?:\/(.{1,200}))?$/;
53
133
  /**
54
- * `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as a choice:
134
+ * `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as typed:
55
135
  * the provider id up to the first slash, the model after it (a model id
56
136
  * may hold slashes of its own). Null for blank or malformed text.
57
137
  */
@@ -64,10 +144,18 @@ export function parseAiDefaultChoice(value) {
64
144
  const model = match[2]?.trim();
65
145
  return model ? { provider: match[1], model } : { provider: match[1] };
66
146
  }
67
- /** The stored text of a choice: `provider` or `provider/model`. */
147
+ /** The text of a choice: `provider` or `provider/model`. */
68
148
  export function formatAiDefaultChoice(choice) {
69
149
  return choice.model ? `${choice.provider}/${choice.model}` : choice.provider;
70
150
  }
151
+ /**
152
+ * The provider id `org_settings.ai_default` holds, or null. The column
153
+ * holds the provider alone (its models are in `ai_models`); text after a
154
+ * first slash is not read.
155
+ */
156
+ export function parseAiDefaultProvider(value) {
157
+ return parseAiDefaultChoice(value)?.provider ?? null;
158
+ }
71
159
  export function canBeAiDefault(capability) {
72
160
  return (capability?.mode === "ai" &&
73
161
  capability.kind !== "proxy" &&
@@ -96,13 +184,28 @@ export function aiGatewayConfigured(isSet) {
96
184
  export function viaAiGateway(capability, isSet) {
97
185
  return capability.aig !== undefined && aiGatewayConfigured(isSet);
98
186
  }
99
- function candidateOf(id, capability, isSet) {
187
+ /** The provider's models: the operator's setting, else the seed. */
188
+ function modelsOf(id, capability, models) {
189
+ const setting = models[id];
190
+ const { chat, embeddings } = capability;
191
+ return {
192
+ model: setting?.chat ?? chat.seedModel,
193
+ ...(embeddings
194
+ ? { embeddingsModel: setting?.embeddings ?? embeddings.seedModel }
195
+ : {}),
196
+ seed: {
197
+ chat: chat.seedModel,
198
+ ...(embeddings ? { embeddings: embeddings.seedModel } : {}),
199
+ },
200
+ };
201
+ }
202
+ function candidateOf(id, capability, isSet, models) {
100
203
  return {
101
204
  id,
102
205
  label: capability.label,
103
206
  settings: aiRequiredSettings(capability),
104
207
  configured: aiProviderConfigured(capability, isSet),
105
- model: capability.chat.model,
208
+ ...modelsOf(id, capability, models),
106
209
  viaAiGateway: viaAiGateway(capability, isSet),
107
210
  embeddings: capability.embeddings !== undefined,
108
211
  };
@@ -112,15 +215,18 @@ function candidateOf(id, capability, isSet) {
112
215
  * then any other `ai` entry of `lookup` with a chat route (a registered
113
216
  * row, one day), each with its state.
114
217
  */
115
- export function aiDefaultCandidates(isSet, lookup = CATALOG) {
218
+ export function aiDefaultCandidates(isSet, lookup = CATALOG, models = {}) {
116
219
  const ids = [
117
220
  ...AI_DEFAULT_ORDER,
118
221
  ...lookup.ids().filter((id) => !AI_DEFAULT_ORDER.includes(id)),
119
222
  ];
120
223
  return ids.flatMap((id) => {
121
224
  const capability = lookup.get(id);
225
+ // A registered row whose key isn't bound to the hosts it has now reads
226
+ // as unset everywhere (`providers.value-bound-to-hosts`).
227
+ const set = lookup.bound && !lookup.bound(id) ? () => false : isSet;
122
228
  return canBeAiDefault(capability)
123
- ? [candidateOf(id, capability, isSet)]
229
+ ? [candidateOf(id, capability, set, models)]
124
230
  : [];
125
231
  });
126
232
  }
@@ -140,6 +246,29 @@ export function aiDefaultChoiceProblem(choice, lookup = CATALOG) {
140
246
  }
141
247
  return null;
142
248
  }
249
+ /**
250
+ * Why `patch` can't be stored for `provider`, or null when it can: the
251
+ * provider must be one the default can be, a model name must be a model
252
+ * name, and an embeddings model needs the entry's embeddings route.
253
+ */
254
+ export function aiModelPatchProblem(provider, patch, lookup = CATALOG) {
255
+ const why = aiDefaultChoiceProblem({ provider }, lookup);
256
+ if (why)
257
+ return why;
258
+ const capability = lookup.get(provider);
259
+ if (!canBeAiDefault(capability))
260
+ return `${provider} can't serve the default`;
261
+ for (const kind of ["chat", "embeddings"]) {
262
+ const value = patch[kind];
263
+ if (typeof value === "string" && !isAiModelName(value)) {
264
+ return `${JSON.stringify(value)} isn't a model id: 1 to 200 characters, no surrounding spaces`;
265
+ }
266
+ }
267
+ if (typeof patch.embeddings === "string" && !capability.embeddings) {
268
+ return `${capability.label} has no embeddings route, so it serves no embeddings model`;
269
+ }
270
+ return null;
271
+ }
143
272
  /**
144
273
  * The Gateway's effective default, from the stored choice (or none) and
145
274
  * what is set. A choice that names a provider which isn't configured
@@ -149,12 +278,13 @@ export function aiDefaultChoiceProblem(choice, lookup = CATALOG) {
149
278
  */
150
279
  export function resolveAiDefault(input) {
151
280
  const lookup = input.lookup ?? CATALOG;
281
+ const models = input.models ?? {};
152
282
  const { isSet } = input;
153
- const candidates = aiDefaultCandidates(isSet, lookup);
154
- const resolve = (id, capability, source, model, problem) => ({
283
+ const candidates = aiDefaultCandidates(isSet, lookup, models);
284
+ const resolve = (id, capability, source, problem) => ({
155
285
  provider: id,
156
286
  label: capability.label,
157
- model: model ?? capability.chat.model,
287
+ ...modelsOf(id, capability, models),
158
288
  source,
159
289
  configured: aiProviderConfigured(capability, isSet),
160
290
  settings: aiRequiredSettings(capability),
@@ -168,11 +298,11 @@ export function resolveAiDefault(input) {
168
298
  ...(problem ? { problem } : {}),
169
299
  });
170
300
  let problem;
171
- if (input.choice) {
172
- const capability = lookup.get(input.choice.provider);
173
- const why = aiDefaultChoiceProblem(input.choice, lookup);
301
+ if (input.provider) {
302
+ const capability = lookup.get(input.provider);
303
+ const why = aiDefaultChoiceProblem({ provider: input.provider }, lookup);
174
304
  if (!why && canBeAiDefault(capability)) {
175
- return resolve(input.choice.provider, capability, "setting", input.choice.model);
305
+ return resolve(input.provider, capability, "setting");
176
306
  }
177
307
  problem = why ?? undefined;
178
308
  }
@@ -184,7 +314,7 @@ export function resolveAiDefault(input) {
184
314
  const capability = lookup.get(first.id);
185
315
  if (!canBeAiDefault(capability))
186
316
  throw new Error("unreachable");
187
- return resolve(first.id, capability, configured ? "order" : "none", undefined, problem);
317
+ return resolve(first.id, capability, configured ? "order" : "none", problem);
188
318
  }
189
319
  /**
190
320
  * The sentence setup shows for an unconfigured default: the one key to
@@ -32,7 +32,9 @@ export type DevAiProvider = AiProviderId;
32
32
  /** The fallback's view of one `ai` catalog entry. */
33
33
  export declare function devAiProviderSpec(capability: AiProviderCapability): DevAiProviderSpec;
34
34
  export declare const DEV_AI_PROVIDERS: Readonly<Record<DevAiProvider, DevAiProviderSpec>>;
35
- export declare function devAiProvider(name: string): DevAiProviderSpec | undefined;
35
+ export declare function devAiProvider(name: string,
36
+ /** The dev Worker's bindings: a registered `ai` spec under `.everyapp/providers/<id>.json` resolves from them. */
37
+ values?: Readonly<Record<string, unknown>>): DevAiProviderSpec | undefined;
36
38
  /** Every setting a provider needs, its key first: the names `.dev.vars` and the gateway use. */
37
39
  export declare function aiProviderSettings(spec: DevAiProviderSpec): string[];
38
40
  /**