@maxceem/agw 0.1.13 → 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 +2275 -811
  2. package/package.json +4 -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.1.13", wrangler: "4.96.0", upgradeFrom: ["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.1.13/gateway-0.1.13.tar.gz", sha256: "52bc83d5f54f79cd8bb1b868333648061dd444b41c34145e49698ad81bb794ac", manifest: "de2e8d1f4a9928be46b87571b7fcff33dc1d37c5bb008ba3261e97101f21781b" } };
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("");
@@ -14526,24 +14526,6 @@ function date4(params) {
14526
14526
  config(en_default());
14527
14527
 
14528
14528
  // ../src/shared/capabilities.ts
14529
- var PROVIDER_TYPES = [
14530
- "openai",
14531
- "anthropic",
14532
- "xai",
14533
- "gemini",
14534
- "perplexity",
14535
- "deepseek",
14536
- "groq",
14537
- "mistral",
14538
- "together",
14539
- "fireworks",
14540
- "cerebras",
14541
- "moonshot",
14542
- "huggingface",
14543
- "baseten",
14544
- "bytedance",
14545
- "openrouter"
14546
- ];
14547
14529
  var API_STYLES = [
14548
14530
  "responses",
14549
14531
  "chat_completions",
@@ -14553,30 +14535,21 @@ var API_STYLES = [
14553
14535
  /** A provider-native operation with no cross-provider contract of its own. */
14554
14536
  "other"
14555
14537
  ];
14556
- var PASSTHROUGH_CAPABILITY = {
14557
- apiStyles: API_STYLES,
14558
- endpointStyles: []
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"
14559
14544
  };
14560
- var PROVIDER_CAPABILITY_EXCEPTIONS = {
14561
- // The two types whose Responses and transcription request shapes the gateway
14562
- // composes itself for named endpoints.
14563
- openai: { apiStyles: API_STYLES, endpointStyles: ["responses", "transcription"] },
14564
- xai: { apiStyles: API_STYLES, endpointStyles: ["responses", "transcription"] },
14565
- // The one narrowed API surface, and it is a metering constraint rather than a
14566
- // missing one: OpenRouter also serves `/responses` and `/messages`, but only
14567
- // its chat-completions response carries `usage.cost`, and its slugs have no
14568
- // local price. Any other style would proxy traffic nothing could bill —
14569
- // exactly the silent $0 the fail-closed gate exists to prevent — so it is
14570
- // refused at the edge instead.
14571
- openrouter: { apiStyles: ["chat_completions"], endpointStyles: [] }
14572
- };
14573
- function providerCapability(provider) {
14574
- const exceptions = PROVIDER_CAPABILITY_EXCEPTIONS;
14575
- return exceptions[provider] ?? PASSTHROUGH_CAPABILITY;
14576
- }
14577
- var ENDPOINT_PROVIDER_TYPES = PROVIDER_TYPES.filter(
14578
- (type) => providerCapability(type).endpointStyles.length > 0
14579
- );
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
+ ];
14580
14553
  var VERCEL_API_STYLES = [
14581
14554
  "responses",
14582
14555
  "chat_completions",
@@ -14604,8 +14577,296 @@ var VERCEL_ROUTES = {
14604
14577
  moonshot: vercelRoute("moonshotai/")
14605
14578
  };
14606
14579
 
14607
- // ../src/core/providers.ts
14608
- var PROVIDER_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
14580
+ // ../src/shared/records.ts
14581
+ function asRecord(value) {
14582
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
14583
+ }
14584
+
14585
+ // ../src/shared/cost-report.ts
14586
+ function finiteNumber(value) {
14587
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
14588
+ }
14589
+ function nonEmptyString(value) {
14590
+ return typeof value === "string" && value.length > 0 ? value : null;
14591
+ }
14592
+ function servedProviderOf(root) {
14593
+ const available = asRecord(asRecord(root.openrouter_metadata)?.endpoints)?.available;
14594
+ if (Array.isArray(available)) {
14595
+ for (const entry of available) {
14596
+ const endpoint = asRecord(entry);
14597
+ if (endpoint?.selected === true) {
14598
+ const named = nonEmptyString(endpoint.provider);
14599
+ if (named) return named;
14600
+ }
14601
+ }
14602
+ }
14603
+ return nonEmptyString(root.provider);
14604
+ }
14605
+ function upstreamInferenceCost(usage2) {
14606
+ const upstream = finiteNumber(asRecord(usage2?.cost_details)?.upstream_inference_cost);
14607
+ return upstream !== null && upstream > 0 ? upstream : null;
14608
+ }
14609
+ function reportedByok(root, usage2) {
14610
+ if (usage2?.is_byok === true) return true;
14611
+ if (asRecord(root.openrouter_metadata)?.is_byok === true) return true;
14612
+ return upstreamInferenceCost(usage2) !== null;
14613
+ }
14614
+ var OPENROUTER_COST_REPORT = {
14615
+ // Which host actually served a request is opt-in per request; without this
14616
+ // header the response names no serving provider at all.
14617
+ requestHeaders: { "x-openrouter-metadata": "enabled" },
14618
+ read(root, report) {
14619
+ let said = false;
14620
+ const usage2 = asRecord(root.usage);
14621
+ if (report.costUsd === null) {
14622
+ const cost = usage2 ? finiteNumber(usage2.cost) : null;
14623
+ if (cost !== null && cost >= 0) {
14624
+ report.costUsd = cost + (upstreamInferenceCost(usage2) ?? 0);
14625
+ said = true;
14626
+ }
14627
+ }
14628
+ if (report.servedProvider === null) {
14629
+ const served = servedProviderOf(root);
14630
+ if (served !== null) {
14631
+ report.servedProvider = served;
14632
+ said = true;
14633
+ }
14634
+ }
14635
+ if (report.servedModel === null) {
14636
+ const model = nonEmptyString(root.model);
14637
+ if (model !== null) {
14638
+ report.servedModel = model;
14639
+ said = true;
14640
+ }
14641
+ }
14642
+ if (report.credentialSource === null && reportedByok(root, usage2)) {
14643
+ report.credentialSource = "byok";
14644
+ said = true;
14645
+ }
14646
+ return said;
14647
+ }
14648
+ };
14649
+
14650
+ // ../src/shared/providers.ts
14651
+ var PROVIDER_DESCRIPTORS = {
14652
+ openai: {
14653
+ directBaseUrl: "https://api.openai.com/",
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" },
14665
+ modelAuthor: "OpenAI"
14666
+ },
14667
+ anthropic: {
14668
+ directBaseUrl: "https://api.anthropic.com/",
14669
+ auth: { header: "x-api-key" },
14670
+ nativeClampStyle: "anthropic",
14671
+ probePath: "v1/models",
14672
+ examplePath: "v1/messages",
14673
+ modelAuthor: "Anthropic",
14674
+ // Anthropic refuses any request without a version header, probe included —
14675
+ // and a probe has no client to send one for it. Deliberately not a
14676
+ // `requestHeaders` entry: on live traffic this is the *client's* API version,
14677
+ // set by every Anthropic SDK, and pinning it server-side would strip that
14678
+ // choice and silently downgrade every request to the oldest version.
14679
+ probeHeaders: { "anthropic-version": "2023-06-01" }
14680
+ },
14681
+ xai: {
14682
+ directBaseUrl: "https://api.x.ai/",
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" },
14689
+ modelAuthor: "xAI"
14690
+ },
14691
+ gemini: {
14692
+ directBaseUrl: "https://generativelanguage.googleapis.com/",
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,
14700
+ modelAuthor: "Google"
14701
+ },
14702
+ perplexity: {
14703
+ directBaseUrl: "https://api.perplexity.ai/",
14704
+ auth: { header: "authorization", scheme: "Bearer " },
14705
+ nativeClampStyle: "responses",
14706
+ // No `probePath`: Perplexity has no unmetered authenticated endpoint.
14707
+ examplePath: "chat/completions",
14708
+ modelAuthor: "Perplexity"
14709
+ },
14710
+ // OpenAI-compatible chat-completions services. `modelAuthor` is set only
14711
+ // where one answer is right for every model the type serves; the hosts below
14712
+ // that carry no author serve other labs' open-weight models, and authorship
14713
+ // comes from the catalog entry per model instead.
14714
+ deepseek: {
14715
+ // No `v1` segment: DeepSeek documents the bare origin as its OpenAI base
14716
+ // URL, and `https://api.deepseek.com/anthropic` for the Anthropic format.
14717
+ directBaseUrl: "https://api.deepseek.com/",
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",
14723
+ modelAuthor: "DeepSeek"
14724
+ },
14725
+ groq: {
14726
+ // Groq's OpenAI-compatible surface lives under `openai/v1/`, so the client
14727
+ // path is `openai/v1/chat/completions` rather than `v1/chat/completions`.
14728
+ directBaseUrl: "https://api.groq.com/",
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"
14734
+ },
14735
+ mistral: {
14736
+ directBaseUrl: "https://api.mistral.ai/",
14737
+ auth: { header: "authorization", scheme: "Bearer " },
14738
+ nativeClampStyle: "chat_completions",
14739
+ probePath: "v1/models",
14740
+ modelAuthor: "Mistral"
14741
+ },
14742
+ together: {
14743
+ // `api.together.ai`, not the `.xyz` host older SDKs default to: the current
14744
+ // OpenAI-compatibility guide names this one and warns against the other.
14745
+ directBaseUrl: "https://api.together.ai/",
14746
+ auth: { header: "authorization", scheme: "Bearer " },
14747
+ nativeClampStyle: "chat_completions",
14748
+ probePath: "v1/models"
14749
+ },
14750
+ fireworks: {
14751
+ // The inference plane is `/inference/v1`; `/v1` on the same host is the
14752
+ // control plane, so the client path is `inference/v1/chat/completions`.
14753
+ directBaseUrl: "https://api.fireworks.ai/",
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"
14760
+ },
14761
+ cerebras: {
14762
+ directBaseUrl: "https://api.cerebras.ai/",
14763
+ auth: { header: "authorization", scheme: "Bearer " },
14764
+ nativeClampStyle: "chat_completions",
14765
+ probePath: "v1/models"
14766
+ },
14767
+ moonshot: {
14768
+ // The international host. `api.moonshot.cn` is the separate China platform
14769
+ // and is not reachable with a key issued for this one.
14770
+ directBaseUrl: "https://api.moonshot.ai/",
14771
+ auth: { header: "authorization", scheme: "Bearer " },
14772
+ nativeClampStyle: "chat_completions",
14773
+ probePath: "v1/models",
14774
+ modelAuthor: "Moonshot AI"
14775
+ },
14776
+ huggingface: {
14777
+ // The Inference Providers router: one OpenAI-compatible surface in front of
14778
+ // many upstreams, so it has no author of its own and no stable per-model
14779
+ // price — the router picks the upstream, and the same model ID costs an
14780
+ // order of magnitude more on some of them than others. It ships with no
14781
+ // catalog section for that reason; see `catalogPrice` in usage.ts.
14782
+ directBaseUrl: "https://router.huggingface.co/",
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.
14789
+ },
14790
+ baseten: {
14791
+ // The Model APIs inference host. `api.baseten.co` is the management plane
14792
+ // for dedicated deployments and answers to different paths entirely.
14793
+ directBaseUrl: "https://inference.baseten.co/",
14794
+ auth: { header: "authorization", scheme: "Bearer " },
14795
+ nativeClampStyle: "chat_completions",
14796
+ probePath: "v1/models"
14797
+ },
14798
+ bytedance: {
14799
+ // BytePlus ModelArk, the international edition: `/api/v3` is its version
14800
+ // segment, so client paths carry no `v1`. `ark.cn-beijing.volces.com` is
14801
+ // the separate China platform, and `ark.eu-west.bytepluses.com` is a second
14802
+ // region — keys and catalogs are region-isolated, so reaching either needs
14803
+ // the per-row base URL override.
14804
+ directBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/",
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"
14811
+ },
14812
+ openrouter: {
14813
+ // An aggregator treated as a provider type: it is the counterparty that
14814
+ // bills the organization, and its slugs (`google/gemini-3.6-flash`) are the
14815
+ // canonical model IDs here — there is no underlying ID to translate to.
14816
+ // `/api/v1` is its documented server URL, so the client path is
14817
+ // `v1/chat/completions` under this origin.
14818
+ directBaseUrl: "https://openrouter.ai/api/",
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"],
14832
+ // Every chat-completions response carries `usage.cost`: what OpenRouter
14833
+ // actually charged for that request, which beats any local estimate of a
14834
+ // catalog this deployment does not track. It is also the only way to bill
14835
+ // the type at all, since no static price list covers 400 models across
14836
+ // every lab. The declaration owns the parsing and the header that asks for
14837
+ // the routing metadata — see `src/shared/cost-report.ts`.
14838
+ costReport: OPENROUTER_COST_REPORT,
14839
+ // No `modelAuthor`: an aggregator serves everyone's models, and the slug
14840
+ // namespace answers per model instead.
14841
+ authorNamespacedModels: true
14842
+ }
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);
14609
14870
 
14610
14871
  // ../src/core/origin-guard.ts
14611
14872
  var MAX_BASE_URL_LENGTH = 200;
@@ -14714,23 +14975,62 @@ function checkOperatorBaseUrl(input) {
14714
14975
  return { ok: true, baseUrl };
14715
14976
  }
14716
14977
 
14717
- // ../src/core/types.ts
14978
+ // ../src/contracts/schemas.ts
14718
14979
  var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
14719
14980
  var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
14720
-
14721
- // ../src/contracts/schemas.ts
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 });
14722
14997
  var ProviderTypeSchema = external_exports.enum(PROVIDER_TYPES);
