zuplo 7.7.8 → 7.7.10

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.
@@ -27,13 +27,38 @@ The gateway owner remains in control:
27
27
  | `inboundPolicyChain` contains entries | Entries run in the listed order |
28
28
  | An entry has `enabled: false` | That entry is skipped |
29
29
 
30
- If a policy returns a response, that response is sent immediately and later
31
- entries do not run. If a chain is invalid, the request fails closed with an
32
- error that identifies the entry to fix.
30
+ If a policy returns a response, later entries do not run. Successful 2xx
31
+ responses still receive the budget check described below before they are sent.
32
+ Other responses, including authentication failures, remain unchanged. If a chain
33
+ is invalid, the request fails closed with an error that identifies the entry to
34
+ fix.
33
35
 
34
36
  This inbound executor does not run `outboundPolicyChain`. That field is reserved
35
37
  for an outbound configuration executor on the response pipeline.
36
38
 
39
+ ## Automatic budget enforcement
40
+
41
+ The executor enforces budgets configured on the application, its parent teams,
42
+ and the gateway. This does not require a Metering policy and applies even when
43
+ the application's policy chain is empty. An application cannot override a shared
44
+ budget with a higher limit of its own.
45
+
46
+ Request logs indicate when automatic budget checks run, including when the
47
+ application has no Metering policy. Warnings identify exceeded budgets, blocked
48
+ requests, fallback decisions, and checks that could not be completed.
49
+
50
+ When a budget blocks a provider request, the gateway uses the configured quota
51
+ fallback model or returns `429 Too Many Requests`. Successful responses from
52
+ policies, including Semantic Cache hits, are also subject to budgets. A blocked
53
+ response returns `429 Too Many Requests` without using a fallback model.
54
+ Warning-only budgets do not block responses. Authentication failures and other
55
+ non-2xx policy responses are preserved.
56
+
57
+ Place Metering before Semantic Cache to count cache hits toward request limits.
58
+ Budget changes can take time to take effect, and concurrent requests can exceed
59
+ a budget before further requests are blocked. If a budget check is unavailable,
60
+ requests can proceed unless a budget has already been determined to block them.
61
+
37
62
  ## Build an AI Gateway from scratch
38
63
 
39
64
  ### 1. Declare the policies
@@ -171,8 +196,7 @@ chain:
171
196
  }
172
197
  ```
173
198
 
174
- Routes that list only the executor keep working — the executor loads
175
- configuration when the channel is empty.
199
+ If you omit the loader, the executor loads the application configuration.
176
200
 
177
201
  Authentication is optional and placement controls its scope:
178
202
 
@@ -205,14 +229,19 @@ An application can inherit the options from `policies.json`. Include
205
229
  {
206
230
  "name": "ai-gateway-metering-v2-inbound",
207
231
  "options": {
208
- "limits": {
209
- "requests": {
210
- "daily": {
211
- "enabled": true,
212
- "limit": 1000
213
- }
232
+ "budgetRules": [
233
+ {
234
+ "budgetBy": "app",
235
+ "meters": [
236
+ {
237
+ "meter": "requests",
238
+ "period": "daily",
239
+ "value": 1000,
240
+ "action": "block"
241
+ }
242
+ ]
214
243
  }
215
- }
244
+ ]
216
245
  }
217
246
  },
218
247
  {
@@ -347,10 +376,10 @@ canonical forms when writing application configuration:
347
376
  | A key requiring brackets | `request.user.data["team-id"]` |
348
377
 
349
378
  The `expression` field contains the complete expression. Do not wrap it in an
350
- interpolation marker such as `${...}`. The runtime resolves it for every request
351
- after the application's inbound policy chain finishes. Updating an application's
352
- configuration changes which value later requests select without rebuilding or
353
- redeploying the gateway.
379
+ interpolation marker such as `${...}`. Expressions use values from the request
380
+ and context, including values set by earlier policies. Place policies that set
381
+ these values before Semantic Cache. You can change an expression in the
382
+ application configuration without rebuilding or redeploying the gateway.
354
383
 
355
384
  JSON encoding and expression syntax are separate. When writing raw JSON, escape
356
385
  the double quotes required by a bracket segment:
@@ -393,10 +422,8 @@ The selectable data model is:
393
422
  | `request` | `url`, `method`, `headers`, `user.sub`, `user.data`, `query`, `searchParams`, `params`, and the scalar Fetch request metadata `bodyUsed`, `cache`, `credentials`, `destination`, `integrity`, `keepalive`, `mode`, `redirect`, `referrer`, and `referrerPolicy` |
394
423
  | `context` | `contextId`, `requestId`, `custom`, `route`, and `incomingRequestProperties` |
395
424
 
396
- `request.body`, `context.log`, policy invocation methods, and every other host
397
- object are outside the selectable data model. The evaluator receives a
398
- plain-data snapshot of the selected root. It never receives the live request or
399
- context object.
425
+ Expressions cannot read the request body, call logging or policy methods, or
426
+ access properties outside this table.
400
427
 
401
428
  Expressions have these limits:
402
429
 
@@ -413,9 +440,8 @@ Expressions have these limits:
413
440
  It does not traverse arrays, class instances, inherited properties, or the
414
441
  property names `__proto__`, `constructor`, and `prototype`.
415
442
 
416
- Expressions observe values after the application's inbound policy chain
417
- finishes. An authentication policy can populate `request.user`. A custom policy
418
- earlier in the chain can derive a value and place it in a request header:
443
+ An authentication policy can populate `request.user`. A custom policy earlier in
444
+ the chain can derive a value and place it in a request header:
419
445
 
420
446
  ```ts
