mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 20:02:45 +03:00
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, e a suíte vitest:ui completa (2149) verde. Sobre esta PR especificamente: rodei os **23 arquivos de teste** que ela toca sobre o tip final, depois do merge da base — **392/392**. A migration `174_server_tool_executions.sql` não colide (o tip está em 173, e você já a renumerou em `c35f0fd7`). O dono foi consultado antes do merge, porque o loop está atrás da flag `SERVER_OWNED_TOOL_LOOP_ENABLED` mas o primeiro send não-streaming mudou de dono sem flag, e a verificação manual em combo com Memory continuava desmarcada. A condição dele foi: entra se os testes focados passarem aqui. Passaram. O lock de passthrough (`fetchCalls.length === 1`) é a parte que mais me convenceu — o double-dispatch que um `if (stream)` em volta do send existente causaria é exatamente o tipo de regressão que não aparece em teste de comportamento, só em contagem de chamada. **Três ajustes meus na sua branch:** 1. `tests/unit/chatcore-stream-error-result.test.ts` procurava `"const legResult = await runNonStreamingProviderLeg"`, mas o seu commit final `6077b9dd` passou a reatribuir `legResult` e trocou para `let`. O guard falhava na sua própria branch (confirmei que o arquivo e o `chatCore.ts` eram byte-idênticos ao head da PR, então não era efeito da leva). Passou a aceitar `const|let` — a intenção do guard é o try/catch em volta da chamada, não a palavra-chave. 2. `tests/integration/skills-pipeline.test.ts` foi de 1156 para 1338 linhas e estourou o `testCap` de 1200. Segui o mesmo caminho que você já tinha tomado em `a1d2d20d` para os testes unitários: extraí os três casos do server-owned tool loop para `tests/integration/server-owned-tool-loop-pipeline.test.ts` (259 linhas), com instância própria do harness. O glob `tests/integration/*.test.ts` pega o arquivo novo sem registro adicional. 3/3 verdes isolados. 3. O arquivo novo herdou cinco `any` do original — que só passavam por estarem congelados no `eslint-suppressions.json` sob o nome antigo. Tipei como `Record<string, unknown>`. E `tests/unit/non-streaming-finalization.test.ts` tinha dois argumentos não usados em `trackPendingRequest`, agora prefixados com `_`. Nada disso toca produção nem enfraquece asserção.
323 lines
10 KiB
TypeScript
323 lines
10 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
translateNonStreamingClientResponse,
|
|
type NonStreamingClientTranslateInput,
|
|
} from "../../open-sse/handlers/chatCore/nonStreamingClientTranslate.ts";
|
|
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
|
import {
|
|
buildAssistantMessageCacheKey,
|
|
clearReasoningCacheAll,
|
|
lookupReasoning,
|
|
} from "../../open-sse/services/reasoningCache.ts";
|
|
import { invalidateBufferTokensCache } from "../../open-sse/utils/usageTracking.ts";
|
|
|
|
/* ── helpers ─────────────────────────────────────────────────────────────── */
|
|
|
|
function baseInput(
|
|
overrides: Partial<NonStreamingClientTranslateInput> = {}
|
|
): NonStreamingClientTranslateInput {
|
|
return {
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
object: "chat.completion",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: "Hello!" },
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
},
|
|
responsePayloadFormat: "openai",
|
|
clientResponseFormat: "openai",
|
|
sourceFormat: "openai",
|
|
provider: "openai",
|
|
model: "gpt-4o",
|
|
requestBody: { messages: [{ role: "user", content: "hi" }] },
|
|
responseToolNameMap: null,
|
|
requestToolIdentityMap: null,
|
|
reasoningCacheScope: null,
|
|
clientHeaders: null,
|
|
isClaudeCodeCompatible: false,
|
|
phase: "final",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/* ── characterization tests ──────────────────────────────────────────────── */
|
|
|
|
test("basic translate: same-format passthrough returns responseBody", () => {
|
|
const input = baseInput();
|
|
const result = translateNonStreamingClientResponse(input);
|
|
assert.equal(result.response.choices[0].message.content, "Hello!");
|
|
assert.ok(result.responseForMemoryExtraction);
|
|
});
|
|
|
|
test("translate from claude to openai format", () => {
|
|
const input = baseInput({
|
|
responseBody: {
|
|
id: "msg-123",
|
|
content: [{ type: "text", text: "Hi there" }],
|
|
stop_reason: "end_turn",
|
|
usage: { input_tokens: 10, output_tokens: 5 },
|
|
},
|
|
responsePayloadFormat: "claude",
|
|
clientResponseFormat: "openai",
|
|
sourceFormat: "claude",
|
|
provider: "anthropic",
|
|
model: "claude-sonnet-4-20250514",
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
const msg = result.response.choices?.[0]?.message;
|
|
assert.ok(msg, "should have choices[0].message");
|
|
assert.equal((msg as { content: string }).content, "Hi there");
|
|
});
|
|
|
|
test("claude source strips markdown code fence", () => {
|
|
const input = baseInput({
|
|
responseBody: {
|
|
id: "msg-123",
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: '```json\n{"key": "value"}\n```',
|
|
},
|
|
],
|
|
stop_reason: "end_turn",
|
|
usage: { input_tokens: 10, output_tokens: 5 },
|
|
},
|
|
responsePayloadFormat: "claude",
|
|
clientResponseFormat: "openai",
|
|
sourceFormat: "claude",
|
|
provider: "anthropic",
|
|
model: "claude-sonnet-4-20250514",
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
const content = result.response.choices?.[0]?.message?.content;
|
|
assert.ok(typeof content === "string");
|
|
// After stripping, the content should not have the markdown fence wrapper
|
|
assert.ok(!content.startsWith("```json"), "markdown fence should be stripped");
|
|
});
|
|
|
|
test("normalizeOpenAIToolFinishReasons: tool_calls present → finish_reason tool_calls", () => {
|
|
const input = baseInput({
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: {
|
|
role: "assistant",
|
|
content: null,
|
|
tool_calls: [
|
|
{
|
|
id: "call_1",
|
|
type: "function",
|
|
function: { name: "get_weather", arguments: "{}" },
|
|
},
|
|
],
|
|
},
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
},
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
assert.equal(result.response.choices[0].finish_reason, "tool_calls");
|
|
});
|
|
|
|
test("reasoning replay: no-tool history comes from historyMessages, not requestBody.input", () => {
|
|
clearReasoningCacheAll();
|
|
const scope = "api-key:test:s...6a";
|
|
const historyMessages = [{ role: "user", content: "hi from translatedBody" }];
|
|
const assistantMessage = {
|
|
role: "assistant",
|
|
content: "thinking result",
|
|
reasoning_content: "let me think...",
|
|
};
|
|
const input = baseInput({
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: assistantMessage,
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
|
},
|
|
// Responses-shaped finalBody: input, no messages. Parent used translatedBody.messages.
|
|
requestBody: { input: [{ role: "user", content: "wrong body" }] },
|
|
historyMessages,
|
|
provider: "deepseek",
|
|
model: "deepseek-v4-pro",
|
|
reasoningCacheScope: scope,
|
|
phase: "intermediate",
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
assert.ok(result.response);
|
|
const cacheKey = buildAssistantMessageCacheKey(
|
|
scope,
|
|
[...historyMessages, assistantMessage],
|
|
historyMessages.length
|
|
);
|
|
assert.equal(
|
|
lookupReasoning(cacheKey),
|
|
"let me think...",
|
|
"must cache against translatedBody.messages, not finalBody.input"
|
|
);
|
|
});
|
|
|
|
test("phase=final applies client usage buffer", () => {
|
|
// Gemini format skips OpenAI/Responses sanitize, so extra usage fields
|
|
// only disappear if applyClientUsageBuffer → filterUsageForFormat runs.
|
|
const input = baseInput({
|
|
phase: "final",
|
|
clientResponseFormat: FORMATS.GEMINI,
|
|
responsePayloadFormat: FORMATS.GEMINI,
|
|
sourceFormat: FORMATS.GEMINI,
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: "Hello!" },
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: 10,
|
|
completion_tokens: 5,
|
|
total_tokens: 15,
|
|
x_provider_extra: 99,
|
|
},
|
|
},
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
const usage = (result.response as { usage: Record<string, unknown> }).usage;
|
|
assert.equal(usage.x_provider_extra, undefined, "final phase must filter extra usage fields");
|
|
assert.equal(
|
|
usage.prompt_tokens,
|
|
undefined,
|
|
"final Gemini filter must drop OpenAI-shaped prompt_tokens"
|
|
);
|
|
});
|
|
|
|
test("phase=intermediate skips applyClientUsageBuffer", () => {
|
|
const input = baseInput({
|
|
phase: "intermediate",
|
|
clientResponseFormat: FORMATS.GEMINI,
|
|
responsePayloadFormat: FORMATS.GEMINI,
|
|
sourceFormat: FORMATS.GEMINI,
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: "partial" },
|
|
finish_reason: null,
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: 10,
|
|
completion_tokens: 5,
|
|
total_tokens: 15,
|
|
x_provider_extra: 99,
|
|
},
|
|
},
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
const usage = (result.response as { usage: Record<string, unknown> }).usage;
|
|
assert.equal(
|
|
usage.x_provider_extra,
|
|
99,
|
|
"intermediate must keep raw extra usage fields (buffer not applied)"
|
|
);
|
|
assert.equal(usage.prompt_tokens, 10);
|
|
});
|
|
|
|
test("Responses API format: sanitizeResponsesApiResponse is applied", () => {
|
|
const input = baseInput({
|
|
clientResponseFormat: FORMATS.OPENAI_RESPONSES,
|
|
responseBody: {
|
|
id: "resp_123",
|
|
object: "response",
|
|
output: [
|
|
{
|
|
type: "function_call",
|
|
name: "ns__get_weather",
|
|
arguments: "{}",
|
|
},
|
|
],
|
|
usage: { input_tokens: 10, output_tokens: 5 },
|
|
illegal_top_level: "drop-me",
|
|
},
|
|
requestToolIdentityMap: new Map([
|
|
["ns__get_weather", { namespace: "ns", name: "get_weather" }],
|
|
]),
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
assert.equal(result.response.object, "response");
|
|
assert.equal(result.response.illegal_top_level, undefined, "sanitizer must drop illegal fields");
|
|
const output = result.response.output as Array<Record<string, unknown>>;
|
|
assert.equal(output[0]?.type, "function_call");
|
|
assert.equal(output[0]?.namespace, "ns", "#7936 restore namespace");
|
|
assert.equal(output[0]?.name, "get_weather", "#7936 restore original name");
|
|
});
|
|
|
|
test("empty content response: passthrough without crash", () => {
|
|
const input = baseInput({
|
|
responseBody: {},
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
assert.ok(result.response);
|
|
assert.ok(result.responseForMemoryExtraction);
|
|
});
|
|
|
|
test("isClaudeCodeCompatible preserves context budget usage", () => {
|
|
const saved = process.env.USAGE_TOKEN_BUFFER;
|
|
process.env.USAGE_TOKEN_BUFFER = "2000";
|
|
invalidateBufferTokensCache();
|
|
try {
|
|
const input = baseInput({
|
|
isClaudeCodeCompatible: true,
|
|
clientResponseFormat: FORMATS.OPENAI,
|
|
phase: "final",
|
|
responseBody: {
|
|
id: "chatcmpl-test",
|
|
object: "chat.completion",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: "test" },
|
|
finish_reason: "stop",
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: 100,
|
|
completion_tokens: 50,
|
|
total_tokens: 150,
|
|
},
|
|
},
|
|
});
|
|
const result = translateNonStreamingClientResponse(input);
|
|
const usage = (result.response as { usage: Record<string, unknown> }).usage;
|
|
assert.equal(
|
|
usage.prompt_tokens,
|
|
2100,
|
|
"Claude Code path must fold context_budget_prompt_tokens (100+2000) into visible prompt_tokens"
|
|
);
|
|
assert.equal(usage.total_tokens, 2150);
|
|
assert.equal("context_budget_prompt_tokens" in usage, false);
|
|
} finally {
|
|
if (saved === undefined) delete process.env.USAGE_TOKEN_BUFFER;
|
|
else process.env.USAGE_TOKEN_BUFFER = saved;
|
|
invalidateBufferTokensCache();
|
|
}
|
|
});
|