14723
14998
  var NullableRequestLimit = external_exports.number().int().positive().nullable();
14724
- var NullableSpendLimit = external_exports.number().nonnegative().nullable();
14725
- 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);
14726
15008
  var ClaimRequirementSchema = external_exports.object({
14727
- path: external_exports.string(),
15009
+ path: external_exports.string().min(1),
14728
15010
  contains: external_exports.union([external_exports.string(), external_exports.array(external_exports.string()).min(1)]).optional(),
14729
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
+ }
14730
15019
  });
14731
- 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);
14732
15021
  var IssuerAuthenticationSchema = external_exports.object({
14733
- 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." }),
14734
15034
  /**
14735
15035
  * Required: several issuers publish one JWKS for every customer — Firebase
14736
15036
  * signs all projects with the same keys — so without `iss` and `aud` a token
@@ -14738,8 +15038,9 @@ var IssuerAuthenticationSchema = external_exports.object({
14738
15038
  */
14739
15039
  issuer: IssuerClaimValuesSchema,
14740
15040
  audience: IssuerClaimValuesSchema,
14741
- user_id_claim: external_exports.string(),
14742
- 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(),
14743
15044
  required_claims: external_exports.array(ClaimRequirementSchema),
14744
15045
  max_token_lifetime_seconds: external_exports.number().int().positive(),
14745
15046
  /**
@@ -14747,11 +15048,30 @@ var IssuerAuthenticationSchema = external_exports.object({
14747
15048
  * which kind of paid-user check `required_claims` implements. Bookkeeping for
14748
15049
  * the console, so it can show "Firebase, project X" and reopen the same
14749
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.
14750
15055
  */
14751
15056
  provider: external_exports.enum(ISSUER_PROVIDERS).optional(),
14752
15057
  entitlement: external_exports.enum(ENTITLEMENT_CHECKS).optional()
14753
15058
  }).strict();
14754
- var EndUserHeaderSchema = external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/, { 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
+ });
14755
15075
  var HeaderEndUserSchema = external_exports.object({
14756
15076
  source: external_exports.literal("header"),
14757
15077
  header: EndUserHeaderSchema
@@ -14771,23 +15091,29 @@ var AppAttestEndUserSchema = external_exports.discriminatedUnion("source", [
14771
15091
  IssuerEndUserSchema,
14772
15092
  AppInstallEndUserSchema
14773
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
+ });
14774
15102
  var AppleAppAttestAuthenticationSchema = external_exports.object({
14775
15103
  type: external_exports.literal("apple_app_attest"),
14776
15104
  end_user: AppAttestEndUserSchema,
14777
- app_attest: external_exports.object({
14778
- team_id: external_exports.string(),
14779
- bundle_id: external_exports.string(),
15105
+ app_attest: AppleAppIdentitySchema.extend({
14780
15106
  /**
14781
15107
  * Which of Apple's two App Attest environments this application accepts.
14782
- * Omitted means `["production"]` alone, because a development-signed build
15108
+ * Defaulted to `["production"]` alone, because a development-signed build
14783
15109
  * stamps a different aaguid and is debuggable on any device carrying the
14784
15110
  * team's provisioning profile. Accepting one is therefore a deliberate
14785
15111
  * per-application opt-in, and belongs on a development bundle id rather
14786
15112
  * than the one shipped to the App Store.
14787
15113
  */
14788
- 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, {
14789
15115
  error: "environments cannot repeat a value"
14790
- }).optional()
15116
+ }).prefault(["production"])
14791
15117
  }).strict()
14792
15118
  }).strict();
14793
15119
  var ApiKeyAuthenticationSchema = external_exports.object({
@@ -14800,59 +15126,96 @@ var ApiKeyAuthenticationSchema = external_exports.object({
14800
15126
  */
14801
15127
  end_user: ApiKeyEndUserSchema.optional()
14802
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
+ ]);
14803
15137
  var ProviderPolicySchema = external_exports.object({
14804
- allowed_paths: external_exports.array(external_exports.union([
14805
- external_exports.string(),
14806
- external_exports.object({
14807
- path: external_exports.string(),
14808
- fixed_model: external_exports.string().optional(),
14809
- clamp: external_exports.enum(["responses", "chat_completions", "gemini_native", "anthropic", "none"]).optional()
14810
- })
14811
- ])),
14812
- 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)),
14813
15140
  max_output_tokens: external_exports.number().int().positive().optional()
14814
- });
15141
+ }).strict();
14815
15142
  var EndpointTargetSchema = external_exports.object({
14816
15143
  provider: SlugSchema,
14817
15144
  model: external_exports.string().min(1)
14818
15145
  });
14819
15146
  var EndpointSchema = EndpointTargetSchema.extend({
14820
- api_style: external_exports.enum(["responses", "transcription"]),
15147
+ api_style: external_exports.enum(ENDPOINT_API_STYLES),
14821
15148
  params: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
14822
15149
  max_output_tokens: external_exports.number().int().positive().optional(),
14823
15150
  fallback: external_exports.array(EndpointTargetSchema).optional()
14824
- });
15151
+ }).strict();
14825
15152
  var LimitScopeSchema = external_exports.object({
14826
15153
  requests: external_exports.object({
14827
15154
  per_minute: NullableRequestLimit,
14828
15155
  per_day: NullableRequestLimit
14829
- }),
14830
- spending: external_exports.object({ monthly_usd: NullableSpendLimit })
14831
- });
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;
14832
15167
  var AppConfigSchema = external_exports.object({
14833
15168
  authentication: external_exports.discriminatedUnion("type", [
14834
15169
  AppleAppAttestAuthenticationSchema,
14835
15170
  ApiKeyAuthenticationSchema
14836
15171
  ]),
14837
15172
  routing: external_exports.object({
14838
- providers: external_exports.object({
14839
- mode: external_exports.enum(["all", "selected"]),
14840
- selected: external_exports.record(SlugSchema, ProviderPolicySchema).optional()
14841
- }),
14842
- model_rewrites: external_exports.record(external_exports.string(), external_exports.string())
14843
- }),
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(),
14844
15192
  /**
14845
- * The organization's own limits on its app's end users. Optional: an absent
14846
- * 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.
14847
15195
  */
14848
15196
  limits: external_exports.object({
14849
- per_user: LimitScopeSchema,
14850
- per_app: LimitScopeSchema
14851
- }).optional(),
14852
- endpoints: external_exports.record(external_exports.string().regex(/^[a-z0-9-]{1,64}$/), 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
+ }
14853
15215
  }).meta({ id: "AppConfig" });
14854
15216
  var AppWriteSchema = external_exports.object({
14855
- 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),
14856
15219
  config: AppConfigSchema,
14857
15220
  status: external_exports.enum(["active", "disabled"]).optional()
14858
15221
  }).strict().meta({ id: "AppWrite" });
