@every-app/sdk 0.3.0-rc.1 → 0.3.0-rc.3
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.
- package/LICENSE +21 -0
- package/dist/cloudflare/index.d.ts +2 -2
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/sentinel.d.ts +39 -1
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +22 -1
- package/dist/cloudflare/worker.d.ts +205 -7
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +174 -11
- package/dist/internal/aiDefault.d.ts +141 -0
- package/dist/internal/aiDefault.d.ts.map +1 -0
- package/dist/internal/aiDefault.js +204 -0
- package/dist/internal/aiProviders.d.ts +4 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +5 -1
- package/dist/internal/appSettings.d.ts +20 -2
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +53 -7
- package/dist/internal/audit.d.ts +108 -0
- package/dist/internal/audit.d.ts.map +1 -0
- package/dist/internal/audit.js +235 -0
- package/dist/internal/capabilities.d.ts +2315 -141
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +122 -0
- package/dist/internal/catalog/commerce.d.ts +394 -0
- package/dist/internal/catalog/commerce.d.ts.map +1 -0
- package/dist/internal/catalog/commerce.js +477 -0
- package/dist/internal/catalog/comms.d.ts +338 -0
- package/dist/internal/catalog/comms.d.ts.map +1 -0
- package/dist/internal/catalog/comms.js +477 -0
- package/dist/internal/catalog/index.d.ts +1936 -0
- package/dist/internal/catalog/index.d.ts.map +1 -0
- package/dist/internal/catalog/index.js +19 -0
- package/dist/internal/catalog/inference.d.ts +393 -0
- package/dist/internal/catalog/inference.d.ts.map +1 -0
- package/dist/internal/catalog/inference.js +457 -0
- package/dist/internal/catalog/infra.d.ts +423 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -0
- package/dist/internal/catalog/infra.js +543 -0
- package/dist/internal/catalog/search.d.ts +400 -0
- package/dist/internal/catalog/search.d.ts.map +1 -0
- package/dist/internal/catalog/search.js +463 -0
- package/dist/internal/devProviders.d.ts +2 -2
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +19 -1
- package/dist/internal/events.d.ts +13 -2
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +21 -3
- package/dist/internal/gatewaySettings.d.ts +2 -1
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +10 -2
- package/dist/internal/index.d.ts +5 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +5 -0
- package/dist/internal/oauthPresets.d.ts +29 -0
- package/dist/internal/oauthPresets.d.ts.map +1 -0
- package/dist/internal/oauthPresets.js +534 -0
- package/dist/internal/oauthScopes.d.ts +56 -1
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +98 -4
- package/dist/internal/oauthSpec.d.ts +59 -0
- package/dist/internal/oauthSpec.d.ts.map +1 -0
- package/dist/internal/oauthSpec.js +676 -0
- package/dist/internal/protocol.d.ts +11 -0
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +9 -0
- package/dist/internal/providerRegistry.d.ts +17 -6
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +51 -2
- package/dist/internal/providerSpec.d.ts +11 -7
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +142 -33
- package/dist/internal/signInProviders.d.ts +104 -0
- package/dist/internal/signInProviders.d.ts.map +1 -0
- package/dist/internal/signInProviders.js +245 -0
- package/dist/server/ai.d.ts +45 -0
- package/dist/server/ai.d.ts.map +1 -0
- package/dist/server/ai.js +40 -0
- package/dist/server/connectUrl.d.ts +21 -0
- package/dist/server/connectUrl.d.ts.map +1 -0
- package/dist/server/connectUrl.js +37 -0
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +72 -8
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/integrationFetch.d.ts +39 -2
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +177 -13
- package/package.json +12 -12
|
@@ -3,7 +3,7 @@ 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_PUBLIC_KEY_PEM } from "../dev/keypair.js";
|
|
6
|
-
import { sentinelDeployTarget } from "./sentinel.js";
|
|
6
|
+
import { sentinelDeployTarget, } from "./sentinel.js";
|
|
7
7
|
/** The names of the manifest's `secrets`, whichever form each took. */
|
|
8
8
|
function manifestSecretNames(manifest) {
|
|
9
9
|
return (manifest.secrets ?? []).map((entry) => typeof entry === "string" ? entry : entry.name);
|
|
@@ -15,17 +15,28 @@ function manifestProviderIds(manifest) {
|
|
|
15
15
|
/**
|
|
16
16
|
* The gateway secrets holding the keys of the AI providers and capabilities
|
|
17
17
|
* an app may ask for, with the settings a provider needs besides its key
|
|
18
|
-
* (Workers AI's account
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
23
26
|
*/
|
|
24
27
|
const DEV_GATEWAY_KEYS = {
|
|
25
28
|
providers: {
|
|
26
29
|
openai: ["OPENAI_API_KEY"],
|
|
27
30
|
openrouter: ["OPENROUTER_API_KEY"],
|
|
28
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"],
|
|
29
40
|
},
|
|
30
41
|
capabilities: {
|
|
31
42
|
dataforseo: ["DATAFORSEO_API_KEY"],
|
|
@@ -73,6 +84,57 @@ const DEV_GATEWAY_KEYS = {
|
|
|
73
84
|
"GOOGLE_WORKSPACE_CLIENT_SECRET",
|
|
74
85
|
],
|
|
75
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"],
|
|
76
138
|
},
|
|
77
139
|
};
|
|
78
140
|
/**
|
|
@@ -194,6 +256,8 @@ function devProviderKeys(manifest, root) {
|
|
|
194
256
|
}
|
|
195
257
|
/** Cloudflare's limit on a Workflow's name. */
|
|
196
258
|
const MAX_WORKFLOW_NAME_LENGTH = 64;
|
|
259
|
+
/** Cloudflare's limit on a Vectorize index's name. */
|
|
260
|
+
const MAX_VECTORIZE_NAME_LENGTH = 64;
|
|
197
261
|
/** Cloudflare's limit on a Worker's name. */
|
|
198
262
|
const MAX_WORKER_NAME_LENGTH = 63;
|
|
199
263
|
/**
|
|
@@ -251,6 +315,10 @@ export function everyAppWorker(options) {
|
|
|
251
315
|
addDevProviders(env, options.root);
|
|
252
316
|
}
|
|
253
317
|
Object.assign(env, storageBindings(manifest, name, deploy));
|
|
318
|
+
addAiBindings(env, manifest, deploy);
|
|
319
|
+
addBrowserBindings(env, manifest);
|
|
320
|
+
addImagesBindings(env, manifest);
|
|
321
|
+
addAiGatewayBindings(env, manifest, name, deploy);
|
|
254
322
|
addDatabaseBindings(env, options, name, undefined, deploy);
|
|
255
323
|
// The app binds every class, a helper Worker's in that Worker, and
|
|
256
324
|
// exports only its own.
|
|
@@ -309,7 +377,8 @@ export function everyAppWorker(options) {
|
|
|
309
377
|
* Build the definition of one of the app's helper Workers (the manifest's
|
|
310
378
|
* `workers`), e.g. one that runs a memory-hungry job away from the app's
|
|
311
379
|
* Worker. It is named `<app worker>--<worker>`, is private like the app, and
|
|
312
|
-
* binds the app's D1 databases, KV namespaces
|
|
380
|
+
* binds the app's D1 databases, KV namespaces, R2 buckets, Workers AI, Browser Rendering and Images, the classes it defines, and
|
|
381
|
+
* binds the app's D1 databases, KV namespaces, R2 buckets, Workers AI, Browser Rendering and Vectorize indexes, the classes it defines, and
|
|
313
382
|
* the gateway (with the app's identity, for capabilities). Only the app's
|
|
314
383
|
* Worker reaches it. Pass it to the Vite plugin:
|
|
315
384
|
*
|
|
@@ -344,6 +413,10 @@ export function everyAppAuxiliaryWorker(options) {
|
|
|
344
413
|
env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
|
|
345
414
|
addDevProviders(env, options.root);
|
|
346
415
|
}
|
|
416
|
+
addAiBindings(env, manifest, deploy);
|
|
417
|
+
addBrowserBindings(env, manifest);
|
|
418
|
+
addImagesBindings(env, manifest);
|
|
419
|
+
addAiGatewayBindings(env, manifest, appWorker, deploy);
|
|
347
420
|
addDatabaseBindings(env, options, appWorker, worker, deploy);
|
|
348
421
|
const { bindings, exports } = classBindings(manifest, appWorker, worker, env);
|
|
349
422
|
Object.assign(env, bindings);
|
|
@@ -410,8 +483,8 @@ function gatewayBinding(deploy, appId, appWorker) {
|
|
|
410
483
|
};
|
|
411
484
|
}
|
|
412
485
|
/**
|
|
413
|
-
* The app's D1 databases, KV namespaces
|
|
414
|
-
* Workers share.
|
|
486
|
+
* The app's D1 databases, KV namespaces, R2 buckets and Vectorize indexes,
|
|
487
|
+
* which all its Workers share.
|
|
415
488
|
*/
|
|
416
489
|
function storageBindings(manifest, appWorker, deploy) {
|
|
417
490
|
const env = {};
|
|
@@ -461,8 +534,90 @@ function storageBindings(manifest, appWorker, deploy) {
|
|
|
461
534
|
env[binding] = { type: "r2", name: bucketNameFor(appWorker, binding) };
|
|
462
535
|
}
|
|
463
536
|
}
|
|
537
|
+
// Deployed, the index the deploy provisioned (a placeholder until
|
|
538
|
+
// installed); in development the account's index of the dev Worker's
|
|
539
|
+
// name, reached remotely: Cloudflare has no local Vectorize, so calls
|
|
540
|
+
// throw until the Vite plugin's remoteBindings is on.
|
|
541
|
+
for (const { name: binding } of manifest.resources?.vectorize ?? []) {
|
|
542
|
+
if (env[binding]) {
|
|
543
|
+
throw new Error(`vectorize: ${binding} reuses a binding name the Worker already has.`);
|
|
544
|
+
}
|
|
545
|
+
if (deploy) {
|
|
546
|
+
env[binding] = {
|
|
547
|
+
type: "vectorize",
|
|
548
|
+
name: provisioned(deploy.resources.vectorize, binding).name,
|
|
549
|
+
};
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const index = suffixedName(appWorker, binding);
|
|
553
|
+
if (index.length > MAX_VECTORIZE_NAME_LENGTH) {
|
|
554
|
+
throw new Error(`vectorize: ${binding} would be the Vectorize index "${index}", longer than Cloudflare's ${MAX_VECTORIZE_NAME_LENGTH} characters; shorten the binding name.`);
|
|
555
|
+
}
|
|
556
|
+
env[binding] = { type: "vectorize", name: index, dev: { remote: true } };
|
|
557
|
+
}
|
|
464
558
|
return env;
|
|
465
559
|
}
|
|
560
|
+
/**
|
|
561
|
+
* The manifest's Workers AI bindings, which every one of the app's Workers
|
|
562
|
+
* gets: `{ type: "ai" }` deployed (account-wide, nothing to provision), and
|
|
563
|
+
* marked remote in development, where Miniflare has no Workers AI. The
|
|
564
|
+
* remote mark does nothing until the app's Vite config sets
|
|
565
|
+
* `remoteBindings: true`, so turning that on is the one switch.
|
|
566
|
+
*/
|
|
567
|
+
function addAiBindings(env, manifest, deploy) {
|
|
568
|
+
for (const binding of manifest.resources?.ai ?? []) {
|
|
569
|
+
if (env[binding]) {
|
|
570
|
+
throw new Error(`ai: ${binding} reuses a binding name the Worker already has.`);
|
|
571
|
+
}
|
|
572
|
+
env[binding] = deploy
|
|
573
|
+
? { type: "ai" }
|
|
574
|
+
: { type: "ai", dev: { remote: true } };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The manifest's Browser Rendering bindings, which every one of the app's
|
|
579
|
+
* Workers gets: `{ type: "browser" }` in both modes (account-wide,
|
|
580
|
+
* nothing to provision; Miniflare serves a local browser in development).
|
|
581
|
+
*/
|
|
582
|
+
function addBrowserBindings(env, manifest) {
|
|
583
|
+
for (const binding of manifest.resources?.browser ?? []) {
|
|
584
|
+
if (env[binding]) {
|
|
585
|
+
throw new Error(`browser: ${binding} reuses a binding name the Worker already has.`);
|
|
586
|
+
}
|
|
587
|
+
env[binding] = { type: "browser" };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* The manifest's Images bindings, which every one of the app's Workers
|
|
592
|
+
* gets: `{ type: "images" }` in both modes (account-wide, nothing to
|
|
593
|
+
* provision; Miniflare answers it locally in development).
|
|
594
|
+
*/
|
|
595
|
+
function addImagesBindings(env, manifest) {
|
|
596
|
+
for (const binding of manifest.resources?.images ?? []) {
|
|
597
|
+
if (env[binding]) {
|
|
598
|
+
throw new Error(`images: ${binding} reuses a binding name the Worker already has.`);
|
|
599
|
+
}
|
|
600
|
+
env[binding] = { type: "images" };
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* The app's AI Gateways, which all its Workers share, each a `json`
|
|
605
|
+
* binding of `{ id, accountId, url }`. Deployed, the gateway the deploy
|
|
606
|
+
* provisioned (a placeholder until installed); in development the id the
|
|
607
|
+
* deploy's rule gives, with an empty account and URL: there is no gateway,
|
|
608
|
+
* so the app calls the provider directly.
|
|
609
|
+
*/
|
|
610
|
+
function addAiGatewayBindings(env, manifest, appWorker, deploy) {
|
|
611
|
+
for (const { name: binding } of manifest.resources?.aiGateway ?? []) {
|
|
612
|
+
if (env[binding]) {
|
|
613
|
+
throw new Error(`aiGateway: ${binding} reuses a binding name the Worker already has.`);
|
|
614
|
+
}
|
|
615
|
+
const value = deploy
|
|
616
|
+
? { ...provisioned(deploy.resources.aiGateway, binding) }
|
|
617
|
+
: { id: aiGatewayIdFor(appWorker, binding), accountId: "", url: "" };
|
|
618
|
+
env[binding] = { type: "json", value };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
466
621
|
/**
|
|
467
622
|
* DATABASE_PROVIDER and the Hyperdrive configs one of the app's Workers
|
|
468
623
|
* binds: each config every Worker shares, and those a helper (`forWorker`)
|
|
@@ -709,8 +864,9 @@ function workerNameFor(appId) {
|
|
|
709
864
|
}
|
|
710
865
|
/**
|
|
711
866
|
* Copies of the gateway's naming rules (@every-app/perimeter/manifest's
|
|
712
|
-
* resourceNameFor and
|
|
713
|
-
* the Worker's name, any other binding
|
|
867
|
+
* resourceNameFor, bucketNameFor and vectorizeNameFor): `DB` and `KV` (`R2`
|
|
868
|
+
* for a bucket) take the Worker's name, any other binding (and every
|
|
869
|
+
* Vectorize index) `<worker>--<binding>`.
|
|
714
870
|
*/
|
|
715
871
|
function resourceNameFor(workerName, binding) {
|
|
716
872
|
return binding === "DB" || binding === "KV"
|
|
@@ -723,6 +879,13 @@ function bucketNameFor(workerName, binding) {
|
|
|
723
879
|
function suffixedName(workerName, binding) {
|
|
724
880
|
return `${workerName}--${binding.toLowerCase().replaceAll("_", "-")}`;
|
|
725
881
|
}
|
|
882
|
+
/**
|
|
883
|
+
* A copy of the gateway's aiGatewayIdFor: one hyphen, since an AI
|
|
884
|
+
* Gateway's id may not hold `--`.
|
|
885
|
+
*/
|
|
886
|
+
function aiGatewayIdFor(workerName, binding) {
|
|
887
|
+
return `${workerName}-${binding.toLowerCase().replaceAll("_", "-")}`;
|
|
888
|
+
}
|
|
726
889
|
function assertAppId(id) {
|
|
727
890
|
if (typeof id !== "string" || !/^[a-z]([a-z0-9-]*[a-z0-9])?$/.test(id)) {
|
|
728
891
|
throw new Error(`everyapp.config.ts id must be kebab-case (lowercase letters, digits, hyphens); got ${JSON.stringify(id)}.`);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Gateway's default model provider: what an app that says "I need a
|
|
3
|
+
* chat model" (manifest `ai: { chat: true }`, the SDK's `ai()`, the route
|
|
4
|
+
* `/api/ai/default/…`) is served (docs/design/2026-10-model-providers.md).
|
|
5
|
+
*
|
|
6
|
+
* The default is resolved, never assumed: the operator's choice when one
|
|
7
|
+
* is stored (`everyapp ai default <provider>[/<model>]`, Manage workspace
|
|
8
|
+
* → Usage → AI default), else the shipped order below, first provider
|
|
9
|
+
* whose Gateway settings are all set; with none set, the first of the
|
|
10
|
+
* order with the settings it needs, so "Needs setup" asks for one key.
|
|
11
|
+
* OpenRouter comes first because one key reaches many models; Workers AI
|
|
12
|
+
* second because it needs no provider account (the Gateway's own
|
|
13
|
+
* Cloudflare token), with weaker models; then the single-vendor keys.
|
|
14
|
+
* Cloudflare AI Gateway is never a default: it has no models of its own
|
|
15
|
+
* and fronts whichever provider the default resolves to when
|
|
16
|
+
* `AI_GATEWAY_ID` is set (./capabilities.ts, `aig`).
|
|
17
|
+
*
|
|
18
|
+
* Everything here is pure: what is set is asked of the caller (`isSet`),
|
|
19
|
+
* which reads the Gateway's env, the Worker's secret names, or `.dev.vars`.
|
|
20
|
+
*/
|
|
21
|
+
import { type AiProviderCapability, type AiRoute, type Capability, type CapabilityLookup } from "./capabilities.js";
|
|
22
|
+
/** The reserved provider id apps call for the default: `/api/ai/default/…`. */
|
|
23
|
+
export declare const AI_DEFAULT_PROVIDER = "default";
|
|
24
|
+
/** The catalog id of Cloudflare AI Gateway, the proxy entry. */
|
|
25
|
+
export declare const AI_GATEWAY_PROVIDER = "ai-gateway";
|
|
26
|
+
/** The settings that make AI Gateway front the providers that have a slug. */
|
|
27
|
+
export declare const AI_GATEWAY_SETTINGS: readonly ["AI_GATEWAY_ID", "CLOUDFLARE_ACCOUNT_ID"];
|
|
28
|
+
/**
|
|
29
|
+
* The shipped order: the provider the default resolves to when the
|
|
30
|
+
* operator chose none, the first of these whose settings are set.
|
|
31
|
+
*/
|
|
32
|
+
export declare const AI_DEFAULT_ORDER: readonly string[];
|
|
33
|
+
/** The model name an app sends to mean "the default's model". */
|
|
34
|
+
export declare const AI_DEFAULT_MODEL = "default";
|
|
35
|
+
/** The paths an app calls under `/api/ai/default`, OpenAI-shaped. */
|
|
36
|
+
export declare const AI_DEFAULT_ROUTES: {
|
|
37
|
+
readonly chat: "/v1/chat/completions";
|
|
38
|
+
readonly embeddings: "/v1/embeddings";
|
|
39
|
+
};
|
|
40
|
+
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. */
|
|
42
|
+
export interface AiDefaultChoice {
|
|
43
|
+
provider: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as a choice:
|
|
48
|
+
* the provider id up to the first slash, the model after it (a model id
|
|
49
|
+
* may hold slashes of its own). Null for blank or malformed text.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseAiDefaultChoice(value: string | null | undefined): AiDefaultChoice | null;
|
|
52
|
+
/** The stored text of a choice: `provider` or `provider/model`. */
|
|
53
|
+
export declare function formatAiDefaultChoice(choice: AiDefaultChoice): string;
|
|
54
|
+
/** Whether `name` holds a value, as the Gateway's env or a secret list says. */
|
|
55
|
+
export type IsSet = (name: string) => boolean;
|
|
56
|
+
/** An `ai` entry the default may resolve to: not the proxy, with a chat route. */
|
|
57
|
+
export type AiDefaultCapability = AiProviderCapability & {
|
|
58
|
+
chat: AiRoute;
|
|
59
|
+
};
|
|
60
|
+
export declare function canBeAiDefault(capability: Capability | undefined): capability is AiDefaultCapability;
|
|
61
|
+
/**
|
|
62
|
+
* The Gateway settings an `ai` entry needs before it serves: its key
|
|
63
|
+
* (unless the entry works without one, `authOptional`) and what its URL
|
|
64
|
+
* needs besides (`alsoNeeds`).
|
|
65
|
+
*/
|
|
66
|
+
export declare function aiRequiredSettings(capability: AiProviderCapability): string[];
|
|
67
|
+
/** Whether every setting the entry needs is set. */
|
|
68
|
+
export declare function aiProviderConfigured(capability: AiProviderCapability, isSet: IsSet): boolean;
|
|
69
|
+
/** Whether AI Gateway fronts calls: its id and the account are set. */
|
|
70
|
+
export declare function aiGatewayConfigured(isSet: IsSet): boolean;
|
|
71
|
+
/** Whether a call to `capability` goes through AI Gateway: it is configured and the entry has a slug. */
|
|
72
|
+
export declare function viaAiGateway(capability: AiProviderCapability, isSet: IsSet): boolean;
|
|
73
|
+
/** One provider the default may resolve to, with its state on this Gateway. */
|
|
74
|
+
export interface AiDefaultCandidate {
|
|
75
|
+
id: string;
|
|
76
|
+
label: string;
|
|
77
|
+
/** The Gateway settings it needs; what to set when it isn't configured. */
|
|
78
|
+
settings: string[];
|
|
79
|
+
configured: boolean;
|
|
80
|
+
/** The model the Gateway fills in when the app names none. */
|
|
81
|
+
model: string;
|
|
82
|
+
/** Through Cloudflare AI Gateway, as things are set now. */
|
|
83
|
+
viaAiGateway: boolean;
|
|
84
|
+
embeddings: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** Where the resolved default came from. */
|
|
87
|
+
export type AiDefaultSource =
|
|
88
|
+
/** The operator's stored choice. */
|
|
89
|
+
"setting"
|
|
90
|
+
/** The shipped order: the first provider that is configured. */
|
|
91
|
+
| "order"
|
|
92
|
+
/** Nothing is configured: the first of the order, with what to set. */
|
|
93
|
+
| "none";
|
|
94
|
+
export interface AiDefaultResolution {
|
|
95
|
+
provider: string;
|
|
96
|
+
label: string;
|
|
97
|
+
/** The model the default serves: the choice's, else the entry's. */
|
|
98
|
+
model: string;
|
|
99
|
+
source: AiDefaultSource;
|
|
100
|
+
/** Every setting the provider needs is set, so the default serves. */
|
|
101
|
+
configured: boolean;
|
|
102
|
+
/** The Gateway settings the provider needs: what to set when it isn't configured. */
|
|
103
|
+
settings: string[];
|
|
104
|
+
via: "ai-gateway" | "direct";
|
|
105
|
+
chat: AiRoute;
|
|
106
|
+
embeddings?: AiRoute;
|
|
107
|
+
/** Every provider the default could be, in the shipped order, the choice first. */
|
|
108
|
+
candidates: AiDefaultCandidate[];
|
|
109
|
+
/**
|
|
110
|
+
* The stored choice names something the default can't be (an id the
|
|
111
|
+
* catalog doesn't have, an entry with no chat route, the proxy): the
|
|
112
|
+
* order decided instead, and this says why.
|
|
113
|
+
*/
|
|
114
|
+
problem?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Every provider the default may resolve to: the shipped order first,
|
|
118
|
+
* then any other `ai` entry of `lookup` with a chat route (a registered
|
|
119
|
+
* row, one day), each with its state.
|
|
120
|
+
*/
|
|
121
|
+
export declare function aiDefaultCandidates(isSet: IsSet, lookup?: CapabilityLookup): AiDefaultCandidate[];
|
|
122
|
+
/** Why `choice.provider` can't be the default, or null when it can. */
|
|
123
|
+
export declare function aiDefaultChoiceProblem(choice: AiDefaultChoice, lookup?: CapabilityLookup): string | null;
|
|
124
|
+
/**
|
|
125
|
+
* The Gateway's effective default, from the stored choice (or none) and
|
|
126
|
+
* what is set. A choice that names a provider which isn't configured
|
|
127
|
+
* still wins (the operator chose it: `configured` is false and `settings`
|
|
128
|
+
* says what to set); a choice the default can't be is reported as
|
|
129
|
+
* `problem` and the order decides.
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveAiDefault(input: {
|
|
132
|
+
choice: AiDefaultChoice | null;
|
|
133
|
+
isSet: IsSet;
|
|
134
|
+
lookup?: CapabilityLookup;
|
|
135
|
+
}): AiDefaultResolution;
|
|
136
|
+
/**
|
|
137
|
+
* The sentence setup shows for an unconfigured default: the one key to
|
|
138
|
+
* set, and the alternatives in the order.
|
|
139
|
+
*/
|
|
140
|
+
export declare function aiDefaultSetupHint(resolution: AiDefaultResolution): string;
|
|
141
|
+
//# sourceMappingURL=aiDefault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aiDefault.d.ts","sourceRoot":"","sources":["../../src/internal/aiDefault.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Gateway's default model provider: what an app that says "I need a
|
|
3
|
+
* chat model" (manifest `ai: { chat: true }`, the SDK's `ai()`, the route
|
|
4
|
+
* `/api/ai/default/…`) is served (docs/design/2026-10-model-providers.md).
|
|
5
|
+
*
|
|
6
|
+
* The default is resolved, never assumed: the operator's choice when one
|
|
7
|
+
* is stored (`everyapp ai default <provider>[/<model>]`, Manage workspace
|
|
8
|
+
* → Usage → AI default), else the shipped order below, first provider
|
|
9
|
+
* whose Gateway settings are all set; with none set, the first of the
|
|
10
|
+
* order with the settings it needs, so "Needs setup" asks for one key.
|
|
11
|
+
* OpenRouter comes first because one key reaches many models; Workers AI
|
|
12
|
+
* second because it needs no provider account (the Gateway's own
|
|
13
|
+
* Cloudflare token), with weaker models; then the single-vendor keys.
|
|
14
|
+
* Cloudflare AI Gateway is never a default: it has no models of its own
|
|
15
|
+
* and fronts whichever provider the default resolves to when
|
|
16
|
+
* `AI_GATEWAY_ID` is set (./capabilities.ts, `aig`).
|
|
17
|
+
*
|
|
18
|
+
* Everything here is pure: what is set is asked of the caller (`isSet`),
|
|
19
|
+
* which reads the Gateway's env, the Worker's secret names, or `.dev.vars`.
|
|
20
|
+
*/
|
|
21
|
+
import { CATALOG, } from "./capabilities.js";
|
|
22
|
+
/** The reserved provider id apps call for the default: `/api/ai/default/…`. */
|
|
23
|
+
export const AI_DEFAULT_PROVIDER = "default";
|
|
24
|
+
/** The catalog id of Cloudflare AI Gateway, the proxy entry. */
|
|
25
|
+
export const AI_GATEWAY_PROVIDER = "ai-gateway";
|
|
26
|
+
/** The settings that make AI Gateway front the providers that have a slug. */
|
|
27
|
+
export const AI_GATEWAY_SETTINGS = [
|
|
28
|
+
"AI_GATEWAY_ID",
|
|
29
|
+
"CLOUDFLARE_ACCOUNT_ID",
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* The shipped order: the provider the default resolves to when the
|
|
33
|
+
* operator chose none, the first of these whose settings are set.
|
|
34
|
+
*/
|
|
35
|
+
export const AI_DEFAULT_ORDER = [
|
|
36
|
+
"openrouter",
|
|
37
|
+
"workers-ai",
|
|
38
|
+
"openai",
|
|
39
|
+
"anthropic",
|
|
40
|
+
"groq",
|
|
41
|
+
"mistral",
|
|
42
|
+
"gemini",
|
|
43
|
+
];
|
|
44
|
+
/** The model name an app sends to mean "the default's model". */
|
|
45
|
+
export const AI_DEFAULT_MODEL = "default";
|
|
46
|
+
/** The paths an app calls under `/api/ai/default`, OpenAI-shaped. */
|
|
47
|
+
export const AI_DEFAULT_ROUTES = {
|
|
48
|
+
chat: "/v1/chat/completions",
|
|
49
|
+
embeddings: "/v1/embeddings",
|
|
50
|
+
};
|
|
51
|
+
/** A stored choice must name a provider id; the model is free text after the first slash. */
|
|
52
|
+
const CHOICE = /^([a-z][a-z0-9-]{1,39})(?:\/(.{1,200}))?$/;
|
|
53
|
+
/**
|
|
54
|
+
* `openrouter` or `openrouter/anthropic/claude-haiku-5.5` as a choice:
|
|
55
|
+
* the provider id up to the first slash, the model after it (a model id
|
|
56
|
+
* may hold slashes of its own). Null for blank or malformed text.
|
|
57
|
+
*/
|
|
58
|
+
export function parseAiDefaultChoice(value) {
|
|
59
|
+
if (typeof value !== "string")
|
|
60
|
+
return null;
|
|
61
|
+
const match = CHOICE.exec(value.trim());
|
|
62
|
+
if (!match)
|
|
63
|
+
return null;
|
|
64
|
+
const model = match[2]?.trim();
|
|
65
|
+
return model ? { provider: match[1], model } : { provider: match[1] };
|
|
66
|
+
}
|
|
67
|
+
/** The stored text of a choice: `provider` or `provider/model`. */
|
|
68
|
+
export function formatAiDefaultChoice(choice) {
|
|
69
|
+
return choice.model ? `${choice.provider}/${choice.model}` : choice.provider;
|
|
70
|
+
}
|
|
71
|
+
export function canBeAiDefault(capability) {
|
|
72
|
+
return (capability?.mode === "ai" &&
|
|
73
|
+
capability.kind !== "proxy" &&
|
|
74
|
+
capability.chat !== undefined);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The Gateway settings an `ai` entry needs before it serves: its key
|
|
78
|
+
* (unless the entry works without one, `authOptional`) and what its URL
|
|
79
|
+
* needs besides (`alsoNeeds`).
|
|
80
|
+
*/
|
|
81
|
+
export function aiRequiredSettings(capability) {
|
|
82
|
+
return [
|
|
83
|
+
...(capability.authOptional ? [] : [capability.auth.secret]),
|
|
84
|
+
...(capability.alsoNeeds ?? []),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
/** Whether every setting the entry needs is set. */
|
|
88
|
+
export function aiProviderConfigured(capability, isSet) {
|
|
89
|
+
return aiRequiredSettings(capability).every((name) => isSet(name));
|
|
90
|
+
}
|
|
91
|
+
/** Whether AI Gateway fronts calls: its id and the account are set. */
|
|
92
|
+
export function aiGatewayConfigured(isSet) {
|
|
93
|
+
return AI_GATEWAY_SETTINGS.every((name) => isSet(name));
|
|
94
|
+
}
|
|
95
|
+
/** Whether a call to `capability` goes through AI Gateway: it is configured and the entry has a slug. */
|
|
96
|
+
export function viaAiGateway(capability, isSet) {
|
|
97
|
+
return capability.aig !== undefined && aiGatewayConfigured(isSet);
|
|
98
|
+
}
|
|
99
|
+
function candidateOf(id, capability, isSet) {
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
label: capability.label,
|
|
103
|
+
settings: aiRequiredSettings(capability),
|
|
104
|
+
configured: aiProviderConfigured(capability, isSet),
|
|
105
|
+
model: capability.chat.model,
|
|
106
|
+
viaAiGateway: viaAiGateway(capability, isSet),
|
|
107
|
+
embeddings: capability.embeddings !== undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Every provider the default may resolve to: the shipped order first,
|
|
112
|
+
* then any other `ai` entry of `lookup` with a chat route (a registered
|
|
113
|
+
* row, one day), each with its state.
|
|
114
|
+
*/
|
|
115
|
+
export function aiDefaultCandidates(isSet, lookup = CATALOG) {
|
|
116
|
+
const ids = [
|
|
117
|
+
...AI_DEFAULT_ORDER,
|
|
118
|
+
...lookup.ids().filter((id) => !AI_DEFAULT_ORDER.includes(id)),
|
|
119
|
+
];
|
|
120
|
+
return ids.flatMap((id) => {
|
|
121
|
+
const capability = lookup.get(id);
|
|
122
|
+
return canBeAiDefault(capability)
|
|
123
|
+
? [candidateOf(id, capability, isSet)]
|
|
124
|
+
: [];
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Why `choice.provider` can't be the default, or null when it can. */
|
|
128
|
+
export function aiDefaultChoiceProblem(choice, lookup = CATALOG) {
|
|
129
|
+
const capability = lookup.get(choice.provider);
|
|
130
|
+
if (!capability)
|
|
131
|
+
return `${choice.provider} isn't a provider this Gateway knows`;
|
|
132
|
+
if (capability.mode !== "ai") {
|
|
133
|
+
return `${choice.provider} is an API provider, not a model provider`;
|
|
134
|
+
}
|
|
135
|
+
if (capability.kind === "proxy") {
|
|
136
|
+
return `${capability.label} fronts the model providers and serves no models of its own; set a provider, and turn the gateway on with everyapp ai gateway on`;
|
|
137
|
+
}
|
|
138
|
+
if (!capability.chat) {
|
|
139
|
+
return `${capability.label} has no OpenAI-shaped chat route, so it can't serve the default; an app pins it by listing it under providers`;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The Gateway's effective default, from the stored choice (or none) and
|
|
145
|
+
* what is set. A choice that names a provider which isn't configured
|
|
146
|
+
* still wins (the operator chose it: `configured` is false and `settings`
|
|
147
|
+
* says what to set); a choice the default can't be is reported as
|
|
148
|
+
* `problem` and the order decides.
|
|
149
|
+
*/
|
|
150
|
+
export function resolveAiDefault(input) {
|
|
151
|
+
const lookup = input.lookup ?? CATALOG;
|
|
152
|
+
const { isSet } = input;
|
|
153
|
+
const candidates = aiDefaultCandidates(isSet, lookup);
|
|
154
|
+
const resolve = (id, capability, source, model, problem) => ({
|
|
155
|
+
provider: id,
|
|
156
|
+
label: capability.label,
|
|
157
|
+
model: model ?? capability.chat.model,
|
|
158
|
+
source,
|
|
159
|
+
configured: aiProviderConfigured(capability, isSet),
|
|
160
|
+
settings: aiRequiredSettings(capability),
|
|
161
|
+
via: viaAiGateway(capability, isSet) ? "ai-gateway" : "direct",
|
|
162
|
+
chat: capability.chat,
|
|
163
|
+
...(capability.embeddings ? { embeddings: capability.embeddings } : {}),
|
|
164
|
+
candidates: [
|
|
165
|
+
...candidates.filter((candidate) => candidate.id === id),
|
|
166
|
+
...candidates.filter((candidate) => candidate.id !== id),
|
|
167
|
+
],
|
|
168
|
+
...(problem ? { problem } : {}),
|
|
169
|
+
});
|
|
170
|
+
let problem;
|
|
171
|
+
if (input.choice) {
|
|
172
|
+
const capability = lookup.get(input.choice.provider);
|
|
173
|
+
const why = aiDefaultChoiceProblem(input.choice, lookup);
|
|
174
|
+
if (!why && canBeAiDefault(capability)) {
|
|
175
|
+
return resolve(input.choice.provider, capability, "setting", input.choice.model);
|
|
176
|
+
}
|
|
177
|
+
problem = why ?? undefined;
|
|
178
|
+
}
|
|
179
|
+
const configured = candidates.find((candidate) => candidate.configured);
|
|
180
|
+
const first = configured ?? candidates[0];
|
|
181
|
+
if (!first) {
|
|
182
|
+
throw new Error("the catalog has no AI provider with a chat route");
|
|
183
|
+
}
|
|
184
|
+
const capability = lookup.get(first.id);
|
|
185
|
+
if (!canBeAiDefault(capability))
|
|
186
|
+
throw new Error("unreachable");
|
|
187
|
+
return resolve(first.id, capability, configured ? "order" : "none", undefined, problem);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The sentence setup shows for an unconfigured default: the one key to
|
|
191
|
+
* set, and the alternatives in the order.
|
|
192
|
+
*/
|
|
193
|
+
export function aiDefaultSetupHint(resolution) {
|
|
194
|
+
const others = resolution.candidates
|
|
195
|
+
.filter((candidate) => candidate.id !== resolution.provider)
|
|
196
|
+
.slice(0, 3)
|
|
197
|
+
.map((candidate) => `${candidate.label} (${candidate.settings.join(" and ")})`);
|
|
198
|
+
const head = resolution.source === "setting"
|
|
199
|
+
? `The Gateway's default model provider is ${resolution.label}; set ${resolution.settings.join(" and ")}.`
|
|
200
|
+
: `Any one model provider's key makes the app work; ${resolution.label} is first in the order: set ${resolution.settings.join(" and ")}.`;
|
|
201
|
+
return others.length > 0
|
|
202
|
+
? `${head} Or another: ${others.join(", ")}; pick one with everyapp ai default <provider>.`
|
|
203
|
+
: head;
|
|
204
|
+
}
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* this derives the fallback's view of each, and holds the one response
|
|
8
8
|
* rewrite the gateway has (Workers AI's numeric tokens).
|
|
9
9
|
*/
|
|
10
|
-
import { type AiProviderCapability, type AiProviderId, type ApiKeyAuth, type CapabilitySettings } from "./capabilities.js";
|
|
10
|
+
import { type AiProviderCapability, type AiProviderId, type AiRoute, type ApiKeyAuth, type CapabilitySettings } from "./capabilities.js";
|
|
11
11
|
export interface DevAiProviderSpec {
|
|
12
12
|
origin: string;
|
|
13
|
+
/** The routes the default maps onto in dev, with the model filled in (the entry's `chat` and `embeddings`). */
|
|
14
|
+
chat?: AiRoute;
|
|
15
|
+
embeddings?: AiRoute;
|
|
13
16
|
/** The gateway secret holding the provider key; in local dev, the `.dev.vars` name. */
|
|
14
17
|
keyEnv: string;
|
|
15
18
|
/** Where the key goes on a request (`authorizationOf`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiProviders.d.ts","sourceRoot":"","sources":["../../src/internal/aiProviders.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,IAAI,EAAE,UAAU,CAAC;IACjB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC;IAClE,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;CACpE;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAEzC,qDAAqD;AACrD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,oBAAoB,GAC/B,iBAAiB,
|
|
1
|
+
{"version":3,"file":"aiProviders.d.ts","sourceRoot":"","sources":["../../src/internal/aiProviders.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,+GAA+G;IAC/G,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,IAAI,EAAE,UAAU,CAAC;IACjB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC;IAClE,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;CACpE;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAEzC,qDAAqD;AACrD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,oBAAoB,GAC/B,iBAAiB,CAoBnB;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CACrC,MAAM,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAUG,CAAC;AAE9C,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAIzE;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,kBAAkB,GAC3B,MAAM,GAAG,IAAI,CAcf;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,oBAAoB,EAChC,QAAQ,EAAE,kBAAkB,GAC3B,MAAM,GAAG,IAAI,CAIf;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,GAAG,GAAG,IAAI,CAOZ;AAWD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GACjB,QAAQ,CAqBV"}
|
|
@@ -13,6 +13,8 @@ export function devAiProviderSpec(capability) {
|
|
|
13
13
|
const { basePath, normalize } = capability;
|
|
14
14
|
return {
|
|
15
15
|
origin: capability.origin,
|
|
16
|
+
...(capability.chat ? { chat: capability.chat } : {}),
|
|
17
|
+
...(capability.embeddings ? { embeddings: capability.embeddings } : {}),
|
|
16
18
|
keyEnv: capability.auth.secret,
|
|
17
19
|
auth: capability.auth,
|
|
18
20
|
docs: capability.docs,
|
|
@@ -30,7 +32,9 @@ export function devAiProviderSpec(capability) {
|
|
|
30
32
|
}
|
|
31
33
|
export const DEV_AI_PROVIDERS = Object.fromEntries(CAPABILITY_IDS.flatMap((id) => {
|
|
32
34
|
const capability = CAPABILITIES[id];
|
|
33
|
-
|
|
35
|
+
// The proxy entry (AI Gateway) serves no models: never a dev target.
|
|
36
|
+
return capability.mode === "ai" &&
|
|
37
|
+
!("kind" in capability && capability.kind === "proxy")
|
|
34
38
|
? [[id, devAiProviderSpec(capability)]]
|
|
35
39
|
: [];
|
|
36
40
|
}));
|