Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/quality/file-size-baseline.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"_rebaseline_2026_09_08_explicit_target_routing_v1": "explicit-target-routing-v1 own growth: open-sse/services/combo.ts 4080->4148 (checker-loc; corrected 2026-09-08, an earlier pass here understated this at 4111 before lint-staged's Prettier reflow had run against the final reconciled diff). Two components: (1) wires the new tryExplicitTargetDispatch() dispatch branch (combo/tryExplicitTargetDispatch.ts, new file) into handleComboChatInner ahead of the existing dispatch prelude, passing ~23 already-in-scope local variables — same individual-named-params call shape already used by the sibling tryFusionDispatch/tryPinnedModelDispatch calls a few lines below, so this is call-site wiring at the existing dispatch chokepoint, not a new pattern; (2) same class as _rebaseline_2026_09_07_ci_baseline_repair_prettier_reflow — lint-staged's Prettier reflow of several pre-existing >100-col lines elsewhere in this same function (log.info/log.warn calls, lockout/quota-affordability condition wraps, the final Response(...) at handleRoundRobinCombo) that this PR's own touch of the file now forces into wrapped form; verified via `git diff -w` between the reconciliation-fixup commit and its parent showing zero non-whitespace change outside the new dispatch block and one comment edit. Not extractable into a smaller call without either inventing a bespoke options-object convention inconsistent with those siblings, or moving unrelated pre-existing code out of this file, both worse than the frozen-cap increase. Covered by tests/unit/combo/combo-explicit-target-detector.test.ts, combo-explicit-target-dispatch.test.ts, combo-explicit-target-resolver.test.ts.",
"_rebaseline_2026_09_07_ci_baseline_repair_prettier_reflow": "fix/release-v3.8.51-ci-baseline own growth, both cases the lint-staged Prettier reflow of a pre-existing >100-col line that any touch of the file now forces into wrapped form (same class as _rebaseline_2026_09_03_12649_free_tier_reaudit_gateways): src/sse/handlers/chat.ts 2454->2457 (+3 = the registerMoonshotQuotaFetcher/registerMoonshotFetchersForNodes import wraps; reproduces on pure release/v3.8.51 via `prettier --check`, unrelated to this PR's own one-word string fix in the image-model rejection message a few hundred lines below); open-sse/utils/stream.ts 3072->3077 (+5 = two pre-existing filterUsageForFormat/costUsd call-site wraps, also reproducing on pure base; this PR's own highWaterMark parameter restore -- a faithful restore of a previously-reverted commit's plumbing -- is a net +0 once its JSDoc is condensed to a trailing comment). Covered by tests/unit/chat-rejects-image-only-model.test.ts and the highwatermark benchmark/executor suites.",
"_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.",
"_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.",
Expand Down Expand Up @@ -426,7 +427,7 @@
"open-sse/mcp-server/server.ts": 1572,
"open-sse/services/accountFallback.ts": 2467,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
"open-sse/services/combo.ts": 4080,
"open-sse/services/combo.ts": 4148,
"open-sse/translator/response/openai-responses.ts": 1466,
"open-sse/utils/cursorAgentProtobuf.ts": 1547,
"open-sse/utils/proxyFetch.ts": 1271,
Expand Down
3,219 changes: 1,643 additions & 1,576 deletions open-sse/services/combo.ts

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion open-sse/services/combo/comboStructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ function toComboLike(combo: ComboInput): ComboLike {
};
}

function getCombosArray(allCombos: ComboCollectionLike): ComboLike[] {
/** Normalize the two ComboCollectionLike shapes (bare array or {combos}) into
* a flat, type-normalized ComboLike[]. Exported for explicitTargetResolver.ts
* (O360 explicit-target routing), which needs the same normalization to scan
* every combo's name/labels — reused rather than reimplemented. */
export function getCombosArray(allCombos: ComboCollectionLike): ComboLike[] {
const combos = Array.isArray(allCombos) ? allCombos : allCombos?.combos || [];
return combos.map((combo) => toComboLike(combo));
}
Expand Down
30 changes: 29 additions & 1 deletion open-sse/services/combo/decisionTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,35 @@ export const COMBO_SKIP_REASONS = [

export type ComboSkipReason = (typeof COMBO_SKIP_REASONS)[number];

export type ComboDecision = "dispatched" | "skipped_before_dispatch" | "not_reached";
/**
* The explicit_*, weak_*, and missing_task values are O360 explicit-target-
* routing V1 additions: the prelude branch that resolves either a deterministic
* "/route <target>" command (routeSlashCommand.ts) or a natural-language
* "use <alias>" command (explicitTargetDetector.ts) records one of these
* instead of "dispatched"/"skipped_before_dispatch"/"not_reached", which
* describe the normal per-target attempt loop and don't fit an
* override-or-fail-closed decision made before that loop even starts.
*
* "weak_unresolved_fallthrough" is natural-language-only: an unresolved
* candidate there ALWAYS falls through to ordinary chat instead of failing
* closed (natural language is deliberately conservative — only "/route" is
* the deterministic, always-fail-closed-on-unknown control API). See each
* detector module's own doc comment for the full rationale.
*
* "missing_task" is "/route"-only: the command was the entire content of
* the user's turn, with no task text before or after it to forward to the
* resolved target.
*/
export type ComboDecision =
| "dispatched"
| "skipped_before_dispatch"
| "not_reached"
| "explicit_override"
| "unknown_target"
| "ambiguous_target"
| "target_unavailable"
| "weak_unresolved_fallthrough"
| "missing_task";

export interface ComboTraceEntry {
/** Safe internal identifier of the combo step (execution key). */
Expand Down
16 changes: 13 additions & 3 deletions open-sse/services/combo/dispatchPrelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ type RunCombo = (options: HandleComboChatOptions) => Promise<Response>;
/**
* The subset of handleComboChat's own arguments that a recursing branch has to
* hand back to it when it dispatches a nested combo-ref.
*
* Exported for tryExplicitTargetDispatch.ts (O360 explicit-target routing),
* which needs the exact same option-bag rebuild for its combo-alias dispatch
* path — reused rather than duplicated.
*/
type PreludeBaseOptionArgs = {
export type PreludeBaseOptionArgs = {
invocationId?: string;
body: Record<string, unknown>;
combo: ComboLike;
Expand All @@ -99,7 +103,7 @@ type PreludeBaseOptionArgs = {
};

/** Rebuild handleComboChat's option bag verbatim for a recursive dispatch. */
function buildBaseOptions(a: PreludeBaseOptionArgs): HandleComboChatOptions {
export function buildBaseOptions(a: PreludeBaseOptionArgs): HandleComboChatOptions {
return {
body: a.body,
combo: a.combo,
Expand Down Expand Up @@ -169,8 +173,14 @@ export function pinIsDurablyUnhealthy(
* Async wrapper: resolve the pinned model's provider, read its circuit state and
* active connections, and decide via {@link pinIsDurablyUnhealthy}. Fail-open
* (return false) on any error so a lookup bug never drops a healthy pin.
*
* Exported for tryExplicitTargetDispatch.ts (O360 explicit-target routing),
* which needs the identical health gate for a resolved explicit target — with
* the opposite failure policy at the call site: an explicit target that is
* durably unhealthy must fail closed (TARGET_UNAVAILABLE), not silently fall
* through to strategy the way a stale context-cache pin does.
*/
async function isPinnedModelDurablyUnhealthy(pinnedModel: string): Promise<boolean> {
export async function isPinnedModelDurablyUnhealthy(pinnedModel: string): Promise<boolean> {
try {
const provider = parseModel(pinnedModel).provider;
if (!provider) return false;
Expand Down
213 changes: 213 additions & 0 deletions open-sse/services/combo/explicitTargetDetector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* Explicit target detector — natural-language "use <alias>" style commands in
* the caller's own last turn (O360 explicit-target-routing V1).
*
* This is the CONVENIENCE path, not the deterministic control API — that is
* routeSlashCommand.ts's "/route <target>" (checked first, higher priority,
* by tryExplicitTargetDispatch.ts). Natural language is intentionally
* conservative: it never fails a request closed on its own account. An
* unresolved natural-language candidate simply falls through to ordinary
* chat (see tryExplicitTargetDispatch.ts) — only an unresolved "/route"
* command, or a candidate that resolves but is ambiguous/unavailable, fails
* closed. This keeps the regex here small on purpose: it only has to decide
* WHETHER the last user-role message asks to route to a named target, not
* how confidently, and it never resolves the alias to a real model/combo
* (see explicitTargetResolver.ts).
*
* Scope is intentionally narrow beyond that too: this module never looks at
* anything but the last role="user" message — never system messages,
* assistant history, tool results, or earlier user turns. Control text from
* anywhere else in the conversation is not something the caller typed to
* OmniRoute just now, and is not a safe place to look for a routing command.
*/

/** One content part in an OpenAI/Anthropic-shaped message.content array. */
type MessageContentPart = { type?: unknown; text?: unknown } | string;

type MessageLike = {
role?: unknown;
content?: string | MessageContentPart[] | unknown;
};

/**
* Flatten an OpenAI/Anthropic-shaped content-parts array to plain text — only
* "text"/"input_text" parts contribute — image and tool-shaped parts are
* ignored, mirroring the part-type filter already used by
* extractMessageContents() in src/shared/utils/inputSanitizer.ts. Split out of
* extractLastUserMessageText() purely to keep that function's own branching
* shallow; no behavior change.
*/
function extractTextFromContentParts(content: MessageContentPart[]): string | null {
const parts: string[] = [];
for (const part of content) {
if (typeof part === "string") {
parts.push(part);
continue;
}
if (!part || typeof part !== "object") continue;
const p = part as { type?: unknown; text?: unknown };
if ((p.type === "text" || p.type === "input_text") && typeof p.text === "string") {
parts.push(p.text);
}
}
const joined = parts.join("\n").trim();
return joined.length > 0 ? joined : null;
}

/**
* Extract the plain text of the LAST role="user" message in the request body,
* handling both a plain string `content` and an OpenAI/Anthropic-shaped
* content-parts array (only "text"/"input_text" parts contribute — image and
* tool-shaped parts are ignored, mirroring the part-type filter already used
* by extractMessageContents() in src/shared/utils/inputSanitizer.ts).
*
* Returns null when there is no user message or its content is empty/unusable.
*
* Shared with routeSlashCommand.ts — the deterministic "/route" path scans
* the same last-user-turn text, just with different (simpler) matching
* rules.
*/
export function extractLastUserMessageText(body: Record<string, unknown>): string | null {
const raw = body?.messages;
const messages = Array.isArray(raw) ? raw : [];
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i] as MessageLike;
if (!msg || typeof msg !== "object" || msg.role !== "user") continue;
const { content } = msg;
if (typeof content === "string") {
return content.length > 0 ? content : null;
}
if (Array.isArray(content)) {
return extractTextFromContentParts(content);
}
return null;
}
return null;
}

// Reuse the same literal markers flattenToolHistory.ts already uses to embed
// tool calls/results into plain text for providers with no native tool
// messages — recognizing them here means a message that went through that
// flattening doesn't get re-scanned as if it were the user's own words.
const TOOL_CALL_PREFIX = "[Called tools: ";
const TOOL_RESULT_PREFIX = "[Tool result: ";

const FENCED_CODE_BLOCK = /```[\s\S]*?```/g;
const INLINE_CODE_SPAN = /`[^`\n]*`/g;

/**
* Strip fenced code blocks, inline code spans, and recognizable flattened
* tool-call/tool-result segments from a block of text, leaving only the
* natural-language remainder to run explicit-command patterns against.
*
* Deliberately does not attempt to recognize arbitrary/unfenced tool payloads
* pasted as plain prose — that is not a solvable text-shape problem without a
* real parser, and is out of scope for V1 (see the patch's OPEN_ISSUES).
*/
export function stripControlNoise(text: string): string {
let out = text.replace(FENCED_CODE_BLOCK, " ").replace(INLINE_CODE_SPAN, " ");
out = out
.split("\n")
.filter((line) => {
const trimmed = line.trimStart();
return !trimmed.startsWith(TOOL_CALL_PREFIX) && !trimmed.startsWith(TOOL_RESULT_PREFIX);
})
.join("\n");
return out;
}

export type ExplicitTargetMatch = { alias: string };

/**
* Command verbs that introduce an explicit-target phrase. "usar" (infinitive)
* is deliberately EXCLUDED: in Portuguese it is the form used in
* reported/descriptive speech ("para usar Astra", "recomendo usar o Claude")
* — not a direct command. The imperative forms "use"/"usa" (2nd person) are
* what a real command uses ("use Astra", "usa o Claude"), so only those are
* treated as command verbs. This is the concrete fix for the required
* negative case: "documentação diz para usar Astra" must never match.
*
* First-letter character classes (not the `i` flag) cover sentence-initial
* capitalization ("Use Astra") — see CANDIDATE below for why `i` is avoided
* entirely in this file.
*/
const COMMAND_VERBS = "(?:[Uu]se|[Uu]sa|[Pp]ede|[Pp]eça|[Pp]ergunta|[Pp]ergunte|[Mm]anda|[Mm]ande)";

/**
* Optional Portuguese connector between the verb and the candidate ("pede
* PRO Astra", "manda O Claude").
*/
const CONNECTOR = "(?:pro|pra|para|ao|à|o|a)";

/**
* The candidate token itself. Aliases, combo names, and literal model ids
* are NOT proper nouns as a matter of system design — "Astra"/"Claude" are
* just examples, real combo names are routinely lowercase-hyphenated
* ("o360-cheap-worker") and literal model ids carry `/` and `.`
* ("cx/gpt-5.6-sol-high", per explicitTargetResolver.ts's own tier-4
* literal-model-id lookup). Capitalization must never gate whether a
* candidate is even extracted — only whether it turns out to resolve to
* something real (see explicitTargetResolver.ts) decides that. No `i` flag
* is used anywhere in this file's patterns: combined with `u`, `i` case-folds
* Unicode property escapes like `\p{Lu}` in V8/JS (confirmed empirically,
* moot here since this class no longer needs `\p{Lu}`, but avoided
* throughout so that trap is never reintroduced later).
*/
const CANDIDATE = "([\\p{L}][\\p{L}0-9_\\-./]{1,79})";

const VERB_FIRST_PATTERN = new RegExp(
`\\b${COMMAND_VERBS}\\b(?:\\s+${CONNECTOR}\\b)?\\s+${CANDIDATE}`,
"u"
);

/**
* "com o/a <alias>" alone is NOT enough signal: it matches plain descriptive/
* reported-speech sentences just as easily as a command —
* "ontem falei com o Astra sobre isso", "já conversei com o Astra",
* "isso foi revisado com o Astra" all contain the exact same "com o <alias>"
* shape with zero routing intent. The one required-positive example,
* "com o Astra, procure falhas", has a real structural signal the negatives
* don't: an imperative task verb immediately follows the alias (across an
* optional comma). Requiring that verb (via a lookahead, so it isn't
* consumed/captured) is what separates a real command from a mention.
*/
const TASK_IMPERATIVE_VERBS =
"procure|verifique|analise|revise|critique|corrija|avalie|teste|confirme|valide|" +
"aponte|liste|resuma|explique|mostre|diga|ache|encontre|chame|pergunte|pe[çc]a|" +
"execute|rode|gere|crie|escreva|refatore|otimize";

const COM_O_PATTERN = new RegExp(
`\\b[Cc]om\\s+(?:o|a)\\s+${CANDIDATE}\\s*,?\\s*(?=\\b(?:${TASK_IMPERATIVE_VERBS})\\b)`,
"u"
);

/**
* Detect an explicit-target command in already-sanitized natural-language
* text (see stripControlNoise). Returns the raw alias token as typed, or
* null when no command pattern matches — a bare mention of an alias name
* with no leading command verb ("Astra", "a documentação fala do Astra")
* never matches, by construction.
*/
export function detectExplicitTargetPhrase(sanitizedText: string): ExplicitTargetMatch | null {
const verbMatch = VERB_FIRST_PATTERN.exec(sanitizedText);
if (verbMatch?.[1]) return { alias: verbMatch[1] };

const comMatch = COM_O_PATTERN.exec(sanitizedText);
if (comMatch?.[1]) return { alias: comMatch[1] };

return null;
}

/**
* Full pipeline: last user message → strip code/tool noise → detect command.
* tryExplicitTargetDispatch.ts calls this only after routeSlashCommand.ts's
* deterministic "/route" check has already come up empty.
*/
export function detectExplicitTargetInBody(
body: Record<string, unknown>
): ExplicitTargetMatch | null {
const text = extractLastUserMessageText(body);
if (!text) return null;
const sanitized = stripControlNoise(text);
return detectExplicitTargetPhrase(sanitized);
}
Loading