@maxceem/agw 0.7.1 → 0.8.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 +225 -27
- package/package.json +3 -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.8.0", wrangler: "4.96.0", upgradeFrom: ["0.8.0", "0.7.2", "0.7.1", "0.7.0", "0.6.4", "0.6.3", "0.6.2", "0.6.1", "0.6.0", "0.5.2", "0.5.1", "0.5.0", "0.4.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.8.0/gateway-0.8.0.tar.gz", sha256: "a9039b1c43d68bf0636e5492a4a2cccfa4b0229e61e790ae414174accc52f9c6", manifest: "eb3280cee275a74cf0ad39a25d20d2a0143be7f3be974f0f5635f4bf8aba0b0e" } };
|
|
10
10
|
|
|
11
11
|
// src/main.ts
|
|
12
12
|
import { Writable as Writable2 } from "node:stream";
|
|
@@ -14841,6 +14841,13 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14841
14841
|
directBaseUrl: "https://api.openai.com/",
|
|
14842
14842
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14843
14843
|
nativeClampStyle: "responses",
|
|
14844
|
+
// Images, which report token usage, embeddings and speech.
|
|
14845
|
+
inferencePaths: [
|
|
14846
|
+
"v1/images/generations",
|
|
14847
|
+
"v1/images/edits",
|
|
14848
|
+
"v1/embeddings",
|
|
14849
|
+
"v1/audio/speech"
|
|
14850
|
+
],
|
|
14844
14851
|
probePath: "v1/models",
|
|
14845
14852
|
examplePath: "v1/responses",
|
|
14846
14853
|
// OpenAI's chat-completions surface deprecated `max_tokens` in favour of
|
|
@@ -14850,6 +14857,9 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14850
14857
|
// The two types whose Responses and transcription request shapes the
|
|
14851
14858
|
// gateway composes itself for custom endpoints.
|
|
14852
14859
|
endpointPaths: { responses: "v1/responses", audio_transcription: "v1/audio/transcriptions" },
|
|
14860
|
+
// Every JSON answer reports `usage`: tokens for the GPT transcription
|
|
14861
|
+
// models, `{ type: "duration", seconds }` for whisper-1.
|
|
14862
|
+
transcriptionFormats: { metered: ["json", "verbose_json", "diarized_json"], default: "json" },
|
|
14853
14863
|
modelAuthor: "OpenAI"
|
|
14854
14864
|
},
|
|
14855
14865
|
anthropic: {
|
|
@@ -14872,6 +14882,14 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14872
14882
|
directBaseUrl: "https://api.x.ai/",
|
|
14873
14883
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14874
14884
|
nativeClampStyle: "responses",
|
|
14885
|
+
// Images and their edits report their cost like chat does; embeddings and
|
|
14886
|
+
// legacy completions answer in tokens, the latter capped in `max_tokens`.
|
|
14887
|
+
inferencePaths: [
|
|
14888
|
+
"v1/images/generations",
|
|
14889
|
+
"v1/images/edits",
|
|
14890
|
+
"v1/embeddings",
|
|
14891
|
+
{ path: "v1/completions", clamp: "chat_completions" }
|
|
14892
|
+
],
|
|
14875
14893
|
probePath: "v1/models",
|
|
14876
14894
|
// Native provider paths: xAI transcribes at `v1/stt`, where OpenAI serves
|
|
14877
14895
|
// `v1/audio/transcriptions`.
|
|
@@ -14888,6 +14906,13 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14888
14906
|
directBaseUrl: "https://generativelanguage.googleapis.com/",
|
|
14889
14907
|
auth: { header: "x-goog-api-key" },
|
|
14890
14908
|
nativeClampStyle: "gemini_native",
|
|
14909
|
+
// Embeddings. `generateContent` already carries text, images, speech and
|
|
14910
|
+
// transcription; `batchEmbedContents` is left out because each item may name
|
|
14911
|
+
// a model the one in the URL does not.
|
|
14912
|
+
inferencePaths: [
|
|
14913
|
+
"v1beta/models/{model}:embedContent",
|
|
14914
|
+
"v1/models/{model}:embedContent"
|
|
14915
|
+
],
|
|
14891
14916
|
probePath: "v1beta/models",
|
|
14892
14917
|
// Native Gemini generation requests carry the model in the URL rather than
|
|
14893
14918
|
// in the JSON body, so there is no example path without a model.
|
|
@@ -14899,6 +14924,15 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14899
14924
|
directBaseUrl: "https://api.perplexity.ai/",
|
|
14900
14925
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14901
14926
|
nativeClampStyle: "responses",
|
|
14927
|
+
// The Agent API, its model router's three formats, and embeddings.
|
|
14928
|
+
inferencePaths: [
|
|
14929
|
+
{ path: "v1/agent", clamp: "responses" },
|
|
14930
|
+
{ path: "router/v1/chat/completions", clamp: "chat_completions" },
|
|
14931
|
+
{ path: "router/v1/responses", clamp: "responses" },
|
|
14932
|
+
{ path: "router/v1/messages", clamp: "anthropic" },
|
|
14933
|
+
"v1/embeddings",
|
|
14934
|
+
"v1/contextualizedembeddings"
|
|
14935
|
+
],
|
|
14902
14936
|
// No `probePath`: Perplexity has no unmetered authenticated endpoint.
|
|
14903
14937
|
examplePath: "chat/completions",
|
|
14904
14938
|
modelAuthor: "Perplexity"
|
|
@@ -14914,6 +14948,13 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14914
14948
|
directBaseUrl: "https://api.deepseek.com/",
|
|
14915
14949
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14916
14950
|
nativeClampStyle: "chat_completions",
|
|
14951
|
+
// Responses, fill-in-the-middle completions and the Anthropic-format API,
|
|
14952
|
+
// which like its chat path carry no `v1/` prefix of their own.
|
|
14953
|
+
inferencePaths: [
|
|
14954
|
+
{ path: "responses", clamp: "responses" },
|
|
14955
|
+
{ path: "beta/completions", clamp: "chat_completions" },
|
|
14956
|
+
{ path: "anthropic/v1/messages", clamp: "anthropic" }
|
|
14957
|
+
],
|
|
14917
14958
|
// DeepSeek's OpenAI base URL carries no `v1` segment.
|
|
14918
14959
|
probePath: "models",
|
|
14919
14960
|
examplePath: "chat/completions",
|
|
@@ -14926,6 +14967,15 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14926
14967
|
directBaseUrl: "https://api.groq.com/",
|
|
14927
14968
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14928
14969
|
nativeClampStyle: "chat_completions",
|
|
14970
|
+
// Its default `json` answer is the transcript alone; `verbose_json` adds the
|
|
14971
|
+
// duration it bills by, and the same `text` a client reads.
|
|
14972
|
+
transcriptionFormats: {
|
|
14973
|
+
metered: ["verbose_json"],
|
|
14974
|
+
default: "json",
|
|
14975
|
+
upgrades: { json: "verbose_json" }
|
|
14976
|
+
},
|
|
14977
|
+
// Speech; transcriptions are a default style already.
|
|
14978
|
+
inferencePaths: ["openai/v1/audio/speech"],
|
|
14929
14979
|
// Groq's OpenAI-compatible surface is namespaced under `openai/`.
|
|
14930
14980
|
probePath: "openai/v1/models",
|
|
14931
14981
|
examplePath: "openai/v1/chat/completions"
|
|
@@ -14935,6 +14985,12 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14935
14985
|
directBaseUrl: "https://api.mistral.ai/",
|
|
14936
14986
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14937
14987
|
nativeClampStyle: "chat_completions",
|
|
14988
|
+
// Embeddings, fill-in-the-middle completions and speech.
|
|
14989
|
+
inferencePaths: [
|
|
14990
|
+
"v1/embeddings",
|
|
14991
|
+
{ path: "v1/fim/completions", clamp: "chat_completions" },
|
|
14992
|
+
"v1/audio/speech"
|
|
14993
|
+
],
|
|
14938
14994
|
probePath: "v1/models",
|
|
14939
14995
|
modelAuthor: "Mistral"
|
|
14940
14996
|
},
|
|
@@ -14945,6 +15001,21 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14945
15001
|
directBaseUrl: "https://api.together.ai/",
|
|
14946
15002
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14947
15003
|
nativeClampStyle: "chat_completions",
|
|
15004
|
+
// Its default `json` answer is the transcript alone; `verbose_json` adds the
|
|
15005
|
+
// duration it bills by, and the same `text` a client reads.
|
|
15006
|
+
transcriptionFormats: {
|
|
15007
|
+
metered: ["verbose_json"],
|
|
15008
|
+
default: "json",
|
|
15009
|
+
upgrades: { json: "verbose_json" }
|
|
15010
|
+
},
|
|
15011
|
+
// Legacy completions, images, speech and embeddings. Video is a job whose
|
|
15012
|
+
// result is polled for, so it is not here.
|
|
15013
|
+
inferencePaths: [
|
|
15014
|
+
{ path: "v1/completions", clamp: "chat_completions" },
|
|
15015
|
+
"v1/images/generations",
|
|
15016
|
+
"v1/audio/speech",
|
|
15017
|
+
"v1/embeddings"
|
|
15018
|
+
],
|
|
14948
15019
|
probePath: "v1/models"
|
|
14949
15020
|
},
|
|
14950
15021
|
fireworks: {
|
|
@@ -14954,6 +15025,13 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14954
15025
|
directBaseUrl: "https://api.fireworks.ai/",
|
|
14955
15026
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14956
15027
|
nativeClampStyle: "chat_completions",
|
|
15028
|
+
// Every format its inference plane speaks, and embeddings.
|
|
15029
|
+
inferencePaths: [
|
|
15030
|
+
{ path: "inference/v1/completions", clamp: "chat_completions" },
|
|
15031
|
+
{ path: "inference/v1/messages", clamp: "anthropic" },
|
|
15032
|
+
{ path: "inference/v1/responses", clamp: "responses" },
|
|
15033
|
+
"inference/v1/embeddings"
|
|
15034
|
+
],
|
|
14957
15035
|
// No `probePath`: its list-models call is `v1/accounts/{account}/models`,
|
|
14958
15036
|
// and the account id cannot be derived from the key. Nothing under
|
|
14959
15037
|
// `inference/v1/` is documented as a GET.
|
|
@@ -14964,6 +15042,8 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14964
15042
|
directBaseUrl: "https://api.cerebras.ai/",
|
|
14965
15043
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14966
15044
|
nativeClampStyle: "chat_completions",
|
|
15045
|
+
// Legacy completions.
|
|
15046
|
+
inferencePaths: [{ path: "v1/completions", clamp: "chat_completions" }],
|
|
14967
15047
|
probePath: "v1/models"
|
|
14968
15048
|
},
|
|
14969
15049
|
moonshot: {
|
|
@@ -14973,6 +15053,8 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
14973
15053
|
directBaseUrl: "https://api.moonshot.ai/",
|
|
14974
15054
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
14975
15055
|
nativeClampStyle: "chat_completions",
|
|
15056
|
+
// The Anthropic-format API.
|
|
15057
|
+
inferencePaths: [{ path: "anthropic/v1/messages", clamp: "anthropic" }],
|
|
14976
15058
|
probePath: "v1/models",
|
|
14977
15059
|
modelAuthor: "Moonshot AI"
|
|
14978
15060
|
},
|
|
@@ -15010,6 +15092,13 @@ var PROVIDER_DESCRIPTORS = {
|
|
|
15010
15092
|
directBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/",
|
|
15011
15093
|
auth: { header: "authorization", scheme: "Bearer " },
|
|
15012
15094
|
nativeClampStyle: "chat_completions",
|
|
15095
|
+
// Responses, Seedream images and multimodal embeddings, all under the base
|
|
15096
|
+
// URL's own `api/v3/`. Seedance video is a polled job, so it is not here.
|
|
15097
|
+
inferencePaths: [
|
|
15098
|
+
{ path: "responses", clamp: "responses" },
|
|
15099
|
+
"images/generations",
|
|
15100
|
+
"embeddings/multimodal"
|
|
15101
|
+
],
|
|
15013
15102
|
// No `probePath`: ModelArk publishes no list-models call, and its own SDK
|
|
15014
15103
|
// has no models resource. It also authenticates before it routes, so every
|
|
15015
15104
|
// path answers the same 401 and a probe would prove nothing about the key.
|
|
@@ -15075,6 +15164,7 @@ var APP_STATUSES = ["active", "disabled"];
|
|
|
15075
15164
|
var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
|
|
15076
15165
|
var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
|
|
15077
15166
|
var APP_ATTEST_ENVIRONMENTS = ["production", "development"];
|
|
15167
|
+
var APP_ATTEST_VERIFY_ON = ["every_request", "token_exchange"];
|
|
15078
15168
|
var ENDPOINT_SLUG = /^[a-z0-9-]{1,64}$/;
|
|
15079
15169
|
var PROVIDER_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
15080
15170
|
var HTTP_FIELD_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
@@ -15225,7 +15315,20 @@ var AppleAppAttestAuthenticationSchema = external_exports.object({
|
|
|
15225
15315
|
*/
|
|
15226
15316
|
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, {
|
|
15227
15317
|
error: "environments cannot repeat a value"
|
|
15228
|
-
}).prefault(["production"])
|
|
15318
|
+
}).prefault(["production"]),
|
|
15319
|
+
/**
|
|
15320
|
+
* Where the install's App Attest key proves itself. `every_request`, the
|
|
15321
|
+
* default, binds every gateway request to the device: each one carries
|
|
15322
|
+
* an assertion over that very request, so a copied token is worthless.
|
|
15323
|
+
* `token_exchange` checks the assertion only when the hourly gateway
|
|
15324
|
+
* token is minted, and requests then carry the token alone; limits and
|
|
15325
|
+
* budgets still count per install, because the token names it.
|
|
15326
|
+
*/
|
|
15327
|
+
verify_on: external_exports.enum(APP_ATTEST_VERIFY_ON, {
|
|
15328
|
+
error: "verify_on must be one of every_request, token_exchange"
|
|
15329
|
+
}).default("every_request").meta({
|
|
15330
|
+
description: "When the install's App Attest assertion is verified. every_request (the default): every gateway request carries X-AppAttest-Proof, a fresh assertion over that request; needs the AppAIGateway Swift package 1.4 or later. token_exchange: the assertion is verified once, when the hourly gateway token is exchanged, and requests carry only the token."
|
|
15331
|
+
})
|
|
15229
15332
|
}).strict()
|
|
15230
15333
|
}).strict();
|
|
15231
15334
|
var ApiKeyAuthenticationSchema = external_exports.object({
|
|
@@ -15268,6 +15371,14 @@ var LimitScopeSchema = external_exports.object({
|
|
|
15268
15371
|
}).strict(),
|
|
15269
15372
|
spending: external_exports.object({ monthly_usd: NullableSpendLimit }).strict()
|
|
15270
15373
|
}).strict();
|
|
15374
|
+
var BUDGET_SCOPES = ["install", "user"];
|
|
15375
|
+
var PerUserLimitScopeSchema = LimitScopeSchema.extend({
|
|
15376
|
+
spending: LimitScopeSchema.shape.spending.extend({
|
|
15377
|
+
scope: external_exports.enum(BUDGET_SCOPES).optional().meta({
|
|
15378
|
+
description: "Whose month monthly_usd is measured over: install (the default on App Attest applications) or user (the default, and the only value, on server applications). user on an App Attest application needs authentication.end_user.source issuer. Filled in on every parsed configuration."
|
|
15379
|
+
})
|
|
15380
|
+
}).strict()
|
|
15381
|
+
}).strict();
|
|
15271
15382
|
var UNLIMITED_SCOPE = {
|
|
15272
15383
|
requests: { per_minute: null, per_day: null },
|
|
15273
15384
|
spending: { monthly_usd: null }
|
|
@@ -15306,7 +15417,7 @@ var AppConfigSchema = external_exports.object({
|
|
|
15306
15417
|
* parsed configuration: an unwritten block is the unlimited one.
|
|
15307
15418
|
*/
|
|
15308
15419
|
limits: external_exports.object({
|
|
15309
|
-
per_user:
|
|
15420
|
+
per_user: PerUserLimitScopeSchema.prefault(UNLIMITED_SCOPE),
|
|
15310
15421
|
per_app: LimitScopeSchema.prefault(UNLIMITED_SCOPE)
|
|
15311
15422
|
}).strict().prefault({}),
|
|
15312
15423
|
endpoints: external_exports.record(
|
|
@@ -15324,6 +15435,29 @@ var AppConfigSchema = external_exports.object({
|
|
|
15324
15435
|
message: "needs an authentication.end_user source other than none: this application identifies no end users, so use limits.per_app"
|
|
15325
15436
|
});
|
|
15326
15437
|
}
|
|
15438
|
+
const budgetScope = perUser?.spending.scope;
|
|
15439
|
+
if (authentication !== void 0 && budgetScope !== void 0) {
|
|
15440
|
+
if (authentication.type === "api_key" && budgetScope === "install") {
|
|
15441
|
+
context.addIssue({
|
|
15442
|
+
code: "custom",
|
|
15443
|
+
path: ["limits", "per_user", "spending", "scope"],
|
|
15444
|
+
message: "cannot be install: server applications have no install; the budget is per user"
|
|
15445
|
+
});
|
|
15446
|
+
}
|
|
15447
|
+
if (authentication.type === "apple_app_attest" && budgetScope === "user" && authentication.end_user.source !== "issuer") {
|
|
15448
|
+
context.addIssue({
|
|
15449
|
+
code: "custom",
|
|
15450
|
+
path: ["limits", "per_user", "spending", "scope"],
|
|
15451
|
+
message: "can be user only when authentication.end_user.source is issuer: under app_install every install is its own user, so use install"
|
|
15452
|
+
});
|
|
15453
|
+
}
|
|
15454
|
+
}
|
|
15455
|
+
}).overwrite((config2) => {
|
|
15456
|
+
const spending = config2.limits.per_user.spending;
|
|
15457
|
+
if (spending.scope === void 0) {
|
|
15458
|
+
spending.scope = config2.authentication.type === "apple_app_attest" ? "install" : "user";
|
|
15459
|
+
}
|
|
15460
|
+
return config2;
|
|
15327
15461
|
}).meta({ id: "AppConfig" });
|
|
15328
15462
|
var APP_ID_IS_SERVER_ASSIGNED = "id is assigned by the server: omit it and read app.id from the response";
|
|
15329
15463
|
var AppWriteSchema = external_exports.object({
|
|
@@ -15760,13 +15894,13 @@ var commands = {
|
|
|
15760
15894
|
"provider-gateway remove": { args: 1, flags: "yes!" },
|
|
15761
15895
|
"app add": {
|
|
15762
15896
|
args: 0,
|
|
15763
|
-
flags: "type name team-id bundle-id attest-environments provider* status file key-output dry-run!"
|
|
15897
|
+
flags: "type name team-id bundle-id attest-environments verify-on provider* status file key-output dry-run!"
|
|
15764
15898
|
},
|
|
15765
15899
|
"app list": { args: 0, flags: "" },
|
|
15766
15900
|
"app show": { args: 1, flags: "" },
|
|
15767
15901
|
"app update": {
|
|
15768
15902
|
args: 1,
|
|
15769
|
-
flags: "name team-id bundle-id attest-environments provider* all-providers! status file dry-run!"
|
|
15903
|
+
flags: "name team-id bundle-id attest-environments verify-on provider* all-providers! status file dry-run!"
|
|
15770
15904
|
},
|
|
15771
15905
|
"app validate": { args: 0, flags: "file" },
|
|
15772
15906
|
"app check": { args: 1, flags: "" },
|
|
@@ -16706,6 +16840,12 @@ var StateStore = class {
|
|
|
16706
16840
|
function selectedProviderPolicies(routing) {
|
|
16707
16841
|
return routing.providers.mode === "selected" ? routing.providers.selected : {};
|
|
16708
16842
|
}
|
|
16843
|
+
var DEFAULT_INFERENCE_PATHS = new Map(
|
|
16844
|
+
PROVIDER_TYPES.map((type) => [
|
|
16845
|
+
type,
|
|
16846
|
+
(providerDescriptor(type).inferencePaths ?? []).map((entry) => typeof entry === "string" ? { path: entry, clamp: "none" } : { ...entry })
|
|
16847
|
+
])
|
|
16848
|
+
);
|
|
16709
16849
|
|
|
16710
16850
|
// ../src/shared/protocols.ts
|
|
16711
16851
|
var PROTOCOLS = {
|
|
@@ -16844,7 +16984,7 @@ async function appDocument(flags, current) {
|
|
|
16844
16984
|
if (!["ios", "server"].includes(type))
|
|
16845
16985
|
fail("invalid_input", "App type must be ios or server.");
|
|
16846
16986
|
const ios = type === "ios";
|
|
16847
|
-
if (!ios &&
|
|
16987
|
+
if (!ios && IOS_FLAGS.some((k) => flags[k]))
|
|
16848
16988
|
fail("invalid_input", "Server apps cannot use iOS flags.");
|
|
16849
16989
|
const bundle = ios ? await required2(flags, "bundle-id", "Apple bundle ID") : null;
|
|
16850
16990
|
doc = localApp({
|
|
@@ -16871,7 +17011,7 @@ async function appDocument(flags, current) {
|
|
|
16871
17011
|
if (flags.name) doc.name = flags.name;
|
|
16872
17012
|
if (flags.status) doc.status = flags.status === "disabled" ? "disabled" : "active";
|
|
16873
17013
|
const auth = doc.config.authentication;
|
|
16874
|
-
if (auth.type === "api_key" &&
|
|
17014
|
+
if (auth.type === "api_key" && IOS_FLAGS.some((k) => flags[k]))
|
|
16875
17015
|
fail("invalid_input", "Server apps cannot use iOS flags.");
|
|
16876
17016
|
if (auth.type === "apple_app_attest") {
|
|
16877
17017
|
if (flags["key-output"])
|
|
@@ -16880,6 +17020,8 @@ async function appDocument(flags, current) {
|
|
|
16880
17020
|
if (flags["bundle-id"]) auth.app_attest.bundle_id = flags["bundle-id"];
|
|
16881
17021
|
if (flags["attest-environments"])
|
|
16882
17022
|
auth.app_attest.environments = attestEnvironments(flags["attest-environments"]);
|
|
17023
|
+
if (flags["verify-on"])
|
|
17024
|
+
auth.app_attest.verify_on = flags["verify-on"].trim();
|
|
16883
17025
|
}
|
|
16884
17026
|
const selectedProviders = flagList(flags.provider);
|
|
16885
17027
|
if (selectedProviders.length) {
|
|
@@ -16894,6 +17036,7 @@ async function appDocument(flags, current) {
|
|
|
16894
17036
|
if (flags["all-providers"]) doc.config.routing.providers = { mode: "all" };
|
|
16895
17037
|
return localApp(doc);
|
|
16896
17038
|
}
|
|
17039
|
+
var IOS_FLAGS = ["team-id", "bundle-id", "attest-environments", "verify-on"];
|
|
16897
17040
|
function attestEnvironments(value) {
|
|
16898
17041
|
return value.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
16899
17042
|
}
|
|
@@ -16979,7 +17122,7 @@ ${snippet}`;
|
|
|
16979
17122
|
...snippet ? { snippet } : {},
|
|
16980
17123
|
app: app2,
|
|
16981
17124
|
...key ? { applicationKey: key } : {},
|
|
16982
|
-
guidance: doc.config.authentication.type === "apple_app_attest" ? `Add https://github.com/maxceem/app-ai-gateway-swift from 1.
|
|
17125
|
+
guidance: doc.config.authentication.type === "apple_app_attest" ? `Add https://github.com/maxceem/app-ai-gateway-swift from 1.4.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.`
|
|
16983
17126
|
};
|
|
16984
17127
|
}
|
|
16985
17128
|
}
|
|
@@ -17078,9 +17221,11 @@ var REJECTION_REASONS = [
|
|
|
17078
17221
|
"blocked_app_rate",
|
|
17079
17222
|
"blocked_app_budget",
|
|
17080
17223
|
"blocked_billing",
|
|
17081
|
-
"blocked_user"
|
|
17224
|
+
"blocked_user",
|
|
17225
|
+
/** An App Attest assertion whose counter the install had already used. */
|
|
17226
|
+
"blocked_replay"
|
|
17082
17227
|
];
|
|
17083
|
-
var REJECTION_SCOPES = ["user", "app", "account"];
|
|
17228
|
+
var REJECTION_SCOPES = ["user", "install", "app", "account"];
|
|
17084
17229
|
|
|
17085
17230
|
// ../src/contracts/billing.ts
|
|
17086
17231
|
var BillingSubscriptionStatusSchema = external_exports.enum([
|
|
@@ -17231,6 +17376,7 @@ var USAGE_BREAKDOWN_DIMENSIONS = [
|
|
|
17231
17376
|
"credential_source",
|
|
17232
17377
|
"model_author",
|
|
17233
17378
|
"user",
|
|
17379
|
+
"install",
|
|
17234
17380
|
"status",
|
|
17235
17381
|
"cost_source",
|
|
17236
17382
|
"route",
|
|
@@ -17242,7 +17388,7 @@ var ErrorResponseSchema = external_exports.object({
|
|
|
17242
17388
|
code: external_exports.string(),
|
|
17243
17389
|
message: external_exports.string(),
|
|
17244
17390
|
data: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional().meta({
|
|
17245
|
-
description: "Machine-readable facts about this rejection, present only where the code alone is not actionable. A billing_request_quota_exceeded rejection carries periodId, periodStart, periodEnd, limit, used, and resetAt; a billing_plan_limit_reached rejection carries limit and used; an app_rate_limited or app_budget_exhausted rejection carries scope,
|
|
17391
|
+
description: "Machine-readable facts about this rejection, present only where the code alone is not actionable. A billing_request_quota_exceeded rejection carries periodId, periodStart, periodEnd, limit, used, and resetAt; a billing_plan_limit_reached rejection carries limit and used; an app_rate_limited or app_budget_exhausted rejection carries scope, one of user, install or app; a rate_limited rejection carries scope naming the endpoint policy that refused, plus limit, windowSeconds, retryAfterSeconds and resetAt."
|
|
17246
17392
|
})
|
|
17247
17393
|
})
|
|
17248
17394
|
}).meta({ id: "ErrorResponse" });
|
|
@@ -17272,17 +17418,23 @@ var AppAttestChallengeResponseSchema = external_exports.object({
|
|
|
17272
17418
|
var AppAttestRegisterResponseSchema = external_exports.object({ user_id: external_exports.string() });
|
|
17273
17419
|
var GatewayTokenResponseSchema = external_exports.object({
|
|
17274
17420
|
access_token: external_exports.string(),
|
|
17275
|
-
expires_in: external_exports.number()
|
|
17421
|
+
expires_in: external_exports.number(),
|
|
17422
|
+
verify_on: external_exports.enum(APP_ATTEST_VERIFY_ON).optional().meta({
|
|
17423
|
+
description: "On App Attest applications, when the gateway verifies the install's assertion: every_request means every request sent with this token must carry X-AppAttest-Proof; token_exchange means requests carry only the token. Absent on other applications."
|
|
17424
|
+
})
|
|
17276
17425
|
});
|
|
17277
17426
|
var CurrentUserResponseSchema = external_exports.object({
|
|
17278
17427
|
user_id: external_exports.string(),
|
|
17428
|
+
install_id: external_exports.string().nullable().meta({
|
|
17429
|
+
description: "The App Attest key id of the install making the request, on App Attest applications, whose request limits are counted per install. Null on server applications."
|
|
17430
|
+
}),
|
|
17279
17431
|
limits: external_exports.object({
|
|
17280
|
-
requests_today: external_exports.number().int().nullable().meta({ description: "Requests
|
|
17432
|
+
requests_today: external_exports.number().int().nullable().meta({ description: "Requests made so far in the current UTC day: by this install on an App Attest application, by this user on a server application. Null when the application sets no per-user limit, in which case requests are not counted at all." }),
|
|
17281
17433
|
requests_remaining: external_exports.number().int().nullable().meta({ description: "What is left of requests_per_day. Null when no daily limit is set." }),
|
|
17282
|
-
requests_per_minute: external_exports.number().nullable().meta({ description: "The per-
|
|
17283
|
-
requests_per_day: external_exports.number().nullable().meta({ description: "The per-
|
|
17284
|
-
monthly_cost_usd: external_exports.number().meta({ description: "What
|
|
17285
|
-
monthly_budget_usd: external_exports.number().nullable().meta({ description: "The
|
|
17434
|
+
requests_per_minute: external_exports.number().nullable().meta({ description: "The per-minute limit this app sets on each install (App Attest) or user (server). Null means unlimited." }),
|
|
17435
|
+
requests_per_day: external_exports.number().nullable().meta({ description: "The per-day limit this app sets on each install (App Attest) or user (server). Null means unlimited." }),
|
|
17436
|
+
monthly_cost_usd: external_exports.number().meta({ description: "What has been spent so far in the current UTC calendar month against the budget below: by this install, or by this user when the budget's scope is user." }),
|
|
17437
|
+
monthly_budget_usd: external_exports.number().nullable().meta({ description: "The monthly spending budget this app sets on each install or user, per limits.per_user.spending.scope. Null means unlimited." }),
|
|
17286
17438
|
blocked: external_exports.boolean().meta({ description: "Whether this user has been blocked in the console." })
|
|
17287
17439
|
}).meta({
|
|
17288
17440
|
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."
|
|
@@ -17292,6 +17444,9 @@ var UsageEventSchema = external_exports.object({
|
|
|
17292
17444
|
id: external_exports.number().int(),
|
|
17293
17445
|
/** Null for an application that identifies no end users. */
|
|
17294
17446
|
user_id: external_exports.string().nullable(),
|
|
17447
|
+
install_id: external_exports.string().nullable().meta({
|
|
17448
|
+
description: "The App Attest key id of the install that made the request, on App Attest applications; null for server applications."
|
|
17449
|
+
}),
|
|
17295
17450
|
api_key_id: external_exports.string().nullable().meta({
|
|
17296
17451
|
description: "Non-secret ID of the application API key that authenticated the request, including the client-proof key carried by an exchanged gateway token."
|
|
17297
17452
|
}),
|
|
@@ -17373,6 +17528,9 @@ var AuthEventListSchema = external_exports.object({
|
|
|
17373
17528
|
var RejectionEventSchema = external_exports.object({
|
|
17374
17529
|
id: external_exports.number().int(),
|
|
17375
17530
|
user_id: external_exports.string().nullable(),
|
|
17531
|
+
install_id: external_exports.string().nullable().meta({
|
|
17532
|
+
description: "The App Attest key id of the refused install, on App Attest applications; null for server applications and for samples recorded before installs were."
|
|
17533
|
+
}),
|
|
17376
17534
|
api_key_id: external_exports.string().nullable(),
|
|
17377
17535
|
reason: external_exports.enum(REJECTION_REASONS),
|
|
17378
17536
|
scope: external_exports.enum(REJECTION_SCOPES).nullable().meta({ description: "Null on historical samples whose limit scope cannot be recovered." }),
|
|
@@ -17431,6 +17589,12 @@ var AuthEventSummarySchema = external_exports.object({
|
|
|
17431
17589
|
description: "Users currently inside an unclosed claim-propagation window \u2014 stuck mid-activation right now."
|
|
17432
17590
|
})
|
|
17433
17591
|
}).meta({ id: "AuthEventSummary" });
|
|
17592
|
+
var AppWarningSchema = external_exports.object({
|
|
17593
|
+
code: external_exports.string().meta({ description: "Stable identifier of what the warning is about." }),
|
|
17594
|
+
message: external_exports.string().meta({ description: "What will stop, when, and what to do about it, in one sentence or two." }),
|
|
17595
|
+
until: external_exports.string().optional().meta({ description: "ISO 8601 UTC instant the behaviour stops." }),
|
|
17596
|
+
docs_url: external_exports.string().optional().meta({ description: "Where the change is described." })
|
|
17597
|
+
}).meta({ id: "AppWarning" });
|
|
17434
17598
|
var AppResponseSchema = external_exports.object({
|
|
17435
17599
|
app: external_exports.object({
|
|
17436
17600
|
revision: external_exports.number().int().positive(),
|
|
@@ -17444,7 +17608,10 @@ var AppResponseSchema = external_exports.object({
|
|
|
17444
17608
|
config: AppConfigSchema,
|
|
17445
17609
|
status: external_exports.enum(APP_STATUSES),
|
|
17446
17610
|
created_at: external_exports.string(),
|
|
17447
|
-
updated_at: external_exports.string()
|
|
17611
|
+
updated_at: external_exports.string(),
|
|
17612
|
+
warnings: external_exports.array(AppWarningSchema).meta({
|
|
17613
|
+
description: "Behaviour of this application that will stop on a stated date, computed when the application is read; empty when there is none. Read-only: tell the person who owns the app, and act before each date."
|
|
17614
|
+
})
|
|
17448
17615
|
})
|
|
17449
17616
|
}).meta({ id: "AppResponse" });
|
|
17450
17617
|
var AppDeleteResponseSchema = external_exports.object({
|
|
@@ -17775,6 +17942,9 @@ var AppCheckResponseSchema = external_exports.object({
|
|
|
17775
17942
|
}),
|
|
17776
17943
|
limitations: external_exports.array(external_exports.string()).meta({
|
|
17777
17944
|
description: "What this check did not exercise, one sentence each."
|
|
17945
|
+
}),
|
|
17946
|
+
warnings: external_exports.array(AppWarningSchema).meta({
|
|
17947
|
+
description: "The application's warnings, as reading it returns them: behaviour that will stop on a stated date. They never make an application not ready."
|
|
17778
17948
|
})
|
|
17779
17949
|
});
|
|
17780
17950
|
var AppSnippetResponseSchema = external_exports.object({
|
|
@@ -17817,9 +17987,16 @@ var ApiKeyRevokeResponseSchema = external_exports.object({
|
|
|
17817
17987
|
var GatewayUserSchema = external_exports.object({
|
|
17818
17988
|
id: external_exports.string(),
|
|
17819
17989
|
status: external_exports.enum(["active", "blocked"]),
|
|
17820
|
-
attest_key_id: external_exports.string().nullable()
|
|
17821
|
-
|
|
17822
|
-
|
|
17990
|
+
attest_key_id: external_exports.string().nullable().meta({
|
|
17991
|
+
description: "The user's most recently seen App Attest key id, if they hold any. Under app_install it is the user's own id."
|
|
17992
|
+
}),
|
|
17993
|
+
attest_registered: external_exports.boolean().meta({ description: "Whether the user holds at least one App Attest key." }),
|
|
17994
|
+
installs: external_exports.number().int().meta({
|
|
17995
|
+
description: "How many App Attest keys the user holds: one per install, that is one copy of the app on one device, and a new one each time the app is reinstalled. Always 0 on server applications."
|
|
17996
|
+
}),
|
|
17997
|
+
is_install: external_exports.boolean().meta({
|
|
17998
|
+
description: "Whether this row's id is itself a registered App Attest key, so the row is one install: every row of an app_install application, and none of a signed-in user's. Decided per row, so history kept from before an application changed its user identity reads as what it was. A per-install budget measures a row's spend only when this is true."
|
|
17999
|
+
}),
|
|
17823
18000
|
created_at: external_exports.string(),
|
|
17824
18001
|
last_seen_at: external_exports.string().nullable(),
|
|
17825
18002
|
is_virtual: external_exports.boolean(),
|
|
@@ -17923,7 +18100,8 @@ var PricesResponseSchema = external_exports.object({
|
|
|
17923
18100
|
var OperationResultSchema = external_exports.object({
|
|
17924
18101
|
/** The account a claim settled, a login landed in, or a bootstrap created. */
|
|
17925
18102
|
accountId: external_exports.string().optional(),
|
|
17926
|
-
|
|
18103
|
+
// An outcome recorded before warnings existed has none.
|
|
18104
|
+
app: AppResponseSchema.shape.app.extend({ warnings: AppResponseSchema.shape.app.shape.warnings.optional() }).optional(),
|
|
17927
18105
|
api_key: AppKeyMetadataSchema.nullable().optional(),
|
|
17928
18106
|
provider: ProviderSummarySchema.optional(),
|
|
17929
18107
|
gateway: ProviderGatewaySummarySchema.optional()
|
|
@@ -17950,7 +18128,7 @@ var RevealedOperationResponseSchema = external_exports.object({
|
|
|
17950
18128
|
id: external_exports.string(),
|
|
17951
18129
|
kind: external_exports.string(),
|
|
17952
18130
|
result: external_exports.object({
|
|
17953
|
-
app: AppResponseSchema.shape.app.optional(),
|
|
18131
|
+
app: AppResponseSchema.shape.app.extend({ warnings: AppResponseSchema.shape.app.shape.warnings.optional() }).optional(),
|
|
17954
18132
|
/** `key` is the plaintext. It is shown this once; the gateway keeps only a hash. */
|
|
17955
18133
|
api_key: CreatedApiKeySchema.nullable().optional()
|
|
17956
18134
|
})
|
|
@@ -18271,6 +18449,9 @@ var GatewayClientHeadersSchema = external_exports.object({
|
|
|
18271
18449
|
"x-app-version": external_exports.string().optional().meta({
|
|
18272
18450
|
description: "Required for gateway-token clients; optional for issuer-less API-key clients."
|
|
18273
18451
|
}),
|
|
18452
|
+
"x-appattest-proof": external_exports.string().regex(/^v1\.[0-9]{1,12}\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]+$/).optional().meta({
|
|
18453
|
+
description: "Required on every request sent with the gateway token of an App Attest application whose authentication.app_attest.verify_on is every_request; ignored, and never forwarded, where verify_on is token_exchange and for API-key clients. Format `v1.<timestamp>.<nonce>.<assertion>`: decimal unix seconds from the device clock (signed, but not compared with the gateway's clock), 16 random bytes as base64url without padding, and the CBOR assertion from DCAppAttestService.generateAssertion as base64url without padding. The assertion's client data is the SHA-256 of these lines joined by \\n with no trailing newline: `appaigateway-request-v1`, the app id, the upper-case HTTP method, the URL path and query exactly as sent, base64url(sha256(body bytes)) (an absent body hashes as empty), base64url(sha256(the bearer token)), the timestamp, and the nonce. Missing answers 401 assertion_required; refused answers 403 attest_failed. The AppAIGateway Swift package 1.4 or later computes it."
|
|
18454
|
+
}),
|
|
18274
18455
|
"x-end-user-id": external_exports.string().optional().meta({
|
|
18275
18456
|
description: "Optional configured end-user identity for issuer-less API-key applications."
|
|
18276
18457
|
})
|
|
@@ -18391,7 +18572,7 @@ var CATALOG = {
|
|
|
18391
18572
|
path: "/v1/apps/{app}/auth/token",
|
|
18392
18573
|
tags: ["Application authentication"],
|
|
18393
18574
|
summary: "Exchange issuer identity and client proof for a gateway token",
|
|
18394
|
-
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.",
|
|
18575
|
+
description: "App Attest clients send key_id, assertion, and challenge; the assertion is verified here whatever the application's verify_on, and the answer's verify_on says whether requests sent with the token must also carry X-AppAttest-Proof. Issuer-backed API-key clients send api_key and issuer_token. The verified issuer claim is always the resulting user identity.",
|
|
18395
18576
|
security: "public",
|
|
18396
18577
|
params: APP_PARAM,
|
|
18397
18578
|
request: external_exports.union([AppAttestTokenRequestSchema, ApiKeyTokenRequestSchema]),
|
|
@@ -18404,8 +18585,10 @@ var CATALOG = {
|
|
|
18404
18585
|
path: "/v1/apps/{app}/me",
|
|
18405
18586
|
tags: ["Application"],
|
|
18406
18587
|
summary: "Get the current user's limits, spend and block state",
|
|
18588
|
+
description: "On an App Attest application the request limits and, by default, the budget are the install's; install_id names it. Signed with X-AppAttest-Proof like every other request where the application verifies App Attest on every request.",
|
|
18407
18589
|
security: "gateway",
|
|
18408
18590
|
params: APP_PARAM,
|
|
18591
|
+
headers: GatewayClientHeadersSchema,
|
|
18409
18592
|
response: CurrentUserResponseSchema,
|
|
18410
18593
|
responseDescription: "Current user state.",
|
|
18411
18594
|
errors: { 402: NO_PLAN, 404: NO_END_USERS }
|
|
@@ -18868,6 +19051,7 @@ var CATALOG = {
|
|
|
18868
19051
|
status: external_exports.enum(USAGE_STATUSES).optional(),
|
|
18869
19052
|
provider: external_exports.string().optional(),
|
|
18870
19053
|
user: external_exports.string().optional(),
|
|
19054
|
+
install: external_exports.string().optional().meta({ description: "Only events of this install: an App Attest key id." }),
|
|
18871
19055
|
model: external_exports.string().optional(),
|
|
18872
19056
|
before_id: BeforeIdSchema
|
|
18873
19057
|
}),
|
|
@@ -19046,7 +19230,7 @@ var CATALOG = {
|
|
|
19046
19230
|
security: "management",
|
|
19047
19231
|
params: APP_PARAM,
|
|
19048
19232
|
query: RangeQuerySchema.extend({
|
|
19049
|
-
by: external_exports.enum(USAGE_BREAKDOWN_DIMENSIONS).default("model").meta({ description: "Which dimension to group by. Defaults to model." }),
|
|
19233
|
+
by: external_exports.enum(USAGE_BREAKDOWN_DIMENSIONS).default("model").meta({ description: "Which dimension to group by. Defaults to model. install groups an App Attest application's traffic by the install \u2014 the App Attest key \u2014 that made it; server applications have none." }),
|
|
19050
19234
|
limit: PageLimitSchema
|
|
19051
19235
|
}),
|
|
19052
19236
|
response: BreakdownResponseSchema,
|
|
@@ -21606,7 +21790,12 @@ function list(command, result, style2) {
|
|
|
21606
21790
|
}
|
|
21607
21791
|
function show(command, result, style2) {
|
|
21608
21792
|
if (command === "app show" && "app" in result && !("guidance" in result)) {
|
|
21609
|
-
return [
|
|
21793
|
+
return [
|
|
21794
|
+
style2.headline(`${result.app.name} (${result.app.id})`),
|
|
21795
|
+
...warnings(result.app.warnings ?? [], style2),
|
|
21796
|
+
"",
|
|
21797
|
+
style2.json(result)
|
|
21798
|
+
];
|
|
21610
21799
|
}
|
|
21611
21800
|
if ("slug" in result)
|
|
21612
21801
|
return [
|
|
@@ -21784,7 +21973,8 @@ function app(command, result, context, style2) {
|
|
|
21784
21973
|
],
|
|
21785
21974
|
style2
|
|
21786
21975
|
),
|
|
21787
|
-
...notes(result.limitations)
|
|
21976
|
+
...notes(result.limitations),
|
|
21977
|
+
...warnings(result.warnings ?? [], style2)
|
|
21788
21978
|
];
|
|
21789
21979
|
if ("definition" in result) {
|
|
21790
21980
|
const dryRun = "dryRun" in result;
|
|
@@ -21811,6 +22001,14 @@ function app(command, result, context, style2) {
|
|
|
21811
22001
|
return written(command, result, context, style2);
|
|
21812
22002
|
return [style2.json(result)];
|
|
21813
22003
|
}
|
|
22004
|
+
function warnings(values, style2) {
|
|
22005
|
+
if (!values.length) return [];
|
|
22006
|
+
return [
|
|
22007
|
+
"",
|
|
22008
|
+
style2.warn("Warnings"),
|
|
22009
|
+
...values.map((warning) => `- ${warning.message}${warning.docs_url ? ` See ${warning.docs_url}` : ""}`)
|
|
22010
|
+
];
|
|
22011
|
+
}
|
|
21814
22012
|
var notes = (values) => values.length ? ["", ...values.map((value) => `- ${value}`)] : [];
|
|
21815
22013
|
function written(command, result, context, style2) {
|
|
21816
22014
|
const parsed = AppConfigSchema.safeParse(result.app.config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maxceem/agw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Manage App AI Gateway from a terminal or coding agent: providers, iOS and server apps, usage, and self-hosted deployment on Cloudflare",
|
|
6
6
|
"keywords": [
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"bugs": "https://github.com/maxceem/app-ai-gateway/issues",
|
|
28
28
|
"upgradeFrom": [
|
|
29
|
+
"0.7.2",
|
|
30
|
+
"0.7.1",
|
|
29
31
|
"0.7.0",
|
|
30
32
|
"0.6.4",
|
|
31
33
|
"0.6.3",
|