zuplo 7.11.3 → 7.11.5

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.
@@ -177,7 +177,7 @@ const client = new BedrockRuntimeClient({
177
177
  // Unchanged from here down.
178
178
  const response = await client.send(
179
179
  new ConverseCommand({
180
- modelId: "bedrockruntime/us.anthropic.claude-haiku-4-5-20251001-v1:0",
180
+ modelId: "bedrockruntime/us.anthropic.claude-haiku-5-5",
181
181
  messages: [{ role: "user", content: [{ text: "Say hi" }] }],
182
182
  }),
183
183
  );
@@ -198,7 +198,7 @@ Apps reference models as `providerName/modelId`, where `providerName` is the
198
198
  name you gave the provider configuration:
199
199
 
200
200
  ```
201
- bedrockruntime/us.anthropic.claude-haiku-4-5-20251001-v1:0
201
+ bedrockruntime/us.anthropic.claude-haiku-5-5
202
202
  ```
203
203
 
204
204
  The gateway strips its own prefix and sends AWS the model ID exactly as you
@@ -25,7 +25,7 @@ runtime feature, and these clients aren't among the supported clients documented
25
25
  on the [Azure AI](../azure-ai.mdx) or [Vertex AI](../vertex-ai.mdx) pages. Model
26
26
  references don't change—they stay the gateway's usual `providerName/deployment`
27
27
  for Azure (`azureai/my-gpt`) and `providerName/publisher/model` for Vertex
28
- (`vertexai/anthropic/claude-haiku-4-5@20251001`).
28
+ (`vertexai/anthropic/claude-haiku-5-5`).
29
29
 
30
30
  ## The policy
31
31
 
@@ -39,7 +39,7 @@ models are referenced like any model, as `providerName/model`.
39
39
 
40
40
  1. Configure the policy:
41
41
  - **Fallback**: the model attempted after a retryable error or timeout, for
42
- example `anthropic/claude-haiku-4-5`.
42
+ example `anthropic/claude-haiku-5-5`.
43
43
  - **Quota fallback**: the model used once a usage limit is exceeded, for
44
44
  example `openai/gpt-6-luna`.
45
45
  - **Fallback timeout (seconds)**: how long the primary call can run before
@@ -285,7 +285,7 @@ const anthropic = createAnthropic({
285
285
  });
286
286
 
287
287
  const { text } = await generateText({
288
- model: anthropic("vertexai/anthropic/claude-haiku-4-5@20251001"),
288
+ model: anthropic("vertexai/anthropic/claude-haiku-5-5"),
289
289
  prompt: "Write a one-sentence bedtime story about a unicorn.",
290
290
  });
291
291
  ```
@@ -79,7 +79,7 @@ use the app's API key and API URL instead.
79
79
  "ANTHROPIC_MODEL": "anthropic/claude-sonnet-5-5",
80
80
  "ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-5-5",
81
81
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-5-5",
82
- "ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4-5",
82
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-5-5",
83
83
  "ANTHROPIC_DEFAULT_FABLE_MODEL": "anthropic/claude-fable-5-1"
84
84
  },
85
85
  "modelOverrides": {
@@ -166,7 +166,7 @@ In the **JSON** editor:
166
166
  "ANTHROPIC_MODEL": "anthropic/claude-sonnet-5-5",
167
167
  "ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-5-5",
168
168
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-5-5",
169
- "ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4-5",
169
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-5-5",
170
170
  "ANTHROPIC_DEFAULT_FABLE_MODEL": "anthropic/claude-fable-5-1"
171
171
  },
172
172
  "modelOverrides": {
@@ -178,7 +178,7 @@ Two things make its setup different from every other provider:
178
178
  Every Vertex model ID is publisher-qualified, so a model reference has two
179
179
  slashes: a provider named `vertexai` serves `vertexai/google/gemini-3.7-flash`,
180
180
  `vertexai/qwen/qwen3-coder-480b-a35b-instruct-maas`, and
181
- `vertexai/anthropic/claude-haiku-4-5@20251001`. Model availability varies by
181
+ `vertexai/anthropic/claude-haiku-5-5`. Model availability varies by
182
182
  location—most Model Garden models are served only from the `global` location,
183
183
  and Google's regional endpoints serve Claude Sonnet 4.6 and earlier only, so
184
184
  `global` is the location that serves every Claude model. Vertex's Claude models
@@ -112,7 +112,7 @@ instead, with the model in the URL:
112
112
 
113
113
  ```
114
114
  https://aiplatform.googleapis.com
115
- /v1/projects/my-project-123/locations/global/publishers/anthropic/models/claude-haiku-4-5@20251001:rawPredict
115
+ /v1/projects/my-project-123/locations/global/publishers/anthropic/models/claude-haiku-5-5:rawPredict
116
116
  ```
117
117
 
118
118
  The **location** lives in the hostname, which is why the provider dialog asks
@@ -403,7 +403,7 @@ const client = new Anthropic({
403
403
  });
404
404
 
405
405
  const message = await client.messages.create({
406
- model: "vertexai/anthropic/claude-haiku-4-5@20251001",
406
+ model: "vertexai/anthropic/claude-haiku-5-5",
407
407
  max_tokens: 1024,
408
408
  messages: [{ role: "user", content: "Say hi" }],
409
409
  });
@@ -8,7 +8,7 @@
8
8
  | ai-gateway-configuration-executor-inbound | AI Gateway Configuration Executor | Loads the app configuration for the request (when auth or the configuration loader has not already), runs the inbound policy chain from that configuration, and enforces limits inherited from parent teams or the gateway root. Place this policy on AI Gateway routes after optional authentication and optional `ai-gateway-configuration-loader-inbound`. When either of those already populated the app-configuration channel, this policy reuses it. Otherwise it loads the configuration with the route's `app_id` path parameter. Applications select from policies pre-declared by the gateway. Applications without a `inboundPolicyChain`, or with an empty chain, run no application-selected policies. Entry options replace the declaration's options as a complete object; omit them to inherit the declaration, including environment-backed credentials. Each occurrence receives a private deep copy of its entry options, so a policy mutating its options cannot corrupt the cached app configuration. | ai-gateway |
9
9
  | ai-gateway-configuration-loader-inbound | AI Gateway Configuration Loader | Loads the AI Gateway app configuration for the request into the request-scoped channel and does nothing else. Place this policy on AI Gateway routes before `ai-gateway-configuration-executor-inbound` when you want configuration loading separated from chain execution. When `ai-gateway-auth-inbound` already populated the channel, this policy reuses it. Otherwise it loads the configuration with the route's `app_id` path parameter. If this policy is omitted, the configuration executor still loads configuration itself before running the application chain. | ai-gateway |
10
10
  | ai-gateway-fallback-model-inbound | AI Gateway Fallback Model | Adds failure and quota fallbacks to an existing AI Gateway model selection. Place this policy after AI Gateway Model Filtering. It never creates a model selection, so a misplaced policy cannot bypass filtering. | ai-gateway |
11
- | ai-gateway-metering-inbound | AI Gateway Metering | Meters AI Gateway usage and enforces limits configured by the application. The authentication policy must run before this policy so the app configuration id is available for meter storage and analytics. | ai-gateway |
11
+ | ai-gateway-metering-inbound | AI Gateway Metering | Meters AI Gateway usage for the application. The authentication policy must run before this policy so the app configuration id is available for meter storage and analytics. Budgets are declared with `budgetRules` and enforced by the configuration executor. | ai-gateway |
12
12
  | ai-gateway-model-filtering-inbound | AI Gateway Model Filtering | Matches AI Gateway requests against curated allow lists or open block lists, then stores the winning model reference for the route handler. | ai-gateway |
13
13
  | ai-gateway-model-override-inbound | AI Gateway Model Override | Forces one model for every request, or supplies a default model when a request omits one, then stores that selection for the AI Gateway handler. An earlier policy's model selection is never overwritten, so a custom routing policy placed before this one stays authoritative. | ai-gateway |
14
14
  | ai-gateway-semantic-cache-inbound | AI Gateway Semantic Cache | AI Gateway Semantic Cache policy. This inbound policy looks up the semantic cache on entry and, on a miss, registers a response-sending hook to write the upstream response back into the cache. Caching parameters (semanticTolerance, expirationSecondsTtl, namespace, recentMessageCount) come from the policy options, and presence in the route's chain is what enables it. An id from the authenticated app configuration always provides the cache namespace so application-supplied options cannot cross tenant partitions. The cache key covers the system prompt plus the last `recentMessageCount` messages (default 1) — applied when storing and when matching — so multi-turn conversations can hit entries cached from earlier, shorter ones. Cache outcomes are reported on the response via the RFC 9211 `Cache-Status` header under the cache name `zp-aigw-sem-cache` (hit: `zp-aigw-sem-cache; hit; detail="similarity=0.93"`; miss: `zp-aigw-sem-cache; fwd=miss; stored`). Responses also include the `x-ai-gateway-cache: HIT\|MISS` and `x-ai-gateway-cache-similarity` headers. | ai-gateway |
@@ -46,7 +46,7 @@
46
46
  | ai-gateway-galileo-tracing-inbound | Galileo Tracing | Galileo Tracing Inbound Policy | ai-gateway |
47
47
  | geo-filter-inbound | Geo-location filtering | Block requests based on geo-location parameters: country, region code, and ASN | api-gateway |
48
48
  | graphql-analytics-outbound | GraphQL Analytics | Reports GraphQL errors returned in response bodies to Zuplo's GraphQL analytics. GraphQL servers following the standard Apollo / graphql-yoga pattern return `200 OK` with an `errors[]` array in the body when an operation fails, which HTTP-level analytics alone report as a success — add this policy to a GraphQL route and failed operations show up as failures on the GraphQL dashboard, classified by error type. Each error in `errors[]` is classified from its `extensions.code` following the Apollo Server conventions (`GRAPHQL_PARSE_FAILED` → `syntax`, `GRAPHQL_VALIDATION_FAILED` → `validation`, `UNAUTHENTICATED` / `FORBIDDEN` → `auth`, timeout codes → `timeout`); custom codes can be mapped with `errorCodeClassification`, and anything unrecognized falls back to `defaultErrorClass` (`resolver`). Optionally set `logErrors` to also write a structured warning per errored response. The policy reads up to `maxScanBytes` of the body (128 KiB by default, 5 MiB maximum), scanning it for the `errors` token; a response larger than that is treated as error-free. When the token is found and the body fits, it is parsed and its errors reported. The response always passes through unchanged — the body is read from a clone, and any internal failure is swallowed so reporting can never break the request. The route must be marked `x-graphql: true` in `routes.oas.json` (which enables GraphQL analytics for the route); without the marker the policy logs a warning and does nothing. | api-gateway |
49
- | graphql-cache-inbound | GraphQL Cache | Caches GraphQL query responses at the edge so identical queries are served without a round-trip to the origin. Unlike CDN caching that keys on the raw request body, this policy parses the GraphQL document and normalizes it before hashing: insignificant whitespace, field formatting, and fragment layout are collapsed, and variable object keys are sorted. Two requests that are semantically identical therefore share a cache entry even when their bodies differ byte-for-byte. There is no query size or nesting-depth limit. Only `query` operations are cached. Mutations, subscriptions, malformed documents, and non-JSON bodies are forwarded to the origin untouched. Cache hits and misses are reported on the `x-cache` response header, with a short key fingerprint on `x-cache-key`. To avoid serving one user's data to another, requests carrying an `authorization` or `cookie` header are not cached by default. Use `cacheKeyHeaders` to opt into caching them: each listed header's value is included in the cache key, so distinct credentials get distinct cache entries. | api-gateway |
49
+ | graphql-cache-inbound | GraphQL Cache | Caches GraphQL query responses at the edge so identical queries are served without a round-trip to the origin. Unlike CDN caching that keys on the raw request body, this policy parses the GraphQL document and normalizes it before hashing: insignificant whitespace, field formatting, and fragment layout are collapsed, and variable object keys are sorted. Two requests that are semantically identical therefore share a cache entry even when their bodies differ byte-for-byte. There is no query size or nesting-depth limit. Only `query` operations are cached. Mutations, subscriptions, malformed documents, and non-JSON bodies are forwarded to the origin untouched. Cache hits and misses are reported on the `x-cache` response header, with a short key fingerprint on `x-cache-key`, and in the Cache analytics alongside the Caching policy's lookups. To avoid serving one user's data to another, requests carrying an `authorization` or `cookie` header are not cached by default. Use `cacheKeyHeaders` to opt into caching them: each listed header's value is included in the cache key, so distinct credentials get distinct cache entries. | api-gateway |
50
50
  | graphql-complexity-limit-inbound | GraphQL Complexity Limit | Policy that limits the complexity and depth of GraphQL queries to prevent abuse. Protects your GraphQL API from expensive queries that could cause performance issues or denial of service attacks. | api-gateway |
51
51
  | graphql-disable-introspection-inbound | GraphQL Disable Introspection | Policy that disables GraphQL introspection queries in production. Introspection allows clients to discover the schema, which can be a security risk as it exposes your entire API structure. | api-gateway |
52
52
  | graphql-introspection-filter-outbound | GraphQL Introspection Filter | Filters GraphQL introspection responses to exclude specific types and fields. This policy intercepts GraphQL introspection query responses and removes configured types and fields from the schema. Useful for hiding internal types or sensitive fields from the public schema. | api-gateway |
@@ -255,9 +255,9 @@ An application can inherit the options from `policies.json`. Include
255
255
  ```
256
256
 
257
257
  Place authentication first when the app requires a key, then model filtering,
258
- fallback-model, and metering so metering can activate the resolved quota
259
- fallback. Put policies that may short-circuit, such as semantic cache, after
260
- metering so those requests still count toward request limits.
258
+ fallback-model, and metering. Put policies that may short-circuit, such as
259
+ semantic cache, after metering so those requests still count toward request
260
+ limits.
261
261
 
262
262
  An application can also provide a complete options object for an entry:
263
263
 
@@ -73,7 +73,8 @@ Embeddings requests. Native create requests at `/v1/messages` and
73
73
  exhausts its upstream 429 retries; they do not inherit `fallbackTimeoutSeconds`
74
74
  or transport/5xx fallback. Responses management operations do not retry a backup
75
75
  because stored response IDs belong to the provider that created them. Quota
76
- fallback remains a separate, metering-driven path.
76
+ fallback is a separate path that the configuration executor activates when a
77
+ budget is exceeded.
77
78
 
78
79
  ## Write your own fallback policy
79
80
 
@@ -408,7 +408,7 @@ declared in `config/policies.json`. See
408
408
  | [Model Filtering](/docs/policies/ai-gateway-model-filtering-inbound) | Not needed for request types this policy has a list for, because the list already limits the models clients can reach. If Model Filtering runs first, it routes or refuses each request by the model the client asked for, and this policy does nothing. Placed after, it doesn't check this policy's choices. |
409
409
  | [Fallback Model](/docs/policies/ai-gateway-fallback-model-inbound) | Place it after, where it changes the retry model, the timeout, and where over-quota requests go. Placed before, it does nothing. See [With Fallback Model](#with-fallback-model). |
410
410
  | [Smart Router](/docs/policies/ai-gateway-smart-router-inbound) | When it routes a request, its model wins, whichever runs first. The request loses this policy's retry model, and Smart Router doesn't apply this policy's endpoint or stored-response rules. Fallback Model placed after Smart Router still adds its `fallback` and `quotaFallback`. Placed before Smart Router, they're lost too. |
411
- | [Metering](/docs/policies/ai-gateway-metering-inbound) | Place it after this policy and Fallback Model, so a request over its quota can switch to Fallback Model's `quotaFallback`. Costs and usage use the model that served the request. |
411
+ | [Metering](/docs/policies/ai-gateway-metering-inbound) | Either order works. Costs and usage use the model that served the request. |
412
412
  | [Semantic Cache](/docs/policies/ai-gateway-semantic-cache-inbound) | Place it after. Its cache key uses the request's own `model`, not this policy's choice, so with a list of different models a cached answer can come from any of them. Placed before, it serves a cache hit without this policy when the request's `model` is a valid model, and skips the hit otherwise. |
413
413
 
414
414
  ### With Fallback Model
@@ -14,8 +14,8 @@ fallback model or returns `429 Too Many Requests`. Cached responses are also
14
14
  subject to budgets: a blocked cache hit returns `429 Too Many Requests` without
15
15
  using a fallback model.
16
16
 
17
- Place Metering after Model Filtering and Fallback Model. Place it before
18
- Semantic Cache so cache hits count toward request limits.
17
+ Place Metering after Model Filtering. Place it before Semantic Cache so cache
18
+ hits count toward request limits.
19
19
 
20
20
  ## Example
21
21
 
@@ -242,6 +242,6 @@ so concurrent requests can exceed a budget before further requests are blocked.
242
242
 
243
243
  If a budget check is unavailable, requests can proceed unless a budget has
244
244
  already been determined to block them. The `throwOnFailure` option controls
245
- whether this policy rejects requests when its metering operations fail; it does
246
- not change how application, team, or gateway `budgetRules` handle unavailable
245
+ whether this policy fails the request when recording usage fails; it does not
246
+ change how application, team, or gateway `budgetRules` handle unavailable
247
247
  checks.
@@ -1,13 +1,14 @@
1
1
  The AI Gateway Metering policy records each application's spend, tokens, and
2
- requests and enforces spend, token, and request budgets. When a limit is
3
- exceeded, it uses the model selection's quota fallback when one is configured or
4
- returns `429 Too Many Requests`.
2
+ requests, and declares the application's spend, token, and request budgets. The
3
+ configuration executor enforces those budgets: when a budget is exceeded, it
4
+ uses the model selection's quota fallback when one is configured or returns
5
+ `429 Too Many Requests`.
5
6
 
6
7
  A budget rule covers the whole application, or gives every distinct value of an
7
8
  expression its own budget, such as one budget per user with
8
9
  `request.headers.get("x-user-id")`.
9
10
 
10
- Place it after Model Filtering and Fallback Model, and before policies such as
11
- Semantic Cache that may answer without calling a provider. Metering fails open
12
- by default when its service is unavailable; set `throwOnFailure` to `true` to
13
- reject requests instead.
11
+ Place it after Model Filtering and before policies such as Semantic Cache that
12
+ may answer without calling a provider. Metering fails open by default when
13
+ recording usage fails; set `throwOnFailure` to `true` to fail the request
14
+ instead.
@@ -12,7 +12,7 @@
12
12
  "requiresAI": true,
13
13
  "policyType": "ai-gateway-metering",
14
14
  "products": ["ai-gateway"],
15
- "description": "Meters AI Gateway usage and enforces limits configured by the application.\n\nThe authentication policy must run before this policy so the app configuration id is available for meter storage and analytics.",
15
+ "description": "Meters AI Gateway usage for the application.\n\nThe authentication policy must run before this policy so the app configuration id is available for meter storage and analytics. Budgets are declared with `budgetRules` and enforced by the configuration executor.",
16
16
  "deprecatedMessage": "",
17
17
  "required": ["handler"],
18
18
  "properties": {
@@ -32,7 +32,7 @@
32
32
  "options": {
33
33
  "title": "AIGatewayMeteringInboundPolicyOptions",
34
34
  "type": "object",
35
- "description": "Options for metering AI Gateway usage and enforcing request-time limits.",
35
+ "description": "Options for metering AI Gateway usage and declaring budget rules.",
36
36
  "additionalProperties": false,
37
37
  "required": [],
38
38
  "examples": [
@@ -63,7 +63,7 @@
63
63
  "throwOnFailure": {
64
64
  "type": "boolean",
65
65
  "default": false,
66
- "description": "Throw when the metering service is unavailable instead of failing open."
66
+ "description": "Fail the request instead of logging the error when recording usage with the metering service fails."
67
67
  },
68
68
  "budgetRules": {
69
69
  "type": "array",
@@ -140,7 +140,7 @@
140
140
  "doNotSuggest": true,
141
141
  "x-show-example": false,
142
142
  "x-advanced": true,
143
- "description": "Usage limits grouped by meter.",
143
+ "description": "Deprecated and not enforced. Declare budgets with budgetRules.",
144
144
  "additionalProperties": false,
145
145
  "properties": {
146
146
  "costs": {
@@ -25,7 +25,7 @@ agentic, document_qa, other) and the built-in classification prompt.
25
25
 
26
26
  | Classifier | Set | Use it when |
27
27
  | ----------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
- | `classifier.app` | `appId`, `model` | You want the classifier to use one of your providers, through an AI Gateway app in this project. The call stays in-process. Set `adapter: "messages"` for Claude models, or `adapter: "system-one"` for Jev and other System One models. |
28
+ | `classifier.app` | `appId`, `model` | You want the classifier to use one of your providers, through an AI Gateway app in this project. The call stays in-process. Set `dialect: "messages"` for Claude models, or `dialect: "system-one"` for Jev and other System One models. |
29
29
  | `classifier.chatCompletions` (deprecated) | `apiKey`, `model` | You need to call an OpenAI-compatible endpoint directly with its own key, without a second AI Gateway app. |
30
30
  | `classifier.jev` (deprecated) | `apiKey` | You need to call Jev, from TypeSafe, directly with its own key. TypeSafe and OpenRouter both serve it. |
31
31
 
@@ -50,7 +50,7 @@ budgets, and analytics, and new classifier features are added only to
50
50
  ```
51
51
 
52
52
  - `appId` — the AI Gateway app that runs the classifier.
53
- - `adapter` — optional: `chat-completions` (default), `messages`, or
53
+ - `dialect` — optional: `chat-completions` (default), `messages`, or
54
54
  `system-one`. Selects `/v1/chat/completions`, `/v1/messages`, or
55
55
  `/v1/systemone` on that app.
56
56
  - `model` — the `providerName/model` that app routes the classifier request to.
@@ -69,7 +69,7 @@ Anthropic Messages API:
69
69
  "classifier": {
70
70
  "app": {
71
71
  "appId": "$env(CLASSIFIER_APP_ID)",
72
- "adapter": "messages",
72
+ "dialect": "messages",
73
73
  "model": "anthropic/claude-haiku-4-5"
74
74
  }
75
75
  }
@@ -107,7 +107,7 @@ schema is built from your `intents`, so the first classification after you
107
107
  change them, or after the cache expires, can be slower than usual. If it runs
108
108
  past `classifierTimeoutMs`, that one request continues unclassified.
109
109
 
110
- The `messages` adapter does not translate between providers: pick a model the
110
+ The `messages` dialect does not translate between providers: pick a model the
111
111
  classifier app serves on `/v1/messages`.
112
112
 
113
113
  For Jev registered with your gateway, use the same app path with System One:
@@ -116,7 +116,7 @@ For Jev registered with your gateway, use the same app path with System One:
116
116
  "classifier": {
117
117
  "app": {
118
118
  "appId": "$env(CLASSIFIER_APP_ID)",
119
- "adapter": "system-one",
119
+ "dialect": "system-one",
120
120
  "model": "jev/jev-latest"
121
121
  }
122
122
  }
@@ -125,12 +125,12 @@ For Jev registered with your gateway, use the same app path with System One:
125
125
  Replace `jev` with your configured provider name. The classifier app resolves
126
126
  the provider assignment and owns upstream credentials, routing, DB pricing,
127
127
  budget checks, token metering, and analytics. No direct provider key, base URL,
128
- or pricing configuration is needed in Smart Router. Its result still includes
129
- the classifier's usage and cost; it does not emit duplicate analytics under the
130
- calling app.
128
+ or pricing configuration is needed in Smart Router. Its result includes the
129
+ classifier's token usage; it does not emit duplicate analytics under the calling
130
+ app.
131
131
 
132
132
  Smart Router sends its intent and complexity questions to the app and interprets
133
- the returned System One answers. Omit `classifierPrompt` for this adapter. Keep
133
+ the returned System One answers. Omit `classifierPrompt` for this dialect. Keep
134
134
  Smart Router off the classifier app's own chain. System One is not a chat-shaped
135
135
  request, so choose policies compatible with that endpoint; existing guardrails
136
136
  that cannot inspect its shape still reject it according to their configuration.
@@ -209,7 +209,7 @@ routing applies only when Jev is sure of both. Jev writes no reasons, so
209
209
 
210
210
  Jev takes no system prompt, so setting `classifierPrompt` with `classifier.jev`
211
211
  is rejected with a `ConfigurationError`. The same restriction applies to
212
- `classifier.app.adapter: "system-one"`.
212
+ `classifier.app.dialect: "system-one"`.
213
213
 
214
214
  `chatCompletions` and `jev` call a service outside the gateway with the key you
215
215
  configure, and send it the prompt. When that service answers with an error, the
@@ -218,9 +218,9 @@ which can echo the request back, prompt included.
218
218
 
219
219
  To migrate a direct section, register the provider with the gateway, create a
220
220
  classifier app for it, and replace the section with `classifier.app`. Use
221
- `adapter: "system-one"` to replace `classifier.jev`, and `adapter: "messages"`
221
+ `dialect: "system-one"` to replace `classifier.jev`, and `dialect: "messages"`
222
222
  for a Claude model. Existing `classifier.app` configurations continue to default
223
- to Chat Completions when `adapter` is omitted. `system-one` uses the existing
223
+ to Chat Completions when `dialect` is omitted. `system-one` uses the existing
224
224
  Jev request and response contract; it does not add support for arbitrary
225
225
  decision APIs.
226
226
 
@@ -295,6 +295,12 @@ They're deprecated, and still honored when `classifier` isn't set:
295
295
  Move all three at once. Setting `classifier` together with any of them is
296
296
  rejected with a `ConfigurationError`.
297
297
 
298
+ ## Migrating from `classifier.app.adapter`
299
+
300
+ `classifier.app.adapter` is the earlier name of `classifier.app.dialect`. It
301
+ takes the same values, and is still honored when `dialect` isn't set. Rename the
302
+ field to `dialect`. Setting both is rejected with a `ConfigurationError`.
303
+
298
304
  ## Policy order
299
305
 
300
306
  ```text
@@ -361,7 +367,7 @@ classifier calls a strict JSON-schema chat completion, so it can only return one
361
367
  of your configured ids. The `description` values are only shown to the
362
368
  classifier if your prompt includes `{{intents}}` (see below).
363
369
 
364
- With `classifier.jev` or `classifier.app.adapter: "system-one"`, the `id` values
370
+ With `classifier.jev` or `classifier.app.dialect: "system-one"`, the `id` values
365
371
  are the options of Jev's intent question, and every `description` is shown to
366
372
  Jev with its option. There is no prompt, so `{{intents}}` doesn't apply.
367
373
 
@@ -372,9 +378,9 @@ so it's never eligible for routing.
372
378
  ### Custom classifier prompt
373
379
 
374
380
  `classifierPrompt` replaces the built-in classifier prompt for `classifier.app`
375
- (its default `chat-completions` adapter, or `messages`, which sends it as
381
+ (its default `chat-completions` dialect, or `messages`, which sends it as
376
382
  `system`) and `classifier.chatCompletions`.
377
- `classifier.app.adapter: "system-one"` and `classifier.jev` take no prompt, so
383
+ `classifier.app.dialect: "system-one"` and `classifier.jev` take no prompt, so
378
384
  combining `classifierPrompt` with either is a configuration error. It accepts
379
385
  either a string or an array of lines:
380
386
 
@@ -422,20 +428,8 @@ interface AIGatewaySmartRouterResult {
422
428
  inputTokens: number;
423
429
  outputTokens: number;
424
430
  totalTokens: number;
425
- breakdown?: {
426
- uncachedInputTokens: number;
427
- cacheReadInputTokens?: number;
428
- cacheWriteInput?: Array<{ tokens: number; cacheTtl?: "5m" | "1h" }>;
429
- visibleOutputTokens: number;
430
- reasoningOutputTokens?: number;
431
- };
432
431
  };
433
432
  classifierModel: string;
434
- cost?: {
435
- costUsd: number;
436
- source: "provider" | "catalog" | "unpriced";
437
- accuracy: "exact" | "estimated" | "unpriced";
438
- };
439
433
  routing: { model?: string };
440
434
  durationMs: number;
441
435
  promptSource: "user" | "prior-user";
@@ -457,7 +451,7 @@ interface AIGatewaySmartRouterResult {
457
451
  }
458
452
  ```
459
453
 
460
- With `classifier.jev` or `classifier.app.adapter: "system-one"`,
454
+ With `classifier.jev` or `classifier.app.dialect: "system-one"`,
461
455
  `classifierModel` is the versioned model that answered, in the host's naming:
462
456
  `jev-1.13.0` on TypeSafe, or `typesafe/jev-1.13-20260917` on OpenRouter. `usage`
463
457
  reports the input and output tokens that the Jev API returns as `inputTokens`
@@ -550,7 +544,8 @@ like smart routing quietly doing nothing:
550
544
  - More than one section in `classifier` (`app`, `chatCompletions`, and `jev`).
551
545
  - A deprecated `classifierAppID`, `classifierModel`, or `classifierAppApiKey`
552
546
  next to `classifier`.
553
- - `classifierPrompt` with `classifier.app.adapter: "system-one"` or
547
+ - Both `classifier.app.dialect` and the deprecated `classifier.app.adapter`.
548
+ - `classifierPrompt` with `classifier.app.dialect: "system-one"` or
554
549
  `classifier.jev`.
555
550
  - A `classifier.app.appId` that names this app.
556
551
  - A classifier app that runs this policy or Akamai AI Firewall.
@@ -564,27 +559,11 @@ the policy from that route to use Bedrock Runtime through it.
564
559
 
565
560
  ### Classifier cost reporting
566
561
 
567
- Use `classifier.app` for DB-backed pricing, metering, analytics and budgets.
568
- After classification, `AIGatewaySmartRouter.get(context)` exposes token usage
569
- and cost from the invoked gateway's accounting headers, including its accounted
570
- input, output, and total tokens and cost accuracy. Smart Router does not
571
- recompute usage from the provider response for app classifiers. Missing or
572
- invalid token headers produce zero counts; missing cost headers produce
573
- `source: "unpriced"`. Responses from older gateways without cost accuracy retain
574
- a conservative estimate for catalog costs.
575
-
576
- Deprecated direct classifiers retain token usage and any validated
577
- provider-reported cost in the Smart Router result, but do not look up DB prices,
578
- send gateway analytics, or increment budget meters. If the provider does not
579
- report a valid cost, the result is `unpriced`; its numeric zero does not mean
580
- the call was free. No additional pricing configuration is supported for direct
581
- calls.
562
+ Use `classifier.app` for DB-backed pricing, metering, analytics and budgets. The
563
+ classifier app records the classifier call's cost under its own app.
564
+ `AIGatewaySmartRouter.get(context)` exposes only the token usage that the
565
+ classifier response body reports, not its cost.
582
566
 
583
- App classifier results also include `usage.breakdown` when the gateway has
584
- canonical accounting detail. It exposes uncached input, cache-read input,
585
- cache-write input (with TTL when known), visible output, and reasoning output.
586
- These disjoint categories are already included in `inputTokens` and
587
- `outputTokens`; do not add them to the totals again. Unreported detail remains
588
- absent, while an explicitly reported zero is preserved. Missing or malformed
589
- breakdown headers do not discard aggregate token counts or cost. Smart Router
590
- does not derive these categories from the provider body or infer cache TTLs.
567
+ Deprecated direct classifiers do not look up DB prices, send gateway analytics,
568
+ or increment budget meters. No pricing configuration is supported for direct
569
+ calls.
@@ -63,7 +63,7 @@
63
63
  "classifier": {
64
64
  "app": {
65
65
  "appId": "$env(CLASSIFIER_APP_ID)",
66
- "adapter": "system-one",
66
+ "dialect": "system-one",
67
67
  "model": "jev/jev-latest"
68
68
  }
69
69
  },
@@ -77,7 +77,7 @@
77
77
  "classifier": {
78
78
  "app": {
79
79
  "appId": "$env(CLASSIFIER_APP_ID)",
80
- "adapter": "messages",
80
+ "dialect": "messages",
81
81
  "model": "anthropic/claude-haiku-4-5"
82
82
  }
83
83
  },
@@ -92,7 +92,7 @@
92
92
  "classifier": {
93
93
  "app": {
94
94
  "appId": "$env(CLASSIFIER_APP_ID)",
95
- "adapter": "chat-completions",
95
+ "dialect": "chat-completions",
96
96
  "model": "openai/gpt-4o-mini"
97
97
  }
98
98
  },
@@ -107,7 +107,7 @@
107
107
  "classifier": {
108
108
  "type": "object",
109
109
  "title": "Classifier",
110
- "description": "Where the prompt is classified. Set `app.adapter` to select the API that classifier app exposes.",
110
+ "description": "Where the prompt is classified. Set `app.dialect` to select the API that classifier app exposes.",
111
111
  "additionalProperties": false,
112
112
  "minProperties": 1,
113
113
  "maxProperties": 1,
@@ -118,6 +118,9 @@
118
118
  "description": "Classify with another AI Gateway app in this project. The call runs in-process and never leaves the gateway, so the classifier uses that app's providers, routing, and quotas.",
119
119
  "additionalProperties": false,
120
120
  "required": ["appId", "model"],
121
+ "not": {
122
+ "required": ["dialect", "adapter"]
123
+ },
121
124
  "properties": {
122
125
  "appId": {
123
126
  "type": "string",
@@ -128,7 +131,7 @@
128
131
  "model": {
129
132
  "type": "string",
130
133
  "title": "Classifier Model",
131
- "description": "The model (`providerName/model`) the classifier app uses to evaluate the user's request. `providerName` is the name of the provider as configured on the classifier app, not a fixed vendor id. E.g. `openai/gpt-4o-mini`, `anthropic/claude-haiku-4-5` with the `messages` adapter, or `jev/jev-latest` when the Jev provider is named `jev`.",
134
+ "description": "The model (`providerName/model`) the classifier app uses to evaluate the user's request. `providerName` is the name of the provider as configured on the classifier app, not a fixed vendor id. E.g. `openai/gpt-4o-mini`, `anthropic/claude-haiku-4-5` with the `messages` dialect, or `jev/jev-latest` when the Jev provider is named `jev`.",
132
135
  "pattern": "^[^/\\s]+/.+$",
133
136
  "examples": ["openai/gpt-4o-mini"]
134
137
  },
@@ -139,12 +142,22 @@
139
142
  "examples": ["$env(CLASSIFIER_APP_API_KEY)"],
140
143
  "x-advanced": true
141
144
  },
142
- "adapter": {
145
+ "dialect": {
143
146
  "type": "string",
144
147
  "enum": ["chat-completions", "messages", "system-one"],
145
148
  "default": "chat-completions",
146
- "title": "Classifier App Adapter",
149
+ "title": "Classifier App Dialect",
147
150
  "description": "API contract exposed by the classifier app. `chat-completions` calls /v1/chat/completions; `messages` calls /v1/messages for Claude models that support structured outputs (`output_config.format`), such as `claude-haiku-4-5`, on a provider whose Messages endpoint accepts them (not Amazon Bedrock Mantle); `system-one` calls /v1/systemone for models such as Jev. The app owns provider credentials, pricing, metering and analytics."
151
+ },
152
+ "adapter": {
153
+ "type": "string",
154
+ "enum": ["chat-completions", "messages", "system-one"],
155
+ "title": "Classifier App Adapter (Deprecated)",
156
+ "description": "\\*\\*Deprecated\\*\\*: use `classifier.app.dialect` instead. Still honored when `dialect` is not set; setting both is a configuration error.",
157
+ "deprecated": true,
158
+ "doNotSuggest": true,
159
+ "x-show-example": false,
160
+ "x-advanced": true
148
161
  }
149
162
  }
150
163
  },
@@ -181,7 +194,7 @@
181
194
  "jev": {
182
195
  "type": "object",
183
196
  "title": "Jev (TypeSafe)",
184
- "description": "Deprecated: use `classifier.app` with `adapter` `system-one` instead. Classify with Jev, TypeSafe's classification model, by calling a System One API directly: TypeSafe's by default, or another host such as OpenRouter through `baseUrl`. Jev answers typed questions instead of generating text, so it takes no `classifierPrompt`.",
197
+ "description": "Deprecated: use `classifier.app` with `dialect` `system-one` instead. Classify with Jev, TypeSafe's classification model, by calling a System One API directly: TypeSafe's by default, or another host such as OpenRouter through `baseUrl`. Jev answers typed questions instead of generating text, so it takes no `classifierPrompt`.",
185
198
  "additionalProperties": false,
186
199
  "required": ["apiKey"],
187
200
  "properties": {
@@ -279,7 +292,7 @@
279
292
  },
280
293
  "classifierPrompt": {
281
294
  "title": "Classifier Prompt",
282
- "description": "Prompt for classifier.app (its default `chat-completions` adapter or the `messages` adapter, where it is sent as the system prompt) and classifier.chatCompletions. Omit to use the built-in prompt. classifier.app.adapter `system-one` and classifier.jev take no prompt; combining them with classifierPrompt is a configuration error.",
295
+ "description": "Prompt for classifier.app (its default `chat-completions` dialect or the `messages` dialect, where it is sent as the system prompt) and classifier.chatCompletions. Omit to use the built-in prompt. classifier.app.dialect `system-one` and classifier.jev take no prompt; combining them with classifierPrompt is a configuration error.",
283
296
  "oneOf": [
284
297
  {
285
298
  "type": "string",
@@ -357,12 +370,27 @@
357
370
  "required": ["app"],
358
371
  "properties": {
359
372
  "app": {
360
- "required": ["adapter"],
361
- "properties": {
362
- "adapter": {
363
- "const": "system-one"
373
+ "anyOf": [
374
+ {
375
+ "required": ["dialect"],
376
+ "properties": {
377
+ "dialect": {
378
+ "const": "system-one"
379
+ }
380
+ }
381
+ },
382
+ {
383
+ "not": {
384
+ "required": ["dialect"]
385
+ },
386
+ "required": ["adapter"],
387
+ "properties": {
388
+ "adapter": {
389
+ "const": "system-one"
390
+ }
391
+ }
364
392
  }
365
- }
393
+ ]
366
394
  }
367
395
  }
368
396
  }
@@ -396,7 +424,7 @@
396
424
  "classifier": {
397
425
  "app": {
398
426
  "appId": "$env(CLASSIFIER_APP_ID)",
399
- "adapter": "system-one",
427
+ "dialect": "system-one",
400
428
  "model": "jev/jev-latest"
401
429
  }
402
430
  },
@@ -414,7 +442,7 @@
414
442
  "classifier": {
415
443
  "app": {
416
444
  "appId": "$env(CLASSIFIER_APP_ID)",
417
- "adapter": "messages",
445
+ "dialect": "messages",
418
446
  "model": "anthropic/claude-haiku-4-5"
419
447
  }
420
448
  },
@@ -433,7 +461,7 @@
433
461
  "classifier": {
434
462
  "app": {
435
463
  "appId": "$env(CLASSIFIER_APP_ID)",
436
- "adapter": "chat-completions",
464
+ "dialect": "chat-completions",
437
465
  "model": "openai/gpt-4o-mini"
438
466
  }
439
467
  },
@@ -30,6 +30,15 @@ Every response served or stored by the policy carries two headers:
30
30
  Both headers are added to `access-control-expose-headers` so browsers can read
31
31
  them, without overwriting any value an upstream CORS policy already set.
32
32
 
33
+ ### Analytics
34
+
35
+ Every lookup is reported in your project's **Cache** analytics, under the `HTTP`
36
+ cache type with the policy's name: hits, misses, stored responses, and cache
37
+ latency. The latency covers both reading entries and storing new ones. Requests
38
+ the policy forwards without a lookup, such as mutations and credentialed
39
+ requests, don't count toward the hit rate. On routes that also run GraphQL
40
+ analytics, the **GraphQL** analytics show the hit rate per operation.
41
+
33
42
  ### What is and isn't cached
34
43
 
35
44
  - Only `query` operations are cached. **Mutations** and **subscriptions** are
@@ -12,7 +12,7 @@
12
12
  "requiresAI": false,
13
13
  "policyType": "graphql-cache-inbound",
14
14
  "products": ["api-gateway"],
15
- "description": "Caches GraphQL query responses at the edge so identical queries are served without a round-trip to the origin.\n\nUnlike CDN caching that keys on the raw request body, this policy parses the GraphQL document and normalizes it before hashing: insignificant whitespace, field formatting, and fragment layout are collapsed, and variable object keys are sorted. Two requests that are semantically identical therefore share a cache entry even when their bodies differ byte-for-byte. There is no query size or nesting-depth limit.\n\nOnly `query` operations are cached. Mutations, subscriptions, malformed documents, and non-JSON bodies are forwarded to the origin untouched. Cache hits and misses are reported on the `x-cache` response header, with a short key fingerprint on `x-cache-key`.\n\nTo avoid serving one user's data to another, requests carrying an `authorization` or `cookie` header are not cached by default. Use `cacheKeyHeaders` to opt into caching them: each listed header's value is included in the cache key, so distinct credentials get distinct cache entries.",
15
+ "description": "Caches GraphQL query responses at the edge so identical queries are served without a round-trip to the origin.\n\nUnlike CDN caching that keys on the raw request body, this policy parses the GraphQL document and normalizes it before hashing: insignificant whitespace, field formatting, and fragment layout are collapsed, and variable object keys are sorted. Two requests that are semantically identical therefore share a cache entry even when their bodies differ byte-for-byte. There is no query size or nesting-depth limit.\n\nOnly `query` operations are cached. Mutations, subscriptions, malformed documents, and non-JSON bodies are forwarded to the origin untouched. Cache hits and misses are reported on the `x-cache` response header, with a short key fingerprint on `x-cache-key`, and in the Cache analytics alongside the Caching policy's lookups.\n\nTo avoid serving one user's data to another, requests carrying an `authorization` or `cookie` header are not cached by default. Use `cacheKeyHeaders` to opt into caching them: each listed header's value is included in the cache key, so distinct credentials get distinct cache entries.",
16
16
  "deprecatedMessage": "",
17
17
  "required": ["handler"],
18
18
  "properties": {
@@ -16,13 +16,14 @@ issued by your API.
16
16
 
17
17
  ## JWT Token
18
18
 
19
- By default, this service issues JWTs using the EdDSA algorithm. This is the
20
- recommended algorithm for new applications due to its strong security properties
21
- and performance characteristics. However, not every library supports EdDSA, so
22
- you should ensure that your [client library](https://jwt.io/libraries) can
23
- handle this algorithm. If you need a different algorithm (for example, for
24
- compatibility with an existing key pair or client library), use the `algorithm`
25
- configuration option.
19
+ This service signs JWTs with the EdDSA algorithm, using the Ed25519 signing key
20
+ that Zuplo provisions for each environment. EdDSA is the only supported signing
21
+ algorithm. The plugin can't sign tokens with RS256 or any other RSA or ECDSA
22
+ algorithm, and you can't supply your own key pair.
23
+
24
+ EdDSA has strong security properties and good performance, but not every JWT
25
+ library supports it. Make sure the [client library](https://jwt.io/libraries)
26
+ that validates your tokens can handle EdDSA.
26
27
 
27
28
  ## Use Cases
28
29
 
@@ -86,13 +87,11 @@ export function runtimeInit(runtime: RuntimeExtensions) {
86
87
  is `"/__zuplo/issuer"`. This affects the issuer URL and OIDC configuration
87
88
  endpoints.
88
89
 
89
- - **`algorithm`** (optional): The asymmetric signing algorithm used for issued
90
- JWTs. Default is `"EdDSA"`. Supported values are `EdDSA`, `RS256`, `RS384`,
91
- `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, and `ES512`. The
92
- algorithm must match the configured key pair — for example, an RSA key
93
- requires an `RS*` or `PS*` value and an Ed25519 key requires `EdDSA`.
94
- Symmetric algorithms (like `HS256`) aren't supported because the plugin
95
- publishes a JWKS endpoint.
90
+ - **`algorithm`** (optional): The signing algorithm for issued JWTs. Leave this
91
+ option unset or set it to `"EdDSA"`, which is the default. EdDSA is the only
92
+ algorithm the environment's Ed25519 key can sign with. See
93
+ [Unsupported algorithms](#unsupported-algorithms) for what happens with other
94
+ values.
96
95
 
97
96
  - **`expiresIn`** (optional): Sets the default expiration time for JWTs. Default
98
97
  is `"1h"`. Can be either:
@@ -121,6 +120,36 @@ export function runtimeInit(runtime: RuntimeExtensions) {
121
120
  Note: Individual JWT creation can override this default by specifying
122
121
  `expiresIn` in the `signJwt` method.
123
122
 
123
+ ### Unsupported algorithms
124
+
125
+ The plugin only signs with EdDSA. Other `algorithm` values fail as follows:
126
+
127
+ - **A symmetric or unknown algorithm**, such as `"HS256"`, fails when the plugin
128
+ is created, so the gateway doesn't start. The error lists the values the
129
+ option accepts:
130
+
131
+ ```txt
132
+ JwtServicePlugin - Unsupported signing algorithm "HS256". Supported algorithms: EdDSA, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512.
133
+ ```
134
+
135
+ Not all of these values work. The environment's key is an Ed25519 key, so only
136
+ `EdDSA` can sign tokens.
137
+
138
+ - **An RSA or ECDSA algorithm**, such as `"RS256"`, `"PS256"`, or `"ES256"`,
139
+ doesn't match the environment's Ed25519 key. The gateway starts, but
140
+ `JwtServicePlugin.signJwt()` fails when it imports the key:
141
+
142
+ ```txt
143
+ JwtServicePlugin - Failed to import private key. Ensure it is a valid JWK format and matches the configured algorithm "RS256".
144
+ ```
145
+
146
+ The OpenID configuration endpoint also advertises the configured algorithm
147
+ while the JWKS endpoint serves the Ed25519 public key, so clients can't verify
148
+ tokens against it.
149
+
150
+ To fix either error, remove the `algorithm` option or set it to `"EdDSA"`, and
151
+ validate tokens with EdDSA in your upstream services.
152
+
124
153
  ## Usage
125
154
 
126
155
  Once the plugin is registered, you can use it to issue JWTs in custom handlers
@@ -190,9 +219,8 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
190
219
  ## Validating JWTs in Upstream Services
191
220
 
192
221
  Upstream services can validate the JWTs issued by your Zuplo API by verifying
193
- the signature and claims. The examples below use `EdDSA`, the plugin's default
194
- signing algorithm. If you configured a different algorithm using the `algorithm`
195
- option, use that value in the `algorithms` list instead.
222
+ the signature and claims. The examples below use `EdDSA`, the algorithm the
223
+ plugin signs with.
196
224
 
197
225
  ### Node.js/Express Example
198
226
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "7.11.3",
3
+ "version": "7.11.5",
4
4
  "type": "module",
5
5
  "description": "The official Zuplo CLI for local development and platform management",
6
6
  "homepage": "https://zuplo.com/docs/cli/overview",
@@ -32,9 +32,9 @@
32
32
  "zuplo": "zuplo.js"
33
33
  },
34
34
  "dependencies": {
35
- "@zuplo/cli": "7.11.3",
36
- "@zuplo/core": "7.11.3",
37
- "@zuplo/runtime": "7.11.3",
38
- "@zuplo/test": "7.11.3"
35
+ "@zuplo/cli": "7.11.5",
36
+ "@zuplo/core": "7.11.5",
37
+ "@zuplo/runtime": "7.11.5",
38
+ "@zuplo/test": "7.11.5"
39
39
  }
40
40
  }