@jeffusion/bungee-core 5.18.0 → 5.20.0
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/dist/gateway/contracts.d.ts +31 -2
- package/dist/gateway/dispatch-plugin.d.ts +7 -0
- package/dist/gateway/dispatch-targets.d.ts +4 -0
- package/dist/gateway/plugin.js +2 -0
- package/dist/gateway/routing-plugin.d.ts +3 -1
- package/dist/hooks/plugin-hooks.d.ts +6 -1
- package/dist/main.js +182 -50
- package/dist/plugin-extensions.d.ts +2 -0
- package/dist/plugins/ai-transformer/index.js +11 -1
- package/dist/plugins/ai-transformer/index.js.map +6 -4
- package/dist/plugins/anthropic-request-sanitizer/index.js.map +1 -1
- package/dist/plugins/anthropic-tool-name-transformer/index.js.map +1 -1
- package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
- package/dist/plugins/chatgpt-oauth/index.js.map +1 -1
- package/dist/plugins/codex-router/control.js +673 -2
- package/dist/plugins/codex-router/control.js.map +19 -4
- package/dist/plugins/codex-router/index.js +3938 -4
- package/dist/plugins/codex-router/index.js.map +24 -7
- package/dist/plugins/codex-router/manifest.json +15 -1
- package/dist/plugins/deepseek-reasoning-fix/index.js.map +1 -1
- package/dist/plugins/key-access/control.js.map +2 -2
- package/dist/plugins/key-access/index.js +14 -10
- package/dist/plugins/key-access/index.js.map +4 -4
- package/dist/plugins/key-access/ingress.js +14 -10
- package/dist/plugins/key-access/ingress.js.map +4 -4
- package/dist/plugins/key-rate-limit/index.js.map +1 -1
- package/dist/plugins/local-accounts/control.js.map +1 -1
- package/dist/plugins/local-accounts/index.js.map +1 -1
- package/dist/plugins/model-mapping/index.js.map +1 -1
- package/dist/plugins/models-dev/index.js.map +1 -1
- package/dist/plugins/openai-messages-to-chat/index.js +691 -543
- package/dist/plugins/openai-messages-to-chat/index.js.map +9 -5
- package/dist/plugins/signature-repair/index.js.map +1 -1
- package/dist/plugins/token-budget/control.js.map +1 -1
- package/dist/plugins/token-budget/index.js.map +1 -1
- package/dist/plugins/token-metering/index.js +652 -71
- package/dist/plugins/token-metering/index.js.map +6 -4
- package/dist/plugins/token-stats/control.js.map +1 -1
- package/dist/plugins/token-stats/index.js.map +1 -1
- package/dist/scoped-plugin-registry.d.ts +5 -2
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Typed extension stages shared by built-in and project plugins. */
|
|
2
|
-
import type { AppConfig, RouteConfig, CorsConfig } from '@jeffusion/bungee-types';
|
|
2
|
+
import type { AppConfig, RouteConfig, CorsConfig, LLMProtocol } from '@jeffusion/bungee-types';
|
|
3
3
|
import type { ExpressionContext } from '../expression-engine';
|
|
4
4
|
import type { EffectiveRouteConfig, RuntimeUpstream, RequestSnapshot } from '../worker/types';
|
|
5
5
|
import type { HandleRequestRuntimeContext } from './request-plugin';
|
|
@@ -24,6 +24,35 @@ export type GatewayBodyRuleArguments = [body: Record<string, any>, rules: Modifi
|
|
|
24
24
|
export type GatewayHeaderRuleArguments = [headers: Headers, rules: ModificationRules['headers'], context: ExpressionContext];
|
|
25
25
|
export type GatewayCorsArguments = [response: Response, cors: CorsConfig | undefined, request: Request];
|
|
26
26
|
export type GatewayQueryRuleArguments = [params: URLSearchParams, rules: ModificationRules['query'], context: ExpressionContext, requestLog: any];
|
|
27
|
+
export interface GatewayDispatchTarget {
|
|
28
|
+
readonly type: 'route' | 'service';
|
|
29
|
+
readonly id: string;
|
|
30
|
+
}
|
|
31
|
+
export interface DispatchRequestInput {
|
|
32
|
+
readonly context: import('../hooks').MutableRequestContext;
|
|
33
|
+
readonly targets: readonly (GatewayDispatchTarget & {
|
|
34
|
+
readonly protocol?: LLMProtocol;
|
|
35
|
+
})[];
|
|
36
|
+
readonly principal?: import('../plugin-extensions').DataPrincipal;
|
|
37
|
+
readonly signal: AbortSignal;
|
|
38
|
+
readonly servingRevision?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface DispatchRequestDecision {
|
|
41
|
+
readonly target: GatewayDispatchTarget;
|
|
42
|
+
readonly adapter?: import('../plugin.types').Plugin;
|
|
43
|
+
}
|
|
44
|
+
export interface GatewayDispatchInput extends Omit<DispatchRequestInput, 'targets'> {
|
|
45
|
+
readonly config: AppConfig;
|
|
46
|
+
readonly entry: RouteConfig;
|
|
47
|
+
}
|
|
48
|
+
export interface GatewayDispatchDecision {
|
|
49
|
+
readonly route: RouteConfig;
|
|
50
|
+
readonly effective: EffectiveRouteConfig;
|
|
51
|
+
readonly context: import('../hooks').MutableRequestContext;
|
|
52
|
+
readonly entryRouteId?: string;
|
|
53
|
+
readonly target?: GatewayDispatchTarget;
|
|
54
|
+
readonly adapter?: import('../plugin.types').Plugin;
|
|
55
|
+
}
|
|
27
56
|
export interface GatewayRouteInput {
|
|
28
57
|
readonly request: Request;
|
|
29
58
|
readonly config: AppConfig;
|
|
@@ -107,4 +136,4 @@ export interface GatewayLogResult {
|
|
|
107
136
|
readonly logger?: RequestLogger;
|
|
108
137
|
}
|
|
109
138
|
/** Provider stages are fixed, required, and registered exactly once at startup. */
|
|
110
|
-
export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayWebSocket", "onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
|
|
139
|
+
export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayWebSocket", "onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayDispatch", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PluginConfigField } from '../plugin.types';
|
|
2
|
+
import type { GatewayDispatchTarget } from './contracts';
|
|
3
|
+
/** Extract references only from the trusted schema, never from arbitrary request JSON. */
|
|
4
|
+
export declare function declaredDispatchTargets(options: Record<string, unknown>, fields: readonly PluginConfigField[]): GatewayDispatchTarget[];
|
package/dist/gateway/plugin.js
CHANGED
|
@@ -325,6 +325,8 @@ function createPluginHooks() {
|
|
|
325
325
|
onGatewayBody: new SyncBailHook("onGatewayBody"),
|
|
326
326
|
onGatewayRequest: new AsyncSeriesBailHook("onGatewayRequest"),
|
|
327
327
|
onGatewayRoute: new AsyncSeriesBailHook("onGatewayRoute"),
|
|
328
|
+
onGatewayDispatch: new AsyncSeriesBailHook("onGatewayDispatch"),
|
|
329
|
+
onDispatchRequest: new AsyncSeriesBailHook("onDispatchRequest"),
|
|
328
330
|
onGatewayAdmission: new AsyncSeriesBailHook("onGatewayAdmission"),
|
|
329
331
|
onGatewayAdmissionSession: new AsyncSeriesBailHook("onGatewayAdmissionSession"),
|
|
330
332
|
onGatewayAdmissionPrepare: new AsyncSeriesBailHook("onGatewayAdmissionPrepare"),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { CorsConfig } from '@jeffusion/bungee-types';
|
|
1
|
+
import type { AppConfig, RouteConfig, CorsConfig } from '@jeffusion/bungee-types';
|
|
2
|
+
import type { EffectiveRouteConfig } from '../worker/types';
|
|
2
3
|
import type { Plugin, PluginHooks } from '@jeffusion/bungee-core/plugin';
|
|
4
|
+
export declare function resolveEffectiveRoute(config: AppConfig, route: RouteConfig): EffectiveRouteConfig;
|
|
3
5
|
export declare function applyCorsHeaders(response: Response, cors: CorsConfig | undefined, request: Request): Response;
|
|
4
6
|
export declare class RoutingPlugin implements Plugin {
|
|
5
7
|
bodyRequirements(): {
|
|
@@ -11,7 +11,7 @@ import type { PluginServices } from '../plugin-services';
|
|
|
11
11
|
import type { PluginStorage, SSEEnvelope } from '../plugin.types';
|
|
12
12
|
import type { InterceptResult, PluginPhase } from '@jeffusion/bungee-types';
|
|
13
13
|
import type { RawResponseResult } from '../plugin-control/contracts';
|
|
14
|
-
import type { GatewayBodyArguments, GatewayRequestArguments, GatewayForwardArguments, GatewayResponseRuleArguments, GatewayBodyRuleArguments, GatewayQueryRuleArguments, GatewayHeaderRuleArguments, GatewayCorsArguments, GatewayRouteInput, GatewayRouteDecision, GatewayAdmissionInput, GatewayAdmissionDecision, GatewayAdmissionSessionInput, GatewayAdmissionPrepareInput, GatewaySelectInput, GatewaySelectDecision, GatewayFailoverInput, GatewayRetryInput, GatewayLogInput, GatewayLogResult } from '../gateway/contracts';
|
|
14
|
+
import type { GatewayBodyArguments, GatewayRequestArguments, GatewayForwardArguments, GatewayResponseRuleArguments, GatewayBodyRuleArguments, GatewayQueryRuleArguments, GatewayHeaderRuleArguments, GatewayCorsArguments, GatewayRouteInput, GatewayRouteDecision, GatewayDispatchInput, GatewayDispatchDecision, DispatchRequestInput, DispatchRequestDecision, GatewayAdmissionInput, GatewayAdmissionDecision, GatewayAdmissionSessionInput, GatewayAdmissionPrepareInput, GatewaySelectInput, GatewaySelectDecision, GatewayFailoverInput, GatewayRetryInput, GatewayLogInput, GatewayLogResult } from '../gateway/contracts';
|
|
15
15
|
import type { WorkerRequestAdmission, PreparedAdmissionAttempt } from '../data-admission/worker';
|
|
16
16
|
import type { FailoverCoordinator } from '../worker/upstream/failover-coordinator';
|
|
17
17
|
import type { ProxyRequestResult } from '../gateway/forward-plugin';
|
|
@@ -184,6 +184,9 @@ export interface PluginScopeInfo {
|
|
|
184
184
|
export interface PluginInitContext {
|
|
185
185
|
/** Host-controlled, dependency-restricted public service facade. */
|
|
186
186
|
readonly services?: PluginServices;
|
|
187
|
+
readonly dispatchTargets?: readonly (import('../gateway/contracts').GatewayDispatchTarget & {
|
|
188
|
+
readonly protocol?: import('@jeffusion/bungee-types').LLMProtocol;
|
|
189
|
+
})[];
|
|
187
190
|
/** 插件配置 */
|
|
188
191
|
config: Record<string, any>;
|
|
189
192
|
/** 插件存储 */
|
|
@@ -233,6 +236,8 @@ export interface PluginHooks {
|
|
|
233
236
|
onGatewayCors: AsyncSeriesBailHook<GatewayCorsArguments, Response>;
|
|
234
237
|
onGatewayRequest: AsyncSeriesBailHook<GatewayRequestArguments, Response>;
|
|
235
238
|
onGatewayRoute: AsyncSeriesBailHook<[GatewayRouteInput], GatewayRouteDecision>;
|
|
239
|
+
onGatewayDispatch: AsyncSeriesBailHook<[GatewayDispatchInput], GatewayDispatchDecision>;
|
|
240
|
+
onDispatchRequest: AsyncSeriesBailHook<[DispatchRequestInput], DispatchRequestDecision>;
|
|
236
241
|
onGatewayAdmission: AsyncSeriesBailHook<[GatewayAdmissionInput], GatewayAdmissionDecision>;
|
|
237
242
|
onGatewayAdmissionSession: AsyncSeriesBailHook<[GatewayAdmissionSessionInput], WorkerRequestAdmission>;
|
|
238
243
|
onGatewayAdmissionPrepare: AsyncSeriesBailHook<[GatewayAdmissionPrepareInput], PreparedAdmissionAttempt[]>;
|
package/dist/main.js
CHANGED
|
@@ -455,14 +455,17 @@ var require_main = __commonJS(function(exports, module) {
|
|
|
455
455
|
function validateGatewayTargets(config, schemas, context) {
|
|
456
456
|
if (!schemas)
|
|
457
457
|
return;
|
|
458
|
-
const routes = new
|
|
459
|
-
const services = new
|
|
458
|
+
const routes = new Map(config.routes.map((route) => [route.id, route]));
|
|
459
|
+
const services = new Map(config.services.map((service) => [service.id, service]));
|
|
460
460
|
const edges = new Map;
|
|
461
461
|
const visit = (value, field, owner, path) => {
|
|
462
462
|
if (field.type === "gateway_target" && value && typeof value === "object") {
|
|
463
463
|
const target = value;
|
|
464
|
-
|
|
464
|
+
const entity = (target.type === "route" ? routes : services).get(target.id);
|
|
465
|
+
if (!entity)
|
|
465
466
|
context.add("invalid_value", path, "Dispatch target does not exist");
|
|
467
|
+
else if (!entity.llm_protocol)
|
|
468
|
+
context.add("invalid_value", path, "Dispatch target requires an explicit receiving protocol");
|
|
466
469
|
const refs = edges.get(owner) ?? new Set;
|
|
467
470
|
refs.add(`${target.type}:${target.id}`);
|
|
468
471
|
edges.set(owner, refs);
|
|
@@ -30765,7 +30768,7 @@ var package_default;
|
|
|
30765
30768
|
var init_package = __esm(() => {
|
|
30766
30769
|
package_default = {
|
|
30767
30770
|
name: "@jeffusion/bungee-core",
|
|
30768
|
-
version: "5.
|
|
30771
|
+
version: "5.20.0",
|
|
30769
30772
|
description: "Bungee reverse proxy core engine",
|
|
30770
30773
|
main: "./dist/main.js",
|
|
30771
30774
|
types: "./dist/main.d.ts",
|
|
@@ -202919,6 +202922,8 @@ function createPluginHooks() {
|
|
|
202919
202922
|
onGatewayBody: new SyncBailHook("onGatewayBody"),
|
|
202920
202923
|
onGatewayRequest: new AsyncSeriesBailHook("onGatewayRequest"),
|
|
202921
202924
|
onGatewayRoute: new AsyncSeriesBailHook("onGatewayRoute"),
|
|
202925
|
+
onGatewayDispatch: new AsyncSeriesBailHook("onGatewayDispatch"),
|
|
202926
|
+
onDispatchRequest: new AsyncSeriesBailHook("onDispatchRequest"),
|
|
202922
202927
|
onGatewayAdmission: new AsyncSeriesBailHook("onGatewayAdmission"),
|
|
202923
202928
|
onGatewayAdmissionSession: new AsyncSeriesBailHook("onGatewayAdmissionSession"),
|
|
202924
202929
|
onGatewayAdmissionPrepare: new AsyncSeriesBailHook("onGatewayAdmissionPrepare"),
|
|
@@ -202990,6 +202995,23 @@ var init_hooks = __esm(() => {
|
|
|
202990
202995
|
init_plugin_hooks();
|
|
202991
202996
|
});
|
|
202992
202997
|
|
|
202998
|
+
// src/gateway/dispatch-targets.ts
|
|
202999
|
+
function declaredDispatchTargets(options, fields) {
|
|
203000
|
+
const result = [];
|
|
203001
|
+
const walk = (value, field) => {
|
|
203002
|
+
if (field.type === "gateway_target" && value && typeof value === "object") {
|
|
203003
|
+
const target = value;
|
|
203004
|
+
if (["route", "service"].includes(target.type) && typeof target.id === "string")
|
|
203005
|
+
result.push({ type: target.type, id: target.id });
|
|
203006
|
+
} else if (field.type === "array" && field.items && Array.isArray(value))
|
|
203007
|
+
value.forEach((item) => walk(item, field.items));
|
|
203008
|
+
else if (field.type === "object" && field.properties && value && typeof value === "object")
|
|
203009
|
+
field.properties.forEach((property) => walk(value[property.name], property));
|
|
203010
|
+
};
|
|
203011
|
+
fields.forEach((field) => walk(options[field.name], field));
|
|
203012
|
+
return result;
|
|
203013
|
+
}
|
|
203014
|
+
|
|
202993
203015
|
// src/plugin-services/rpc-runtime.ts
|
|
202994
203016
|
import { isProxy as isProxy4 } from "util/types";
|
|
202995
203017
|
function positiveLimit2(value, fallback, label) {
|
|
@@ -213003,6 +213025,7 @@ function assertBodyContract(handler) {
|
|
|
213003
213025
|
|
|
213004
213026
|
class ScopedPluginRegistry {
|
|
213005
213027
|
serviceHost;
|
|
213028
|
+
dispatchConfig;
|
|
213006
213029
|
rpcContexts = new Map;
|
|
213007
213030
|
requestLeases = new AsyncLocalStorage3;
|
|
213008
213031
|
runWithRequestLeases(leases, run) {
|
|
@@ -213331,6 +213354,7 @@ ${errorMessages}`);
|
|
|
213331
213354
|
}
|
|
213332
213355
|
async createInitContext(pluginName, config, scope) {
|
|
213333
213356
|
const scopeInfo = scope ? toPluginScopeInfo(scope) : undefined;
|
|
213357
|
+
const dispatchTargets = scope?.type === "route" ? declaredDispatchTargets(config, this.pluginClasses.get(pluginName)?.configSchema ?? []).map((target) => ({ ...target, protocol: (target.type === "route" ? this.dispatchConfig?.routes : this.dispatchConfig?.services)?.find((entity) => entity.id === target.id)?.llm_protocol })) : [];
|
|
213334
213358
|
const services = this.serviceHost.createContext(pluginName, scope ? getScopeKey(scope) : "global", this.serviceDependencies.get(pluginName) ?? {});
|
|
213335
213359
|
this.rpcContexts.set(`${pluginName}\x00${scope ? getScopeKey(scope) : "global"}`, services);
|
|
213336
213360
|
if (isPluginContextManagerInitialized()) {
|
|
@@ -213341,18 +213365,20 @@ ${errorMessages}`);
|
|
|
213341
213365
|
...existingContext,
|
|
213342
213366
|
config,
|
|
213343
213367
|
scope: scopeInfo,
|
|
213344
|
-
services
|
|
213368
|
+
services,
|
|
213369
|
+
dispatchTargets
|
|
213345
213370
|
};
|
|
213346
213371
|
}
|
|
213347
213372
|
const newContext = contextManager.getOrCreateContext(pluginName, "", config);
|
|
213348
|
-
return { ...newContext, scope: scopeInfo, services };
|
|
213373
|
+
return { ...newContext, scope: scopeInfo, services, dispatchTargets };
|
|
213349
213374
|
}
|
|
213350
213375
|
return {
|
|
213351
213376
|
config,
|
|
213352
213377
|
storage: this.createDummyStorage(),
|
|
213353
213378
|
logger: this.createPluginLogger(pluginName),
|
|
213354
213379
|
scope: scopeInfo,
|
|
213355
|
-
services
|
|
213380
|
+
services,
|
|
213381
|
+
dispatchTargets
|
|
213356
213382
|
};
|
|
213357
213383
|
}
|
|
213358
213384
|
createPluginLogger(pluginName) {
|
|
@@ -213402,21 +213428,28 @@ ${errorMessages}`);
|
|
|
213402
213428
|
}
|
|
213403
213429
|
};
|
|
213404
213430
|
}
|
|
213405
|
-
getPrecompiledHooks(routeId, upstreamId, serviceName) {
|
|
213431
|
+
getPrecompiledHooks(routeId, upstreamId, serviceName, adapter) {
|
|
213406
213432
|
if (!this.precompiled) {
|
|
213407
213433
|
this.precompileAllHooks();
|
|
213408
213434
|
}
|
|
213409
213435
|
const cacheKey = `phase-aware:${routeId}#${upstreamId || ""}#${serviceName || ""}`;
|
|
213410
|
-
const cached = this.phaseAwareCache.get(cacheKey);
|
|
213436
|
+
const cached = adapter ? undefined : this.phaseAwareCache.get(cacheKey);
|
|
213411
213437
|
if (cached) {
|
|
213412
213438
|
return cached;
|
|
213413
213439
|
}
|
|
213414
213440
|
const routePhase = this.buildRoutePhaseHooks(routeId);
|
|
213415
213441
|
const servicePhase = serviceName ? this.buildServicePhaseHooks(routeId, serviceName) : null;
|
|
213416
213442
|
const upstreamPhase = upstreamId ? this.buildUpstreamPhaseHooks(routeId, upstreamId) : this.buildEmptyPrecompiledHooks();
|
|
213417
|
-
const
|
|
213418
|
-
|
|
213419
|
-
|
|
213443
|
+
const dispatchAdapter = adapter ? this.buildPrecompiledHooks([{
|
|
213444
|
+
scope: { type: "route", routeId },
|
|
213445
|
+
config: { name: "gateway-dispatch-adapter" },
|
|
213446
|
+
priority: 0,
|
|
213447
|
+
handler: { pluginName: "gateway-dispatch-adapter", config: {}, bodyRequirements: adapter.bodyRequirements.bind(adapter), register: adapter.register.bind(adapter) }
|
|
213448
|
+
}], "dispatch-adapter") : undefined;
|
|
213449
|
+
const inbound = this.buildInboundChain(routeId, upstreamId || "", serviceName, dispatchAdapter);
|
|
213450
|
+
const result = { dispatchAdapter, routePhase, servicePhase, upstreamPhase, inbound, globalPrecompiled: this.globalPrecompiled, routePrecompiled: this.routePrecompiled.get(routeId) ?? null };
|
|
213451
|
+
if (!adapter)
|
|
213452
|
+
this.phaseAwareCache.set(cacheKey, result);
|
|
213420
213453
|
return result;
|
|
213421
213454
|
}
|
|
213422
213455
|
getWebSocketHandshakeOwners(routeId, upstreamId, serviceName) {
|
|
@@ -213629,7 +213662,7 @@ ${errorMessages}`);
|
|
|
213629
213662
|
}
|
|
213630
213663
|
return this.buildPrecompiledHooks(instances, `upstream-phase:${key}`, true);
|
|
213631
213664
|
}
|
|
213632
|
-
buildInboundChain(routeId, upstreamId, serviceName) {
|
|
213665
|
+
buildInboundChain(routeId, upstreamId, serviceName, adapter) {
|
|
213633
213666
|
const upstreamHooks = this.upstreamPrecompiled.get(`${routeId}#${upstreamId}`);
|
|
213634
213667
|
const serviceHooks = serviceName ? this.servicePrecompiled.get(`${routeId}#${serviceName}`) : null;
|
|
213635
213668
|
const routeHooks = this.routePrecompiled.get(routeId);
|
|
@@ -213645,6 +213678,8 @@ ${errorMessages}`);
|
|
|
213645
213678
|
response = await routeHooks.hooks.onResponse.promise(response, ctx);
|
|
213646
213679
|
if (globalHooks)
|
|
213647
213680
|
response = await globalHooks.hooks.onResponse.promise(response, ctx);
|
|
213681
|
+
if (adapter)
|
|
213682
|
+
response = await adapter.hooks.onResponse.promise(response, ctx);
|
|
213648
213683
|
return response;
|
|
213649
213684
|
},
|
|
213650
213685
|
onRawResponse: async (result, ctx) => {
|
|
@@ -213657,11 +213692,13 @@ ${errorMessages}`);
|
|
|
213657
213692
|
current = await routeHooks.hooks.onRawResponse.promise(current, ctx);
|
|
213658
213693
|
if (globalHooks)
|
|
213659
213694
|
current = await globalHooks.hooks.onRawResponse.promise(current, ctx);
|
|
213695
|
+
if (adapter)
|
|
213696
|
+
current = await adapter.hooks.onRawResponse.promise(current, ctx);
|
|
213660
213697
|
return current;
|
|
213661
213698
|
},
|
|
213662
213699
|
onStreamChunk: async (chunk, ctx) => {
|
|
213663
213700
|
let chunks = [chunk];
|
|
213664
|
-
const phaseHooks = [upstreamHooks, serviceHooks, routeHooks, globalHooks];
|
|
213701
|
+
const phaseHooks = [upstreamHooks, serviceHooks, routeHooks, globalHooks, adapter];
|
|
213665
213702
|
for (const precompiledHooks of phaseHooks) {
|
|
213666
213703
|
if (!precompiledHooks)
|
|
213667
213704
|
continue;
|
|
@@ -213684,6 +213721,8 @@ ${errorMessages}`);
|
|
|
213684
213721
|
result = await routeHooks.hooks.onFlushStream.promise(result, ctx);
|
|
213685
213722
|
if (globalHooks)
|
|
213686
213723
|
result = await globalHooks.hooks.onFlushStream.promise(result, ctx);
|
|
213724
|
+
if (adapter)
|
|
213725
|
+
result = await adapter.hooks.onFlushStream.promise(result, ctx);
|
|
213687
213726
|
return result;
|
|
213688
213727
|
},
|
|
213689
213728
|
onError: async (ctx) => {
|
|
@@ -213695,6 +213734,8 @@ ${errorMessages}`);
|
|
|
213695
213734
|
await routeHooks.hooks.onError.promise(ctx);
|
|
213696
213735
|
if (globalHooks)
|
|
213697
213736
|
await globalHooks.hooks.onError.promise(ctx);
|
|
213737
|
+
if (adapter)
|
|
213738
|
+
await adapter.hooks.onError.promise(ctx);
|
|
213698
213739
|
}
|
|
213699
213740
|
};
|
|
213700
213741
|
}
|
|
@@ -213763,7 +213804,11 @@ ${errorMessages}`);
|
|
|
213763
213804
|
getGlobalAdmissionHandlers() {
|
|
213764
213805
|
return this.globalInstances.map((instance) => instance.handler);
|
|
213765
213806
|
}
|
|
213807
|
+
getDeclaredDispatchTargets(routeId) {
|
|
213808
|
+
return (this.routeInstances.get(routeId) ?? []).flatMap((instance) => declaredDispatchTargets(instance.config.options ?? {}, this.pluginClasses.get(instance.handler.pluginName)?.configSchema ?? []));
|
|
213809
|
+
}
|
|
213766
213810
|
async initializeFromConfig(config, dependencies) {
|
|
213811
|
+
this.dispatchConfig = config;
|
|
213767
213812
|
this.dependencies = dependencies;
|
|
213768
213813
|
if (dependencies) {
|
|
213769
213814
|
this.setServiceDependencies(dependencies.declarations());
|
|
@@ -213835,8 +213880,9 @@ ${errorMessages}`);
|
|
|
213835
213880
|
logger2.error({ error: result.error, pluginConfig, routeId }, "Failed to create route plugin instance after retries");
|
|
213836
213881
|
}
|
|
213837
213882
|
}
|
|
213838
|
-
|
|
213839
|
-
|
|
213883
|
+
const dispatchServices = this.getDeclaredDispatchTargets(routeId).filter((target) => target.type === "service").map((target) => config.services?.find((service) => service.id === target.id)).filter((service) => !!service);
|
|
213884
|
+
const relatedServices = [...new Map([...config.services?.filter((service) => service.name === route.service) ?? [], ...dispatchServices].map((service) => [service.name, service])).values()];
|
|
213885
|
+
for (const service of relatedServices) {
|
|
213840
213886
|
if (service?.plugins?.length) {
|
|
213841
213887
|
const serviceName = service.name;
|
|
213842
213888
|
for (const pluginConfig of orderBindings(service.plugins)) {
|
|
@@ -213853,7 +213899,15 @@ ${errorMessages}`);
|
|
|
213853
213899
|
}
|
|
213854
213900
|
}
|
|
213855
213901
|
}
|
|
213856
|
-
const endpoints = resolveEffectiveRouteEndpoints(route, config.services);
|
|
213902
|
+
const endpoints = [...resolveEffectiveRouteEndpoints(route, config.services)];
|
|
213903
|
+
const endpointIds = new Set(endpoints.flatMap((endpoint) => endpoint.id ? [endpoint.id] : []));
|
|
213904
|
+
for (const endpoint of dispatchServices.flatMap((service) => service.endpoints)) {
|
|
213905
|
+
if (endpoint.id && endpointIds.has(endpoint.id))
|
|
213906
|
+
continue;
|
|
213907
|
+
if (endpoint.id)
|
|
213908
|
+
endpointIds.add(endpoint.id);
|
|
213909
|
+
endpoints.push(endpoint);
|
|
213910
|
+
}
|
|
213857
213911
|
for (const [upstreamIndex, upstream] of endpoints.entries()) {
|
|
213858
213912
|
const upstreamId = upstream.id || String(upstreamIndex);
|
|
213859
213913
|
for (const pluginConfig of orderBindings(upstream.plugins || [])) {
|
|
@@ -214333,6 +214387,7 @@ var init_contracts = __esm(() => {
|
|
|
214333
214387
|
"onGatewayBody",
|
|
214334
214388
|
"onGatewayRequest",
|
|
214335
214389
|
"onGatewayRoute",
|
|
214390
|
+
"onGatewayDispatch",
|
|
214336
214391
|
"onGatewayAdmission",
|
|
214337
214392
|
"onGatewayAdmissionSession",
|
|
214338
214393
|
"onGatewayAdmissionPrepare",
|
|
@@ -214609,7 +214664,7 @@ class DataAdmissionHost {
|
|
|
214609
214664
|
if (this.options.authorizeWorker(worker) !== "active")
|
|
214610
214665
|
throw new DataAdmissionError(503, "admission_worker_not_active");
|
|
214611
214666
|
for (const requirement of this.routeRequirements)
|
|
214612
|
-
if (requirement.routeIds.includes(target.routeId) && !this.plugins.some((runtime) => runtime.publication.name === requirement.plugin && runtime.plugin.resolveIdentity))
|
|
214667
|
+
if ((requirement.routeIds.includes(target.routeId) || !!target.entryRouteId && requirement.routeIds.includes(target.entryRouteId)) && !this.plugins.some((runtime) => runtime.publication.name === requirement.plugin && runtime.plugin.resolveIdentity))
|
|
214613
214668
|
throw new DataAdmissionError(503, "route_protection_unavailable");
|
|
214614
214669
|
let principal = ANONYMOUS_PRINCIPAL;
|
|
214615
214670
|
for (const runtime of this.plugins)
|
|
@@ -214620,7 +214675,7 @@ class DataAdmissionHost {
|
|
|
214620
214675
|
throw normalizeAdmissionError(error) ?? new DataAdmissionError(503, "route_protection_unavailable");
|
|
214621
214676
|
}
|
|
214622
214677
|
}
|
|
214623
|
-
if (principal.domain === "anonymous" && this.routeRequirements.some((requirement) => requirement.routeIds.includes(target.routeId)))
|
|
214678
|
+
if (principal.domain === "anonymous" && this.routeRequirements.some((requirement) => requirement.routeIds.includes(target.routeId) || !!target.entryRouteId && requirement.routeIds.includes(target.entryRouteId)))
|
|
214624
214679
|
throw new DataAdmissionError(503, "route_protection_unavailable");
|
|
214625
214680
|
const requirements = {};
|
|
214626
214681
|
for (const runtime of this.plugins) {
|
|
@@ -214651,7 +214706,7 @@ class DataAdmissionHost {
|
|
|
214651
214706
|
if (this.options.authorizeWorker(worker) !== "active")
|
|
214652
214707
|
throw new DataAdmissionError(503, "admission_worker_not_active");
|
|
214653
214708
|
for (const requirement of this.routeRequirements) {
|
|
214654
|
-
if (requirement.routeIds.includes(target.routeId) && !this.plugins.some((p) => p.publication.name === requirement.plugin && p.plugin.resolveIdentity)) {
|
|
214709
|
+
if ((requirement.routeIds.includes(target.routeId) || !!target.entryRouteId && requirement.routeIds.includes(target.entryRouteId)) && !this.plugins.some((p) => p.publication.name === requirement.plugin && p.plugin.resolveIdentity)) {
|
|
214655
214710
|
throw new DataAdmissionError(503, "route_protection_unavailable");
|
|
214656
214711
|
}
|
|
214657
214712
|
}
|
|
@@ -214667,10 +214722,10 @@ class DataAdmissionHost {
|
|
|
214667
214722
|
throw new DataAdmissionError(503, "route_protection_unavailable");
|
|
214668
214723
|
}
|
|
214669
214724
|
}
|
|
214670
|
-
if (principal.domain === "anonymous" && this.routeRequirements.some((r) => r.routeIds.includes(target.routeId)))
|
|
214725
|
+
if (principal.domain === "anonymous" && this.routeRequirements.some((r) => r.routeIds.includes(target.routeId) || !!target.entryRouteId && r.routeIds.includes(target.entryRouteId)))
|
|
214671
214726
|
throw new DataAdmissionError(503, "route_protection_unavailable");
|
|
214672
214727
|
const ticket = this.previews.get(target.requestId);
|
|
214673
|
-
if (!preview && expectedVersion !== undefined && (!ticket || ticket.version !== this.version || ticket.expires <= this.now() || ticket.worker !== workerKey(worker) || ticket.target.attemptId !== target.attemptId || ticket.target.url !== target.url || ticket.target.model !== target.model || ticket.target.routeId !== target.routeId || JSON.stringify(ticket.target.principal) !== JSON.stringify(principal) || ticket.target.serviceId !== target.serviceId || ticket.target.upstreamId !== target.upstreamId)) {
|
|
214728
|
+
if (!preview && expectedVersion !== undefined && (!ticket || ticket.version !== this.version || ticket.expires <= this.now() || ticket.worker !== workerKey(worker) || ticket.target.attemptId !== target.attemptId || ticket.target.url !== target.url || ticket.target.model !== target.model || ticket.target.routeId !== target.routeId || ticket.target.entryRouteId !== target.entryRouteId || JSON.stringify(ticket.target.principal) !== JSON.stringify(principal) || ticket.target.serviceId !== target.serviceId || ticket.target.upstreamId !== target.upstreamId)) {
|
|
214674
214729
|
throw new DataAdmissionError(409, "admission_preview_changed");
|
|
214675
214730
|
}
|
|
214676
214731
|
const nowTarget = Object.freeze({ ...target, principal, now: !preview && ticket ? ticket.target.now : this.now() });
|
|
@@ -214716,7 +214771,7 @@ class DataAdmissionHost {
|
|
|
214716
214771
|
plan.runtime.state = plan.state;
|
|
214717
214772
|
}
|
|
214718
214773
|
this.previews.delete(target.requestId);
|
|
214719
|
-
this.grants.set(target.requestId, { routeId: target.routeId, sourcePrincipal: structuredClone(target.principal), worker: workerKey(worker), grant, plugins: this.plugins.map((runtime) => ({ publication: runtime.publication, plugin: runtime.plugin, state: null })), expires: null });
|
|
214774
|
+
this.grants.set(target.requestId, { entryRouteId: target.entryRouteId, routeId: target.routeId, sourcePrincipal: structuredClone(target.principal), worker: workerKey(worker), grant, plugins: this.plugins.map((runtime) => ({ publication: runtime.publication, plugin: runtime.plugin, state: null })), expires: null });
|
|
214720
214775
|
return grant;
|
|
214721
214776
|
}
|
|
214722
214777
|
beforeAttempt(target, worker) {
|
|
@@ -214739,7 +214794,7 @@ class DataAdmissionHost {
|
|
|
214739
214794
|
stored.expires = this.now() + 15000;
|
|
214740
214795
|
}
|
|
214741
214796
|
assertGrant(stored, target, worker) {
|
|
214742
|
-
if (stored.worker !== workerKey(worker) || stored.routeId !== target.routeId || JSON.stringify(stored.sourcePrincipal) !== JSON.stringify(target.principal) || !["active", "retired"].includes(this.options.authorizeWorker(worker)))
|
|
214797
|
+
if (stored.worker !== workerKey(worker) || stored.routeId !== target.routeId || stored.entryRouteId !== target.entryRouteId || JSON.stringify(stored.sourcePrincipal) !== JSON.stringify(target.principal) || !["active", "retired"].includes(this.options.authorizeWorker(worker)))
|
|
214743
214798
|
throw new DataAdmissionError(403, "admission_identity_mismatch");
|
|
214744
214799
|
}
|
|
214745
214800
|
}
|
|
@@ -220383,7 +220438,7 @@ async function executeForward(requestSnapshot, route, upstream, requestLog, conf
|
|
|
220383
220438
|
throw new ManagedUpstreamAccessError("managed upstream access is unavailable", { cause: error });
|
|
220384
220439
|
}
|
|
220385
220440
|
}
|
|
220386
|
-
const responseDemand = collectPluginBodyRequirements([phaseAwareHooks?.upstreamPhase, phaseAwareHooks?.servicePhase, phaseAwareHooks?.routePhase], {
|
|
220441
|
+
const responseDemand = collectPluginBodyRequirements([phaseAwareHooks?.upstreamPhase, phaseAwareHooks?.servicePhase, phaseAwareHooks?.routePhase, phaseAwareHooks?.dispatchAdapter], {
|
|
220387
220442
|
requestId,
|
|
220388
220443
|
method: requestSnapshot.method,
|
|
220389
220444
|
url: finalTargetUrl,
|
|
@@ -220579,7 +220634,7 @@ async function executeForward(requestSnapshot, route, upstream, requestLog, conf
|
|
|
220579
220634
|
streamCompletionState = proxyRes.body ? { interrupted: false, cancelled: false, complete: (outcome) => {
|
|
220580
220635
|
transportCompletion.settle(outcome);
|
|
220581
220636
|
} } : undefined;
|
|
220582
|
-
const preparedResponse = await abortable(prepareResponse(proxyRes, finalResponseRules, createExpressionContext(attemptContext), requestLog, reqLogger, config, undefined, streamRequestContext, streamCompletionState, phaseAwareHooks?.inbound, Boolean(phaseAwareHooks && (selectedDemand.response?.includes("sse-json") || phase1and2Context?.bodyRequirements?.response?.includes("sse-json")) && (phaseAwareHooks.globalPrecompiled?.hasStreamCallbacks || phaseAwareHooks.upstreamPhase.hasStreamCallbacks || phaseAwareHooks.servicePhase?.hasStreamCallbacks || phaseAwareHooks.routePhase.hasStreamCallbacks)), hasRawResponseCallbacks, attemptSignal, responseBodyOwners, restoredPluginWire ? pluginBodyOwner : undefined, pluginRepresentationChanged), attemptSignal, (lateResponse) => {
|
|
220637
|
+
const preparedResponse = await abortable(prepareResponse(proxyRes, finalResponseRules, createExpressionContext(attemptContext), requestLog, reqLogger, config, undefined, streamRequestContext, streamCompletionState, phaseAwareHooks?.inbound, Boolean(phaseAwareHooks && (selectedDemand.response?.includes("sse-json") || phase1and2Context?.bodyRequirements?.response?.includes("sse-json")) && (phaseAwareHooks.globalPrecompiled?.hasStreamCallbacks || phaseAwareHooks.upstreamPhase.hasStreamCallbacks || phaseAwareHooks.servicePhase?.hasStreamCallbacks || phaseAwareHooks.routePhase.hasStreamCallbacks || phaseAwareHooks.dispatchAdapter?.hasStreamCallbacks)), hasRawResponseCallbacks, attemptSignal, responseBodyOwners, restoredPluginWire ? pluginBodyOwner : undefined, pluginRepresentationChanged), attemptSignal, (lateResponse) => {
|
|
220583
220638
|
if (lateResponse.body instanceof ReadableStream) {
|
|
220584
220639
|
lateResponse.body.cancel("request aborted").catch(() => {
|
|
220585
220640
|
return;
|
|
@@ -221778,7 +221833,7 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
221778
221833
|
=== Incoming Request ===`);
|
|
221779
221834
|
const routeMatchStart = performance.now();
|
|
221780
221835
|
const routeDecision = requireGatewayResult(await gatewayHooks().onGatewayRoute.promise({ request: req, config }), "onGatewayRoute");
|
|
221781
|
-
|
|
221836
|
+
let route = routeDecision.route;
|
|
221782
221837
|
if (!route) {
|
|
221783
221838
|
logger2.error({ request: requestLog }, `No route found for path: ${url.pathname}`);
|
|
221784
221839
|
success = false;
|
|
@@ -221793,41 +221848,64 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
221793
221848
|
originalHeaders[key] = value;
|
|
221794
221849
|
});
|
|
221795
221850
|
reqLogger.setOriginalRequestHeaders(redactRequestHeaders(originalHeaders));
|
|
221796
|
-
routeId = route.path;
|
|
221797
|
-
routeServiceName = route.service;
|
|
221798
|
-
const currentRouteId = route.path;
|
|
221799
|
-
const effectiveRoute = requireGatewayResult(routeDecision.effective, "onGatewayRoute");
|
|
221800
|
-
const endpoints = effectiveRoute.endpoints;
|
|
221801
|
-
const runtimeStateKey = route.service ?? route.path;
|
|
221802
221851
|
if (hasWorkerAdmissionSession() && !trustedIdentity) {
|
|
221803
221852
|
success = false;
|
|
221804
221853
|
responseStatus = 401;
|
|
221805
221854
|
return finalizeLocalResponse(Response.json({ error: "unauthorized" }, { status: 401, headers: { "www-authenticate": "Bearer" } }));
|
|
221806
221855
|
}
|
|
221856
|
+
if (routeDecision.response) {
|
|
221857
|
+
responseStatus = routeDecision.response.status;
|
|
221858
|
+
success = responseStatus < 400;
|
|
221859
|
+
return routeDecision.responseKind === "rule" ? finalizeRootStreamingResponse(routeDecision.response) : finalizeLocalResponse(routeDecision.response);
|
|
221860
|
+
}
|
|
221861
|
+
const snapshotStart = performance.now();
|
|
221862
|
+
const processingDeadline = AbortSignal.timeout(route.timeouts?.request_ms ?? 30000);
|
|
221863
|
+
const processingSignal = AbortSignal.any([req.signal, processingDeadline]);
|
|
221864
|
+
const requestSnapshot = await createRequestSnapshot(req, config.body_parser_limit, processingSignal, originalBody, { requestId, attemptId: requestId, direction: "request", stage: "original-request", version: 0, contentType: req.headers.get("content-type") ?? "", contentEncoding: req.headers.get("content-encoding") ?? "" });
|
|
221865
|
+
requestBodySource = requestSnapshot.bodySource;
|
|
221866
|
+
const entryRoute = route;
|
|
221867
|
+
const dispatchContext = createPhaseContext(requestSnapshot, requestId, entryRoute.path, entryRoute.service);
|
|
221868
|
+
const dispatchHooks = requestRegistry?.getRoutePrecompiledHooks?.(entryRoute.path);
|
|
221869
|
+
if (dispatchHooks?.hooks.onDispatchRequest.hasCallbacks()) {
|
|
221870
|
+
await readSnapshotJson(requestSnapshot, "internal-dispatch", true);
|
|
221871
|
+
dispatchContext.body = structuredClone(requestSnapshot.body);
|
|
221872
|
+
}
|
|
221873
|
+
const dispatch = requireGatewayResult(await gatewayHooks().onGatewayDispatch.promise({
|
|
221874
|
+
config,
|
|
221875
|
+
entry: entryRoute,
|
|
221876
|
+
context: dispatchContext,
|
|
221877
|
+
signal: processingSignal,
|
|
221878
|
+
principal: trustedIdentity?.principal,
|
|
221879
|
+
servingRevision: runtimeContext?.servingRevision
|
|
221880
|
+
}), "onGatewayDispatch");
|
|
221881
|
+
route = dispatch.route;
|
|
221882
|
+
if (dispatch.target) {
|
|
221883
|
+
requestSnapshot.body = dispatch.context.body;
|
|
221884
|
+
reqLogger.addStep("internal_dispatch", { entry: entryRoute.id ?? entryRoute.path, target: dispatch.target, model: dispatch.context.body?.model });
|
|
221885
|
+
}
|
|
221886
|
+
routeId = route.path;
|
|
221887
|
+
routeServiceName = route.service;
|
|
221888
|
+
const currentRouteId = route.path;
|
|
221889
|
+
const effectiveRoute = dispatch.effective;
|
|
221890
|
+
const endpoints = effectiveRoute.endpoints;
|
|
221891
|
+
const runtimeStateKey = route.service ?? route.path;
|
|
221807
221892
|
if (trustedIdentity) {
|
|
221808
221893
|
for (const handler of admissionHandlers)
|
|
221809
221894
|
retainOwner(handler.pluginName);
|
|
221810
221895
|
dataAdmission = requireGatewayResult(await gatewayHooks().onGatewayAdmissionSession.promise({ handlers: admissionHandlers, identity: {
|
|
221811
221896
|
requestId,
|
|
221812
221897
|
principal: trustedIdentity.principal,
|
|
221898
|
+
...dispatch.entryRouteId ? { entryRouteId: dispatch.entryRouteId } : {},
|
|
221813
221899
|
routeId: route.id ?? currentRouteId,
|
|
221814
221900
|
serviceId: route.service_id ?? config.services?.find((service) => service.name === route.service)?.id ?? null
|
|
221815
221901
|
}, invoke: (plugin, method, payload, target) => requestRegistry.invokeAdmissionRpc(plugin, method, payload, target, ownerLeases.get(`${plugin}\x00global`)) }), "onGatewayAdmissionSession");
|
|
221816
221902
|
}
|
|
221817
|
-
|
|
221818
|
-
responseStatus = routeDecision.response.status;
|
|
221819
|
-
success = responseStatus < 400;
|
|
221820
|
-
return routeDecision.responseKind === "rule" ? finalizeRootStreamingResponse(routeDecision.response) : finalizeLocalResponse(routeDecision.response);
|
|
221821
|
-
}
|
|
221822
|
-
const snapshotStart = performance.now();
|
|
221823
|
-
const processingDeadline = AbortSignal.timeout(effectiveRoute.timeouts?.request_ms ?? 30000);
|
|
221824
|
-
const processingSignal = AbortSignal.any([req.signal, processingDeadline]);
|
|
221825
|
-
const requestSnapshot = await createRequestSnapshot(req, config.body_parser_limit, processingSignal, originalBody, { requestId, attemptId: requestId, direction: "request", stage: "original-request", version: 0, contentType: req.headers.get("content-type") ?? "", contentEncoding: req.headers.get("content-encoding") ?? "" });
|
|
221826
|
-
requestBodySource = requestSnapshot.bodySource;
|
|
221827
|
-
const demandHooks = requestRegistry?.getPrecompiledHooks(currentRouteId, undefined, routeServiceName);
|
|
221903
|
+
const demandHooks = requestRegistry?.getPrecompiledHooks(currentRouteId, undefined, routeServiceName, dispatch.adapter);
|
|
221828
221904
|
const demandContext = createPhaseContext(requestSnapshot, requestId, currentRouteId, routeServiceName);
|
|
221905
|
+
if (dispatch.target)
|
|
221906
|
+
demandContext.url = new URL(dispatch.context.url);
|
|
221829
221907
|
applyRoutePathRewriteToContext(demandContext, route, requestLog);
|
|
221830
|
-
const routeDemand = collectPluginBodyRequirements([demandHooks?.routePhase, demandHooks?.servicePhase], {
|
|
221908
|
+
const routeDemand = collectPluginBodyRequirements([demandHooks?.routePhase, demandHooks?.servicePhase, demandHooks?.dispatchAdapter], {
|
|
221831
221909
|
requestId,
|
|
221832
221910
|
method: req.method,
|
|
221833
221911
|
url: demandContext.url,
|
|
@@ -221844,7 +221922,7 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
221844
221922
|
const routeNeedsBody = analyzeExpressionDependencies(routeRequestDemand, "request").requestBody || analyzeExpressionDependencies(route.response, "response").requestBody;
|
|
221845
221923
|
if (routeDemand.replay || route.retry?.enabled || effectiveRoute.failover?.enabled)
|
|
221846
221924
|
await requestBodySource?.buffer("configured-replay");
|
|
221847
|
-
if (selectionNeedsBody || routeNeedsBody || routeDemand.request !== "none")
|
|
221925
|
+
if (!requestSnapshot.is_json_body && (selectionNeedsBody || routeNeedsBody || routeDemand.request !== "none"))
|
|
221848
221926
|
await readSnapshotJson(requestSnapshot, selectionNeedsBody ? "selection-expression" : routeDemand.request !== "none" ? "plugin-body-demand" : "route-expression", routeDemand.request === "json-write" && !["GET", "HEAD"].includes(req.method));
|
|
221849
221927
|
reqLogger.addStep("request_body_plan", { mode: requestBodySource?.mode, reasons: requestBodySource?.reasons ?? [], replay: requestBodySource?.replayable ?? false, source: "wire" });
|
|
221850
221928
|
reqLogger.addStepWithDuration("request_snapshot_created", performance.now() - snapshotStart, {
|
|
@@ -221863,6 +221941,11 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
221863
221941
|
method: req.method,
|
|
221864
221942
|
env: process.env
|
|
221865
221943
|
};
|
|
221944
|
+
if (dispatch.target && entryRoute.id !== route.id) {
|
|
221945
|
+
const entryAdmission = requireGatewayResult(await gatewayHooks().onGatewayAdmission.promise({ route: entryRoute, trustedPeer: getTrustedWorkerPeer(req), context: expressionContext, servingRevision: runtimeContext?.servingRevision, signal: processingSignal }), "onGatewayAdmission");
|
|
221946
|
+
if (!entryAdmission.allowed)
|
|
221947
|
+
throw new DataAdmissionError(entryAdmission.retryAfterMs === undefined ? 503 : 429, "entry_route_rate_limited");
|
|
221948
|
+
}
|
|
221866
221949
|
const rateLimit = requireGatewayResult(await gatewayHooks().onGatewayAdmission.promise({
|
|
221867
221950
|
route,
|
|
221868
221951
|
trustedPeer: getTrustedWorkerPeer(req),
|
|
@@ -221889,9 +221972,11 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
221889
221972
|
}));
|
|
221890
221973
|
}
|
|
221891
221974
|
const scopedRegistry = requestRegistry;
|
|
221892
|
-
const requestPhaseHooks = scopedRegistry?.getPrecompiledHooks(currentRouteId, undefined, routeServiceName) ?? null;
|
|
221975
|
+
const requestPhaseHooks = scopedRegistry?.getPrecompiledHooks(currentRouteId, undefined, routeServiceName, dispatch.adapter) ?? null;
|
|
221893
221976
|
const phaseContext = createPhaseContext(requestSnapshot, requestId, currentRouteId, routeServiceName);
|
|
221894
|
-
phaseContext.bodyWrite = routeDemand.request === "json-write";
|
|
221977
|
+
phaseContext.bodyWrite = !!dispatch.target || routeDemand.request === "json-write";
|
|
221978
|
+
if (dispatch.target)
|
|
221979
|
+
phaseContext.url = new URL(dispatch.context.url);
|
|
221895
221980
|
phaseContext.bodyRequirements = routeDemand;
|
|
221896
221981
|
applyRoutePathRewriteToContext(phaseContext, route, requestLog);
|
|
221897
221982
|
const routePhaseResponse = await executePreFailoverPhase(requestPhaseHooks?.routePhase, phaseContext, {
|
|
@@ -222003,7 +222088,7 @@ async function executeHttpRequestInternal(req, config, runtimeContextOrSelector,
|
|
|
222003
222088
|
};
|
|
222004
222089
|
let repairClaimed = false;
|
|
222005
222090
|
const proxyWithRouteRetry = async (selectedUpstream, attemptLogger) => {
|
|
222006
|
-
const phaseAwareHooks = scopedRegistry?.getPrecompiledHooks(currentRouteId, selectedUpstream.upstream_id, routeServiceName) ?? null;
|
|
222091
|
+
const phaseAwareHooks = scopedRegistry?.getPrecompiledHooks(currentRouteId, selectedUpstream.upstream_id, routeServiceName, dispatch.adapter) ?? null;
|
|
222007
222092
|
const attemptLoggers = new Map;
|
|
222008
222093
|
const runAttempt = async (requestOverride) => {
|
|
222009
222094
|
const currentLogger = requestOverride ? await createRequestLogger(req, { isFailoverAttempt: true, parentRequestId: reqLogger.getRequestId(), attemptUpstream: selectedUpstream.target, requestType: "final" }) : attemptLogger;
|
|
@@ -222942,6 +223027,49 @@ var init_routing_plugin = __esm(() => {
|
|
|
222942
223027
|
init_endpoint_resolver();
|
|
222943
223028
|
});
|
|
222944
223029
|
|
|
223030
|
+
// src/gateway/dispatch-plugin.ts
|
|
223031
|
+
class DispatchPlugin {
|
|
223032
|
+
bodyRequirements() {
|
|
223033
|
+
return { request: "none" };
|
|
223034
|
+
}
|
|
223035
|
+
register(hooks) {
|
|
223036
|
+
hooks.onGatewayDispatch.tapPromise("builtin.dispatch", async ({ config, entry, context, signal, principal, servingRevision }) => {
|
|
223037
|
+
const registry = getScopedPluginRegistry();
|
|
223038
|
+
const references = registry?.getDeclaredDispatchTargets?.(entry.path) ?? [];
|
|
223039
|
+
const targets = references.map((target) => {
|
|
223040
|
+
const entity = (target.type === "route" ? config.routes : config.services ?? []).find((entity) => entity.id === target.id);
|
|
223041
|
+
return { ...target, protocol: entity?.llm_protocol };
|
|
223042
|
+
});
|
|
223043
|
+
const dispatch = await registry?.getRoutePrecompiledHooks?.(entry.path)?.hooks.onDispatchRequest.promise({ context, signal, principal, servingRevision, targets });
|
|
223044
|
+
if (!dispatch)
|
|
223045
|
+
return { route: entry, effective: resolveEffectiveRoute(config, entry), context };
|
|
223046
|
+
if (!targets.some((target) => target.type === dispatch.target.type && target.id === dispatch.target.id))
|
|
223047
|
+
throw new DataAdmissionError(403, "dispatch_target_not_declared");
|
|
223048
|
+
signal.throwIfAborted();
|
|
223049
|
+
let route;
|
|
223050
|
+
if (dispatch.target.type === "route") {
|
|
223051
|
+
const target = config.routes.find((route) => route.id === dispatch.target.id);
|
|
223052
|
+
if (!target || target.id === entry.id || target.direct_response?.enabled || target.redirect?.enabled || target.response_rules?.some((rule) => rule.enabled))
|
|
223053
|
+
throw new DataAdmissionError(422, "dispatch_target_unavailable");
|
|
223054
|
+
route = target;
|
|
223055
|
+
const suffix = context.url.pathname.startsWith(entry.path) ? context.url.pathname.slice(entry.path.length) : "";
|
|
223056
|
+
context.url.pathname = target.path.replace(/\/$/, "") + suffix;
|
|
223057
|
+
} else {
|
|
223058
|
+
const service = config.services?.find((service) => service.id === dispatch.target.id);
|
|
223059
|
+
if (!service)
|
|
223060
|
+
throw new DataAdmissionError(422, "dispatch_target_unavailable");
|
|
223061
|
+
route = { ...entry, service: service.name, service_id: service.id, endpoints: undefined };
|
|
223062
|
+
}
|
|
223063
|
+
return { route, effective: resolveEffectiveRoute(config, route), context, entryRouteId: entry.id ?? entry.path, target: dispatch.target, adapter: dispatch.adapter };
|
|
223064
|
+
});
|
|
223065
|
+
}
|
|
223066
|
+
}
|
|
223067
|
+
var init_dispatch_plugin = __esm(() => {
|
|
223068
|
+
init_errors();
|
|
223069
|
+
init_scoped_plugin_registry();
|
|
223070
|
+
init_routing_plugin();
|
|
223071
|
+
});
|
|
223072
|
+
|
|
222945
223073
|
// src/rate-limit/json.ts
|
|
222946
223074
|
class RateLimitJsonScanner {
|
|
222947
223075
|
content;
|
|
@@ -224575,7 +224703,9 @@ function exact3(value, keys) {
|
|
|
224575
224703
|
}
|
|
224576
224704
|
function parseAdmissionTarget(value) {
|
|
224577
224705
|
const target = object(value);
|
|
224578
|
-
exact3(target, ["requestId", "attemptId", "principal", "routeId", "serviceId", "upstreamId", "url", "model", "now"]);
|
|
224706
|
+
exact3(target, ["requestId", "attemptId", "principal", "routeId", "serviceId", "upstreamId", "url", "model", "now", ...Object.hasOwn(target, "entryRouteId") ? ["entryRouteId"] : []]);
|
|
224707
|
+
if (Object.hasOwn(target, "entryRouteId") && (typeof target.entryRouteId !== "string" || !target.entryRouteId || target.entryRouteId.length > 256))
|
|
224708
|
+
throw new Error("invalid RPC entry route");
|
|
224579
224709
|
for (const name of ["requestId", "attemptId", "routeId", "upstreamId", "url"])
|
|
224580
224710
|
if (typeof target[name] !== "string" || !target[name].length || target[name].length > (name === "url" ? 65536 : 256))
|
|
224581
224711
|
throw new Error("invalid RPC target");
|
|
@@ -230573,6 +230703,7 @@ function gatewayBuiltins() {
|
|
|
230573
230703
|
new BodyServicePlugin,
|
|
230574
230704
|
new HttpRequestPlugin,
|
|
230575
230705
|
new RoutingPlugin,
|
|
230706
|
+
new DispatchPlugin,
|
|
230576
230707
|
new AdmissionPlugin,
|
|
230577
230708
|
new SelectionPlugin,
|
|
230578
230709
|
new RetryPlugin,
|
|
@@ -230633,6 +230764,7 @@ var init_runtime = __esm(() => {
|
|
|
230633
230764
|
init_contracts();
|
|
230634
230765
|
init_request_plugin();
|
|
230635
230766
|
init_forward_plugin();
|
|
230767
|
+
init_dispatch_plugin();
|
|
230636
230768
|
init_routing_plugin();
|
|
230637
230769
|
init_admission_plugin();
|
|
230638
230770
|
init_selection_plugin();
|