zuplo 6.73.8 → 6.73.11

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.
@@ -1,7 +1,8 @@
1
1
  # AI Gateway Model Routing (v2) Policy
2
2
 
3
3
  The AI Gateway Model Routing (v2) policy applies declarative allow or block
4
- rules to AI requests. Configured targets use `provider/model` form and split on
4
+ rules to AI requests. The Provider Name configured in the Zuplo Portal is its
5
+ routing address: configured targets use `providerName/model` form and split on
5
6
  the first slash, so provider-specific model IDs can contain additional slashes.
6
7
 
7
8
  ## Configuration
@@ -53,23 +54,23 @@ An allow-list entry can be a string shorthand or a full target object:
53
54
  }
54
55
  ```
55
56
 
56
- The first slash separates the provider from the model. Model IDs may contain
57
- additional slashes, such as `my-fireworks/accounts/fireworks/models/llama-v3`.
58
- Provider and model matching is case-insensitive. In curated mode the configured
59
- casing is preserved when the request is sent upstream.
57
+ The first slash separates the Provider Name from the model. Model IDs may
58
+ contain additional slashes, such as
59
+ `my-fireworks/accounts/fireworks/models/llama-v3`. Provider Name and model
60
+ matching is case-insensitive. In curated mode the configured casing is preserved
61
+ when the request is sent upstream.
60
62
 
61
63
  ## Request behavior
62
64
 
63
- Clients can send a bare model name or `provider/model`. In curated mode, a bare
64
- name selects the one allow-list `main` with that model name; an ambiguous name
65
- receives a 400 response asking for `provider/model`. In block-list mode, bare
66
- Chat Completions and Embeddings models use OpenAI, while native Messages and
67
- Responses models use Anthropic and OpenAI respectively.
65
+ Every non-empty request model must use `providerName/model`. A bare model name
66
+ receives an actionable 400 response. When the model is omitted, curated mode
67
+ uses allow-list entry zero; block-list mode rejects the request because it has
68
+ no default.
68
69
 
69
70
  `/v1/embeddings` uses the `embeddings` rules; Chat Completions, Responses, and
70
- Anthropic Messages use `completions` rules. A request containing `/` is treated
71
- as `provider/model`, so model IDs containing slashes must include their
72
- provider.
71
+ Anthropic Messages use `completions` rules. The first slash separates the
72
+ Provider Name, so model IDs containing slashes remain intact after that
73
+ separator.
73
74
 
74
75
  The pass-through routes are provider-native: `/v1/messages` accepts Anthropic
75
76
  targets and `/v1/responses` accepts OpenAI targets. A request that explicitly
@@ -104,8 +105,8 @@ with whatever logic it wants:
104
105
  handler consumes.
105
106
 
106
107
  A per-capability target is the same `AIGatewayRouteTarget` shape used by
107
- `allowList` entries: a `"provider/model"` string, or an object with `main` and
108
- optional `backup`, `fallbackTimeoutSeconds`, and `quotaFallback`.
108
+ `allowList` entries: a `"providerName/model"` string, or an object with `main`
109
+ and optional `backup`, `fallbackTimeoutSeconds`, and `quotaFallback`.
109
110
 
110
111
  ```typescript