421
447
  import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
@@ -446,14 +472,12 @@ value to well-formed NFC Unicode and ignores it when it exceeds 256 UTF-8 bytes
446
472
  or contains control characters, U+2028, or U+2029. A missing or invalid value
447
473
  does not contribute to the rule for that request.
448
474
 
449
- The exact stored expression is the rule identity and analytics dimension name.
450
- Store its authored bytes unchanged. Different accepted spellings, such as single
451
- quotes and double quotes, identify different rules even when they select the
452
- same value.
475
+ Use consistent spelling for each expression. Different spellings, such as single
476
+ quotes and double quotes, identify different budget rules even when they select
477
+ the same value.
453
478
 
454
- Malformed budget rules are logged and ignored individually. An unsupported
455
- expression is also logged and omitted from dimension capture and quota tokens;
456
- other valid rules continue to run.
479
+ Invalid budget rules and unsupported expressions are logged and skipped. Other
480
+ valid rules continue to apply.
457
481
 
458
482
  ## Write a custom policy for the chain
459
483
 
@@ -70,9 +70,12 @@ not override. A configured `fallback` replaces an existing backup and timeout; a
70
70
  configured `quotaFallback` replaces an existing quota fallback.
71
71
 
72
72
  Retry and timeout fallback is available for translated Chat Completions and
73
- Embeddings requests. Native `/v1/messages` and `/v1/responses` requests are
74
- passed through without retrying a backup. Quota fallback remains a separate,
75
- metering-driven path.
73
+ Embeddings requests. Native create requests at `/v1/messages` and
74
+ `/v1/responses` use a compatible completions backup only after the primary
75
+ exhausts its upstream 429 retries; they do not inherit `fallbackTimeoutSeconds`
76
+ or transport/5xx fallback. Responses management operations do not retry a backup
77
+ because stored response IDs belong to the provider that created them. Quota
78
+ fallback remains a separate, metering-driven path.
76
79
 
77
80
  ## Write your own fallback policy
78
81
 
@@ -5,13 +5,19 @@ budgets before the provider request runs. It meters spend, tokens, and requests.
5
5
  Budget rules can cover the whole application or each distinct value of an
6
6
  expression.
7
7
 
8
- When a limit is exceeded, the policy activates the model selection's
9
- `quotaFallback` when AI Gateway Fallback Model supplied one. Otherwise it
10
- returns `429 Too Many Requests`.
8
+ Application, team, and gateway budgets are enforced even when this policy is not
9
+ included in the application's policy chain.
11
10
 
12
- Place Metering after Model Filtering and Fallback Model so an exceeded budget
13
- can activate the quota fallback. Put policies that may answer early, such as
14
- Semantic Cache, after Metering so cache hits still count toward request limits.
11
+ Request logs indicate when automatic budget checks run and when a budget blocks
12
+ a request or selects a fallback model.
13
+
14
+ When a budget blocks a provider request, the gateway uses the configured quota
15
+ fallback model or returns `429 Too Many Requests`. Cached responses are also
16
+ subject to budgets: a blocked cache hit returns `429 Too Many Requests` without
17
+ using a fallback model.
18
+
19
+ Place Metering after Model Filtering and Fallback Model. Place it before
20
+ Semantic Cache so cache hits count toward request limits.
15
21
 
16
22
  ## Example
17
23
 
@@ -62,19 +68,26 @@ value of its expression. An action of `"warn"` notifies without blocking. An
62
68
  action of `"block"` activates the configured quota fallback or returns
63
69
  `429 Too Many Requests` when usage reaches the value.
64
70
 
65
- > **Budgets fail open by default.** When `throwOnFailure` is `false`, a metering
66
- > service failure lets the request proceed unmetered and no limit is checked.
67
- > Set it to `true` to reject the request instead.
68
-
69
71
  ## Team limits
70
72
 
71
- Budgets configured on this application govern only this app. Limits configured
72
- on a parent team or the gateway root are enforced centrally after the
73
- application's policy chain, whether or not this policy appears in that chain. An
74
- inherited limit activates the selected model's quota fallback when available and
75
- otherwise returns `429 Too Many Requests`.
73
+ Application budgets apply only to that application. Parent team and gateway
74
+ budgets apply across their applications, even when an application does not
75
+ include Metering. An application cannot override a shared budget with a higher
76
+ limit of its own.
77
+
78
+ For example, two applications that spend $40 and $10 exhaust their team's $50
79
+ monthly budget. An application outside that team does not share its budget.
80
+
81
+ Team policy templates provide defaults for application policies. To set a shared
82
+ team budget, configure the budget on the team itself.
83
+
84
+ ## Budget availability
85
+
86
+ Budget changes can take time to take effect. Usage is recorded asynchronously,
87
+ so concurrent requests can exceed a budget before further requests are blocked.
76
88
 
