@maxceem/agw 0.2.1 → 0.3.0

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 (2) hide show
  1. package/dist/agw.mjs +2130 -898
  2. package/package.json +2 -1
package/dist/agw.mjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // <define:__AGW_MANIFEST__>
9
- var define_AGW_MANIFEST_default = { version: "0.2.1", wrangler: "4.96.0", upgradeFrom: ["0.2.1", "0.2.0", "0.1.13", "0.1.12", "0.1.11", "0.1.10", "0.1.9", "0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.2.1/gateway-0.2.1.tar.gz", sha256: "09d5d34a924dedf99ca6268d2b699e3d524b72d07a6c6853629dc6c8c9a42307", manifest: "f0a1e385150b39c24707296f9618dea4bb21b73822bb976a58ac03a1482408a0" } };
9
+ var define_AGW_MANIFEST_default = { version: "0.3.0", wrangler: "4.96.0", upgradeFrom: ["0.3.0", "0.2.1", "0.2.0", "0.1.13", "0.1.12", "0.1.11", "0.1.10", "0.1.9", "0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.3.0/gateway-0.3.0.tar.gz", sha256: "ead1f1da6ad0b2d2c35397fae2e58c640ffd2a5ba51a096b3a91facd877b6b61", manifest: "86b7799f57fe35aae0a372dd01e3cf32e3d6e7f8a3f3c8e6e1e207cf321c1c57" } };
10
10
 
11
11
  // src/main.ts
12
12
  import { Writable as Writable2 } from "node:stream";
@@ -1418,18 +1418,18 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
1418
1418
  }
1419
1419
  function toDotPath(_path) {
1420
1420
  const segs = [];
1421
- const path = _path.map((seg2) => typeof seg2 === "object" ? seg2.key : seg2);
1422
- for (const seg2 of path) {
1423
- if (typeof seg2 === "number")
1424
- segs.push(`[${seg2}]`);
1425
- else if (typeof seg2 === "symbol")
1426
- segs.push(`[${JSON.stringify(String(seg2))}]`);
1427
- else if (/[^\w$]/.test(seg2))
1428
- segs.push(`[${JSON.stringify(seg2)}]`);
1421
+ const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1422
+ for (const seg of path) {
1423
+ if (typeof seg === "number")
1424
+ segs.push(`[${seg}]`);
1425
+ else if (typeof seg === "symbol")
1426
+ segs.push(`[${JSON.stringify(String(seg))}]`);
1427
+ else if (/[^\w$]/.test(seg))
1428
+ segs.push(`[${JSON.stringify(seg)}]`);
1429
1429
  else {
1430
1430
  if (segs.length)
1431
1431
  segs.push(".");
1432
- segs.push(seg2);
1432
+ segs.push(seg);
1433
1433
  }
1434
1434
  }
1435
1435
  return segs.join("");
@@ -14525,12 +14525,64 @@ function date4(params) {
14525
14525
  // ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14526
14526
  config(en_default());
14527
14527
 
14528
- // ../src/core/records.ts
14528
+ // ../src/shared/capabilities.ts
14529
+ var API_STYLES = [
14530
+ "responses",
14531
+ "chat_completions",
14532
+ "anthropic_messages",
14533
+ "gemini_native",
14534
+ "audio_transcription",
14535
+ /** A provider-native operation with no cross-provider contract of its own. */
14536
+ "other"
14537
+ ];
14538
+ var API_STYLE_PATHS = {
14539
+ responses: "v1/responses",
14540
+ chat_completions: "v1/chat/completions",
14541
+ anthropic_messages: "v1/messages",
14542
+ gemini_native: "v1beta/models/{model}:generateContent",
14543
+ audio_transcription: "v1/audio/transcriptions"
14544
+ };
14545
+ var ENDPOINT_API_STYLES = ["responses", "transcription"];
14546
+ var OUTPUT_CLAMP_STYLES = [
14547
+ "responses",
14548
+ "chat_completions",
14549
+ "gemini_native",
14550
+ "anthropic",
14551
+ "none"
14552
+ ];
14553
+ var VERCEL_API_STYLES = [
14554
+ "responses",
14555
+ "chat_completions",
14556
+ "anthropic_messages"
14557
+ ];
14558
+ var VERCEL_ENDPOINT_STYLES = ["responses"];
14559
+ function vercelRoute(modelPrefix) {
14560
+ return {
14561
+ modelPrefix,
14562
+ apiStyles: VERCEL_API_STYLES,
14563
+ endpointStyles: VERCEL_ENDPOINT_STYLES
14564
+ };
14565
+ }
14566
+ var VERCEL_ROUTES = {
14567
+ openai: vercelRoute("openai/"),
14568
+ anthropic: vercelRoute("anthropic/"),
14569
+ // Google AI Studio's models are Google's, and Vercel namespaces them by
14570
+ // author: `gemini-2.5-flash` goes out as `google/gemini-2.5-flash`.
14571
+ gemini: vercelRoute("google/"),
14572
+ xai: vercelRoute("spacexai/"),
14573
+ perplexity: vercelRoute("perplexity/"),
14574
+ deepseek: vercelRoute("deepseek/"),
14575
+ // Moonshot's own IDs are the Kimi ones, and Vercel publishes them verbatim
14576
+ // under the lab's name rather than the product's: `moonshotai/kimi-k3`.
14577
+ moonshot: vercelRoute("moonshotai/")
14578
+ };
14579
+
14580
+ // ../src/shared/records.ts
14529
14581
  function asRecord(value) {
14530
14582
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
14531
14583
  }
14532
14584
 
14533
- // ../src/core/cost-report.ts
14585
+ // ../src/shared/cost-report.ts
14534
14586
  function finiteNumber(value) {
14535
14587
  return typeof value === "number" && Number.isFinite(value) ? value : null;
14536
14588
  }
@@ -14595,133 +14647,29 @@ var OPENROUTER_COST_REPORT = {
14595
14647
  }
14596
14648
  };
14597
14649
 
14598
- // ../src/shared/provider-auth.ts
14599
- var PROVIDER_AUTH = {
14600
- openai: { header: "authorization", scheme: "Bearer " },
14601
- anthropic: { header: "x-api-key" },
14602
- xai: { header: "authorization", scheme: "Bearer " },
14603
- gemini: { header: "x-goog-api-key" },
14604
- perplexity: { header: "authorization", scheme: "Bearer " },
14605
- deepseek: { header: "authorization", scheme: "Bearer " },
14606
- groq: { header: "authorization", scheme: "Bearer " },
14607
- mistral: { header: "authorization", scheme: "Bearer " },
14608
- together: { header: "authorization", scheme: "Bearer " },
14609
- fireworks: { header: "authorization", scheme: "Bearer " },
14610
- cerebras: { header: "authorization", scheme: "Bearer " },
14611
- moonshot: { header: "authorization", scheme: "Bearer " },
14612
- huggingface: { header: "authorization", scheme: "Bearer " },
14613
- baseten: { header: "authorization", scheme: "Bearer " },
14614
- bytedance: { header: "authorization", scheme: "Bearer " },
14615
- openrouter: { header: "authorization", scheme: "Bearer " }
14616
- };
14617
- var PROVIDER_CREDENTIAL_HEADERS = [
14618
- ...new Set(Object.values(PROVIDER_AUTH).map((auth) => auth.header.toLowerCase()))
14619
- ];
14620
-
14621
- // ../src/shared/capabilities.ts
14622
- var PROVIDER_TYPES = [
14623
- "openai",
14624
- "anthropic",
14625
- "xai",
14626
- "gemini",
14627
- "perplexity",
14628
- "deepseek",
14629
- "groq",
14630
- "mistral",
14631
- "together",
14632
- "fireworks",
14633
- "cerebras",
14634
- "moonshot",
14635
- "huggingface",
14636
- "baseten",
14637
- "bytedance",
14638
- "openrouter"
14639
- ];
14640
- var API_STYLES = [
14641
- "responses",
14642
- "chat_completions",
14643
- "anthropic_messages",
14644
- "gemini_native",
14645
- "audio_transcription",
14646
- /** A provider-native operation with no cross-provider contract of its own. */
14647
- "other"
14648
- ];
14649
- var PASSTHROUGH_CAPABILITY = {
14650
- apiStyles: API_STYLES,
14651
- endpointStyles: []
14652
- };
14653
- var PROVIDER_CAPABILITY_EXCEPTIONS = {
14654
- // The two types whose Responses and transcription request shapes the gateway
14655
- // composes itself for named endpoints.
14656
- openai: { apiStyles: API_STYLES, endpointStyles: ["responses", "transcription"] },
14657
- xai: { apiStyles: API_STYLES, endpointStyles: ["responses", "transcription"] },
14658
- // The one narrowed API surface, and it is a metering constraint rather than a
14659
- // missing one: OpenRouter also serves `/responses` and `/messages`, but only
14660
- // its chat-completions response carries `usage.cost`, and its slugs have no
14661
- // local price. Any other style would proxy traffic nothing could bill —
14662
- // exactly the silent $0 the fail-closed gate exists to prevent — so it is
14663
- // refused at the edge instead.
14664
- openrouter: { apiStyles: ["chat_completions"], endpointStyles: [] }
14665
- };
14666
- function providerCapability(provider) {
14667
- const exceptions = PROVIDER_CAPABILITY_EXCEPTIONS;
14668
- return exceptions[provider] ?? PASSTHROUGH_CAPABILITY;
14669
- }
14670
- var ENDPOINT_PROVIDER_TYPES = PROVIDER_TYPES.filter(
14671
- (type) => providerCapability(type).endpointStyles.length > 0
14672
- );
14673
- var VERCEL_API_STYLES = [
14674
- "responses",
14675
- "chat_completions",
14676
- "anthropic_messages"
14677
- ];
14678
- var VERCEL_ENDPOINT_STYLES = ["responses"];
14679
- function vercelRoute(modelPrefix) {
14680
- return {
14681
- modelPrefix,
14682
- apiStyles: VERCEL_API_STYLES,
14683
- endpointStyles: VERCEL_ENDPOINT_STYLES
14684
- };
14685
- }
14686
- var VERCEL_ROUTES = {
14687
- openai: vercelRoute("openai/"),
14688
- anthropic: vercelRoute("anthropic/"),
14689
- // Google AI Studio's models are Google's, and Vercel namespaces them by
14690
- // author: `gemini-2.5-flash` goes out as `google/gemini-2.5-flash`.
14691
- gemini: vercelRoute("google/"),
14692
- xai: vercelRoute("spacexai/"),
14693
- perplexity: vercelRoute("perplexity/"),
14694
- deepseek: vercelRoute("deepseek/"),
14695
- // Moonshot's own IDs are the Kimi ones, and Vercel publishes them verbatim
14696
- // under the lab's name rather than the product's: `moonshotai/kimi-k3`.
14697
- moonshot: vercelRoute("moonshotai/")
14698
- };
14699
-
14700
- // ../src/shared/app-config.ts
14701
- var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
14702
- var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
14703
- var ENDPOINT_SLUG = /^[a-z0-9-]{1,64}$/;
14704
- var PROVIDER_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
14705
- var HTTP_FIELD_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
14706
- var RESERVED_END_USER_HEADERS = /* @__PURE__ */ new Set([
14707
- "authorization",
14708
- ...PROVIDER_CREDENTIAL_HEADERS,
14709
- "x-app-version",
14710
- "content-type",
14711
- "content-length",
14712
- "host"
14713
- ]);
14714
-
14715
- // ../src/core/providers.ts
14716
- var PROVIDER_REGISTRY = {
14650
+ // ../src/shared/providers.ts
14651
+ var PROVIDER_DESCRIPTORS = {
14717
14652
  openai: {
14718
14653
  directBaseUrl: "https://api.openai.com/",
14719
- auth: PROVIDER_AUTH.openai,
14654
+ auth: { header: "authorization", scheme: "Bearer " },
14655
+ nativeClampStyle: "responses",
14656
+ probePath: "v1/models",
14657
+ examplePath: "v1/responses",
14658
+ // OpenAI's chat-completions surface deprecated `max_tokens` in favour of
14659
+ // `max_completion_tokens`; every other OpenAI-compatible service still
14660
+ // reads the older field.
14661
+ chatCompletionsCapField: "max_completion_tokens",
14662
+ // The two types whose Responses and transcription request shapes the
14663
+ // gateway composes itself for named endpoints.
14664
+ endpointPaths: { responses: "v1/responses", transcription: "v1/audio/transcriptions" },
14720
14665
  modelAuthor: "OpenAI"
14721
14666
  },
14722
14667
  anthropic: {
14723
14668
  directBaseUrl: "https://api.anthropic.com/",
14724
- auth: PROVIDER_AUTH.anthropic,
14669
+ auth: { header: "x-api-key" },
14670
+ nativeClampStyle: "anthropic",
14671
+ probePath: "v1/models",
14672
+ examplePath: "v1/messages",
14725
14673
  modelAuthor: "Anthropic",
14726
14674
  // Anthropic refuses any request without a version header, probe included —
14727
14675
  // and a probe has no client to send one for it. Deliberately not a
@@ -14732,17 +14680,31 @@ var PROVIDER_REGISTRY = {
14732
14680
  },
14733
14681
  xai: {
14734
14682
  directBaseUrl: "https://api.x.ai/",
14735
- auth: PROVIDER_AUTH.xai,
14683
+ auth: { header: "authorization", scheme: "Bearer " },
14684
+ nativeClampStyle: "responses",
14685
+ probePath: "v1/models",
14686
+ // Native provider paths: xAI transcribes at `v1/stt`, where OpenAI serves
14687
+ // `v1/audio/transcriptions`.
14688
+ endpointPaths: { responses: "v1/responses", transcription: "v1/stt" },
14736
14689
  modelAuthor: "xAI"
14737
14690
  },
14738
14691
  gemini: {
14739
14692
  directBaseUrl: "https://generativelanguage.googleapis.com/",
14740
- auth: PROVIDER_AUTH.gemini,
14693
+ auth: { header: "x-goog-api-key" },
14694
+ nativeClampStyle: "gemini_native",
14695
+ probePath: "v1beta/models",
14696
+ // Native Gemini generation requests carry the model in the URL rather than
14697
+ // in the JSON body, so there is no example path without a model and a
14698
+ // request on one has its model captured from the path.
14699
+ modelInPath: true,
14741
14700
  modelAuthor: "Google"
14742
14701
  },
14743
14702
  perplexity: {
14744
14703
  directBaseUrl: "https://api.perplexity.ai/",
14745
- auth: PROVIDER_AUTH.perplexity,
14704
+ auth: { header: "authorization", scheme: "Bearer " },
14705
+ nativeClampStyle: "responses",
14706
+ // No `probePath`: Perplexity has no unmetered authenticated endpoint.
14707
+ examplePath: "chat/completions",
14746
14708
  modelAuthor: "Perplexity"
14747
14709
  },
14748
14710
  // OpenAI-compatible chat-completions services. `modelAuthor` is set only
@@ -14753,41 +14715,62 @@ var PROVIDER_REGISTRY = {
14753
14715
  // No `v1` segment: DeepSeek documents the bare origin as its OpenAI base
14754
14716
  // URL, and `https://api.deepseek.com/anthropic` for the Anthropic format.
14755
14717
  directBaseUrl: "https://api.deepseek.com/",
14756
- auth: PROVIDER_AUTH.deepseek,
14718
+ auth: { header: "authorization", scheme: "Bearer " },
14719
+ nativeClampStyle: "chat_completions",
14720
+ // DeepSeek's OpenAI base URL carries no `v1` segment.
14721
+ probePath: "models",
14722
+ examplePath: "chat/completions",
14757
14723
  modelAuthor: "DeepSeek"
14758
14724
  },
14759
14725
  groq: {
14760
14726
  // Groq's OpenAI-compatible surface lives under `openai/v1/`, so the client
14761
14727
  // path is `openai/v1/chat/completions` rather than `v1/chat/completions`.
14762
14728
  directBaseUrl: "https://api.groq.com/",
14763
- auth: PROVIDER_AUTH.groq
14729
+ auth: { header: "authorization", scheme: "Bearer " },
14730
+ nativeClampStyle: "chat_completions",
14731
+ // Groq's OpenAI-compatible surface is namespaced under `openai/`.
14732
+ probePath: "openai/v1/models",
14733
+ examplePath: "openai/v1/chat/completions"
14764
14734
  },
14765
14735
  mistral: {
14766
14736
  directBaseUrl: "https://api.mistral.ai/",
14767
- auth: PROVIDER_AUTH.mistral,
14737
+ auth: { header: "authorization", scheme: "Bearer " },
14738
+ nativeClampStyle: "chat_completions",
14739
+ probePath: "v1/models",
14768
14740
  modelAuthor: "Mistral"
14769
14741
  },
14770
14742
  together: {
14771
14743
  // `api.together.ai`, not the `.xyz` host older SDKs default to: the current
14772
14744
  // OpenAI-compatibility guide names this one and warns against the other.
14773
14745
  directBaseUrl: "https://api.together.ai/",
14774
- auth: PROVIDER_AUTH.together
14746
+ auth: { header: "authorization", scheme: "Bearer " },
14747
+ nativeClampStyle: "chat_completions",
14748
+ probePath: "v1/models"
14775
14749
  },
14776
14750
  fireworks: {
14777
14751
  // The inference plane is `/inference/v1`; `/v1` on the same host is the
14778
14752
  // control plane, so the client path is `inference/v1/chat/completions`.
14779
14753
  directBaseUrl: "https://api.fireworks.ai/",
14780
- auth: PROVIDER_AUTH.fireworks
14754
+ auth: { header: "authorization", scheme: "Bearer " },
14755
+ nativeClampStyle: "chat_completions",
14756
+ // No `probePath`: its list-models call is `v1/accounts/{account}/models`,
14757
+ // and the account id cannot be derived from the key. Nothing under
14758
+ // `inference/v1/` is documented as a GET.
14759
+ examplePath: "inference/v1/chat/completions"
14781
14760
  },
14782
14761
  cerebras: {
14783
14762
  directBaseUrl: "https://api.cerebras.ai/",
14784
- auth: PROVIDER_AUTH.cerebras
14763
+ auth: { header: "authorization", scheme: "Bearer " },
14764
+ nativeClampStyle: "chat_completions",
14765
+ probePath: "v1/models"
14785
14766
  },
14786
14767
  moonshot: {
14787
14768
  // The international host. `api.moonshot.cn` is the separate China platform
14788
14769
  // and is not reachable with a key issued for this one.
14789
14770
  directBaseUrl: "https://api.moonshot.ai/",
14790
- auth: PROVIDER_AUTH.moonshot,
14771
+ auth: { header: "authorization", scheme: "Bearer " },
14772
+ nativeClampStyle: "chat_completions",
14773
+ probePath: "v1/models",
14791
14774
  modelAuthor: "Moonshot AI"
14792
14775
  },
14793
14776
  huggingface: {
@@ -14797,22 +14780,34 @@ var PROVIDER_REGISTRY = {
14797
14780
  // order of magnitude more on some of them than others. It ships with no
14798
14781
  // catalog section for that reason; see `catalogPrice` in usage.ts.
14799
14782
  directBaseUrl: "https://router.huggingface.co/",
14800
- auth: PROVIDER_AUTH.huggingface
14783
+ auth: { header: "authorization", scheme: "Bearer " },
14784
+ nativeClampStyle: "chat_completions"
14785
+ // No `probePath`: `router.huggingface.co/v1/models` is public — it answers
14786
+ // 200 to a garbage token, so probing it would validate every key. The
14787
+ // endpoint that does check a token lives on a different origin
14788
+ // (`huggingface.co`), which a path under this base URL cannot express.
14801
14789
  },
14802
14790
  baseten: {
14803
14791
  // The Model APIs inference host. `api.baseten.co` is the management plane
14804
14792
  // for dedicated deployments and answers to different paths entirely.
14805
14793
  directBaseUrl: "https://inference.baseten.co/",
14806
- auth: PROVIDER_AUTH.baseten
14794
+ auth: { header: "authorization", scheme: "Bearer " },
14795
+ nativeClampStyle: "chat_completions",
14796
+ probePath: "v1/models"
14807
14797
  },
14808
14798
  bytedance: {
14809
14799
  // BytePlus ModelArk, the international edition: `/api/v3` is its version
14810
14800
  // segment, so client paths carry no `v1`. `ark.cn-beijing.volces.com` is
14811
14801
  // the separate China platform, and `ark.eu-west.bytepluses.com` is a second
14812
14802
  // region — keys and catalogs are region-isolated, so reaching either needs
14813
- // the per-row base URL override that Stage 6 introduces.
14803
+ // the per-row base URL override.
14814
14804
  directBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/",
14815
- auth: PROVIDER_AUTH.bytedance
14805
+ auth: { header: "authorization", scheme: "Bearer " },
14806
+ nativeClampStyle: "chat_completions",
14807
+ // No `probePath`: ModelArk publishes no list-models call, and its own SDK
14808
+ // has no models resource. It also authenticates before it routes, so every
14809
+ // path answers the same 401 and a probe would prove nothing about the key.
14810
+ examplePath: "chat/completions"
14816
14811
  },
14817
14812
  openrouter: {
14818
14813
  // An aggregator treated as a provider type: it is the counterparty that
@@ -14821,19 +14816,57 @@ var PROVIDER_REGISTRY = {
14821
14816
  // `/api/v1` is its documented server URL, so the client path is
14822
14817
  // `v1/chat/completions` under this origin.
14823
14818
  directBaseUrl: "https://openrouter.ai/api/",
14824
- auth: PROVIDER_AUTH.openrouter,
14819
+ auth: { header: "authorization", scheme: "Bearer " },
14820
+ nativeClampStyle: "chat_completions",
14821
+ // OpenRouter's key-status call, not its model list: `v1/models` is public
14822
+ // and answers 200 to any token, exactly the trap `huggingface` has no
14823
+ // probe for. `v1/key` answers 401 to a key that does not exist.
14824
+ probePath: "v1/key",
14825
+ // The one narrowed API surface, and it is a metering constraint rather than
14826
+ // a missing one: OpenRouter also serves `/responses` and `/messages`, but
14827
+ // only its chat-completions response carries `usage.cost`, and its slugs
14828
+ // have no local price. Any other style would proxy traffic nothing could
14829
+ // bill — exactly the silent $0 the fail-closed gate exists to prevent — so
14830
+ // it is refused at the edge instead.
14831
+ apiStyles: ["chat_completions"],
14825
14832
  // Every chat-completions response carries `usage.cost`: what OpenRouter
14826
14833
  // actually charged for that request, which beats any local estimate of a
14827
14834
  // catalog this deployment does not track. It is also the only way to bill
14828
14835
  // the type at all, since no static price list covers 400 models across
14829
14836
  // every lab. The declaration owns the parsing and the header that asks for
14830
- // the routing metadata — see `src/core/cost-report.ts`.
14837
+ // the routing metadata — see `src/shared/cost-report.ts`.
14831
14838
  costReport: OPENROUTER_COST_REPORT,
14832
14839
  // No `modelAuthor`: an aggregator serves everyone's models, and the slug
14833
14840
  // namespace answers per model instead.
14834
14841
  authorNamespacedModels: true
14835
14842
  }
14836
14843
  };
14844
+ var PROVIDER_TYPES = Object.keys(PROVIDER_DESCRIPTORS);
14845
+ function providerDescriptor(type) {
14846
+ return PROVIDER_DESCRIPTORS[type];
14847
+ }
14848
+ function isProviderType(value) {
14849
+ return typeof value === "string" && Object.hasOwn(PROVIDER_DESCRIPTORS, value);
14850
+ }
14851
+ var PROVIDER_CREDENTIAL_HEADERS = [
14852
+ ...new Set(
14853
+ PROVIDER_TYPES.map((type) => providerDescriptor(type).auth.header.toLowerCase())
14854
+ )
14855
+ ];
14856
+ function providerCapability(provider) {
14857
+ const descriptor = providerDescriptor(provider);
14858
+ return {
14859
+ apiStyles: descriptor.apiStyles ?? API_STYLES,
14860
+ endpointStyles: Object.keys(descriptor.endpointPaths ?? {})
14861
+ };
14862
+ }
14863
+ var ENDPOINT_PROVIDER_TYPES = PROVIDER_TYPES.filter(
14864
+ (type) => providerCapability(type).endpointStyles.length > 0
14865
+ );
14866
+ function reportsCost(type) {
14867
+ return isProviderType(type) && providerDescriptor(type).costReport !== void 0;
14868
+ }
14869
+ var COST_REPORTING_PROVIDER_TYPES = PROVIDER_TYPES.filter(reportsCost);
14837
14870
 
14838
14871
  // ../src/core/origin-guard.ts
14839
14872
  var MAX_BASE_URL_LENGTH = 200;
@@ -14943,18 +14976,61 @@ function checkOperatorBaseUrl(input) {
14943
14976
  }
14944
14977
 
14945
14978
  // ../src/contracts/schemas.ts
14979
+ var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
14980
+ var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
14981
+ var APP_ATTEST_ENVIRONMENTS = ["production", "development"];
14982
+ var ENDPOINT_SLUG = /^[a-z0-9-]{1,64}$/;
14983
+ var PROVIDER_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
14984
+ var HTTP_FIELD_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
14985
+ var MONTH_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])$/;
14986
+ var MONTH_FORMAT_MESSAGE = "month must use YYYY-MM format";
14987
+ var MonthSchema = external_exports.string().regex(MONTH_PATTERN, { error: MONTH_FORMAT_MESSAGE });
14988
+ var APPLE_TEAM_ID = /^[A-Z0-9]{10}$/;
14989
+ var APPLE_BUNDLE_ID = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/;
14990
+ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
14991
+ "__proto__",
14992
+ "constructor",
14993
+ "prototype"
14994
+ ]);
14995
+ var RESERVED_KEY_ERROR = "key cannot be __proto__, constructor or prototype";
14996
+ var safeKey = (schema) => schema.refine((key) => !RESERVED_OBJECT_KEYS.has(key), { error: RESERVED_KEY_ERROR });
14946
14997
  var ProviderTypeSchema = external_exports.enum(PROVIDER_TYPES);
14947
14998
  var NullableRequestLimit = external_exports.number().int().positive().nullable();
14948
- var NullableSpendLimit = external_exports.number().nonnegative().nullable();
14949
- var SlugSchema = external_exports.string().regex(PROVIDER_SLUG_PATTERN);
14999
+ var NullableSpendLimit = external_exports.number().nonnegative().refine((usd) => Number.isSafeInteger(Math.round(usd * 1e6)), {
15000
+ error: "monthly_usd is too large"
15001
+ }).nullable();
15002
+ var SlugSchema = safeKey(
15003
+ external_exports.string().regex(PROVIDER_SLUG_PATTERN).meta({
15004
+ description: "Lowercase letters, digits and hyphens, starting with a letter or digit. `constructor` and `prototype` are reserved."
15005
+ })
15006
+ );
15007
+ var StoredSlugSchema = external_exports.string().regex(PROVIDER_SLUG_PATTERN);
14950
15008
  var ClaimRequirementSchema = external_exports.object({
14951
- path: external_exports.string(),
15009
+ path: external_exports.string().min(1),
14952
15010
  contains: external_exports.union([external_exports.string(), external_exports.array(external_exports.string()).min(1)]).optional(),
14953
15011
  equals: external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]).optional()
15012
+ }).strict().superRefine((value, context) => {
15013
+ if (value.contains === void 0 === (value.equals === void 0)) {
15014
+ context.addIssue({
15015
+ code: "custom",
15016
+ message: "Claim requirements need exactly one of contains or equals"
15017
+ });
15018
+ }
14954
15019
  });
