@maxceem/agw 0.1.13 → 0.2.1

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 +248 -16
  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.1.13", wrangler: "4.96.0", upgradeFrom: ["0.1.13", "0.1.12", "0.1.11", "0.1.10", "0.1.9", "0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.1.13/gateway-0.1.13.tar.gz", sha256: "52bc83d5f54f79cd8bb1b868333648061dd444b41c34145e49698ad81bb794ac", manifest: "de2e8d1f4a9928be46b87571b7fcff33dc1d37c5bb008ba3261e97101f21781b" } };
9
+ var define_AGW_MANIFEST_default = { version: "0.2.1", wrangler: "4.96.0", upgradeFrom: ["0.2.1", "0.2.0", "0.1.13", "0.1.12", "0.1.11", "0.1.10", "0.1.9", "0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.2.1/gateway-0.2.1.tar.gz", sha256: "09d5d34a924dedf99ca6268d2b699e3d524b72d07a6c6853629dc6c8c9a42307", manifest: "f0a1e385150b39c24707296f9618dea4bb21b73822bb976a58ac03a1482408a0" } };
10
10
 
11
11
  // src/main.ts
12
12
  import { Writable as Writable2 } from "node:stream";
@@ -14525,6 +14525,99 @@ function date4(params) {
14525
14525
  // ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14526
14526
  config(en_default());
14527
14527
 
14528
+ // ../src/core/records.ts
14529
+ function asRecord(value) {
14530
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
14531
+ }
14532
+
14533
+ // ../src/core/cost-report.ts
14534
+ function finiteNumber(value) {
14535
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
14536
+ }
14537
+ function nonEmptyString(value) {
14538
+ return typeof value === "string" && value.length > 0 ? value : null;
14539
+ }
14540
+ function servedProviderOf(root) {
14541
+ const available = asRecord(asRecord(root.openrouter_metadata)?.endpoints)?.available;
14542
+ if (Array.isArray(available)) {
14543
+ for (const entry of available) {
14544
+ const endpoint = asRecord(entry);
14545
+ if (endpoint?.selected === true) {
14546
+ const named = nonEmptyString(endpoint.provider);
14547
+ if (named) return named;
14548
+ }
14549
+ }
14550
+ }
14551
+ return nonEmptyString(root.provider);
14552
+ }
14553
+ function upstreamInferenceCost(usage2) {
14554
+ const upstream = finiteNumber(asRecord(usage2?.cost_details)?.upstream_inference_cost);
14555
+ return upstream !== null && upstream > 0 ? upstream : null;
14556
+ }
14557
+ function reportedByok(root, usage2) {
14558
+ if (usage2?.is_byok === true) return true;
14559
+ if (asRecord(root.openrouter_metadata)?.is_byok === true) return true;
14560
+ return upstreamInferenceCost(usage2) !== null;
14561
+ }
14562
+ var OPENROUTER_COST_REPORT = {
14563
+ // Which host actually served a request is opt-in per request; without this
14564
+ // header the response names no serving provider at all.
14565
+ requestHeaders: { "x-openrouter-metadata": "enabled" },
14566
+ read(root, report) {
14567
+ let said = false;
14568
+ const usage2 = asRecord(root.usage);
14569
+ if (report.costUsd === null) {
14570
+ const cost = usage2 ? finiteNumber(usage2.cost) : null;
14571
+ if (cost !== null && cost >= 0) {
14572
+ report.costUsd = cost + (upstreamInferenceCost(usage2) ?? 0);
14573
+ said = true;
14574
+ }
14575
+ }
14576
+ if (report.servedProvider === null) {
14577
+ const served = servedProviderOf(root);
14578
+ if (served !== null) {
14579
+ report.servedProvider = served;
14580
+ said = true;
14581
+ }
14582
+ }
14583
+ if (report.servedModel === null) {
14584
+ const model = nonEmptyString(root.model);
14585
+ if (model !== null) {
14586
+ report.servedModel = model;
14587
+ said = true;
14588
+ }
14589
+ }
14590
+ if (report.credentialSource === null && reportedByok(root, usage2)) {
14591
+ report.credentialSource = "byok";
14592
+ said = true;
14593
+ }
14594
+ return said;
14595
+ }
14596
+ };
14597
+
14598
+ // ../src/shared/provider-auth.ts
14599
+ var PROVIDER_AUTH = {
14600
+ openai: { header: "authorization", scheme: "Bearer " },
14601
+ anthropic: { header: "x-api-key" },
14602
+ xai: { header: "authorization", scheme: "Bearer " },
14603
+ gemini: { header: "x-goog-api-key" },
14604
+ perplexity: { header: "authorization", scheme: "Bearer " },
14605
+ deepseek: { header: "authorization", scheme: "Bearer " },
14606
+ groq: { header: "authorization", scheme: "Bearer " },
14607
+ mistral: { header: "authorization", scheme: "Bearer " },
14608
+ together: { header: "authorization", scheme: "Bearer " },
14609
+ fireworks: { header: "authorization", scheme: "Bearer " },
14610
+ cerebras: { header: "authorization", scheme: "Bearer " },
14611
+ moonshot: { header: "authorization", scheme: "Bearer " },
14612
+ huggingface: { header: "authorization", scheme: "Bearer " },
14613
+ baseten: { header: "authorization", scheme: "Bearer " },
14614
+ bytedance: { header: "authorization", scheme: "Bearer " },
14615
+ openrouter: { header: "authorization", scheme: "Bearer " }
14616
+ };
14617
+ var PROVIDER_CREDENTIAL_HEADERS = [
14618
+ ...new Set(Object.values(PROVIDER_AUTH).map((auth) => auth.header.toLowerCase()))
14619
+ ];
14620
+
14528
14621
  // ../src/shared/capabilities.ts
14529
14622
  var PROVIDER_TYPES = [
14530
14623
  "openai",
@@ -14604,8 +14697,143 @@ var VERCEL_ROUTES = {
14604
14697
  moonshot: vercelRoute("moonshotai/")
14605
14698
  };
14606
14699
 
14607
- // ../src/core/providers.ts
14700
+ // ../src/shared/app-config.ts
14701
+ var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
14702
+ var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
14703
+ var ENDPOINT_SLUG = /^[a-z0-9-]{1,64}$/;
14608
14704
  var PROVIDER_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
14705
+ var HTTP_FIELD_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
14706
+ var RESERVED_END_USER_HEADERS = /* @__PURE__ */ new Set([
14707
+ "authorization",
14708
+ ...PROVIDER_CREDENTIAL_HEADERS,
14709
+ "x-app-version",
14710
+ "content-type",
14711
+ "content-length",
14712
+ "host"
14713
+ ]);
14714
+
14715
+ // ../src/core/providers.ts
14716
+ var PROVIDER_REGISTRY = {
14717
+ openai: {
14718
+ directBaseUrl: "https://api.openai.com/",
14719
+ auth: PROVIDER_AUTH.openai,
14720
+ modelAuthor: "OpenAI"
14721
+ },
14722
+ anthropic: {
14723
+ directBaseUrl: "https://api.anthropic.com/",
14724
+ auth: PROVIDER_AUTH.anthropic,
14725
+ modelAuthor: "Anthropic",
14726
+ // Anthropic refuses any request without a version header, probe included —
14727
+ // and a probe has no client to send one for it. Deliberately not a
14728
+ // `requestHeaders` entry: on live traffic this is the *client's* API version,
14729
+ // set by every Anthropic SDK, and pinning it server-side would strip that
14730
+ // choice and silently downgrade every request to the oldest version.
14731
+ probeHeaders: { "anthropic-version": "2023-06-01" }
14732
+ },
14733
+ xai: {
14734
+ directBaseUrl: "https://api.x.ai/",
14735
+ auth: PROVIDER_AUTH.xai,
14736
+ modelAuthor: "xAI"
14737
+ },
14738
+ gemini: {
14739
+ directBaseUrl: "https://generativelanguage.googleapis.com/",
14740
+ auth: PROVIDER_AUTH.gemini,
14741
+ modelAuthor: "Google"
14742
+ },
14743
+ perplexity: {
14744
+ directBaseUrl: "https://api.perplexity.ai/",
14745
+ auth: PROVIDER_AUTH.perplexity,
14746
+ modelAuthor: "Perplexity"
14747
+ },
14748
+ // OpenAI-compatible chat-completions services. `modelAuthor` is set only
14749
+ // where one answer is right for every model the type serves; the hosts below
14750
+ // that carry no author serve other labs' open-weight models, and authorship
14751
+ // comes from the catalog entry per model instead.
14752
+ deepseek: {
14753
+ // No `v1` segment: DeepSeek documents the bare origin as its OpenAI base
14754
+ // URL, and `https://api.deepseek.com/anthropic` for the Anthropic format.
14755
+ directBaseUrl: "https://api.deepseek.com/",
14756
+ auth: PROVIDER_AUTH.deepseek,
14757
+ modelAuthor: "DeepSeek"
14758
+ },
14759
+ groq: {
14760
+ // Groq's OpenAI-compatible surface lives under `openai/v1/`, so the client
14761
+ // path is `openai/v1/chat/completions` rather than `v1/chat/completions`.
14762
+ directBaseUrl: "https://api.groq.com/",
14763
+ auth: PROVIDER_AUTH.groq
14764
+ },
14765
+ mistral: {
14766
+ directBaseUrl: "https://api.mistral.ai/",
14767
+ auth: PROVIDER_AUTH.mistral,
14768
+ modelAuthor: "Mistral"
14769
+ },
14770
+ together: {
14771
+ // `api.together.ai`, not the `.xyz` host older SDKs default to: the current
14772
+ // OpenAI-compatibility guide names this one and warns against the other.
14773
+ directBaseUrl: "https://api.together.ai/",
14774
+ auth: PROVIDER_AUTH.together
14775
+ },
14776
+ fireworks: {
14777
+ // The inference plane is `/inference/v1`; `/v1` on the same host is the
14778
+ // control plane, so the client path is `inference/v1/chat/completions`.
14779
+ directBaseUrl: "https://api.fireworks.ai/",
14780
+ auth: PROVIDER_AUTH.fireworks
14781
+ },
14782
+ cerebras: {
14783
+ directBaseUrl: "https://api.cerebras.ai/",
14784
+ auth: PROVIDER_AUTH.cerebras
14785
+ },
14786
+ moonshot: {
14787
+ // The international host. `api.moonshot.cn` is the separate China platform
14788
+ // and is not reachable with a key issued for this one.
14789
+ directBaseUrl: "https://api.moonshot.ai/",
14790
+ auth: PROVIDER_AUTH.moonshot,
14791
+ modelAuthor: "Moonshot AI"
14792
+ },
14793
+ huggingface: {
14794
+ // The Inference Providers router: one OpenAI-compatible surface in front of
14795
+ // many upstreams, so it has no author of its own and no stable per-model
14796
+ // price — the router picks the upstream, and the same model ID costs an
14797
+ // order of magnitude more on some of them than others. It ships with no
14798
+ // catalog section for that reason; see `catalogPrice` in usage.ts.
14799
+ directBaseUrl: "https://router.huggingface.co/",
14800
+ auth: PROVIDER_AUTH.huggingface
14801
+ },
14802
+ baseten: {
14803
+ // The Model APIs inference host. `api.baseten.co` is the management plane
14804
+ // for dedicated deployments and answers to different paths entirely.
14805
+ directBaseUrl: "https://inference.baseten.co/",
14806
+ auth: PROVIDER_AUTH.baseten
14807
+ },
14808
+ bytedance: {
14809
+ // BytePlus ModelArk, the international edition: `/api/v3` is its version
14810
+ // segment, so client paths carry no `v1`. `ark.cn-beijing.volces.com` is
14811
+ // the separate China platform, and `ark.eu-west.bytepluses.com` is a second
14812
+ // region — keys and catalogs are region-isolated, so reaching either needs
14813
+ // the per-row base URL override that Stage 6 introduces.
14814
+ directBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/",
14815
+ auth: PROVIDER_AUTH.bytedance
14816
+ },
14817
+ openrouter: {
14818
+ // An aggregator treated as a provider type: it is the counterparty that
14819
+ // bills the organization, and its slugs (`google/gemini-3.6-flash`) are the
14820
+ // canonical model IDs here — there is no underlying ID to translate to.
14821
+ // `/api/v1` is its documented server URL, so the client path is
14822
+ // `v1/chat/completions` under this origin.
14823
+ directBaseUrl: "https://openrouter.ai/api/",
14824
+ auth: PROVIDER_AUTH.openrouter,
14825
+ // Every chat-completions response carries `usage.cost`: what OpenRouter
14826
+ // actually charged for that request, which beats any local estimate of a
14827
+ // catalog this deployment does not track. It is also the only way to bill
14828
+ // the type at all, since no static price list covers 400 models across
14829
+ // every lab. The declaration owns the parsing and the header that asks for
14830
+ // the routing metadata — see `src/core/cost-report.ts`.
14831
+ costReport: OPENROUTER_COST_REPORT,
14832
+ // No `modelAuthor`: an aggregator serves everyone's models, and the slug
14833
+ // namespace answers per model instead.
14834
+ authorNamespacedModels: true
14835
+ }
14836
+ };
14609
14837
 
14610
14838
  // ../src/core/origin-guard.ts
14611
14839
  var MAX_BASE_URL_LENGTH = 200;
@@ -14714,10 +14942,6 @@ function checkOperatorBaseUrl(input) {
14714
14942
  return { ok: true, baseUrl };
14715
14943
  }
14716
14944
 
14717
- // ../src/core/types.ts
14718
- var ISSUER_PROVIDERS = ["firebase", "supabase", "auth0", "clerk", "custom"];
14719
- var ENTITLEMENT_CHECKS = ["revenuecat", "custom"];
14720
-
14721
14945
  // ../src/contracts/schemas.ts
14722
14946
  var ProviderTypeSchema = external_exports.enum(PROVIDER_TYPES);
14723
14947
  var NullableRequestLimit = external_exports.number().int().positive().nullable();
@@ -14751,7 +14975,7 @@ var IssuerAuthenticationSchema = external_exports.object({
14751
14975
  provider: external_exports.enum(ISSUER_PROVIDERS).optional(),
14752
14976
  entitlement: external_exports.enum(ENTITLEMENT_CHECKS).optional()
14753
14977
  }).strict();
14754
- var EndUserHeaderSchema = external_exports.string().trim().min(1).max(64).regex(/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/, { error: "header must be a valid HTTP header name" });
14978
+ var EndUserHeaderSchema = external_exports.string().trim().min(1).max(64).regex(HTTP_FIELD_NAME, { error: "header must be a valid HTTP header name" });
14755
14979
  var HeaderEndUserSchema = external_exports.object({
14756
14980
  source: external_exports.literal("header"),
14757
14981
  header: EndUserHeaderSchema
@@ -14849,7 +15073,7 @@ var AppConfigSchema = external_exports.object({
14849
15073
  per_user: LimitScopeSchema,
14850
15074
  per_app: LimitScopeSchema
14851
15075
  }).optional(),
14852
- endpoints: external_exports.record(external_exports.string().regex(/^[a-z0-9-]{1,64}$/), EndpointSchema).optional()
15076
+ endpoints: external_exports.record(external_exports.string().regex(ENDPOINT_SLUG), EndpointSchema).optional()
14853
15077
  }).meta({ id: "AppConfig" });
14854
15078
  var AppWriteSchema = external_exports.object({
14855
15079
  name: external_exports.string().min(1).max(100),
@@ -14972,13 +15196,16 @@ var ProviderGatewayTestRequestSchema = external_exports.discriminatedUnion("type
14972
15196
  error: "Provider gateway type must be one of cf_aig, vercel"
14973
15197
  }).meta({ id: "ProviderGatewayTestRequest" });
14974
15198
  var ProviderGatewayUpdateRequestSchema = external_exports.object({
14975
- name: ProviderNameSchema
15199
+ name: ProviderNameSchema,
15200
+ revision: external_exports.number().int().positive().meta({ description: "The gateway revision returned by the read this edit is based on." })
14976
15201
  }).strict().meta({ id: "ProviderGatewayUpdateRequest" });
14977
15202
  var ProviderGatewayRotateRequestSchema = external_exports.object({
14978
- token: ProviderSecretSchema
15203
+ token: ProviderSecretSchema,
15204
+ revision: external_exports.number().int().positive().meta({ description: "The gateway revision returned by the read this rotation is based on." })
14979
15205
  }).strict().meta({ id: "ProviderGatewayRotateRequest" });
14980
15206
  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
15207
  var ProviderUpdateRequestSchema = external_exports.object({
15208
+ revision: external_exports.number().int().positive().meta({ description: "The provider revision returned by the read this update is based on." }),
14982
15209
  name: ProviderNameSchema.optional(),
14983
15210
  secret: ProviderSecretSchema.optional(),
14984
15211
  /** A full replace; `null` clears the row's routing configuration. */
@@ -15222,6 +15449,7 @@ var ProviderSummarySchema = external_exports.object({
15222
15449
  example: "https://my-resource.openai.azure.com/openai/v1/"
15223
15450
  }),
15224
15451
  pricing: ProviderPricingSchema.nullable(),
15452
+ revision: external_exports.number().int().positive(),
15225
15453
  status: external_exports.enum(["active", "disabled"]).meta({
15226
15454
  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
15455
  }),
@@ -15262,6 +15490,7 @@ var providerGatewayFields = {
15262
15490
  referencedCount: external_exports.number().int().nonnegative().meta({
15263
15491
  description: "All provider instances referencing this gateway, including disabled rows retained for re-enabling. Deletion is refused while this is above zero."
15264
15492
  }),
15493
+ revision: external_exports.number().int().positive(),
15265
15494
  status: external_exports.enum(["active", "revoked"]),
15266
15495
  createdAt: external_exports.string(),
15267
15496
  updatedAt: external_exports.string(),
@@ -16477,9 +16706,9 @@ async function resourceCommand(ctx, command, args, flags) {
16477
16706
  if (!existingGateway) fail("invalid_arguments", "Supply the gateway to rotate.");
16478
16707
  const value2 = await secret(flags, "Gateway token");
16479
16708
  if (flags.browser)
16480
- return ctx.operation("provider-gateway.rotate-key", { id: existingGateway.id });
16709
+ return ctx.operation("provider-gateway.rotate-key", { id: existingGateway.id, revision: existingGateway.revision });
16481
16710
  return (await ctx.call("rotateProviderGateway", [existingGateway.id], {
16482
- body: { token: value2 }
16711
+ body: { token: value2, revision: existingGateway.revision }
16483
16712
  })).data;
16484
16713
  }
16485
16714
  if (!existingProvider) fail("invalid_arguments", "Supply the provider to rotate.");
@@ -16490,16 +16719,17 @@ async function resourceCommand(ctx, command, args, flags) {
16490
16719
  );
16491
16720
  const value = await secret(flags, "Provider API key");
16492
16721
  if (flags.browser)
16493
- return ctx.operation("provider.rotate-key", { id: existingProvider.id });
16722
+ return ctx.operation("provider.rotate-key", { id: existingProvider.id, revision: existingProvider.revision });
16494
16723
  return (await ctx.call("updateProvider", [existingProvider.id], {
16495
- body: { secret: value }
16724
+ body: { secret: value, revision: existingProvider.revision }
16496
16725
  })).data;
16497
16726
  }
16498
16727
  if (action === "update") {
16499
16728
  if (gateway) {
16500
16729
  if (!existingGateway) fail("invalid_arguments", "Supply the gateway to update.");
16501
16730
  const body2 = validate(ProviderGatewayUpdateRequestSchema, {
16502
- name: await required2(flags, "name")
16731
+ name: await required2(flags, "name"),
16732
+ revision: existingGateway.revision
16503
16733
  });
16504
16734
  return (await ctx.call("updateProviderGateway", [existingGateway.id], { body: body2 })).data;
16505
16735
  }
@@ -16532,6 +16762,7 @@ async function resourceCommand(ctx, command, args, flags) {
16532
16762
  if (flags["base-url"] || flags["clear-base-url"]) {
16533
16763
  validate(ProviderUpdateRequestSchema, {
16534
16764
  ...draft,
16765
+ revision: existingProvider.revision,
16535
16766
  secret: "validation-placeholder"
16536
16767
  });
16537
16768
  const value = await secret(flags, "Provider API key");
@@ -16539,10 +16770,11 @@ async function resourceCommand(ctx, command, args, flags) {
16539
16770
  }
16540
16771
  const body = validate(ProviderUpdateRequestSchema, {
16541
16772
  ...draft,
16773
+ revision: existingProvider.revision,
16542
16774
  ...flags.browser ? { secret: "validation-placeholder" } : {}
16543
16775
  });
16544
16776
  if (flags.browser)
16545
- return ctx.operation("provider.update", { id: existingProvider.id, ...draft });
16777
+ return ctx.operation("provider.update", { id: existingProvider.id, revision: existingProvider.revision, ...draft });
16546
16778
  return (await ctx.call("updateProvider", [existingProvider.id], { body })).data;
16547
16779
  }
16548
16780
  fail("unknown_command", "Unknown command.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxceem/agw",
3
- "version": "0.1.13",
3
+ "version": "0.2.1",
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.2.0",
30
+ "0.1.13",
29
31
  "0.1.12",
30
32
  "0.1.11",
31
33
  "0.1.10",