@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.
- package/dist/agw.mjs +2275 -811
- 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.
|
|
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((
|
|
1422
|
-
for (const
|
|
1423
|
-
if (typeof
|
|
1424
|
-
segs.push(`[${
|
|
1425
|
-
else if (typeof
|
|
1426
|
-
segs.push(`[${JSON.stringify(String(
|
|
1427
|
-
else if (/[^\w$]/.test(
|
|
1428
|
-
segs.push(`[${JSON.stringify(
|
|
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(
|
|
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
|
|
14557
|
-
|
|
14558
|
-
|
|
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
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
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/
|
|
14608
|
-
|
|
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/
|
|
14978
|
+
// ../src/contracts/schemas.ts
|
|
14718
14979
|
var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
|
|
14719
14980
|
var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
|
|
14720
|
-
|
|
14721
|
-
|
|
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().
|
|
14725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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(
|
|
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
|
-
}).
|
|
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(
|
|
14805
|
-
|
|
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(
|
|
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
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
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
|
|
14846
|
-
*
|
|
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
|
-
}).
|
|
14852
|
-
endpoints: external_exports.record(
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
14946
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
15166
|
-
*
|
|
15167
|
-
* it
|
|
15168
|
-
*
|
|
15169
|
-
*
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
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:
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
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"
|
|
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",
|
|
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",
|
|
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",
|
|
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)
|
|
16471
|
-
|
|
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",
|
|
16482
|
-
|
|
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",
|
|
16495
|
-
|
|
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",
|
|
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(
|
|
16546
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
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
|
-
"
|
|
16840
|
-
"
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
16982
|
-
if (
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
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,
|
|
17001
|
-
if (
|
|
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
|
-
|
|
17010
|
-
|
|
17011
|
-
const
|
|
17012
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
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"]
|
|
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
|
|
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] ??
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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"
|
|
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",
|
|
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(
|
|
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",
|
|
17309
|
-
({ app: app2,
|
|
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",
|
|
18746
|
+
const updated = await ctx.call("updateApp", {
|
|
18747
|
+
params: { app: appId2 },
|
|
17328
18748
|
body: { ...doc2, revision }
|
|
17329
18749
|
});
|
|
17330
|
-
({ app: app2,
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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 =
|
|
17416
|
-
(p) =>
|
|
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
|
|
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
|
|
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
|
-
|
|
17461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
18990
|
+
pathFor(name, params, query),
|
|
17582
18991
|
{
|
|
17583
|
-
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 =
|
|
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,
|
|
17616
|
-
return this.publicCall(name,
|
|
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
|
|
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",
|
|
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,
|
|
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
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
|
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
|
-
|
|
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] = { ...
|
|
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
|
|
17965
|
-
if (!
|
|
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 !==
|
|
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",
|
|
17980
|
-
|
|
17981
|
-
|
|
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" && !
|
|
17984
|
-
if ((this.state.generation ?? 0) !==
|
|
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
|
-
|
|
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
|
|
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 {
|
|
18057
|
-
import {
|
|
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/
|
|
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
|
|
18523
|
-
|
|
18524
|
-
if (
|
|
18525
|
-
|
|
18526
|
-
return
|
|
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",
|
|
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
|
|
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
|
|
20054
|
+
const records = await cf.all(
|
|
18624
20055
|
"/zones/" + zone.id + "/dns_records?name=" + encodeURIComponent(domain2)
|
|
18625
20056
|
);
|
|
18626
|
-
if (
|
|
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
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
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
|
-
|
|
18726
|
-
await ctx.save();
|
|
20157
|
+
await advance(ctx, journal, journal.phase, { secrets: void 0 });
|
|
18727
20158
|
}
|
|
18728
|
-
async function matchExisting(ctx, cf, flags) {
|
|
18729
|
-
|
|
18730
|
-
|
|
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,
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
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
|
-
`
|
|
20254
|
+
`Attach ${domain2} to ${journal.name} in Cloudflare account ${journal.accountId}?`,
|
|
18834
20255
|
flags
|
|
18835
20256
|
);
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
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
|
-
|
|
18841
|
-
|
|
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
|
-
|
|
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 && !
|
|
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 &&
|
|
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 !==
|
|
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 (
|
|
18877
|
-
|
|
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
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
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
|
-
|
|
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 ("
|
|
19111
|
-
lines.push(`Free access ends: ${result.
|
|
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
|
|
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, ...
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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",
|
|
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 (
|
|
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",
|
|
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"
|
|
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),
|