zuplo 7.2.3 → 7.2.4

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.
Files changed (157) hide show
  1. package/docs/ai-gateway/integrations/ai-sdk.mdx +3 -2
  2. package/docs/ai-gateway/integrations/claude-code.mdx +3 -2
  3. package/docs/ai-gateway/integrations/codex.mdx +7 -5
  4. package/docs/ai-gateway/integrations/goose.mdx +4 -3
  5. package/docs/ai-gateway/integrations/langchain.mdx +3 -3
  6. package/docs/ai-gateway/integrations/openai.mdx +3 -3
  7. package/docs/ai-gateway/policies/overview.mdx +3 -17
  8. package/docs/ai-gateway/universal-api.mdx +4 -3
  9. package/docs/caching/custom-caching-policy.mdx +1 -1
  10. package/docs/caching/gateway-caching.mdx +3 -3
  11. package/docs/caching/overview.mdx +10 -10
  12. package/docs/dedicated/akamai/ai-powered-applications.mdx +2 -2
  13. package/docs/dev-portal/zudoku/configuration/search.md +5 -0
  14. package/docs/dev-portal/zudoku/openapi-extensions/x-mcp-server.md +30 -0
  15. package/docs/policies/_index.md +12 -7
  16. package/docs/policies/ai-gateway-auth-v2-inbound/doc.md +168 -0
  17. package/docs/policies/ai-gateway-auth-v2-inbound/intro.md +10 -0
  18. package/docs/policies/ai-gateway-auth-v2-inbound/schema.json +74 -0
  19. package/docs/policies/ai-gateway-configuration-executor-v2-inbound/doc.md +442 -0
  20. package/docs/policies/ai-gateway-configuration-executor-v2-inbound/intro.md +14 -0
  21. package/docs/policies/ai-gateway-configuration-executor-v2-inbound/schema.json +60 -0
  22. package/docs/policies/ai-gateway-configuration-loader-v2-inbound/doc.md +105 -0
  23. package/docs/policies/ai-gateway-configuration-loader-v2-inbound/intro.md +6 -0
  24. package/docs/policies/ai-gateway-configuration-loader-v2-inbound/schema.json +60 -0
  25. package/docs/policies/ai-gateway-dlp-inbound/doc.md +127 -0
  26. package/docs/policies/ai-gateway-dlp-inbound/intro.md +5 -0
  27. package/docs/policies/ai-gateway-dlp-inbound/schema.json +4050 -0
  28. package/docs/policies/ai-gateway-fallback-model-v2-inbound/doc.md +10 -2
  29. package/docs/policies/ai-gateway-fallback-model-v2-inbound/intro.md +2 -2
  30. package/docs/policies/ai-gateway-fallback-model-v2-inbound/schema.json +22 -8
  31. package/docs/policies/ai-gateway-metering-v2-inbound/doc.md +19 -16
  32. package/docs/policies/ai-gateway-metering-v2-inbound/intro.md +9 -0
  33. package/docs/policies/ai-gateway-metering-v2-inbound/schema.json +20 -24
  34. package/docs/policies/ai-gateway-model-filtering-v2-inbound/doc.md +6 -5
  35. package/docs/policies/ai-gateway-model-filtering-v2-inbound/intro.md +2 -2
  36. package/docs/policies/ai-gateway-model-filtering-v2-inbound/schema.json +17 -3
  37. package/docs/policies/ai-gateway-semantic-cache-v2-inbound/doc.md +72 -0
  38. package/docs/policies/ai-gateway-semantic-cache-v2-inbound/intro.md +9 -0
  39. package/docs/policies/ai-gateway-semantic-cache-v2-inbound/schema.json +120 -0
  40. package/docs/policies/akamai-ai-firewall-v2-inbound/doc.md +87 -0
  41. package/docs/policies/akamai-ai-firewall-v2-inbound/intro.md +10 -0
  42. package/docs/policies/{akamai-ai-firewall → akamai-ai-firewall-v2-inbound}/schema.json +36 -9
  43. package/docs/policies/akamai-firewall-for-ai-inbound/schema.json +1 -0
  44. package/docs/policies/akamai-firewall-for-ai-outbound/schema.json +1 -0
  45. package/docs/policies/amberflo-metering-inbound/schema.json +1 -0
  46. package/docs/policies/api-key-inbound/schema.json +1 -0
  47. package/docs/policies/audit-log-inbound/schema.json +1 -0
  48. package/docs/policies/auth0-jwt-auth-inbound/schema.json +1 -0
  49. package/docs/policies/authzen-inbound/schema.json +1 -0
  50. package/docs/policies/axiomatics-authz-inbound/schema.json +1 -0
  51. package/docs/policies/basic-auth-inbound/schema.json +1 -0
  52. package/docs/policies/bot-detection-inbound/schema.json +1 -0
  53. package/docs/policies/brownout-inbound/schema.json +1 -0
  54. package/docs/policies/caching-inbound/schema.json +1 -0
  55. package/docs/policies/cdn-cache-control-outbound/schema.json +1 -0
  56. package/docs/policies/change-method-inbound/schema.json +1 -0
  57. package/docs/policies/clear-headers-inbound/schema.json +1 -0
  58. package/docs/policies/clear-headers-outbound/schema.json +1 -0
  59. package/docs/policies/clerk-jwt-auth-inbound/schema.json +1 -0
  60. package/docs/policies/cognito-jwt-auth-inbound/schema.json +1 -0
  61. package/docs/policies/comet-opik-tracing-v2-inbound/doc.md +78 -0
  62. package/docs/policies/comet-opik-tracing-v2-inbound/intro.md +10 -0
  63. package/docs/policies/{comet-opik-tracing-inbound → comet-opik-tracing-v2-inbound}/schema.json +40 -5
  64. package/docs/policies/complex-rate-limit-inbound/schema.json +1 -0
  65. package/docs/policies/composite-inbound/schema.json +1 -0
  66. package/docs/policies/composite-outbound/schema.json +1 -0
  67. package/docs/policies/curity-phantom-token-inbound/schema.json +1 -0
  68. package/docs/policies/data-loss-prevention-inbound/doc.md +22 -6
  69. package/docs/policies/data-loss-prevention-inbound/schema.json +10 -1
  70. package/docs/policies/data-loss-prevention-outbound/doc.md +23 -6
  71. package/docs/policies/data-loss-prevention-outbound/schema.json +10 -1
  72. package/docs/policies/firebase-jwt-inbound/schema.json +1 -0
  73. package/docs/policies/formdata-to-json-inbound/schema.json +1 -0
  74. package/docs/policies/galileo-tracing-v2-inbound/doc.md +72 -0
  75. package/docs/policies/galileo-tracing-v2-inbound/intro.md +8 -0
  76. package/docs/policies/{galileo-tracing-inbound → galileo-tracing-v2-inbound}/schema.json +40 -5
  77. package/docs/policies/geo-filter-inbound/schema.json +1 -0
  78. package/docs/policies/graphql-analytics-outbound/schema.json +1 -0
  79. package/docs/policies/graphql-cache-inbound/schema.json +1 -0
  80. package/docs/policies/graphql-complexity-limit-inbound/schema.json +1 -0
  81. package/docs/policies/graphql-disable-introspection-inbound/schema.json +1 -0
  82. package/docs/policies/graphql-introspection-filter-outbound/schema.json +1 -0
  83. package/docs/policies/http-deprecation-outbound/schema.json +1 -0
  84. package/docs/policies/ip-address-restriction-inbound/schema.json +1 -0
  85. package/docs/policies/jwt-scopes-inbound/schema.json +1 -0
  86. package/docs/policies/ldap-auth-inbound/schema.json +1 -0
  87. package/docs/policies/mcp-auth0-oauth-inbound/schema.json +1 -0
  88. package/docs/policies/mcp-capability-filter-inbound/schema.json +1 -0
  89. package/docs/policies/mcp-clerk-oauth-inbound/schema.json +1 -0
  90. package/docs/policies/mcp-cognito-oauth-inbound/schema.json +1 -0
  91. package/docs/policies/mcp-entra-oauth-inbound/schema.json +1 -0
  92. package/docs/policies/mcp-google-oauth-inbound/schema.json +1 -0
  93. package/docs/policies/mcp-keycloak-oauth-inbound/schema.json +1 -0
  94. package/docs/policies/mcp-logto-oauth-inbound/schema.json +1 -0
  95. package/docs/policies/mcp-oauth-inbound/schema.json +1 -0
  96. package/docs/policies/mcp-okta-oauth-inbound/schema.json +1 -0
  97. package/docs/policies/mcp-onelogin-oauth-inbound/schema.json +1 -0
  98. package/docs/policies/mcp-ping-oauth-inbound/schema.json +1 -0
  99. package/docs/policies/mcp-token-exchange-inbound/schema.json +1 -0
  100. package/docs/policies/mcp-workos-oauth-inbound/schema.json +1 -0
  101. package/docs/policies/mock-api-inbound/schema.json +1 -0
  102. package/docs/policies/moesif-inbound/schema.json +1 -0
  103. package/docs/policies/monetization-inbound/schema.json +1 -0
  104. package/docs/policies/mtls-auth-inbound/schema.json +1 -0
  105. package/docs/policies/okta-fga-authz-inbound/schema.json +1 -0
  106. package/docs/policies/okta-jwt-auth-inbound/schema.json +1 -0
  107. package/docs/policies/open-id-jwt-auth-inbound/schema.json +1 -0
  108. package/docs/policies/openfga-authz-inbound/schema.json +1 -0
  109. package/docs/policies/openmeter-inbound/schema.json +1 -0
  110. package/docs/policies/prompt-injection-outbound/schema.json +1 -0
  111. package/docs/policies/propel-auth-jwt-inbound/schema.json +1 -0
  112. package/docs/policies/query-param-to-header-inbound/schema.json +1 -0
  113. package/docs/policies/quota-inbound/schema.json +1 -0
  114. package/docs/policies/rate-limit-inbound/schema.json +1 -0
  115. package/docs/policies/readme-metrics-inbound/schema.json +1 -0
  116. package/docs/policies/remove-headers-inbound/schema.json +1 -0
  117. package/docs/policies/remove-headers-outbound/schema.json +1 -0
  118. package/docs/policies/remove-query-params-inbound/schema.json +1 -0
  119. package/docs/policies/replace-string-outbound/schema.json +1 -0
  120. package/docs/policies/request-size-limit-inbound/schema.json +1 -0
  121. package/docs/policies/request-validation-inbound/schema.json +1 -0
  122. package/docs/policies/require-origin-inbound/schema.json +1 -0
  123. package/docs/policies/require-user-claims-inbound/schema.json +1 -0
  124. package/docs/policies/secret-masking-outbound/schema.json +1 -0
  125. package/docs/policies/semantic-cache-inbound/schema.json +3 -2
  126. package/docs/policies/set-body-inbound/schema.json +1 -0
  127. package/docs/policies/set-headers-inbound/schema.json +1 -0
  128. package/docs/policies/set-headers-outbound/schema.json +1 -0
  129. package/docs/policies/set-query-params-inbound/schema.json +1 -0
  130. package/docs/policies/set-status-outbound/schema.json +1 -0
  131. package/docs/policies/set-upstream-api-key-inbound/schema.json +1 -0
  132. package/docs/policies/sleep-inbound/schema.json +1 -0
  133. package/docs/policies/stripe-webhook-verification-inbound/schema.json +1 -0
  134. package/docs/policies/supabase-jwt-auth-inbound/schema.json +1 -0
  135. package/docs/policies/traffic-splitting-inbound/schema.json +1 -0
  136. package/docs/policies/upstream-aws-federated-auth-inbound/schema.json +1 -0
  137. package/docs/policies/upstream-aws-service-auth-inbound/schema.json +1 -0
  138. package/docs/policies/upstream-azure-ad-service-auth-inbound/schema.json +1 -0
  139. package/docs/policies/upstream-firebase-admin-auth-inbound/schema.json +1 -0
  140. package/docs/policies/upstream-firebase-user-auth-inbound/schema.json +1 -0
  141. package/docs/policies/upstream-gcp-federated-auth-inbound/schema.json +1 -0
  142. package/docs/policies/upstream-gcp-jwt-inbound/schema.json +1 -0
  143. package/docs/policies/upstream-gcp-service-auth-inbound/schema.json +1 -0
  144. package/docs/policies/upstream-oauth-client-credentials-inbound/schema.json +1 -0
  145. package/docs/policies/upstream-zuplo-jwt-auth-inbound/schema.json +1 -0
  146. package/docs/policies/validate-json-schema-inbound/schema.json +1 -0
  147. package/docs/policies/web-bot-auth-inbound/schema.json +1 -0
  148. package/docs/policies/xml-to-json-outbound/schema.json +1 -0
  149. package/package.json +5 -5
  150. package/docs/ai-gateway/policies/akamai-ai-firewall.mdx +0 -122
  151. package/docs/ai-gateway/policies/authentication.mdx +0 -43
  152. package/docs/ai-gateway/policies/comet-opik-tracing.mdx +0 -177
  153. package/docs/ai-gateway/policies/fallback-model.mdx +0 -40
  154. package/docs/ai-gateway/policies/galileo-tracing.mdx +0 -161
  155. package/docs/ai-gateway/policies/metering.mdx +0 -47
  156. package/docs/ai-gateway/policies/model-filtering.mdx +0 -49
  157. package/docs/ai-gateway/policies/semantic-cache.mdx +0 -48