14955
- var IssuerClaimValuesSchema = external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]);
15020
+ var IssuerClaimValuesSchema = external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1)]).transform((value) => typeof value === "string" ? [value] : value);
14956
15021
  var IssuerAuthenticationSchema = external_exports.object({
14957
- jwks_url: external_exports.url(),
15022
+ /**
15023
+ * HTTPS only, and stored in the canonical form `URL` prints: the gateway
15024
+ * fetches this on the authentication path, so a plaintext origin would put
15025
+ * the keys that verify every token on the wire for anyone to replace.
15026
+ */
15027
+ jwks_url: external_exports.url({ protocol: /^https$/, error: "Issuer JWKS URLs must be valid and use HTTPS" }).overwrite((value) => {
15028
+ try {
15029
+ return new URL(value).toString();
15030
+ } catch {
15031
+ return value;
15032
+ }
15033
+ }).meta({ description: "An https URL. Stored in the canonical form `URL` prints." }),
14958
15034
  /**
14959
15035
  * Required: several issuers publish one JWKS for every customer — Firebase
14960
15036
  * signs all projects with the same keys — so without `iss` and `aud` a token
@@ -14962,8 +15038,9 @@ var IssuerAuthenticationSchema = external_exports.object({
14962
15038
  */
14963
15039
  issuer: IssuerClaimValuesSchema,
14964
15040
  audience: IssuerClaimValuesSchema,
14965
- user_id_claim: external_exports.string(),
14966
- token_header: external_exports.string().optional(),
15041
+ user_id_claim: external_exports.string().min(1),
15042
+ /** Lowercased, because header lookups ignore case. */
15043
+ token_header: external_exports.string().min(1).toLowerCase().optional(),
14967
15044
  required_claims: external_exports.array(ClaimRequirementSchema),
14968
15045
  max_token_lifetime_seconds: external_exports.number().int().positive(),
14969
15046
  /**
@@ -14971,11 +15048,30 @@ var IssuerAuthenticationSchema = external_exports.object({
14971
15048
  * which kind of paid-user check `required_claims` implements. Bookkeeping for
14972
15049
  * the console, so it can show "Firebase, project X" and reopen the same
14973
15050
  * form: the gateway verifies tokens from the fields above and reads neither.
15051
+ *
15052
+ * Refused rather than dropped when this build does not know the name: this
15053
+ * schema is checked on the way in, so a name it cannot interpret never
15054
+ * reaches storage in the first place.
14974
15055
  */
14975
15056
  provider: external_exports.enum(ISSUER_PROVIDERS).optional(),
14976
15057
  entitlement: external_exports.enum(ENTITLEMENT_CHECKS).optional()
14977
15058
  }).strict();
14978
- var EndUserHeaderSchema = external_exports.string().trim().min(1).max(64).regex(HTTP_FIELD_NAME, { error: "header must be a valid HTTP header name" });
15059
+ var RESERVED_END_USER_HEADERS = /* @__PURE__ */ new Set([
15060
+ "authorization",
15061
+ ...PROVIDER_CREDENTIAL_HEADERS,
15062
+ "x-app-version",
15063
+ "content-type",
15064
+ "content-length",
15065
+ "host"
15066
+ ]);
15067
+ var EndUserHeaderSchema = external_exports.string().trim().toLowerCase().min(1).max(64).regex(HTTP_FIELD_NAME, { error: "must be a valid HTTP header name" }).superRefine((header, context) => {
15068
+ if (RESERVED_END_USER_HEADERS.has(header)) {
15069
+ context.addIssue({
15070
+ code: "custom",
15071
+ message: `cannot be ${header}: the gateway already uses that header`
15072
+ });
15073
+ }
15074
+ });
14979
15075
  var HeaderEndUserSchema = external_exports.object({
14980
15076
  source: external_exports.literal("header"),
14981
15077
  header: EndUserHeaderSchema
@@ -14995,23 +15091,29 @@ var AppAttestEndUserSchema = external_exports.discriminatedUnion("source", [
14995
15091
  IssuerEndUserSchema,
14996
15092
  AppInstallEndUserSchema
14997
15093
  ], { error: "authentication.end_user.source must be one of issuer, app_install" });
15094
+ var AppleAppIdentitySchema = external_exports.object({
15095
+ team_id: external_exports.string().regex(APPLE_TEAM_ID, {
15096
+ error: "team_id must contain ten uppercase letters or digits"
15097
+ }),
15098
+ bundle_id: external_exports.string().regex(APPLE_BUNDLE_ID, {
15099
+ error: "bundle_id must be a reverse DNS identifier"
15100
+ })
15101
+ });
14998
15102
  var AppleAppAttestAuthenticationSchema = external_exports.object({
14999
15103
  type: external_exports.literal("apple_app_attest"),
15000
15104
  end_user: AppAttestEndUserSchema,
15001
- app_attest: external_exports.object({
15002
- team_id: external_exports.string(),
15003
- bundle_id: external_exports.string(),
15105
+ app_attest: AppleAppIdentitySchema.extend({
15004
15106
  /**
15005
15107
  * Which of Apple's two App Attest environments this application accepts.
15006
- * Omitted means `["production"]` alone, because a development-signed build
15108
+ * Defaulted to `["production"]` alone, because a development-signed build
15007
15109
  * stamps a different aaguid and is debuggable on any device carrying the
15008
15110
  * team's provisioning profile. Accepting one is therefore a deliberate
15009
15111
  * per-application opt-in, and belongs on a development bundle id rather
15010
15112
  * than the one shipped to the App Store.
15011
15113
  */
15012
- environments: external_exports.array(external_exports.enum(["production", "development"])).min(1).refine((values) => new Set(values).size === values.length, {
15114
+ environments: external_exports.array(external_exports.enum(APP_ATTEST_ENVIRONMENTS)).min(1, { error: "environments must name at least one environment" }).refine((values) => new Set(values).size === values.length, {
15013
15115
  error: "environments cannot repeat a value"
15014
- }).optional()
15116
+ }).prefault(["production"])
15015
15117
  }).strict()
15016
15118
  }).strict();
15017
15119
  var ApiKeyAuthenticationSchema = external_exports.object({
@@ -15024,59 +15126,96 @@ var ApiKeyAuthenticationSchema = external_exports.object({
15024
15126
  */
15025
15127
  end_user: ApiKeyEndUserSchema.optional()
15026
15128
  }).strict();
15129
+ var AllowedPathSchema = external_exports.union([
15130
+ external_exports.string().min(1),
15131
+ external_exports.object({
15132
+ path: external_exports.string().min(1),
15133
+ fixed_model: external_exports.string().min(1).optional(),
15134
+ clamp: external_exports.enum(OUTPUT_CLAMP_STYLES).optional()
15135
+ }).strict()
15136
+ ]);
15027
15137
  var ProviderPolicySchema = external_exports.object({
15028
- allowed_paths: external_exports.array(external_exports.union([
15029
- external_exports.string(),
15030
- external_exports.object({
15031
- path: external_exports.string(),
15032
- fixed_model: external_exports.string().optional(),
15033
- clamp: external_exports.enum(["responses", "chat_completions", "gemini_native", "anthropic", "none"]).optional()
15034
- })
15035
- ])),
15036
- allowed_models: external_exports.array(external_exports.string()),
15138
+ allowed_paths: external_exports.array(AllowedPathSchema),
15139
+ allowed_models: external_exports.array(external_exports.string().min(1)),
15037
15140
  max_output_tokens: external_exports.number().int().positive().optional()
15038
- });
15141
+ }).strict();
15039
15142
  var EndpointTargetSchema = external_exports.object({
15040
15143
  provider: SlugSchema,
15041
15144
  model: external_exports.string().min(1)
15042
15145
  });
15043
15146
  var EndpointSchema = EndpointTargetSchema.extend({
15044
- api_style: external_exports.enum(["responses", "transcription"]),
15147
+ api_style: external_exports.enum(ENDPOINT_API_STYLES),
15045
15148
  params: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
15046
15149
  max_output_tokens: external_exports.number().int().positive().optional(),
15047
15150
  fallback: external_exports.array(EndpointTargetSchema).optional()
15048
- });
15151
+ }).strict();
15049
15152
  var LimitScopeSchema = external_exports.object({
15050
15153
  requests: external_exports.object({
15051
15154
  per_minute: NullableRequestLimit,
15052
15155
  per_day: NullableRequestLimit
15053
- }),
15054
- spending: external_exports.object({ monthly_usd: NullableSpendLimit })
15055
- });
15156
+ }).strict(),
15157
+ spending: external_exports.object({ monthly_usd: NullableSpendLimit }).strict()
15158
+ }).strict();
15159
+ var UNLIMITED_SCOPE = {
15160
+ requests: { per_minute: null, per_day: null },
15161
+ spending: { monthly_usd: null }
15162
+ };
15163
+ function identifiesEndUsers(authentication) {
15164
+ return authentication.type !== "api_key" || authentication.end_user !== void 0;
15165
+ }
15166
+ var scopeHasLimits = (scope) => scope.requests.per_minute !== null || scope.requests.per_day !== null || scope.spending.monthly_usd !== null;
15056
15167
  var AppConfigSchema = external_exports.object({
15057
15168
  authentication: external_exports.discriminatedUnion("type", [
15058
15169
  AppleAppAttestAuthenticationSchema,
15059
15170
  ApiKeyAuthenticationSchema
15060
15171
  ]),
15061
15172
  routing: external_exports.object({
15062
- providers: external_exports.object({
15063
- mode: external_exports.enum(["all", "selected"]),
15064
- selected: external_exports.record(SlugSchema, ProviderPolicySchema).optional()
15065
- }),
15066
- model_rewrites: external_exports.record(external_exports.string(), external_exports.string())
15067
- }),
15173
+ /**
15174
+ * Discriminated, because the two modes carry different fields: `all` names
15175
+ * nothing and `selected` must name its policies. Declaring `selected` as an
15176
+ * optional member of one object would make "all mode with a selection" and
15177
+ * "selected mode with nothing selected" both expressible.
15178
+ */
15179
+ providers: external_exports.discriminatedUnion("mode", [
15180
+ external_exports.object({ mode: external_exports.literal("all") }).strict(),
15181
+ external_exports.object({
15182
+ mode: external_exports.literal("selected"),
15183
+ selected: external_exports.record(SlugSchema, ProviderPolicySchema, {
15184
+ error: "routing.providers.selected must be keyed by provider instance slug"
15185
+ })
15186
+ }).strict()
15187
+ ], { error: "routing.providers.mode must be all or selected" }),
15188
+ model_rewrites: external_exports.record(safeKey(external_exports.string().min(1)), external_exports.string().min(1), {
15189
+ error: "routing.model_rewrites must be keyed by a model name"
15190
+ })
15191
+ }).strict(),
15068
15192
  /**
15069
- * The organization's own limits on its app's end users. Optional: an absent
15070
- * block is unlimited, so an app that never sets one stores nothing.
15193
+ * The organization's own limits on its app's end users, always present on a
15194
+ * parsed configuration: an unwritten block is the unlimited one.
15071
15195
  */
15072
15196
  limits: external_exports.object({
15073
- per_user: LimitScopeSchema,
15074
- per_app: LimitScopeSchema
15075
- }).optional(),
15076
- endpoints: external_exports.record(external_exports.string().regex(ENDPOINT_SLUG), EndpointSchema).optional()
15197
+ per_user: LimitScopeSchema.prefault(UNLIMITED_SCOPE),
15198
+ per_app: LimitScopeSchema.prefault(UNLIMITED_SCOPE)
15199
+ }).strict().prefault({}),
15200
+ endpoints: external_exports.record(
15201
+ safeKey(external_exports.string().regex(ENDPOINT_SLUG)),
15202
+ EndpointSchema,
15203
+ { error: "is not a valid slug; use 1-64 characters from a-z, 0-9, and -" }
15204
+ ).prefault({})
15205
+ }).strict().superRefine((config2, context) => {
15206
+ const authentication = config2.authentication;
15207
+ const perUser = config2.limits?.per_user;
15208
+ if (authentication !== void 0 && perUser !== void 0 && !identifiesEndUsers(authentication) && scopeHasLimits(perUser)) {
15209
+ context.addIssue({
15210
+ code: "custom",
15211
+ path: ["limits", "per_user"],
15212
+ message: "needs an authentication.end_user source: this application identifies no end users, so use limits.per_app"
15213
+ });
15214
+ }
15077
15215
  }).meta({ id: "AppConfig" });
15078
15216
  var AppWriteSchema = external_exports.object({
15079
- name: external_exports.string().min(1).max(100),
15217
+ /** Trimmed, so a name of nothing but spaces is the empty name it looks like. */
15218
+ name: external_exports.string().trim().min(1).max(100),
15080
15219
  config: AppConfigSchema,
15081
15220
  status: external_exports.enum(["active", "disabled"]).optional()
15082
15221
  }).strict().meta({ id: "AppWrite" });
