zuplo 7.11.4 → 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.
- package/docs/ai-gateway/bedrock-runtime.mdx +2 -2
- package/docs/ai-gateway/cookbooks/sdk-path-shim.mdx +1 -1
- package/docs/ai-gateway/fallback.mdx +1 -1
- package/docs/ai-gateway/integrations/ai-sdk.mdx +1 -1
- package/docs/ai-gateway/integrations/claude-code.mdx +2 -2
- package/docs/ai-gateway/providers.mdx +1 -1
- package/docs/ai-gateway/vertex-ai.mdx +2 -2
- package/docs/policies/_index.md +2 -2
- package/docs/policies/ai-gateway-configuration-executor-inbound/doc.md +3 -3
- package/docs/policies/ai-gateway-fallback-model-inbound/doc.md +2 -1
- package/docs/policies/ai-gateway-load-balancing-inbound/doc.md +1 -1
- package/docs/policies/ai-gateway-metering-inbound/doc.md +4 -4
- package/docs/policies/ai-gateway-metering-inbound/intro.md +8 -7
- package/docs/policies/ai-gateway-metering-inbound/schema.json +4 -4
- package/docs/policies/ai-gateway-smart-router-inbound/doc.md +31 -52
- package/docs/policies/ai-gateway-smart-router-inbound/schema.json +45 -17
- package/docs/policies/graphql-cache-inbound/doc.md +9 -0
- package/docs/policies/graphql-cache-inbound/schema.json +1 -1
- package/docs/programmable-api/jwt-service-plugin.mdx +45 -17
- package/package.json +5 -5
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
406
|
+
model: "vertexai/anthropic/claude-haiku-5-5",
|
|
407
407
|
max_tokens: 1024,
|
|
408
408
|
messages: [{ role: "user", content: "Say hi" }],
|
|
409
409
|
});
|
package/docs/policies/_index.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
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) |
|
|
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
|
|
18
|
-
|
|
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
|
|
246
|
-
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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": "
|
|
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": "
|
|
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 `
|
|
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
|
-
- `
|
|
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
|
-
"
|
|
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`
|
|
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
|
-
"
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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.
|
|
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
|
-
`
|
|
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 `
|
|
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.
|
|
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`
|
|
381
|
+
(its default `chat-completions` dialect, or `messages`, which sends it as
|
|
376
382
|
`system`) and `classifier.chatCompletions`.
|
|
377
|
-
`classifier.app.
|
|
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.
|
|
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
|
-
- `
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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`
|
|
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
|
-
"
|
|
145
|
+
"dialect": {
|
|
143
146
|
"type": "string",
|
|
144
147
|
"enum": ["chat-completions", "messages", "system-one"],
|
|
145
148
|
"default": "chat-completions",
|
|
146
|
-
"title": "Classifier App
|
|
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 `
|
|
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`
|
|
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
|
-
"
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
you
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
194
|
-
|
|
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
|
+
"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.
|
|
36
|
-
"@zuplo/core": "7.11.
|
|
37
|
-
"@zuplo/runtime": "7.11.
|
|
38
|
-
"@zuplo/test": "7.11.
|
|
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
|
}
|