@maxceem/agw 0.7.0 → 0.7.2

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 +151 -11
  2. package/package.json +14 -2
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.0", wrangler: "4.96.0", upgradeFrom: ["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.0/gateway-0.7.0.tar.gz", sha256: "9cbcb05a1b611470c03e522af7e34d786a4d7e9889b589bac7603695187575bd", manifest: "707e02868d2133307ff4d71a3fbcb7abe6d0b62cbc9498fab5b7d3f6d2d96fb0" } };
9
+ var define_AGW_MANIFEST_default = { version: "0.7.2", wrangler: "4.96.0", upgradeFrom: ["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.7.2/gateway-0.7.2.tar.gz", sha256: "6673bdde6e77fcdbe5da4c13013d084e99354b67cc4ba43704b9dc81b40e1ff7", manifest: "ff987daf1a4740041f3410ac7f852a3b0bce4d98a8fc9abe2cf835f35b2d4d3a" } };
10
10
 
11
11
  // src/main.ts
12
12
  import { Writable as Writable2 } from "node:stream";
@@ -14699,7 +14699,9 @@ var GATEWAY_DESCRIPTORS = {
14699
14699
  { key: "accountId", label: "Cloudflare Account ID" },
14700
14700
  { key: "gatewayId", label: "Cloudflare Gateway ID" }
14701
14701
  ],
14702
- routes: CF_AIG_ROUTES
14702
+ routes: CF_AIG_ROUTES,
14703
+ // A proxy in front of the provider's own API: the body is the provider's.
14704
+ relaysProviderResponses: true
14703
14705
  },
14704
14706
  vercel: {
14705
14707
  label: "Vercel AI Gateway",
@@ -14715,7 +14717,11 @@ var GATEWAY_DESCRIPTORS = {
14715
14717
  description: "Vercel's origin is fixed in adapter code, so it has no configuration of its own."
14716
14718
  }),
14717
14719
  connectionFields: [],
14718
- routes: VERCEL_ROUTES
14720
+ routes: VERCEL_ROUTES,
14721
+ // Vercel answers through its own client APIs, translating between them and
14722
+ // the serving provider's protocol, so a provider's own fields are not part
14723
+ // of the contract.
14724
+ relaysProviderResponses: false
14719
14725
  }
14720
14726
  };
14721
14727
  var GATEWAY_TYPES = Object.keys(GATEWAY_DESCRIPTORS);
@@ -14767,9 +14773,18 @@ function reportedByok(root, usage2) {
14767
14773
  return upstreamInferenceCost(usage2) !== null;
14768
14774
  }
