mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-22 07:02:16 +03:00
Compare commits
1 Commits
fix/10986-
...
fix/10028-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b35f67d9 |
@@ -94,10 +94,15 @@ export function ensureAndroidCacheDir(options = {}) {
|
||||
*/
|
||||
export function isFatalInstrumentationHookFailure(text) {
|
||||
if (!text) return false;
|
||||
return (
|
||||
/Unsupported platform:\s*android/i.test(text) ||
|
||||
/error occurred while loading instrumentation hook/i.test(text)
|
||||
);
|
||||
// Next.js wraps ANY throw inside instrumentation.register() with the generic
|
||||
// "An error occurred while loading instrumentation hook:" prefix, on every
|
||||
// platform (node_modules/next/dist/server/web/globals.js). That prefix alone
|
||||
// therefore cannot identify the Android/Termux cache-probe failure — a bare
|
||||
// generic instrumentation error on win32/desktop would be misreported as the
|
||||
// Android bug and hide the real cause. Only match when the text actually
|
||||
// carries the Android platform marker that Next's getCacheDirectory() emits.
|
||||
// #10028
|
||||
return /Unsupported platform:\s*android/i.test(text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
1
changelog.d/fixes/10028-windows-instrumentation-hook.md
Normal file
1
changelog.d/fixes/10028-windows-instrumentation-hook.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(cli): stop diagnosing every Next.js instrumentation-hook failure as the Android/Termux cache bug — only the Android "Unsupported platform: android" signal now triggers the Android hint, so a win32/desktop instrumentation error surfaces its real cause instead of a useless `mkdir -p ~/.cache` (#10028)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(command-code): surface reasoning-only output as content when a model emits no text-delta (#10986)
|
||||
@@ -455,10 +455,9 @@
|
||||
"src/sse/handlers/chatHelpers.ts": 1019,
|
||||
"src/shared/middleware/chatBodyAdmission.ts": 1005,
|
||||
"_rebaseline_2026_08_20_10668_tabitoken_gateway": "#10668 (yawar-aquil) own catalog growth: src/shared/constants/providers/apikey/gateways.ts 1268->1283 (+15, entirely this PR diff -- one new tabitoken gateway entry, data lines only; base moved from 1255 to 1268 via other merges since the PR forked). Not combination drift: reproducible on the PR branch alone, so the WS5.5 release-captain rule does not apply. Extraction is not available -- the file is pure data (own header: \"Pure data; merged by apikey/index.ts via spread\") and already split into 6 family files under apikey/. Same precedent as _rebaseline_2026_08_14_imagetotext_servicekinds (#10275/#10291, gateways.ts 1250->1255, data lines only) and _rebaseline_2026_08_11_v3850_merge_storm_provider_registry (owner-authorized for this same file).",
|
||||
"open-sse/executors/commandCode.ts": 1059,
|
||||
"open-sse/executors/commandCode.ts": 1038,
|
||||
"_rebaseline_2026_08_21_10859_vision_bridge_catalog": "#10859 own growth (Vision Bridge fixes #10808/#10809): src/lib/modelCapabilities.ts 1006->1016 (+10, cmd/gpt-5.3-codex* text-only capability resolution) and open-sse/executors/commandCode.ts 988->1023 (+35, Command Code wire-model normalization for bare ids + reasoning field fallback for opencode-routed gateways). Cohesive bug fixes at the existing capability-resolution / executor chokepoints; not extractable mid-fix. Covered by tests/unit/model-capabilities-command-code-codex-textonly-10703.test.ts, tests/unit/command-code-vision.test.ts, tests/unit/opencode-mimo-reasoning-details-nonstream.test.ts. Pushed directly to release (own-session miss: the original rebaseline was made in a throwaway validation worktree and never landed on the PR branch or the release before merge).",
|
||||
"_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts.",
|
||||
"_rebaseline_2026_08_21_10986_reasoning_only_content": "#10986 own growth: open-sse/executors/commandCode.ts 1038->1059 (+21, reasoning-only content fallback — when upstream emits only reasoning-delta events and never a text-delta, surface the reasoning text as message.content in createJsonResponse and emit a synthetic content delta in createStreamResponse). Cohesive bug fix at the existing executor chokepoint (mirrors precedent style of #10907/#10859). Covered by tests/unit/command-code-executor.test.ts (2 new cases: non-stream + streaming)."
|
||||
"_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts."
|
||||
},
|
||||
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
|
||||
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
|
||||
|
||||
@@ -738,7 +738,6 @@ function createStreamResponse(
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let sentRole = false;
|
||||
let sentContent = false;
|
||||
let closed = false;
|
||||
const state: AggregateState = {
|
||||
content: "",
|
||||
@@ -773,10 +772,7 @@ function createStreamResponse(
|
||||
switch (event.type) {
|
||||
case "text-delta": {
|
||||
const text = stringValue(event.text) || "";
|
||||
if (text) {
|
||||
sentContent = true;
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { content: text })));
|
||||
}
|
||||
if (text) controller.enqueue(sse(chatCompletionChunk(id, model, { content: text })));
|
||||
state.content += text;
|
||||
break;
|
||||
}
|
||||
@@ -814,12 +810,6 @@ function createStreamResponse(
|
||||
break;
|
||||
case "finish": {
|
||||
state.finishReason = mapFinishReason(event.finishReason);
|
||||
// If the model only produced reasoning-delta events (no text-delta), the
|
||||
// client-visible stream would otherwise end with no content. Emit one
|
||||
// content delta carrying the accumulated reasoning text (#10986).
|
||||
if (!sentContent && state.reasoning && state.toolCalls.length === 0) {
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { content: state.reasoning })));
|
||||
}
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, {}, state.finishReason)));
|
||||
// Emit a standards-compliant usage-only chunk (choices: []) before
|
||||
// [DONE] when upstream reported usage. stream.ts's extractUsage
|
||||
@@ -867,9 +857,6 @@ function createStreamResponse(
|
||||
if (!closed) {
|
||||
if (!sentRole)
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { role: "assistant" })));
|
||||
if (!sentContent && state.reasoning && state.toolCalls.length === 0) {
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { content: state.reasoning })));
|
||||
}
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, {}, state.finishReason)));
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
@@ -960,14 +947,6 @@ async function createJsonResponse(
|
||||
}
|
||||
|
||||
const message: JsonRecord = { role: "assistant", content: state.content };
|
||||
// Some Command Code models emit the whole answer as reasoning-delta events and
|
||||
// never a text-delta. When that leaves content empty, surface the reasoning text
|
||||
// as content too (#10986) so OpenAI-compatible clients get a usable answer. Keep
|
||||
// reasoning_content populated as well for reasoning-aware clients, and do not
|
||||
// override content when real text OR tool calls are present.
|
||||
if (!state.content && state.reasoning && state.toolCalls.length === 0) {
|
||||
message.content = state.reasoning;
|
||||
}
|
||||
if (state.reasoning) message.reasoning_content = state.reasoning;
|
||||
if (state.toolCalls.length > 0) message.tool_calls = state.toolCalls;
|
||||
|
||||
|
||||
@@ -333,68 +333,6 @@ test("Command Code data: SSE lines aggregate into non-stream ChatCompletion JSON
|
||||
});
|
||||
});
|
||||
|
||||
test("Command Code reasoning-only output falls back to reasoning as content (non-stream)", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
commandCodeStream(
|
||||
[
|
||||
{ type: "reasoning-delta", text: "The user wants 79874+93658. " },
|
||||
{ type: "reasoning-delta", text: "That equals 173532." },
|
||||
{
|
||||
type: "finish",
|
||||
finishReason: "stop",
|
||||
totalUsage: { inputTokens: 20, outputTokens: 64, outputTokenDetails: { reasoningTokens: 61 } },
|
||||
},
|
||||
],
|
||||
{ sse: true }
|
||||
);
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
model: "meta/muse-spark-1.2-contributor",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Calculate 79874+93658, and reply with the result only." }] },
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
const message = json.choices[0].message;
|
||||
// Regression #10986: when the model emits only reasoning-delta events (never a
|
||||
// text-delta), content must fall back to the reasoning text instead of "" (which
|
||||
// OpenAI-compatible clients treat as null/no answer).
|
||||
assert.equal(message.content, "The user wants 79874+93658. That equals 173532.");
|
||||
// reasoning_content must STAY populated for reasoning-aware clients.
|
||||
assert.equal(message.reasoning_content, "The user wants 79874+93658. That equals 173532.");
|
||||
});
|
||||
|
||||
test("Command Code reasoning-only output emits a content delta chunk when streaming", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
commandCodeStream(
|
||||
[
|
||||
{ type: "reasoning-delta", text: "The result is 173532." },
|
||||
{ type: "finish", finishReason: "stop" },
|
||||
],
|
||||
{ sse: true }
|
||||
);
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
model: "meta/muse-spark-1.2-contributor",
|
||||
stream: true,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Calcular 79874+93658" }] },
|
||||
});
|
||||
|
||||
const sse = await response.text();
|
||||
assert.match(sse, /data: \[DONE\]/);
|
||||
const chunks = parseSsePayloads(sse);
|
||||
assert.equal(chunks[0].choices[0].delta.role, "assistant");
|
||||
// Regression #10986: the reasoning-only stream must emit a content delta when it
|
||||
// otherwise ends with no content. reasoning_content stays present too.
|
||||
const contentDelta = chunks.find((c) => c.choices[0].delta.content !== undefined);
|
||||
assert.equal(contentDelta.choices[0].delta.content, "The result is 173532.");
|
||||
const reasoningDelta = chunks.find((c) => c.choices[0].delta.reasoning_content !== undefined);
|
||||
assert.equal(reasoningDelta.choices[0].delta.reasoning_content, "The result is 173532.");
|
||||
assert.equal(chunks.at(-1).choices[0].finish_reason, "stop");
|
||||
});
|
||||
|
||||
test("Command Code executor surfaces upstream and streamed errors", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
new Response("bad key", { status: 401, statusText: "Unauthorized" });
|
||||
|
||||
@@ -166,6 +166,19 @@ test("isFatalInstrumentationHookFailure: matches Next.js android + hook errors",
|
||||
assert.equal(isFatalInstrumentationHookFailure(""), false);
|
||||
});
|
||||
|
||||
test("isFatalInstrumentationHookFailure: BUG #10028 — generic non-Android instrumentation failure is not Android/Termux", () => {
|
||||
// Next.js wraps ANY throw inside instrumentation.register() with the generic
|
||||
// "An error occurred while loading instrumentation hook:" prefix, on every
|
||||
// platform. Without an actual Android/"Unsupported platform:" signal, that
|
||||
// generic wrapper must NOT be diagnosed as the Android/Termux cache-dir bug,
|
||||
// or a plain win32/desktop failure gets a useless `mkdir -p ~/.cache` hint
|
||||
// and the real cause is hidden.
|
||||
const genericWindowsFailure =
|
||||
"Error: An error occurred while loading instrumentation hook: " +
|
||||
"Cannot find module 'C:\\Users\\dev\\.omniroute\\config.json'";
|
||||
assert.equal(isFatalInstrumentationHookFailure(genericWindowsFailure), false);
|
||||
});
|
||||
|
||||
test("formatAndroidInstrumentationFailureHint: names the cache dir and TERMUX_GUIDE", () => {
|
||||
const hint = formatAndroidInstrumentationFailureHint("/data/home/.cache");
|
||||
assert.match(hint, /\/data\/home\/\.cache/);
|
||||
|
||||
Reference in New Issue
Block a user