111
112
  import {
@@ -120,7 +121,9 @@ export default async function routeModel(
120
121
  context: ZuploContext
121
122
  ) {
122
123
  const providers = await AIGatewayModels.load(context);
123
- const openAI = providers.find((provider) => provider.name === "openai");
124
+ const openAI = providers.find(
125
+ (provider) => provider.providerName === "openai"
126
+ );
124
127
  const model = openAI?.models.find(
125
128
  (candidate) =>
126
129
  candidate.capability === "completions" && candidate.status === "active"
@@ -1,18 +1,17 @@
1
1
  The AI Gateway Model Routing (v2) policy selects models with either a curated
2
2
  `allowList` or an open-but-filtered `blockList`. Allow-list entries accept the
3
- string shorthand `"provider/model"` or an object with `main`, `backup`,
3
+ string shorthand `"providerName/model"` or an object with `main`, `backup`,
4
4
  `fallbackTimeoutSeconds`, and `quotaFallback`; entry zero is the default when a
5
5
  curated request omits `model`, while block-list mode requires every request to
6
6
  name a model.
7
7
 
8
- Request model references split on the first slash, match provider and model
9
- case-insensitively, and preserve configured casing upstream. Clients may send a
10
- bare model name: curated rules resolve a unique allow-list match, while open
11
- rules default to OpenAI except on Anthropic-native Messages routes. Bodyless
12
- Responses management operations need curated or custom routing because
13
- block-list mode has no default. Retry backups apply to translated Chat
14
- Completions and Embeddings, not native pass-through routes; quota fallbacks
15
- remain independent and fallbacks do not chain.
8
+ Request model references use `providerName/model`, split on the first slash,
9
+ match provider and model case-insensitively, and preserve configured casing
10
+ upstream. A non-empty request model without a Provider Name is rejected with an
11
+ actionable 400. Bodyless Responses management operations need curated or custom
12
+ routing because block-list mode has no default. Retry backups apply to
13
+ translated Chat Completions and Embeddings, not native pass-through routes;
14
+ quota fallbacks remain independent and fallbacks do not chain.
16
15
 
17
16
  Custom inbound policies can inspect `AIGatewayModels.load(context)` and call
18
17
  `AIGatewayModelRouting.set(context, routing)` to select the same target without
@@ -31,7 +31,7 @@
31
31
  "options": {
32
32
  "type": "object",
33
33
  "title": "AIGatewayModelRoutingV2InboundPolicyOptions",
34
- "description": "Options for allowing or blocking provider/model routes for each AI Gateway capability.",
34
+ "description": "Options for allowing or blocking providerName/model routes for each AI Gateway capability.",
35
35
  "additionalProperties": false,
36
36
  "required": ["models"],
37
37
  "properties": {
@@ -67,12 +67,12 @@
67
67
  "minItems": 1,
68
68
  "items": {
69
69
  "title": "Route Target",
70
- "description": "A provider/model string shorthand or a full model route target.",
70
+ "description": "A providerName/model string shorthand or a full model route target.",
71
71
  "oneOf": [
72
72
  {
73
73
  "type": "string",
74
74
  "title": "Provider and Model",
75
- "description": "A model reference in provider/model form, split on the first slash.",
75
+ "description": "A model reference in providerName/model form, split on the first slash.",
76
76
  "pattern": "^[^/\\s]+/.+$"
77
77
  },
78
78
  {
@@ -85,13 +85,13 @@
85
85
  "main": {
86
86
  "type": "string",
87
87
  "title": "Provider and Model",
88
- "description": "A model reference in provider/model form, split on the first slash.",
88
+ "description": "A model reference in providerName/model form, split on the first slash.",
89
89
  "pattern": "^[^/\\s]+/.+$"
90
90
  },
91
91
  "backup": {
92
92
  "type": "string",
93
93
  "title": "Provider and Model",
94
- "description": "A model reference in provider/model form, split on the first slash.",
94
+ "description": "A model reference in providerName/model form, split on the first slash.",
95
95
  "pattern": "^[^/\\s]+/.+$"
96
96
  },
97
97
  "fallbackTimeoutSeconds": {
@@ -105,7 +105,7 @@
105
105
  "quotaFallback": {
106
106
  "type": "string",
107
107
  "title": "Provider and Model",
108
- "description": "A model reference in provider/model form, split on the first slash.",
108
+ "description": "A model reference in providerName/model form, split on the first slash.",
109
109
  "pattern": "^[^/\\s]+/.+$"
110
110
  }
111
111
  }
@@ -128,12 +128,12 @@
128
128
  "blockList": {
129
129
  "type": "array",
130
130
  "title": "Blocked Models",
131
- "description": "Configured provider/model targets rejected in open-but-filtered mode; requests may use a bare model name or provider/model.",
131
+ "description": "Configured providerName/model targets rejected in open-but-filtered mode.",
132
132
  "minItems": 1,
133
133
  "items": {
134
134
  "type": "string",
135
135
  "title": "Provider and Model",
136
- "description": "A model reference in provider/model form, split on the first slash.",
136
+ "description": "A model reference in providerName/model form, split on the first slash.",
137
137
  "pattern": "^[^/\\s]+/.+$"
138
138
  }
139
139
  }
@@ -168,12 +168,12 @@
168
168
  "minItems": 1,
169
169
  "items": {
170
170
  "title": "Route Target",
171
- "description": "A provider/model string shorthand or a full model route target.",
171
+ "description": "A providerName/model string shorthand or a full model route target.",
172
172
  "oneOf": [
173
173
  {
174
174
  "type": "string",
175
175
  "title": "Provider and Model",
176
- "description": "A model reference in provider/model form, split on the first slash.",
176
+ "description": "A model reference in providerName/model form, split on the first slash.",
177
177
  "pattern": "^[^/\\s]+/.+$"
178
178
  },
179
179
  {
@@ -186,13 +186,13 @@
186
186
  "main": {
187
187
  "type": "string",
188
188
  "title": "Provider and Model",
189
- "description": "A model reference in provider/model form, split on the first slash.",
189
+ "description": "A model reference in providerName/model form, split on the first slash.",
190
190
  "pattern": "^[^/\\s]+/.+$"
191
191
  },
192
192
  "backup": {
193
193
  "type": "string",
194
194
  "title": "Provider and Model",
195
- "description": "A model reference in provider/model form, split on the first slash.",
195
+ "description": "A model reference in providerName/model form, split on the first slash.",
196
196
  "pattern": "^[^/\\s]+/.+$"
197
197
  },
198
198
  "fallbackTimeoutSeconds": {
@@ -206,7 +206,7 @@
206
206
  "quotaFallback": {
207
207
  "type": "string",
208
208
  "title": "Provider and Model",
209
- "description": "A model reference in provider/model form, split on the first slash.",
209
+ "description": "A model reference in providerName/model form, split on the first slash.",
210
210
  "pattern": "^[^/\\s]+/.+$"
211
211
  }
212
212
  }
@@ -229,12 +229,12 @@
229
229
  "blockList": {
230
230
  "type": "array",
231
231
  "title": "Blocked Models",
232
- "description": "Configured provider/model targets rejected in open-but-filtered mode; requests may use a bare model name or provider/model.",
232
+ "description": "Configured providerName/model targets rejected in open-but-filtered mode.",
233
233
  "minItems": 1,
234
234
  "items": {
235
235
  "type": "string",
236
236
  "title": "Provider and Model",
237
- "description": "A model reference in provider/model form, split on the first slash.",
237
+ "description": "A model reference in providerName/model form, split on the first slash.",
238
238
  "pattern": "^[^/\\s]+/.+$"
239
239
  }
240
240
  }
@@ -1,6 +1,6 @@
1
1
  The GraphQL Cache policy stores successful GraphQL query responses in a
2
- [ZoneCache](https://zuplo.com/docs/programmable-api/zone-cache) and serves later,
3
- identical queries directly from the edge.
2
+ [ZoneCache](https://zuplo.com/docs/programmable-api/zone-cache) and serves
3
+ later, identical queries directly from the edge.
4
4
 
5
5
  ### How caching works
6
6
 
@@ -124,12 +124,12 @@ in two server-to-server steps:
124
124
  Resource Authorization Server's token endpoint using the `jwt-bearer` grant
125
125
  and receives the upstream access token it attaches to the forwarded request.
126
126
 
127
- Use this mode in enterprise deployments where the gateway and the upstream
128
- trust the same identity provider (for example, Okta Cross-App Access), so
129
- upstream access follows the caller's identity without a per-user upstream
130
- consent flow. The `user-oauth`/`shared-oauth` options — `scopes`,
131
- `scopeDelimiter`, `prompt`, and `clientRegistration` — do not apply; the
132
- `id-jag` mode is configured entirely through the `idJag` option.
127
+ Use this mode in enterprise deployments where the gateway and the upstream trust
128
+ the same identity provider (for example, Okta Cross-App Access), so upstream
129
+ access follows the caller's identity without a per-user upstream consent flow.
130
+ The `user-oauth`/`shared-oauth` options — `scopes`, `scopeDelimiter`, `prompt`,
131
+ and `clientRegistration` — do not apply; the `id-jag` mode is configured
132
+ entirely through the `idJag` option.
133
133
 
134
134
  ```json
135
135
  {
@@ -168,9 +168,9 @@ consent flow. The `user-oauth`/`shared-oauth` options — `scopes`,
168
168
  ```
169
169
 
170
170
  Both `clientAuth` blocks also accept `client_secret_basic` and
171
- `private_key_jwt`. Use `private_key_jwt` for providers such as Okta that
172
- require a signed client assertion for token exchange; it takes `privateKeyPem`,
173
- an optional `keyId`, and an `algorithm` (default `RS256`). Source secrets and
171
+ `private_key_jwt`. Use `private_key_jwt` for providers such as Okta that require
172
+ a signed client assertion for token exchange; it takes `privateKeyPem`, an
173
+ optional `keyId`, and an `algorithm` (default `RS256`). Source secrets and
174
174
  private keys from environment variables with `$env(VAR_NAME)`. When `resource`
175
175
  is omitted, the gateway defaults it to the upstream MCP server URL from the
176
176
  route handler's `rewritePattern`.
@@ -1,10 +1,10 @@
1
1
  ## Overview
2
2
 
3
3
  The `require-user-claims-inbound` policy authorizes requests by evaluating a
4
- rule against the claims of the authenticated user (`request.user`). It
5
- separates authentication from authorization: an authentication policy — a JWT
6
- policy such as `open-id-jwt-auth-inbound`, API key auth, mTLS, or any other —
7
- proves _who_ the caller is; this policy then decides _what_ they may call.
4
+ rule against the claims of the authenticated user (`request.user`). It separates
5
+ authentication from authorization: an authentication policy — a JWT policy such
6
+ as `open-id-jwt-auth-inbound`, API key auth, mTLS, or any other — proves _who_
7
+ the caller is; this policy then decides _what_ they may call.
8
8
 
9
9
  Typical uses:
10
10
 
@@ -86,8 +86,8 @@ metadata.
86
86
  `['https://example.com/roles']`, or nested:
87
87
  `['https://sts.amazonaws.com/']['lambda_source_function_arn']`. Single or
88
88
  double quotes both work; single quotes avoid backslash-escaping when the
89
- selector is written in a JSON `policies.json`. The two notations may be
90
- mixed, e.g. `realm_access['some.key']`.
89
+ selector is written in a JSON `policies.json`. The two notations may be mixed,
90
+ e.g. `realm_access['some.key']`.
91
91
  - `sub` falls back to `request.user.sub` when the user data has no `sub`
92
92
  property, so subject checks also work with authentication policies that only
93
93
  set the subject (for example API key authentication).
@@ -102,8 +102,8 @@ Every check fails closed:
102
102
  the number `42`. Note that Google-issued tokens carry `sub` as a string of
103
103
  digits, and `$env(...)` substitution always produces strings — quote numeric
104
104
  values accordingly.
105
- - `startsWith` only matches string claim values, and an empty prefix is
106
- rejected as a configuration error.
105
+ - `startsWith` only matches string claim values, and an empty prefix is rejected
106
+ as a configuration error.
107
107
  - When a claim value is an **array** (for example `groups` or `roles`), a check
108
108
  passes if **any element** satisfies the operator.
109
109
 
@@ -113,8 +113,7 @@ checks and the caller's `sub` are written to the request log instead.
113
113
  ## Related policies
114
114
 
115
115
  - [Open ID JWT Auth](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound) —
116
- verifies token signature, issuer, and audience, and populates
117
- `request.user`.
116
+ verifies token signature, issuer, and audience, and populates `request.user`.
118
117
  - [JWT Scope Validation](https://zuplo.com/docs/policies/jwt-scopes-inbound) —
119
118
  requires that the space-delimited `scope` claim contains **all** configured
120
119
  scopes; use it for OAuth scope enforcement.
@@ -2,8 +2,8 @@ Authorize requests by validating claims on the authenticated user against a
2
2
  configurable rule — allowlists (`in`), exact values (`eq`), and prefixes
3
3
  (`startsWith`), combined with `and`/`or`.
4
4
 
5
- Run this policy after any authentication policy (for example Open ID JWT Auth
6
- or one of its provider-specific variants): the authentication policy proves
7
- _who_ the caller is, and this policy decides _what_ they may call — for
8
- example, pinning a route to specific service accounts, OAuth clients, tenants,
9
- or groups without writing custom code.
5
+ Run this policy after any authentication policy (for example Open ID JWT Auth or
6
+ one of its provider-specific variants): the authentication policy proves _who_
7
+ the caller is, and this policy decides _what_ they may call — for example,
8
+ pinning a route to specific service accounts, OAuth clients, tenants, or groups
9
+ without writing custom code.
@@ -2,9 +2,8 @@
2
2
 
3
3
  The Upstream AWS Federated Auth policy obtains AWS credentials **without storing
4
4
  any AWS keys**. It exchanges Zuplo's ambient OIDC identity token for an IAM
5
- role's short-lived temporary credentials using STS
6
- `AssumeRoleWithWebIdentity`, then registers those credentials on the request
7
- context.
5
+ role's short-lived temporary credentials using STS `AssumeRoleWithWebIdentity`,
6
+ then registers those credentials on the request context.
8
7
 
9
8
  Like the `upstream-aws-service-auth` policy, it does **not** add an
10
9
  `Authorization` header itself — AWS Signature Version 4 signs the exact final
@@ -9,8 +9,8 @@ resolved credentials and sign the requests they build.
9
9
 
10
10
  There are two modes:
11
11
 
12
- - **Static credentials** — the configured `accessKeyId` / `secretAccessKey`
13
- (and optional `sessionToken`) are used directly.
12
+ - **Static credentials** — the configured `accessKeyId` / `secretAccessKey` (and
13
+ optional `sessionToken`) are used directly.
14
14
  - **Assumed role** — when `roleArn` is set, the policy calls STS `AssumeRole`
15
15
  (signed with the configured keys) and uses the role's short-lived temporary
16
16
  credentials. These are cached in memory and refreshed before they expire.
@@ -99,6 +99,58 @@ Applies a single shared counter across all requests to the route, regardless of
99
99
  who makes them. Use this for global rate limits on endpoints that call
100
100
  resource-constrained backends.
101
101
 
102
+ ## Sub-minute time windows
103
+
104
+ Time windows don't have to be whole minutes. The `timeWindowMinutes` option
105
+ accepts fractional values, so you can define windows measured in seconds:
106
+
107
+ ```json
108
+ {
109
+ "name": "burst-protection",
110
+ "policyType": "rate-limit-inbound",
111
+ "handler": {
112
+ "export": "RateLimitInboundPolicy",
113
+ "module": "$import(@zuplo/runtime)",
114
+ "options": {
115
+ "rateLimitBy": "user",
116
+ "requestsAllowed": 10,
117
+ "timeWindowMinutes": 0.5
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ This policy allows each user 10 requests in any 30-second sliding window. Some
124
+ useful values:
125
+
126
+ | `timeWindowMinutes` | Window length |
127
+ | ------------------- | ------------- |
128
+ | `0.5` | 30 seconds |
129
+ | `0.25` | 15 seconds |
130
+ | `0.1` | 6 seconds |
131
+
132
+ Fractional values work anywhere `timeWindowMinutes` appears — including the
133
+ [Complex Rate Limiting policy](#complex-rate-limiting-policy) and the values
134
+ returned by a [custom rate limit function](#custom-rate-limit-functions).
135
+
136
+ Short windows pair well with a longer sustained limit on the same route: the
137
+ tight window absorbs bursts while the longer one enforces overall usage. See
138
+ [Combining Policies](./combining-policies.mdx) for how to stack rate limits.
139
+
140
+ ### Short windows and accuracy
141
+
142
+ The rate limiter is globally distributed: counters synchronize across edge
143
+ locations worldwide, so a limit applies consistently regardless of which region
144
+ serves a request. That synchronization can't outrun the speed of light (Zuplo is
145
+ working on bending physics). As a window shrinks toward a few seconds,
146
+ synchronization takes up a bigger fraction of the window, so counts from
147
+ different regions can fall out of step before the window elapses — and a small
148
+ number of requests that should exceed the limit can slip through.
149
+
150
+ Windows of roughly 5 seconds or longer give the most consistent enforcement.
151
+ Shorter windows still work; they just carry a slightly higher chance of a missed
152
+ limit application.
153
+
102
154
  ## Custom rate limit functions
103
155
 
104
156
  When `rateLimitBy` is set to `"function"`, Zuplo calls a TypeScript function you
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.73.8",
3
+ "version": "6.73.11",
4
4
  "type": "module",
5
5
  "description": "The programmable API Gateway",
6
6
  "author": "Zuplo, Inc.",
@@ -19,9 +19,9 @@
19
19
  "zuplo": "zuplo.js"
20
20
  },
21
21
  "dependencies": {
22
- "@zuplo/cli": "6.73.8",
23
- "@zuplo/core": "6.73.8",
24
- "@zuplo/runtime": "6.73.8",
22
+ "@zuplo/cli": "6.73.11",
23
+ "@zuplo/core": "6.73.11",
24
+ "@zuplo/runtime": "6.73.11",
25
25
  "@zuplo/test": "1.4.0"
26
26
  }
27
27
  }