@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.
Files changed (2) hide show
  1. package/dist/agw.mjs +225 -27
  2. 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.7.1", wrangler: "4.96.0", upgradeFrom: ["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.7.1/gateway-0.7.1.tar.gz", sha256: "1b3ac4141c993b30bf1e90404dee7a21785a38b686c3f2562ec7b0c07fd4534d", manifest: "8455d531a2dc7df63af0fcbe1d8a181c3b6c74796e5f5a69cacf7890ca2dc42a" } };
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: LimitScopeSchema.prefault(UNLIMITED_SCOPE),
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 && ["team-id", "bundle-id", "attest-environments"].some((k) => flags[k]))
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" && ["team-id", "bundle-id", "attest-environments"].some((k) => flags[k]))
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.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.`
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, either user or app; a rate_limited rejection carries scope naming the endpoint policy that refused, plus limit, windowSeconds, retryAfterSeconds and resetAt."
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 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." }),
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-user per-minute limit this app sets. Null means unlimited." }),
17283
- requests_per_day: external_exports.number().nullable().meta({ description: "The per-user per-day limit this app sets. Null means unlimited." }),
17284
- monthly_cost_usd: external_exports.number().meta({ description: "What this user's traffic has cost so far in the current UTC calendar month." }),
17285
- monthly_budget_usd: external_exports.number().nullable().meta({ description: "The per-user monthly spending budget this app sets. Null means unlimited." }),
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
- attest_registered: external_exports.boolean(),
17822
- attest_counter: external_exports.number(),
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
- app: AppResponseSchema.shape.app.optional(),
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 [style2.headline(`${result.app.name} (${result.app.id})`), "", style2.json(result)];
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.7.1",
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",