@@ -15104,7 +15243,7 @@ var ApiKeyTokenRequestSchema = external_exports.object({
15104
15243
  var UsageRepriceRequestSchema = external_exports.object({
15105
15244
  provider: ProviderTypeSchema,
15106
15245
  model: external_exports.string().min(1),
15107
- month: external_exports.string().regex(/^\d{4}-\d{2}$/),
15246
+ month: MonthSchema,
15108
15247
  apply: external_exports.boolean().default(false)
15109
15248
  }).strict().meta({ id: "UsageRepriceRequest" });
15110
15249
  var ProviderPricingSchema = external_exports.record(
@@ -15130,15 +15269,17 @@ var GatewayRouteConfigSchema = external_exports.object({
15130
15269
  modelPrefix: external_exports.string().trim().min(1).max(100).optional(),
15131
15270
  providerOnly: external_exports.array(external_exports.string().trim().min(1).max(100)).min(1).max(20).optional()
15132
15271
  }).strict().meta({ id: "GatewayRouteConfig" });
15133
- var ProviderCreateRequestSchema = external_exports.object({
15272
+ var ProviderCreateFieldsSchema = external_exports.object({
15134
15273
  type: ProviderTypeSchema,
15135
15274
  name: ProviderNameSchema,
15136
15275
  slug: SlugSchema.optional(),
15137
- secret: ProviderSecretSchema.optional(),
15138
15276
  providerGatewayId: external_exports.string().trim().min(1).optional(),
15139
15277
  gatewayRoute: GatewayRouteConfigSchema.optional(),
15140
15278
  baseUrl: ProviderBaseUrlSchema.optional(),
15141
15279
  pricing: ProviderPricingSchema.optional()
15280
+ });
15281
+ var ProviderCreateRequestSchema = ProviderCreateFieldsSchema.extend({
15282
+ secret: ProviderSecretSchema.optional()
15142
15283
  }).strict().superRefine((value, context) => {
15143
15284
  if (value.secret === void 0 === (value.providerGatewayId === void 0)) {
15144
15285
  context.addIssue({
@@ -15165,21 +15306,22 @@ var ProviderTestRequestSchema = external_exports.object({
15165
15306
  }
15166
15307
  assertBaseUrlIsDirect(value, context);
15167
15308
  }).meta({ id: "ProviderTestRequest" });
15309
+ var CfAigGatewayFieldsSchema = external_exports.object({
15310
+ type: external_exports.literal("cf_aig"),
15311
+ name: ProviderNameSchema,
15312
+ accountId: external_exports.string().trim().min(1).max(100),
15313
+ gatewayId: external_exports.string().trim().min(1).max(100)
15314
+ });
15315
+ var VercelGatewayFieldsSchema = external_exports.object({
15316
+ type: external_exports.literal("vercel"),
15317
+ name: ProviderNameSchema
15318
+ });
15319
+ var GATEWAY_TYPE_ERROR = "Provider gateway type must be one of cf_aig, vercel";
15168
15320
  var ProviderGatewayCreateRequestSchema = external_exports.discriminatedUnion("type", [
15169
- external_exports.object({
15170
- type: external_exports.literal("cf_aig"),
15171
- name: ProviderNameSchema,
15172
- accountId: external_exports.string().trim().min(1).max(100),
15173
- gatewayId: external_exports.string().trim().min(1).max(100),
15174
- token: ProviderSecretSchema
15175
- }).strict(),
15176
- external_exports.object({
15177
- type: external_exports.literal("vercel"),
15178
- name: ProviderNameSchema,
15179
- token: ProviderSecretSchema
15180
- }).strict()
15321
+ CfAigGatewayFieldsSchema.extend({ token: ProviderSecretSchema }).strict(),
15322
+ VercelGatewayFieldsSchema.extend({ token: ProviderSecretSchema }).strict()
15181
15323
  ], {
15182
- error: "Provider gateway type must be one of cf_aig, vercel"
15324
+ error: GATEWAY_TYPE_ERROR
15183
15325
  }).meta({ id: "ProviderGatewayCreateRequest" });
15184
15326
  var ProviderGatewayTestRequestSchema = external_exports.discriminatedUnion("type", [
15185
15327
  external_exports.object({
@@ -15204,10 +15346,8 @@ var ProviderGatewayRotateRequestSchema = external_exports.object({
15204
15346
  revision: external_exports.number().int().positive().meta({ description: "The gateway revision returned by the read this rotation is based on." })
15205
15347
  }).strict().meta({ id: "ProviderGatewayRotateRequest" });
15206
15348
  var BASE_URL_REQUIRES_SECRET = "Changing the base URL requires re-supplying the provider key, because the stored key is never sent to a new origin";
15207
- var ProviderUpdateRequestSchema = external_exports.object({
15208
- revision: external_exports.number().int().positive().meta({ description: "The provider revision returned by the read this update is based on." }),
15349
+ var ProviderUpdateFieldsSchema = external_exports.object({
15209
15350
  name: ProviderNameSchema.optional(),
15210
- secret: ProviderSecretSchema.optional(),
15211
15351
  /** A full replace; `null` clears the row's routing configuration. */
15212
15352
  gatewayRoute: GatewayRouteConfigSchema.nullable().optional(),
15213
15353
  /**
@@ -15223,6 +15363,10 @@ var ProviderUpdateRequestSchema = external_exports.object({
15223
15363
  * nothing can take the slug meanwhile and re-enabling always succeeds.
15224
15364
  */
15225
15365
  status: external_exports.enum(["active", "disabled"]).optional()
15366
+ });
15367
+ var ProviderUpdateRequestSchema = ProviderUpdateFieldsSchema.extend({
15368
+ revision: external_exports.number().int().positive().meta({ description: "The provider revision returned by the read this update is based on." }),
15369
+ secret: ProviderSecretSchema.optional()
15226
15370
  }).strict().superRefine((value, context) => {
15227
15371
  if (value.name === void 0 && value.secret === void 0 && value.gatewayRoute === void 0 && value.baseUrl === void 0 && value.pricing === void 0 && value.status === void 0) {
15228
15372
  context.addIssue({
@@ -15234,12 +15378,73 @@ var ProviderUpdateRequestSchema = external_exports.object({
15234
15378
  context.addIssue({ code: "custom", message: BASE_URL_REQUIRES_SECRET, path: ["secret"] });
15235
15379
  }
15236
15380
  }).meta({ id: "ProviderUpdateRequest" });
15381
+ var HandoffTargetFields = {
15382
+ /** The existing row the handoff edits. */
15383
+ id: external_exports.string().trim().min(1),
15384
+ /** The revision the CLI read, when it read one; the server pins the current one either way. */
15385
+ revision: external_exports.number().int().positive().optional()
15386
+ };
15387
+ var HandoffProviderAddPayloadSchema = ProviderCreateFieldsSchema.strict().superRefine(assertBaseUrlIsDirect);
15388
+ var HandoffProviderUpdatePayloadSchema = ProviderUpdateFieldsSchema.extend(HandoffTargetFields).strict().superRefine((value, context) => {
15389
+ if (value.name === void 0 && value.gatewayRoute === void 0 && value.baseUrl === void 0 && value.pricing === void 0 && value.status === void 0) {
15390
+ context.addIssue({
15391
+ code: "custom",
15392
+ message: "Provide at least one of name, gatewayRoute, baseUrl, pricing, or status"
15393
+ });
15394
+ }
15395
+ });
15396
+ var HandoffRotatePayloadSchema = external_exports.object(HandoffTargetFields).strict();
15397
+ var HandoffProviderGatewayAddPayloadSchema = external_exports.discriminatedUnion("type", [
15398
+ CfAigGatewayFieldsSchema.strict(),
15399
+ VercelGatewayFieldsSchema.strict()
15400
+ ], { error: GATEWAY_TYPE_ERROR });
15237
15401
  var OrganizationRoleSchema = external_exports.enum(["owner", "admin", "member"]);
15238
15402
  var OrganizationSelectRequestSchema = external_exports.object({
15239
15403
  organizationId: external_exports.string().trim().min(1)
15240
15404
  }).meta({ id: "OrganizationSelectRequest" });
15405
+ var CredentialNameRequestSchema = external_exports.object({
15406
+ name: external_exports.string().trim().min(1).max(100)
15407
+ });
15408
+ var ManagementKeyCreateRequestSchema = CredentialNameRequestSchema.meta({ id: "ManagementKeyCreateRequest" });
15409
+ var ApiKeyCreateRequestSchema = CredentialNameRequestSchema.meta({ id: "ApiKeyCreateRequest" });
15410
+
15411
+ // ../src/shared/app-config.ts
15412
+ function selectedProviderPolicies(routing) {
15413
+ return routing.providers.mode === "selected" ? routing.providers.selected : {};
15414
+ }
15415
+ function providerPolicyFor(routing, slug) {
15416
+ if (routing.providers.mode === "all") return { allowed_paths: [], allowed_models: [] };
15417
+ return Object.hasOwn(routing.providers.selected, slug) ? routing.providers.selected[slug] : void 0;
15418
+ }
15419
+ function reachableProviders(routing, instances) {
15420
+ return instances.filter(
15421
+ (instance) => instance.status === "active" && providerPolicyFor(routing, instance.slug) !== void 0
15422
+ );
15423
+ }
15241
15424
 
15242
15425
  // ../src/contracts/responses.ts
15426
+ var USAGE_STATUSES = [
15427
+ "ok",
15428
+ "provider_error",
15429
+ "blocked_app_rate",
15430
+ "blocked_app_budget",
15431
+ "blocked_billing",
15432
+ "blocked_user"
15433
+ ];
15434
+ var USAGE_BREAKDOWN_DIMENSIONS = [
15435
+ "model",
15436
+ "provider",
15437
+ "provider_slug",
15438
+ "provider_gateway",
15439
+ "credential_source",
15440
+ "model_author",
15441
+ "user",
15442
+ "status",
15443
+ "cost_source",
15444
+ "route",
15445
+ "endpoint",
15446
+ "app_version"
15447
+ ];
15243
15448
  var ErrorResponseSchema = external_exports.object({
15244
15449
  error: external_exports.object({
15245
15450
  code: external_exports.string(),
@@ -15267,6 +15472,24 @@ var AppAttestChallengeResponseSchema = external_exports.object({
15267
15472
  expires_in: external_exports.number()
15268
15473
  });
15269
15474
  var AppAttestRegisterResponseSchema = external_exports.object({ user_id: external_exports.string() });
15475
+ var GatewayTokenResponseSchema = external_exports.object({
15476
+ access_token: external_exports.string(),
15477
+ expires_in: external_exports.number()
15478
+ });
15479
+ var CurrentUserResponseSchema = external_exports.object({
15480
+ user_id: external_exports.string(),
15481
+ limits: external_exports.object({
15482
+ requests_today: external_exports.number().int().nullable().meta({ description: "Requests this user has made so far in the current UTC day. Null when the application sets no per-user limit, in which case requests are not counted at all." }),
15483
+ requests_remaining: external_exports.number().int().nullable().meta({ description: "What is left of requests_per_day. Null when no daily limit is set." }),
15484
+ requests_per_minute: external_exports.number().nullable().meta({ description: "The per-user per-minute limit this app sets. Null means unlimited." }),
15485
+ requests_per_day: external_exports.number().nullable().meta({ description: "The per-user per-day limit this app sets. Null means unlimited." }),
15486
+ monthly_cost_usd: external_exports.number().meta({ description: "What this user's traffic has cost so far in the current UTC calendar month." }),
15487
+ monthly_budget_usd: external_exports.number().nullable().meta({ description: "The per-user monthly spending budget this app sets. Null means unlimited." }),
15488
+ blocked: external_exports.boolean().meta({ description: "Whether this user has been blocked in the console." })
15489
+ }).meta({
15490
+ description: "The limits the app sets on this user, and where the user stands against them. The account's request allowance is not reported here: it is shared by all apps, and is reported on the rejection that spends it."
15491
+ })
15492
+ });
15270
15493
  var UsageEventSchema = external_exports.object({
15271
15494
  id: external_exports.number().int(),
15272
15495
  /** Null for an application that identifies no end users. */
@@ -15310,7 +15533,7 @@ var UsageEventSchema = external_exports.object({
15310
15533
  }),
15311
15534
  app_version: external_exports.string().nullable(),
15312
15535
  auth_method: external_exports.enum(["attest", "api_key"]).nullable(),
15313
- status: external_exports.enum(["ok", "provider_error", "blocked_app_rate", "blocked_app_budget", "blocked_billing", "blocked_user"]),
15536
+ status: external_exports.enum(USAGE_STATUSES),
15314
15537
  client_aborted: external_exports.number().int().nullable().meta({
15315
15538
  description: "1 when the client disconnected before the upstream finished streaming, which cancelled the provider call; null otherwise. Not a failure \u2014 the request was served as far as the caller wanted it \u2014 but an aborted stream often takes the provider's end-of-response usage with it, which is why such an event may carry cost_source unresolved."
15316
15539
  }),
@@ -15389,18 +15612,18 @@ var AppResponseSchema = external_exports.object({
15389
15612
  id: external_exports.string().meta({ description: "The gateway-assigned id, and the `{app}` segment of every URL for this application." }),
15390
15613
  name: external_exports.string(),
15391
15614
  /**
15392
- * An `AppConfig`, except on the one row this shape's own `config_error`
15393
- * describes: a configuration written before a schema change is returned as
15394
- * it is stored, so an operator can read and repair it. Declared as the
15395
- * union rather than as `AppConfig` alone because a client that parses this
15396
- * response must still be able to read such a row.
15615
+ * An `AppConfig` — the parsed one, which is also the stored one: what the
15616
+ * gateway accepts is what it keeps, so there is no second "resolved" view
15617
+ * of it to publish. The one exception is the row this shape's own
15618
+ * `config_error` describes: a configuration written before a schema change
15619
+ * is returned as it is stored, so an operator can read and repair it, and
15620
+ * that is why this is declared as the union rather than as `AppConfig`.
15397
15621
  */
15398
15622
  config: external_exports.union([AppConfigSchema, external_exports.record(external_exports.string(), external_exports.unknown())]),
15399
15623
  status: external_exports.enum(["active", "disabled"]),
15400
15624
  created_at: external_exports.string(),
15401
15625
  updated_at: external_exports.string()
15402
15626
  }),
15403
- resolved: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().meta({ description: "The configuration as the request path resolves it, with provider routing and limits applied. Null when the stored configuration does not parse, which is the one case `app.config` is not an AppConfig." }),
15404
15627
  config_error: external_exports.string().nullable().meta({ description: "Why the stored configuration does not parse, for a row written before a schema change. Always null on create and update, which validate before they write." })
15405
15628
  }).meta({ id: "AppResponse" });
15406
15629
  var AppDeleteResponseSchema = external_exports.object({
@@ -15435,7 +15658,7 @@ var ManagementKeyResponseSchema = external_exports.object({ key: ManagementKeySu
15435
15658
  var ProviderSummarySchema = external_exports.object({
15436
15659
  id: external_exports.string(),
15437
15660
  type: external_exports.enum(PROVIDER_TYPES),
15438
- slug: SlugSchema.meta({ description: "The URL segment used under /proxy/{slug}/, unique across your providers." }),
15661
+ slug: StoredSlugSchema.meta({ description: "The URL segment used under /proxy/{slug}/, unique across your providers." }),
15439
15662
  name: external_exports.string(),
15440
15663
  secretHint: external_exports.string().nullable().meta({
15441
15664
  description: "Last characters of a direct provider key; null when a shared provider gateway owns the token."
@@ -15611,13 +15834,17 @@ var AppSummarySchema = external_exports.object({
15611
15834
  });
15612
15835
  var AppListResponseSchema = external_exports.object({
15613
15836
  month: external_exports.string(),
15614
- has_proxied_requests: external_exports.boolean(),
15837
+ has_proxied_requests: external_exports.boolean().meta({
15838
+ description: "Whether this account has ever had a request recorded, at any time. Unlike the per-application `usage` totals beside it, which cover `month` only, this does not reset when a new month begins, and it never goes from true back to false. Intended for first-run interfaces that stop offering setup guidance once traffic has started."
15839
+ }),
15615
15840
  apps: external_exports.array(AppSummarySchema)
15616
15841
  });
15617
15842
  var AppValidateResponseSchema = external_exports.object({
15618
15843
  valid: external_exports.literal(true),
15619
- app_id: external_exports.string(),
15620
- exists: external_exports.boolean()
15844
+ app_id: external_exports.string()
15845
+ });
15846
+ var AppDraftValidateResponseSchema = external_exports.object({
15847
+ valid: external_exports.literal(true)
15621
15848
  });
15622
15849
  var CreatedApiKeySchema = external_exports.object({
15623
15850
  id: external_exports.string(),
@@ -15700,11 +15927,32 @@ var BreakdownRowSchema = UsageTotalsSchema.extend({
15700
15927
  });
15701
15928
  var BreakdownResponseSchema = external_exports.object({
15702
15929
  app_id: external_exports.string(),
15703
- by: external_exports.string(),
15930
+ by: external_exports.enum(USAGE_BREAKDOWN_DIMENSIONS),
15704
15931
  from: external_exports.string(),
15705
15932
  to: external_exports.string(),
15706
15933
  rows: external_exports.array(BreakdownRowSchema)
15707
15934
  });
15935
+ var UsageRepriceResponseSchema = external_exports.object({
15936
+ app_id: external_exports.string(),
15937
+ provider: external_exports.string(),
15938
+ model: external_exports.string(),
15939
+ month: external_exports.string(),
15940
+ applied: external_exports.boolean(),
15941
+ matched_events: external_exports.number().int(),
15942
+ unmetered_events: external_exports.number().int().meta({
15943
+ description: "Matched events that carried no readable usage, and so were repriced to the zero their zero counts imply while keeping whatever cost_source they had. A non-zero number means the month contains spend nothing could meter, which repricing cannot fix and must not appear to have fixed."
15944
+ }),
15945
+ unpriced_events: external_exports.number().int().meta({
15946
+ description: "Dry-run only: matched events whose serving instance can no longer price them."
15947
+ }),
15948
+ unpriced_cost_usd: external_exports.number(),
15949
+ previous_cost_usd: external_exports.number(),
15950
+ recalculated_cost_usd: external_exports.number(),
15951
+ delta_usd: external_exports.number(),
15952
+ reconciled_users: external_exports.number().int().meta({
15953
+ description: "End-user spend ledgers reprojected inside this request; the rest are left to scheduled recovery."
15954
+ })
15955
+ });
15708
15956
  var ModelPriceSchema = external_exports.object({
15709
15957
  input: external_exports.number().optional(),
15710
15958
  output: external_exports.number().optional(),
@@ -15725,18 +15973,25 @@ var PricesResponseSchema = external_exports.object({
15725
15973
  // ../src/contracts/cli.ts
15726
15974
  var CliProofSchema = external_exports.string().regex(/^[A-Za-z0-9_-]{32,256}$/);
15727
15975
  var CliBootstrapRequestSchema = external_exports.object({ idempotencyKey: CliProofSchema, pollToken: CliProofSchema }).strict();
15728
- var CliOperationRequestSchema = external_exports.object({
15729
- kind: external_exports.enum([
15730
- "claim",
15731
- "provider.add",
15732
- "provider.rotate-key",
15733
- "provider.update",
15734
- "provider-gateway.add",
15735
- "provider-gateway.rotate-key"
15736
- ]),
15737
- payload: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15738
- pollToken: CliProofSchema
15739
- }).strict();
15976
+ var CliOperationKindSchema = external_exports.enum([
15977
+ "claim",
15978
+ "provider.add",
15979
+ "provider.rotate-key",
15980
+ "provider.update",
15981
+ "provider-gateway.add",
15982
+ "provider-gateway.rotate-key"
15983
+ ]);
15984
+ function operationRequest(kind, payload) {
15985
+ return external_exports.object({ kind: external_exports.literal(kind), payload, pollToken: CliProofSchema }).strict();
15986
+ }
15987
+ var CliOperationRequestSchema = external_exports.discriminatedUnion("kind", [
15988
+ operationRequest("claim", external_exports.object({}).strict().default({})),
15989
+ operationRequest("provider.add", HandoffProviderAddPayloadSchema),
15990
+ operationRequest("provider.rotate-key", HandoffRotatePayloadSchema),
15991
+ operationRequest("provider.update", HandoffProviderUpdatePayloadSchema),
15992
+ operationRequest("provider-gateway.add", HandoffProviderGatewayAddPayloadSchema),
15993
+ operationRequest("provider-gateway.rotate-key", HandoffRotatePayloadSchema)
15994
+ ]);
15740
15995
  var CliSubmissionRequestSchema = external_exports.object({
15741
15996
  submissionToken: CliProofSchema,
15742
15997
  approve: external_exports.literal(true).optional(),
@@ -15767,7 +16022,7 @@ var CliViewerSchema = external_exports.object({
15767
16022
  email: external_exports.string().nullable()
15768
16023
  });
15769
16024
  var CliBrowserDetailsResponseSchema = external_exports.object({
15770
- kind: CliOperationRequestSchema.shape.kind,
16025
+ kind: CliOperationKindSchema,
15771
16026
  payload: external_exports.record(external_exports.string(), external_exports.unknown()),
15772
16027
  account: CliAccountSchema,
15773
16028
  viewer: CliViewerSchema.nullable(),
@@ -15800,11 +16055,17 @@ var CliBrowserGoogleResponseSchema = external_exports.object({
15800
16055
  var CliCredentialSchema = external_exports.object({
15801
16056
  token: external_exports.string()
15802
16057
  });
16058
+ var UnclaimedAccessSchema = external_exports.object({ endsAt: external_exports.string(), limit: external_exports.number().int().optional() }).nullable();
15803
16059
  var CliBootstrapResponseSchema = external_exports.object({
15804
16060
  deployment: CliDeploymentSchema,
15805
16061
  account: CliAccountSchema,
15806
16062
  credential: CliCredentialSchema,
15807
- trial: external_exports.object({ endsAt: external_exports.string(), limit: external_exports.number().int().optional() }).nullable()
16063
+ /**
16064
+ * On a hosted deployment, when this unclaimed account's free access ends and
16065
+ * the request allowance it has until then; null on a self-host, where an
16066
+ * account has no such window.
16067
+ */
16068
+ unclaimedAccess: UnclaimedAccessSchema
15808
16069
  });
15809
16070
  var CliOperationResponseSchema = external_exports.object({
15810
16071
  id: external_exports.string(),
@@ -15921,57 +16182,6 @@ var CliAccountResponseSchema = external_exports.object({
15921
16182
  billing: CliBillingSchema,
15922
16183
  usage: CliQuotaUsageSchema
15923
16184
  });
15924
-
15925
- // ../src/contracts/operation-schemas.ts
15926
- var responseSchemas = {
15927
- getConsoleCapabilities: ConsoleCapabilitiesResponseSchema,
15928
- getAdminSession: IdentitySessionSchema,
15929
- listOrganizations: OrganizationListResponseSchema,
15930
- selectOrganization: IdentitySessionSchema,
15931
- listManagementKeys: ManagementKeyListResponseSchema,
15932
- createManagementKey: CreatedManagementKeyResponseSchema,
15933
- revokeManagementKey: ManagementKeyResponseSchema,
15934
- listProviders: ProviderListResponseSchema,
15935
- createProvider: ProviderResponseSchema,
15936
- testProvider: ProviderTestResponseSchema,
15937
- updateProvider: ProviderResponseSchema,
15938
- deleteProvider: ProviderDeleteResponseSchema,
15939
- listProviderGateways: ProviderGatewayListResponseSchema,
15940
- createProviderGateway: ProviderGatewayResponseSchema,
15941
- testProviderGateway: ProviderGatewayTestResponseSchema,
15942
- updateProviderGateway: ProviderGatewayResponseSchema,
15943
- rotateProviderGateway: ProviderGatewayResponseSchema,
15944
- deleteProviderGateway: ProviderGatewayDeleteResponseSchema,
15945
- listApps: AppListResponseSchema,
15946
- createApp: CreatedAppResponseSchema,
15947
- getApp: AppResponseSchema,
15948
- updateApp: AppResponseSchema,
15949
- deleteApp: AppDeleteResponseSchema,
15950
- validateApp: AppValidateResponseSchema,
15951
- listAppKeys: ApiKeyListResponseSchema,
15952
- createAppKey: CreatedApiKeySchema,
15953
- revokeAppKey: ApiKeyRevokeResponseSchema,
15954
- listAppUsers: UserListResponseSchema,
15955
- setAppUserBlocked: UserBlockResponseSchema,
15956
- getAppUsage: MonthlyUsageResponseSchema,
15957
- getAppUsageTimeseries: TimeseriesResponseSchema,
15958
- getAppUsageBreakdown: BreakdownResponseSchema,
15959
- listAppEvents: UsageEventListSchema,
15960
- getAppAuthEventSummary: AuthEventSummarySchema,
15961
- listAppAuthEvents: AuthEventListSchema,
15962
- listModelPrices: PricesResponseSchema,
15963
- getCliCapabilities: CliCapabilitiesResponseSchema,
15964
- bootstrapCliAccount: CliBootstrapResponseSchema,
15965
- createCliOperation: CliOperationResponseSchema,
15966
- pollCliOperation: CliPollResponseSchema,
15967
- getCliAccount: CliAccountResponseSchema,
15968
- getCliUsage: CliUsageResponseSchema,
15969
- cliBrowserDetails: CliBrowserDetailsResponseSchema,
15970
- cliBrowserSubmit: CliBrowserSubmitResponseSchema,
15971
- cliBrowserRegister: CliBrowserRegisterResponseSchema,
15972
- cliBrowserGoogle: CliBrowserGoogleResponseSchema
15973
- };
15974
- var responseSchemaFor = responseSchemas;
15975
16185
  var CliErrorDetailsSchema = external_exports.object({
15976
16186
  status: external_exports.number().optional(),
15977
16187
  appId: external_exports.string().optional(),
@@ -16006,7 +16216,1058 @@ var CliErrorDetailsSchema = external_exports.object({
16006
16216
  // src/apps.ts
16007
16217
  import { createHash as createHash2 } from "node:crypto";
16008
16218
 
16009
- // ../src/contracts/operations.ts
16219
+ // ../src/contracts/billing.ts
16220
+ var BillingSubscriptionStatusSchema = external_exports.enum([
16221
+ "on_trial",
16222
+ "active",
16223
+ "paused",
16224
+ "past_due",
16225
+ "unpaid",
16226
+ "cancelled",
16227
+ "expired"
16228
+ ]);
16229
+ var BillingSourceSchema = external_exports.enum(["lemon_squeezy", "manual"]);
16230
+ var BillingPeriodSchema = external_exports.enum(["month", "year"]);
16231
+ var EntitledPlanSchema = external_exports.object({
16232
+ planKey: external_exports.string(),
16233
+ planName: external_exports.string(),
16234
+ limits: external_exports.unknown().optional(),
16235
+ /** True when it came from the service's default plan rather than a subscription. */
16236
+ isDefault: external_exports.boolean()
16237
+ }).meta({ id: "EntitledPlan" });
16238
+ var SubscriptionStateSchema = external_exports.object({
16239
+ subscriptionId: external_exports.string().nullable(),
16240
+ status: BillingSubscriptionStatusSchema,
16241
+ planKey: external_exports.string(),
16242
+ planName: external_exports.string(),
16243
+ /** Null for trials and manual grants, which carry no price. */
16244
+ billingPeriod: BillingPeriodSchema.nullable(),
16245
+ renewsAt: external_exports.string().nullable(),
16246
+ endsAt: external_exports.string().nullable(),
16247
+ trialEndsAt: external_exports.string().nullable(),
16248
+ source: BillingSourceSchema,
16249
+ /** This subscription generation */
16250
+ createdAt: external_exports.string(),
16251
+ updatedAt: external_exports.string(),
16252
+ /** Original provider day, retained when short months clamp the exact anchor. */
16253
+ billingAnchorDay: external_exports.number().nullable(),
16254
+ /** Exact normalized UTC origin of the monthly allowance schedule. */
16255
+ billingAnchorAt: external_exports.string(),
16256
+ /** When the current allowance schedule took effect. */
16257
+ billingScheduleUpdatedAt: external_exports.string()
16258
+ }).meta({ id: "Subscription" });
16259
+ var BillingAccessSchema = external_exports.object({
16260
+ /** `null` means no entitlement at all, and the gateway refuses the request. */
16261
+ plan: EntitledPlanSchema.nullable(),
16262
+ /** `null` means the organization has no billing row. */
16263
+ subscription: SubscriptionStateSchema.nullable()
16264
+ });
16265
+ var GatewayBillingAccessSchema = external_exports.discriminatedUnion("state", [
16266
+ /** No `BILLING` binding: self-hosted, unlimited, never refused. */
16267
+ external_exports.object({ state: external_exports.literal("self_hosted") }),
16268
+ /** The billing RPC failed. The allowance is unknown, so traffic waits. */
16269
+ external_exports.object({ state: external_exports.literal("unavailable"), billingErrorCode: external_exports.string().optional() }),
16270
+ /** The billing service answered. `plan === null` means no entitlement at all. */
16271
+ external_exports.object({
16272
+ state: external_exports.literal("billed"),
16273
+ plan: EntitledPlanSchema.nullable(),
16274
+ subscription: SubscriptionStateSchema.nullable(),
16275
+ /**
16276
+ * Set when the billing service could not be reached and this is the last
16277
+ * reading it gave for the organization. Entitlement decisions are
16278
+ * unchanged: a stale `plan === null` is still a paywall.
16279
+ */
16280
+ stale: external_exports.literal(true).optional(),
16281
+ /** Why the refresh failed, on a stale reading. */
16282
+ billingErrorCode: external_exports.string().optional()
16283
+ })
16284
+ ]).meta({ id: "BillingAccess" });
16285
+ var PlanLimitsSchema = external_exports.object({
16286
+ maxRequestsPerMonth: external_exports.number().int().optional().meta({ description: "Requests the account may dispatch per allowance period." }),
16287
+ maxApps: external_exports.number().int().optional().meta({ description: "Applications the account may own." }),
16288
+ maxProviders: external_exports.number().int().optional().meta({ description: "Providers the account may own." }),
16289
+ maxProviderGateways: external_exports.number().int().optional().meta({ description: "Provider gateways the account may own." }),
16290
+ maxActiveKeysPerApp: external_exports.number().int().optional().meta({ description: "Active API keys each of the account's applications may hold." })
16291
+ }).meta({
16292
+ description: "The plan's ceilings, as this gateway enforces them. Every key is optional and an absent key means unlimited, so an empty object is a plan with no ceilings. A write that would exceed one is refused with billing_plan_limit_reached."
16293
+ });
16294
+ var OrganizationQuotaStatusSchema = external_exports.object({
16295
+ periodId: external_exports.string().meta({ description: "Opaque identifier for the allowance period being reported." }),
16296
+ periodStart: external_exports.string().meta({ description: "Inclusive UTC instant at which this allowance period began." }),
16297
+ periodEnd: external_exports.string().meta({ description: "Exclusive UTC instant at which this allowance period ends." }),
16298
+ used: external_exports.number().int().meta({ description: "Requests dispatched to a provider in this period, across all apps." }),
16299
+ limit: external_exports.number().int().optional().meta({ description: "The plan's maxRequestsPerMonth. Absent means unlimited." }),
16300
+ resetAt: external_exports.string().meta({ description: "UTC instant at which the allowance resets; currently equal to periodEnd." })
16301
+ });
16302
+ var SubscriptionActionsSchema = external_exports.object({
16303
+ cancel: external_exports.boolean().meta({ description: "A self-service subscription can be cancelled at the end of its period." }),
16304
+ resume: external_exports.boolean().meta({ description: "A cancelled self-service subscription can be resumed." }),
16305
+ manual: external_exports.boolean().meta({ description: "A live grant made by hand, which no self-service action may change." })
16306
+ });
16307
+ var BillingStatusResponseSchema = external_exports.object({
16308
+ access: GatewayBillingAccessSchema,
16309
+ limits: PlanLimitsSchema,
16310
+ quota: OrganizationQuotaStatusSchema.nullable().meta({
16311
+ description: "Null when billing is unavailable or no plan entitlement resolves."
16312
+ }),
16313
+ actions: SubscriptionActionsSchema,
16314
+ unclaimedAccessEndsAt: external_exports.string().nullable().meta({
16315
+ description: "When this account's free access ends if nobody claims it; null once a person owns it."
16316
+ })
16317
+ });
16318
+ var BillingPlanOfferPriceSchema = external_exports.object({
16319
+ billingPeriod: BillingPeriodSchema,
16320
+ priceAmountCents: external_exports.number(),
16321
+ priceCurrency: external_exports.string()
16322
+ });
16323
+ var BillingPlanOfferSchema = external_exports.object({
16324
+ planKey: external_exports.string(),
16325
+ name: external_exports.string(),
16326
+ description: external_exports.string(),
16327
+ features: external_exports.array(external_exports.string()),
16328
+ limits: external_exports.unknown().optional(),
16329
+ trialDays: external_exports.number(),
16330
+ prices: external_exports.array(BillingPlanOfferPriceSchema)
16331
+ }).meta({ id: "BillingPlan" });
16332
+ var BillingPlansResponseSchema = external_exports.object({
16333
+ plans: external_exports.array(BillingPlanOfferSchema)
16334
+ });
16335
+ var BillingPlanSelectionSchema = external_exports.object({
16336
+ planKey: external_exports.string().min(1),
16337
+ billingPeriod: BillingPeriodSchema
16338
+ });
16339
+ var BillingCheckoutRequestSchema = BillingPlanSelectionSchema.extend({
16340
+ successUrl: external_exports.url().optional(),
16341
+ cancelUrl: external_exports.url().optional()
16342
+ });
16343
+ var BillingCheckoutResponseSchema = external_exports.object({ url: external_exports.string() });
16344
+ var BillingChangeResponseSchema = external_exports.object({
16345
+ ok: external_exports.literal(true),
16346
+ requiredActionUrl: external_exports.string().optional()
16347
+ });
16348
+ var BillingCancelResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
16349
+ var BillingTrialRequestSchema = external_exports.object({ planKey: external_exports.string().min(1) });
16350
+ var billingSubscriptionStatuses = BillingSubscriptionStatusSchema.options;
16351
+ var billingSources = BillingSourceSchema.options;
16352
+ var billingPeriods = BillingPeriodSchema.options;
16353
+
16354
+ // ../src/contracts/catalog.ts
16355
+ var NO_PLAN = "No billing plan resolves for the account.";
16356
+ var APP_AUTH_RATE_LIMITED = "Too many application authentication requests from this network address for this application. Carries Retry-After.";
16357
+ var NO_END_USERS = "This application identifies no end users, so there is no per-user standing to report. Answered with auth_method_not_supported: the request was well-formed, and nothing the caller can rephrase would make it work.";
16358
+ var DATA_PLANE_RATE_LIMITED = "Either the app's own limits or the account's request allowance refused the request. The app's limits are checked first and answer app_rate_limited or app_budget_exhausted, carrying scope; the plan allowance answers billing_request_quota_exceeded with limit, used, and the UTC resetAt. Every one of them carries a Retry-After header.";
16359
+ var ISSUER_ERRORS = {
16360
+ 403: "`issuer_token_rejected` \u2014 the token did not verify; get a fresh one, retry once, then fail. `issuer_claims_missing` \u2014 the token is valid but a required entitlement claim has not propagated yet; do not re-authenticate, wait and retry. `auth_required` \u2014 the key was refused or the user is blocked. `attest_failed` \u2014 the App Attest proof did not hold.",
16361
+ 503: "`issuer_verification_unavailable` \u2014 the gateway could not reach or read the issuer's keys, so the token was never judged. Retry with backoff."
16362
+ };
16363
+ var CLI_ERRORS = {
16364
+ 409: "A conflicting transition or setup cap prevents this operation.",
16365
+ 410: "The protected credential exchange has expired; no replacement is minted.",
16366
+ 429: "The durable initiation or submission rate limit was reached."
16367
+ };
16368
+ var BROWSER_HANDOFF_DESCRIPTION = "First-party browser only: both the request URL origin and exact Origin header must match consoleOrigin; a separate submissionToken is required. Identity approval also requires an interactive human session; registration is limited to a valid pending claim. Provider secret values are write-only.";
16369
+ var SignUpRequestSchema = external_exports.object({
16370
+ name: external_exports.string(),
16371
+ email: external_exports.email(),
16372
+ password: external_exports.string().min(8)
16373
+ });
16374
+ var SignInRequestSchema = external_exports.object({ email: external_exports.email(), password: external_exports.string() });
16375
+ var ProviderNativeBodySchema = external_exports.record(external_exports.string(), external_exports.unknown()).meta({
16376
+ description: "Provider-native JSON request. Consult the selected provider's API reference for the exact shape."
16377
+ });
16378
+ var ResponsesStyleBodySchema = external_exports.record(external_exports.string(), external_exports.unknown()).meta({
16379
+ description: "OpenAI Responses API body for endpoints whose api_style is responses. The gateway overwrites model and deep-merges the configured params."
16380
+ });
16381
+ var TranscriptionStyleBodySchema = external_exports.object({
16382
+ file: external_exports.string().meta({ format: "binary" }),
16383
+ model: external_exports.string().optional().meta({ description: "Ignored; the gateway sets the configured model." }),
16384
+ prompt: external_exports.string().optional(),
16385
+ language: external_exports.string().optional(),
16386
+ response_format: external_exports.string().optional()
16387
+ }).meta({ description: "Body for endpoints whose api_style is transcription." });
16388
+ var GatewayClientHeadersSchema = external_exports.object({
16389
+ "x-app-version": external_exports.string().optional().meta({
16390
+ description: "Required for gateway-token clients; optional for issuer-less API-key clients."
16391
+ }),
16392
+ "x-end-user-id": external_exports.string().optional().meta({
16393
+ description: "Optional configured end-user identity for issuer-less API-key applications."
16394
+ })
16395
+ });
16396
+ var MonthQuerySchema = external_exports.object({
16397
+ month: MonthSchema.optional().meta({ description: "YYYY-MM; defaults to the current UTC month." })
16398
+ });
16399
+ var DaySchema = (name) => external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, { error: `${name} must use YYYY-MM-DD format` });
16400
+ var RangeQuerySchema = external_exports.object({
16401
+ from: DaySchema("from").optional().meta({ description: "Inclusive YYYY-MM-DD start; defaults to 29 days before `to`." }),
16402
+ to: DaySchema("to").optional().meta({ description: "Inclusive YYYY-MM-DD end; defaults to today." })
16403
+ });
16404
+ var PageLimitSchema = external_exports.coerce.number().int({ error: "limit must be an integer between 1 and 200" }).min(1, { error: "limit must be an integer between 1 and 200" }).max(200, { error: "limit must be an integer between 1 and 200" }).default(50);
16405
+ var BeforeIdSchema = external_exports.coerce.number().int({ error: "before_id must be a positive integer" }).positive({ error: "before_id must be a positive integer" }).optional().meta({ description: "Page backwards: only events older than this id." });
16406
+ var APP_PARAM = { app: { example: "my-app" } };
16407
+ var NO_RESPONSE_BODY = external_exports.never();
16408
+ var CATALOG = {
16409
+ getHealth: {
16410
+ method: "GET",
16411
+ path: "/v1/healthz",
16412
+ tags: ["Operations"],
16413
+ summary: "Check gateway health",
16414
+ security: "public",
16415
+ response: HealthResponseSchema,
16416
+ responseDescription: "The Worker is accepting requests.",
16417
+ errors: "none"
16418
+ },
16419
+ getConsoleCapabilities: {
16420
+ method: "GET",
16421
+ path: "/v1/console/capabilities",
16422
+ tags: ["Operations"],
16423
+ summary: "Discover optional deployment capabilities",
16424
+ security: "public",
16425
+ response: ConsoleCapabilitiesResponseSchema,
16426
+ responseDescription: "Capabilities the console adapts to.",
16427
+ errors: "none"
16428
+ },
16429
+ signUp: {
16430
+ method: "POST",
16431
+ path: "/v1/auth/sign-up/email",
16432
+ tags: ["Console authentication"],
16433
+ summary: "Create an account",
16434
+ security: "public",
16435
+ request: SignUpRequestSchema,
16436
+ response: external_exports.unknown(),
16437
+ responseDescription: "Authenticated session."
16438
+ },
16439
+ // Only the password route is throttled by the gateway, so the two console
16440
+ // authentication routes do not document the same responses.
16441
+ signIn: {
16442
+ method: "POST",
16443
+ path: "/v1/auth/sign-in/email",
16444
+ tags: ["Console authentication"],
16445
+ summary: "Sign in with email and password",
16446
+ security: "public",
16447
+ request: SignInRequestSchema,
16448
+ response: external_exports.unknown(),
16449
+ responseDescription: "Authenticated session.",
16450
+ errors: {
16451
+ 429: "Too many sign-in attempts. Counted twice over: per client address, and per email address so that a spread of addresses cannot grind through one account's passwords. Carries Retry-After."
16452
+ }
16453
+ },
16454
+ getSession: {
16455
+ method: "GET",
16456
+ path: "/v1/auth/get-session",
16457
+ tags: ["Console authentication"],
16458
+ summary: "Get the current session",
16459
+ security: "session",
16460
+ response: external_exports.unknown(),
16461
+ responseDescription: "Current session or null."
16462
+ },
16463
+ signOut: {
16464
+ method: "POST",
16465
+ path: "/v1/auth/sign-out",
16466
+ tags: ["Console authentication"],
16467
+ summary: "End the current session",
16468
+ security: "session",
16469
+ response: external_exports.unknown(),
16470
+ responseDescription: "Session ended."
16471
+ },
16472
+ signInWithGoogle: {
16473
+ method: "GET",
16474
+ path: "/v1/auth/sign-in/social",
16475
+ tags: ["Console authentication"],
16476
+ summary: "Start optional Google sign-in",
16477
+ description: "Available only when GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are configured.",
16478
+ security: "public",
16479
+ query: external_exports.object({ provider: external_exports.literal("google") }),
16480
+ status: 302,
16481
+ response: NO_RESPONSE_BODY,
16482
+ responseDescription: "Redirect to Google."
16483
+ },
16484
+ createAppAttestChallenge: {
16485
+ method: "POST",
16486
+ path: "/v1/apps/{app}/auth/challenge",
16487
+ tags: ["Application authentication"],
16488
+ summary: "Create an App Attest challenge",
16489
+ security: "public",
16490
+ params: APP_PARAM,
16491
+ response: AppAttestChallengeResponseSchema,
16492
+ responseDescription: "A five-minute, single-use challenge.",
16493
+ errors: { 402: NO_PLAN, 429: APP_AUTH_RATE_LIMITED, 404: NO_END_USERS }
16494
+ },
16495
+ registerAppAttestKey: {
16496
+ method: "POST",
16497
+ path: "/v1/apps/{app}/auth/register",
16498
+ tags: ["Application authentication"],
16499
+ summary: "Register an App Attest key",
16500
+ security: "public",
16501
+ params: APP_PARAM,
16502
+ request: AppAttestRegisterRequestSchema,
16503
+ response: AppAttestRegisterResponseSchema,
16504
+ responseDescription: "The key was registered for the verified issuer identity.",
16505
+ errors: { ...ISSUER_ERRORS, 402: NO_PLAN, 429: APP_AUTH_RATE_LIMITED }
16506
+ },
16507
+ exchangeGatewayToken: {
16508
+ method: "POST",
16509
+ path: "/v1/apps/{app}/auth/token",
16510
+ tags: ["Application authentication"],
16511
+ summary: "Exchange issuer identity and client proof for a gateway token",
16512
+ description: "App Attest clients send key_id, assertion, and challenge. Issuer-backed API-key clients send api_key and issuer_token. The verified issuer claim is always the resulting user identity.",
16513
+ security: "public",
16514
+ params: APP_PARAM,
16515
+ request: external_exports.union([AppAttestTokenRequestSchema, ApiKeyTokenRequestSchema]),
16516
+ response: GatewayTokenResponseSchema,
16517
+ responseDescription: "A short-lived gateway access token.",
16518
+ errors: { ...ISSUER_ERRORS, 402: NO_PLAN, 429: APP_AUTH_RATE_LIMITED }
16519
+ },
16520
+ getCurrentUser: {
16521
+ method: "GET",
16522
+ path: "/v1/apps/{app}/me",
16523
+ tags: ["Application"],
16524
+ summary: "Get the current user's limits, spend and block state",
16525
+ security: "gateway",
16526
+ params: APP_PARAM,
16527
+ response: CurrentUserResponseSchema,
16528
+ responseDescription: "Current user state.",
16529
+ errors: { 402: NO_PLAN, 404: NO_END_USERS }
16530
+ },
16531
+ proxyProviderRequest: {
16532
+ method: "POST",
16533
+ path: "/v1/apps/{app}/proxy/{provider}/{path}",
16534
+ tags: ["Provider proxy"],
16535
+ summary: "Proxy a provider-native model request",
16536
+ description: "The path, body, and successful response retain the selected provider's native contract. For example, OpenAI clients send v1/responses or v1/chat/completions; gateway-specific provider slug quirks are never part of the client path. The gateway validates the configured path and model, spends one request from the account's monthly allowance, and streams the upstream response without buffering.",
16537
+ security: "gateway",
16538
+ params: {
16539
+ app: { example: "my-app" },
16540
+ provider: { example: "openai-dev", pattern: PROVIDER_SLUG_PATTERN },
16541
+ path: {
16542
+ description: "The provider's native API path verbatim, without a leading slash.",
16543
+ example: "v1/responses"
16544
+ }
16545
+ },
16546
+ headers: GatewayClientHeadersSchema,
16547
+ request: ProviderNativeBodySchema,
16548
+ response: external_exports.unknown(),
16549
+ responseDescription: "Provider-native response. Streaming responses remain streamed.",
16550
+ errors: {
16551
+ 402: NO_PLAN,
16552
+ 429: DATA_PLANE_RATE_LIMITED,
16553
+ 502: "The upstream provider request failed.",
16554
+ 504: "The upstream provider sent no response headers within the gateway's time-to-first-byte budget."
16555
+ }
16556
+ },
16557
+ callNamedEndpoint: {
16558
+ method: "POST",
16559
+ path: "/v1/apps/{app}/endpoints/{slug}",
16560
+ tags: ["Named endpoints"],
16561
+ summary: "Call a server-configured named endpoint",
16562
+ description: "The endpoint's provider, model, fixed parameters, output cap, and fallback chain come from the application configuration, so you can change models without shipping a client release. Responses-style endpoints accept an OpenAI Responses body; transcription-style endpoints accept an OpenAI audio transcription multipart body and may omit the model field. The successful response keeps the serving provider's native format and streaming behaviour.",
16563
+ security: "gateway",
16564
+ params: {
16565
+ app: { example: "my-app" },
16566
+ slug: { example: "chat", pattern: /^[a-z0-9-]{1,64}$/ }
16567
+ },
16568
+ headers: GatewayClientHeadersSchema,
16569
+ request: {
16570
+ content: {
16571
+ "application/json": ResponsesStyleBodySchema,
16572
+ "multipart/form-data": TranscriptionStyleBodySchema
16573
+ }
16574
+ },
16575
+ response: external_exports.unknown(),
16576
+ responseDescription: "Provider-native response. Streaming responses remain streamed.",
16577
+ errors: {
16578
+ 402: NO_PLAN,
16579
+ 429: DATA_PLANE_RATE_LIMITED,
16580
+ 502: "Every configured target failed.",
16581
+ 504: "Every configured target failed, and the last one sent no response headers within the gateway's time-to-first-byte budget."
16582
+ }
16583
+ },
16584
+ listApps: {
16585
+ method: "GET",
16586
+ path: "/v1/admin/apps",
16587
+ tags: ["Admin applications"],
16588
+ summary: "List applications",
16589
+ security: "management",
16590
+ query: MonthQuerySchema,
16591
+ response: AppListResponseSchema,
16592
+ responseDescription: "Applications and current usage summaries."
16593
+ },
16594
+ createApp: {
16595
+ method: "POST",
16596
+ path: "/v1/admin/apps",
16597
+ tags: ["Admin applications"],
16598
+ summary: "Create an application",
16599
+ description: "Send only `name`, `config` and an optional `status`. The gateway assigns the id \u2014 the name slugified plus a six-character random suffix \u2014 and returns it as `app.id`; it cannot be chosen, and it cannot change once the application exists. A body that still carries `id` is refused with `400`. API-key applications receive a one-time plaintext initial key in the response.",
16600
+ security: "management",
16601
+ request: AppWriteSchema,
16602
+ status: 201,
16603
+ response: CreatedAppResponseSchema,
16604
+ responseDescription: "Application created. `app.id` is the assigned id and the segment every gateway URL for this app uses. `api_key` is the one-time plaintext initial key for an API-key application, and null for any other.",
16605
+ errors: {
16606
+ 409: "`invalid_request` \u2014 no unique generated id could be allocated after repeated attempts. Retrying is safe."
16607
+ },
16608
+ receipt: true
16609
+ },
16610
+ listBillingPlans: {
16611
+ method: "GET",
16612
+ path: "/v1/admin/billing/plans",
16613
+ hidden: true,
16614
+ tags: ["Admin billing"],
16615
+ summary: "List billing plans",
16616
+ security: "management",
16617
+ // The whole billing subtree is a person's to act on: a service credential
16618
+ // may run an account but may not buy, change or cancel what pays for it.
16619
+ policy: { identity: "human" },
16620
+ response: BillingPlansResponseSchema,
16621
+ responseDescription: "Billing service response."
16622
+ },
16623
+ getBillingStatus: {
16624
+ method: "GET",
16625
+ path: "/v1/admin/billing/status",
16626
+ hidden: true,
16627
+ tags: ["Admin billing"],
16628
+ summary: "Get billing access and the current allowance period",
16629
+ security: "management",
16630
+ policy: { identity: "human" },
16631
+ response: BillingStatusResponseSchema,
16632
+ responseDescription: "Billing access, and the current period against the plan's request allowance when an entitlement resolves."
16633
+ },
16634
+ startCheckout: {
16635
+ method: "POST",
16636
+ path: "/v1/admin/billing/checkout",
16637
+ hidden: true,
16638
+ tags: ["Admin billing"],
16639
+ summary: "Create a hosted checkout",
16640
+ security: "management",
16641
+ policy: { identity: "human" },
16642
+ request: BillingCheckoutRequestSchema,
16643
+ response: BillingCheckoutResponseSchema,
16644
+ responseDescription: "Hosted checkout URL."
16645
+ },
16646
+ changePlan: {
16647
+ method: "POST",
16648
+ path: "/v1/admin/billing/change",
16649
+ hidden: true,
16650
+ tags: ["Admin billing"],
16651
+ summary: "Change the subscription plan",
16652
+ security: "management",
16653
+ policy: { identity: "human" },
16654
+ request: BillingPlanSelectionSchema,
16655
+ response: BillingChangeResponseSchema,
16656
+ responseDescription: "Billing service response."
16657
+ },
16658
+ resumeSubscription: {
16659
+ method: "POST",
16660
+ path: "/v1/admin/billing/resume",
16661
+ hidden: true,
16662
+ tags: ["Admin billing"],
16663
+ summary: "Resume a canceled subscription",
16664
+ security: "management",
16665
+ policy: { identity: "human" },
16666
+ request: BillingPlanSelectionSchema,
16667
+ response: BillingChangeResponseSchema,
16668
+ responseDescription: "Billing service response."
16669
+ },
16670
+ cancelSubscription: {
16671
+ method: "POST",
16672
+ path: "/v1/admin/billing/cancel",
16673
+ hidden: true,
16674
+ tags: ["Admin billing"],
16675
+ summary: "Cancel the subscription at period end",
16676
+ security: "management",
16677
+ policy: { identity: "human" },
16678
+ response: BillingCancelResponseSchema,
16679
+ responseDescription: "Cancellation accepted."
16680
+ },
16681
+ startTrial: {
16682
+ method: "POST",
16683
+ path: "/v1/admin/billing/trial",
16684
+ hidden: true,
16685
+ tags: ["Admin billing"],
16686
+ summary: "Start a no-card trial",
16687
+ security: "management",
16688
+ policy: { identity: "human" },
16689
+ request: BillingTrialRequestSchema,
16690
+ response: BillingAccessSchema,
16691
+ responseDescription: "Trial access state."
16692
+ },
16693
+ getApp: {
16694
+ method: "GET",
16695
+ path: "/v1/admin/apps/{app}",
16696
+ tags: ["Admin applications"],
16697
+ summary: "Get an application",
16698
+ security: "management",
16699
+ params: APP_PARAM,
16700
+ response: AppResponseSchema,
16701
+ responseDescription: "Application state."
16702
+ },
16703
+ updateApp: {
16704
+ method: "PUT",
16705
+ path: "/v1/admin/apps/{app}",
16706
+ tags: ["Admin applications"],
16707
+ summary: "Update an application",
16708
+ description: "Requires `revision` in the body, the one the application was read at; a stale revision answers `409 app_revision_conflict` and an absent one `400 app_revision_required`. Updates an existing application in place. It never creates one: an id none of your applications holds answers `404 app_not_found`, and nothing is written. Applications are created only by `POST /v1/admin/apps`, which assigns the id.",
16709
+ security: "management",
16710
+ params: APP_PARAM,
16711
+ request: AppUpdateSchema,
16712
+ response: AppResponseSchema,
16713
+ responseDescription: "Application state.",
16714
+ errors: { 409: "The application changed since it was read." }
16715
+ },
16716
+ validateApp: {
16717
+ method: "POST",
16718
+ path: "/v1/admin/apps/{app}/validate",
16719
+ tags: ["Admin applications"],
16720
+ summary: "Validate an edit of an application without saving it",
16721
+ description: "Judged as an update of this application would be: provider slugs its stored configuration already names stay acceptable even if their provider has since been deleted. Use the draft validation for an application that does not exist yet.",
16722
+ security: "management",
16723
+ // A POST that stores nothing: it answers whether a body would be accepted,
16724
+ // which is a read of the configuration rules and not a write.
16725
+ policy: { role: "member", access: "read" },
16726
+ params: APP_PARAM,
16727
+ request: AppWriteSchema,
16728
+ response: AppValidateResponseSchema,
16729
+ responseDescription: "Resolved valid configuration."
16730
+ },
16731
+ validateAppDraft: {
16732
+ method: "POST",
16733
+ path: "/v1/admin/app-drafts/validate",
16734
+ tags: ["Admin applications"],
16735
+ summary: "Validate a new application configuration without saving it",
16736
+ description: "Judged as a creation would be, against the account's current providers and prices.",
16737
+ security: "management",
16738
+ policy: { role: "member", access: "read" },
16739
+ request: AppWriteSchema,
16740
+ response: AppDraftValidateResponseSchema,
16741
+ responseDescription: "The configuration would be accepted."
16742
+ },
16743
+ deleteApp: {
16744
+ method: "DELETE",
16745
+ path: "/v1/admin/apps/{app}",
16746
+ tags: ["Admin applications"],
16747
+ summary: "Delete an application and its associated operational data",
16748
+ security: "management",
16749
+ params: APP_PARAM,
16750
+ query: external_exports.object({
16751
+ confirm: external_exports.string({ error: "Pass ?confirm=<app-id> to delete an app" })
16752
+ }),
16753
+ response: AppDeleteResponseSchema,
16754
+ responseDescription: "Application deleted. Its usage events are kept, which is what `usage_events_retained` reports."
16755
+ },
16756
+ listManagementKeys: {
16757
+ method: "GET",
16758
+ path: "/v1/admin/keys",
16759
+ tags: ["Admin management keys"],
16760
+ summary: "List management keys",
16761
+ description: "Console session only. Management keys cannot administer management keys.",
16762
+ security: "session",
16763
+ response: ManagementKeyListResponseSchema,
16764
+ responseDescription: "Management key metadata without plaintext tokens."
16765
+ },
16766
+ createManagementKey: {
16767
+ method: "POST",
16768
+ path: "/v1/admin/keys",
16769
+ tags: ["Admin management keys"],
16770
+ summary: "Create a management key",
16771
+ description: "Console session only, and requires the owner or admin role. A management key cannot create another one, so revoking a key you handed out ends that access for good. The plaintext agw_mgmt_ token is returned once, and never expires; the account's own deadline is the only one.",
16772
+ security: "session",
16773
+ request: ManagementKeyCreateRequestSchema,
16774
+ status: 201,
16775
+ response: CreatedManagementKeyResponseSchema,
16776
+ responseDescription: "One-time plaintext management key."
16777
+ },
16778
+ revokeManagementKey: {
16779
+ method: "POST",
16780
+ path: "/v1/admin/keys/{id}/revoke",
16781
+ tags: ["Admin management keys"],
16782
+ summary: "Revoke a management key",
16783
+ description: "Console session only, and requires the owner or admin role.",
16784
+ security: "session",
16785
+ params: { id: { example: "key_123" } },
16786
+ response: ManagementKeyResponseSchema,
16787
+ responseDescription: "Revoked management key metadata."
16788
+ },
16789
+ listProviders: {
16790
+ method: "GET",
16791
+ path: "/v1/admin/providers",
16792
+ tags: ["Admin providers"],
16793
+ summary: "List provider credentials",
16794
+ security: "management",
16795
+ response: ProviderListResponseSchema,
16796
+ responseDescription: "Provider metadata without credentials."
16797
+ },
16798
+ createProvider: {
16799
+ method: "POST",
16800
+ path: "/v1/admin/providers",
16801
+ tags: ["Admin providers"],
16802
+ summary: "Store a provider credential",
16803
+ description: "Creates one named provider instance. Supply exactly one direct provider secret or reusable providerGatewayId. The credential is stored as given and never probed: check one first with POST /v1/admin/providers/test. The slug defaults to the provider type and is unique among your provider instances, disabled ones included; only deleting an instance frees its slug.",
16804
+ security: "management",
16805
+ request: ProviderCreateRequestSchema,
16806
+ status: 201,
16807
+ response: ProviderResponseSchema,
16808
+ responseDescription: "Stored provider.",
16809
+ errors: { 409: "The requested active provider slug is already in use." },
16810
+ receipt: true
16811
+ },
16812
+ testProviderCredential: {
16813
+ method: "POST",
16814
+ path: "/v1/admin/providers/test",
16815
+ tags: ["Admin providers"],
16816
+ summary: "Probe a provider credential without storing it",
16817
+ description: "Calls the provider with a credential, and reports what it answered. Nothing is stored, and no other endpoint runs this check: a write stores what it is given. Supply exactly one direct provider secret or an existing providerGatewayId.",
16818
+ security: "management",
16819
+ request: ProviderTestRequestSchema,
16820
+ response: ProviderTestResponseSchema,
16821
+ responseDescription: "Probe outcome."
16822
+ },
16823
+ updateProvider: {
16824
+ method: "PUT",
16825
+ path: "/v1/admin/providers/{id}",
16826
+ tags: ["Admin providers"],
16827
+ summary: "Rotate a credential, rename it, move it to another origin, replace its custom pricing, or disable it",
16828
+ description: "Requires the revision returned by the provider read this edit is based on; a stale revision returns 409 conflict. Sending status disables or re-enables the instance. Disabling keeps the secret, the pricing and the slug, so requests to it fail with provider_disabled and no other instance can take its slug meanwhile. Re-enabling does not require reclaiming the slug. Sending a non-null baseUrl also requires secret in the same request: the stored key is write-only and is never decrypted to be sent to an origin it has not been sent to before, so a move carries the key it is to be used with. Sending baseUrl: null returns the instance to its provider type's own origin and needs nothing else.",
16829
+ security: "management",
16830
+ params: { id: { example: "b0a1\u2026" } },
16831
+ request: ProviderUpdateRequestSchema,
16832
+ response: ProviderResponseSchema,
16833
+ responseDescription: "Updated provider."
16834
+ },
16835
+ deleteProvider: {
16836
+ method: "DELETE",
16837
+ path: "/v1/admin/providers/{id}",
16838
+ tags: ["Admin providers"],
16839
+ summary: "Delete a provider credential and its custom pricing",
16840
+ description: 'A hard delete, secret and pricing included. Applications using this provider start failing with provider_not_configured within a minute. To pause an instance reversibly instead, send status: "disabled" to PUT /v1/admin/providers/{id}.',
16841
+ security: "management",
16842
+ params: { id: { example: "b0a1\u2026" } },
16843
+ response: ProviderDeleteResponseSchema,
16844
+ responseDescription: "Provider deleted."
16845
+ },
16846
+ listProviderGateways: {
16847
+ method: "GET",
16848
+ path: "/v1/admin/provider-gateways",
16849
+ tags: ["Admin provider gateways"],
16850
+ summary: "List reusable provider gateways",
16851
+ security: "management",
16852
+ response: ProviderGatewayListResponseSchema,
16853
+ responseDescription: "Provider gateway metadata without tokens."
16854
+ },
16855
+ createProviderGateway: {
16856
+ method: "POST",
16857
+ path: "/v1/admin/provider-gateways",
16858
+ tags: ["Admin provider gateways"],
16859
+ summary: "Create a reusable provider gateway connection",
16860
+ description: "Cloudflare AI Gateway takes an account and gateway id; Vercel AI Gateway takes only a name and a token. The token is encrypted and stored as given, never probed: check a connection first with POST /v1/admin/provider-gateways/test. Provider instances are attached separately through the providers API.",
16861
+ security: "management",
16862
+ request: ProviderGatewayCreateRequestSchema,
16863
+ status: 201,
16864
+ response: ProviderGatewayResponseSchema,
16865
+ responseDescription: "Created provider gateway.",
16866
+ receipt: true
16867
+ },
16868
+ testProviderGateway: {
16869
+ method: "POST",
16870
+ path: "/v1/admin/provider-gateways/test",
16871
+ tags: ["Admin provider gateways"],
16872
+ summary: "Probe a gateway connection without storing it",
16873
+ description: "Calls the gateway with a token, and reports what it answered. Nothing is stored, and no other endpoint runs this check: a write stores what it is given. Unlike the providers API, a refused token is reported as reason: rejected rather than raised as provider_key_invalid, because the same 401 means both a wrong token and a gateway that is not finished being set up.",
16874
+ security: "management",
16875
+ request: ProviderGatewayTestRequestSchema,
16876
+ response: ProviderGatewayTestResponseSchema,
16877
+ responseDescription: "Probe outcome."
16878
+ },
16879
+ updateProviderGateway: {
16880
+ method: "PATCH",
16881
+ path: "/v1/admin/provider-gateways/{id}",
16882
+ tags: ["Admin provider gateways"],
16883
+ summary: "Rename a provider gateway",
16884
+ description: "Requires the revision returned by the gateway read this rename is based on; a stale revision returns 409 conflict.",
16885
+ security: "management",
16886
+ params: { id: { example: "b0a1\u2026" } },
16887
+ request: ProviderGatewayUpdateRequestSchema,
16888
+ response: ProviderGatewayResponseSchema,
16889
+ responseDescription: "Updated provider gateway."
16890
+ },
16891
+ rotateProviderGateway: {
16892
+ method: "POST",
16893
+ path: "/v1/admin/provider-gateways/{id}/rotate",
16894
+ tags: ["Admin provider gateways"],
16895
+ summary: "Rotate a shared provider gateway token",
16896
+ description: "Requires the revision returned by the gateway read this rotation is based on; a stale revision returns 409 conflict. Re-encrypts the token once for every provider instance referencing this gateway. The new token is stored as given, never probed.",
16897
+ security: "management",
16898
+ params: { id: { example: "b0a1\u2026" } },
16899
+ request: ProviderGatewayRotateRequestSchema,
16900
+ response: ProviderGatewayResponseSchema,
16901
+ responseDescription: "Rotated provider gateway."
16902
+ },
16903
+ deleteProviderGateway: {
16904
+ method: "DELETE",
16905
+ path: "/v1/admin/provider-gateways/{id}",
16906
+ tags: ["Admin provider gateways"],
16907
+ summary: "Delete an unused provider gateway",
16908
+ security: "management",
16909
+ params: { id: { example: "b0a1\u2026" } },
16910
+ response: ProviderGatewayDeleteResponseSchema,
16911
+ responseDescription: "Provider gateway deleted.",
16912
+ errors: {
16913
+ 409: "Provider instances still reference this gateway. Disabled rows are retained for re-enabling and block deletion too; see referencedCount."
16914
+ }
16915
+ },
16916
+ getAdminSession: {
16917
+ method: "GET",
16918
+ path: "/v1/admin/session",
16919
+ tags: ["Admin organizations"],
16920
+ summary: "Get the caller's identity, current organization and role",
16921
+ description: "The console needs the caller's role and active organization to gate its UI; the sign-in session endpoint reports neither.",
16922
+ security: "management",
16923
+ response: IdentitySessionSchema,
16924
+ responseDescription: "Resolved session."
16925
+ },
16926
+ listOrganizations: {
16927
+ method: "GET",
16928
+ path: "/v1/admin/organizations",
16929
+ tags: ["Admin organizations"],
16930
+ summary: "List the organizations the caller belongs to",
16931
+ security: "management",
16932
+ response: OrganizationListResponseSchema,
16933
+ responseDescription: "Memberships ordered by organization creation time."
16934
+ },
16935
+ selectOrganization: {
16936
+ method: "POST",
16937
+ path: "/v1/admin/organizations/select",
16938
+ tags: ["Admin organizations"],
16939
+ summary: "Switch the caller's active organization",
16940
+ description: "Available to every member, including read-only members, of the target organization.",
16941
+ security: "management",
16942
+ // Switching the active organization re-signs the cookie naming which
16943
+ // tenant the caller reads; gating it behind owner/admin would strand a
16944
+ // read-only member in one organization, and gating it behind setup access
16945
+ // would strand them in an account whose trial has ended.
16946
+ policy: { role: "member", access: "read" },
16947
+ request: OrganizationSelectRequestSchema,
16948
+ response: IdentitySessionSchema,
16949
+ responseDescription: "Session rescoped to the selected organization."
16950
+ },
16951
+ listAppEvents: {
16952
+ method: "GET",
16953
+ path: "/v1/admin/apps/{app}/events",
16954
+ tags: ["Admin operations"],
16955
+ summary: "List application usage events",
16956
+ security: "management",
16957
+ params: APP_PARAM,
16958
+ query: external_exports.object({
16959
+ limit: PageLimitSchema,
16960
+ status: external_exports.enum(USAGE_STATUSES).optional(),
16961
+ provider: external_exports.string().optional(),
16962
+ user: external_exports.string().optional(),
16963
+ model: external_exports.string().optional(),
16964
+ before_id: BeforeIdSchema
16965
+ }),
16966
+ response: UsageEventListSchema,
16967
+ responseDescription: "Paginated application usage events."
16968
+ },
16969
+ getAppAuthEventSummary: {
16970
+ method: "GET",
16971
+ path: "/v1/admin/apps/{app}/auth-events/summary",
16972
+ tags: ["Admin operations"],
16973
+ summary: "Summarize application authentication outcomes",
16974
+ description: "Daily authentication outcomes and reasons, non-ok proxied requests, token-exchange success rate, entitlement-claim propagation delays, and how many users are waiting on a claim right now.",
16975
+ security: "management",
16976
+ params: APP_PARAM,
16977
+ query: external_exports.object({
16978
+ days: external_exports.coerce.number().int({ error: "days must be an integer between 1 and 365" }).min(1, { error: "days must be an integer between 1 and 365" }).max(365, { error: "days must be an integer between 1 and 365" }).default(30).meta({ description: "Trailing window in days, ending today. Defaults to 30." })
16979
+ }),
16980
+ response: AuthEventSummarySchema,
16981
+ responseDescription: "Authentication activity for the window."
16982
+ },
16983
+ listAppAuthEvents: {
16984
+ method: "GET",
16985
+ path: "/v1/admin/apps/{app}/auth-events",
16986
+ tags: ["Admin operations"],
16987
+ summary: "List application authentication events",
16988
+ security: "management",
16989
+ params: APP_PARAM,
16990
+ query: external_exports.object({
16991
+ limit: PageLimitSchema,
16992
+ outcome: external_exports.string().optional(),
16993
+ event: external_exports.enum(["token_exchange", "register"]).optional(),
16994
+ user: external_exports.string().optional(),
16995
+ before_id: BeforeIdSchema
16996
+ }),
16997
+ response: AuthEventListSchema,
16998
+ responseDescription: "Paginated authentication attempts, newest first."
16999
+ },
17000
+ listAppKeys: {
17001
+ method: "GET",
17002
+ path: "/v1/admin/apps/{app}/keys",
17003
+ tags: ["Admin operations"],
17004
+ summary: "List application API keys",
17005
+ security: "management",
17006
+ params: APP_PARAM,
17007
+ response: ApiKeyListResponseSchema,
17008
+ responseDescription: "Application API keys without plaintext tokens."
17009
+ },
17010
+ createAppKey: {
17011
+ method: "POST",
17012
+ path: "/v1/admin/apps/{app}/keys",
17013
+ tags: ["Admin operations"],
17014
+ summary: "Create an application API key",
17015
+ security: "management",
17016
+ params: APP_PARAM,
17017
+ request: ApiKeyCreateRequestSchema,
17018
+ status: 201,
17019
+ response: CreatedApiKeySchema,
17020
+ responseDescription: "One-time plaintext application API key.",
17021
+ receipt: true
17022
+ },
17023
+ revokeAppKey: {
17024
+ method: "POST",
17025
+ path: "/v1/admin/apps/{app}/keys/{key}/revoke",
17026
+ tags: ["Admin operations"],
17027
+ summary: "Revoke an application API key",
17028
+ security: "management",
17029
+ params: { app: { example: "my-app" }, key: { example: "key_123" } },
17030
+ response: ApiKeyRevokeResponseSchema,
17031
+ responseDescription: "Revoked application API key."
17032
+ },
17033
+ listAppUsers: {
17034
+ method: "GET",
17035
+ path: "/v1/admin/apps/{app}/users",
17036
+ tags: ["Admin operations"],
17037
+ summary: "List application users",
17038
+ security: "management",
17039
+ params: APP_PARAM,
17040
+ query: external_exports.object({
17041
+ month: MonthSchema.optional().meta({ description: "YYYY-MM; defaults to the current UTC month." }),
17042
+ query: external_exports.string().optional().meta({ description: "Substring match on the user id." }),
17043
+ status: external_exports.enum(["active", "blocked"]).optional(),
17044
+ limit: PageLimitSchema,
17045
+ offset: external_exports.coerce.number().int({ error: "offset must be a non-negative integer" }).min(0, { error: "offset must be a non-negative integer" }).default(0)
17046
+ }),
17047
+ response: UserListResponseSchema,
17048
+ responseDescription: "Application users and their usage for the month."
17049
+ },
17050
+ getAppUser: {
17051
+ method: "GET",
17052
+ path: "/v1/admin/apps/{app}/users/{user}",
17053
+ tags: ["Admin operations"],
17054
+ summary: "Get an application user",
17055
+ security: "management",
17056
+ params: { app: { example: "my-app" }, user: { example: "user-123" } },
17057
+ query: MonthQuerySchema,
17058
+ response: UserResponseSchema,
17059
+ responseDescription: "One application user and their usage for the month."
17060
+ },
17061
+ blockAppUser: {
17062
+ method: "POST",
17063
+ path: "/v1/admin/apps/{app}/users/{user}/block",
17064
+ tags: ["Admin operations"],
17065
+ summary: "Block an application user",
17066
+ security: "management",
17067
+ params: { app: { example: "my-app" }, user: { example: "user-123" } },
17068
+ response: UserBlockResponseSchema,
17069
+ responseDescription: "The user's new block state."
17070
+ },
17071
+ unblockAppUser: {
17072
+ method: "POST",
17073
+ path: "/v1/admin/apps/{app}/users/{user}/unblock",
17074
+ tags: ["Admin operations"],
17075
+ summary: "Unblock an application user",
17076
+ security: "management",
17077
+ params: { app: { example: "my-app" }, user: { example: "user-123" } },
17078
+ response: UserBlockResponseSchema,
17079
+ responseDescription: "The user's new block state."
17080
+ },
17081
+ getAppUsage: {
17082
+ method: "GET",
17083
+ path: "/v1/admin/apps/{app}/usage",
17084
+ tags: ["Admin operations"],
17085
+ summary: "Get application usage totals",
17086
+ security: "management",
17087
+ params: APP_PARAM,
17088
+ query: MonthQuerySchema,
17089
+ response: MonthlyUsageResponseSchema,
17090
+ responseDescription: "One month's totals for one application."
17091
+ },
17092
+ repriceAppUsage: {
17093
+ method: "POST",
17094
+ path: "/v1/admin/apps/{app}/usage/reprice",
17095
+ tags: ["Admin operations"],
17096
+ summary: "Preview or apply current catalog prices to stored usage",
17097
+ security: "management",
17098
+ params: APP_PARAM,
17099
+ request: UsageRepriceRequestSchema,
17100
+ response: UsageRepriceResponseSchema,
17101
+ responseDescription: "What repricing would change, or did."
17102
+ },
17103
+ getAppUsageTimeseries: {
17104
+ method: "GET",
17105
+ path: "/v1/admin/apps/{app}/usage/timeseries",
17106
+ tags: ["Admin operations"],
17107
+ summary: "Get application usage over time",
17108
+ security: "management",
17109
+ params: APP_PARAM,
17110
+ query: RangeQuerySchema,
17111
+ response: TimeseriesResponseSchema,
17112
+ responseDescription: "Daily buckets split by provider."
17113
+ },
17114
+ getAppUsageBreakdown: {
17115
+ method: "GET",
17116
+ path: "/v1/admin/apps/{app}/usage/breakdown",
17117
+ tags: ["Admin operations"],
17118
+ summary: "Get grouped application usage",
17119
+ security: "management",
17120
+ params: APP_PARAM,
17121
+ query: RangeQuerySchema.extend({
17122
+ by: external_exports.enum(USAGE_BREAKDOWN_DIMENSIONS).default("model").meta({ description: "Which dimension to group by. Defaults to model." }),
17123
+ limit: PageLimitSchema
17124
+ }),
17125
+ response: BreakdownResponseSchema,
17126
+ responseDescription: "One dimension's totals, largest first."
17127
+ },
17128
+ listModelPrices: {
17129
+ method: "GET",
17130
+ path: "/v1/admin/prices",
17131
+ tags: ["Admin models"],
17132
+ summary: "List known model prices",
17133
+ security: "management",
17134
+ response: PricesResponseSchema,
17135
+ responseDescription: "The priced model catalog this deployment enforces."
17136
+ },
17137
+ getCliCapabilities: {
17138
+ method: "GET",
17139
+ path: "/v1/cli/capabilities",
17140
+ tags: ["CLI"],
17141
+ summary: "Discover deployment identity and provider capabilities",
17142
+ security: "public",
17143
+ response: CliCapabilitiesResponseSchema,
17144
+ responseDescription: "Public deployment capabilities. No credentials or inference calls.",
17145
+ errors: "none"
17146
+ },
17147
+ bootstrapCliAccount: {
17148
+ method: "POST",
17149
+ path: "/v1/cli/bootstrap",
17150
+ tags: ["CLI"],
17151
+ summary: "Initialize a recoverable CLI account",
17152
+ description: "Persist both random proofs before sending. An identical retry returns the same account and protected credential during its exchange window. Cloud initialization is public and rate limited. Self-hosted initialization is public too and creates the deployment's single initial account, so whoever initializes an empty deployment first owns it, exactly as its first console registration does; it is not rate limited, because the only thing a limit could refuse on a deployment nobody owns yet is its own installer retrying, and every caller after the first is refused permanently anyway. All responses are no-store.",
17153
+ security: "public",
17154
+ request: CliBootstrapRequestSchema,
17155
+ response: CliBootstrapResponseSchema,
17156
+ responseDescription: "Initial account and credential. Never print or log the credential.",
17157
+ errors: CLI_ERRORS
17158
+ },
17159
+ createCliOperation: {
17160
+ method: "POST",
17161
+ path: "/v1/cli/operations",
17162
+ tags: ["CLI"],
17163
+ summary: "Create or recover a browser handoff",
17164
+ description: "Persist pollToken before initiation. Repeating the same proof and payload recovers the same operation. A current account key is required. Claims require interactive human sign-in and explicit consent. Provider handoffs require the browser URL proof and show the exact resource configuration before secret submission. Handoffs expire after 15 minutes.",
17165
+ security: "management",
17166
+ // Admin, like every write; read access because the claim is the one kind
17167
+ // an account past its free window may still open. Every resource kind then
17168
+ // asks for setup access of its own, in the handler, from its kind entry.
17169
+ policy: { role: "admin", access: "read" },
17170
+ request: CliOperationRequestSchema,
17171
+ response: CliOperationResponseSchema,
17172
+ responseDescription: "Browser URL for the pending handoff.",
17173
+ errors: CLI_ERRORS
17174
+ },
17175
+ pollCliOperation: {
17176
+ method: "GET",
17177
+ path: "/v1/cli/operations/{id}",
17178
+ tags: ["CLI"],
17179
+ summary: "Poll a browser handoff",
17180
+ description: "Only the original polling proof can recover the result. Completed claims report the account they landed on; the CLI keeps the access it already had. Provider secrets are never returned.",
17181
+ security: "cliPoll",
17182
+ params: { id: {} },
17183
+ response: CliPollResponseSchema,
17184
+ responseDescription: "Current operation state and nonsecret result.",
17185
+ errors: CLI_ERRORS
17186
+ },
17187
+ getCliAccount: {
17188
+ method: "GET",
17189
+ path: "/v1/cli/account",
17190
+ tags: ["CLI"],
17191
+ summary: "Read account lifecycle and current access",
17192
+ security: "management",
17193
+ response: CliAccountResponseSchema,
17194
+ responseDescription: "Account deadlines, effective access and current request count.",
17195
+ errors: CLI_ERRORS
17196
+ },
17197
+ getCliUsage: {
17198
+ method: "GET",
17199
+ path: "/v1/cli/usage",
17200
+ tags: ["CLI"],
17201
+ summary: "Read retained account usage for a UTC month",
17202
+ description: "Includes retained usage for deleted apps, with durable account attribution. Historical rows whose owner was already unknown when attribution was introduced cannot be counted. Coverage describes this limitation without disclosing other accounts' data.",
17203
+ security: "management",
17204
+ query: external_exports.object({
17205
+ month: MonthSchema.optional().meta({ description: "YYYY-MM; defaults to the current UTC month." })
17206
+ }),
17207
+ response: CliUsageResponseSchema,
17208
+ responseDescription: "Account totals, per-app totals and attribution coverage.",
17209
+ errors: CLI_ERRORS
17210
+ },
17211
+ /*
17212
+ * The human half of a handoff. There is no page to fetch here: the console
17213
+ * renders the approval screen at `/cli/approve/{id}` from its own bundle, and
17214
+ * these four are the API it calls with the proof it read from the URL
17215
+ * fragment. Every one of them carries that proof in its body, so they are
17216
+ * POSTs with no readable URL of their own.
17217
+ */
17218
+ cliBrowserDetails: {
17219
+ method: "POST",
17220
+ path: "/v1/cli/browser/{id}/details",
17221
+ tags: ["CLI"],
17222
+ summary: "Browser handoff: details",
17223
+ description: BROWSER_HANDOFF_DESCRIPTION,
17224
+ security: "public",
17225
+ params: { id: {} },
17226
+ request: CliSubmissionRequestSchema,
17227
+ response: CliBrowserDetailsResponseSchema,
17228
+ responseDescription: "The pending action, its configuration, the account it lands on, the signed-in human who would approve it, if any, and what stands between this browser and approving it.",
17229
+ errors: CLI_ERRORS
17230
+ },
17231
+ cliBrowserSubmit: {
17232
+ method: "POST",
17233
+ path: "/v1/cli/browser/{id}/submit",
17234
+ tags: ["CLI"],
17235
+ summary: "Browser handoff: submit",
17236
+ description: BROWSER_HANDOFF_DESCRIPTION,
17237
+ security: "public",
17238
+ params: { id: {} },
17239
+ request: CliSubmissionRequestSchema,
17240
+ response: CliBrowserSubmitResponseSchema,
17241
+ responseDescription: "The handoff is approved and consumed. No submitted secret is ever echoed.",
17242
+ errors: CLI_ERRORS
17243
+ },
17244
+ cliBrowserRegister: {
17245
+ method: "POST",
17246
+ path: "/v1/cli/browser/{id}/register",
17247
+ tags: ["CLI"],
17248
+ summary: "Browser handoff: register",
17249
+ description: BROWSER_HANDOFF_DESCRIPTION,
17250
+ security: "public",
17251
+ params: { id: {} },
17252
+ request: CliSubmissionRequestSchema,
17253
+ response: CliBrowserRegisterResponseSchema,
17254
+ responseDescription: "A new human identity for a pending claim, with its session set as a cookie.",
17255
+ errors: CLI_ERRORS
17256
+ },
17257
+ cliBrowserGoogle: {
17258
+ method: "POST",
17259
+ path: "/v1/cli/browser/{id}/google",
17260
+ tags: ["CLI"],
17261
+ summary: "Browser handoff: google",
17262
+ description: BROWSER_HANDOFF_DESCRIPTION,
17263
+ security: "public",
17264
+ params: { id: {} },
17265
+ request: CliSubmissionRequestSchema,
17266
+ response: CliBrowserGoogleResponseSchema,
17267
+ responseDescription: "Where to send the browser to start Google consent for a pending claim.",
17268
+ errors: CLI_ERRORS
17269
+ }
17270
+ };
16010
17271
  function searchSuffix(params) {
16011
17272
  const search = new URLSearchParams();
16012
17273
  for (const [key, value] of Object.entries(params)) {
@@ -16015,200 +17276,15 @@ function searchSuffix(params) {
16015
17276
  const encoded = search.toString();
16016
17277
  return encoded ? `?${encoded}` : "";
16017
17278
  }
16018
- var seg = encodeURIComponent;
16019
- function operation() {
16020
- return (method, path) => ({ method, path });
17279
+ function operationPath(name, ...rest) {
17280
+ const [params, query] = rest;
17281
+ const filled = CATALOG[name].path.replace(/\{(\w+)\}/gu, (_match, key) => {
17282
+ const value = params?.[key];
17283
+ if (value === void 0) throw new Error(`${String(name)} needs a "${key}" path parameter`);
17284
+ return encodeURIComponent(value);
17285
+ });
17286
+ return query === void 0 ? filled : filled + searchSuffix(query);
16021
17287
  }
16022
- var operations = {
16023
- // Deployment and identity
16024
- getConsoleCapabilities: operation()(
16025
- "GET",
16026
- () => "/v1/console/capabilities"
16027
- ),
16028
- getAdminSession: operation()(
16029
- "GET",
16030
- () => "/v1/admin/session"
16031
- ),
16032
- listOrganizations: operation()(
16033
- "GET",
16034
- () => "/v1/admin/organizations"
16035
- ),
16036
- selectOrganization: operation()(
16037
- "POST",
16038
- () => "/v1/admin/organizations/select"
16039
- ),
16040
- // Management keys
16041
- listManagementKeys: operation()(
16042
- "GET",
16043
- () => "/v1/admin/keys"
16044
- ),
16045
- createManagementKey: operation()(
16046
- "POST",
16047
- () => "/v1/admin/keys"
16048
- ),
16049
- revokeManagementKey: operation()(
16050
- "POST",
16051
- (id) => `/v1/admin/keys/${seg(id)}/revoke`
16052
- ),
16053
- // Providers
16054
- listProviders: operation()(
16055
- "GET",
16056
- () => "/v1/admin/providers"
16057
- ),
16058
- createProvider: operation()(
16059
- "POST",
16060
- () => "/v1/admin/providers"
16061
- ),
16062
- testProvider: operation()(
16063
- "POST",
16064
- () => "/v1/admin/providers/test"
16065
- ),
16066
- updateProvider: operation()(
16067
- "PUT",
16068
- (id) => `/v1/admin/providers/${seg(id)}`
16069
- ),
16070
- deleteProvider: operation()(
16071
- "DELETE",
16072
- (id) => `/v1/admin/providers/${seg(id)}`
16073
- ),
16074
- // Provider gateways
16075
- listProviderGateways: operation()(
16076
- "GET",
16077
- () => "/v1/admin/provider-gateways"
16078
- ),
16079
- createProviderGateway: operation()("POST", () => "/v1/admin/provider-gateways"),
16080
- testProviderGateway: operation()("POST", () => "/v1/admin/provider-gateways/test"),
16081
- updateProviderGateway: operation()("PATCH", (id) => `/v1/admin/provider-gateways/${seg(id)}`),
16082
- rotateProviderGateway: operation()("POST", (id) => `/v1/admin/provider-gateways/${seg(id)}/rotate`),
16083
- deleteProviderGateway: operation()(
16084
- "DELETE",
16085
- (id) => `/v1/admin/provider-gateways/${seg(id)}`
16086
- ),
16087
- // Applications
16088
- listApps: operation()(
16089
- "GET",
16090
- (query = {}) => `/v1/admin/apps${searchSuffix(query)}`
16091
- ),
16092
- createApp: operation()(
16093
- "POST",
16094
- () => "/v1/admin/apps"
16095
- ),
16096
- getApp: operation()(
16097
- "GET",
16098
- (appId) => `/v1/admin/apps/${seg(appId)}`
16099
- ),
16100
- updateApp: operation()(
16101
- "PUT",
16102
- (appId) => `/v1/admin/apps/${seg(appId)}`
16103
- ),
16104
- deleteApp: operation()(
16105
- "DELETE",
16106
- (appId) => `/v1/admin/apps/${seg(appId)}${searchSuffix({ confirm: appId })}`
16107
- ),
16108
- validateApp: operation()(
16109
- "POST",
16110
- (appId) => `/v1/admin/apps/${seg(appId)}/validate`
16111
- ),
16112
- // Application keys
16113
- listAppKeys: operation()(
16114
- "GET",
16115
- (appId) => `/v1/admin/apps/${seg(appId)}/keys`
16116
- ),
16117
- createAppKey: operation()(
16118
- "POST",
16119
- (appId) => `/v1/admin/apps/${seg(appId)}/keys`
16120
- ),
16121
- revokeAppKey: operation()(
16122
- "POST",
16123
- (appId, keyId) => `/v1/admin/apps/${seg(appId)}/keys/${seg(keyId)}/revoke`
16124
- ),
16125
- // Application users
16126
- listAppUsers: operation()(
16127
- "GET",
16128
- (appId, query) => `/v1/admin/apps/${seg(appId)}/users${searchSuffix(query)}`
16129
- ),
16130
- setAppUserBlocked: operation()(
16131
- "POST",
16132
- (appId, userId, blocked) => `/v1/admin/apps/${seg(appId)}/users/${seg(userId)}/${blocked ? "block" : "unblock"}`
16133
- ),
16134
- // Application usage
16135
- getAppUsage: operation()(
16136
- "GET",
16137
- (appId, query = {}) => `/v1/admin/apps/${seg(appId)}/usage${searchSuffix(query)}`
16138
- ),
16139
- getAppUsageTimeseries: operation()(
16140
- "GET",
16141
- (appId, query) => `/v1/admin/apps/${seg(appId)}/usage/timeseries${searchSuffix(query)}`
16142
- ),
16143
- getAppUsageBreakdown: operation()(
16144
- "GET",
16145
- (appId, query) => `/v1/admin/apps/${seg(appId)}/usage/breakdown${searchSuffix(query)}`
16146
- ),
16147
- listAppEvents: operation()(
16148
- "GET",
16149
- (appId, query) => `/v1/admin/apps/${seg(appId)}/events${searchSuffix(query)}`
16150
- ),
16151
- getAppAuthEventSummary: operation()(
16152
- "GET",
16153
- (appId, query) => `/v1/admin/apps/${seg(appId)}/auth-events/summary${searchSuffix(query)}`
16154
- ),
16155
- listAppAuthEvents: operation()(
16156
- "GET",
16157
- (appId, query) => `/v1/admin/apps/${seg(appId)}/auth-events${searchSuffix(query)}`
16158
- ),
16159
- listModelPrices: operation()(
16160
- "GET",
16161
- () => "/v1/admin/prices"
16162
- ),
16163
- // CLI surface
16164
- getCliCapabilities: operation()(
16165
- "GET",
16166
- () => "/v1/cli/capabilities"
16167
- ),
16168
- bootstrapCliAccount: operation()(
16169
- "POST",
16170
- () => "/v1/cli/bootstrap"
16171
- ),
16172
- createCliOperation: operation()(
16173
- "POST",
16174
- () => "/v1/cli/operations"
16175
- ),
16176
- pollCliOperation: operation()(
16177
- "GET",
16178
- (id) => `/v1/cli/operations/${seg(id)}`
16179
- ),
16180
- getCliAccount: operation()(
16181
- "GET",
16182
- () => "/v1/cli/account"
16183
- ),
16184
- getCliUsage: operation()(
16185
- "GET",
16186
- (query = {}) => `/v1/cli/usage${searchSuffix(query)}`
16187
- ),
16188
- /*
16189
- * The browser half of a handoff, called by the console's approval page rather
16190
- * than by the CLI. Every one of them carries the submission proof from the
16191
- * URL fragment in its body, so they are POSTs with no readable URL of their
16192
- * own; routing them through here is what keeps the page from writing those
16193
- * paths by hand.
16194
- */
16195
- cliBrowserDetails: operation()(
16196
- "POST",
16197
- (id) => `/v1/cli/browser/${seg(id)}/details`
16198
- ),
16199
- cliBrowserSubmit: operation()(
16200
- "POST",
16201
- (id) => `/v1/cli/browser/${seg(id)}/submit`
16202
- ),
16203
- cliBrowserRegister: operation()(
16204
- "POST",
16205
- (id) => `/v1/cli/browser/${seg(id)}/register`
16206
- ),
16207
- cliBrowserGoogle: operation()(
16208
- "POST",
16209
- (id) => `/v1/cli/browser/${seg(id)}/google`
16210
- )
16211
- };
16212
17288
 
16213
17289
  // src/common.ts
16214
17290
  import { randomBytes } from "node:crypto";
@@ -16579,14 +17655,14 @@ async function required2(flags, key, label = key) {
16579
17655
  return typeof value === "string" ? value : await prompt(label, flags);
16580
17656
  }
16581
17657
  async function resolveProvider(ctx, id) {
16582
- const { data } = await ctx.call("listProviders", []);
17658
+ const { data } = await ctx.call("listProviders");
16583
17659
  const matches = data.providers.filter((p) => p.id === id || p.slug === id);
16584
17660
  if (matches.length !== 1 || !matches[0])
16585
17661
  fail("provider_not_found", "Choose one exact provider ID or slug.");
16586
17662
  return matches[0];
16587
17663
  }
16588
17664
  async function resolveGateway(ctx, id) {
16589
- const { data } = await ctx.call("listProviderGateways", []);
17665
+ const { data } = await ctx.call("listProviderGateways");
16590
17666
  const value = data.gateways.find((g) => g.id === id);
16591
17667
  if (!value)
16592
17668
  fail("provider_gateway_not_found", "Provider gateway ID was not found.");
@@ -16596,14 +17672,14 @@ async function resourceCommand(ctx, command, args, flags) {
16596
17672
  const [group, action] = command.split(" ");
16597
17673
  const gateway = group === "provider-gateway";
16598
17674
  if (action === "types") {
16599
- const { data } = await ctx.publicCall("getCliCapabilities", []);
17675
+ const { data } = await ctx.publicCall("getCliCapabilities");
16600
17676
  return gateway ? data.providerGateways.map((entry) => ({
16601
17677
  ...entry,
16602
17678
  type: entry.type === "cf_aig" ? "cloudflare" : entry.type
16603
17679
  })) : data.providers;
16604
17680
  }
16605
17681
  if (action === "list")
16606
- return gateway ? (await ctx.call("listProviderGateways", [])).data : (await ctx.call("listProviders", [])).data;
17682
+ return gateway ? (await ctx.call("listProviderGateways")).data : (await ctx.call("listProviders")).data;
16607
17683
  let existingProvider;
16608
17684
  let existingGateway;
16609
17685
  const target = args[0];
@@ -16623,14 +17699,14 @@ async function resourceCommand(ctx, command, args, flags) {
16623
17699
  `Delete ${group} ${existingGateway.name} (${existingGateway.id})? Referenced by ${existingGateway.referencedCount} providers; deletion is refused until all references are removed.`,
16624
17700
  flags
16625
17701
  );
16626
- return (await ctx.call("deleteProviderGateway", [existingGateway.id])).data;
17702
+ return (await ctx.call("deleteProviderGateway", { params: { id: existingGateway.id } })).data;
16627
17703
  }
16628
17704
  if (!existingProvider) fail("invalid_arguments", "Supply the resource to remove.");
16629
17705
  await confirm(
16630
17706
  `Delete ${group} ${existingProvider.name} (${existingProvider.id})? Apps referencing this provider lose access through this slug.`,
16631
17707
  flags
16632
17708
  );
16633
- return (await ctx.call("deleteProvider", [existingProvider.id])).data;
17709
+ return (await ctx.call("deleteProvider", { params: { id: existingProvider.id } })).data;
16634
17710
  }
16635
17711
  if (action === "add") {
16636
17712
  const type = await required2(
@@ -16664,7 +17740,7 @@ async function resourceCommand(ctx, command, args, flags) {
16664
17740
  await ctx.bootstrap();
16665
17741
  if (flags.browser)
16666
17742
  return ctx.operation("provider-gateway.add", { ...draft2 });
16667
- const created2 = await ctx.create("createProviderGateway", [], body2);
17743
+ const created2 = await ctx.create("createProviderGateway", { body: body2 });
16668
17744
  await created2.complete();
16669
17745
  return created2.data;
16670
17746
  }
@@ -16696,8 +17772,9 @@ async function resourceCommand(ctx, command, args, flags) {
16696
17772
  let body;
16697
17773
  if (!flags.browser) body = validate(ProviderCreateRequestSchema, draft);
16698
17774
  await ctx.bootstrap();
16699
- if (flags.browser) return ctx.operation("provider.add", { ...draft });
16700
- const created = await ctx.create("createProvider", [], body);
17775
+ if (flags.browser)
17776
+ return ctx.operation("provider.add", validate(HandoffProviderAddPayloadSchema, draft));
17777
+ const created = await ctx.create("createProvider", { body });
16701
17778
  await created.complete();
16702
17779
  return created.data;
16703
17780
  }
@@ -16707,7 +17784,8 @@ async function resourceCommand(ctx, command, args, flags) {
16707
17784
  const value2 = await secret(flags, "Gateway token");
16708
17785
  if (flags.browser)
16709
17786
  return ctx.operation("provider-gateway.rotate-key", { id: existingGateway.id, revision: existingGateway.revision });
16710
- return (await ctx.call("rotateProviderGateway", [existingGateway.id], {
17787
+ return (await ctx.call("rotateProviderGateway", {
17788
+ params: { id: existingGateway.id },
16711
17789
  body: { token: value2, revision: existingGateway.revision }
16712
17790
  })).data;
16713
17791
  }
@@ -16720,7 +17798,8 @@ async function resourceCommand(ctx, command, args, flags) {
16720
17798
  const value = await secret(flags, "Provider API key");
16721
17799
  if (flags.browser)
16722
17800
  return ctx.operation("provider.rotate-key", { id: existingProvider.id, revision: existingProvider.revision });
16723
- return (await ctx.call("updateProvider", [existingProvider.id], {
17801
+ return (await ctx.call("updateProvider", {
17802
+ params: { id: existingProvider.id },
16724
17803
  body: { secret: value, revision: existingProvider.revision }
16725
17804
  })).data;
16726
17805
  }
@@ -16731,7 +17810,7 @@ async function resourceCommand(ctx, command, args, flags) {
16731
17810
  name: await required2(flags, "name"),
16732
17811
  revision: existingGateway.revision
16733
17812
  });
16734
- return (await ctx.call("updateProviderGateway", [existingGateway.id], { body: body2 })).data;
17813
+ return (await ctx.call("updateProviderGateway", { params: { id: existingGateway.id }, body: body2 })).data;
16735
17814
  }
16736
17815
  if (!existingProvider) fail("invalid_arguments", "Supply the provider to update.");
16737
17816
  const draft = {};
@@ -16774,13 +17853,20 @@ async function resourceCommand(ctx, command, args, flags) {
16774
17853
  ...flags.browser ? { secret: "validation-placeholder" } : {}
16775
17854
  });
16776
17855
  if (flags.browser)
16777
- return ctx.operation("provider.update", { id: existingProvider.id, revision: existingProvider.revision, ...draft });
16778
- return (await ctx.call("updateProvider", [existingProvider.id], { body })).data;
17856
+ return ctx.operation(
17857
+ "provider.update",
17858
+ validate(HandoffProviderUpdatePayloadSchema, {
17859
+ id: existingProvider.id,
17860
+ revision: existingProvider.revision,
17861
+ ...draft
17862
+ })
17863
+ );
17864
+ return (await ctx.call("updateProvider", { params: { id: existingProvider.id }, body })).data;
16779
17865
  }
16780
17866
  fail("unknown_command", "Unknown command.");
16781
17867
  }
16782
17868
  async function assertSupportedType(ctx, gateway, type) {
16783
- const { data: capabilities } = await ctx.publicCall("getCliCapabilities", []);
17869
+ const { data: capabilities } = await ctx.publicCall("getCliCapabilities");
16784
17870
  const supported = gateway ? capabilities.providerGateways : capabilities.providers;
16785
17871
  if (!supported.some((p) => p.type === type))
16786
17872
  fail(
@@ -16805,6 +17891,95 @@ import { join, resolve } from "node:path";
16805
17891
  import { homedir, hostname as hostname3, platform } from "node:os";
16806
17892
  import { setTimeout as delay } from "node:timers/promises";
16807
17893
  var posixPermissions = () => process.platform !== "win32";
17894
+ var StoredOrigin = external_exports.string().refine(
17895
+ (value) => {
17896
+ try {
17897
+ origin(value);
17898
+ return true;
17899
+ } catch {
17900
+ return false;
17901
+ }
17902
+ },
17903
+ "must be a deployment origin without credentials, path, query or fragment"
17904
+ );
17905
+ var StoredConnectionSchema = external_exports.looseObject({
17906
+ url: StoredOrigin,
17907
+ authenticated: external_exports.boolean(),
17908
+ credential: external_exports.string().optional(),
17909
+ account: external_exports.looseObject({}).optional(),
17910
+ deployment: external_exports.looseObject({}).optional()
17911
+ }).refine(
17912
+ (connection) => !connection.authenticated || typeof connection.credential === "string",
17913
+ "an authenticated connection must carry its credential"
17914
+ );
17915
+ var StoredOperationSchema = external_exports.looseObject({
17916
+ url: StoredOrigin,
17917
+ pollToken: external_exports.string(),
17918
+ kind: external_exports.string(),
17919
+ phase: external_exports.enum(["initiating", "pending"]).optional(),
17920
+ payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
17921
+ generation: external_exports.number().optional(),
17922
+ completed: external_exports.boolean().optional()
17923
+ });
17924
+ var StoredMutationSchema = external_exports.looseObject({
17925
+ id: external_exports.string(),
17926
+ proof: external_exports.string(),
17927
+ requestHash: external_exports.string(),
17928
+ url: external_exports.string(),
17929
+ accountId: external_exports.string().nullable(),
17930
+ path: external_exports.string(),
17931
+ createdAt: external_exports.string(),
17932
+ completedAt: external_exports.string().optional()
17933
+ });
17934
+ var StoredInstallationSchema = external_exports.looseObject({
17935
+ id: external_exports.string(),
17936
+ name: external_exports.string(),
17937
+ accountId: external_exports.string(),
17938
+ version: external_exports.string(),
17939
+ phase: external_exports.enum(["prepared", "deployed", "ready"]),
17940
+ databaseId: external_exports.string().optional(),
17941
+ databaseName: external_exports.string().optional(),
17942
+ url: external_exports.string().optional(),
17943
+ vars: external_exports.record(external_exports.string(), external_exports.string()).optional(),
17944
+ domains: external_exports.array(external_exports.string()).optional(),
17945
+ pendingDomain: external_exports.string().optional(),
17946
+ bootstrap: external_exports.looseObject({ idempotencyKey: external_exports.string(), pollToken: external_exports.string() }).optional(),
17947
+ secrets: external_exports.record(external_exports.string(), external_exports.string()).optional()
17948
+ });
17949
+ var CliStateSchema = external_exports.object({
17950
+ schemaVersion: external_exports.literal(1),
17951
+ active: StoredConnectionSchema.nullable(),
17952
+ previous: StoredConnectionSchema.optional(),
17953
+ operations: external_exports.record(external_exports.string(), StoredOperationSchema),
17954
+ generation: external_exports.number().optional(),
17955
+ bootstrap: external_exports.looseObject({
17956
+ createdAt: external_exports.string().optional(),
17957
+ idempotencyKey: external_exports.string(),
17958
+ pollToken: external_exports.string()
17959
+ }).optional(),
17960
+ mutations: external_exports.record(external_exports.string(), StoredMutationSchema).optional(),
17961
+ installations: external_exports.record(external_exports.string(), StoredInstallationSchema).optional()
17962
+ });
17963
+ var RECOVER_STATE = "Recover the state file; it will not be treated as a new account.";
17964
+ function parseState(path, text2) {
17965
+ let value;
17966
+ try {
17967
+ value = JSON.parse(text2);
17968
+ } catch {
17969
+ fail("invalid_state", `Connection state at ${path} is not valid JSON.`, RECOVER_STATE, 4);
17970
+ }
17971
+ try {
17972
+ return validate(CliStateSchema, value);
17973
+ } catch (error51) {
17974
+ const first = error51 instanceof CliError ? error51.message.split("; ")[0] : void 0;
17975
+ fail(
17976
+ "invalid_state",
17977
+ `Unsupported or malformed connection state at ${path}${first ? `: ${first}` : ""}.`,
17978
+ RECOVER_STATE,
17979
+ 4
17980
+ );
17981
+ }
17982
+ }
16808
17983
  function stateDirectory() {
16809
17984
  if (platform() === "win32")
16810
17985
  return join(
@@ -16929,7 +18104,6 @@ async function breakAbandonedLock(path) {
16929
18104
  return true;
16930
18105
  }
16931
18106
  var same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
16932
- var records = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
16933
18107
  function mergeRecords(baseline, local, disk) {
16934
18108
  const merged = { ...disk };
16935
18109
  for (const [id, value] of Object.entries(local)) {
@@ -16941,8 +18115,7 @@ function mergeRecords(baseline, local, disk) {
16941
18115
  return merged;
16942
18116
  }
16943
18117
  function mergeState(baseline, local, disk) {
16944
- if (!disk || disk.schemaVersion !== 1 || typeof disk.operations !== "object" || disk.operations === null || Array.isArray(disk.operations))
16945
- return local;
18118
+ if (!disk) return local;
16946
18119
  const connection = baseline && same(baseline.active, local.active) && same(baseline.previous, local.previous) && same(baseline.generation, local.generation) && same(baseline.bootstrap, local.bootstrap) ? disk : local;
16947
18120
  const merged = {
16948
18121
  schemaVersion: 1,
@@ -16960,13 +18133,13 @@ function mergeState(baseline, local, disk) {
16960
18133
  const mutations = mergeRecords(
16961
18134
  baseline?.mutations ?? {},
16962
18135
  local.mutations ?? {},
16963
- records(disk.mutations)
18136
+ disk.mutations ?? {}
16964
18137
  );
16965
18138
  if (Object.keys(mutations).length) merged.mutations = mutations;
16966
18139
  const installations = mergeRecords(
16967
18140
  baseline?.installations ?? {},
16968
18141
  local.installations ?? {},
16969
- records(disk.installations)
18142
+ disk.installations ?? {}
16970
18143
  );
16971
18144
  if (Object.keys(installations).length) merged.installations = installations;
16972
18145
  return merged;
@@ -17030,50 +18203,32 @@ var StateStore = class {
17030
18203
  });
17031
18204
  }
17032
18205
  }
18206
+ /**
18207
+ * The state this command starts from: what the file holds, or a fresh state
18208
+ * when there is no file.
18209
+ *
18210
+ * Only a missing file is a fresh state. A file that is present and cannot be
18211
+ * read — wrong owner, wrong mode, unparseable, or a shape this release does
18212
+ * not know — is refused, because every one of those would otherwise start a
18213
+ * command as though this machine had never held an account.
18214
+ */
17033
18215
  async read() {
17034
- try {
17035
- const s = await lstat(this.path);
17036
- if (!s.isFile() || s.isSymbolicLink() || posixPermissions() && (process.getuid && s.uid !== process.getuid() || (s.mode & 63) !== 0))
17037
- fail(
17038
- "unsafe_storage",
17039
- "Connection state must be owned by you with mode 0600."
17040
- );
17041
- const state = JSON.parse(await readFile2(this.path, "utf8"));
17042
- if (state.schemaVersion !== 1 || !Object.hasOwn(state, "active") || !state.operations || typeof state.operations !== "object" || Array.isArray(state.operations))
17043
- fail("invalid_state", "Unsupported or malformed connection state.");
17044
- if (state.active !== null) {
17045
- const a = state.active;
17046
- if (!a || typeof a !== "object" || typeof a.url !== "string" || typeof a.authenticated !== "boolean" || a.authenticated && typeof a.credential !== "string")
17047
- fail(
17048
- "invalid_state",
17049
- "Malformed prior connection; recovery is required."
17050
- );
17051
- origin(a.url);
17052
- }
17053
- for (const op of Object.values(state.operations)) {
17054
- if (!op || typeof op.url !== "string" || typeof op.pollToken !== "string" || typeof op.kind !== "string")
17055
- fail(
17056
- "invalid_state",
17057
- "Malformed operation state; recovery is required."
17058
- );
17059
- origin(op.url);
17060
- }
17061
- this.baseline = structuredClone(state);
17062
- return state;
17063
- } catch (e) {
17064
- if (errorCode(e) === "ENOENT") {
17065
- const empty = { schemaVersion: 1, active: null, operations: {} };
17066
- this.baseline = structuredClone(empty);
17067
- return empty;
17068
- }
17069
- if (e instanceof CliError) throw e;
18216
+ const held = await lstat(this.path).catch((error51) => {
18217
+ if (errorCode(error51) === "ENOENT") return null;
18218
+ fail("invalid_state", "Cannot read connection state.", RECOVER_STATE, 4);
18219
+ });
18220
+ if (held && (!held.isFile() || held.isSymbolicLink() || posixPermissions() && (process.getuid && held.uid !== process.getuid() || (held.mode & 63) !== 0)))
17070
18221
  fail(
17071
- "invalid_state",
17072
- "Cannot read connection state.",
17073
- "Recover the state file; it will not be treated as a new account.",
17074
- 4
18222
+ "unsafe_storage",
18223
+ "Connection state must be owned by you with mode 0600."
17075
18224
  );
17076
- }
18225
+ const state = held && await this.stored() || {
18226
+ schemaVersion: 1,
18227
+ active: null,
18228
+ operations: {}
18229
+ };
18230
+ this.baseline = structuredClone(state);
18231
+ return state;
17077
18232
  }
17078
18233
  /**
17079
18234
  * Persists the state, merged with whatever another command wrote meanwhile.
@@ -17125,13 +18280,23 @@ var StateStore = class {
17125
18280
  else state.generation = merged.generation;
17126
18281
  }
17127
18282
  }
17128
- /** The state file as it stands, or null when there is nothing to merge with. */
18283
+ /**
18284
+ * The state file as it stands, or null when there is no file at all.
18285
+ *
18286
+ * A file that will not parse is refused here rather than treated as nothing
18287
+ * to merge with: this is what a write reads to keep another command's
18288
+ * receipts, and answering "nothing" would replace a file whose contents
18289
+ * could not be understood with this command's own state.
18290
+ */
17129
18291
  async stored() {
18292
+ let text2;
17130
18293
  try {
17131
- return JSON.parse(await readFile2(this.path, "utf8"));
17132
- } catch {
17133
- return null;
18294
+ text2 = await readFile2(this.path, "utf8");
18295
+ } catch (error51) {
18296
+ if (errorCode(error51) === "ENOENT") return null;
18297
+ fail("invalid_state", "Cannot read connection state.", RECOVER_STATE, 4);
17134
18298
  }
18299
+ return parseState(this.path, text2);
17135
18300
  }
17136
18301
  async replace(state) {
17137
18302
  const temp = join(this.directory, `.state-${randomToken()}`);
@@ -17210,15 +18375,12 @@ var EXAMPLE_GAP_NOTES = {
17210
18375
  body: `This app allows no path whose request shape is known here, so ${BODY_PLACEHOLDER} stands in for the body the provider documents.`
17211
18376
  };
17212
18377
  function examplePath(type, { gatewayRouted = false, model } = {}) {
17213
- if (type === "openai") return "v1/responses";
17214
- if (type === "anthropic") return "v1/messages";
17215
- if (type === "gemini")
17216
- return model ? `v1beta/models/${model}:generateContent` : void 0;
17217
- if (gatewayRouted) return "v1/chat/completions";
17218
- if (type === "groq") return "openai/v1/chat/completions";
17219
- if (type === "fireworks") return "inference/v1/chat/completions";
17220
- if (["deepseek", "perplexity", "bytedance"].includes(type)) return "chat/completions";
17221
- return "v1/chat/completions";
18378
+ const descriptor = isProviderType(type) ? providerDescriptor(type) : void 0;
18379
+ if (descriptor?.modelInPath) {
18380
+ return model ? API_STYLE_PATHS.gemini_native.replace("{model}", model) : void 0;
18381
+ }
18382
+ const own = descriptor?.examplePath ?? API_STYLE_PATHS.chat_completions;
18383
+ return gatewayRouted && own.endsWith("chat/completions") ? API_STYLE_PATHS.chat_completions : own;
17222
18384
  }
17223
18385
  function bodyFor(path, model) {
17224
18386
  const messages = [{ role: "user", content: "Say hello." }];
@@ -17229,8 +18391,8 @@ function bodyFor(path, model) {
17229
18391
  return { contents: [{ parts: [{ text: "Say hello." }] }] };
17230
18392
  return null;
17231
18393
  }
17232
- function modelsFor(provider, policy2, prices) {
17233
- if (policy2?.allowed_models?.length) return policy2.allowed_models;
18394
+ function modelsFor(provider, policy, prices) {
18395
+ if (policy?.allowed_models?.length) return policy.allowed_models;
17234
18396
  return [
17235
18397
  ...Object.keys(provider.pricing ?? {}),
17236
18398
  ...Object.entries(prices[provider.type] ?? {}).filter(([, price]) => price.output !== void 0).map(([model]) => model)
@@ -17238,18 +18400,17 @@ function modelsFor(provider, policy2, prices) {
17238
18400
  }
17239
18401
  function firstRequest(routing, providers, prices) {
17240
18402
  let fallback;
17241
- for (const provider of providers) {
17242
- if (provider.status !== "active") continue;
17243
- const policy2 = routing?.providers?.[provider.slug];
17244
- if (routing?.providerMode === "selected" && !policy2) continue;
17245
- const models = modelsFor(provider, policy2, prices);
18403
+ const candidates = routing ? reachableProviders(routing, providers) : providers.filter((provider) => provider.status === "active");
18404
+ for (const provider of candidates) {
18405
+ const policy = routing?.providers.mode === "selected" ? providerPolicyFor(routing, provider.slug) : void 0;
18406
+ const models = modelsFor(provider, policy, prices);
17246
18407
  const model = models[0] ?? MODEL_PLACEHOLDER;
17247
18408
  const gaps = models.length ? [] : ["model"];
17248
18409
  const fallbackPath = examplePath(provider.type, {
17249
18410
  gatewayRouted: Boolean(provider.providerGatewayId),
17250
18411
  model
17251
18412
  });
17252
- const paths = policy2?.allowed_paths?.length ? policy2.allowed_paths : fallbackPath ? [fallbackPath] : [];
18413
+ const paths = policy?.allowed_paths?.length ? policy.allowed_paths : fallbackPath ? [fallbackPath] : [];
17253
18414
  for (const entry of paths) {
17254
18415
  const path = typeof entry === "string" ? entry : entry.path;
17255
18416
  if (path.includes("*")) continue;
@@ -17298,8 +18459,16 @@ function curlSnippet(options) {
17298
18459
  ` + (example.anthropic ? " -H 'anthropic-version: 2023-06-01' \\\n" : "") + ` -d ${shellQuote(body)}
17299
18460
  `;
17300
18461
  }
18462
+ var ISSUER_TOKEN_NOTE = "Replace yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh) with your configured issuer integration.";
18463
+ function swiftSignsInUsers(authentication) {
18464
+ return authentication?.type === "apple_app_attest" && authentication.end_user.source === "issuer";
18465
+ }
18466
+ function swiftAuthMode(authentication) {
18467
+ return swiftSignsInUsers(authentication) ? ".appAttest(issuerTokenProvider: { forceRefresh in\n // Return a fresh signed token from your configured identity SDK.\n try await yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh)\n })" : ".appAttestInstall";
18468
+ }
17301
18469
  function swiftSnippet(options) {
17302
- const { baseUrl, appId, example, notes: notes2, authMode = ".appAttestInstall" } = options;
18470
+ const { baseUrl, appId, example, notes: notes2, authentication } = options;
18471
+ const authMode = swiftAuthMode(authentication);
17303
18472
  const target = "endpoint" in example.target ? `endpointSlug: ${JSON.stringify(example.target.endpoint)}` : `provider: ${JSON.stringify(example.target.provider)},
17304
18473
  providerPath: ${JSON.stringify(example.target.path)}`;
17305
18474
  const body = example.body ? `request.httpBody = Data(${JSON.stringify(JSON.stringify(example.body))}.utf8)` : `// Supply the provider-native JSON body.
@@ -17323,33 +18492,54 @@ print(String(decoding: data, as: UTF8.self))
17323
18492
  `;
17324
18493
  }
17325
18494
 
18495
+ // ../src/shared/app-defaults.ts
18496
+ function unlimitedScope() {
18497
+ return {
18498
+ requests: { per_minute: null, per_day: null },
18499
+ spending: { monthly_usd: null }
18500
+ };
18501
+ }
18502
+ function mobileDefaultLimits() {
18503
+ return {
18504
+ per_user: { requests: { per_minute: 10, per_day: 300 }, spending: { monthly_usd: null } },
18505
+ per_app: unlimitedScope()
18506
+ };
18507
+ }
18508
+ function emptyPolicy() {
18509
+ return { allowed_paths: [], allowed_models: [] };
18510
+ }
18511
+ function newAppConfig(input) {
18512
+ const routing = { providers: { mode: "all" }, model_rewrites: {} };
18513
+ if (input.type === "api_key") {
18514
+ return {
18515
+ authentication: {
18516
+ type: "api_key",
18517
+ ...input.endUser ? { end_user: input.endUser } : {}
18518
+ },
18519
+ routing
18520
+ };
18521
+ }
18522
+ return {
18523
+ authentication: {
18524
+ type: "apple_app_attest",
18525
+ app_attest: {
18526
+ team_id: input.teamId,
18527
+ bundle_id: input.bundleId,
18528
+ ...input.environments ? { environments: [...input.environments] } : {}
18529
+ },
18530
+ end_user: input.endUser ?? { source: "app_install" }
18531
+ },
18532
+ routing,
18533
+ limits: mobileDefaultLimits()
18534
+ };
18535
+ }
18536
+
17326
18537
  // src/apps.ts
17327
- var unlimited = () => ({
17328
- requests: { per_minute: null, per_day: null },
17329
- spending: { monthly_usd: null }
17330
- });
17331
- var policy = () => ({ allowed_paths: [], allowed_models: [] });
17332
18538
  function documentOf(app2) {
17333
18539
  return localApp({ name: app2.name, config: app2.config, status: app2.status });
17334
18540
  }
17335
18541
  function localApp(value) {
17336
- const doc = validate(AppWriteSchema, value);
17337
- const auth = doc.config.authentication;
17338
- if (auth.type === "apple_app_attest") {
17339
- if (!/^[A-Z0-9]{10}$/.test(auth.app_attest.team_id))
17340
- fail(
17341
- "invalid_input",
17342
- "App Attest team_id must contain ten uppercase letters or digits."
17343
- );
17344
- if (!/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(auth.app_attest.bundle_id))
17345
- fail(
17346
- "invalid_input",
17347
- "App Attest bundle_id must be a reverse DNS identifier."
17348
- );
17349
- }
17350
- if (auth.type === "api_key" && !auth.end_user && doc.config.limits?.per_user && (doc.config.limits.per_user.requests.per_minute !== null || doc.config.limits.per_user.requests.per_day !== null || doc.config.limits.per_user.spending.monthly_usd !== null))
17351
- fail("invalid_input", "User limits require an end-user identity source.");
17352
- return doc;
18542
+ return validate(AppWriteSchema, value);
17353
18543
  }
17354
18544
  async function appDocument(flags, current) {
17355
18545
  if (flags.file) {
@@ -17383,27 +18573,22 @@ async function appDocument(flags, current) {
17383
18573
  doc = localApp({
17384
18574
  name: flags.name ?? (ios && bundle ? bundle.split(".").slice(-2).join(" ") : await required2(flags, "name", "App name")),
17385
18575
  status: flags.status ?? "active",
17386
- config: {
17387
- authentication: ios ? {
17388
- type: "apple_app_attest",
17389
- end_user: { source: "app_install" },
17390
- app_attest: {
17391
- team_id: await required2(flags, "team-id", "Apple team ID"),
17392
- bundle_id: bundle,
17393
- environments: (flags["attest-environments"] ?? "production,development").split(",")
17394
- }
17395
- } : { type: "api_key" },
17396
- routing: { providers: { mode: "all" }, model_rewrites: {} },
17397
- ...ios ? {
17398
- limits: {
17399
- per_user: {
17400
- requests: { per_minute: 10, per_day: 300 },
17401
- spending: { monthly_usd: null }
17402
- },
17403
- per_app: unlimited()
17404
- }
17405
- } : {}
17406
- }
18576
+ config: ios ? newAppConfig({
18577
+ type: "apple_app_attest",
18578
+ teamId: await required2(flags, "team-id", "Apple team ID"),
18579
+ // Asked for above, because `ios` is exactly when there is one.
18580
+ bundleId: bundle ?? "",
18581
+ /*
18582
+ * Both environments, which is `agw`'s own default rather than the
18583
+ * schema's: someone reaching for the CLI to create an iOS app is
18584
+ * building it, and a development-signed build is what they have in
18585
+ * hand. Passed through unmapped, so `localApp` below is what
18586
+ * refuses a name that is neither.
18587
+ */
18588
+ environments: attestEnvironments(
18589
+ flags["attest-environments"] ?? "production,development"
18590
+ )
18591
+ }) : newAppConfig({ type: "api_key" })
17407
18592
  });
17408
18593
  }
17409
18594
  if (flags.name) doc.name = flags.name;
@@ -17417,22 +18602,25 @@ async function appDocument(flags, current) {
17417
18602
  if (flags["team-id"]) auth.app_attest.team_id = flags["team-id"];
17418
18603
  if (flags["bundle-id"]) auth.app_attest.bundle_id = flags["bundle-id"];
17419
18604
  if (flags["attest-environments"])
17420
- auth.app_attest.environments = flags["attest-environments"].split(",").map((value) => value === "development" ? "development" : "production");
18605
+ auth.app_attest.environments = attestEnvironments(flags["attest-environments"]);
17421
18606
  }
17422
18607
  const selectedProviders = flagList(flags.provider);
17423
18608
  if (selectedProviders.length) {
17424
- const previous = doc.config.routing.providers.selected ?? {};
18609
+ const previous = selectedProviderPolicies(doc.config.routing);
17425
18610
  doc.config.routing.providers = {
17426
18611
  mode: "selected",
17427
18612
  selected: Object.fromEntries(
17428
- selectedProviders.map((slug) => [slug, previous[slug] ?? policy()])
18613
+ selectedProviders.map((slug) => [slug, previous[slug] ?? emptyPolicy()])
17429
18614
  )
17430
18615
  };
17431
18616
  }
17432
18617
  if (flags["all-providers"]) doc.config.routing.providers = { mode: "all" };
17433
18618
  return localApp(doc);
17434
18619
  }
17435
- async function remoteValidation(ctx, doc, id = "validation-preview") {
18620
+ function attestEnvironments(value) {
18621
+ return value.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
18622
+ }
18623
+ async function remoteValidation(ctx, doc, id) {
17436
18624
  if (!ctx.active?.credential)
17437
18625
  return {
17438
18626
  local: true,
@@ -17441,7 +18629,7 @@ async function remoteValidation(ctx, doc, id = "validation-preview") {
17441
18629
  "Provider references, pricing, and saved configuration checks require login."
17442
18630
  ]
17443
18631
  };
17444
- const { data } = await ctx.call("validateApp", [id], { body: doc });
18632
+ const { data } = id === void 0 ? await ctx.call("validateAppDraft", { body: doc }) : await ctx.call("validateApp", { params: { app: id }, body: doc });
17445
18633
  return { local: true, remote: true, ...data };
17446
18634
  }
17447
18635
  function mintedKey(value) {
@@ -17465,7 +18653,7 @@ async function saveKey(ctx, keyRecord, output, appId) {
17465
18653
  }
17466
18654
  let revoked = false;
17467
18655
  try {
17468
- await ctx.call("revokeAppKey", [appId, keyRecord.id]);
18656
+ await ctx.call("revokeAppKey", { params: { app: appId, key: keyRecord.id } });
17469
18657
  revoked = true;
17470
18658
  } catch {
17471
18659
  }
@@ -17495,7 +18683,7 @@ async function saveKey(ctx, keyRecord, output, appId) {
17495
18683
  }
17496
18684
  async function appCommand(ctx, command, args, flags) {
17497
18685
  const action = command.slice(4);
17498
- if (action === "list") return (await ctx.call("listApps", [])).data;
18686
+ if (action === "list") return (await ctx.call("listApps")).data;
17499
18687
  if (action === "validate") {
17500
18688
  const doc2 = localApp(await jsonFile(await required2(flags, "file")));
17501
18689
  return { definition: doc2, validation: await remoteValidation(ctx, doc2) };
@@ -17505,7 +18693,7 @@ async function appCommand(ctx, command, args, flags) {
17505
18693
  let revision = 0;
17506
18694
  const appId2 = args[0] ?? "";
17507
18695
  if (action === "update") {
17508
- const r = await ctx.call("getApp", [appId2]);
18696
+ const r = await ctx.call("getApp", { params: { app: appId2 } });
17509
18697
  current = documentOf(r.data.app);
17510
18698
  revision = r.data.app.revision;
17511
18699
  if (!revision && !flags["dry-run"])
@@ -17529,16 +18717,15 @@ async function appCommand(ctx, command, args, flags) {
17529
18717
  definition: doc2,
17530
18718
  validation: await remoteValidation(ctx, doc2, appId2 || void 0)
17531
18719
  };
17532
- const output = action === "add" && doc2.config.authentication.type === "api_key" ? await ctx.keyOutput(operations.createApp.path(), doc2, flags["key-output"]) : null;
18720
+ const output = action === "add" && doc2.config.authentication.type === "api_key" ? await ctx.keyOutput(operationPath("createApp"), doc2, flags["key-output"]) : null;
17533
18721
  try {
17534
18722
  let app2;
17535
- let resolved;
17536
18723
  let configError;
17537
18724
  let key;
17538
18725
  if (action === "add") {
17539
18726
  await ctx.bootstrap();
17540
- const created = await ctx.create("createApp", [], doc2);
17541
- ({ app: app2, resolved, config_error: configError } = created.data);
18727
+ const created = await ctx.create("createApp", { body: doc2 });
18728
+ ({ app: app2, config_error: configError } = created.data);
17542
18729
  if (output) {
17543
18730
  if (created.keyMetadata) key = created.keyMetadata;
17544
18731
  else {
@@ -17556,10 +18743,11 @@ async function appCommand(ctx, command, args, flags) {
17556
18743
  }
17557
18744
  await created.complete();
17558
18745
  } else {
17559
- const updated = await ctx.call("updateApp", [appId2], {
18746
+ const updated = await ctx.call("updateApp", {
18747
+ params: { app: appId2 },
17560
18748
  body: { ...doc2, revision }
17561
18749
  });
17562
- ({ app: app2, resolved, config_error: configError } = updated.data);
18750
+ ({ app: app2, config_error: configError } = updated.data);
17563
18751
  }
17564
18752
  let snippet;
17565
18753
  try {
@@ -17574,7 +18762,6 @@ ${snippet}`;
17574
18762
  return {
17575
18763
  ...snippet ? { snippet } : {},
17576
18764
  app: app2,
17577
- resolved,
17578
18765
  config_error: configError,
17579
18766
  ...key ? { applicationKey: key } : {},
17580
18767
  guidance: doc2.config.authentication.type === "apple_app_attest" ? `Add https://github.com/maxceem/app-ai-gateway-swift from 1.0.0, enable App Attest, and test on a supported physical device. Run agw app snippet ${app2.id} --provider <slug> for integration code. Production releases should use production-only App Attest.` : `Store the generated key on your server; never embed it in a mobile application. Run agw app snippet ${app2.id} for this request again.`
@@ -17586,31 +18773,31 @@ ${snippet}`;
17586
18773
  if (action.startsWith("key ")) {
17587
18774
  const sub = action.slice(4);
17588
18775
  const appId2 = args[0] ?? "";
17589
- const { data: app2 } = await ctx.call("getApp", [appId2]);
18776
+ const { data: app2 } = await ctx.call("getApp", { params: { app: appId2 } });
17590
18777
  if (documentOf(app2.app).config.authentication.type !== "api_key")
17591
18778
  fail(
17592
18779
  "invalid_input",
17593
18780
  "Only server applications support application keys."
17594
18781
  );
17595
- if (sub === "list") return (await ctx.call("listAppKeys", [appId2])).data;
18782
+ if (sub === "list") return (await ctx.call("listAppKeys", { params: { app: appId2 } })).data;
17596
18783
  if (sub === "revoke") {
17597
18784
  const keyId = args[1] ?? "";
17598
18785
  await confirm(
17599
18786
  `Revoke key ${keyId} for app ${appId2}? Clients using it will lose access.`,
17600
18787
  flags
17601
18788
  );
17602
- return (await ctx.call("revokeAppKey", [appId2, keyId])).data;
18789
+ return (await ctx.call("revokeAppKey", { params: { app: appId2, key: keyId } })).data;
17603
18790
  }
17604
18791
  const name = await required2(flags, "name");
17605
18792
  if (!name.trim() || name.length > 100)
17606
18793
  fail("invalid_input", "Key name must be 1\u2013100 characters.");
17607
18794
  const output = await ctx.keyOutput(
17608
- operations.createAppKey.path(appId2),
18795
+ operationPath("createAppKey", { app: appId2 }),
17609
18796
  { name },
17610
18797
  flags["key-output"]
17611
18798
  );
17612
18799
  try {
17613
- const created = await ctx.create("createAppKey", [appId2], { name });
18800
+ const created = await ctx.create("createAppKey", { params: { app: appId2 }, body: { name } });
17614
18801
  let applicationKey = created.keyMetadata;
17615
18802
  if (!applicationKey) {
17616
18803
  const minted = mintedKey(created.data);
@@ -17631,7 +18818,7 @@ ${snippet}`;
17631
18818
  }
17632
18819
  }
17633
18820
  const appId = args[0] ?? "";
17634
- const { data } = await ctx.call("getApp", [appId]);
18821
+ const { data } = await ctx.call("getApp", { params: { app: appId } });
17635
18822
  if (action === "show") return data;
17636
18823
  const doc = documentOf(data.app);
17637
18824
  if (action === "remove") {
@@ -17639,13 +18826,13 @@ ${snippet}`;
17639
18826
  `Delete app ${data.app.name} (${appId})? Its keys, users and authentication state will be removed and clients will lose access.`,
17640
18827
  flags
17641
18828
  );
17642
- return (await ctx.call("deleteApp", [appId])).data;
18829
+ return (await ctx.call("deleteApp", { params: { app: appId }, query: { confirm: appId } })).data;
17643
18830
  }
17644
18831
  if (action === "check") {
17645
18832
  const validation = await remoteValidation(ctx, doc, appId);
17646
- const { data: providers } = await ctx.call("listProviders", []);
17647
- const selected = doc.config.routing.providers.mode === "all" ? providers.providers : providers.providers.filter(
17648
- (p) => Object.hasOwn(doc.config.routing.providers.selected ?? {}, p.slug)
18833
+ const { data: providers } = await ctx.call("listProviders");
18834
+ const selected = providers.providers.filter(
18835
+ (p) => providerPolicyFor(doc.config.routing, p.slug) !== void 0
17649
18836
  );
17650
18837
  return {
17651
18838
  appId,
@@ -17656,7 +18843,7 @@ ${snippet}`;
17656
18843
  slug: p.slug,
17657
18844
  status: p.status
17658
18845
  })),
17659
- ready: doc.status === "active" && selected.some((p) => p.status === "active") && !data.config_error,
18846
+ ready: doc.status === "active" && reachableProviders(doc.config.routing, selected).length > 0 && !data.config_error,
17660
18847
  limitations: [
17661
18848
  "No inference was sent.",
17662
18849
  "Physical device attestation, issuer login, subscription entitlement and upstream credentials were not exercised."
@@ -17677,7 +18864,7 @@ ${snippet}`;
17677
18864
  const notes2 = [];
17678
18865
  let example;
17679
18866
  if (flags.endpoint) {
17680
- const endpoint = doc.config.endpoints?.[flags.endpoint];
18867
+ const endpoint = doc.config.endpoints[flags.endpoint];
17681
18868
  if (!endpoint)
17682
18869
  fail("endpoint_not_found", "Choose an existing named endpoint.");
17683
18870
  const responses = endpoint.api_style === "responses";
@@ -17688,14 +18875,9 @@ ${snippet}`;
17688
18875
  gaps: responses ? [] : ["body"]
17689
18876
  };
17690
18877
  } else {
17691
- const routing = {
17692
- providerMode: doc.config.routing.providers.mode,
17693
- providers: doc.config.routing.providers.selected
17694
- };
17695
- const { data: all } = await ctx.call("listProviders", []);
17696
- const reachable = all.providers.filter(
17697
- (p) => p.status === "active" && (routing.providerMode === "all" || Object.hasOwn(routing.providers ?? {}, p.slug))
17698
- );
18878
+ const routing = doc.config.routing;
18879
+ const { data: all } = await ctx.call("listProviders");
18880
+ const reachable = reachableProviders(routing, all.providers);
17699
18881
  const requested = typeof flags.provider === "string" ? flags.provider : void 0;
17700
18882
  if (requested && !reachable.some((p) => p.slug === requested))
17701
18883
  fail(
@@ -17703,7 +18885,7 @@ ${snippet}`;
17703
18885
  all.providers.some((p) => p.slug === requested) ? "That provider is disabled or outside this app's proxy policy." : "No provider has that slug.",
17704
18886
  "Run agw provider list for the slugs, and agw app show <id> for the policy."
17705
18887
  );
17706
- const { data: catalog } = await ctx.call("listModelPrices", []);
18888
+ const { data: catalog } = await ctx.call("listModelPrices");
17707
18889
  const providers = requested ? reachable.filter((p) => p.slug === requested) : reachable;
17708
18890
  example = firstRequest(routing, providers, catalog.prices);
17709
18891
  if (!requested && reachable.length > 1)
@@ -17713,17 +18895,12 @@ ${snippet}`;
17713
18895
  const clientUrl = ctx.active?.deployment?.apiUrl ?? ctx.url;
17714
18896
  let snippet;
17715
18897
  if (ios) {
17716
- const endUser = doc.config.authentication.type === "apple_app_attest" ? doc.config.authentication.end_user : void 0;
17717
- const issuer = endUser?.source === "issuer";
17718
- if (issuer)
17719
- notes2.push(
17720
- "Replace yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh) with your configured issuer integration."
17721
- );
18898
+ if (swiftSignsInUsers(doc.config.authentication)) notes2.push(ISSUER_TOKEN_NOTE);
17722
18899
  snippet = swiftSnippet({
17723
18900
  baseUrl: clientUrl,
17724
18901
  appId,
17725
18902
  example,
17726
- authMode: issuer ? ".appAttest(issuerTokenProvider: { forceRefresh in\n // Return a fresh signed token from your configured identity SDK.\n try await yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh)\n })" : ".appAttestInstall",
18903
+ authentication: doc.config.authentication,
17727
18904
  notes: [
17728
18905
  "Swift package: https://github.com/maxceem/app-ai-gateway-swift (from: 1.0.0)",
17729
18906
  "Enable App Attest and test on a supported physical device.",
@@ -17769,6 +18946,7 @@ var RECORDED_RESULT = {
17769
18946
  createProviderGateway: ProviderGatewayResponseSchema
17770
18947
  };
17771
18948
  var recordedResultFor = RECORDED_RESULT;
18949
+ var pathFor = operationPath;
17772
18950
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
17773
18951
  var RECEIPT_BOUND_STATUS = /* @__PURE__ */ new Set([409, 410]);
17774
18952
  function definitiveRefusal(error51) {
@@ -17806,13 +18984,12 @@ var Context = class {
17806
18984
  * this is what keeps an undocumented server field — or anything an unexpected
17807
18985
  * response carries — out of the values the commands go on to print.
17808
18986
  */
17809
- async publicCall(name, params, { body, key, url: url2, headers } = {}) {
17810
- const descriptor = operations[name];
18987
+ async publicCall(name, { params, query, body, key, url: url2, headers } = {}) {
17811
18988
  const wire = await this.transport.request(
17812
18989
  url2 ?? this.url,
17813
- descriptor.path(...params),
18990
+ pathFor(name, params, query),
17814
18991
  {
17815
- method: descriptor.method,
18992
+ method: CATALOG[name].method,
17816
18993
  ...body === void 0 ? {} : { body },
17817
18994
  ...key === void 0 ? {} : { key },
17818
18995
  ...headers === void 0 ? {} : { headers }
@@ -17821,7 +18998,7 @@ var Context = class {
17821
18998
  return { data: this.parse(name, wire.data) };
17822
18999
  }
17823
19000
  parse(name, data) {
17824
- const parsed = responseSchemaFor[name].safeParse(data);
19001
+ const parsed = CATALOG[name].response.safeParse(data);
17825
19002
  if (!parsed.success)
17826
19003
  fail(
17827
19004
  "invalid_response",
@@ -17844,11 +19021,8 @@ var Context = class {
17844
19021
  return parsed.data;
17845
19022
  }
17846
19023
  /** The same call, with the connection's management credential attached. */
17847
- async call(name, params, options = {}) {
17848
- return this.publicCall(name, params, {
17849
- ...options,
17850
- key: options.key ?? this.credential(name)
17851
- });
19024
+ async call(name, options = {}) {
19025
+ return this.publicCall(name, { ...options, key: options.key ?? this.credential(name) });
17852
19026
  }
17853
19027
  /**
17854
19028
  * The current management credential, or the refusal that names the way back.
@@ -17931,7 +19105,7 @@ var Context = class {
17931
19105
  * The receipt in protected state holds the original server answer until the
17932
19106
  * command has printed it; `complete` is what releases it.
17933
19107
  */
17934
- async create(name, params, body) {
19108
+ async create(name, { params, body }) {
17935
19109
  if (!this.active?.credential)
17936
19110
  fail(
17937
19111
  "login_required",
@@ -17939,8 +19113,7 @@ var Context = class {
17939
19113
  "Run agw account login.",
17940
19114
  4
17941
19115
  );
17942
- const descriptor = operations[name];
17943
- const path = descriptor.path(...params);
19116
+ const path = pathFor(name, params);
17944
19117
  const mutation2 = await this.prepareCreate(path, body);
17945
19118
  if (mutation2.accountId === null) {
17946
19119
  mutation2.accountId = this.active.account?.id ?? null;
@@ -17979,7 +19152,7 @@ var Context = class {
17979
19152
  const keyRecord = recoveredKey(name, recovered);
17980
19153
  if (keyRecord) {
17981
19154
  const appId = recoveredAppId(name, recovered, params);
17982
- const { data: existing } = await this.call("listAppKeys", [appId]);
19155
+ const { data: existing } = await this.call("listAppKeys", { params: { app: appId } });
17983
19156
  if (!existing.keys.some(
17984
19157
  (key) => key.id === keyRecord.id && key.status === "active"
17985
19158
  )) {
@@ -17997,7 +19170,8 @@ var Context = class {
17997
19170
  }
17998
19171
  data = recovered;
17999
19172
  } else {
18000
- const response = await this.call(name, params, {
19173
+ const response = await this.call(name, {
19174
+ ...params === void 0 ? {} : { params },
18001
19175
  body,
18002
19176
  headers: {
18003
19177
  "Idempotency-Key": mutation2.id,
@@ -18082,11 +19256,10 @@ var Context = class {
18082
19256
  4
18083
19257
  );
18084
19258
  const { idempotencyKey, pollToken } = reservation;
18085
- const { data } = await this.publicCall(
18086
- "bootstrapCliAccount",
18087
- [],
18088
- { body: { idempotencyKey, pollToken }, url: CLOUD }
18089
- ).catch(async (error51) => {
19259
+ const { data } = await this.publicCall("bootstrapCliAccount", {
19260
+ body: { idempotencyKey, pollToken },
19261
+ url: CLOUD
19262
+ }).catch(async (error51) => {
18090
19263
  if (definitiveRefusal(error51)) {
18091
19264
  delete this.state.bootstrap;
18092
19265
  await this.save().catch(() => {
@@ -18097,7 +19270,7 @@ var Context = class {
18097
19270
  await this.select(CLOUD, data);
18098
19271
  this.onboarding = {
18099
19272
  account: data.account,
18100
- trial: data.trial,
19273
+ unclaimedAccess: data.unclaimedAccess,
18101
19274
  deployment: data.deployment
18102
19275
  };
18103
19276
  delete this.state.bootstrap;
@@ -18133,7 +19306,7 @@ var Context = class {
18133
19306
  const token = await secret(this.flags, "Management API key");
18134
19307
  if (!token)
18135
19308
  fail("input_required", "A management API key is required.", "Supply it with --key-stdin.");
18136
- const { data } = await this.publicCall("getCliAccount", [], { key: token, url: url2 });
19309
+ const { data } = await this.publicCall("getCliAccount", { key: token, url: url2 });
18137
19310
  await this.select(url2, { ...data, credential: { token } });
18138
19311
  return { connected: true, ...data };
18139
19312
  }
@@ -18161,12 +19334,14 @@ var Context = class {
18161
19334
  };
18162
19335
  await this.save();
18163
19336
  }
18164
- const operation3 = this.state.operations[localId];
18165
- const { data: capabilities } = await this.publicCall("getCliCapabilities", [], {
19337
+ const operation2 = this.state.operations[localId];
19338
+ const { data: capabilities } = await this.publicCall("getCliCapabilities", {
18166
19339
  url: target
18167
19340
  });
18168
- const { data } = await this.publicCall("createCliOperation", [], {
18169
- body: { kind, payload, pollToken: operation3.pollToken },
19341
+ const { data } = await this.publicCall("createCliOperation", {
19342
+ // One member of the per-kind union: `payload` was typed by `kind` above,
19343
+ // which is the correlation the compiler cannot follow through a generic.
19344
+ body: { kind, payload, pollToken: operation2.pollToken },
18170
19345
  url: target,
18171
19346
  ...authenticated ? { key: this.active?.credential } : {}
18172
19347
  });
@@ -18179,7 +19354,7 @@ var Context = class {
18179
19354
  "invalid_response",
18180
19355
  "The handoff URL is not on this deployment\u2019s trusted console origin."
18181
19356
  );
18182
- this.state.operations[data.id] = { ...operation3, phase: "pending" };
19357
+ this.state.operations[data.id] = { ...operation2, phase: "pending" };
18183
19358
  delete this.state.operations[localId];
18184
19359
  await this.save();
18185
19360
  if (!this.flags["no-open"]) await openBrowser(data.url);
@@ -18193,34 +19368,35 @@ Operation: ${data.id}
18193
19368
  return this.wait(data.id, 300);
18194
19369
  }
18195
19370
  async poll(id) {
18196
- const operation3 = this.state.operations[id];
18197
- if (!operation3)
19371
+ const operation2 = this.state.operations[id];
19372
+ if (!operation2)
18198
19373
  fail(
18199
19374
  "operation_unknown",
18200
19375
  "This operation has no locally stored polling authorization.",
18201
19376
  "Resume it from the machine that initiated the operation.",
18202
19377
  4
18203
19378
  );
18204
- if (this.active && this.active.url !== operation3.url)
19379
+ if (this.active && this.active.url !== operation2.url)
18205
19380
  fail(
18206
19381
  "operation_context",
18207
19382
  "This operation belongs to another selected deployment.",
18208
19383
  "Reconnect explicitly to the operation\u2019s originating deployment.",
18209
19384
  4
18210
19385
  );
18211
- const { data } = await this.publicCall("pollCliOperation", [id], {
18212
- key: operation3.pollToken,
18213
- url: operation3.url
19386
+ const { data } = await this.publicCall("pollCliOperation", {
19387
+ params: { id },
19388
+ key: operation2.pollToken,
19389
+ url: operation2.url
18214
19390
  });
18215
- if (data.state === "completed" && !operation3.completed && operation3.kind === "claim") {
18216
- if ((this.state.generation ?? 0) !== operation3.generation)
19391
+ if (data.state === "completed" && !operation2.completed && operation2.kind === "claim") {
19392
+ if ((this.state.generation ?? 0) !== operation2.generation)
18217
19393
  fail(
18218
19394
  "operation_context",
18219
19395
  "The active connection changed after this operation began.",
18220
19396
  "Start a new login for the intended connection.",
18221
19397
  4
18222
19398
  );
18223
- operation3.completed = true;
19399
+ operation2.completed = true;
18224
19400
  if (this.active && data.account) this.active.account = data.account;
18225
19401
  await this.save();
18226
19402
  }
@@ -18260,7 +19436,7 @@ function recoveredKey(name, data) {
18260
19436
  }
18261
19437
  function recoveredAppId(name, data, params) {
18262
19438
  if (name === "createApp") return data.app.id;
18263
- return String(params[0]);
19439
+ return String(params?.app);
18264
19440
  }
18265
19441
  async function openBrowser(url2) {
18266
19442
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
@@ -18281,14 +19457,9 @@ async function openBrowser(url2) {
18281
19457
  }
18282
19458
 
18283
19459
  // src/deployment.ts
18284
- import { spawn as spawn2 } from "node:child_process";
18285
- import { stripVTControlCharacters } from "node:util";
18286
- import { createRequire as createRequire2 } from "node:module";
18287
19460
  import { randomUUID } from "node:crypto";
18288
- import { cp, writeFile as writeFile2, unlink as unlink2 } from "node:fs/promises";
18289
- import { tmpdir } from "node:os";
18290
- import { join as join3 } from "node:path";
18291
- import { setTimeout as delay3 } from "node:timers/promises";
19461
+ import { writeFile as writeFile3, unlink as unlink2 } from "node:fs/promises";
19462
+ import { join as join4 } from "node:path";
18292
19463
 
18293
19464
  // src/release.ts
18294
19465
  import { readFile as readFile3, lstat as lstat2, mkdir as mkdir2, rename as rename2, rm, writeFile } from "node:fs/promises";
@@ -18530,7 +19701,12 @@ async function release(requested = VERSION, base = dirname(fileURLToPath(import.
18530
19701
  };
18531
19702
  }
18532
19703
 
18533
- // src/deployment.ts
19704
+ // src/cloudflare.ts
19705
+ import { spawn as spawn2 } from "node:child_process";
19706
+ import { stripVTControlCharacters } from "node:util";
19707
+ import { createRequire as createRequire2 } from "node:module";
19708
+ import { tmpdir } from "node:os";
19709
+ import { setTimeout as delay3 } from "node:timers/promises";
18534
19710
  var require2 = createRequire2(import.meta.url);
18535
19711
  var OUTPUT_TAIL = 5e3;
18536
19712
  function wranglerFailure(args, code, output, redact = []) {
@@ -18751,11 +19927,57 @@ async function resultOf(cf, path, options) {
18751
19927
  if (!data) fail("cloudflare_error", "Cloudflare returned no result.");
18752
19928
  return data.result;
18753
19929
  }
18754
- function hostname4(value) {
18755
- const u = origin("https://" + value);
18756
- if (new URL(u).hostname !== value || !value.includes(".") || value.includes(":"))
18757
- fail("invalid_input", "Supply a hostname without protocol, path or port.");
18758
- return value;
19930
+ function transient(error51) {
19931
+ if (!(error51 instanceof CliError)) return false;
19932
+ if (error51.code === "connection_failed") return true;
19933
+ const status2 = error51.details?.status;
19934
+ return typeof status2 === "number" && status2 >= 500;
19935
+ }
19936
+ async function whileWarming(run, sleep = delay3, report = (line) => process.stderr.write(line + "\n")) {
19937
+ let wait = 1e3;
19938
+ for (let attempt = 0; ; attempt++) {
19939
+ try {
19940
+ return await run();
19941
+ } catch (error51) {
19942
+ if (attempt === 5 || !transient(error51)) throw error51;
19943
+ const { code, details } = error51;
19944
+ const status2 = details?.status;
19945
+ report(
19946
+ `Deployment is still coming up (${code}${status2 === void 0 ? "" : ` ${status2}`}); retrying in ${wait / 1e3}s.`
19947
+ );
19948
+ await sleep(wait);
19949
+ wait = Math.min(wait * 2, 8e3);
19950
+ }
19951
+ }
19952
+ }
19953
+
19954
+ // src/installation.ts
19955
+ import { cp, writeFile as writeFile2 } from "node:fs/promises";
19956
+ import { join as join3 } from "node:path";
19957
+ import { setTimeout as delay4 } from "node:timers/promises";
19958
+ async function advance(ctx, journal, phase, patch = {}) {
19959
+ const fields = journal;
19960
+ for (const [field, value] of Object.entries(patch)) {
19961
+ if (value === void 0) delete fields[field];
19962
+ else fields[field] = value;
19963
+ }
19964
+ journal.phase = phase;
19965
+ await ctx.save();
19966
+ }
19967
+ async function prepare(ctx, installations, draft) {
19968
+ const journal = { ...draft, phase: "prepared" };
19969
+ installations[draft.id] = journal;
19970
+ await ctx.save();
19971
+ return journal;
19972
+ }
19973
+ function selectedInstallation(ctx) {
19974
+ if (ctx.active?.deployment?.mode !== "self_hosted")
19975
+ fail("not_self_hosted", "This command requires a selected self-hosted deployment.");
19976
+ return {
19977
+ url: ctx.url,
19978
+ deploymentId: ctx.active.deployment.id,
19979
+ installations: ctx.state.installations ??= {}
19980
+ };
18759
19981
  }
18760
19982
  async function configFile(ctx, journal, artifact) {
18761
19983
  const directory = join3(ctx.store.directory, "deployments", journal.id);
@@ -18792,33 +20014,10 @@ async function configFile(ctx, journal, artifact) {
18792
20014
  await writeFile2(path, JSON.stringify(config2, null, 2), { mode: 384 });
18793
20015
  return { path, directory, config: config2 };
18794
20016
  }
18795
- function transient(error51) {
18796
- if (!(error51 instanceof CliError)) return false;
18797
- if (error51.code === "connection_failed") return true;
18798
- const status2 = error51.details?.status;
18799
- return typeof status2 === "number" && status2 >= 500;
18800
- }
18801
- async function whileWarming(run, sleep = delay3, report = (line) => process.stderr.write(line + "\n")) {
18802
- let wait = 1e3;
18803
- for (let attempt = 0; ; attempt++) {
18804
- try {
18805
- return await run();
18806
- } catch (error51) {
18807
- if (attempt === 5 || !transient(error51)) throw error51;
18808
- const { code, details } = error51;
18809
- const status2 = details?.status;
18810
- report(
18811
- `Deployment is still coming up (${code}${status2 === void 0 ? "" : ` ${status2}`}); retrying in ${wait / 1e3}s.`
18812
- );
18813
- await sleep(wait);
18814
- wait = Math.min(wait * 2, 8e3);
18815
- }
18816
- }
18817
- }
18818
20017
  async function verifyDeployment(ctx, url2, id) {
18819
20018
  for (let attempt = 0; attempt < 20; attempt++) {
18820
20019
  try {
18821
- const { data } = await ctx.publicCall("getCliCapabilities", [], { url: url2 });
20020
+ const { data } = await ctx.publicCall("getCliCapabilities", { url: url2 });
18822
20021
  if (data.deployment.id !== id)
18823
20022
  fail(
18824
20023
  "deployment_identity_mismatch",
@@ -18828,7 +20027,7 @@ async function verifyDeployment(ctx, url2, id) {
18828
20027
  } catch (error51) {
18829
20028
  if (error51 instanceof CliError && error51.code === "deployment_identity_mismatch") throw error51;
18830
20029
  if (attempt === 19) throw error51;
18831
- await delay3(1500);
20030
+ await delay4(1500);
18832
20031
  }
18833
20032
  }
18834
20033
  fail("deployment_identity_mismatch", "The endpoint never identified the expected deployment.");
@@ -18852,10 +20051,10 @@ async function checkDomain(cf, journal, domain2) {
18852
20051
  if (current && current.service !== journal.name)
18853
20052
  fail("domain_in_use", "The hostname is attached to a different Worker.");
18854
20053
  if (!current) {
18855
- const records2 = await cf.all(
20054
+ const records = await cf.all(
18856
20055
  "/zones/" + zone.id + "/dns_records?name=" + encodeURIComponent(domain2)
18857
20056
  );
18858
- if (records2.length)
20057
+ if (records.length)
18859
20058
  fail("domain_in_use", "The hostname has existing DNS records; no records were changed.");
18860
20059
  }
18861
20060
  return zone;
@@ -18885,9 +20084,10 @@ async function attachDomain(ctx, cf, journal, domain2, artifact, dryRun) {
18885
20084
  zone_id: zone.id
18886
20085
  }
18887
20086
  });
18888
- journal.domains = [.../* @__PURE__ */ new Set([...journal.domains ?? [], domain2])];
18889
- journal.vars = { ...journal.vars, CLI_CONSOLE_ORIGIN: "https://" + domain2 };
18890
- await ctx.save();
20087
+ await advance(ctx, journal, journal.phase, {
20088
+ domains: [.../* @__PURE__ */ new Set([...journal.domains ?? [], domain2])],
20089
+ vars: { ...journal.vars, CLI_CONSOLE_ORIGIN: "https://" + domain2 }
20090
+ });
18891
20091
  const file2 = await configFile(ctx, journal, artifact);
18892
20092
  await cf.run(["deploy", "--config", file2.path, "--keep-vars"], {
18893
20093
  cwd: file2.directory
@@ -18954,14 +20154,11 @@ async function retireInstallationSecrets(ctx, journal, flags) {
18954
20154
  if (flags["dry-run"] || journal.phase !== "ready" || !journal.secrets) return;
18955
20155
  const kek = journal.secrets["SECRET_VAULT_LOCAL_KEK_V1"];
18956
20156
  if (kek) await ctx.store.vaultKey(journal.id, kek);
18957
- delete journal.secrets;
18958
- await ctx.save();
20157
+ await advance(ctx, journal, journal.phase, { secrets: void 0 });
18959
20158
  }
18960
- async function matchExisting(ctx, cf, flags) {
18961
- if (ctx.active?.deployment?.mode !== "self_hosted")
18962
- fail("not_self_hosted", "This command requires a selected self-hosted deployment.");
18963
- const installations = ctx.state.installations ??= {};
18964
- let journal = installations[ctx.active.deployment.id];
20159
+ async function matchExisting(ctx, cf, flags, target) {
20160
+ const { installations } = target;
20161
+ let journal = installations[target.deploymentId];
18965
20162
  if (journal) {
18966
20163
  const settings2 = await resultOf(
18967
20164
  cf,
@@ -18972,7 +20169,7 @@ async function matchExisting(ctx, cf, flags) {
18972
20169
  "deployment_identity_mismatch",
18973
20170
  "The Cloudflare Worker no longer matches this installation journal."
18974
20171
  );
18975
- const capabilities2 = await verifyDeployment(ctx, ctx.url, journal.id);
20172
+ const capabilities2 = await verifyDeployment(ctx, target.url, journal.id);
18976
20173
  const inventory2 = await currentInventory(
18977
20174
  cf,
18978
20175
  journal.accountId,
@@ -19002,7 +20199,7 @@ async function matchExisting(ctx, cf, flags) {
19002
20199
  const deploymentId = settings.bindings?.find(
19003
20200
  (b) => b.name === "DEPLOYMENT_ID" && b.type === "plain_text"
19004
20201
  )?.text;
19005
- if (deploymentId !== ctx.active.deployment.id)
20202
+ if (deploymentId !== target.deploymentId)
19006
20203
  fail(
19007
20204
  "deployment_identity_mismatch",
19008
20205
  "Cloudflare Worker identity does not match the selected deployment."
@@ -19014,7 +20211,9 @@ async function matchExisting(ctx, cf, flags) {
19014
20211
  `Bind deployment ${deploymentId} to Cloudflare account ${accountId}, Worker ${name} and D1 ${db.id}?`,
19015
20212
  flags
19016
20213
  );
19017
- const { data: capabilities } = await ctx.publicCall("getCliCapabilities", []);
20214
+ const { data: capabilities } = await ctx.publicCall("getCliCapabilities", {
20215
+ url: target.url
20216
+ });
19018
20217
  const inventory = await currentInventory(cf, accountId, name, settings);
19019
20218
  journal = {
19020
20219
  id: deploymentId,
@@ -19031,52 +20230,170 @@ async function matchExisting(ctx, cf, flags) {
19031
20230
  }
19032
20231
  return journal;
19033
20232
  }
19034
- async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loadRelease = release) {
20233
+
20234
+ // src/deployment.ts
20235
+ function hostname4(value) {
20236
+ const u = origin("https://" + value);
20237
+ if (new URL(u).hostname !== value || !value.includes(".") || value.includes(":"))
20238
+ fail("invalid_input", "Supply a hostname without protocol, path or port.");
20239
+ return value;
20240
+ }
20241
+ async function authorized(flags, cf, loadRelease) {
19035
20242
  const artifact = await loadRelease(flags.version, void 0, {
19036
20243
  archive: flags["release-archive"]
19037
20244
  });
19038
20245
  await cf.authenticate(flags);
19039
- if (command !== "deployment setup") {
19040
- const journal2 = await matchExisting(ctx, cf, flags);
19041
- if (command === "deployment domain") {
19042
- const domain3 = hostname4(flags.hostname ?? await prompt("Custom hostname", flags));
19043
- if (!flags["dry-run"])
19044
- await confirm(
19045
- `Attach ${domain3} to ${journal2.name} in Cloudflare account ${journal2.accountId}?`,
19046
- flags
19047
- );
19048
- return attachDomain(ctx, cf, journal2, domain3, artifact, Boolean(flags["dry-run"]));
19049
- }
19050
- if (!artifact.manifest.upgradeFrom.includes(journal2.version))
19051
- fail(
19052
- "unsupported_upgrade",
19053
- "This release cannot upgrade the selected database schema.",
19054
- "Use a supported migration release; no data was changed."
19055
- );
19056
- const plan2 = {
19057
- worker: journal2.name,
19058
- accountId: journal2.accountId,
19059
- databaseId: journal2.databaseId,
19060
- fromVersion: journal2.version,
19061
- toVersion: artifact.manifest.version
19062
- };
19063
- if (flags["dry-run"]) return { dryRun: true, ...plan2 };
20246
+ return artifact;
20247
+ }
20248
+ async function deploymentDomain(ctx, flags, cf = new Cloudflare(), loadRelease = release) {
20249
+ const artifact = await authorized(flags, cf, loadRelease);
20250
+ const journal = await matchExisting(ctx, cf, flags, selectedInstallation(ctx));
20251
+ const domain2 = hostname4(flags.hostname ?? await prompt("Custom hostname", flags));
20252
+ if (!flags["dry-run"])
19064
20253
  await confirm(
19065
- `Update ${journal2.name} to ${artifact.manifest.version}? Database migrations cannot be rolled back automatically.`,
20254
+ `Attach ${domain2} to ${journal.name} in Cloudflare account ${journal.accountId}?`,
19066
20255
  flags
19067
20256
  );
19068
- const file2 = await configFile(ctx, journal2, artifact);
19069
- await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
19070
- cwd: file2.directory
20257
+ return attachDomain(ctx, cf, journal, domain2, artifact, Boolean(flags["dry-run"]));
20258
+ }
20259
+ async function deploymentUpdate(ctx, flags, cf = new Cloudflare(), loadRelease = release) {
20260
+ const artifact = await authorized(flags, cf, loadRelease);
20261
+ const journal = await matchExisting(ctx, cf, flags, selectedInstallation(ctx));
20262
+ if (!artifact.manifest.upgradeFrom.includes(journal.version))
20263
+ fail(
20264
+ "unsupported_upgrade",
20265
+ "This release cannot upgrade the selected database schema.",
20266
+ "Use a supported migration release; no data was changed."
20267
+ );
20268
+ const plan = {
20269
+ worker: journal.name,
20270
+ accountId: journal.accountId,
20271
+ databaseId: journal.databaseId,
20272
+ fromVersion: journal.version,
20273
+ toVersion: artifact.manifest.version
20274
+ };
20275
+ if (flags["dry-run"]) return { dryRun: true, ...plan };
20276
+ await confirm(
20277
+ `Update ${journal.name} to ${artifact.manifest.version}? Database migrations cannot be rolled back automatically.`,
20278
+ flags
20279
+ );
20280
+ const file2 = await configFile(ctx, journal, artifact);
20281
+ await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
20282
+ cwd: file2.directory
20283
+ });
20284
+ await cf.run(["deploy", "--config", file2.path, "--keep-vars"], {
20285
+ cwd: file2.directory
20286
+ });
20287
+ await verifyDeployment(ctx, ctx.url, journal.id);
20288
+ await advance(ctx, journal, journal.phase, { version: artifact.manifest.version });
20289
+ return { updated: true, ...plan };
20290
+ }
20291
+ function newInstallation(name, accountId, url2, version2) {
20292
+ const id = randomUUID();
20293
+ return {
20294
+ id,
20295
+ name,
20296
+ accountId,
20297
+ databaseName: name + "-" + id.slice(0, 8),
20298
+ version: version2,
20299
+ url: url2,
20300
+ vars: { CLI_CONSOLE_ORIGIN: url2 },
20301
+ bootstrap: { idempotencyKey: randomToken(), pollToken: randomToken() },
20302
+ // Held only until the Worker has them and the install is ready; the vault
20303
+ // key is the one that outlives setup, and it lives in its own file.
20304
+ secrets: {
20305
+ JWT_SECRET: randomToken(),
20306
+ BETTER_AUTH_SECRET: randomToken()
20307
+ }
20308
+ };
20309
+ }
20310
+ async function completeInstallation(ctx, cf, flags, ready, artifact, installations) {
20311
+ await retireInstallationSecrets(ctx, ready, flags);
20312
+ let journal = ready;
20313
+ if (journal.pendingDomain) {
20314
+ journal = await matchExisting(ctx, cf, flags, {
20315
+ url: journal.url ?? ctx.url,
20316
+ deploymentId: journal.id,
20317
+ installations
20318
+ });
20319
+ if (flags["dry-run"])
20320
+ return {
20321
+ installed: true,
20322
+ pendingDomain: journal.pendingDomain,
20323
+ dryRun: true
20324
+ };
20325
+ await attachDomain(ctx, cf, journal, journal.pendingDomain, artifact, false);
20326
+ await advance(ctx, journal, journal.phase, { pendingDomain: void 0 });
20327
+ }
20328
+ await verifyDeployment(ctx, journal.url ?? ctx.url, journal.id);
20329
+ return {
20330
+ installed: true,
20331
+ deploymentId: journal.id,
20332
+ name: journal.name,
20333
+ accountId: journal.accountId,
20334
+ url: journal.url ?? ctx.url,
20335
+ note: "This installation is already complete. Use deployment connect to authenticate or deployment update to change its release."
20336
+ };
20337
+ }
20338
+ async function install(ctx, cf, journal, artifact, url2, domain2) {
20339
+ if (!journal.databaseId) {
20340
+ const databases = await cf.all(
20341
+ "/accounts/" + journal.accountId + "/d1/database"
20342
+ );
20343
+ let database = databases.find((d) => d.name === journal.databaseName);
20344
+ if (!database)
20345
+ database = await resultOf(
20346
+ cf,
20347
+ "/accounts/" + journal.accountId + "/d1/database",
20348
+ {
20349
+ method: "POST",
20350
+ body: { name: journal.databaseName }
20351
+ }
20352
+ );
20353
+ await advance(ctx, journal, journal.phase, { databaseId: database.uuid });
20354
+ }
20355
+ const file2 = await configFile(ctx, journal, artifact);
20356
+ await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
20357
+ cwd: file2.directory
20358
+ });
20359
+ const secrets = {
20360
+ ...journal.secrets,
20361
+ SECRET_VAULT_LOCAL_KEK_V1: await ctx.store.vaultKey(journal.id)
20362
+ };
20363
+ const secretPath = join4(file2.directory, "secrets.json");
20364
+ await writeFile3(secretPath, JSON.stringify(secrets), {
20365
+ mode: 384
20366
+ });
20367
+ try {
20368
+ await cf.run(["deploy", "--config", file2.path, "--secrets-file", secretPath], {
20369
+ cwd: file2.directory,
20370
+ redact: Object.values(secrets)
19071
20371
  });
19072
- await cf.run(["deploy", "--config", file2.path, "--keep-vars"], {
19073
- cwd: file2.directory
20372
+ } finally {
20373
+ await unlink2(secretPath).catch(() => {
19074
20374
  });
19075
- await verifyDeployment(ctx, ctx.url, journal2.id);
19076
- journal2.version = artifact.manifest.version;
19077
- await ctx.save();
19078
- return { updated: true, ...plan2 };
19079
20375
  }
20376
+ await advance(ctx, journal, "deployed");
20377
+ await verifyDeployment(ctx, url2, journal.id);
20378
+ const proofs = {
20379
+ idempotencyKey: journal.bootstrap.idempotencyKey,
20380
+ pollToken: journal.bootstrap.pollToken
20381
+ };
20382
+ const { data } = await whileWarming(
20383
+ () => ctx.publicCall("bootstrapCliAccount", { body: proofs, url: url2 })
20384
+ );
20385
+ await ctx.select(url2, data);
20386
+ await advance(ctx, journal, "ready", {
20387
+ ...domain2 ? { pendingDomain: domain2 } : {},
20388
+ secrets: void 0
20389
+ });
20390
+ if (domain2) {
20391
+ await attachDomain(ctx, cf, journal, domain2, artifact, false);
20392
+ await advance(ctx, journal, journal.phase, { pendingDomain: void 0 });
20393
+ }
20394
+ }
20395
+ async function deploymentSetup(ctx, flags, cf = new Cloudflare(), loadRelease = release) {
20396
+ const artifact = await authorized(flags, cf, loadRelease);
19080
20397
  const name = flags.name ?? await prompt("Worker name", flags, { defaultValue: "app-ai-gateway" });
19081
20398
  if (!/^[a-z][a-z0-9-]{0,62}$/.test(name))
19082
20399
  fail("invalid_input", "Worker name must be a lowercase slug of at most 63 characters.");
@@ -19084,58 +20401,29 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
19084
20401
  const accountId = await cf.account(flags);
19085
20402
  const workers = await cf.all("/accounts/" + accountId + "/workers/scripts");
19086
20403
  const installations = ctx.state.installations ??= {};
19087
- let journal = Object.values(installations).find(
20404
+ const recorded = Object.values(installations).find(
19088
20405
  (j) => j.accountId === accountId && j.name === name
19089
20406
  );
19090
20407
  const exists = workers.some((w) => w.id === name);
19091
- if (exists && !journal)
20408
+ if (exists && !recorded)
19092
20409
  fail(
19093
20410
  "worker_name_collision",
19094
20411
  "A Worker already uses this name.",
19095
20412
  "Choose a different --name; setup never takes over an existing installation."
19096
20413
  );
19097
- if (exists && journal) {
20414
+ if (exists && recorded) {
19098
20415
  const settings = await resultOf(
19099
20416
  cf,
19100
20417
  `/accounts/${accountId}/workers/scripts/${name}/settings`
19101
20418
  );
19102
- if (settings.bindings?.find((b) => b.name === "DEPLOYMENT_ID")?.text !== journal.id)
20419
+ if (settings.bindings?.find((b) => b.name === "DEPLOYMENT_ID")?.text !== recorded.id)
19103
20420
  fail(
19104
20421
  "deployment_identity_mismatch",
19105
20422
  "Existing Worker does not match the saved installation journal."
19106
20423
  );
19107
20424
  }
19108
- if (journal?.phase === "ready") {
19109
- await retireInstallationSecrets(ctx, journal, flags);
19110
- if (journal.pendingDomain) {
19111
- const inventoryContext = Object.create(ctx);
19112
- Object.defineProperty(inventoryContext, "active", {
19113
- value: { deployment: { id: journal.id, mode: "self_hosted" } }
19114
- });
19115
- Object.defineProperty(inventoryContext, "url", {
19116
- value: journal.url ?? ctx.url
19117
- });
19118
- journal = await matchExisting(inventoryContext, cf, flags);
19119
- if (flags["dry-run"])
19120
- return {
19121
- installed: true,
19122
- pendingDomain: journal.pendingDomain,
19123
- dryRun: true
19124
- };
19125
- await attachDomain(ctx, cf, journal, journal.pendingDomain, artifact, false);
19126
- delete journal.pendingDomain;
19127
- await ctx.save();
19128
- }
19129
- await verifyDeployment(ctx, journal.url ?? ctx.url, journal.id);
19130
- return {
19131
- installed: true,
19132
- deploymentId: journal.id,
19133
- name,
19134
- accountId,
19135
- url: journal.url ?? ctx.url,
19136
- note: "This installation is already complete. Use deployment connect to authenticate or deployment update to change its release."
19137
- };
19138
- }
20425
+ if (recorded?.phase === "ready")
20426
+ return completeInstallation(ctx, cf, flags, recorded, artifact, installations);
19139
20427
  const subdomain = (await resultOf(cf, "/accounts/" + accountId + "/workers/subdomain")).subdomain;
19140
20428
  if (!subdomain)
19141
20429
  fail(
@@ -19157,81 +20445,13 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
19157
20445
  `Install ${name} in Cloudflare account ${accountId} at ${url2}${domain2 ? " with " + domain2 : ""}?`,
19158
20446
  flags
19159
20447
  );
19160
- if (!journal) {
19161
- const id = randomUUID();
19162
- journal = {
19163
- id,
19164
- name,
19165
- accountId,
19166
- databaseName: name + "-" + id.slice(0, 8),
19167
- version: artifact.manifest.version,
19168
- phase: "prepared",
19169
- url: url2,
19170
- vars: { CLI_CONSOLE_ORIGIN: url2 },
19171
- bootstrap: { idempotencyKey: randomToken(), pollToken: randomToken() },
19172
- // Held only until the Worker has them and the install is ready; the vault
19173
- // key is the one that outlives setup, and it lives in its own file.
19174
- secrets: {
19175
- JWT_SECRET: randomToken(),
19176
- BETTER_AUTH_SECRET: randomToken()
19177
- }
19178
- };
19179
- installations[id] = journal;
19180
- await ctx.save();
19181
- }
20448
+ const journal = recorded ?? await prepare(
20449
+ ctx,
20450
+ installations,
20451
+ newInstallation(name, accountId, url2, artifact.manifest.version)
20452
+ );
19182
20453
  try {
19183
- if (!journal.databaseId) {
19184
- const databases = await cf.all("/accounts/" + accountId + "/d1/database");
19185
- let database = databases.find((d) => d.name === journal.databaseName);
19186
- if (!database)
19187
- database = await resultOf(cf, "/accounts/" + accountId + "/d1/database", {
19188
- method: "POST",
19189
- body: { name: journal.databaseName }
19190
- });
19191
- journal.databaseId = database.uuid;
19192
- await ctx.save();
19193
- }
19194
- const file2 = await configFile(ctx, journal, artifact);
19195
- await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
19196
- cwd: file2.directory
19197
- });
19198
- const secrets = {
19199
- ...journal.secrets,
19200
- SECRET_VAULT_LOCAL_KEK_V1: await ctx.store.vaultKey(journal.id)
19201
- };
19202
- const secretPath = join3(file2.directory, "secrets.json");
19203
- await writeFile2(secretPath, JSON.stringify(secrets), {
19204
- mode: 384
19205
- });
19206
- try {
19207
- await cf.run(["deploy", "--config", file2.path, "--secrets-file", secretPath], {
19208
- cwd: file2.directory,
19209
- redact: Object.values(secrets)
19210
- });
19211
- } finally {
19212
- await unlink2(secretPath).catch(() => {
19213
- });
19214
- }
19215
- journal.phase = "deployed";
19216
- await ctx.save();
19217
- await verifyDeployment(ctx, url2, journal.id);
19218
- const proofs = {
19219
- idempotencyKey: journal.bootstrap.idempotencyKey,
19220
- pollToken: journal.bootstrap.pollToken
19221
- };
19222
- const { data } = await whileWarming(
19223
- () => ctx.publicCall("bootstrapCliAccount", [], { body: proofs, url: url2 })
19224
- );
19225
- await ctx.select(url2, data);
19226
- journal.phase = "ready";
19227
- if (domain2) journal.pendingDomain = domain2;
19228
- delete journal.secrets;
19229
- await ctx.save();
19230
- if (domain2) {
19231
- await attachDomain(ctx, cf, journal, domain2, artifact, false);
19232
- delete journal.pendingDomain;
19233
- await ctx.save();
19234
- }
20454
+ await install(ctx, cf, journal, artifact, url2, domain2);
19235
20455
  return {
19236
20456
  installed: true,
19237
20457
  ...plan,
@@ -19250,6 +20470,12 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
19250
20470
  throw error51;
19251
20471
  }
19252
20472
  }
20473
+ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loadRelease = release) {
20474
+ if (command === "deployment setup") return deploymentSetup(ctx, flags, cf, loadRelease);
20475
+ if (command === "deployment domain") return deploymentDomain(ctx, flags, cf, loadRelease);
20476
+ if (command === "deployment update") return deploymentUpdate(ctx, flags, cf, loadRelease);
20477
+ fail("unknown_command", "Unknown command.");
20478
+ }
19253
20479
 
19254
20480
  // src/style.ts
19255
20481
  import { Writable } from "node:stream";
@@ -19339,8 +20565,8 @@ function humanResult(command, result, context, style2 = plain) {
19339
20565
  return style2.code(result.snippet, language).join("\n") + "\n";
19340
20566
  }
19341
20567
  const lines = render(command, result, context, style2);
19342
- if ("trial" in result && result.trial)
19343
- lines.push(`Free access ends: ${result.trial.endsAt}`);
20568
+ if ("unclaimedAccess" in result && result.unclaimedAccess)
20569
+ lines.push(`Free access ends: ${result.unclaimedAccess.endsAt}`);
19344
20570
  if ("account" in result && result.account?.expiresAt)
19345
20571
  lines.push(`Recovery deadline: ${result.account.expiresAt}`);
19346
20572
  return lines.join("\n") + "\n";
@@ -19388,7 +20614,7 @@ function render(command, result, context, style2) {
19388
20614
  return usage(command, result, style2);
19389
20615
  case "operation status":
19390
20616
  case "operation wait":
19391
- return operation2(result, style2);
20617
+ return operation(result, style2);
19392
20618
  case "app add":
19393
20619
  case "app update":
19394
20620
  case "app validate":
@@ -19434,7 +20660,7 @@ function status(command, result, context, style2) {
19434
20660
  style2
19435
20661
  )
19436
20662
  ];
19437
- if ("state" in result) return [...did, ...operation2(result, style2)];
20663
+ if ("state" in result) return [...did, ...operation(result, style2)];
19438
20664
  return [...did, style2.json(result)];
19439
20665
  }
19440
20666
  function list(command, result, style2) {
@@ -19670,7 +20896,7 @@ function usage(command, result, style2) {
19670
20896
  );
19671
20897
  return [style2.json(result)];
19672
20898
  }
19673
- function operation2(result, style2) {
20899
+ function operation(result, style2) {
19674
20900
  if (!("state" in result)) return [style2.json(result)];
19675
20901
  const stored = "result" in result ? result.result : void 0;
19676
20902
  return kv(
@@ -19716,7 +20942,9 @@ function app(command, result, context, style2) {
19716
20942
  ["Gateway", context.url],
19717
20943
  ["Status", result.definition.status],
19718
20944
  ["Authentication", result.definition.config.authentication.type],
19719
- ["App ID", result.validation.remote ? result.validation.app_id : void 0]
20945
+ // Only an edit of an application that exists has an id to show; a
20946
+ // new one is judged as a draft and has none yet.
20947
+ ["App ID", result.validation.remote && "app_id" in result.validation ? result.validation.app_id : void 0]
19720
20948
  ],
19721
20949
  style2
19722
20950
  ),
@@ -19942,8 +21170,9 @@ var Transport = class {
19942
21170
  };
19943
21171
 
19944
21172
  // src/usage.ts
21173
+ var CLI_BREAKDOWNS = ["provider", "model", "status"];
19945
21174
  function month(value) {
19946
- if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(value))
21175
+ if (!MONTH_PATTERN.test(value))
19947
21176
  fail(
19948
21177
  "invalid_input",
19949
21178
  "Month must use YYYY-MM with a valid calendar month."
@@ -19966,11 +21195,11 @@ function positive(value, max = 3600) {
19966
21195
  async function usageCommand(ctx, command, flags) {
19967
21196
  if (command === "usage show") {
19968
21197
  const m = month(flags.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7));
19969
- return flags.app ? (await ctx.call("getAppUsage", [flags.app, { month: m }])).data : (await ctx.call("getCliUsage", [{ month: m }])).data;
21198
+ return flags.app ? (await ctx.call("getAppUsage", { params: { app: flags.app }, query: { month: m } })).data : (await ctx.call("getCliUsage", { query: { month: m } })).data;
19970
21199
  }
19971
21200
  const app2 = await required2(flags, "app");
19972
- const by = flags.by ?? "model";
19973
- if (!["provider", "model", "status"].includes(by))
21201
+ const by = CLI_BREAKDOWNS.find((dimension) => dimension === (flags.by ?? "model"));
21202
+ if (by === void 0)
19974
21203
  fail("invalid_input", "--by must be provider, model or status.");
19975
21204
  if (Boolean(flags.from) !== Boolean(flags.to))
19976
21205
  fail("invalid_input", "Supply both --from and --to or neither.");
@@ -19980,7 +21209,10 @@ async function usageCommand(ctx, command, flags) {
19980
21209
  if (from > to) fail("invalid_input", "--from must not be later than --to.");
19981
21210
  const limit = positive(flags.limit ?? 50, 200);
19982
21211
  return {
19983
- ...(await ctx.call("getAppUsageBreakdown", [app2, { from, to, by, limit }])).data,
21212
+ ...(await ctx.call("getAppUsageBreakdown", {
21213
+ params: { app: app2 },
21214
+ query: { from, to, by, limit }
21215
+ })).data,
19984
21216
  coverage: {
19985
21217
  from,
19986
21218
  to,
@@ -19998,7 +21230,7 @@ async function execute(parsed, ctx) {
19998
21230
  if (command.startsWith("usage ")) return usageCommand(ctx, command, flags);
19999
21231
  if (command.startsWith("operation "))
20000
21232
  return command === "operation wait" ? ctx.wait(args[0] ?? "", positive(flags.timeout ?? 300)) : ctx.poll(args[0] ?? "");
20001
- if (command === "account status") return (await ctx.call("getCliAccount", [])).data;
21233
+ if (command === "account status") return (await ctx.call("getCliAccount")).data;
20002
21234
  if (command === "account login") return ctx.login();
20003
21235
  if (command === "account claim") return ctx.operation("claim", {});
20004
21236
  if (command === "account logout") {
@@ -20021,7 +21253,7 @@ async function execute(parsed, ctx) {
20021
21253
  return ctx.login(url2);
20022
21254
  }
20023
21255
  if (command === "deployment status") {
20024
- const { data } = await ctx.publicCall("getCliCapabilities", []);
21256
+ const { data } = await ctx.publicCall("getCliCapabilities");
20025
21257
  return {
20026
21258
  url: ctx.url,
20027
21259
  authenticated: Boolean(ctx.active?.credential),