-
Notifications
You must be signed in to change notification settings - Fork 611
feat(routing): execute capability-aware policy profiles #1012
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Wibias
merged 8 commits into
lidge-jun:dev
from
Wibias:feat/ri-05-capability-aware-routing
Aug 5, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
683b85d
feat(routing): execute capability-aware policy profiles (RI-05)
Wibias 71eb4f4
fix(routing): harden RI-05 request evidence and capability assembly
Wibias 40b9e6d
fix(routing): persist no-eligible policy traces and reserve routing n…
Wibias 6ad6a7c
test(routing): add RI-05 regression coverage
Wibias 043e654
docs(routing): sync routing reference to RI-05 execution
Wibias da1b7b1
fix(config): reserve only the policy namespace, not combo
Wibias a0e7b3d
fix(routing): persist no-eligible trace on compact path; doc resoluti…
Wibias b63d1a7
Merge remote-tracking branch 'upstream/dev' into feat/ri-05-capabilit…
Wibias File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| /** | ||
| * Candidate capability evidence for policy routing (RI-05). | ||
| * | ||
| * Evidence comes from canonical local sources only - provider config maps, | ||
| * the provider registry, the cached Codex catalog file, and the native-model | ||
| * metadata helpers. No live network fetch happens at routing time. | ||
| * | ||
| * "Unknown is not zero": any dimension without canonical evidence stays | ||
| * `undefined` (unknown) and the profile's `unknownEvidence` policy decides | ||
| * how that affects eligibility. | ||
| */ | ||
|
|
||
| import type { OcxConfig } from "../types"; | ||
| import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers"; | ||
| import { PROVIDER_REGISTRY } from "../providers/registry"; | ||
| import { | ||
| nativeInputModalities, | ||
| nativeOpenAiContextWindow, | ||
| nativeParallelToolCalls, | ||
| nativeReasoningEfforts, | ||
| } from "../codex/catalog/metadata"; | ||
| import { readCatalog, readCodexCatalogPath } from "../codex/catalog/parsing"; | ||
| import { statSync } from "node:fs"; | ||
| import type { RouteCapabilityEvidence } from "./trace"; | ||
|
|
||
| type CatalogModelRow = { | ||
| provider: string; | ||
| id: string; | ||
| contextWindow?: number; | ||
| inputModalities?: string[]; | ||
| reasoningEfforts?: string[]; | ||
| capabilities?: string[]; | ||
| }; | ||
|
|
||
| /** | ||
| * Catalog rows memoized by path + mtime: the cached Codex catalog is stable | ||
| * between refreshes, and re-reading/parsing the whole file per candidate on | ||
| * the request path would multiply a synchronous disk + JSON cost by the | ||
| * profile candidate count for every policy-routed request. | ||
| */ | ||
| let catalogCache: { path: string; mtimeMs: number; rows: CatalogModelRow[] } | null = null; | ||
|
|
||
| function cachedCatalogModels(): CatalogModelRow[] { | ||
| try { | ||
| const path = readCodexCatalogPath(); | ||
| const mtimeMs = statSync(path).mtimeMs; | ||
| if (catalogCache && catalogCache.path === path && catalogCache.mtimeMs === mtimeMs) { | ||
| return catalogCache.rows; | ||
| } | ||
| const catalog = readCatalog(path); | ||
| const models = catalog?.models; | ||
| if (!Array.isArray(models)) return []; | ||
| const rows = models | ||
| .filter((model): model is Record<string, unknown> & { id: string; provider: string } => | ||
| typeof model === "object" && model !== null && typeof model.id === "string" && typeof model.provider === "string") | ||
| .map(model => ({ | ||
| provider: model.provider, | ||
| id: model.id, | ||
| ...(typeof model.contextWindow === "number" ? { contextWindow: model.contextWindow } : {}), | ||
| ...(Array.isArray(model.inputModalities) | ||
| ? { inputModalities: model.inputModalities.filter((value): value is string => typeof value === "string") } | ||
| : {}), | ||
| ...(Array.isArray(model.reasoningEfforts) | ||
| ? { reasoningEfforts: model.reasoningEfforts.filter((value): value is string => typeof value === "string") } | ||
| : {}), | ||
| ...(Array.isArray(model.capabilities) | ||
| ? { capabilities: model.capabilities.filter((value): value is string => typeof value === "string") } | ||
| : {}), | ||
| })); | ||
| catalogCache = { path, mtimeMs, rows }; | ||
| return rows; | ||
| } catch { | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Classify a hostname for locality evidence. `URL.hostname` keeps IPv6 | ||
| * literals bracketed (`[::1]`), so strip the brackets before matching. | ||
| * Anything not positively local or private stays unknown: "unknown is not | ||
| * zero", so an unrecognized host must never assert `remoteAllowed`. | ||
| */ | ||
| function classifyHostname(hostname: string): "local" | "private" | null { | ||
| const host = hostname.trim().toLowerCase().replace(/\.$/, "").replace(/^\[|\]$/g, ""); | ||
| if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0") return "local"; | ||
| if (host === "::1" || /^127\./.test(host)) return "local"; | ||
| if (/^10\./.test(host) | ||
| || /^192\.168\./.test(host) | ||
| || /^169\.254\./.test(host) | ||
| || /^172\.(1[6-9]|2\d|3[01])\./.test(host) | ||
| || /^f[cd][0-9a-f]{2}:/.test(host) | ||
| || /^fe80:/.test(host) | ||
| || /^::ffff:(?:10\.|127\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(host)) { | ||
| return "private"; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| function localRemoteEvidence(baseUrl: string | undefined): Pick<RouteCapabilityEvidence, "localOnly" | "remoteAllowed"> { | ||
| if (typeof baseUrl !== "string" || baseUrl.length === 0) return {}; | ||
| try { | ||
| const hostname = new URL(baseUrl).hostname; | ||
| if (!hostname) return {}; | ||
| const kind = classifyHostname(hostname); | ||
| if (kind === null) return {}; | ||
| // Both booleans are emitted once classified: definitive negative evidence, | ||
| // so a local host cannot satisfy `require.remoteAllowed` (or vice versa) | ||
| // under `unknownEvidence.capability: "allow"`/`"penalize"`. | ||
| return kind === "local" || kind === "private" | ||
| ? { localOnly: true, remoteAllowed: false } | ||
| : { remoteAllowed: true, localOnly: false }; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Assemble canonical capability evidence for one `provider/model` candidate. | ||
| * Sources (in priority order): provider config maps, provider registry hints, | ||
| * cached Codex catalog row, native-model metadata. | ||
| */ | ||
| export function candidateCapabilityEvidence( | ||
| config: OcxConfig, | ||
| providerName: string, | ||
| modelId: string, | ||
| ): RouteCapabilityEvidence { | ||
| const provider = config.providers[providerName]; | ||
| const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName); | ||
| const catalogRow = cachedCatalogModels().find(model => model.provider === providerName && model.id === modelId); | ||
| const isNative = providerName === "openai" && !modelId.includes("/"); | ||
|
|
||
| const contextWindow = provider?.modelContextWindows?.[modelId] | ||
| ?? provider?.contextWindow | ||
| ?? registryEntry?.modelContextWindows?.[modelId] | ||
| ?? catalogRow?.contextWindow | ||
| ?? (isNative ? nativeOpenAiContextWindow(modelId) : undefined); | ||
|
|
||
| const modalities = provider?.modelInputModalities?.[modelId] | ||
| ?? registryEntry?.modelInputModalities?.[modelId] | ||
| ?? catalogRow?.inputModalities | ||
| ?? (isNative ? nativeInputModalities(modelId) : undefined); | ||
| const image = Array.isArray(modalities) | ||
| ? modalities.includes("image") | ||
| : undefined; | ||
|
|
||
| const capabilities = catalogRow?.capabilities ?? []; | ||
| // `parallelToolCalls` is provider-level evidence that the provider accepts | ||
| // parallel tool calls (registry-set per provider); the catalog `capabilities` | ||
| // list is the per-model signal. Both are positive local evidence only. | ||
| const tools = capabilities.includes("tools") | ||
| || (isNative ? true : provider?.parallelToolCalls === true) | ||
| || undefined; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| const reasoningEfforts = provider?.modelReasoningEfforts?.[modelId] | ||
| ?? registryEntry?.modelReasoningEfforts?.[modelId] | ||
| ?? catalogRow?.reasoningEfforts | ||
| ?? (isNative ? nativeReasoningEfforts(modelId) : undefined); | ||
|
|
||
| const tierSupport = provider?.supportsServiceTier | ||
| ?? registryEntry?.supportsServiceTier; | ||
| const serviceTier = tierSupport === true | ||
| ? "supported" | ||
| : tierSupport === false ? "unsupported" : "unknown"; | ||
|
|
||
| const localRemote = localRemoteEvidence(provider?.baseUrl); | ||
| const encryptedCodexTasks = isCanonicalOpenAiForwardProvider( | ||
| provider ?? { adapter: "", authMode: undefined, baseUrl: undefined }, | ||
| ); | ||
|
|
||
| return { | ||
| ...(typeof contextWindow === "number" ? { contextWindow } : {}), | ||
| ...(typeof image === "boolean" ? { image } : {}), | ||
| ...(typeof tools === "boolean" ? { tools } : {}), | ||
| ...(reasoningEfforts !== undefined && reasoningEfforts.length > 0 ? { reasoningEfforts } : {}), | ||
| ...(serviceTier !== "unknown" ? { serviceTier } : {}), | ||
| ...localRemote, | ||
| encryptedCodexTasks, | ||
| }; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.