14769
14775
  var OPENROUTER_COST_REPORT = {
14776
+ // Only chat completions carry `usage.cost`, which is why it is the only
14777
+ // style the provider type offers at all.
14778
+ styles: ["chat_completions"],
14770
14779
  // Which host actually served a request is opt-in per request; without this
14771
14780
  // header the response names no serving provider at all.
14772
14781
  requestHeaders: { "x-openrouter-metadata": "enabled" },
14782
+ // `credentialSource` is deliberately not required: it is a claim that is
14783
+ // simply absent on a non-BYOK request, so waiting for it would mean walking
14784
+ // every chunk of every stream. The response that carries it carries the cost
14785
+ // too.
14786
+ settled: (report) => report.costUsd !== null && report.servedProvider !== null && report.servedModel !== null,
14787
+ readsCredentialSource: true,
14773
14788
  read(root, report) {
14774
14789
  let said = false;
14775
14790
  const usage2 = asRecord(root.usage);
@@ -14801,6 +14816,23 @@ var OPENROUTER_COST_REPORT = {
14801
14816
  return said;
14802
14817
  }
14803
14818
  };
14819
+ var XAI_TICKS_PER_USD = 1e10;
14820
+ var XAI_COST_REPORT = {
14821
+ // Images and videos are provider-native operations, `other` to this gateway.
14822
+ // Its Anthropic-compatible Messages API and `v1/stt` are not documented to
14823
+ // report a cost, so they keep billing on the catalog.
14824
+ styles: ["chat_completions", "responses", "other"],
14825
+ settled: (report) => report.costUsd !== null,
14826
+ readsCredentialSource: false,
14827
+ read(root, report) {
14828
+ if (report.costUsd !== null) return false;
14829
+ const usage2 = asRecord(root.usage) ?? asRecord(asRecord(root.response)?.usage);
14830
+ const ticks = usage2 ? finiteNumber(usage2.cost_in_usd_ticks) : null;
14831
+ if (ticks === null || ticks < 0) return false;
14832
+ report.costUsd = ticks / XAI_TICKS_PER_USD;
14833
+ return true;
14834
+ }
14835
+ };
14804
14836
 
14805
14837
  // ../src/shared/providers.ts
14806
14838
  var PROVIDER_DESCRIPTORS = {
@@ -14809,6 +14841,13 @@ var PROVIDER_DESCRIPTORS = {
14809
14841
  directBaseUrl: "https://api.openai.com/",
14810
14842
  auth: { header: "authorization", scheme: "Bearer " },
14811
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
+ ],
14812
14851
  probePath: "v1/models",
14813
14852
  examplePath: "v1/responses",
14814
14853
  // OpenAI's chat-completions surface deprecated `max_tokens` in favour of
@@ -14818,6 +14857,9 @@ var PROVIDER_DESCRIPTORS = {
14818
14857
  // The two types whose Responses and transcription request shapes the
14819
14858
  // gateway composes itself for custom endpoints.
14820
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" },
14821
14863
  modelAuthor: "OpenAI"
14822
14864
  },
14823
14865
  anthropic: {
@@ -14840,10 +14882,23 @@ var PROVIDER_DESCRIPTORS = {
14840
14882
  directBaseUrl: "https://api.x.ai/",
14841
14883
  auth: { header: "authorization", scheme: "Bearer " },
14842
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
+ ],
14843
14893
  probePath: "v1/models",
14844
14894
  // Native provider paths: xAI transcribes at `v1/stt`, where OpenAI serves
14845
14895
  // `v1/audio/transcriptions`.
14846
14896
  endpointPaths: { responses: "v1/responses", audio_transcription: "v1/stt" },
14897
+ // Every Chat Completions, Responses, image and video answer carries
14898
+ // `usage.cost_in_usd_ticks`, what xAI actually billed for it. An image
14899
+ // reports nothing else, so this is the only way to bill one; on the token
14900
+ // APIs it beats the catalog's estimate, which knows no discounts.
14901
+ costReport: XAI_COST_REPORT,
14847
14902
  modelAuthor: "xAI"
14848
14903
  },
14849
14904
  gemini: {
@@ -14851,6 +14906,13 @@ var PROVIDER_DESCRIPTORS = {
14851
14906
  directBaseUrl: "https://generativelanguage.googleapis.com/",
14852
14907
  auth: { header: "x-goog-api-key" },
14853
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
+ ],
14854
14916
  probePath: "v1beta/models",
14855
14917
  // Native Gemini generation requests carry the model in the URL rather than
14856
14918
  // in the JSON body, so there is no example path without a model.
@@ -14862,6 +14924,15 @@ var PROVIDER_DESCRIPTORS = {
14862
14924
  directBaseUrl: "https://api.perplexity.ai/",
14863
14925
  auth: { header: "authorization", scheme: "Bearer " },
14864
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
+ ],
14865
14936
  // No `probePath`: Perplexity has no unmetered authenticated endpoint.
14866
14937
  examplePath: "chat/completions",
14867
14938
  modelAuthor: "Perplexity"
@@ -14877,6 +14948,13 @@ var PROVIDER_DESCRIPTORS = {
14877
14948
  directBaseUrl: "https://api.deepseek.com/",
14878
14949
  auth: { header: "authorization", scheme: "Bearer " },
14879
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
+ ],
14880
14958
  // DeepSeek's OpenAI base URL carries no `v1` segment.
14881
14959
  probePath: "models",
14882
14960
  examplePath: "chat/completions",
@@ -14889,6 +14967,15 @@ var PROVIDER_DESCRIPTORS = {
14889
14967
  directBaseUrl: "https://api.groq.com/",
14890
14968
  auth: { header: "authorization", scheme: "Bearer " },
14891
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"],
14892
14979
  // Groq's OpenAI-compatible surface is namespaced under `openai/`.
14893
14980
  probePath: "openai/v1/models",
14894
14981
  examplePath: "openai/v1/chat/completions"
@@ -14898,6 +14985,12 @@ var PROVIDER_DESCRIPTORS = {
14898
14985
  directBaseUrl: "https://api.mistral.ai/",
14899
14986
  auth: { header: "authorization", scheme: "Bearer " },
14900
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
+ ],
14901
14994
  probePath: "v1/models",
14902
14995
  modelAuthor: "Mistral"
14903
14996
  },
@@ -14908,6 +15001,21 @@ var PROVIDER_DESCRIPTORS = {
14908
15001
  directBaseUrl: "https://api.together.ai/",
14909
15002
  auth: { header: "authorization", scheme: "Bearer " },
14910
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
+ ],
14911
15019
  probePath: "v1/models"
14912
15020
  },
14913
15021
  fireworks: {
@@ -14917,6 +15025,13 @@ var PROVIDER_DESCRIPTORS = {
14917
15025
  directBaseUrl: "https://api.fireworks.ai/",
14918
15026
  auth: { header: "authorization", scheme: "Bearer " },
14919
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
+ ],
14920
15035
  // No `probePath`: its list-models call is `v1/accounts/{account}/models`,
14921
15036
  // and the account id cannot be derived from the key. Nothing under
14922
15037
  // `inference/v1/` is documented as a GET.
@@ -14927,6 +15042,8 @@ var PROVIDER_DESCRIPTORS = {
14927
15042
  directBaseUrl: "https://api.cerebras.ai/",
14928
15043
  auth: { header: "authorization", scheme: "Bearer " },
14929
15044
  nativeClampStyle: "chat_completions",
15045
+ // Legacy completions.
15046
+ inferencePaths: [{ path: "v1/completions", clamp: "chat_completions" }],
14930
15047
  probePath: "v1/models"
14931
15048
  },
14932
15049
  moonshot: {
@@ -14936,6 +15053,8 @@ var PROVIDER_DESCRIPTORS = {
14936
15053
  directBaseUrl: "https://api.moonshot.ai/",
14937
15054
  auth: { header: "authorization", scheme: "Bearer " },
14938
15055
  nativeClampStyle: "chat_completions",
15056
+ // The Anthropic-format API.
15057
+ inferencePaths: [{ path: "anthropic/v1/messages", clamp: "anthropic" }],
14939
15058
  probePath: "v1/models",
14940
15059
  modelAuthor: "Moonshot AI"
14941
15060
  },
@@ -14973,6 +15092,13 @@ var PROVIDER_DESCRIPTORS = {
14973
15092
  directBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/",
14974
15093
  auth: { header: "authorization", scheme: "Bearer " },
14975
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
+ ],
14976
15102
  // No `probePath`: ModelArk publishes no list-models call, and its own SDK
14977
15103
  // has no models resource. It also authenticates before it routes, so every
14978
15104
  // path answers the same 401 and a probe would prove nothing about the key.
@@ -15015,9 +15141,6 @@ var PROVIDER_TYPES = Object.keys(PROVIDER_DESCRIPTORS);
15015
15141
  function providerDescriptor(type) {
15016
15142
  return PROVIDER_DESCRIPTORS[type];
15017
15143
  }
15018
- function isProviderType(value) {
15019
- return typeof value === "string" && Object.hasOwn(PROVIDER_DESCRIPTORS, value);
15020
- }
15021
15144
  var PROVIDER_CREDENTIAL_HEADERS = [
15022
15145
  ...new Set(
15023
15146
  PROVIDER_TYPES.map((type) => providerDescriptor(type).auth.header.toLowerCase())
@@ -15033,10 +15156,6 @@ function providerCapability(provider) {
15033
15156
  var ENDPOINT_PROVIDER_TYPES = PROVIDER_TYPES.filter(
15034
15157
  (type) => providerCapability(type).endpointStyles.length > 0
15035
15158
  );
15036
- function reportsCost(type) {
15037
- return isProviderType(type) && providerDescriptor(type).costReport !== void 0;
15038
- }
15039
- var COST_REPORTING_PROVIDER_TYPES = PROVIDER_TYPES.filter(reportsCost);
15040
15159
 
15041
15160
  // ../src/shared/app-status.ts
15042
15161
  var APP_STATUSES = ["active", "disabled"];
@@ -16676,6 +16795,12 @@ var StateStore = class {
16676
16795
  function selectedProviderPolicies(routing) {
16677
16796
  return routing.providers.mode === "selected" ? routing.providers.selected : {};
16678
16797
  }
16798
+ var DEFAULT_INFERENCE_PATHS = new Map(
16799
+ PROVIDER_TYPES.map((type) => [
16800
+ type,
16801
+ (providerDescriptor(type).inferencePaths ?? []).map((entry) => typeof entry === "string" ? { path: entry, clamp: "none" } : { ...entry })
16802
+ ])
16803
+ );
16679
16804
 
16680
16805
  // ../src/shared/protocols.ts
16681
16806
  var PROTOCOLS = {
@@ -17849,7 +17974,7 @@ var UsageRepriceResponseSchema = external_exports.object({
17849
17974
  description: "Matched events that carried no readable usage, and so were repriced to the zero their zero counts imply while keeping whatever cost_source they had. A non-zero number means the month contains spend nothing could meter, which repricing cannot fix and must not appear to have fixed."
17850
17975
  }),
17851
17976
  unpriced_events: external_exports.number().int().meta({
17852
- description: "Dry-run only: matched events whose serving instance can no longer price them."
17977
+ description: "Dry-run only: matched events whose serving instance can no longer price them, or that predate the modality breakdown their model's price depends on."
17853
17978
  }),
17854
17979
  unpriced_cost_usd: external_exports.number(),
17855
17980
  previous_cost_usd: external_exports.number(),
@@ -17859,10 +17984,25 @@ var UsageRepriceResponseSchema = external_exports.object({
17859
17984
  var ModelPriceSchema = external_exports.object({
17860
17985
  input: external_exports.number().optional(),
17861
17986
  output: external_exports.number().optional(),
17987
+ image_input: external_exports.number().optional().meta({
17988
+ description: "USD per 1M image input tokens, where they are priced apart from text input."
17989
+ }),
17990
+ audio_input: external_exports.number().optional().meta({
17991
+ description: "USD per 1M audio input tokens, where they are priced apart from text input."
17992
+ }),
17993
+ video_input: external_exports.number().optional().meta({
17994
+ description: "USD per 1M video input tokens, where they are priced apart from text input."
17995
+ }),
17996
+ image_output: external_exports.number().optional().meta({
17997
+ description: "USD per 1M generated image tokens, where they cost more than text output."
17998
+ }),
17862
17999
  cached_input: external_exports.number().optional(),
17863
18000
  cache_write: external_exports.number().optional(),
17864
18001
  per_minute: external_exports.number().optional(),
17865
18002
  per_hour: external_exports.number().optional(),
18003
+ per_image: external_exports.number().optional().meta({
18004
+ description: "USD per generated image, for a model billed per image rather than per token."
18005
+ }),
17866
18006
  long_context_threshold: external_exports.number().optional(),
17867
18007
  long_input: external_exports.number().optional(),
17868
18008
  long_output: external_exports.number().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxceem/agw",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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": [
@@ -25,7 +25,19 @@
25
25
  "directory": "cli"
26
26
  },
27
27
  "bugs": "https://github.com/maxceem/app-ai-gateway/issues",
28
- "upgradeFrom": ["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"],
28
+ "upgradeFrom": [
29
+ "0.7.1",
30
+ "0.7.0",
31
+ "0.6.4",
32
+ "0.6.3",
33
+ "0.6.2",
34
+ "0.6.1",
35
+ "0.6.0",
36
+ "0.5.2",
37
+ "0.5.1",
38
+ "0.5.0",
39
+ "0.4.0"
40
+ ],
29
41
  "license": "Apache-2.0",
30
42
  "bin": {
31
43
  "agw": "dist/agw.mjs"