@@ -0,0 +1,442 @@
1
+ # AI Gateway Configuration Executor
2
+
3
+ The AI Gateway Configuration Executor loads each application's configuration
4
+ (when auth or `ai-gateway-configuration-loader-v2-inbound` has not already) and
5
+ runs its ordered inbound policy chain. Configuration comes from route-level
6
+ `ai-gateway-auth-v2-inbound` or the configuration loader when either already
7
+ ran, otherwise from the route's `app_id` path parameter via Gateway Service.
8
+ Applications can still require API keys later by including
9
+ `ai-gateway-auth-v2-inbound` in their own `inboundPolicyChain`.
10
+
11
+ Prefer placing the dedicated configuration loader before this executor on the
12
+ route. When the loader is omitted, this executor still loads configuration
13
+ itself before running the chain.
14
+
15
+ The gateway owner remains in control:
16
+
17
+ - Every selectable policy must be declared in `config/policies.json`.
18
+ - Application and team policy templates determine which entries applications may
19
+ edit or remove before the resulting chain reaches the gateway.
20
+
21
+ ## How application chains behave
22
+
23
+ | Application configuration | Result |
24
+ | -------------------------------------- | ------------------------------------ |
25
+ | No application configuration | No application-selected policies run |
26
+ | `inboundPolicyChain` is absent or `[]` | No application-selected policies run |
27
+ | `inboundPolicyChain` contains entries | Entries run in the listed order |
28
+ | An entry has `enabled: false` | That entry is skipped |
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.
33
+
34
+ This inbound executor does not run `outboundPolicyChain`. That field is reserved
35
+ for an outbound configuration executor on the response pipeline.
36
+
37
+ ## Build an AI Gateway from scratch
38
+
39
+ ### 1. Declare the policies
40
+
41
+ Add the optional configuration loader, the executor, and every policy an
42
+ application may select to `config/policies.json`. Optionally declare AI Gateway
43
+ Authentication when applications or routes will authenticate with application
44
+ API keys.
45
+
46
+ The following example allows applications to select model filtering and semantic
47
+ caching. It does not assign either policy automatically; each application
48
+ chooses the policies it needs in its `inboundPolicyChain`:
49
+
50
+ ```json
51
+ {
52
+ "policies": [
53
+ {
54
+ "name": "ai-gateway-auth-v2-inbound",
55
+ "policyType": "ai-gateway-auth-v2",
56
+ "handler": {
57
+ "export": "AIGatewayAuthV2InboundPolicy",
58
+ "module": "$import(@zuplo/runtime)",
59
+ "options": {
60
+ "cacheTtlSeconds": 60
61
+ }
62
+ }
63
+ },
64
+ {
65
+ "name": "ai-gateway-configuration-loader-v2-inbound",
66
+ "policyType": "ai-gateway-configuration-loader-v2",
67
+ "handler": {
68
+ "export": "AIGatewayConfigurationLoaderV2InboundPolicy",
69
+ "module": "$import(@zuplo/runtime)",
70
+ "options": {
71
+ "cacheTtlSeconds": 60
72
+ }
73
+ }
74
+ },
75
+ {
76
+ "name": "ai-gateway-configuration-executor-v2-inbound",
77
+ "policyType": "ai-gateway-configuration-executor-v2",
78
+ "handler": {
79
+ "export": "AIGatewayConfigurationExecutorV2InboundPolicy",
80
+ "module": "$import(@zuplo/runtime)",
81
+ "options": {
82
+ "cacheTtlSeconds": 60
83
+ }
84
+ }
85
+ },
86
+ {
87
+ "name": "ai-gateway-metering-v2-inbound",
88
+ "policyType": "ai-gateway-metering-v2",
89
+ "handler": {
90
+ "export": "AIGatewayMeteringV2InboundPolicy",
91
+ "module": "$import(@zuplo/runtime)",
92
+ "options": {}
93
+ }
94
+ },
95
+ {
96
+ "name": "ai-gateway-model-filtering-v2-inbound",
97
+ "policyType": "ai-gateway-model-filtering-v2",
98
+ "handler": {
99
+ "export": "AIGatewayModelFilteringV2InboundPolicy",
100
+ "module": "$import(@zuplo/runtime)",
101
+ "options": {
102
+ "models": {
103
+ "completions": {
104
+ "allowList": ["openai/gpt-5-mini", "anthropic/claude-sonnet-4-6"]
105
+ }
106
+ }
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "name": "ai-gateway-fallback-model-v2-inbound",
112
+ "policyType": "ai-gateway-fallback-model-v2",
113
+ "handler": {
114
+ "export": "AIGatewayFallbackModelV2InboundPolicy",
115
+ "module": "$import(@zuplo/runtime)",
116
+ "options": {
117
+ "models": {
118
+ "completions": {
119
+ "fallback": "anthropic/claude-sonnet-4-6"
120
+ }
121
+ },
122
+ "fallbackTimeoutSeconds": 60
123
+ }
124
+ }
125
+ },
126
+ {
127
+ "name": "ai-gateway-semantic-cache-v2-inbound",
128
+ "policyType": "ai-gateway-semantic-cache-v2",
129
+ "handler": {
130
+ "export": "AIGatewaySemanticCacheV2InboundPolicy",
131
+ "module": "$import(@zuplo/runtime)",
132
+ "options": {
133
+ "semanticTolerance": 0.4,
134
+ "expirationSecondsTtl": 3600
135
+ }
136
+ }
137
+ }
138
+ ]
139
+ }
140
+ ```
141
+
142
+ Chain entries use declaration names, not policy types. The executor rejects
143
+ undeclared policies and prevents direct or transitive re-entry into the
144
+ configuration loader or executor. Use application and team policy templates to
145
+ control which declared policies an application may edit or remove.
146
+
147
+ ### 2. Add the loader and executor to the route
148
+
149
+ Place the loader before the executor on each AI Gateway route. The loader loads
150
+ configuration from the route's `app_id` path parameter (or reuses the channel
151
+ when route-level auth already ran); the executor then runs that application's
152
+ `inboundPolicyChain`. The AI Gateway handler runs after the selected inbound
153
+ chain:
154
+
155
+ ```json
156
+ {
157
+ "x-zuplo-route": {
158
+ "corsPolicy": "none",
159
+ "handler": {
160
+ "export": "aiGatewayHandlerV2",
161
+ "module": "$import(@zuplo/runtime)",
162
+ "options": {}
163
+ },
164
+ "policies": {
165
+ "inbound": [
166
+ "ai-gateway-configuration-loader-v2-inbound",
167
+ "ai-gateway-configuration-executor-v2-inbound"
168
+ ]
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ Routes that list only the executor keep working — the executor loads
175
+ configuration when the channel is empty.
176
+
177
+ Authentication is optional and placement controls its scope:
178
+
179
+ - **App-level** — add `ai-gateway-auth-v2-inbound` to an application's
180
+ `inboundPolicyChain`. Only that application requires an API key.
181
+ - **Route-level** — add `ai-gateway-auth-v2-inbound` on the route **before** the
182
+ loader (or before the executor on executor-only routes). That requires an API
183
+ key for every application on the route.
184
+
185
+ Use the same placement on each AI Gateway operation that should support
186
+ application-selected chains.
187
+
188
+ ### 3. Set an application's policy chain
189
+
190
+ An application can inherit the options from `policies.json`. Include
191
+ `ai-gateway-auth-v2-inbound` when this application should require an API key:
192
+
193
+ ```json
194
+ {
195
+ "inboundPolicyChain": [
196
+ {
197
+ "name": "ai-gateway-auth-v2-inbound"
198
+ },
199
+ {
200
+ "name": "ai-gateway-model-filtering-v2-inbound"
201
+ },
202
+ {
203
+ "name": "ai-gateway-fallback-model-v2-inbound"
204
+ },
205
+ {
206
+ "name": "ai-gateway-metering-v2-inbound",
207
+ "options": {
208
+ "limits": {
209
+ "requests": {
210
+ "daily": {
211
+ "enabled": true,
212
+ "limit": 1000
213
+ }
214
+ }
215
+ }
216
+ }
217
+ },
218
+ {
219
+ "name": "ai-gateway-semantic-cache-v2-inbound"
220
+ }
221
+ ]
222
+ }
223
+ ```
224
+
225
+ Place authentication first when the app requires a key, then model filtering,
226
+ fallback-model, and metering so metering can activate the resolved quota
227
+ fallback. Put policies that may short-circuit, such as semantic cache, after
228
+ metering so those requests still count toward request limits.
229
+
230
+ An application can also provide a complete options object for an entry:
231
+
232
+ ```json
233
+ {
234
+ "inboundPolicyChain": [
235
+ {
236
+ "name": "ai-gateway-model-filtering-v2-inbound",
237
+ "options": {
238
+ "models": {
239
+ "completions": {
240
+ "allowList": ["anthropic/claude-sonnet-4-6"]
241
+ }
242
+ }
243
+ }
244
+ }
245
+ ]
246
+ }
247
+ ```
248
+
249
+ Entry options replace the declaration's entire options object; fields are not
250
+ merged. Omit `options` to inherit the complete `handler.options` value from
251
+ `policies.json`.
252
+
253
+ An entry may also carry portal/template ACL metadata in `permissions`. The
254
+ executor accepts this field and ignores it when running the chain; unknown keys
255
+ under `permissions` fail closed:
256
+
257
+ ```json
258
+ {
259
+ "inboundPolicyChain": [
260
+ {
261
+ "name": "ai-gateway-model-filtering-v2-inbound",
262
+ "permissions": {
263
+ "canEdit": true,
264
+ "canRemove": false
265
+ },
266
+ "options": {
267
+ "models": {
268
+ "completions": {
269
+ "allowList": ["anthropic/claude-sonnet-4-6"]
270
+ }
271
+ }
272
+ }
273
+ }
274
+ ]
275
+ }
276
+ ```
277
+
278
+ | `permissions` field | Meaning (portal/templates) |
279
+ | ------------------- | ---------------------------------------------------- |
280
+ | `canEdit` | Whether the application may edit the entry's options |
281
+ | `canRemove` | Whether the application may remove the entry |
282
+
283
+ Omit `permissions`, or either flag, when the portal does not need to record that
284
+ constraint on the stored chain.
285
+
286
+ ## Write a custom policy for the chain
287
+
288
+ A chain entry can run any declared custom policy. The policy uses the standard
289
+ inbound policy signature and receives its options from the chain entry (or the
290
+ declaration, when the entry omits `options`). Entry-owned options are
291
+ deep-copied for each invocation, so mutating them is safe. Inherited declaration
292
+ options are passed through unchanged to match static route behavior; do not
293
+ mutate them.
294
+
295
+ ```ts
296
+ import {
297
+ AIGatewayModelRouting,
298
+ AIGatewayModels,
299
+ ZuploContext,
300
+ ZuploRequest,
301
+ } from "@zuplo/runtime";
302
+
303
+ interface CheapestModelOptions {
304
+ providers?: string[];
305
+ }
306
+
307
+ export default async function cheapestModel(
308
+ request: ZuploRequest,
309
+ context: ZuploContext,
310
+ options: CheapestModelOptions
311
+ ): Promise<ZuploRequest | Response> {
312
+ // The authenticated application: sub is the application name, data its
313
+ // metadata.
314
+ context.log.info(`Routing for application ${request.user?.sub}`);
315
+
316
+ // Read the live provider and model catalog. Credentials are never returned.
317
+ const allowed = options.providers ?? ["openai", "anthropic"];
318
+ const candidates = (await AIGatewayModels.load(context))
319
+ .filter(({ providerName }) => allowed.includes(providerName.toLowerCase()))
320
+ .flatMap((provider) =>
321
+ provider.models.map((model) => ({
322
+ providerName: provider.providerName,
323
+ model,
324
+ }))
325
+ )
326
+ .filter(
327
+ ({ model }) =>
328
+ model.capability === "completions" && model.status === "active"
329
+ )
330
+ .sort(
331
+ (left, right) =>
332
+ left.model.inputCostPerToken +
333
+ left.model.outputCostPerToken -
334
+ (right.model.inputCostPerToken + right.model.outputCostPerToken)
335
+ );
336
+
337
+ const cheapest = candidates[0];
338
+ if (!cheapest) {
339
+ // Returning a Response short-circuits the chain and answers the request.
340
+ return new Response("No eligible model", { status: 503 });
341
+ }
342
+
343
+ // Select the model the gateway calls for this request.
344
+ await AIGatewayModelRouting.set(context, {
345
+ completions: `${cheapest.providerName}/${cheapest.model.model}`,
346
+ });
347
+ return request;
348
+ }
349
+ ```
350
+
351
+ From a chain policy you can:
352
+
353
+ - **Read the calling application** from `request.user`: `sub` is the application
354
+ name and `data` its metadata.
355
+ - **Read the model catalog** with `AIGatewayModels.load(context)`.
356
+ - **Choose the model for the request** with
357
+ `AIGatewayModelRouting.set(context, routing)`, and read the current selection
358
+ with `AIGatewayModelRouting.get(context)`.
359
+ - **Block or answer the request** by returning a `Response`; later chain entries
360
+ do not run.
361
+ - **Receive settings** through the chain entry's `options`, exactly like the
362
+ built-in policies.
363
+
364
+ ## Store secrets safely
365
+
366
+ Keep credentials and other environment-backed values in the pre-declared
367
+ policy's `handler.options`, then omit `options` from the application chain entry
368
+ to inherit them:
369
+
370
+ ```json
371
+ {
372
+ "name": "my-ai-guardrail",
373
+ "policyType": "custom-code-inbound",
374
+ "handler": {
375
+ "export": "default",
376
+ "module": "$import(./modules/my-ai-guardrail)",
377
+ "options": {
378
+ "apiKey": "$env(MY_GUARDRAIL_API_KEY)"
379
+ }
380
+ }
381
+ }
382
+ ```
383
+
384
+ ```json
385
+ {
386
+ "inboundPolicyChain": [
387
+ {
388
+ "name": "my-ai-guardrail"
389
+ }
390
+ ]
391
+ }
392
+ ```
393
+
394
+ Do not place `$env(...)` expressions in application configuration. Environment
395
+ references are resolved when gateway configuration is built, while application
396
+ chains are evaluated when a request arrives.
397
+
398
+ ## Chain entry reference
399
+
400
+ Every chain entry supports:
401
+
402
+ - `name` (required): Name of a declaration in `policies.json`.
403
+ - `options` (optional): Complete replacement options for this invocation. Omit
404
+ it to inherit the declaration's options.
405
+ - `enabled` (optional): Set to `false` to keep an entry in the configuration
406
+ without running it. Omitted or `true` entries run normally.
407
+
408
+ Disabled entries are still validated. They must be well-formed, name a declared
409
+ policy, contain no literal `$env(...)` value, and cannot select the
410
+ configuration loader or executor.
411
+
412
+ The executor permits repeated entries and cannot infer the behavior of custom or
413
+ wrapper policies. Configuration authors are responsible for avoiding repeated
414
+ invocation when a policy is not safe to run more than once.
415
+
416
+ ## Empty chains
417
+
418
+ An application without an `inboundPolicyChain`, or with an explicit empty array,
419
+ runs no application-selected policies:
420
+
421
+ ```json
422
+ {
423
+ "inboundPolicyChain": []
424
+ }
425
+ ```
426
+
427
+ Ensure the loaded configuration supplies every policy required for the request,
428
+ or attach required policies directly to the route.
429
+
430
+ ## Configuration checklist
431
+
432
+ Before deploying:
433
+
434
+ 1. Declare every selectable policy in `config/policies.json`.
435
+ 2. Configure application and team policy templates with the policies each app
436
+ may edit or remove.
437
+ 3. Put environment-backed values in declared policy options.
438
+ 4. Place the configuration loader before the executor on every AI Gateway route
439
+ (or the executor alone when you prefer the combined path). Add AI Gateway
440
+ Authentication to an application's chain for that app only, or before the
441
+ loader on the route to require keys for every app.
442
+ 5. Ensure every application chain includes the policies required for that route.
@@ -0,0 +1,14 @@
1
+ The AI Gateway Configuration Executor loads each application's configuration
2
+ (when auth or the configuration loader has not already) and runs an ordered
3
+ inbound policy chain assembled from policies declared by the gateway.
4
+ Applications may also include authentication in their own chain. This makes it
5
+ possible to offer different routing, caching, guardrail, metering, and tracing
6
+ behavior from one AI Gateway deployment.
7
+
8
+ Prefer placing `ai-gateway-configuration-loader-v2-inbound` before this executor
9
+ on the route so loading and chain execution stay separate. When the loader is
10
+ omitted, this executor still loads configuration itself.
11
+
12
+ The gateway owner decides which policy declarations are available, while
13
+ application and team templates control which entries an application may edit or
14
+ remove. Each request carries the resulting chain and policy options.
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/ai-gateway-configuration-executor-v2-inbound.json",
4
+ "type": "object",
5
+ "title": "AI Gateway Configuration Executor",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": false,
11
+ "isHidden": false,
12
+ "requiresAI": true,
13
+ "policyType": "ai-gateway-configuration-executor-v2",
14
+ "products": ["ai-gateway"],
15
+ "description": "Loads the app configuration for the request (when auth or the configuration loader has not already) and runs the inbound policy chain from that configuration.\n\nPlace this policy on AI Gateway routes after optional authentication and optional `ai-gateway-configuration-loader-v2-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.",
16
+ "deprecatedMessage": "",
17
+ "required": ["handler"],
18
+ "properties": {
19
+ "handler": {
20
+ "type": "object",
21
+ "default": {},
22
+ "required": ["export", "module", "options"],
23
+ "properties": {
24
+ "export": {
25
+ "const": "AIGatewayConfigurationExecutorV2InboundPolicy",
26
+ "description": "The name of the exported type"
27
+ },
28
+ "module": {
29
+ "const": "$import(@zuplo/runtime)",
30
+ "description": "The module containing the policy"
31
+ },
32
+ "options": {
33
+ "x-zuplo-policy-type": "ai-gateway-configuration-executor-v2",
34
+ "title": "AIGatewayConfigurationExecutorV2InboundPolicyOptions",
35
+ "type": "object",
36
+ "description": "Options for loading each application's AI Gateway configuration and running its inbound policy chain.",
37
+ "additionalProperties": false,
38
+ "required": [],
39
+ "properties": {
40
+ "cacheTtlSeconds": {
41
+ "type": "number",
42
+ "default": 10,
43
+ "minimum": 2,
44
+ "description": "The time in seconds to cache app configurations loaded by app\\_id. Defaults to 10 seconds when omitted. Higher values decrease latency; lower values pick up portal changes sooner. Cached results remain valid until the cache expires even if the configuration changes in the portal. This cache is only used when neither ai-gateway-auth-v2-inbound nor ai-gateway-configuration-loader-v2-inbound already loaded the configuration for the request."
45
+ }
46
+ }
47
+ }
48
+ },
49
+ "examples": [
50
+ {
51
+ "export": "AIGatewayConfigurationExecutorV2InboundPolicy",
52
+ "module": "$import(@zuplo/runtime)",
53
+ "options": {
54
+ "cacheTtlSeconds": 10
55
+ }
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,105 @@
1
+ # AI Gateway Configuration Loader
2
+
3
+ The AI Gateway Configuration Loader loads each application's configuration into
4
+ the request-scoped app-configuration channel. Configuration comes from
5
+ route-level `ai-gateway-auth-v2-inbound` when that policy already ran, otherwise
6
+ from the route's `app_id` path parameter via Gateway Service.
7
+
8
+ This policy does **not** run `inboundPolicyChain`. Pair it with
9
+ `ai-gateway-configuration-executor-v2-inbound` when applications select policies
10
+ dynamically. If you omit this loader, the executor still loads configuration
11
+ before running the chain.
12
+
13
+ Applications can still require API keys later by including
14
+ `ai-gateway-auth-v2-inbound` in their own `inboundPolicyChain`.
15
+
16
+ ## Build an AI Gateway from scratch
17
+
18
+ ### 1. Declare the policies
19
+
20
+ Add the loader, the executor, and every policy an application may select to
21
+ `config/policies.json`:
22
+
23
+ ```json
24
+ {
25
+ "policies": [
26
+ {
27
+ "name": "ai-gateway-configuration-loader-v2-inbound",
28
+ "policyType": "ai-gateway-configuration-loader-v2",
29
+ "handler": {
30
+ "export": "AIGatewayConfigurationLoaderV2InboundPolicy",
31
+ "module": "$import(@zuplo/runtime)",
32
+ "options": {
33
+ "cacheTtlSeconds": 60
34
+ }
35
+ }
36
+ },
37
+ {
38
+ "name": "ai-gateway-configuration-executor-v2-inbound",
39
+ "policyType": "ai-gateway-configuration-executor-v2",
40
+ "handler": {
41
+ "export": "AIGatewayConfigurationExecutorV2InboundPolicy",
42
+ "module": "$import(@zuplo/runtime)",
43
+ "options": {
44
+ "cacheTtlSeconds": 60
45
+ }
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ `cacheTtlSeconds` sets how long a loaded app configuration is cached by
53
+ `app_id`. It defaults to 10 seconds and accepts any value of 2 seconds or more —
54
+ raise it to cut Gateway Service calls, lower it to pick up portal changes
55
+ sooner. The AI Gateway project template ships 5 seconds.
56
+
57
+ Neither the loader nor the executor may appear in an application's
58
+ `inboundPolicyChain`. The executor rejects those entries fail-closed.
59
+
60
+ ### 2. Add the loader and executor to the route
61
+
62
+ Place the loader before the executor on each AI Gateway route:
63
+
64
+ ```json
65
+ {
66
+ "x-zuplo-route": {
67
+ "corsPolicy": "none",
68
+ "handler": {
69
+ "export": "aiGatewayHandlerV2",
70
+ "module": "$import(@zuplo/runtime)",
71
+ "options": {}
72
+ },
73
+ "policies": {
74
+ "inbound": [
75
+ "ai-gateway-configuration-loader-v2-inbound",
76
+ "ai-gateway-configuration-executor-v2-inbound"
77
+ ]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ Authentication is optional and placement controls its scope:
84
+
85
+ - **App-level** — add `ai-gateway-auth-v2-inbound` to an application's
86
+ `inboundPolicyChain`. Only that application requires an API key.
87
+ - **Route-level** — add `ai-gateway-auth-v2-inbound` on the route **before** the
88
+ loader. That requires an API key for every application on the route.
89
+
90
+ ### Executor-only routes
91
+
92
+ Routes that list only `ai-gateway-configuration-executor-v2-inbound` keep
93
+ working. The executor loads configuration when the channel is empty, then runs
94
+ the chain. Use the dedicated loader when you want other route policies between
95
+ load and chain execution, or a clearer separation of concerns.
96
+
97
+ ## Configuration checklist
98
+
99
+ Before deploying:
100
+
101
+ 1. Declare the loader (optional but recommended), the executor, and every
102
+ selectable policy in `config/policies.json`.
103
+ 2. Put the loader before the executor on every AI Gateway route — or put only
104
+ the executor when you prefer the combined path.
105
+ 3. Do not add the loader or executor to any application's `inboundPolicyChain`.
@@ -0,0 +1,6 @@
1
+ The AI Gateway Configuration Loader loads each application's configuration into
2
+ the request-scoped channel — reusing the channel from route-level
3
+ `ai-gateway-auth-v2-inbound` when present, otherwise fetching by path `app_id`.
4
+ It does not run the application's policy chain; place
5
+ `ai-gateway-configuration-executor-v2-inbound` after it for that. When this
6
+ loader is omitted, the executor still loads configuration itself.
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/ai-gateway-configuration-loader-v2-inbound.json",
4
+ "type": "object",
5
+ "title": "AI Gateway Configuration Loader",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": false,
11
+ "isHidden": false,
12
+ "requiresAI": true,
13
+ "policyType": "ai-gateway-configuration-loader-v2",
14
+ "products": ["ai-gateway"],
15
+ "description": "Loads the AI Gateway app configuration for the request into the request-scoped channel and does nothing else.\n\nPlace this policy on AI Gateway routes before `ai-gateway-configuration-executor-v2-inbound` when you want configuration loading separated from chain execution. When `ai-gateway-auth-v2-inbound` already populated the channel, this policy reuses it. Otherwise it loads the configuration with the route's `app_id` path parameter.\n\nIf this policy is omitted, the configuration executor still loads configuration itself before running the application chain.",
16
+ "deprecatedMessage": "",
17
+ "required": ["handler"],
18
+ "properties": {
19
+ "handler": {
20
+ "type": "object",
21
+ "default": {},
22
+ "required": ["export", "module", "options"],
23
+ "properties": {
24
+ "export": {
25
+ "const": "AIGatewayConfigurationLoaderV2InboundPolicy",
26
+ "description": "The name of the exported type"
27
+ },
28
+ "module": {
29
+ "const": "$import(@zuplo/runtime)",
30
+ "description": "The module containing the policy"
31
+ },
32
+ "options": {
33
+ "x-zuplo-policy-type": "ai-gateway-configuration-loader-v2",
34
+ "title": "AIGatewayConfigurationLoaderV2InboundPolicyOptions",
35
+ "type": "object",
36
+ "description": "Options for loading each application's AI Gateway configuration into the request-scoped channel.",
37
+ "additionalProperties": false,
38
+ "required": [],
39
+ "properties": {
40
+ "cacheTtlSeconds": {
41
+ "type": "number",
42
+ "default": 10,
43
+ "minimum": 2,
44
+ "description": "The time in seconds to cache app configurations loaded by app\\_id. Defaults to 10 seconds when omitted. Higher values decrease latency; lower values pick up portal changes sooner. Cached results remain valid until the cache expires even if the configuration changes in the portal. When ai-gateway-auth-v2-inbound already loaded the configuration for the request, this cache is not used."
45
+ }
46
+ }
47
+ }
48
+ },
49
+ "examples": [
50
+ {
51
+ "export": "AIGatewayConfigurationLoaderV2InboundPolicy",
52
+ "module": "$import(@zuplo/runtime)",
53
+ "options": {
54
+ "cacheTtlSeconds": 10
55
+ }
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }