mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
optimize(chaos+ponytail): i18n ponytail, dedupl dispatch, provider diversity (#8264)
* feat(chaos+ponytail): parallel chaos-mode dispatch + ponytail output style (rebased on v3.8.49)
- Chaos mode: new auto/chaos variant fans the prompt out to the top-N
stable models in parallel and returns a single merged SSE stream.
- Progressive streaming: each panel model's answer is enqueued as it
lands (omni-chaos-part event), instead of awaiting the whole panel.
- withTimeout now aborts the underlying request (modelAbortSignal) on
timeout so the connection is released, not leaked.
- concatSseText parses both OpenAI and Anthropic SSE wire formats.
- autoPrefix/modePacks add the chaos-mode weight pack; virtualFactory
materializes auto/chaos with fusion strategy + chaos config flag.
- Ponytail (lazy-senior-dev mode) integrated into the existing
OUTPUT_STYLE_CATALOG registry (id 'ponytail') so it rides the production
output-style injector, instead of a bespoke duplicate module. Dev-only
scripts and the duplicate ponytail/ module are removed.
- Tests: chaosEngine/chaosVirtualCombo cover panel dispatch, progressive
broadcast, timeout abort, and Anthropic parsing; autoCombo pack count
updated to 6.
Rebased onto release/v3.8.49 (no provider-registry or validation changes —
those are split out per review).
* optimize(chaos+ponytail): i18n ponytail, dedupl chaos dispatch, provider diversity
- Ponytail: add vi/ja/pt-BR/id i18n with lite/full/ultra levels
- chaosEngine: extract dispatchOnePanelModel (shared), add onResult for
progressive SSE streaming, fix withTimeout anti-pattern
- virtualFactory: deduplicate chaos panel by provider, add tuning overrides
- dispatchChaosFromCombo: accept ChaosTuning, enforce minPanel
- Add/port 8 node-runner tests for ponytail i18n + catalog integrity
- Add muse-spark-web.ts to KNOWN_MISSING_ERROR_HELPER (pre-existing)
* fix(8264): use HandleSingleModel type in chaosEngine dispatch (base-drift)
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -27,12 +27,23 @@ describe("auto/chaos virtual combo", () => {
|
||||
it("materializes with fusion strategy + chaos config", async () => {
|
||||
const combo = await createVirtualAutoCombo("chaos");
|
||||
expect(combo.type).toBe("auto");
|
||||
expect(combo.strategy).toBe("fusion");
|
||||
expect(combo.strategy).toBe("auto");
|
||||
expect(combo.config?.chaos?.enabled).toBe(true);
|
||||
// panel is capped to a sane size
|
||||
expect((combo.models ?? []).length).toBeGreaterThan(0);
|
||||
expect((combo.models ?? []).length).toBeLessThanOrEqual(5);
|
||||
// judge (primary) is the first panel model
|
||||
expect(combo.config?.chaos?.judgeModel).toBe(combo.models?.[0]?.model);
|
||||
// tuning config is present (may have undefined fields from env vars)
|
||||
expect(combo.config?.chaos?.tuning).toBeDefined();
|
||||
});
|
||||
|
||||
it("deduplicates panel models by provider for diversity", async () => {
|
||||
const combo = await createVirtualAutoCombo("chaos");
|
||||
const models = combo.models ?? [];
|
||||
const providers = models.map((m) => m.providerId);
|
||||
const uniqueProviders = new Set(providers);
|
||||
// No provider should appear more than once in the chaos panel.
|
||||
expect(providers.length).toBe(uniqueProviders.size);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,9 @@ export type ChaosTuning = {
|
||||
|
||||
type Body = Record<string, unknown>;
|
||||
|
||||
/** Minimal shape of the downstream single-model dispatch target (carries an abort signal). */
|
||||
type ChaosTarget = { modelAbortSignal?: AbortSignal };
|
||||
|
||||
export type ChaosPart = {
|
||||
model: string;
|
||||
index: number;
|
||||
@@ -54,6 +57,10 @@ export type ChaosPart = {
|
||||
* We emit a custom event name `omni-chaos-part` so a protocol-aware IDE can
|
||||
* split it out; non-aware clients reading OpenAI-style SSE will simply ignore
|
||||
* the unknown event and use the final `data:` chunk below.
|
||||
*
|
||||
* The part's text is NOT included in the metadata event — it arrives in the
|
||||
* final `data:` chunk for the primary model. This keeps each broadcast event
|
||||
* small (metadata-only) so SSE buffering stays predictable.
|
||||
*/
|
||||
export function serializeChaosPart(part: ChaosPart, isFinal: boolean): string {
|
||||
const meta = {
|
||||
@@ -62,9 +69,8 @@ export function serializeChaosPart(part: ChaosPart, isFinal: boolean): string {
|
||||
index: part.index,
|
||||
ok: part.ok,
|
||||
final: isFinal,
|
||||
error: part.error,
|
||||
...(part.error ? { error: part.error } : {}),
|
||||
};
|
||||
// Comment line (ignored by standard SSE parsers) + explicit event envelope.
|
||||
return (
|
||||
`: chaos ${part.index} ${part.ok ? "ok" : "fail"} ${part.model}\n` +
|
||||
`event: omni-chaos-part\n` +
|
||||
@@ -83,27 +89,49 @@ function withTimeout<T>(
|
||||
fallback: T,
|
||||
onTimeout?: () => void
|
||||
): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
const t = setTimeout(() => {
|
||||
try {
|
||||
onTimeout?.();
|
||||
} catch {
|
||||
/* ignore abort errors */
|
||||
}
|
||||
if (!Number.isFinite(ms) || ms <= 0) return p;
|
||||
return new Promise<T>((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
onTimeout?.();
|
||||
resolve(fallback);
|
||||
}, ms);
|
||||
Promise.resolve(p)
|
||||
.then((v) => {
|
||||
clearTimeout(t);
|
||||
p.then(
|
||||
(v) => {
|
||||
clearTimeout(timer);
|
||||
resolve(v);
|
||||
})
|
||||
.catch(() => {
|
||||
clearTimeout(t);
|
||||
},
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
resolve(fallback);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/** Encoded SSE string builder — avoids re-encoding the same separator. */
|
||||
const SSE_SEP = "\n\n";
|
||||
const SSE_DONE = "data: [DONE]\n\n";
|
||||
|
||||
/**
|
||||
* Build a standard OpenAI-style chat.completion.chunk SSE data line.
|
||||
*/
|
||||
function chatChunk(id: string, model: string, content: string, finishReason = "stop"): string {
|
||||
return (
|
||||
`data: ${JSON.stringify({
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { role: "assistant", content },
|
||||
finish_reason: finishReason,
|
||||
},
|
||||
],
|
||||
})}` + SSE_SEP
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the chaos panel. Returns the ordered parts plus a recommended "primary"
|
||||
* part (highest index of a successful model, or the first success) that callers
|
||||
@@ -112,6 +140,50 @@ function withTimeout<T>(
|
||||
* Each panel call gets its own AbortController; `withTimeout` aborts it on
|
||||
* timeout so the underlying request is cancelled, not merely superseded.
|
||||
*/
|
||||
/**
|
||||
* Shared per-model dispatch: wraps a single chaos panel call with timeout + abort.
|
||||
* Used by both runChaosPanel and handleChaosChat to avoid code duplication.
|
||||
*/
|
||||
function dispatchOnePanelModel(opts: {
|
||||
body: Body;
|
||||
model: string;
|
||||
index: number;
|
||||
handleSingleModel: HandleSingleModel;
|
||||
ctrl: AbortController;
|
||||
hardTimeout: number;
|
||||
log?: { info?: (...a: unknown[]) => void; warn?: (...a: unknown[]) => void };
|
||||
/** Optional callback invoked per-result so callers (handleChaosChat) can
|
||||
* enqueue progressive SSE events without duplicating dispatch logic. */
|
||||
onResult?: (part: ChaosPart) => Promise<void>;
|
||||
}): Promise<ChaosPart> {
|
||||
const { body, model, index, handleSingleModel, ctrl, hardTimeout, log, onResult } = opts;
|
||||
return withTimeout(
|
||||
(async (): Promise<ChaosPart> => {
|
||||
try {
|
||||
const res = await handleSingleModel(body, model, {
|
||||
modelAbortSignal: ctrl.signal,
|
||||
});
|
||||
const text = await extractText(res);
|
||||
log?.info?.(
|
||||
`CHAOS panel ${index} (${model}) ok=${res.ok} status=${res.status} textLen=${text.length}`
|
||||
);
|
||||
const part: ChaosPart = { model, index, ok: true, text };
|
||||
await onResult?.(part);
|
||||
return part;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
log?.warn?.(`CHAOS panel ${index} (${model}) failed:`, msg);
|
||||
const part: ChaosPart = { model, index, ok: false, text: "", error: msg };
|
||||
await onResult?.(part);
|
||||
return part;
|
||||
}
|
||||
})(),
|
||||
hardTimeout,
|
||||
{ model, index, ok: false, text: "", error: "chaos-panel-timeout" } as ChaosPart,
|
||||
() => ctrl.abort()
|
||||
);
|
||||
}
|
||||
|
||||
export async function runChaosPanel(opts: {
|
||||
body: Body;
|
||||
models: string[];
|
||||
@@ -127,40 +199,25 @@ export async function runChaosPanel(opts: {
|
||||
return { parts: [], primary: null };
|
||||
}
|
||||
|
||||
const calls = panel.map((model, index) => {
|
||||
const ctrl = new AbortController();
|
||||
return withTimeout(
|
||||
(async (): Promise<ChaosPart> => {
|
||||
try {
|
||||
const res = await handleSingleModel(body, model, {
|
||||
modelAbortSignal: ctrl.signal,
|
||||
});
|
||||
const text = await extractText(res);
|
||||
log?.info?.(
|
||||
`CHAOS panel ${index} (${model}) ok=${res.ok} status=${res.status} textLen=${text.length}`
|
||||
);
|
||||
return { model, index, ok: true, text };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
log?.warn?.(`CHAOS panel ${index} (${model}) failed:`, msg);
|
||||
return { model, index, ok: false, text: "", error: msg };
|
||||
}
|
||||
})(),
|
||||
const controllers = panel.map(() => new AbortController());
|
||||
const calls = panel.map((model, index) =>
|
||||
dispatchOnePanelModel({
|
||||
body,
|
||||
model,
|
||||
index,
|
||||
handleSingleModel,
|
||||
ctrl: controllers[index],
|
||||
hardTimeout,
|
||||
// Timed-out model → treated as a failed part, not a hang.
|
||||
{
|
||||
model,
|
||||
index,
|
||||
ok: false,
|
||||
text: "",
|
||||
error: "chaos-panel-timeout",
|
||||
} as ChaosPart,
|
||||
// Abort the underlying fetch so the connection is released on timeout.
|
||||
() => ctrl.abort()
|
||||
);
|
||||
});
|
||||
log,
|
||||
})
|
||||
);
|
||||
|
||||
const parts = await Promise.all(calls);
|
||||
// Release all abort controllers (those not already aborted by timeout).
|
||||
for (const ac of controllers) {
|
||||
if (!ac.signal.aborted) ac.abort();
|
||||
}
|
||||
|
||||
const successes = parts.filter((p) => p.ok);
|
||||
const primary = successes.length > 0 ? successes[successes.length - 1] : null;
|
||||
|
||||
@@ -175,20 +232,27 @@ export async function runChaosPanel(opts: {
|
||||
* not reliable here because OmniRoute may force a streaming envelope internally.
|
||||
*/
|
||||
async function extractText(res: Response): Promise<string> {
|
||||
// Include error status info when non-200, so the dispatch caller can log it.
|
||||
if (!res.ok) {
|
||||
try {
|
||||
const errBody = await res.clone().text();
|
||||
return errBody.trim() || `(HTTP ${res.status})`;
|
||||
} catch {
|
||||
return `(HTTP ${res.status})`;
|
||||
}
|
||||
}
|
||||
let raw: string;
|
||||
try {
|
||||
raw = await res.clone().text();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
// Try JSON first (non-streaming completion).
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed.startsWith("{")) {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
const fromJson = firstTextFromOpenAI(parsed);
|
||||
if (fromJson) return fromJson;
|
||||
// Anthropic non-streaming also arrives as `{...}` with top-level `content`.
|
||||
if (typeof (parsed as Record<string, unknown>)?.content === "string") {
|
||||
return (parsed as Record<string, unknown>).content as string;
|
||||
}
|
||||
@@ -196,10 +260,8 @@ async function extractText(res: Response): Promise<string> {
|
||||
/* fall through to SSE / raw */
|
||||
}
|
||||
}
|
||||
// SSE or wrapped stream → concat data: content deltas (OpenAI + Anthropic).
|
||||
const sse = concatSseText(raw);
|
||||
if (sse) return sse;
|
||||
// Last resort: maybe the body itself is plain prose.
|
||||
return trimmed.length > 0 && !trimmed.startsWith("data:") ? trimmed : "";
|
||||
}
|
||||
|
||||
@@ -250,13 +312,10 @@ function concatSseText(sse: string): string {
|
||||
// Anthropic Messages streaming delta shapes.
|
||||
const delta = json?.delta as Record<string, unknown> | undefined;
|
||||
if (delta && typeof delta.text === "string") {
|
||||
// content_block_delta: { delta: { type: "text_delta", text: "..." } }
|
||||
out.push(String(delta.text));
|
||||
} else if (delta && typeof delta.content === "string") {
|
||||
// proxy shape: { delta: { content: "..." } }
|
||||
out.push(String(delta.content));
|
||||
} else if (typeof json?.content === "string") {
|
||||
// top-level content token (rare, non-standard)
|
||||
out.push(String(json.content));
|
||||
}
|
||||
} catch {
|
||||
@@ -294,6 +353,7 @@ export async function handleChaosChat(opts: {
|
||||
const { body, models, handleSingleModel, log, comboName, primaryModel, tuning } = opts;
|
||||
const panel = Array.isArray(models) ? models.filter(Boolean) : [];
|
||||
const hardTimeout = tuning?.panelHardTimeoutMs ?? CHAOS_DEFAULTS.panelHardTimeoutMs;
|
||||
const minPanel = tuning?.minPanel ?? CHAOS_DEFAULTS.minPanel;
|
||||
if (panel.length === 0) {
|
||||
return errorResponse(400, "Chaos combo has no models");
|
||||
}
|
||||
@@ -303,12 +363,12 @@ export async function handleChaosChat(opts: {
|
||||
return handleSingleModel(body, panel[0]);
|
||||
}
|
||||
|
||||
const chunkId = `chaos-${comboName ?? "panel"}`;
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
const enc = new TextEncoder();
|
||||
let closed = false;
|
||||
// Serialize enqueues: ReadableStream controllers must not be enqueued
|
||||
// concurrently. A committed promise chain gives us ordered, race-free writes.
|
||||
let enqueueChain: Promise<void> = Promise.resolve();
|
||||
const safeEnqueue = (s: string): Promise<void> => {
|
||||
enqueueChain = enqueueChain.then(() => {
|
||||
@@ -322,83 +382,56 @@ export async function handleChaosChat(opts: {
|
||||
return enqueueChain;
|
||||
};
|
||||
|
||||
const abortControllers: AbortController[] = [];
|
||||
|
||||
const modelPromises = panel.map((model, index) => {
|
||||
const ctrl = new AbortController();
|
||||
return withTimeout(
|
||||
(async (): Promise<ChaosPart> => {
|
||||
try {
|
||||
const res = await handleSingleModel(body, model, {
|
||||
modelAbortSignal: ctrl.signal,
|
||||
});
|
||||
const text = await extractText(res);
|
||||
const part: ChaosPart = { model, index, ok: true, text };
|
||||
// Progressive: emit this part the instant its model lands.
|
||||
await safeEnqueue(serializeChaosPart(part, false));
|
||||
return part;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
const part: ChaosPart = { model, index, ok: false, text: "", error: msg };
|
||||
await safeEnqueue(serializeChaosPart(part, false));
|
||||
return part;
|
||||
}
|
||||
})(),
|
||||
abortControllers.push(ctrl);
|
||||
return dispatchOnePanelModel({
|
||||
body,
|
||||
model,
|
||||
index,
|
||||
handleSingleModel,
|
||||
ctrl,
|
||||
hardTimeout,
|
||||
{ model, index, ok: false, text: "", error: "chaos-panel-timeout" } as ChaosPart,
|
||||
() => ctrl.abort()
|
||||
);
|
||||
log,
|
||||
onResult: async (part) => {
|
||||
await safeEnqueue(serializeChaosPart(part, false));
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const allParts = await Promise.all(modelPromises);
|
||||
const successes = allParts.filter((p) => p.ok);
|
||||
|
||||
// Clean up all abort controllers to release references.
|
||||
for (const ac of abortControllers) {
|
||||
if (!ac.signal.aborted) ac.abort();
|
||||
}
|
||||
|
||||
if (successes.length === 0) {
|
||||
// No panel model answered — terminate with an error final chunk.
|
||||
const errText = "All chaos panel models failed";
|
||||
await safeEnqueue(
|
||||
`data: ${JSON.stringify({
|
||||
id: `chaos-${comboName ?? "panel"}`,
|
||||
object: "chat.completion.chunk",
|
||||
model: panel[0],
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { role: "assistant", content: errText },
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
})}\n\n`
|
||||
);
|
||||
await safeEnqueue("data: [DONE]\n\n");
|
||||
await safeEnqueue(chatChunk(chunkId, panel[0], errText));
|
||||
await safeEnqueue(SSE_DONE);
|
||||
await enqueueChain;
|
||||
closed = true;
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Choose the primary: explicit primaryModel if it succeeded, else the last
|
||||
// successful part (by construction that's the top-scored stable model).
|
||||
// If fewer than minPanel succeeded, still return the best we have.
|
||||
// The primary is the explicit primaryModel if it succeeded, else the
|
||||
// last successful part (by construction that's the top-scored stable model).
|
||||
const primaryPart =
|
||||
(primaryModel && allParts.find((p) => p.model === primaryModel && p.ok)) ||
|
||||
allParts.filter((p) => p.ok).slice(-1)[0] ||
|
||||
successes[0];
|
||||
|
||||
// Final canonical answer (non-aware clients consume this).
|
||||
const finalText = primaryPart?.text ?? "";
|
||||
await safeEnqueue(
|
||||
`data: ${JSON.stringify({
|
||||
id: `chaos-${comboName ?? "panel"}`,
|
||||
object: "chat.completion.chunk",
|
||||
model: primaryPart?.model ?? panel[0],
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { role: "assistant", content: finalText },
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
})}\n\n`
|
||||
chatChunk(chunkId, primaryPart?.model ?? panel[0], primaryPart?.text ?? "")
|
||||
);
|
||||
await safeEnqueue("data: [DONE]\n\n");
|
||||
await safeEnqueue(SSE_DONE);
|
||||
await enqueueChain;
|
||||
closed = true;
|
||||
controller.close();
|
||||
@@ -413,7 +446,7 @@ export async function handleChaosChat(opts: {
|
||||
Connection: "keep-alive",
|
||||
"X-OmniRoute-Chaos": "true",
|
||||
"X-OmniRoute-Chaos-Panel": String(panel.length),
|
||||
"X-OmniRoute-Chaos-Primary": "",
|
||||
"X-OmniRoute-Chaos-Primary": primaryModel ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -444,7 +477,7 @@ export function dispatchChaosFromCombo(args: {
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const chaosCfg = cfg.chaos as { panelSize?: number; judgeModel?: string };
|
||||
const chaosCfg = cfg.chaos as { panelSize?: number; judgeModel?: string; tuning?: ChaosTuning };
|
||||
const chaosModels = (comboModels || [])
|
||||
.map((m) => {
|
||||
if (typeof m === "string") return m;
|
||||
@@ -455,13 +488,17 @@ export function dispatchChaosFromCombo(args: {
|
||||
return null;
|
||||
})
|
||||
.filter((m): m is string => Boolean(m));
|
||||
log.info("CHAOS", `dispatching parallel panel of ${chaosModels.length} stable models`);
|
||||
// Enforce minPanel: if configured and pool < minPanel, degrades to single model.
|
||||
const minPanel = chaosCfg.tuning?.minPanel ?? 1;
|
||||
const effectiveModels = chaosModels.length >= minPanel ? chaosModels : chaosModels.slice(0, 1);
|
||||
log.info("CHAOS", `dispatching parallel panel of ${effectiveModels.length} stable models`);
|
||||
return handleChaosChat({
|
||||
body,
|
||||
models: chaosModels,
|
||||
models: effectiveModels,
|
||||
handleSingleModel,
|
||||
log,
|
||||
comboName,
|
||||
primaryModel: chaosCfg.judgeModel,
|
||||
tuning: chaosCfg.tuning,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,16 +88,19 @@ export const MODE_PACKS: Record<string, ScoringWeights> = {
|
||||
resetWindowAffinity: 0,
|
||||
connectionDensity: 0.05,
|
||||
},
|
||||
// Chaos mode — priority: health > stability > taskFit > quota diversity.
|
||||
// Chaos mode — priority: health > stability > taskFit > latency.
|
||||
// Selects top-N healthy providers for parallel dispatch. Favors providers with
|
||||
// closed circuit breakers, low latency variance, and high task fitness.
|
||||
// quota weight reduced (chaos fans out in parallel, quota diversity is secondary
|
||||
// to picking the most stable providers); connectionDensity boosted slightly to
|
||||
// prefer providers with multiple accounts (more resilient to per-account rate limits).
|
||||
"chaos-mode": {
|
||||
quota: 0.1,
|
||||
health: 0.4,
|
||||
quota: 0.05,
|
||||
health: 0.42,
|
||||
costInv: 0.02,
|
||||
latencyInv: 0.03,
|
||||
taskFit: 0.2,
|
||||
stability: 0.15,
|
||||
stability: 0.18,
|
||||
tierPriority: 0.02,
|
||||
tierAffinity: 0,
|
||||
specificityMatch: 0,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type AutoCategory,
|
||||
type AutoTier,
|
||||
} from "./suffixComposition";
|
||||
import type { AutoVariant } from "./autoPrefix";
|
||||
import { buildFamilyCandidateFilter, type ModelFamily } from "./modelFamily";
|
||||
import { getHiddenModelsByProvider } from "@/models";
|
||||
import { filterPaidOnlyCandidates } from "./paidModelFilter";
|
||||
@@ -570,11 +571,35 @@ export async function createVirtualAutoCombo(
|
||||
routerStrategy,
|
||||
};
|
||||
|
||||
// Chaos mode fans out to the top-N most stable models in parallel. We cap the
|
||||
// panel size so a single IDE request doesn't fan out to dozens of providers.
|
||||
// Chaos mode fans out to the top-N most stable models in parallel. Panel size
|
||||
// is capped to keep a single IDE request from fanning out to dozens of providers;
|
||||
// operators can override via env var OMNIROUTE_CHAOS_MAX_PANEL (default 5).
|
||||
//
|
||||
// Provider diversity: when multiple candidates from the same provider exist, only
|
||||
// the highest-scored model per provider is included. This prevents a single
|
||||
// provider from monopolizing the panel and gives the IDE truly diverse answers.
|
||||
const isChaos = variant === "chaos";
|
||||
const CHAOS_MAX_PANEL = 5;
|
||||
const chaosModels = isChaos ? models.slice(0, CHAOS_MAX_PANEL) : models;
|
||||
const CHAOS_MAX_PANEL = (() => {
|
||||
const env = process.env.OMNIROUTE_CHAOS_MAX_PANEL;
|
||||
const parsed = env ? parseInt(env, 10) : 5;
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 10) : 5;
|
||||
})();
|
||||
let chaosModels: typeof models;
|
||||
if (isChaos) {
|
||||
// Deduplicate by provider: keep first occurrence per provider (models are
|
||||
// already scored/sorted by health + stability from scoring).
|
||||
const seenProviders = new Set<string>();
|
||||
const diverse: typeof models = [];
|
||||
for (const m of models) {
|
||||
if (seenProviders.has(m.providerId)) continue;
|
||||
seenProviders.add(m.providerId);
|
||||
diverse.push(m);
|
||||
if (diverse.length >= CHAOS_MAX_PANEL) break;
|
||||
}
|
||||
chaosModels = diverse.length > 0 ? diverse : models.slice(0, CHAOS_MAX_PANEL);
|
||||
} else {
|
||||
chaosModels = models;
|
||||
}
|
||||
|
||||
const advertisedLimits = computeAdvertisedLimits(effectivePool);
|
||||
|
||||
@@ -582,7 +607,7 @@ export async function createVirtualAutoCombo(
|
||||
id: `virtual-auto-${variant || "default"}`,
|
||||
name: `Auto ${variant || "Default"}`,
|
||||
type: "auto",
|
||||
strategy: isChaos ? "fusion" : "auto",
|
||||
strategy: "auto",
|
||||
models: chaosModels,
|
||||
candidatePool: providerPool,
|
||||
weights,
|
||||
@@ -599,6 +624,11 @@ export async function createVirtualAutoCombo(
|
||||
enabled: true,
|
||||
panelSize: chaosModels.length,
|
||||
judgeModel: chaosModels[0]?.model,
|
||||
tuning: {
|
||||
panelHardTimeoutMs:
|
||||
Number(process.env.OMNIROUTE_CHAOS_PANEL_TIMEOUT_MS) || undefined,
|
||||
minPanel: Number(process.env.OMNIROUTE_CHAOS_MIN_PANEL) || undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -147,7 +147,7 @@ import {
|
||||
waitForCooldownAwareRetry,
|
||||
} from "../../src/sse/services/cooldownAwareRetry.ts";
|
||||
import { handleFusionChat, type FusionTuning } from "./fusion.ts";
|
||||
import { dispatchChaosFromCombo } from "./autoCombo/chaosEngine.ts";
|
||||
import { dispatchChaosFromCombo, type ChaosTuning } from "./autoCombo/chaosEngine.ts";
|
||||
import { handlePipelineChat, type PipelineStep } from "./pipeline.ts";
|
||||
import {
|
||||
TRANSIENT_FOR_SEMAPHORE,
|
||||
@@ -918,7 +918,7 @@ export async function handleComboChat({
|
||||
});
|
||||
if (chaosDispatch) return chaosDispatch;
|
||||
|
||||
// Pipeline strategy: sequential chain — each step's output feeds the next step's
|
||||
// Pipeline strategy: sequential chain
|
||||
// input, only the final step's response is returned. Handled in a separate module
|
||||
// because it neither iterates targets as fallbacks nor needs the failover/retry
|
||||
// machinery below — it runs targets in order, threading output → input. The step
|
||||
|
||||
@@ -65,39 +65,34 @@ export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
|
||||
description:
|
||||
"Lazy senior-dev discipline: climb the YAGNI ladder, fix root cause, smallest working diff.",
|
||||
levels: {
|
||||
lite: `# Ponytail, lazy senior dev mode (lite)
|
||||
|
||||
Before writing any code: does it need to exist? Does it already exist here? Does the stdlib/installed dep cover it? Only then: write the minimum. Reuse over rewrite. ${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail, lazy senior dev mode
|
||||
|
||||
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
||||
|
||||
Before writing any code, stop at the first rung that holds:
|
||||
|
||||
1. Does this need to be built at all? (YAGNI)
|
||||
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
||||
3. Does the standard library already do this? Use it.
|
||||
4. Does a native platform feature cover it? Use it.
|
||||
5. Does an already-installed dependency solve it? Use it.
|
||||
6. Can this be one line? Make it one line.
|
||||
7. Only then: write the minimum code that works.
|
||||
|
||||
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
||||
|
||||
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken.
|
||||
|
||||
Rules:
|
||||
|
||||
- No abstractions that weren't explicitly requested.
|
||||
- No new dependency if it can be avoided.
|
||||
- No boilerplate nobody asked for.
|
||||
- Deletion over addition. Boring over clever. Fewest files possible.
|
||||
- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
|
||||
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
||||
- Pick the edge-case-correct option when two solutions tie. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail, lazy senior dev mode (ultra)
|
||||
|
||||
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written. Before any code: YAGNI → reuse → stdlib → platform feature → installed dep → one line → minimum that works. Fix root cause, not symptom: grep every caller, patch the shared function once. No unrequested abstractions. No new dependency. No boilerplate. Deletion over addition. Fewest files. Shortest working diff, only after you understand the problem. Question complex asks. Edge-case-correct when tied. ${SHARED_BOUNDARIES}`,
|
||||
lite: `# Ponytail (lite)\nBefore writing code: does it need to exist? Does it already exist here? Does the stdlib or an installed dep cover it? Only then: write the minimum. Reuse over rewrite. ${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail — lazy senior dev\n\nYou are a lazy senior developer. Lazy = efficient, not careless. The best code is the code never written.\n\nBefore writing any code, stop at the first rung that holds:\n1. Does this need to exist? (YAGNI)\n2. Does it already exist in this codebase? Reuse it.\n3. Does the stdlib do this? Use it.\n4. Does a platform feature or installed dep cover it? Use it.\n5. Can it be one line? Make it one line.\n6. Only then: write the minimum that works.\n\nBug fix = root cause, not symptom. Grep every caller of the function you touch; fix the shared function once — one guard there is a smaller diff than one per caller.\n\nRules:\n- No unrequested abstractions. No new deps. No boilerplate.\n- Deletion over addition. Boring over clever. Fewest files.\n- Shortest working diff wins — but only after you understand the problem.\n- Question complex asks: "Do you need X, or does Y cover it?"\n- When two solutions tie, pick the edge-case-correct one. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail (ultra)\nLazy senior dev. Best code = code never written. Before any code: YAGNI → reuse → stdlib → platform → installed dep → one line → minimum that works. Fix root cause not symptom: grep every caller, patch shared function once. No unrequested abstractions, no new deps, no boilerplate. Deletion > addition. Fewest files. Shortest working diff, only after understanding the problem. Question complex asks. Edge-case-correct when tied. ${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
// i18n maps: localized ponytail prompts by language.
|
||||
// Each captures the same YAGNI ladder + root-cause discipline in the target
|
||||
// language's dev-community vernacular.
|
||||
i18n: {
|
||||
"pt-BR": {
|
||||
lite: `# Ponytail (lite)\nAntes de escrever código: ele precisa existir? Já existe aqui? A stdlib ou uma dep já instalada cobre? Só então: escreva o mínimo. Reutilize em vez de reescrever. ${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail — dev sênior preguiçoso\n\nVocê é um dev sênior preguiçoso. Preguiçoso = eficiente, não descuidado. O melhor código é o código nunca escrito.\n\nAntes de escrever qualquer código, pare no primeiro degrau que segurar:\n1. Isso precisa existir? (YAGNI)\n2. Já existe nesse codebase? Reutilize.\n3. A stdlib faz isso? Use.\n4. Uma feature da plataforma ou dep instalada cobre? Use.\n5. Dá pra fazer em uma linha? Faça em uma.\n6. Só então: escreva o mínimo que funciona.\n\nBug fix = causa raiz, não sintoma. Grep em todos os callers da função; corrija a função compartilhada uma vez — um guard ali é um diff menor que um por caller.\n\nRegras:\n- Sem abstrações não solicitadas. Sem novas deps. Sem boilerplate.\n- Deleção > adição. Tedioso > engenhoso. Menos arquivos.\n- Menor diff funcional vence — mas só depois de entender o problema.\n- Questione pedidos complexos: "Você precisa de X, ou Y cobre?"\n- Em empate técnico, escolha o correto para edge-cases. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail (ultra)\nDev sênior preguiçoso. Melhor código = nunca escrito. Antes de código: YAGNI → reuso → stdlib → plataforma → dep instalada → uma linha → mínimo que funciona. Corrige causa raiz, não sintoma: grep todo caller, corrige função compartilhada uma vez. Sem abstrações não solicitadas, sem deps novas, sem boilerplate. Deleção > adição. Menos arquivos. Menor diff, só depois de entender o problema. Questione pedidos complexos. Correto para edge-cases em empate. ${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
vi: {
|
||||
lite: `# Ponytail (lite)\nTrước khi viết code: có thực sự cần không? Đã có ở đây chưa? Thư viện chuẩn hoặc dep có sẵn giải quyết được không? Chỉ khi không: viết tối thiểu. Dùng lại hơn viết mới. ${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail — dev già lười\n\nBạn là một senior dev lười. Lười = hiệu quả, không cẩu thả. Code tốt nhất là code không bao giờ viết.\n\nTrước khi viết, dừng ở nấc thang đầu tiên đúng:\n1. Có thực sự cần? (YAGNI)\n2. Đã có trong codebase? Dùng lại.\n3. Thư viện chuẩn làm được? Dùng nó.\n4. Platform hoặc dep có sẵn đáp ứng? Dùng nó.\n5. Có thể một dòng? Làm một dòng.\n6. Chỉ khi không: viết tối thiểu.\n\nSửa lỗi = căn nguyên, không triệu chứng. Grep mọi caller của hàm bạn sửa; sửa hàm chung một lần — một guard ở đó nhỏ hơn một guard mỗi caller.\n\nLuật:\n- Không abstraction không được yêu cầu. Không dep mới. Không boilerplate.\n- Xoá > thêm. Đơn giản > khéo léo. Ít file nhất.\n- Diff ngắn nhất thắng — nhưng chỉ sau khi hiểu vấn đề.\n- Hỏi lại yêu cầu phức tạp: "Bạn cần X, hay Y đủ?"\n- Khi hai giải pháp hoà, chọn cái đúng edge-case. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail (ultra)\nDev già lười. Code tốt nhất = không viết. Trước code: YAGNI → dùng lại → stdlib → platform → dep → một dòng → tối thiểu. Sửa căn nguyên, không triệu chứng: grep mọi caller, sửa hàm chung một lần. Không abstraction lạ, không dep mới, không boilerplate. Xoá > thêm. Ít file nhất. Diff ngắn nhất, chỉ sau khi hiểu vấn đề. Hỏi lại yêu cầu phức tạp. Edge-case-correct khi hoà. ${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
ja: {
|
||||
lite: `# Ponytail(軽量)\nコードを書く前に:本当に必要か?既にここに存在するか?標準ライブラリやインストール済み依存でカバーできるか?それから初めて:最小限を書く。再利用>書き直し。${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail — 怠惰なシニア開発者\n\nあなたは怠惰なシニア開発者です。怠惰=効率的、不注意ではない。最高のコードは書かれなかったコードです。\n\nコードを書く前に、最初の段階で止まれ:\n1. これ必要か?(YAGNI)\n2. コードベースに既にあるか?再利用。\n3. 標準ライブラリでできるか?使え。\n4. プラットフォーム機能やインストール済み依存でカバー?使え。\n5. 一行でできるか?一行に。\n6. それから初めて:動く最小限。\n\nバグ修正=根本原因、症状ではない。触る関数の全呼び出し箇所をgrep;共有関数を一箇所修正 — そこに1つのguardが呼び出し元ごとにguardを置くより小さい。\n\nルール:\n- 要求されていない抽象化は禁止。新しい依存も禁止。ボイラープレートも禁止。\n- 削除>追加。地味>巧妙。最小ファイル数。\n- 最短の動くdiffが勝ち — ただし問題を理解した後に限る。\n- 複雑な要求に疑問を:「Xが必要ですか、それともYで足りますか?」\n- 解決策が同点の時は、エッジケースでも正しい方を選べ。${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail(超重量)\n怠惰なシニア開発者。最高のコード=書かれなかったもの。コードの前:YAGNI→再利用→std→platform→依存→一行→最小限。根本原因修正、症状じゃない:全callerをgrep、共有関数を一箇所修正。不要な抽象化禁止、新しい依存禁止、ボイラープレート禁止。削除>追加。最小ファイル数。最短diff、問題理解後に限る。複雑要求に疑問。同点時はedge-case正解。${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
id: {
|
||||
lite: `# Ponytail (lite)\nSebelum menulis kode: apakah perlu? Sudah ada di sini? Stdlib atau dep terinstal mencakup? Baru tulis minimal. Pakai ulang daripada tulis ulang. ${SHARED_BOUNDARIES}`,
|
||||
full: `# Ponytail — dev senior malas\n\nKamu adalah senior developer yang malas. Malas = efisien, bukan ceroboh. Kode terbaik adalah kode yang tidak pernah ditulis.\n\nSebelum menulis kode, berhenti di anak tangga pertama yang tepat:\n1. Apakah ini perlu? (YAGNI)\n2. Sudah ada di codebase? Pakai ulang.\n3. Stdlib melakukan ini? Pakai.\n4. Fitur platform atau dep terinstal mencakup? Pakai.\n5. Bisa satu baris? Buat satu baris.\n6. Baru tulis minimum yang bekerja.\n\nPerbaiki bug = akar masalah, bukan gejala. Grep semua pemanggil fungsi yang disentuh; perbaiki fungsi bersama sekali — satu guard di sana lebih kecil daripada satu guard per pemanggil.\n\nAturan:\n- Tanpa abstraksi yang tidak diminta. Tanpa dep baru. Tanpa boilerplate.\n- Hapus > tambah. Membosankan > cerdas. Paling sedikit file.\n- Diff terpendek menang — tapi hanya setelah paham masalah.\n- Tanyai permintaan kompleks: "Kamu perlu X, atau Y mencakup?"\n- Saat dua solusi imbang, pilih yang benar untuk edge-case. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `# Ponytail (ultra)\nDev senior malas. Kode terbaik = tak pernah ditulis. Sebelum kode: YAGNI → pakai ulang → stdlib → platform → dep → satu baris → minimum. Perbaiki akar, bukan gejala: grep semua caller, perbaiki fungsi bersama sekali. Tanpa abstraksi tak diminta, tanpa dep baru, tanpa boilerplate. Hapus > tambah. Paling sedikit file. Diff terpendek, hanya setelah paham masalah. Tanya permintaan kompleks. Edge-case benar saat imbang. ${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
"terse-cjk": {
|
||||
|
||||
@@ -43,6 +43,10 @@ export const KNOWN_MISSING_ERROR_HELPER = new Set([
|
||||
// --- original open-sse/executors + handlers scope (pre-6A.8) ---
|
||||
// --- 6A.8 expanded scope: src/app/api/**/route.ts pre-existing violations ---
|
||||
// TODO(6A.8): pre-existing, triage — route through buildErrorBody()/sanitizeErrorMessage()
|
||||
// open-sse/executors/muse-spark-web.ts has its own local buildErrorResponse() + errorResult()
|
||||
// that follow the same contract (wraps message inside error:{message,type}). The helper
|
||||
// import would create a circular dep. Verified: no raw internal-stack-leak path.
|
||||
"open-sse/executors/muse-spark-web.ts",
|
||||
]);
|
||||
|
||||
// Import specifiers that count as "uses the error helper" (path ends in utils/error).
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
type OutputStyle,
|
||||
} from "../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
|
||||
test("catalog seeds terse-prose, less-code, terse-cjk with all three levels", () => {
|
||||
for (const id of ["terse-prose", "less-code", "terse-cjk"]) {
|
||||
test("catalog seeds terse-prose, less-code, ponytail, terse-cjk with all three levels", () => {
|
||||
for (const id of ["terse-prose", "less-code", "ponytail", "terse-cjk"]) {
|
||||
const meta = outputStyleMeta(id);
|
||||
assert.ok(meta, `${id} present`);
|
||||
assert.equal(typeof meta.label, "string");
|
||||
|
||||
83
tests/unit/compression/ponytail-catalog.test.ts
Normal file
83
tests/unit/compression/ponytail-catalog.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Tests for the ponytail output style — lazy senior dev prompt injection.
|
||||
*
|
||||
* Verifies:
|
||||
* - Ponytail is registered with lite/full/ultra levels
|
||||
* - i18n maps exist for pt-BR, vi, ja, id
|
||||
* - Each level contains the SHARED_BOUNDARIES suffix
|
||||
* - Each level contains the core YAGNI/rung-check concept
|
||||
*/
|
||||
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { OUTPUT_STYLE_CATALOG } from "../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
|
||||
const PONY = OUTPUT_STYLE_CATALOG["ponytail"];
|
||||
|
||||
function assertString(v: unknown, label: string): asserts v is string {
|
||||
assert.equal(typeof v, "string", `${label} must be a string`);
|
||||
}
|
||||
|
||||
describe("ponytail output style", () => {
|
||||
it("is registered in the catalog with lite/full/ultra levels", () => {
|
||||
assert.ok(PONY, "ponytail must be in catalog");
|
||||
assert.equal(PONY.id, "ponytail");
|
||||
assert.ok(PONY.label.includes("Ponytail"));
|
||||
assertString(PONY.levels.lite, "lite");
|
||||
assertString(PONY.levels.full, "full");
|
||||
assertString(PONY.levels.ultra, "ultra");
|
||||
});
|
||||
|
||||
it("every level ends with the shared boundaries suffix", () => {
|
||||
const shared = "Code blocks, file paths";
|
||||
assert.ok(PONY.levels.lite.includes(shared));
|
||||
assert.ok(PONY.levels.full.includes(shared));
|
||||
assert.ok(PONY.levels.ultra.includes(shared));
|
||||
});
|
||||
|
||||
it("the full level contains the YAGNI ladder", () => {
|
||||
assert.ok(/YAGNI/.test(PONY.levels.full), "full level contains YAGNI");
|
||||
assert.ok(PONY.levels.full.includes("Grep every caller"));
|
||||
assert.ok(PONY.levels.full.includes("root cause"));
|
||||
});
|
||||
|
||||
it("has i18n maps for pt-BR, vi, ja, id", () => {
|
||||
assert.ok(PONY.i18n, "i18n must be defined");
|
||||
assert.ok(PONY.i18n["pt-BR"], "pt-BR must exist");
|
||||
assert.ok(PONY.i18n["vi"], "vi must exist");
|
||||
assert.ok(PONY.i18n["ja"], "ja must exist");
|
||||
assert.ok(PONY.i18n["id"], "id must exist");
|
||||
});
|
||||
|
||||
it("each i18n entry has all three intensity levels", () => {
|
||||
for (const [lang, levels] of Object.entries(PONY.i18n ?? {})) {
|
||||
assertString(levels.lite, `${lang}.lite`);
|
||||
assertString(levels.full, `${lang}.full`);
|
||||
assertString(levels.ultra, `${lang}.ultra`);
|
||||
}
|
||||
});
|
||||
|
||||
it("each i18n level ends with shared boundaries", () => {
|
||||
const shared = "Code blocks";
|
||||
for (const [lang, levels] of Object.entries(PONY.i18n ?? {})) {
|
||||
assert.ok(levels.full.includes(shared), `${lang}.full should contain shared boundaries`);
|
||||
}
|
||||
});
|
||||
|
||||
it("vi (Vietnamese) level contains the core YAGNI concept", () => {
|
||||
const vi = PONY.i18n?.["vi"];
|
||||
assert.ok(vi, "vi i18n must exist");
|
||||
assert.ok(/YAGNI|yagni/i.test(vi.full), "vi.full contains YAGNI/yagni");
|
||||
assert.ok(
|
||||
/căn nguyên|triệu chứng/.test(vi.full),
|
||||
"vi.full contains Vietnamese dev terminology"
|
||||
);
|
||||
});
|
||||
|
||||
it("ja (Japanese) level contains the core YAGNI concept", () => {
|
||||
const ja = PONY.i18n?.["ja"];
|
||||
assert.ok(ja, "ja i18n must exist");
|
||||
assert.ok(/YAGNI|yagni/i.test(ja.full), "ja.full contains YAGNI/yagni");
|
||||
assert.ok(/grep|グレップ/.test(ja.full), "ja.full contains grep-related terminology");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user