mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
chatCore #3501: extract recordContextEditingTelemetryHook. Integrated into release/v3.8.35.
This commit is contained in:
committed by
GitHub
parent
89aa09f6df
commit
fb44bbcbb9
@@ -165,6 +165,7 @@ import {
|
||||
restoreClaudePassthroughToolNames,
|
||||
mergeResponseToolNameMap,
|
||||
} from "./chatCore/passthroughToolNames.ts";
|
||||
import { recordContextEditingTelemetryHook } from "./chatCore/contextEditingTelemetry.ts";
|
||||
import {
|
||||
appendNonStreamingSseTerminalSignal,
|
||||
type NonStreamingSseTerminalState,
|
||||
@@ -3540,25 +3541,13 @@ export async function handleChatCore({
|
||||
// Context Editing telemetry: when the delegated server-side clear actually ran,
|
||||
// record the provider's cleared-token receipt under engine "context-editing" so
|
||||
// it surfaces in compression analytics. Best-effort, Claude-only, non-streaming.
|
||||
if (contextEditingEnabled && provider === "claude") {
|
||||
void (async () => {
|
||||
try {
|
||||
const { extractContextEditingTelemetry } = await import("../config/contextEditing.ts");
|
||||
const tele = extractContextEditingTelemetry(responseBody);
|
||||
if (tele) {
|
||||
const { recordContextEditingTelemetry } =
|
||||
await import("../../src/lib/db/compressionAnalytics.ts");
|
||||
recordContextEditingTelemetry(skillRequestId, tele, provider);
|
||||
log?.debug?.(
|
||||
"CONTEXT_EDITING",
|
||||
`cleared ${tele.clearedInputTokens} input tokens / ${tele.clearedToolUses} tool uses (${tele.editCount} edits)`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Telemetry is best-effort and must never affect the response.
|
||||
}
|
||||
})();
|
||||
}
|
||||
recordContextEditingTelemetryHook({
|
||||
contextEditingEnabled,
|
||||
provider,
|
||||
responseBody,
|
||||
skillRequestId,
|
||||
log,
|
||||
});
|
||||
appendRequestLog({ model, provider, connectionId, tokens: usage, status: "200 OK" }).catch(
|
||||
() => {}
|
||||
);
|
||||
|
||||
40
open-sse/handlers/chatCore/contextEditingTelemetry.ts
Normal file
40
open-sse/handlers/chatCore/contextEditingTelemetry.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* chatCore context-editing telemetry hook (Quality Gate v2 / Fase 9 — chatCore god-file
|
||||
* decomposition, #3501).
|
||||
*
|
||||
* Extracted from handleChatCore's non-streaming success path: when the delegated server-side
|
||||
* context-clear actually ran (Claude-only), record the provider's cleared-token receipt under the
|
||||
* "context-editing" engine so it surfaces in compression analytics. Best-effort, fire-and-forget,
|
||||
* and must never affect the response — the inner work is an un-awaited IIFE that swallows its own
|
||||
* errors. Behaviour is byte-identical to the previous inline block.
|
||||
*/
|
||||
|
||||
type LoggerLike = { debug?: (...args: unknown[]) => void } | null | undefined;
|
||||
|
||||
export function recordContextEditingTelemetryHook(args: {
|
||||
contextEditingEnabled: boolean;
|
||||
provider: string | null | undefined;
|
||||
responseBody: unknown;
|
||||
skillRequestId: string;
|
||||
log?: LoggerLike;
|
||||
}): void {
|
||||
const { contextEditingEnabled, provider, responseBody, skillRequestId, log } = args;
|
||||
if (!contextEditingEnabled || provider !== "claude") return;
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const { extractContextEditingTelemetry } = await import("../../config/contextEditing.ts");
|
||||
const tele = extractContextEditingTelemetry(responseBody);
|
||||
if (tele) {
|
||||
const { recordContextEditingTelemetry } = await import("@/lib/db/compressionAnalytics");
|
||||
recordContextEditingTelemetry(skillRequestId, tele, provider);
|
||||
log?.debug?.(
|
||||
"CONTEXT_EDITING",
|
||||
`cleared ${tele.clearedInputTokens} input tokens / ${tele.clearedToolUses} tool uses (${tele.editCount} edits)`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Telemetry is best-effort and must never affect the response.
|
||||
}
|
||||
})();
|
||||
}
|
||||
106
tests/unit/chatcore-context-editing-telemetry.test.ts
Normal file
106
tests/unit/chatcore-context-editing-telemetry.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
// Characterization of recordContextEditingTelemetryHook — the Claude-only context-editing
|
||||
// telemetry hook extracted from handleChatCore's non-streaming success path (chatCore god-file
|
||||
// decomposition, #3501). The work is a fire-and-forget IIFE; uses a real temp DB and polls the
|
||||
// captured log. Locks: the enabled+claude guard, the no-telemetry no-op, and that a valid
|
||||
// applied_edits payload records and logs the cleared-token receipt.
|
||||
import { test, before, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-ctxedit-test-"));
|
||||
process.env.DATA_DIR = testDataDir;
|
||||
|
||||
const coreDb = await import("../../src/lib/db/core.ts");
|
||||
const { recordContextEditingTelemetryHook } = await import(
|
||||
"../../open-sse/handlers/chatCore/contextEditingTelemetry.ts"
|
||||
);
|
||||
|
||||
function makeLog() {
|
||||
const debug: string[] = [];
|
||||
return {
|
||||
log: { debug: (tag: string, msg: string) => debug.push(`${tag} ${msg}`) },
|
||||
debug,
|
||||
};
|
||||
}
|
||||
|
||||
const telemetryBody = {
|
||||
context_management: {
|
||||
applied_edits: [{ cleared_input_tokens: 120, cleared_tool_uses: 3 }],
|
||||
},
|
||||
};
|
||||
|
||||
async function waitFor(pred: () => boolean, timeoutMs = 3000): Promise<void> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline && !pred()) {
|
||||
await new Promise((r) => setTimeout(r, 25));
|
||||
}
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await coreDb.ensureDbInitialized();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
coreDb.resetDbInstance();
|
||||
try {
|
||||
fs.rmSync(testDataDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// best-effort cleanup
|
||||
}
|
||||
});
|
||||
|
||||
test("disabled context-editing is a no-op", async () => {
|
||||
const { log, debug } = makeLog();
|
||||
recordContextEditingTelemetryHook({
|
||||
contextEditingEnabled: false,
|
||||
provider: "claude",
|
||||
responseBody: telemetryBody,
|
||||
skillRequestId: "req-1",
|
||||
log,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
assert.equal(debug.length, 0);
|
||||
});
|
||||
|
||||
test("non-claude provider is a no-op", async () => {
|
||||
const { log, debug } = makeLog();
|
||||
recordContextEditingTelemetryHook({
|
||||
contextEditingEnabled: true,
|
||||
provider: "openai",
|
||||
responseBody: telemetryBody,
|
||||
skillRequestId: "req-2",
|
||||
log,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
assert.equal(debug.length, 0);
|
||||
});
|
||||
|
||||
test("valid applied_edits records and logs the cleared-token receipt", async () => {
|
||||
const { log, debug } = makeLog();
|
||||
recordContextEditingTelemetryHook({
|
||||
contextEditingEnabled: true,
|
||||
provider: "claude",
|
||||
responseBody: telemetryBody,
|
||||
skillRequestId: "req-3",
|
||||
log,
|
||||
});
|
||||
await waitFor(() => debug.length > 0);
|
||||
assert.ok(debug.length >= 1, "expected a CONTEXT_EDITING debug line");
|
||||
assert.match(debug[0], /CONTEXT_EDITING/);
|
||||
assert.match(debug[0], /cleared 120 input tokens \/ 3 tool uses \(1 edits\)/);
|
||||
});
|
||||
|
||||
test("response without applied_edits is a silent no-op (no throw)", async () => {
|
||||
const { log, debug } = makeLog();
|
||||
recordContextEditingTelemetryHook({
|
||||
contextEditingEnabled: true,
|
||||
provider: "claude",
|
||||
responseBody: { choices: [] },
|
||||
skillRequestId: "req-4",
|
||||
log,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
assert.equal(debug.length, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user