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.
- package/docs/policies/ai-gateway-model-routing-v2-inbound/doc.md +19 -16
- package/docs/policies/ai-gateway-model-routing-v2-inbound/intro.md +8 -9
- package/docs/policies/ai-gateway-model-routing-v2-inbound/schema.json +15 -15
- package/docs/policies/graphql-cache-inbound/doc.md +2 -2
- package/docs/policies/mcp-token-exchange-inbound/doc.md +9 -9
- package/docs/policies/require-user-claims-inbound/doc.md +9 -10
- package/docs/policies/require-user-claims-inbound/intro.md +5 -5
- package/docs/policies/upstream-aws-federated-auth-inbound/doc.md +2 -3
- package/docs/policies/upstream-aws-service-auth-inbound/doc.md +2 -2
- package/docs/rate-limiting/how-it-works.md +52 -0
- package/package.json +4 -4
|
@@ -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.
|
|
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
|
|
57
|
-
additional slashes, such as
|
|
58
|
-
Provider and model
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
71
|
-
|
|
72
|
-
|
|
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 `"
|
|
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(
|
|
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 `"
|
|
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,
|
|
9
|
-
case-insensitively, and preserve configured casing
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
23
|
-
"@zuplo/core": "6.73.
|
|
24
|
-
"@zuplo/runtime": "6.73.
|
|
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
|
}
|