@@ -14880,7 +15243,7 @@ var ApiKeyTokenRequestSchema = external_exports.object({
14880
15243
  var UsageRepriceRequestSchema = external_exports.object({
14881
15244
  provider: ProviderTypeSchema,
14882
15245
  model: external_exports.string().min(1),
14883
- month: external_exports.string().regex(/^\d{4}-\d{2}$/),
15246
+ month: MonthSchema,
14884
15247
  apply: external_exports.boolean().default(false)
14885
15248
  }).strict().meta({ id: "UsageRepriceRequest" });
14886
15249
  var ProviderPricingSchema = external_exports.record(
@@ -14906,15 +15269,17 @@ var GatewayRouteConfigSchema = external_exports.object({
14906
15269
  modelPrefix: external_exports.string().trim().min(1).max(100).optional(),
14907
15270
  providerOnly: external_exports.array(external_exports.string().trim().min(1).max(100)).min(1).max(20).optional()
14908
15271
  }).strict().meta({ id: "GatewayRouteConfig" });
14909
- var ProviderCreateRequestSchema = external_exports.object({
15272
+ var ProviderCreateFieldsSchema = external_exports.object({
14910
15273
  type: ProviderTypeSchema,
14911
15274
  name: ProviderNameSchema,
14912
15275
  slug: SlugSchema.optional(),
14913
- secret: ProviderSecretSchema.optional(),
14914
15276
  providerGatewayId: external_exports.string().trim().min(1).optional(),
14915
15277
  gatewayRoute: GatewayRouteConfigSchema.optional(),
14916
15278
  baseUrl: ProviderBaseUrlSchema.optional(),
14917
15279
  pricing: ProviderPricingSchema.optional()
15280
+ });
15281
+ var ProviderCreateRequestSchema = ProviderCreateFieldsSchema.extend({
15282
+ secret: ProviderSecretSchema.optional()
14918
15283
  }).strict().superRefine((value, context) => {
14919
15284
  if (value.secret === void 0 === (value.providerGatewayId === void 0)) {
14920
15285
  context.addIssue({
@@ -14941,21 +15306,22 @@ var ProviderTestRequestSchema = external_exports.object({
14941
15306
  }
14942
15307
  assertBaseUrlIsDirect(value, context);
14943
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";
14944
15320
  var ProviderGatewayCreateRequestSchema = external_exports.discriminatedUnion("type", [
14945
- external_exports.object({
14946
- type: external_exports.literal("cf_aig"),
14947
- name: ProviderNameSchema,
14948
- accountId: external_exports.string().trim().min(1).max(100),
14949
- gatewayId: external_exports.string().trim().min(1).max(100),
14950
- token: ProviderSecretSchema
14951
- }).strict(),
14952
- external_exports.object({
14953
- type: external_exports.literal("vercel"),
14954
- name: ProviderNameSchema,
14955
- token: ProviderSecretSchema
14956
- }).strict()
15321
+ CfAigGatewayFieldsSchema.extend({ token: ProviderSecretSchema }).strict(),
15322
+ VercelGatewayFieldsSchema.extend({ token: ProviderSecretSchema }).strict()
14957
15323
  ], {
14958
- error: "Provider gateway type must be one of cf_aig, vercel"
15324
+ error: GATEWAY_TYPE_ERROR
14959
15325
  }).meta({ id: "ProviderGatewayCreateRequest" });
14960
15326
  var ProviderGatewayTestRequestSchema = external_exports.discriminatedUnion("type", [
14961
15327
  external_exports.object({
@@ -14972,15 +15338,16 @@ var ProviderGatewayTestRequestSchema = external_exports.discriminatedUnion("type
14972
15338
  error: "Provider gateway type must be one of cf_aig, vercel"
14973
15339
  }).meta({ id: "ProviderGatewayTestRequest" });
14974
15340
  var ProviderGatewayUpdateRequestSchema = external_exports.object({
14975
- name: ProviderNameSchema
15341
+ name: ProviderNameSchema,
15342
+ revision: external_exports.number().int().positive().meta({ description: "The gateway revision returned by the read this edit is based on." })
14976
15343
  }).strict().meta({ id: "ProviderGatewayUpdateRequest" });
14977
15344
  var ProviderGatewayRotateRequestSchema = external_exports.object({
14978
- token: ProviderSecretSchema
15345
+ token: ProviderSecretSchema,
15346
+ revision: external_exports.number().int().positive().meta({ description: "The gateway revision returned by the read this rotation is based on." })
14979
15347
  }).strict().meta({ id: "ProviderGatewayRotateRequest" });
14980
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";
14981
- var ProviderUpdateRequestSchema = external_exports.object({
15349
+ var ProviderUpdateFieldsSchema = external_exports.object({
14982
15350
  name: ProviderNameSchema.optional(),
14983
- secret: ProviderSecretSchema.optional(),
14984
15351
  /** A full replace; `null` clears the row's routing configuration. */
14985
15352
  gatewayRoute: GatewayRouteConfigSchema.nullable().optional(),
14986
15353
  /**
@@ -14996,6 +15363,10 @@ var ProviderUpdateRequestSchema = external_exports.object({
14996
15363
  * nothing can take the slug meanwhile and re-enabling always succeeds.
14997
15364
  */
14998
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()
14999
15370
  }).strict().superRefine((value, context) => {
15000
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) {
15001
15372
  context.addIssue({
@@ -15007,12 +15378,73 @@ var ProviderUpdateRequestSchema = external_exports.object({
15007
15378
  context.addIssue({ code: "custom", message: BASE_URL_REQUIRES_SECRET, path: ["secret"] });
15008
15379
  }
15009
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 });
15010
15401
  var OrganizationRoleSchema = external_exports.enum(["owner", "admin", "member"]);
15011
15402
  var OrganizationSelectRequestSchema = external_exports.object({
15012
15403
  organizationId: external_exports.string().trim().min(1)
15013
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
+ }
15014
15424
 
15015
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
+ ];
15016
15448
  var ErrorResponseSchema = external_exports.object({
15017
15449
  error: external_exports.object({
15018
15450
  code: external_exports.string(),
@@ -15040,6 +15472,24 @@ var AppAttestChallengeResponseSchema = external_exports.object({
15040
15472
  expires_in: external_exports.number()
15041
15473
  });
15042
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
+ });
15043
15493
  var UsageEventSchema = external_exports.object({
15044
15494
  id: external_exports.number().int(),
15045
15495
  /** Null for an application that identifies no end users. */
@@ -15083,7 +15533,7 @@ var UsageEventSchema = external_exports.object({
15083
15533
  }),
15084
15534
  app_version: external_exports.string().nullable(),
15085
15535
  auth_method: external_exports.enum(["attest", "api_key"]).nullable(),
15086
- status: external_exports.enum(["ok", "provider_error", "blocked_app_rate", "blocked_app_budget", "blocked_billing", "blocked_user"]),
15536
+ status: external_exports.enum(USAGE_STATUSES),
15087
15537
  client_aborted: external_exports.number().int().nullable().meta({
15088
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."
15089
15539
  }),
@@ -15162,18 +15612,18 @@ var AppResponseSchema = external_exports.object({
15162
15612
  id: external_exports.string().meta({ description: "The gateway-assigned id, and the `{app}` segment of every URL for this application." }),
15163
15613
  name: external_exports.string(),
15164
15614
  /**
15165
- * An `AppConfig`, except on the one row this shape's own `config_error`
15166
- * describes: a configuration written before a schema change is returned as
15167
- * it is stored, so an operator can read and repair it. Declared as the
15168
- * union rather than as `AppConfig` alone because a client that parses this
15169
- * 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`.
15170
15621
  */
15171
15622
  config: external_exports.union([AppConfigSchema, external_exports.record(external_exports.string(), external_exports.unknown())]),
15172
15623
  status: external_exports.enum(["active", "disabled"]),
15173
15624
  created_at: external_exports.string(),
15174
15625
  updated_at: external_exports.string()
15175
15626
  }),
15176
- 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." }),
15177
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." })
15178
15628
  }).meta({ id: "AppResponse" });
15179
15629
  var AppDeleteResponseSchema = external_exports.object({
@@ -15208,7 +15658,7 @@ var ManagementKeyResponseSchema = external_exports.object({ key: ManagementKeySu
15208
15658
  var ProviderSummarySchema = external_exports.object({
15209
15659
  id: external_exports.string(),
15210
15660
  type: external_exports.enum(PROVIDER_TYPES),
15211
- 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." }),
15212
15662
  name: external_exports.string(),
15213
15663
  secretHint: external_exports.string().nullable().meta({
15214
15664
  description: "Last characters of a direct provider key; null when a shared provider gateway owns the token."
@@ -15222,6 +15672,7 @@ var ProviderSummarySchema = external_exports.object({
15222
15672
  example: "https://my-resource.openai.azure.com/openai/v1/"
15223
15673
  }),
15224
15674
  pricing: ProviderPricingSchema.nullable(),
15675
+ revision: external_exports.number().int().positive(),
15225
15676
  status: external_exports.enum(["active", "disabled"]).meta({
15226
15677
  description: "disabled is a reversible pause: the row keeps its secret, its pricing and its slug, and requests to it fail with provider_disabled until it is enabled again."
15227
15678
  }),
@@ -15262,6 +15713,7 @@ var providerGatewayFields = {
15262
15713
  referencedCount: external_exports.number().int().nonnegative().meta({
15263
15714
  description: "All provider instances referencing this gateway, including disabled rows retained for re-enabling. Deletion is refused while this is above zero."
15264
15715
  }),
15716
+ revision: external_exports.number().int().positive(),
15265
15717
  status: external_exports.enum(["active", "revoked"]),
15266
15718
  createdAt: external_exports.string(),
15267
15719
  updatedAt: external_exports.string(),
@@ -15382,13 +15834,17 @@ var AppSummarySchema = external_exports.object({
15382
15834
  });
15383
15835
  var AppListResponseSchema = external_exports.object({
15384
15836
  month: external_exports.string(),
15385
- 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
+ }),
15386
15840
  apps: external_exports.array(AppSummarySchema)
15387
15841
  });
15388
15842
  var AppValidateResponseSchema = external_exports.object({
15389
15843
  valid: external_exports.literal(true),
15390
- app_id: external_exports.string(),
15391
- exists: external_exports.boolean()
15844
+ app_id: external_exports.string()
15845
+ });
15846
+ var AppDraftValidateResponseSchema = external_exports.object({
15847
+ valid: external_exports.literal(true)
15392
15848
  });
15393
15849
  var CreatedApiKeySchema = external_exports.object({
15394
15850
  id: external_exports.string(),
@@ -15471,11 +15927,32 @@ var BreakdownRowSchema = UsageTotalsSchema.extend({
15471
15927
  });
15472
15928
  var BreakdownResponseSchema = external_exports.object({
15473
15929
  app_id: external_exports.string(),
15474
- by: external_exports.string(),
15930
+ by: external_exports.enum(USAGE_BREAKDOWN_DIMENSIONS),
15475
15931
  from: external_exports.string(),
15476
15932
  to: external_exports.string(),
15477
15933
  rows: external_exports.array(BreakdownRowSchema)
15478
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
+ });
15479
15956
  var ModelPriceSchema = external_exports.object({
15480
15957
  input: external_exports.number().optional(),
15481
15958
  output: external_exports.number().optional(),
@@ -15496,18 +15973,25 @@ var PricesResponseSchema = external_exports.object({
15496
15973
  // ../src/contracts/cli.ts
15497
15974
  var CliProofSchema = external_exports.string().regex(/^[A-Za-z0-9_-]{32,256}$/);
15498
15975
  var CliBootstrapRequestSchema = external_exports.object({ idempotencyKey: CliProofSchema, pollToken: CliProofSchema }).strict();
15499
- var CliOperationRequestSchema = external_exports.object({
15500
- kind: external_exports.enum([
15501
- "claim",
15502
- "provider.add",
15503
- "provider.rotate-key",
15504
- "provider.update",
15505
- "provider-gateway.add",
15506
- "provider-gateway.rotate-key"
15507
- ]),
15508
- payload: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15509
- pollToken: CliProofSchema
15510
- }).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
+ ]);
15511
15995
  var CliSubmissionRequestSchema = external_exports.object({
15512
15996
  submissionToken: CliProofSchema,
15513
15997
  approve: external_exports.literal(true).optional(),
@@ -15538,7 +16022,7 @@ var CliViewerSchema = external_exports.object({
15538
16022
  email: external_exports.string().nullable()
15539
16023
  });
15540
16024
  var CliBrowserDetailsResponseSchema = external_exports.object({
15541
- kind: CliOperationRequestSchema.shape.kind,
16025
+ kind: CliOperationKindSchema,
15542
16026
  payload: external_exports.record(external_exports.string(), external_exports.unknown()),
15543
16027
  account: CliAccountSchema,
15544
16028
  viewer: CliViewerSchema.nullable(),
@@ -15571,11 +16055,17 @@ var CliBrowserGoogleResponseSchema = external_exports.object({
15571
16055
  var CliCredentialSchema = external_exports.object({
15572
16056
  token: external_exports.string()
15573
16057
  });
16058
+ var UnclaimedAccessSchema = external_exports.object({ endsAt: external_exports.string(), limit: external_exports.number().int().optional() }).nullable();
15574
16059
  var CliBootstrapResponseSchema = external_exports.object({
15575
16060
  deployment: CliDeploymentSchema,
15576
16061
  account: CliAccountSchema,
15577
16062
  credential: CliCredentialSchema,
15578
- 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
15579
16069
  });
15580
16070
  var CliOperationResponseSchema = external_exports.object({
15581
16071
  id: external_exports.string(),
@@ -15692,57 +16182,6 @@ var CliAccountResponseSchema = external_exports.object({
15692
16182
  billing: CliBillingSchema,
15693
16183
  usage: CliQuotaUsageSchema
15694
16184
  });
15695
-
15696
- // ../src/contracts/operation-schemas.ts
15697
- var responseSchemas = {
15698
- getConsoleCapabilities: ConsoleCapabilitiesResponseSchema,
15699
- getAdminSession: IdentitySessionSchema,
15700
- listOrganizations: OrganizationListResponseSchema,
15701
- selectOrganization: IdentitySessionSchema,
15702
- listManagementKeys: ManagementKeyListResponseSchema,
15703
- createManagementKey: CreatedManagementKeyResponseSchema,
15704
- revokeManagementKey: ManagementKeyResponseSchema,
15705
- listProviders: ProviderListResponseSchema,
15706
- createProvider: ProviderResponseSchema,
15707
- testProvider: ProviderTestResponseSchema,
15708
- updateProvider: ProviderResponseSchema,
15709
- deleteProvider: ProviderDeleteResponseSchema,
15710
- listProviderGateways: ProviderGatewayListResponseSchema,
15711
- createProviderGateway: ProviderGatewayResponseSchema,
15712
- testProviderGateway: ProviderGatewayTestResponseSchema,
15713
- updateProviderGateway: ProviderGatewayResponseSchema,
15714
- rotateProviderGateway: ProviderGatewayResponseSchema,
15715
- deleteProviderGateway: ProviderGatewayDeleteResponseSchema,
15716
- listApps: AppListResponseSchema,
15717
- createApp: CreatedAppResponseSchema,
15718
- getApp: AppResponseSchema,
15719
- updateApp: AppResponseSchema,
15720
- deleteApp: AppDeleteResponseSchema,
15721
- validateApp: AppValidateResponseSchema,
15722
- listAppKeys: ApiKeyListResponseSchema,
15723
- createAppKey: CreatedApiKeySchema,
15724
- revokeAppKey: ApiKeyRevokeResponseSchema,
15725
- listAppUsers: UserListResponseSchema,
15726
- setAppUserBlocked: UserBlockResponseSchema,
15727
- getAppUsage: MonthlyUsageResponseSchema,
15728
- getAppUsageTimeseries: TimeseriesResponseSchema,
15729
- getAppUsageBreakdown: BreakdownResponseSchema,
15730
- listAppEvents: UsageEventListSchema,
15731
- getAppAuthEventSummary: AuthEventSummarySchema,
15732
- listAppAuthEvents: AuthEventListSchema,
15733
- listModelPrices: PricesResponseSchema,
15734
- getCliCapabilities: CliCapabilitiesResponseSchema,
15735
- bootstrapCliAccount: CliBootstrapResponseSchema,
15736
- createCliOperation: CliOperationResponseSchema,
15737
- pollCliOperation: CliPollResponseSchema,
15738
- getCliAccount: CliAccountResponseSchema,
15739
- getCliUsage: CliUsageResponseSchema,
15740
- cliBrowserDetails: CliBrowserDetailsResponseSchema,
15741
- cliBrowserSubmit: CliBrowserSubmitResponseSchema,
15742
- cliBrowserRegister: CliBrowserRegisterResponseSchema,
15743
- cliBrowserGoogle: CliBrowserGoogleResponseSchema
15744
- };
15745
- var responseSchemaFor = responseSchemas;
15746
16185
  var CliErrorDetailsSchema = external_exports.object({
15747
16186
  status: external_exports.number().optional(),
15748
16187
  appId: external_exports.string().optional(),
@@ -15777,7 +16216,1058 @@ var CliErrorDetailsSchema = external_exports.object({
15777
16216
  // src/apps.ts
15778
16217
  import { createHash as createHash2 } from "node:crypto";
15779
16218
 
15780
- // ../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
+ };
15781
17271
  function searchSuffix(params) {
15782
17272
  const search = new URLSearchParams();
15783
17273
  for (const [key, value] of Object.entries(params)) {
@@ -15786,200 +17276,15 @@ function searchSuffix(params) {
15786
17276
  const encoded = search.toString();
15787
17277
  return encoded ? `?${encoded}` : "";
15788
17278
  }
15789
- var seg = encodeURIComponent;
15790
- function operation() {
15791
- 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);
15792
17287
  }
15793
- var operations = {
15794
- // Deployment and identity
15795
- getConsoleCapabilities: operation()(
15796
- "GET",
15797
- () => "/v1/console/capabilities"
15798
- ),
15799
- getAdminSession: operation()(
15800
- "GET",
15801
- () => "/v1/admin/session"
15802
- ),
15803
- listOrganizations: operation()(
15804
- "GET",
15805
- () => "/v1/admin/organizations"
15806
- ),
15807
- selectOrganization: operation()(
15808
- "POST",
15809
- () => "/v1/admin/organizations/select"
15810
- ),
15811
- // Management keys
15812
- listManagementKeys: operation()(
15813
- "GET",
15814
- () => "/v1/admin/keys"
15815
- ),
15816
- createManagementKey: operation()(
15817
- "POST",
15818
- () => "/v1/admin/keys"
15819
- ),
15820
- revokeManagementKey: operation()(
15821
- "POST",
15822
- (id) => `/v1/admin/keys/${seg(id)}/revoke`
15823
- ),
15824
- // Providers
15825
- listProviders: operation()(
15826
- "GET",
15827
- () => "/v1/admin/providers"
15828
- ),
15829
- createProvider: operation()(
15830
- "POST",
15831
- () => "/v1/admin/providers"
15832
- ),
15833
- testProvider: operation()(
15834
- "POST",
15835
- () => "/v1/admin/providers/test"
15836
- ),
15837
- updateProvider: operation()(
15838
- "PUT",
15839
- (id) => `/v1/admin/providers/${seg(id)}`
15840
- ),
15841
- deleteProvider: operation()(
15842
- "DELETE",
15843
- (id) => `/v1/admin/providers/${seg(id)}`
15844
- ),
15845
- // Provider gateways
15846
- listProviderGateways: operation()(
15847
- "GET",
15848
- () => "/v1/admin/provider-gateways"
15849
- ),
15850
- createProviderGateway: operation()("POST", () => "/v1/admin/provider-gateways"),
15851
- testProviderGateway: operation()("POST", () => "/v1/admin/provider-gateways/test"),
15852
- updateProviderGateway: operation()("PATCH", (id) => `/v1/admin/provider-gateways/${seg(id)}`),
15853
- rotateProviderGateway: operation()("POST", (id) => `/v1/admin/provider-gateways/${seg(id)}/rotate`),
15854
- deleteProviderGateway: operation()(
15855
- "DELETE",
15856
- (id) => `/v1/admin/provider-gateways/${seg(id)}`
15857
- ),
15858
- // Applications
15859
- listApps: operation()(
15860
- "GET",
15861
- (query = {}) => `/v1/admin/apps${searchSuffix(query)}`
15862
- ),
15863
- createApp: operation()(
15864
- "POST",
15865
- () => "/v1/admin/apps"
15866
- ),
15867
- getApp: operation()(
15868
- "GET",
15869
- (appId) => `/v1/admin/apps/${seg(appId)}`
15870
- ),
15871
- updateApp: operation()(
15872
- "PUT",
15873
- (appId) => `/v1/admin/apps/${seg(appId)}`
15874
- ),
15875
- deleteApp: operation()(
15876
- "DELETE",
15877
- (appId) => `/v1/admin/apps/${seg(appId)}${searchSuffix({ confirm: appId })}`
15878
- ),
15879
- validateApp: operation()(
15880
- "POST",
15881
- (appId) => `/v1/admin/apps/${seg(appId)}/validate`
15882
- ),
15883
- // Application keys
15884
- listAppKeys: operation()(
15885
- "GET",
15886
- (appId) => `/v1/admin/apps/${seg(appId)}/keys`
15887
- ),
15888
- createAppKey: operation()(
15889
- "POST",
15890
- (appId) => `/v1/admin/apps/${seg(appId)}/keys`
15891
- ),
15892
- revokeAppKey: operation()(
15893
- "POST",
15894
- (appId, keyId) => `/v1/admin/apps/${seg(appId)}/keys/${seg(keyId)}/revoke`
15895
- ),
15896
- // Application users
15897
- listAppUsers: operation()(
15898
- "GET",
15899
- (appId, query) => `/v1/admin/apps/${seg(appId)}/users${searchSuffix(query)}`
15900
- ),
15901
- setAppUserBlocked: operation()(
15902
- "POST",
15903
- (appId, userId, blocked) => `/v1/admin/apps/${seg(appId)}/users/${seg(userId)}/${blocked ? "block" : "unblock"}`
15904
- ),
15905
- // Application usage
15906
- getAppUsage: operation()(
15907
- "GET",
15908
- (appId, query = {}) => `/v1/admin/apps/${seg(appId)}/usage${searchSuffix(query)}`
15909
- ),
15910
- getAppUsageTimeseries: operation()(
15911
- "GET",
15912
- (appId, query) => `/v1/admin/apps/${seg(appId)}/usage/timeseries${searchSuffix(query)}`
15913
- ),
15914
- getAppUsageBreakdown: operation()(
15915
- "GET",
15916
- (appId, query) => `/v1/admin/apps/${seg(appId)}/usage/breakdown${searchSuffix(query)}`
15917
- ),
15918
- listAppEvents: operation()(
15919
- "GET",
15920
- (appId, query) => `/v1/admin/apps/${seg(appId)}/events${searchSuffix(query)}`
15921
- ),
15922
- getAppAuthEventSummary: operation()(
15923
- "GET",
15924
- (appId, query) => `/v1/admin/apps/${seg(appId)}/auth-events/summary${searchSuffix(query)}`
15925
- ),
15926
- listAppAuthEvents: operation()(
15927
- "GET",
15928
- (appId, query) => `/v1/admin/apps/${seg(appId)}/auth-events${searchSuffix(query)}`
15929
- ),
15930
- listModelPrices: operation()(
15931
- "GET",
15932
- () => "/v1/admin/prices"
15933
- ),
15934
- // CLI surface
15935
- getCliCapabilities: operation()(
15936
- "GET",
15937
- () => "/v1/cli/capabilities"
15938
- ),
15939
- bootstrapCliAccount: operation()(
15940
- "POST",
15941
- () => "/v1/cli/bootstrap"
15942
- ),
15943
- createCliOperation: operation()(
15944
- "POST",
15945
- () => "/v1/cli/operations"
15946
- ),
15947
- pollCliOperation: operation()(
15948
- "GET",
15949
- (id) => `/v1/cli/operations/${seg(id)}`
15950
- ),
15951
- getCliAccount: operation()(
15952
- "GET",
15953
- () => "/v1/cli/account"
15954
- ),
15955
- getCliUsage: operation()(
15956
- "GET",
15957
- (query = {}) => `/v1/cli/usage${searchSuffix(query)}`
15958
- ),
15959
- /*
15960
- * The browser half of a handoff, called by the console's approval page rather
15961
- * than by the CLI. Every one of them carries the submission proof from the
15962
- * URL fragment in its body, so they are POSTs with no readable URL of their
15963
- * own; routing them through here is what keeps the page from writing those
15964
- * paths by hand.
15965
- */
15966
- cliBrowserDetails: operation()(
15967
- "POST",
15968
- (id) => `/v1/cli/browser/${seg(id)}/details`
15969
- ),
15970
- cliBrowserSubmit: operation()(
15971
- "POST",
15972
- (id) => `/v1/cli/browser/${seg(id)}/submit`
15973
- ),
15974
- cliBrowserRegister: operation()(
15975
- "POST",
15976
- (id) => `/v1/cli/browser/${seg(id)}/register`
15977
- ),
15978
- cliBrowserGoogle: operation()(
15979
- "POST",
15980
- (id) => `/v1/cli/browser/${seg(id)}/google`
15981
- )
15982
- };
15983
17288
 
15984
17289
  // src/common.ts
15985
17290
  import { randomBytes } from "node:crypto";
@@ -16350,14 +17655,14 @@ async function required2(flags, key, label = key) {
16350
17655
  return typeof value === "string" ? value : await prompt(label, flags);
16351
17656
  }
16352
17657
  async function resolveProvider(ctx, id) {
16353
- const { data } = await ctx.call("listProviders", []);
17658
+ const { data } = await ctx.call("listProviders");
16354
17659
  const matches = data.providers.filter((p) => p.id === id || p.slug === id);
16355
17660
  if (matches.length !== 1 || !matches[0])
16356
17661
  fail("provider_not_found", "Choose one exact provider ID or slug.");
16357
17662
  return matches[0];
16358
17663
  }
16359
17664
  async function resolveGateway(ctx, id) {
16360
- const { data } = await ctx.call("listProviderGateways", []);
17665
+ const { data } = await ctx.call("listProviderGateways");
16361
17666
  const value = data.gateways.find((g) => g.id === id);
16362
17667
  if (!value)
16363
17668
  fail("provider_gateway_not_found", "Provider gateway ID was not found.");
@@ -16367,14 +17672,14 @@ async function resourceCommand(ctx, command, args, flags) {
16367
17672
  const [group, action] = command.split(" ");
16368
17673
  const gateway = group === "provider-gateway";
16369
17674
  if (action === "types") {
16370
- const { data } = await ctx.publicCall("getCliCapabilities", []);
17675
+ const { data } = await ctx.publicCall("getCliCapabilities");
16371
17676
  return gateway ? data.providerGateways.map((entry) => ({
16372
17677
  ...entry,
16373
17678
  type: entry.type === "cf_aig" ? "cloudflare" : entry.type
16374
17679
  })) : data.providers;
16375
17680
  }
16376
17681
  if (action === "list")
16377
- 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;
16378
17683
  let existingProvider;
16379
17684
  let existingGateway;
16380
17685
  const target = args[0];
@@ -16394,14 +17699,14 @@ async function resourceCommand(ctx, command, args, flags) {
16394
17699
  `Delete ${group} ${existingGateway.name} (${existingGateway.id})? Referenced by ${existingGateway.referencedCount} providers; deletion is refused until all references are removed.`,
16395
17700
  flags
16396
17701
  );
16397
- return (await ctx.call("deleteProviderGateway", [existingGateway.id])).data;
17702
+ return (await ctx.call("deleteProviderGateway", { params: { id: existingGateway.id } })).data;
16398
17703
  }
16399
17704
  if (!existingProvider) fail("invalid_arguments", "Supply the resource to remove.");
16400
17705
  await confirm(
16401
17706
  `Delete ${group} ${existingProvider.name} (${existingProvider.id})? Apps referencing this provider lose access through this slug.`,
16402
17707
  flags
16403
17708
  );
16404
- return (await ctx.call("deleteProvider", [existingProvider.id])).data;
17709
+ return (await ctx.call("deleteProvider", { params: { id: existingProvider.id } })).data;
16405
17710
  }
16406
17711
  if (action === "add") {
16407
17712
  const type = await required2(
@@ -16435,7 +17740,7 @@ async function resourceCommand(ctx, command, args, flags) {
16435
17740
  await ctx.bootstrap();
16436
17741
  if (flags.browser)
16437
17742
  return ctx.operation("provider-gateway.add", { ...draft2 });
16438
- const created2 = await ctx.create("createProviderGateway", [], body2);
17743
+ const created2 = await ctx.create("createProviderGateway", { body: body2 });
16439
17744
  await created2.complete();
16440
17745
  return created2.data;
16441
17746
  }
@@ -16467,8 +17772,9 @@ async function resourceCommand(ctx, command, args, flags) {
16467
17772
  let body;
16468
17773
  if (!flags.browser) body = validate(ProviderCreateRequestSchema, draft);
16469
17774
  await ctx.bootstrap();
16470
- if (flags.browser) return ctx.operation("provider.add", { ...draft });
16471
- 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 });
16472
17778
  await created.complete();
16473
17779
  return created.data;
16474
17780
  }
@@ -16477,9 +17783,10 @@ async function resourceCommand(ctx, command, args, flags) {
16477
17783
  if (!existingGateway) fail("invalid_arguments", "Supply the gateway to rotate.");
16478
17784
  const value2 = await secret(flags, "Gateway token");
16479
17785
  if (flags.browser)
16480
- return ctx.operation("provider-gateway.rotate-key", { id: existingGateway.id });
16481
- return (await ctx.call("rotateProviderGateway", [existingGateway.id], {
16482
- body: { token: value2 }
17786
+ return ctx.operation("provider-gateway.rotate-key", { id: existingGateway.id, revision: existingGateway.revision });
17787
+ return (await ctx.call("rotateProviderGateway", {
17788
+ params: { id: existingGateway.id },
17789
+ body: { token: value2, revision: existingGateway.revision }
16483
17790
  })).data;
16484
17791
  }
16485
17792
  if (!existingProvider) fail("invalid_arguments", "Supply the provider to rotate.");
@@ -16490,18 +17797,20 @@ async function resourceCommand(ctx, command, args, flags) {
16490
17797
  );
16491
17798
  const value = await secret(flags, "Provider API key");
16492
17799
  if (flags.browser)
16493
- return ctx.operation("provider.rotate-key", { id: existingProvider.id });
16494
- return (await ctx.call("updateProvider", [existingProvider.id], {
16495
- body: { secret: value }
17800
+ return ctx.operation("provider.rotate-key", { id: existingProvider.id, revision: existingProvider.revision });
17801
+ return (await ctx.call("updateProvider", {
17802
+ params: { id: existingProvider.id },
17803
+ body: { secret: value, revision: existingProvider.revision }
16496
17804
  })).data;
16497
17805
  }
16498
17806
  if (action === "update") {
16499
17807
  if (gateway) {
16500
17808
  if (!existingGateway) fail("invalid_arguments", "Supply the gateway to update.");
16501
17809
  const body2 = validate(ProviderGatewayUpdateRequestSchema, {
16502
- name: await required2(flags, "name")
17810
+ name: await required2(flags, "name"),
17811
+ revision: existingGateway.revision
16503
17812
  });
16504
- return (await ctx.call("updateProviderGateway", [existingGateway.id], { body: body2 })).data;
17813
+ return (await ctx.call("updateProviderGateway", { params: { id: existingGateway.id }, body: body2 })).data;
16505
17814
  }
16506
17815
  if (!existingProvider) fail("invalid_arguments", "Supply the provider to update.");
16507
17816
  const draft = {};
@@ -16532,6 +17841,7 @@ async function resourceCommand(ctx, command, args, flags) {
16532
17841
  if (flags["base-url"] || flags["clear-base-url"]) {
16533
17842
  validate(ProviderUpdateRequestSchema, {
16534
17843
  ...draft,
17844
+ revision: existingProvider.revision,
16535
17845
  secret: "validation-placeholder"
16536
17846
  });
16537
17847
  const value = await secret(flags, "Provider API key");
@@ -16539,16 +17849,24 @@ async function resourceCommand(ctx, command, args, flags) {
16539
17849
  }
16540
17850
  const body = validate(ProviderUpdateRequestSchema, {
16541
17851
  ...draft,
17852
+ revision: existingProvider.revision,
16542
17853
  ...flags.browser ? { secret: "validation-placeholder" } : {}
16543
17854
  });
16544
17855
  if (flags.browser)
16545
- return ctx.operation("provider.update", { id: existingProvider.id, ...draft });
16546
- 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;
16547
17865
  }
16548
17866
  fail("unknown_command", "Unknown command.");
16549
17867
  }
16550
17868
  async function assertSupportedType(ctx, gateway, type) {
16551
- const { data: capabilities } = await ctx.publicCall("getCliCapabilities", []);
17869
+ const { data: capabilities } = await ctx.publicCall("getCliCapabilities");
16552
17870
  const supported = gateway ? capabilities.providerGateways : capabilities.providers;
16553
17871
  if (!supported.some((p) => p.type === type))
16554
17872
  fail(
@@ -16573,6 +17891,95 @@ import { join, resolve } from "node:path";
16573
17891
  import { homedir, hostname as hostname3, platform } from "node:os";
16574
17892
  import { setTimeout as delay } from "node:timers/promises";
16575
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
+ }
16576
17983
  function stateDirectory() {
16577
17984
  if (platform() === "win32")
16578
17985
  return join(
@@ -16697,7 +18104,6 @@ async function breakAbandonedLock(path) {
16697
18104
  return true;
16698
18105
  }
16699
18106
  var same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
16700
- var records = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
16701
18107
  function mergeRecords(baseline, local, disk) {
16702
18108
  const merged = { ...disk };
16703
18109
  for (const [id, value] of Object.entries(local)) {
@@ -16709,8 +18115,7 @@ function mergeRecords(baseline, local, disk) {
16709
18115
  return merged;
16710
18116
  }
16711
18117
  function mergeState(baseline, local, disk) {
16712
- if (!disk || disk.schemaVersion !== 1 || typeof disk.operations !== "object" || disk.operations === null || Array.isArray(disk.operations))
16713
- return local;
18118
+ if (!disk) return local;
16714
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;
16715
18120
  const merged = {
16716
18121
  schemaVersion: 1,
@@ -16728,13 +18133,13 @@ function mergeState(baseline, local, disk) {
16728
18133
  const mutations = mergeRecords(
16729
18134
  baseline?.mutations ?? {},
16730
18135
  local.mutations ?? {},
16731
- records(disk.mutations)
18136
+ disk.mutations ?? {}
16732
18137
  );
16733
18138
  if (Object.keys(mutations).length) merged.mutations = mutations;
16734
18139
  const installations = mergeRecords(
16735
18140
  baseline?.installations ?? {},
16736
18141
  local.installations ?? {},
16737
- records(disk.installations)
18142
+ disk.installations ?? {}
16738
18143
  );
16739
18144
  if (Object.keys(installations).length) merged.installations = installations;
16740
18145
  return merged;
@@ -16798,50 +18203,32 @@ var StateStore = class {
16798
18203
  });
16799
18204
  }
16800
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
+ */
16801
18215
  async read() {
16802
- try {
16803
- const s = await lstat(this.path);
16804
- if (!s.isFile() || s.isSymbolicLink() || posixPermissions() && (process.getuid && s.uid !== process.getuid() || (s.mode & 63) !== 0))
16805
- fail(
16806
- "unsafe_storage",
16807
- "Connection state must be owned by you with mode 0600."
16808
- );
16809
- const state = JSON.parse(await readFile2(this.path, "utf8"));
16810
- if (state.schemaVersion !== 1 || !Object.hasOwn(state, "active") || !state.operations || typeof state.operations !== "object" || Array.isArray(state.operations))
16811
- fail("invalid_state", "Unsupported or malformed connection state.");
16812
- if (state.active !== null) {
16813
- const a = state.active;
16814
- if (!a || typeof a !== "object" || typeof a.url !== "string" || typeof a.authenticated !== "boolean" || a.authenticated && typeof a.credential !== "string")
16815
- fail(
16816
- "invalid_state",
16817
- "Malformed prior connection; recovery is required."
16818
- );
16819
- origin(a.url);
16820
- }
16821
- for (const op of Object.values(state.operations)) {
16822
- if (!op || typeof op.url !== "string" || typeof op.pollToken !== "string" || typeof op.kind !== "string")
16823
- fail(
16824
- "invalid_state",
16825
- "Malformed operation state; recovery is required."
16826
- );
16827
- origin(op.url);
16828
- }
16829
- this.baseline = structuredClone(state);
16830
- return state;
16831
- } catch (e) {
16832
- if (errorCode(e) === "ENOENT") {
16833
- const empty = { schemaVersion: 1, active: null, operations: {} };
16834
- this.baseline = structuredClone(empty);
16835
- return empty;
16836
- }
16837
- 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)))
16838
18221
  fail(
16839
- "invalid_state",
16840
- "Cannot read connection state.",
16841
- "Recover the state file; it will not be treated as a new account.",
16842
- 4
18222
+ "unsafe_storage",
18223
+ "Connection state must be owned by you with mode 0600."
16843
18224
  );
16844
- }
18225
+ const state = held && await this.stored() || {
18226
+ schemaVersion: 1,
18227
+ active: null,
18228
+ operations: {}
18229
+ };
18230
+ this.baseline = structuredClone(state);
18231
+ return state;
16845
18232
  }
16846
18233
  /**
16847
18234
  * Persists the state, merged with whatever another command wrote meanwhile.
@@ -16893,13 +18280,23 @@ var StateStore = class {
16893
18280
  else state.generation = merged.generation;
16894
18281
  }
16895
18282
  }
16896
- /** 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
+ */
16897
18291
  async stored() {
18292
+ let text2;
16898
18293
  try {
16899
- return JSON.parse(await readFile2(this.path, "utf8"));
16900
- } catch {
16901
- 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);
16902
18298
  }
18299
+ return parseState(this.path, text2);
16903
18300
  }
16904
18301
  async replace(state) {
16905
18302
  const temp = join(this.directory, `.state-${randomToken()}`);
@@ -16978,15 +18375,12 @@ var EXAMPLE_GAP_NOTES = {
16978
18375
  body: `This app allows no path whose request shape is known here, so ${BODY_PLACEHOLDER} stands in for the body the provider documents.`
16979
18376
  };
16980
18377
  function examplePath(type, { gatewayRouted = false, model } = {}) {
16981
- if (type === "openai") return "v1/responses";
16982
- if (type === "anthropic") return "v1/messages";
16983
- if (type === "gemini")
16984
- return model ? `v1beta/models/${model}:generateContent` : void 0;
16985
- if (gatewayRouted) return "v1/chat/completions";
16986
- if (type === "groq") return "openai/v1/chat/completions";
16987
- if (type === "fireworks") return "inference/v1/chat/completions";
16988
- if (["deepseek", "perplexity", "bytedance"].includes(type)) return "chat/completions";
16989
- 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;
16990
18384
  }
16991
18385
  function bodyFor(path, model) {
16992
18386
  const messages = [{ role: "user", content: "Say hello." }];
@@ -16997,8 +18391,8 @@ function bodyFor(path, model) {
16997
18391
  return { contents: [{ parts: [{ text: "Say hello." }] }] };
16998
18392
  return null;
16999
18393
  }
17000
- function modelsFor(provider, policy2, prices) {
17001
- 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;
17002
18396
  return [
17003
18397
  ...Object.keys(provider.pricing ?? {}),
17004
18398
  ...Object.entries(prices[provider.type] ?? {}).filter(([, price]) => price.output !== void 0).map(([model]) => model)
@@ -17006,18 +18400,17 @@ function modelsFor(provider, policy2, prices) {
17006
18400
  }
17007
18401
  function firstRequest(routing, providers, prices) {
17008
18402
  let fallback;
17009
- for (const provider of providers) {
17010
- if (provider.status !== "active") continue;
17011
- const policy2 = routing?.providers?.[provider.slug];
17012
- if (routing?.providerMode === "selected" && !policy2) continue;
17013
- 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);
17014
18407
  const model = models[0] ?? MODEL_PLACEHOLDER;
17015
18408
  const gaps = models.length ? [] : ["model"];
17016
18409
  const fallbackPath = examplePath(provider.type, {
17017
18410
  gatewayRouted: Boolean(provider.providerGatewayId),
17018
18411
  model
17019
18412
  });
17020
- const paths = policy2?.allowed_paths?.length ? policy2.allowed_paths : fallbackPath ? [fallbackPath] : [];
18413
+ const paths = policy?.allowed_paths?.length ? policy.allowed_paths : fallbackPath ? [fallbackPath] : [];
17021
18414
  for (const entry of paths) {
17022
18415
  const path = typeof entry === "string" ? entry : entry.path;
17023
18416
  if (path.includes("*")) continue;
@@ -17066,8 +18459,16 @@ function curlSnippet(options) {
17066
18459
  ` + (example.anthropic ? " -H 'anthropic-version: 2023-06-01' \\\n" : "") + ` -d ${shellQuote(body)}
17067
18460
  `;
17068
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
+ }
17069
18469
  function swiftSnippet(options) {
17070
- const { baseUrl, appId, example, notes: notes2, authMode = ".appAttestInstall" } = options;
18470
+ const { baseUrl, appId, example, notes: notes2, authentication } = options;
18471
+ const authMode = swiftAuthMode(authentication);
17071
18472
  const target = "endpoint" in example.target ? `endpointSlug: ${JSON.stringify(example.target.endpoint)}` : `provider: ${JSON.stringify(example.target.provider)},
17072
18473
  providerPath: ${JSON.stringify(example.target.path)}`;
17073
18474
  const body = example.body ? `request.httpBody = Data(${JSON.stringify(JSON.stringify(example.body))}.utf8)` : `// Supply the provider-native JSON body.
@@ -17091,33 +18492,54 @@ print(String(decoding: data, as: UTF8.self))
17091
18492
  `;
17092
18493
  }
17093
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
+
17094
18537
  // src/apps.ts
17095
- var unlimited = () => ({
17096
- requests: { per_minute: null, per_day: null },
17097
- spending: { monthly_usd: null }
17098
- });
17099
- var policy = () => ({ allowed_paths: [], allowed_models: [] });
17100
18538
  function documentOf(app2) {
17101
18539
  return localApp({ name: app2.name, config: app2.config, status: app2.status });
17102
18540
  }
17103
18541
  function localApp(value) {
17104
- const doc = validate(AppWriteSchema, value);
17105
- const auth = doc.config.authentication;
17106
- if (auth.type === "apple_app_attest") {
17107
- if (!/^[A-Z0-9]{10}$/.test(auth.app_attest.team_id))
17108
- fail(
17109
- "invalid_input",
17110
- "App Attest team_id must contain ten uppercase letters or digits."
17111
- );
17112
- if (!/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(auth.app_attest.bundle_id))
17113
- fail(
17114
- "invalid_input",
17115
- "App Attest bundle_id must be a reverse DNS identifier."
17116
- );
17117
- }
17118
- 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))
17119
- fail("invalid_input", "User limits require an end-user identity source.");
17120
- return doc;
18542
+ return validate(AppWriteSchema, value);
17121
18543
  }
17122
18544
  async function appDocument(flags, current) {
17123
18545
  if (flags.file) {
@@ -17151,27 +18573,22 @@ async function appDocument(flags, current) {
17151
18573
  doc = localApp({
17152
18574
  name: flags.name ?? (ios && bundle ? bundle.split(".").slice(-2).join(" ") : await required2(flags, "name", "App name")),
17153
18575
  status: flags.status ?? "active",
17154
- config: {
17155
- authentication: ios ? {
17156
- type: "apple_app_attest",
17157
- end_user: { source: "app_install" },
17158
- app_attest: {
17159
- team_id: await required2(flags, "team-id", "Apple team ID"),
17160
- bundle_id: bundle,
17161
- environments: (flags["attest-environments"] ?? "production,development").split(",")
17162
- }
17163
- } : { type: "api_key" },
17164
- routing: { providers: { mode: "all" }, model_rewrites: {} },
17165
- ...ios ? {
17166
- limits: {
17167
- per_user: {
17168
- requests: { per_minute: 10, per_day: 300 },
17169
- spending: { monthly_usd: null }
17170
- },
17171
- per_app: unlimited()
17172
- }
17173
- } : {}
17174
- }
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" })
17175
18592
  });
17176
18593
  }
17177
18594
  if (flags.name) doc.name = flags.name;
@@ -17185,22 +18602,25 @@ async function appDocument(flags, current) {
17185
18602
  if (flags["team-id"]) auth.app_attest.team_id = flags["team-id"];
17186
18603
  if (flags["bundle-id"]) auth.app_attest.bundle_id = flags["bundle-id"];
17187
18604
  if (flags["attest-environments"])
17188
- auth.app_attest.environments = flags["attest-environments"].split(",").map((value) => value === "development" ? "development" : "production");
18605
+ auth.app_attest.environments = attestEnvironments(flags["attest-environments"]);
17189
18606
  }
17190
18607
  const selectedProviders = flagList(flags.provider);
17191
18608
  if (selectedProviders.length) {
17192
- const previous = doc.config.routing.providers.selected ?? {};
18609
+ const previous = selectedProviderPolicies(doc.config.routing);
17193
18610
  doc.config.routing.providers = {
17194
18611
  mode: "selected",
17195
18612
  selected: Object.fromEntries(
17196
- selectedProviders.map((slug) => [slug, previous[slug] ?? policy()])
18613
+ selectedProviders.map((slug) => [slug, previous[slug] ?? emptyPolicy()])
17197
18614
  )
17198
18615
  };
17199
18616
  }
17200
18617
  if (flags["all-providers"]) doc.config.routing.providers = { mode: "all" };
17201
18618
  return localApp(doc);
17202
18619
  }
17203
- 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) {
17204
18624
  if (!ctx.active?.credential)
17205
18625
  return {
17206
18626
  local: true,
@@ -17209,7 +18629,7 @@ async function remoteValidation(ctx, doc, id = "validation-preview") {
17209
18629
  "Provider references, pricing, and saved configuration checks require login."
17210
18630
  ]
17211
18631
  };
17212
- 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 });
17213
18633
  return { local: true, remote: true, ...data };
17214
18634
  }
17215
18635
  function mintedKey(value) {
@@ -17233,7 +18653,7 @@ async function saveKey(ctx, keyRecord, output, appId) {
17233
18653
  }
17234
18654
  let revoked = false;
17235
18655
  try {
17236
- await ctx.call("revokeAppKey", [appId, keyRecord.id]);
18656
+ await ctx.call("revokeAppKey", { params: { app: appId, key: keyRecord.id } });
17237
18657
  revoked = true;
17238
18658
  } catch {
17239
18659
  }
@@ -17263,7 +18683,7 @@ async function saveKey(ctx, keyRecord, output, appId) {
17263
18683
  }
17264
18684
  async function appCommand(ctx, command, args, flags) {
17265
18685
  const action = command.slice(4);
17266
- if (action === "list") return (await ctx.call("listApps", [])).data;
18686
+ if (action === "list") return (await ctx.call("listApps")).data;
17267
18687
  if (action === "validate") {
17268
18688
  const doc2 = localApp(await jsonFile(await required2(flags, "file")));
17269
18689
  return { definition: doc2, validation: await remoteValidation(ctx, doc2) };
@@ -17273,7 +18693,7 @@ async function appCommand(ctx, command, args, flags) {
17273
18693
  let revision = 0;
17274
18694
  const appId2 = args[0] ?? "";
17275
18695
  if (action === "update") {
17276
- const r = await ctx.call("getApp", [appId2]);
18696
+ const r = await ctx.call("getApp", { params: { app: appId2 } });
17277
18697
  current = documentOf(r.data.app);
17278
18698
  revision = r.data.app.revision;
17279
18699
  if (!revision && !flags["dry-run"])
@@ -17297,16 +18717,15 @@ async function appCommand(ctx, command, args, flags) {
17297
18717
  definition: doc2,
17298
18718
  validation: await remoteValidation(ctx, doc2, appId2 || void 0)
17299
18719
  };
17300
- 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;
17301
18721
  try {
17302
18722
  let app2;
17303
- let resolved;
17304
18723
  let configError;
17305
18724
  let key;
17306
18725
  if (action === "add") {
17307
18726
  await ctx.bootstrap();
17308
- const created = await ctx.create("createApp", [], doc2);
17309
- ({ 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);
17310
18729
  if (output) {
17311
18730
  if (created.keyMetadata) key = created.keyMetadata;
17312
18731
  else {
@@ -17324,10 +18743,11 @@ async function appCommand(ctx, command, args, flags) {
17324
18743
  }
17325
18744
  await created.complete();
17326
18745
  } else {
17327
- const updated = await ctx.call("updateApp", [appId2], {
18746
+ const updated = await ctx.call("updateApp", {
18747
+ params: { app: appId2 },
17328
18748
  body: { ...doc2, revision }
17329
18749
  });
17330
- ({ app: app2, resolved, config_error: configError } = updated.data);
18750
+ ({ app: app2, config_error: configError } = updated.data);
17331
18751
  }
17332
18752
  let snippet;
17333
18753
  try {
@@ -17342,7 +18762,6 @@ ${snippet}`;
17342
18762
  return {
17343
18763
  ...snippet ? { snippet } : {},
17344
18764
  app: app2,
17345
- resolved,
17346
18765
  config_error: configError,
17347
18766
  ...key ? { applicationKey: key } : {},
17348
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.`
@@ -17354,31 +18773,31 @@ ${snippet}`;
17354
18773
  if (action.startsWith("key ")) {
17355
18774
  const sub = action.slice(4);
17356
18775
  const appId2 = args[0] ?? "";
17357
- const { data: app2 } = await ctx.call("getApp", [appId2]);
18776
+ const { data: app2 } = await ctx.call("getApp", { params: { app: appId2 } });
17358
18777
  if (documentOf(app2.app).config.authentication.type !== "api_key")
17359
18778
  fail(
17360
18779
  "invalid_input",
17361
18780
  "Only server applications support application keys."
17362
18781
  );
17363
- if (sub === "list") return (await ctx.call("listAppKeys", [appId2])).data;
18782
+ if (sub === "list") return (await ctx.call("listAppKeys", { params: { app: appId2 } })).data;
17364
18783
  if (sub === "revoke") {
17365
18784
  const keyId = args[1] ?? "";
17366
18785
  await confirm(
17367
18786
  `Revoke key ${keyId} for app ${appId2}? Clients using it will lose access.`,
17368
18787
  flags
17369
18788
  );
17370
- return (await ctx.call("revokeAppKey", [appId2, keyId])).data;
18789
+ return (await ctx.call("revokeAppKey", { params: { app: appId2, key: keyId } })).data;
17371
18790
  }
17372
18791
  const name = await required2(flags, "name");
17373
18792
  if (!name.trim() || name.length > 100)
17374
18793
  fail("invalid_input", "Key name must be 1\u2013100 characters.");
17375
18794
  const output = await ctx.keyOutput(
17376
- operations.createAppKey.path(appId2),
18795
+ operationPath("createAppKey", { app: appId2 }),
17377
18796
  { name },
17378
18797
  flags["key-output"]
17379
18798
  );
17380
18799
  try {
17381
- const created = await ctx.create("createAppKey", [appId2], { name });
18800
+ const created = await ctx.create("createAppKey", { params: { app: appId2 }, body: { name } });
17382
18801
  let applicationKey = created.keyMetadata;
17383
18802
  if (!applicationKey) {
17384
18803
  const minted = mintedKey(created.data);
@@ -17399,7 +18818,7 @@ ${snippet}`;
17399
18818
  }
17400
18819
  }
17401
18820
  const appId = args[0] ?? "";
17402
- const { data } = await ctx.call("getApp", [appId]);
18821
+ const { data } = await ctx.call("getApp", { params: { app: appId } });
17403
18822
  if (action === "show") return data;
17404
18823
  const doc = documentOf(data.app);
17405
18824
  if (action === "remove") {
@@ -17407,13 +18826,13 @@ ${snippet}`;
17407
18826
  `Delete app ${data.app.name} (${appId})? Its keys, users and authentication state will be removed and clients will lose access.`,
17408
18827
  flags
17409
18828
  );
17410
- return (await ctx.call("deleteApp", [appId])).data;
18829
+ return (await ctx.call("deleteApp", { params: { app: appId }, query: { confirm: appId } })).data;
17411
18830
  }
17412
18831
  if (action === "check") {
17413
18832
  const validation = await remoteValidation(ctx, doc, appId);
17414
- const { data: providers } = await ctx.call("listProviders", []);
17415
- const selected = doc.config.routing.providers.mode === "all" ? providers.providers : providers.providers.filter(
17416
- (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
17417
18836
  );
17418
18837
  return {
17419
18838
  appId,
@@ -17424,7 +18843,7 @@ ${snippet}`;
17424
18843
  slug: p.slug,
17425
18844
  status: p.status
17426
18845
  })),
17427
- 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,
17428
18847
  limitations: [
17429
18848
  "No inference was sent.",
17430
18849
  "Physical device attestation, issuer login, subscription entitlement and upstream credentials were not exercised."
@@ -17445,7 +18864,7 @@ ${snippet}`;
17445
18864
  const notes2 = [];
17446
18865
  let example;
17447
18866
  if (flags.endpoint) {
17448
- const endpoint = doc.config.endpoints?.[flags.endpoint];
18867
+ const endpoint = doc.config.endpoints[flags.endpoint];
17449
18868
  if (!endpoint)
17450
18869
  fail("endpoint_not_found", "Choose an existing named endpoint.");
17451
18870
  const responses = endpoint.api_style === "responses";
@@ -17456,14 +18875,9 @@ ${snippet}`;
17456
18875
  gaps: responses ? [] : ["body"]
17457
18876
  };
17458
18877
  } else {
17459
- const routing = {
17460
- providerMode: doc.config.routing.providers.mode,
17461
- providers: doc.config.routing.providers.selected
17462
- };
17463
- const { data: all } = await ctx.call("listProviders", []);
17464
- const reachable = all.providers.filter(
17465
- (p) => p.status === "active" && (routing.providerMode === "all" || Object.hasOwn(routing.providers ?? {}, p.slug))
17466
- );
18878
+ const routing = doc.config.routing;
18879
+ const { data: all } = await ctx.call("listProviders");
18880
+ const reachable = reachableProviders(routing, all.providers);
17467
18881
  const requested = typeof flags.provider === "string" ? flags.provider : void 0;
17468
18882
  if (requested && !reachable.some((p) => p.slug === requested))
17469
18883
  fail(
@@ -17471,7 +18885,7 @@ ${snippet}`;
17471
18885
  all.providers.some((p) => p.slug === requested) ? "That provider is disabled or outside this app's proxy policy." : "No provider has that slug.",
17472
18886
  "Run agw provider list for the slugs, and agw app show <id> for the policy."
17473
18887
  );
17474
- const { data: catalog } = await ctx.call("listModelPrices", []);
18888
+ const { data: catalog } = await ctx.call("listModelPrices");
17475
18889
  const providers = requested ? reachable.filter((p) => p.slug === requested) : reachable;
17476
18890
  example = firstRequest(routing, providers, catalog.prices);
17477
18891
  if (!requested && reachable.length > 1)
@@ -17481,17 +18895,12 @@ ${snippet}`;
17481
18895
  const clientUrl = ctx.active?.deployment?.apiUrl ?? ctx.url;
17482
18896
  let snippet;
17483
18897
  if (ios) {
17484
- const endUser = doc.config.authentication.type === "apple_app_attest" ? doc.config.authentication.end_user : void 0;
17485
- const issuer = endUser?.source === "issuer";
17486
- if (issuer)
17487
- notes2.push(
17488
- "Replace yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh) with your configured issuer integration."
17489
- );
18898
+ if (swiftSignsInUsers(doc.config.authentication)) notes2.push(ISSUER_TOKEN_NOTE);
17490
18899
  snippet = swiftSnippet({
17491
18900
  baseUrl: clientUrl,
17492
18901
  appId,
17493
18902
  example,
17494
- 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,
17495
18904
  notes: [
17496
18905
  "Swift package: https://github.com/maxceem/app-ai-gateway-swift (from: 1.0.0)",
17497
18906
  "Enable App Attest and test on a supported physical device.",
@@ -17537,6 +18946,7 @@ var RECORDED_RESULT = {
17537
18946
  createProviderGateway: ProviderGatewayResponseSchema
17538
18947
  };
17539
18948
  var recordedResultFor = RECORDED_RESULT;
18949
+ var pathFor = operationPath;
17540
18950
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
17541
18951
  var RECEIPT_BOUND_STATUS = /* @__PURE__ */ new Set([409, 410]);
17542
18952
  function definitiveRefusal(error51) {
@@ -17574,13 +18984,12 @@ var Context = class {
17574
18984
  * this is what keeps an undocumented server field — or anything an unexpected
17575
18985
  * response carries — out of the values the commands go on to print.
17576
18986
  */
17577
- async publicCall(name, params, { body, key, url: url2, headers } = {}) {
17578
- const descriptor = operations[name];
18987
+ async publicCall(name, { params, query, body, key, url: url2, headers } = {}) {
17579
18988
  const wire = await this.transport.request(
17580
18989
  url2 ?? this.url,
17581
- descriptor.path(...params),
18990
+ pathFor(name, params, query),
17582
18991
  {
17583
- method: descriptor.method,
18992
+ method: CATALOG[name].method,
17584
18993
  ...body === void 0 ? {} : { body },
17585
18994
  ...key === void 0 ? {} : { key },
17586
18995
  ...headers === void 0 ? {} : { headers }
@@ -17589,7 +18998,7 @@ var Context = class {
17589
18998
  return { data: this.parse(name, wire.data) };
17590
18999
  }
17591
19000
  parse(name, data) {
17592
- const parsed = responseSchemaFor[name].safeParse(data);
19001
+ const parsed = CATALOG[name].response.safeParse(data);
17593
19002
  if (!parsed.success)
17594
19003
  fail(
17595
19004
  "invalid_response",
@@ -17612,11 +19021,8 @@ var Context = class {
17612
19021
  return parsed.data;
17613
19022
  }
17614
19023
  /** The same call, with the connection's management credential attached. */
17615
- async call(name, params, options = {}) {
17616
- return this.publicCall(name, params, {
17617
- ...options,
17618
- key: options.key ?? this.credential(name)
17619
- });
19024
+ async call(name, options = {}) {
19025
+ return this.publicCall(name, { ...options, key: options.key ?? this.credential(name) });
17620
19026
  }
17621
19027
  /**
17622
19028
  * The current management credential, or the refusal that names the way back.
@@ -17699,7 +19105,7 @@ var Context = class {
17699
19105
  * The receipt in protected state holds the original server answer until the
17700
19106
  * command has printed it; `complete` is what releases it.
17701
19107
  */
17702
- async create(name, params, body) {
19108
+ async create(name, { params, body }) {
17703
19109
  if (!this.active?.credential)
17704
19110
  fail(
17705
19111
  "login_required",
@@ -17707,8 +19113,7 @@ var Context = class {
17707
19113
  "Run agw account login.",
17708
19114
  4
17709
19115
  );
17710
- const descriptor = operations[name];
17711
- const path = descriptor.path(...params);
19116
+ const path = pathFor(name, params);
17712
19117
  const mutation2 = await this.prepareCreate(path, body);
17713
19118
  if (mutation2.accountId === null) {
17714
19119
  mutation2.accountId = this.active.account?.id ?? null;
@@ -17747,7 +19152,7 @@ var Context = class {
17747
19152
  const keyRecord = recoveredKey(name, recovered);
17748
19153
  if (keyRecord) {
17749
19154
  const appId = recoveredAppId(name, recovered, params);
17750
- const { data: existing } = await this.call("listAppKeys", [appId]);
19155
+ const { data: existing } = await this.call("listAppKeys", { params: { app: appId } });
17751
19156
  if (!existing.keys.some(
17752
19157
  (key) => key.id === keyRecord.id && key.status === "active"
17753
19158
  )) {
@@ -17765,7 +19170,8 @@ var Context = class {
17765
19170
  }
17766
19171
  data = recovered;
17767
19172
  } else {
17768
- const response = await this.call(name, params, {
19173
+ const response = await this.call(name, {
19174
+ ...params === void 0 ? {} : { params },
17769
19175
  body,
17770
19176
  headers: {
17771
19177
  "Idempotency-Key": mutation2.id,
@@ -17850,11 +19256,10 @@ var Context = class {
17850
19256
  4
17851
19257
  );
17852
19258
  const { idempotencyKey, pollToken } = reservation;
17853
- const { data } = await this.publicCall(
17854
- "bootstrapCliAccount",
17855
- [],
17856
- { body: { idempotencyKey, pollToken }, url: CLOUD }
17857
- ).catch(async (error51) => {
19259
+ const { data } = await this.publicCall("bootstrapCliAccount", {
19260
+ body: { idempotencyKey, pollToken },
19261
+ url: CLOUD
19262
+ }).catch(async (error51) => {
17858
19263
  if (definitiveRefusal(error51)) {
17859
19264
  delete this.state.bootstrap;
17860
19265
  await this.save().catch(() => {
@@ -17865,7 +19270,7 @@ var Context = class {
17865
19270
  await this.select(CLOUD, data);
17866
19271
  this.onboarding = {
17867
19272
  account: data.account,
17868
- trial: data.trial,
19273
+ unclaimedAccess: data.unclaimedAccess,
17869
19274
  deployment: data.deployment
17870
19275
  };
17871
19276
  delete this.state.bootstrap;
@@ -17901,7 +19306,7 @@ var Context = class {
17901
19306
  const token = await secret(this.flags, "Management API key");
17902
19307
  if (!token)
17903
19308
  fail("input_required", "A management API key is required.", "Supply it with --key-stdin.");
17904
- const { data } = await this.publicCall("getCliAccount", [], { key: token, url: url2 });
19309
+ const { data } = await this.publicCall("getCliAccount", { key: token, url: url2 });
17905
19310
  await this.select(url2, { ...data, credential: { token } });
17906
19311
  return { connected: true, ...data };
17907
19312
  }
@@ -17929,12 +19334,14 @@ var Context = class {
17929
19334
  };
17930
19335
  await this.save();
17931
19336
  }
17932
- const operation3 = this.state.operations[localId];
17933
- const { data: capabilities } = await this.publicCall("getCliCapabilities", [], {
19337
+ const operation2 = this.state.operations[localId];
19338
+ const { data: capabilities } = await this.publicCall("getCliCapabilities", {
17934
19339
  url: target
17935
19340
  });
17936
- const { data } = await this.publicCall("createCliOperation", [], {
17937
- 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 },
17938
19345
  url: target,
17939
19346
  ...authenticated ? { key: this.active?.credential } : {}
17940
19347
  });
@@ -17947,7 +19354,7 @@ var Context = class {
17947
19354
  "invalid_response",
17948
19355
  "The handoff URL is not on this deployment\u2019s trusted console origin."
17949
19356
  );
17950
- this.state.operations[data.id] = { ...operation3, phase: "pending" };
19357
+ this.state.operations[data.id] = { ...operation2, phase: "pending" };
17951
19358
  delete this.state.operations[localId];
17952
19359
  await this.save();
17953
19360
  if (!this.flags["no-open"]) await openBrowser(data.url);
@@ -17961,34 +19368,35 @@ Operation: ${data.id}
17961
19368
  return this.wait(data.id, 300);
17962
19369
  }
17963
19370
  async poll(id) {
17964
- const operation3 = this.state.operations[id];
17965
- if (!operation3)
19371
+ const operation2 = this.state.operations[id];
19372
+ if (!operation2)
17966
19373
  fail(
17967
19374
  "operation_unknown",
17968
19375
  "This operation has no locally stored polling authorization.",
17969
19376
  "Resume it from the machine that initiated the operation.",
17970
19377
  4
17971
19378
  );
17972
- if (this.active && this.active.url !== operation3.url)
19379
+ if (this.active && this.active.url !== operation2.url)
17973
19380
  fail(
17974
19381
  "operation_context",
17975
19382
  "This operation belongs to another selected deployment.",
17976
19383
  "Reconnect explicitly to the operation\u2019s originating deployment.",
17977
19384
  4
17978
19385
  );
17979
- const { data } = await this.publicCall("pollCliOperation", [id], {
17980
- key: operation3.pollToken,
17981
- url: operation3.url
19386
+ const { data } = await this.publicCall("pollCliOperation", {
19387
+ params: { id },
19388
+ key: operation2.pollToken,
19389
+ url: operation2.url
17982
19390
  });
17983
- if (data.state === "completed" && !operation3.completed && operation3.kind === "claim") {
17984
- 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)
17985
19393
  fail(
17986
19394
  "operation_context",
17987
19395
  "The active connection changed after this operation began.",
17988
19396
  "Start a new login for the intended connection.",
17989
19397
  4
17990
19398
  );
17991
- operation3.completed = true;
19399
+ operation2.completed = true;
17992
19400
  if (this.active && data.account) this.active.account = data.account;
17993
19401
  await this.save();
17994
19402
  }
@@ -18028,7 +19436,7 @@ function recoveredKey(name, data) {
18028
19436
  }
18029
19437
  function recoveredAppId(name, data, params) {
18030
19438
  if (name === "createApp") return data.app.id;
18031
- return String(params[0]);
19439
+ return String(params?.app);
18032
19440
  }
18033
19441
  async function openBrowser(url2) {
18034
19442
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
@@ -18049,14 +19457,9 @@ async function openBrowser(url2) {
18049
19457
  }
18050
19458
 
18051
19459
  // src/deployment.ts
18052
- import { spawn as spawn2 } from "node:child_process";
18053
- import { stripVTControlCharacters } from "node:util";
18054
- import { createRequire as createRequire2 } from "node:module";
18055
19460
  import { randomUUID } from "node:crypto";
18056
- import { cp, writeFile as writeFile2, unlink as unlink2 } from "node:fs/promises";
18057
- import { tmpdir } from "node:os";
18058
- import { join as join3 } from "node:path";
18059
- 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";
18060
19463
 
18061
19464
  // src/release.ts
18062
19465
  import { readFile as readFile3, lstat as lstat2, mkdir as mkdir2, rename as rename2, rm, writeFile } from "node:fs/promises";
@@ -18298,7 +19701,12 @@ async function release(requested = VERSION, base = dirname(fileURLToPath(import.
18298
19701
  };
18299
19702
  }
18300
19703
 
18301
- // 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";
18302
19710
  var require2 = createRequire2(import.meta.url);
18303
19711
  var OUTPUT_TAIL = 5e3;
18304
19712
  function wranglerFailure(args, code, output, redact = []) {
@@ -18519,11 +19927,57 @@ async function resultOf(cf, path, options) {
18519
19927
  if (!data) fail("cloudflare_error", "Cloudflare returned no result.");
18520
19928
  return data.result;
18521
19929
  }
18522
- function hostname4(value) {
18523
- const u = origin("https://" + value);
18524
- if (new URL(u).hostname !== value || !value.includes(".") || value.includes(":"))
18525
- fail("invalid_input", "Supply a hostname without protocol, path or port.");
18526
- 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
+ };
18527
19981
  }
18528
19982
  async function configFile(ctx, journal, artifact) {
18529
19983
  const directory = join3(ctx.store.directory, "deployments", journal.id);
@@ -18560,33 +20014,10 @@ async function configFile(ctx, journal, artifact) {
18560
20014
  await writeFile2(path, JSON.stringify(config2, null, 2), { mode: 384 });
18561
20015
  return { path, directory, config: config2 };
18562
20016
  }
18563
- function transient(error51) {
18564
- if (!(error51 instanceof CliError)) return false;
18565
- if (error51.code === "connection_failed") return true;
18566
- const status2 = error51.details?.status;
18567
- return typeof status2 === "number" && status2 >= 500;
18568
- }
18569
- async function whileWarming(run, sleep = delay3, report = (line) => process.stderr.write(line + "\n")) {
18570
- let wait = 1e3;
18571
- for (let attempt = 0; ; attempt++) {
18572
- try {
18573
- return await run();
18574
- } catch (error51) {
18575
- if (attempt === 5 || !transient(error51)) throw error51;
18576
- const { code, details } = error51;
18577
- const status2 = details?.status;
18578
- report(
18579
- `Deployment is still coming up (${code}${status2 === void 0 ? "" : ` ${status2}`}); retrying in ${wait / 1e3}s.`
18580
- );
18581
- await sleep(wait);
18582
- wait = Math.min(wait * 2, 8e3);
18583
- }
18584
- }
18585
- }
18586
20017
  async function verifyDeployment(ctx, url2, id) {
18587
20018
  for (let attempt = 0; attempt < 20; attempt++) {
18588
20019
  try {
18589
- const { data } = await ctx.publicCall("getCliCapabilities", [], { url: url2 });
20020
+ const { data } = await ctx.publicCall("getCliCapabilities", { url: url2 });
18590
20021
  if (data.deployment.id !== id)
18591
20022
  fail(
18592
20023
  "deployment_identity_mismatch",
@@ -18596,7 +20027,7 @@ async function verifyDeployment(ctx, url2, id) {
18596
20027
  } catch (error51) {
18597
20028
  if (error51 instanceof CliError && error51.code === "deployment_identity_mismatch") throw error51;
18598
20029
  if (attempt === 19) throw error51;
18599
- await delay3(1500);
20030
+ await delay4(1500);
18600
20031
  }
18601
20032
  }
18602
20033
  fail("deployment_identity_mismatch", "The endpoint never identified the expected deployment.");
@@ -18620,10 +20051,10 @@ async function checkDomain(cf, journal, domain2) {
18620
20051
  if (current && current.service !== journal.name)
18621
20052
  fail("domain_in_use", "The hostname is attached to a different Worker.");
18622
20053
  if (!current) {
18623
- const records2 = await cf.all(
20054
+ const records = await cf.all(
18624
20055
  "/zones/" + zone.id + "/dns_records?name=" + encodeURIComponent(domain2)
18625
20056
  );
18626
- if (records2.length)
20057
+ if (records.length)
18627
20058
  fail("domain_in_use", "The hostname has existing DNS records; no records were changed.");
18628
20059
  }
18629
20060
  return zone;
@@ -18653,9 +20084,10 @@ async function attachDomain(ctx, cf, journal, domain2, artifact, dryRun) {
18653
20084
  zone_id: zone.id
18654
20085
  }
18655
20086
  });
18656
- journal.domains = [.../* @__PURE__ */ new Set([...journal.domains ?? [], domain2])];
18657
- journal.vars = { ...journal.vars, CLI_CONSOLE_ORIGIN: "https://" + domain2 };
18658
- 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
+ });
18659
20091
  const file2 = await configFile(ctx, journal, artifact);
18660
20092
  await cf.run(["deploy", "--config", file2.path, "--keep-vars"], {
18661
20093
  cwd: file2.directory
@@ -18722,14 +20154,11 @@ async function retireInstallationSecrets(ctx, journal, flags) {
18722
20154
  if (flags["dry-run"] || journal.phase !== "ready" || !journal.secrets) return;
18723
20155
  const kek = journal.secrets["SECRET_VAULT_LOCAL_KEK_V1"];
18724
20156
  if (kek) await ctx.store.vaultKey(journal.id, kek);
18725
- delete journal.secrets;
18726
- await ctx.save();
20157
+ await advance(ctx, journal, journal.phase, { secrets: void 0 });
18727
20158
  }
18728
- async function matchExisting(ctx, cf, flags) {
18729
- if (ctx.active?.deployment?.mode !== "self_hosted")
18730
- fail("not_self_hosted", "This command requires a selected self-hosted deployment.");
18731
- const installations = ctx.state.installations ??= {};
18732
- 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];
18733
20162
  if (journal) {
18734
20163
  const settings2 = await resultOf(
18735
20164
  cf,
@@ -18740,7 +20169,7 @@ async function matchExisting(ctx, cf, flags) {
18740
20169
  "deployment_identity_mismatch",
18741
20170
  "The Cloudflare Worker no longer matches this installation journal."
18742
20171
  );
18743
- const capabilities2 = await verifyDeployment(ctx, ctx.url, journal.id);
20172
+ const capabilities2 = await verifyDeployment(ctx, target.url, journal.id);
18744
20173
  const inventory2 = await currentInventory(
18745
20174
  cf,
18746
20175
  journal.accountId,
@@ -18770,7 +20199,7 @@ async function matchExisting(ctx, cf, flags) {
18770
20199
  const deploymentId = settings.bindings?.find(
18771
20200
  (b) => b.name === "DEPLOYMENT_ID" && b.type === "plain_text"
18772
20201
  )?.text;
18773
- if (deploymentId !== ctx.active.deployment.id)
20202
+ if (deploymentId !== target.deploymentId)
18774
20203
  fail(
18775
20204
  "deployment_identity_mismatch",
18776
20205
  "Cloudflare Worker identity does not match the selected deployment."
@@ -18782,7 +20211,9 @@ async function matchExisting(ctx, cf, flags) {
18782
20211
  `Bind deployment ${deploymentId} to Cloudflare account ${accountId}, Worker ${name} and D1 ${db.id}?`,
18783
20212
  flags
18784
20213
  );
18785
- const { data: capabilities } = await ctx.publicCall("getCliCapabilities", []);
20214
+ const { data: capabilities } = await ctx.publicCall("getCliCapabilities", {
20215
+ url: target.url
20216
+ });
18786
20217
  const inventory = await currentInventory(cf, accountId, name, settings);
18787
20218
  journal = {
18788
20219
  id: deploymentId,
@@ -18799,52 +20230,170 @@ async function matchExisting(ctx, cf, flags) {
18799
20230
  }
18800
20231
  return journal;
18801
20232
  }
18802
- 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) {
18803
20242
  const artifact = await loadRelease(flags.version, void 0, {
18804
20243
  archive: flags["release-archive"]
18805
20244
  });
18806
20245
  await cf.authenticate(flags);
18807
- if (command !== "deployment setup") {
18808
- const journal2 = await matchExisting(ctx, cf, flags);
18809
- if (command === "deployment domain") {
18810
- const domain3 = hostname4(flags.hostname ?? await prompt("Custom hostname", flags));
18811
- if (!flags["dry-run"])
18812
- await confirm(
18813
- `Attach ${domain3} to ${journal2.name} in Cloudflare account ${journal2.accountId}?`,
18814
- flags
18815
- );
18816
- return attachDomain(ctx, cf, journal2, domain3, artifact, Boolean(flags["dry-run"]));
18817
- }
18818
- if (!artifact.manifest.upgradeFrom.includes(journal2.version))
18819
- fail(
18820
- "unsupported_upgrade",
18821
- "This release cannot upgrade the selected database schema.",
18822
- "Use a supported migration release; no data was changed."
18823
- );
18824
- const plan2 = {
18825
- worker: journal2.name,
18826
- accountId: journal2.accountId,
18827
- databaseId: journal2.databaseId,
18828
- fromVersion: journal2.version,
18829
- toVersion: artifact.manifest.version
18830
- };
18831
- 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"])
18832
20253
  await confirm(
18833
- `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}?`,
18834
20255
  flags
18835
20256
  );
18836
- const file2 = await configFile(ctx, journal2, artifact);
18837
- await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
18838
- 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)
18839
20371
  });
18840
- await cf.run(["deploy", "--config", file2.path, "--keep-vars"], {
18841
- cwd: file2.directory
20372
+ } finally {
20373
+ await unlink2(secretPath).catch(() => {
18842
20374
  });
18843
- await verifyDeployment(ctx, ctx.url, journal2.id);
18844
- journal2.version = artifact.manifest.version;
18845
- await ctx.save();
18846
- return { updated: true, ...plan2 };
18847
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);
18848
20397
  const name = flags.name ?? await prompt("Worker name", flags, { defaultValue: "app-ai-gateway" });
18849
20398
  if (!/^[a-z][a-z0-9-]{0,62}$/.test(name))
18850
20399
  fail("invalid_input", "Worker name must be a lowercase slug of at most 63 characters.");
@@ -18852,58 +20401,29 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
18852
20401
  const accountId = await cf.account(flags);
18853
20402
  const workers = await cf.all("/accounts/" + accountId + "/workers/scripts");
18854
20403
  const installations = ctx.state.installations ??= {};
18855
- let journal = Object.values(installations).find(
20404
+ const recorded = Object.values(installations).find(
18856
20405
  (j) => j.accountId === accountId && j.name === name
18857
20406
  );
18858
20407
  const exists = workers.some((w) => w.id === name);
18859
- if (exists && !journal)
20408
+ if (exists && !recorded)
18860
20409
  fail(
18861
20410
  "worker_name_collision",
18862
20411
  "A Worker already uses this name.",
18863
20412
  "Choose a different --name; setup never takes over an existing installation."
18864
20413
  );
18865
- if (exists && journal) {
20414
+ if (exists && recorded) {
18866
20415
  const settings = await resultOf(
18867
20416
  cf,
18868
20417
  `/accounts/${accountId}/workers/scripts/${name}/settings`
18869
20418
  );
18870
- 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)
18871
20420
  fail(
18872
20421
  "deployment_identity_mismatch",
18873
20422
  "Existing Worker does not match the saved installation journal."
18874
20423
  );
18875
20424
  }
18876
- if (journal?.phase === "ready") {
18877
- await retireInstallationSecrets(ctx, journal, flags);
18878
- if (journal.pendingDomain) {
18879
- const inventoryContext = Object.create(ctx);
18880
- Object.defineProperty(inventoryContext, "active", {
18881
- value: { deployment: { id: journal.id, mode: "self_hosted" } }
18882
- });
18883
- Object.defineProperty(inventoryContext, "url", {
18884
- value: journal.url ?? ctx.url
18885
- });
18886
- journal = await matchExisting(inventoryContext, cf, flags);
18887
- if (flags["dry-run"])
18888
- return {
18889
- installed: true,
18890
- pendingDomain: journal.pendingDomain,
18891
- dryRun: true
18892
- };
18893
- await attachDomain(ctx, cf, journal, journal.pendingDomain, artifact, false);
18894
- delete journal.pendingDomain;
18895
- await ctx.save();
18896
- }
18897
- await verifyDeployment(ctx, journal.url ?? ctx.url, journal.id);
18898
- return {
18899
- installed: true,
18900
- deploymentId: journal.id,
18901
- name,
18902
- accountId,
18903
- url: journal.url ?? ctx.url,
18904
- note: "This installation is already complete. Use deployment connect to authenticate or deployment update to change its release."
18905
- };
18906
- }
20425
+ if (recorded?.phase === "ready")
20426
+ return completeInstallation(ctx, cf, flags, recorded, artifact, installations);
18907
20427
  const subdomain = (await resultOf(cf, "/accounts/" + accountId + "/workers/subdomain")).subdomain;
18908
20428
  if (!subdomain)
18909
20429
  fail(
@@ -18925,81 +20445,13 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
18925
20445
  `Install ${name} in Cloudflare account ${accountId} at ${url2}${domain2 ? " with " + domain2 : ""}?`,
18926
20446
  flags
18927
20447
  );
18928
- if (!journal) {
18929
- const id = randomUUID();
18930
- journal = {
18931
- id,
18932
- name,
18933
- accountId,
18934
- databaseName: name + "-" + id.slice(0, 8),
18935
- version: artifact.manifest.version,
18936
- phase: "prepared",
18937
- url: url2,
18938
- vars: { CLI_CONSOLE_ORIGIN: url2 },
18939
- bootstrap: { idempotencyKey: randomToken(), pollToken: randomToken() },
18940
- // Held only until the Worker has them and the install is ready; the vault
18941
- // key is the one that outlives setup, and it lives in its own file.
18942
- secrets: {
18943
- JWT_SECRET: randomToken(),
18944
- BETTER_AUTH_SECRET: randomToken()
18945
- }
18946
- };
18947
- installations[id] = journal;
18948
- await ctx.save();
18949
- }
20448
+ const journal = recorded ?? await prepare(
20449
+ ctx,
20450
+ installations,
20451
+ newInstallation(name, accountId, url2, artifact.manifest.version)
20452
+ );
18950
20453
  try {
18951
- if (!journal.databaseId) {
18952
- const databases = await cf.all("/accounts/" + accountId + "/d1/database");
18953
- let database = databases.find((d) => d.name === journal.databaseName);
18954
- if (!database)
18955
- database = await resultOf(cf, "/accounts/" + accountId + "/d1/database", {
18956
- method: "POST",
18957
- body: { name: journal.databaseName }
18958
- });
18959
- journal.databaseId = database.uuid;
18960
- await ctx.save();
18961
- }
18962
- const file2 = await configFile(ctx, journal, artifact);
18963
- await cf.run(["d1", "migrations", "apply", "DB", "--remote", "--config", file2.path], {
18964
- cwd: file2.directory
18965
- });
18966
- const secrets = {
18967
- ...journal.secrets,
18968
- SECRET_VAULT_LOCAL_KEK_V1: await ctx.store.vaultKey(journal.id)
18969
- };
18970
- const secretPath = join3(file2.directory, "secrets.json");
18971
- await writeFile2(secretPath, JSON.stringify(secrets), {
18972
- mode: 384
18973
- });
18974
- try {
18975
- await cf.run(["deploy", "--config", file2.path, "--secrets-file", secretPath], {
18976
- cwd: file2.directory,
18977
- redact: Object.values(secrets)
18978
- });
18979
- } finally {
18980
- await unlink2(secretPath).catch(() => {
18981
- });
18982
- }
18983
- journal.phase = "deployed";
18984
- await ctx.save();
18985
- await verifyDeployment(ctx, url2, journal.id);
18986
- const proofs = {
18987
- idempotencyKey: journal.bootstrap.idempotencyKey,
18988
- pollToken: journal.bootstrap.pollToken
18989
- };
18990
- const { data } = await whileWarming(
18991
- () => ctx.publicCall("bootstrapCliAccount", [], { body: proofs, url: url2 })
18992
- );
18993
- await ctx.select(url2, data);
18994
- journal.phase = "ready";
18995
- if (domain2) journal.pendingDomain = domain2;
18996
- delete journal.secrets;
18997
- await ctx.save();
18998
- if (domain2) {
18999
- await attachDomain(ctx, cf, journal, domain2, artifact, false);
19000
- delete journal.pendingDomain;
19001
- await ctx.save();
19002
- }
20454
+ await install(ctx, cf, journal, artifact, url2, domain2);
19003
20455
  return {
19004
20456
  installed: true,
19005
20457
  ...plan,
@@ -19018,6 +20470,12 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
19018
20470
  throw error51;
19019
20471
  }
19020
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
+ }
19021
20479
 
19022
20480
  // src/style.ts
19023
20481
  import { Writable } from "node:stream";
@@ -19107,8 +20565,8 @@ function humanResult(command, result, context, style2 = plain) {
19107
20565
  return style2.code(result.snippet, language).join("\n") + "\n";
19108
20566
  }
19109
20567
  const lines = render(command, result, context, style2);
19110
- if ("trial" in result && result.trial)
19111
- lines.push(`Free access ends: ${result.trial.endsAt}`);
20568
+ if ("unclaimedAccess" in result && result.unclaimedAccess)
20569
+ lines.push(`Free access ends: ${result.unclaimedAccess.endsAt}`);
19112
20570
  if ("account" in result && result.account?.expiresAt)
19113
20571
  lines.push(`Recovery deadline: ${result.account.expiresAt}`);
19114
20572
  return lines.join("\n") + "\n";
@@ -19156,7 +20614,7 @@ function render(command, result, context, style2) {
19156
20614
  return usage(command, result, style2);
19157
20615
  case "operation status":
19158
20616
  case "operation wait":
19159
- return operation2(result, style2);
20617
+ return operation(result, style2);
19160
20618
  case "app add":
19161
20619
  case "app update":
19162
20620
  case "app validate":
@@ -19202,7 +20660,7 @@ function status(command, result, context, style2) {
19202
20660
  style2
19203
20661
  )
19204
20662
  ];
19205
- if ("state" in result) return [...did, ...operation2(result, style2)];
20663
+ if ("state" in result) return [...did, ...operation(result, style2)];
19206
20664
  return [...did, style2.json(result)];
19207
20665
  }
19208
20666
  function list(command, result, style2) {
@@ -19438,7 +20896,7 @@ function usage(command, result, style2) {
19438
20896
  );
19439
20897
  return [style2.json(result)];
19440
20898
  }
19441
- function operation2(result, style2) {
20899
+ function operation(result, style2) {
19442
20900
  if (!("state" in result)) return [style2.json(result)];
19443
20901
  const stored = "result" in result ? result.result : void 0;
19444
20902
  return kv(
@@ -19484,7 +20942,9 @@ function app(command, result, context, style2) {
19484
20942
  ["Gateway", context.url],
19485
20943
  ["Status", result.definition.status],
19486
20944
  ["Authentication", result.definition.config.authentication.type],
19487
- ["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]
19488
20948
  ],
19489
20949
  style2
19490
20950
  ),
@@ -19710,8 +21170,9 @@ var Transport = class {
19710
21170
  };
19711
21171
 
19712
21172
  // src/usage.ts
21173
+ var CLI_BREAKDOWNS = ["provider", "model", "status"];
19713
21174
  function month(value) {
19714
- if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(value))
21175
+ if (!MONTH_PATTERN.test(value))
19715
21176
  fail(
19716
21177
  "invalid_input",
19717
21178
  "Month must use YYYY-MM with a valid calendar month."
@@ -19734,11 +21195,11 @@ function positive(value, max = 3600) {
19734
21195
  async function usageCommand(ctx, command, flags) {
19735
21196
  if (command === "usage show") {
19736
21197
  const m = month(flags.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7));
19737
- 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;
19738
21199
  }
19739
21200
  const app2 = await required2(flags, "app");
19740
- const by = flags.by ?? "model";
19741
- if (!["provider", "model", "status"].includes(by))
21201
+ const by = CLI_BREAKDOWNS.find((dimension) => dimension === (flags.by ?? "model"));
21202
+ if (by === void 0)
19742
21203
  fail("invalid_input", "--by must be provider, model or status.");
19743
21204
  if (Boolean(flags.from) !== Boolean(flags.to))
19744
21205
  fail("invalid_input", "Supply both --from and --to or neither.");
@@ -19748,7 +21209,10 @@ async function usageCommand(ctx, command, flags) {
19748
21209
  if (from > to) fail("invalid_input", "--from must not be later than --to.");
19749
21210
  const limit = positive(flags.limit ?? 50, 200);
19750
21211
  return {
19751
- ...(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,
19752
21216
  coverage: {
19753
21217
  from,
19754
21218
  to,
@@ -19766,7 +21230,7 @@ async function execute(parsed, ctx) {
19766
21230
  if (command.startsWith("usage ")) return usageCommand(ctx, command, flags);
19767
21231
  if (command.startsWith("operation "))
19768
21232
  return command === "operation wait" ? ctx.wait(args[0] ?? "", positive(flags.timeout ?? 300)) : ctx.poll(args[0] ?? "");
19769
- if (command === "account status") return (await ctx.call("getCliAccount", [])).data;
21233
+ if (command === "account status") return (await ctx.call("getCliAccount")).data;
19770
21234
  if (command === "account login") return ctx.login();
19771
21235
  if (command === "account claim") return ctx.operation("claim", {});
19772
21236
  if (command === "account logout") {
@@ -19789,7 +21253,7 @@ async function execute(parsed, ctx) {
19789
21253
  return ctx.login(url2);
19790
21254
  }
19791
21255
  if (command === "deployment status") {
19792
- const { data } = await ctx.publicCall("getCliCapabilities", []);
21256
+ const { data } = await ctx.publicCall("getCliCapabilities");
19793
21257
  return {
19794
21258
  url: ctx.url,
19795
21259
  authenticated: Boolean(ctx.active?.credential),