@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.
Files changed (41) hide show
  1. package/dist/gateway/contracts.d.ts +31 -2
  2. package/dist/gateway/dispatch-plugin.d.ts +7 -0
  3. package/dist/gateway/dispatch-targets.d.ts +4 -0
  4. package/dist/gateway/plugin.js +2 -0
  5. package/dist/gateway/routing-plugin.d.ts +3 -1
  6. package/dist/hooks/plugin-hooks.d.ts +6 -1
  7. package/dist/main.js +182 -50
  8. package/dist/plugin-extensions.d.ts +2 -0
  9. package/dist/plugins/ai-transformer/index.js +11 -1
  10. package/dist/plugins/ai-transformer/index.js.map +6 -4
  11. package/dist/plugins/anthropic-request-sanitizer/index.js.map +1 -1
  12. package/dist/plugins/anthropic-tool-name-transformer/index.js.map +1 -1
  13. package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
  14. package/dist/plugins/chatgpt-oauth/index.js.map +1 -1
  15. package/dist/plugins/codex-router/control.js +673 -2
  16. package/dist/plugins/codex-router/control.js.map +19 -4
  17. package/dist/plugins/codex-router/index.js +3938 -4
  18. package/dist/plugins/codex-router/index.js.map +24 -7
  19. package/dist/plugins/codex-router/manifest.json +15 -1
  20. package/dist/plugins/deepseek-reasoning-fix/index.js.map +1 -1
  21. package/dist/plugins/key-access/control.js.map +2 -2
  22. package/dist/plugins/key-access/index.js +14 -10
  23. package/dist/plugins/key-access/index.js.map +4 -4
  24. package/dist/plugins/key-access/ingress.js +14 -10
  25. package/dist/plugins/key-access/ingress.js.map +4 -4
  26. package/dist/plugins/key-rate-limit/index.js.map +1 -1
  27. package/dist/plugins/local-accounts/control.js.map +1 -1
  28. package/dist/plugins/local-accounts/index.js.map +1 -1
  29. package/dist/plugins/model-mapping/index.js.map +1 -1
  30. package/dist/plugins/models-dev/index.js.map +1 -1
  31. package/dist/plugins/openai-messages-to-chat/index.js +691 -543
  32. package/dist/plugins/openai-messages-to-chat/index.js.map +9 -5
  33. package/dist/plugins/signature-repair/index.js.map +1 -1
  34. package/dist/plugins/token-budget/control.js.map +1 -1
  35. package/dist/plugins/token-budget/index.js.map +1 -1
  36. package/dist/plugins/token-metering/index.js +652 -71
  37. package/dist/plugins/token-metering/index.js.map +6 -4
  38. package/dist/plugins/token-stats/control.js.map +1 -1
  39. package/dist/plugins/token-stats/index.js.map +1 -1
  40. package/dist/scoped-plugin-registry.d.ts +5 -2
  41. 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,7 @@
1
+ import type { Plugin, PluginHooks } from './plugin';
2
+ export declare class DispatchPlugin implements Plugin {
3
+ bodyRequirements(): {
4
+ request: "none";
5
+ };
6
+ register(hooks: PluginHooks): void;
7
+ }
@@ -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[];
@@ -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 Set(config.routes.map((route) => route.id));
459
- const services = new Set(config.services.map((service) => service.id));
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
- if (!(target.type === "route" ? routes : services).has(target.id))
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.18.0",
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 inbound = this.buildInboundChain(routeId, upstreamId || "", serviceName);
213418
- const result = { routePhase, servicePhase, upstreamPhase, inbound, globalPrecompiled: this.globalPrecompiled, routePrecompiled: this.routePrecompiled.get(routeId) ?? null };
213419
- this.phaseAwareCache.set(cacheKey, result);
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
- if (route.service) {
213839
- const service = config.services?.find((candidate) => candidate.name === route.service);
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
- const route = routeDecision.route;
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
- if (routeDecision.response) {
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();