77
- An application cannot disable inherited enforcement through its policy chain. If
78
- the central hierarchical check is unavailable, the request proceeds. The
79
- policy's `throwOnFailure` option controls failures while checking or recording
80
- the app's own limits; it does not change inherited-limit behavior.
89
+ If a budget check is unavailable, requests can proceed unless a budget has
90
+ already been determined to block them. The `throwOnFailure` option controls
91
+ whether this policy rejects requests when its metering operations fail; it does
92
+ not change how application, team, or gateway `budgetRules` handle unavailable
93
+ checks.
@@ -253,3 +253,58 @@ Policy order determines precedence:
253
253
 
254
254
  Prefer one policy as the primary selector so the route's intent is easy to
255
255
  understand.
256
+
257
+ ## Model discovery
258
+
259
+ Authenticated `GET /v1/models` and `GET /v1/models/{providerName/model}` use the
260
+ same app URL as inference. Select the inference endpoint with the optional
261
+ `x-zuplo-models-endpoint` request header:
262
+
263
+ | Value | Eligible models | Response format |
264
+ | ------------------ | ----------------------------------------------------------------------------------------------- | --------------- |
265
+ | `chat-completions` | Active completion models supported by Chat Completions adapters, including existing translation | OpenAI |
266
+ | `messages` | Active completion models supported by native Messages passthrough | Anthropic |
267
+ | `responses` | Active completion models supported by the resolved Responses adapter | OpenAI |
268
+ | `embeddings` | Active embedding models supported by the resolved embedding adapter | OpenAI |
269
+
270
+ An explicit header wins. Otherwise, the presence of `anthropic-version` selects
271
+ `messages`; other requests default to `chat-completions`. Empty, unknown, and
272
+ multiple selector values return an actionable 400. The selector affects
273
+ discovery only. Embedding discovery is opt-in. Messages never translates to Chat
274
+ Completions.
275
+
276
+ Discovery applies the first Model Filtering policy's parsed rules, matching
277
+ inference's selection precedence. A capability omitted from that policy lists no
278
+ models. Without Model Filtering, discovery lists the eligible catalog. Inactive
279
+ models, unavailable adapters, and assignments without locally resolvable
280
+ credentials are excluded. List and retrieve use identical eligibility rules;
281
+ unknown, filtered-out, and wrong-endpoint IDs return the same 404.
282
+
283
+ IDs retain the exact model-name casing; only the provider assignment label is
284
+ normalized. OpenAI entries contain `id`, `object`, `created`, and `owned_by`.
285
+ `owned_by` means the provider assignment namespace, not the model's developer.
286
+ Anthropic entries contain `id`, `type`, `display_name`, and `created_at`, with
287
+ unknown required nullable metadata set to `null`. Unknown creation dates use the
288
+ epoch; optional unknown metadata is omitted.
289
+
290
+ OpenAI lists return every eligible model and ignore pagination parameters.
291
+ Anthropic lists default to 20 entries, accept `limit` from 1–1000, and accept
292
+ either `after_id` or `before_id` from a previous page. Follow `has_more` and
293
+ `last_id` for forward pagination. Invalid cursors return 400; restart without a
294
+ cursor.
295
+
296
+ Discovery uses the existing 60-second catalog cache. An uncached lookup,
297
+ including legacy fallback, has a two-second budget; failure returns a
298
+ sanitized 503. A successfully loaded catalog with no eligible models returns an
299
+ empty 200. Responses use `Cache-Control: private, no-store` and vary on both
300
+ selector headers. Only GET is supported. POST, PUT, PATCH, DELETE, and HEAD
301
+ return 405 with `Allow: GET`. OPTIONS is handled by the gateway’s existing CORS
302
+ preflight handler before app routing.
303
+
304
+ Authentication and general abuse protections still apply. Discovery skips prompt
305
+ inspection, semantic caching, and inference-budget blocking, and records one
306
+ baseline request. Eligibility describes the catalog, adapters, and declared
307
+ filtering rules; it does not guarantee upstream availability, provider
308
+ acceptance of credentials, or acceptance by prompt-dependent custom policies.
309
+ Existing Chat Completions translation does not promise full feature parity. Keep
310
+ a qualified model configured when a client's picker does not consume discovery.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "7.7.8",
3
+ "version": "7.7.10",
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.7.8",
36
- "@zuplo/core": "7.7.8",
37
- "@zuplo/runtime": "7.7.8",
38
- "@zuplo/test": "7.7.8"
35
+ "@zuplo/cli": "7.7.10",
36
+ "@zuplo/core": "7.7.10",
37
+ "@zuplo/runtime": "7.7.10",
38
+ "@zuplo/test": "7.7.10"
39
39
  }
40
40
  }