Compare commits

...

37 Commits

Author SHA1 Message Date
diegosouzapw
4fee959ad2 fix(video): preserve safe continuation and handoffs 2026-08-27 03:29:50 -03:00
diegosouzapw
d62ecb5d98 test(video): prove safe continuation and handoff gaps 2026-08-27 03:18:39 -03:00
diegosouzapw
ab60b95e31 fix(video): redact executor diagnostics 2026-08-27 03:07:06 -03:00
diegosouzapw
6d209a9128 test(video): cover GLM fallback diagnostic retention 2026-08-27 03:02:28 -03:00
diegosouzapw
62b1835bb5 test(video): prove executor diagnostics redaction gaps 2026-08-27 02:58:06 -03:00
diegosouzapw
23832ba1e9 security(video): bypass response-derived caches 2026-08-27 02:44:46 -03:00
diegosouzapw
11e501f099 Merge remote cache-retention RED commit 2026-08-27 02:38:37 -03:00
diegosouzapw
747ae10e62 test(video): expose response-derived cache retention 2026-08-27 02:36:42 -03:00
diegosouzapw
64b78df100 test(video): expose response-derived cache retention 2026-08-27 02:35:27 -03:00
diegosouzapw
ff70ea7633 security(video): propagate stream retention context 2026-08-27 02:27:14 -03:00
diegosouzapw
6b36696d95 test(video): expose streaming finalization leak 2026-08-27 02:24:25 -03:00
diegosouzapw
d62c0f2688 fix(video): preserve operational model lockouts 2026-08-27 02:20:04 -03:00
diegosouzapw
b945e3c80e test(video): preserve daily quota lockout semantics 2026-08-27 02:17:21 -03:00
diegosouzapw
132ad5f16d security(video): fail closed on malformed transcript cues 2026-08-27 02:13:51 -03:00
diegosouzapw
6475be13c7 test(video): expose malformed serialized cue leak 2026-08-27 02:09:24 -03:00
diegosouzapw
f47f63df83 Merge release/v3.8.51 into security/video-transcript-redaction 2026-08-27 02:05:33 -03:00
Diego Rodrigues de Sa e Souza
2acbfc6fa6 Merge pull request #11751 from diegosouzapw/test/v3851-tiered-rotation-timeout
test(auto-combo): tolerate full-suite rotation load
2026-08-27 02:02:24 -03:00
diegosouzapw
c467c3232b Merge remote-tracking branch 'origin/release/v3.8.51' into security/video-transcript-redaction 2026-08-27 01:20:11 -03:00
diegosouzapw
4d519c406a docs(video): define transcript retention trust boundary 2026-08-26 23:01:16 -03:00
diegosouzapw
6fee356aa2 test(video): retain structured fusion log details 2026-08-26 22:10:21 -03:00
diegosouzapw
e77b386619 security(video): close transcript retention boundaries 2026-08-26 22:02:13 -03:00
diegosouzapw
8893816d0c test(video): defend server-owned plugin sensitivity 2026-08-26 21:58:53 -03:00
diegosouzapw
71765435fb test(video): exercise proxy fast-fail boundary 2026-08-26 21:52:31 -03:00
diegosouzapw
e385778bcf test(video): expose proxy fast-fail log retention 2026-08-26 21:49:04 -03:00
diegosouzapw
fb8897e926 test(video): expose quota-model transcript retention 2026-08-26 21:34:41 -03:00
diegosouzapw
a9f98fcb48 test(video): expose guardrail log retention paths 2026-08-26 21:30:08 -03:00
diegosouzapw
6dab0df038 test(video): cover terminal and plugin-context retention 2026-08-26 21:24:41 -03:00
diegosouzapw
2ac715aa9c test(video): expose retained combo and plugin error paths 2026-08-26 21:22:23 -03:00
diegosouzapw
86e48e3c9a test(video): cover nested carrier chains 2026-08-26 21:11:10 -03:00
diegosouzapw
5fbf218139 test(video): cover round-robin transcript error logs 2026-08-26 21:09:00 -03:00
diegosouzapw
0095f3b38f test(video): prove application logs omit transcript echoes 2026-08-26 21:07:55 -03:00
diegosouzapw
dabf8dd580 test(video): expose residual transcript retention paths 2026-08-26 20:47:09 -03:00
Xiangzhe
d92088c5dc docs(video): document transcript log-protection contract 2026-08-26 19:05:50 -03:00
Xiangzhe
20bd2c067c security(video): fail closed on hostile log payloads 2026-08-26 19:01:30 -03:00
Xiangzhe
1ed8f4ad4a test(video): prove fail-closed transcript inspection 2026-08-26 18:59:04 -03:00
Xiangzhe
f2f2fab636 security(video): redact transcript content across persistence and streams 2026-08-26 18:54:18 -03:00
Xiangzhe
b76aa02875 test(video): prove transcript retention across log sinks 2026-08-26 18:29:29 -03:00
78 changed files with 4534 additions and 382 deletions

View File

@@ -0,0 +1 @@
- **fix(security):** Video Bridge transcript text is now omitted from OmniRoute-owned retained request, response, usage, error, stream, continuation, handoff, and Memory surfaces while the live bridge request remains intact. Server-generated descriptions are matched by bounded SHA-256/length identities emitted only by a successful Video Bridge rewrite; ordinary fields named `transcript` and caller-forged description prose remain untouched. Traversal is cycle-safe and bounded, and hostile getters, proxies, or budget overflow fail closed to a constant omission marker instead of leaking content or breaking the request. Custom plugins and guardrails remain privileged processors of the live payload and must secure any sinks they create themselves. ([#11658](https://github.com/diegosouzapw/OmniRoute/issues/11658))

View File

@@ -1,13 +1,13 @@
---
title: "Guardrails"
version: 3.8.50
lastUpdated: 2026-08-24
version: 3.8.51
lastUpdated: 2026-08-26
---
# Guardrails
> **Source of truth:** `src/lib/guardrails/`
> **Last updated:** 2026-08-24 — v3.8.50 (Video Bridge visual dedup hardening + focused captions)
> **Last updated:** 2026-08-26 — v3.8.51 (Video Bridge transcript-retention boundary)
Guardrails enforce safety, policy, and content transformations at the boundary
between OmniRoute and upstream providers. Each guardrail can inspect (and
@@ -439,6 +439,61 @@ are rendered as untrusted observations alongside the frame captions. Invalid,
out-of-range, or provenance-free text is rejected rather than mixed into the
caption stream.
#### Transcript retention boundary
Transcript text remains available to the live bridge and upstream request, but it is not retained
by OmniRoute's logging and memory surfaces. `videoTranscriptLogRedaction.ts` recognizes
`transcript` and `audioTranscript` only inside supported video carriers. A field with either name
in an ordinary tool argument or application object is preserved, and delimiter-shaped caller
prose is not trusted as a bridge result.
When the Video Bridge produces a description containing validated cues, it emits only a
SHA-256/length identity in guardrail metadata. The request pipeline accepts those identities only
from a successful, payload-modifying `video-bridge` result and uses them to omit the exact generated
description segment after translation or concatenation. Raw transcript text is never placed in the
metadata.
The retention policy covers raw, converted, and provider request logs; provider and client response
bodies; detailed pipeline artifacts; call logs, active/pending usage, rejected requests, proxy/error
diagnostics, SSE chunks, and Memory extraction. Response bodies and stream chunks for a sensitive
request are omitted because an upstream may echo request text. Safe operational metadata such as
status, timing, provider, model, headers-presence, token counts, and the fact that redaction occurred
remains available. The constant retained marker is `[omitted: video transcript]`.
Sensitivity is resolved before the guardrail chain from the original client body and resolved again
after the chain from the original body, processed body, and trusted description identities. A
guardrail that removes or replaces the raw carrier therefore cannot clear the request-scoped bit.
Explicit video parts remain sensitive when malformed, and nested carrier objects are inspected under
the same bounds. Rejected-request persistence also inspects the body itself, so forgetting to pass the
bit at one caller does not expose a raw carrier.
The request-scoped bit protects OmniRoute-owned application paths in addition to structured payload
clones. Native priority, round-robin, pinned, and fusion diagnostics; quality-rejection and live-event
logs; terminal combo errors; proxy fast-fail logs; guardrail-registry logs; plugin-dispatcher failure
logs; and stream lifecycle callbacks retain only the constant marker when their detail could echo the
request. Transcript-sensitive requests do not generate Context Relay or Universal Handoff summaries,
and Memory extraction is skipped. Daily-quota routing state uses the server-resolved model rather than
promoting a model token parsed from transcript-sensitive upstream text. These rules affect retention,
not the live request or the functional response returned to the same client.
Inspection is bounded by depth, aggregate entries, trusted identities, description-prefix
occurrences, candidate hashes, and total hashed text. Cycles and binary views are handled without
retaining their contents. If a bound is exceeded, or an enumerable getter/proxy throws during
inspection, the retained copy fails closed to the omission marker; the live request is not changed.
Privacy-redacted Responses artifacts cannot safely reconstruct complete `previous_response_id`
history. `responsesContinuationStore.ts` therefore rejects an artifact carrying the trusted
pipeline-level redaction marker and asks the client to resend full history instead of replaying a
silently incomplete conversation.
Custom guardrails and plugins are privileged, in-process processors: they intentionally receive the
live payload so they can inspect, transform, or block it. OmniRoute supplies the server-owned
`videoTranscriptSensitive` bit, wraps `GuardrailContext.log`, and protects errors emitted by its native
plugin dispatcher. It cannot control a third-party processor that independently writes the live body
to `console`, a file, a database, or the network. Operators must audit such code and treat its own
sinks as outside the OmniRoute-owned retention boundary; clearing or ignoring the bit does not make
the transcript safe to persist.
An advanced caller may provide an already-authorized `audioTranscript` track
for the same video. The fusion seam runs visual and audio observations under
one deadline and abort signal, orders them on a common timeline, collapses

View File

@@ -202,6 +202,10 @@ export type ExecuteInput = {
* this to apply client-format-aware policies such as `</think>` close-marker
* suppression. */
clientResponseFormat?: string | null;
/** True when upstream diagnostics may echo a video transcript. Executors must
* preserve operational responses/errors while omitting those echoes from
* retained logs. */
videoTranscriptSensitive?: boolean;
/** Callback to persist tokens that are proactively refreshed during execution.
* Accepts a partial credentials patch (e.g. `{ accessToken, refreshToken }` or
* `{ testStatus: "expired", isActive: false }`); the caller merges into the

View File

@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { redactVideoTranscriptSensitiveText } from "../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
import type { KeyHealth } from "../services/apiKeyRotator.ts";
import { DefaultExecutor } from "./default.ts";
@@ -45,6 +46,11 @@ function asRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
function retainGlmDiagnostic(error: unknown, input: ExecuteInput): string {
const message = error instanceof Error ? error.message : String(error);
return redactVideoTranscriptSensitiveText(message, input.videoTranscriptSensitive === true);
}
function getEffectiveKey(credentials: ProviderCredentials): string {
const extraKeys = (credentials.providerSpecificData?.extraApiKeys as string[] | undefined) ?? [];
if (credentials.apiKey && credentials.connectionId && extraKeys.length > 0) {
@@ -465,6 +471,7 @@ export class GlmExecutor extends DefaultExecutor {
timeoutMs: STREAM_READINESS_TIMEOUT_MS,
provider: this.provider,
model: input.model,
redactUpstreamDiagnosticForLog: input.videoTranscriptSensitive === true,
log: input.log,
});
response = readiness.response;
@@ -559,7 +566,7 @@ export class GlmExecutor extends DefaultExecutor {
if (!isRetryableGlmFallbackError(error)) throw error;
input.log?.debug?.(
"GLM_FALLBACK",
`${primaryTransport} error (${error instanceof Error ? error.message : String(error)}); trying ${fallbackTransport}`
`${primaryTransport} error (${retainGlmDiagnostic(error, input)}); trying ${fallbackTransport}`
);
}
@@ -572,7 +579,7 @@ export class GlmExecutor extends DefaultExecutor {
if (!primaryResult) throw error;
input.log?.debug?.(
"GLM_FALLBACK",
`${fallbackTransport} fallback failed (${error instanceof Error ? error.message : String(error)}); returning primary response`
`${fallbackTransport} fallback failed (${retainGlmDiagnostic(error, input)}); returning primary response`
);
}

View File

@@ -166,6 +166,10 @@ import {
runWithCasGuard,
} from "../services/tokenRefresh.ts";
import { createRequestLogger } from "../utils/requestLogger.ts";
import {
redactVideoTranscriptSensitiveText,
resolveVideoTranscriptLogSensitivity,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts";
import { summarizeToolSources } from "../utils/toolSources.ts";
import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts";
@@ -524,7 +528,21 @@ export async function handleChatCore({
skipResourcePressureGuard = false,
reasoningTransportFallback = "drop",
managedLease = null,
videoTranscriptSensitive = false,
videoTranscriptDescriptionFingerprints = [],
}) {
videoTranscriptSensitive =
videoTranscriptSensitive ||
resolveVideoTranscriptLogSensitivity({
rawRequestBody: clientRawRequest?.body,
processedBody: body,
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints,
});
const retainedErrorTextForLog = (error: unknown): string =>
redactVideoTranscriptSensitiveText(
error instanceof Error ? error.message : String(error),
videoTranscriptSensitive
);
let { provider, model, extendedContext } = modelInfo;
const resilienceSettings = resolveResilienceSettings(cachedSettings);
if (!skipResourcePressureGuard) {
@@ -637,6 +655,7 @@ export async function handleChatCore({
apiKeyInfo,
headers: clientRawRequest?.headers,
log,
videoTranscriptSensitive,
});
if (pluginGate.blocked === true) {
return {
@@ -721,6 +740,7 @@ export async function handleChatCore({
effectiveServiceTier,
startTime,
log,
videoTranscriptSensitive,
});
if (idempotencyHit) {
return idempotencyHit;
@@ -902,6 +922,8 @@ export async function handleChatCore({
stage: "registered",
correlationId,
sessionTag: conversationId || null,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
}) || generateRequestId();
// Initialize rate limit settings from persisted DB (once, lazy)
@@ -1034,6 +1056,8 @@ export async function handleChatCore({
noLogEnabled,
correlationId,
modelPinned,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
// Resolved conversationId (open-sse/services/conversationTracker.ts) wins when
// present — it's populated for every request now, not just ones where the
// client explicitly sent x-omniroute-session-id. The raw header remains a
@@ -1156,8 +1180,17 @@ export async function handleChatCore({
model,
provider: provider || undefined,
connectionId: connectionId || credentials?.connectionId || undefined,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
});
const pendingScope = { id: pendingRequestId, model, provider, connectionId: pendingConnId };
const pendingScope = {
id: pendingRequestId,
model,
provider,
connectionId: pendingConnId,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
};
const providerRequestCapture = createPreparedRequestLogger(reqLogger, pendingScope);
// 0. Log client raw request (before format conversion)
if (clientRawRequest) {
@@ -1203,6 +1236,7 @@ export async function handleChatCore({
apiKeyId: apiKeyInfo?.id ?? undefined,
cacheDefaultMode: (apiKeyInfo as { cacheDefaultMode?: "legacy" | "bypass" } | null)
?.cacheDefaultMode,
videoTranscriptSensitive,
});
if (cacheHit) {
return cacheHit;
@@ -1482,8 +1516,7 @@ export async function handleChatCore({
} catch (err) {
log?.debug?.(
"COMPRESSION",
"Combo compression override lookup skipped: " +
(err instanceof Error ? err.message : String(err))
"Combo compression override lookup skipped: " + retainedErrorTextForLog(err)
);
}
}
@@ -1492,10 +1525,7 @@ export async function handleChatCore({
const { listCompressionCombos } = await import("../../src/lib/db/compressionCombos.ts");
namedCombos = buildNamedComboLookup(listCompressionCombos());
} catch (err) {
log?.debug?.(
"COMPRESSION",
"Named combos load skipped: " + (err instanceof Error ? err.message : String(err))
);
log?.debug?.("COMPRESSION", "Named combos load skipped: " + retainedErrorTextForLog(err));
}
// Phase 3: per-request override. Unknown values fall through in the resolver (never error).
const compressionHeader = resolveCompressionHeader(clientRawRequest?.headers ?? null);
@@ -1543,8 +1573,7 @@ export async function handleChatCore({
} catch (err) {
log?.debug?.(
"COMPRESSION",
"Default compression combo lookup skipped: " +
(err instanceof Error ? err.message : String(err))
"Default compression combo lookup skipped: " + retainedErrorTextForLog(err)
);
}
}
@@ -1586,10 +1615,7 @@ export async function handleChatCore({
}
}
} catch (err) {
log?.debug?.(
"COMPRESSION",
"Output styles skipped: " + (err instanceof Error ? err.message : String(err))
);
log?.debug?.("COMPRESSION", "Output styles skipped: " + retainedErrorTextForLog(err));
}
}
const compressionInputBody = body as Record<string, unknown>;
@@ -1899,8 +1925,7 @@ export async function handleChatCore({
} catch (err) {
log?.warn?.(
"COMPRESSION",
"Compression pipeline error (non-fatal): " +
(err instanceof Error ? err.message : String(err))
"Compression pipeline error (non-fatal): " + retainedErrorTextForLog(err)
);
}
// --- End Modular Compression Pipeline ---
@@ -1954,7 +1979,10 @@ export async function handleChatCore({
`Combo context limit: ${resolved.limit} (source=${resolved.source})`
);
} catch (err) {
log?.warn?.("CONTEXT", "Failed to resolve combo limits for compression: " + err);
log?.warn?.(
"CONTEXT",
"Failed to resolve combo limits for compression: " + retainedErrorTextForLog(err)
);
}
}
@@ -2432,13 +2460,21 @@ export async function handleChatCore({
try {
const { runOnError } = await import("@/lib/plugins/hooks");
await runOnError(
{ requestId: traceId, body, model, provider, apiKeyInfo, metadata: {} },
{
requestId: traceId,
body,
model,
provider,
apiKeyInfo,
metadata: {},
videoTranscriptSensitive,
},
error instanceof Error ? error : new Error(String(error))
);
} catch (pluginErr) {
log?.debug?.(
"PLUGIN",
`onError hook error (non-fatal): ${pluginErr instanceof Error ? pluginErr.message : String(pluginErr)}`
`onError hook error (non-fatal): ${retainedErrorTextForLog(pluginErr)}`
);
}
@@ -2450,7 +2486,13 @@ export async function handleChatCore({
const message = error?.message || "Invalid request";
const errorType = typeof error?.errorType === "string" ? error.errorType : null;
log?.warn?.("TRANSLATE", `Request translation failed: ${message}`);
log?.warn?.(
"TRANSLATE",
`Request translation failed: ${redactVideoTranscriptSensitiveText(
message,
videoTranscriptSensitive
)}`
);
if (errorType) {
trackPendingRequest(model, provider, connectionId, false);
@@ -2544,8 +2586,7 @@ export async function handleChatCore({
// must never turn an otherwise valid translated request into a 500.
log?.warn?.(
"COMPRESSION",
"Post-translation OmniGlyph skipped: " +
(error instanceof Error ? error.message : String(error))
"Post-translation OmniGlyph skipped: " + retainedErrorTextForLog(error)
);
}
}
@@ -2861,7 +2902,7 @@ export async function handleChatCore({
}).catch((err: unknown): EnforceDecision => {
log?.warn?.(
"QUOTA_SHARE",
`enforceQuotaShare failed; fail-open: ${err instanceof Error ? err.message : String(err)}`
`enforceQuotaShare failed; fail-open: ${retainedErrorTextForLog(err)}`
);
return { kind: "allow" as const };
});
@@ -2903,7 +2944,7 @@ export async function handleChatCore({
// Outer fail-open guard — should not be reached (inner .catch covers it)
log?.warn?.(
"QUOTA_SHARE",
`[quotaShare] enforceQuotaShare unexpected error; fail-open: ${err instanceof Error ? err.message : String(err)}`
`[quotaShare] enforceQuotaShare unexpected error; fail-open: ${retainedErrorTextForLog(err)}`
);
}
}
@@ -2915,7 +2956,7 @@ export async function handleChatCore({
} catch (err) {
log?.warn?.(
"QUOTA_SHARE",
`[quotaShare] could not set soft penalty on candidate: ${err instanceof Error ? err.message : String(err)}`
`[quotaShare] could not set soft penalty on candidate: ${retainedErrorTextForLog(err)}`
);
}
}
@@ -2949,6 +2990,8 @@ export async function handleChatCore({
let onPipelineStreamError: streamFailure.PipelineStreamErrorHandler | null = null;
let onClientDisconnectFinalize:
((event: { reason: string; duration: number }) => boolean) | null = null;
const redactStreamDiagnosticsForLog =
videoTranscriptSensitive || reqLogger.isVideoTranscriptSensitive();
// Create stream controller for disconnect detection
const streamController = createStreamController({
@@ -2980,6 +3023,7 @@ export async function handleChatCore({
clientAbortSignal: clientRawRequest?.signal,
allowCompletedToolHandoffGrace: isCodexResponsesEcho,
clientDisconnectGracePeriodMs: STREAM_DISCONNECT_GRACE_PERIOD_MS,
redactStreamDiagnosticsForLog,
});
const dedupRequestBody = { ...translatedBody, model: `${provider}/${model}`, stream };
@@ -3129,6 +3173,7 @@ export async function handleChatCore({
userAgent
),
clientResponseFormat,
videoTranscriptSensitive,
onCredentialsRefreshed,
skipUpstreamRetry,
contextEditing: { enabled: contextEditingEnabled },
@@ -3163,7 +3208,7 @@ export async function handleChatCore({
invalidateCodexQuotaCache(String(attemptConnectionId));
}
} catch (err) {
const errMessage = err instanceof Error ? err.message : String(err);
const errMessage = retainedErrorTextForLog(err);
log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`);
}
}
@@ -3445,6 +3490,7 @@ export async function handleChatCore({
userAgent
),
clientResponseFormat,
videoTranscriptSensitive,
onCredentialsRefreshed,
skipUpstreamRetry,
contextEditing: { enabled: contextEditingEnabled },
@@ -3677,7 +3723,9 @@ export async function handleChatCore({
} catch (err) {
// Fail-open at Tier 2: Tier 1 already enforced the model/global limit pre-dispatch.
// A transient counter read error here must not break an otherwise-valid request.
log?.warn?.("TOKEN_LIMIT", "Tier 2 token-limit check failed; allowing request", { err });
log?.warn?.("TOKEN_LIMIT", "Tier 2 token-limit check failed; allowing request", {
error: retainedErrorTextForLog(err),
});
}
}
@@ -3696,7 +3744,9 @@ export async function handleChatCore({
);
}
} catch (err) {
log?.warn?.("GEMINI_RATE_LIMIT", "Pre-dispatch TPM check failed; allowing request", { err });
log?.warn?.("GEMINI_RATE_LIMIT", "Pre-dispatch TPM check failed; allowing request", {
error: retainedErrorTextForLog(err),
});
}
}
@@ -3846,7 +3896,12 @@ export async function handleChatCore({
failureStatus,
upstreamErrorCode || (error instanceof Error && error.name ? error.name : "upstream_error")
);
console.log(`${COLORS.red}[ERROR] ${failureMessage}${COLORS.reset}`);
console.log(
`${COLORS.red}[ERROR] ${redactVideoTranscriptSensitiveText(
failureMessage,
videoTranscriptSensitive
)}${COLORS.reset}`
);
if (stream && upstreamErrorCode) {
const result = createStreamingErrorResult(
failureStatus,
@@ -3983,6 +4038,7 @@ export async function handleChatCore({
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent),
clientResponseFormat,
videoTranscriptSensitive,
onCredentialsRefreshed,
skipUpstreamRetry: isCombo,
contextEditing: { enabled: contextEditingEnabled },
@@ -4012,9 +4068,13 @@ export async function handleChatCore({
// executor throw). Don't swallow — the operator-visible signal "the user
// saw 401 even though auth was actually fixed" is much more confusing
// than the original 401 alone. Surface at error level with sanitization.
const retainedRetryError = redactVideoTranscriptSensitiveText(
sanitizeErrorMessage(retryErr),
videoTranscriptSensitive
);
log?.error?.(
"TOKEN",
`${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}`
`${provider?.toUpperCase()} | retry after refresh failed: ${retainedRetryError}`
);
}
} else {
@@ -4131,6 +4191,11 @@ export async function handleChatCore({
if (signatureRecovery.succeeded) break providerFailure;
const retainedProviderMessage = redactVideoTranscriptSensitiveText(
message,
videoTranscriptSensitive
);
// #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check
// sends `max_tokens: 1`): the model burns the whole budget on thinking, and
// some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty
@@ -4153,7 +4218,7 @@ export async function handleChatCore({
});
log?.warn?.(
"PROBE",
`Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"`
`Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${retainedProviderMessage}"`
);
break providerFailure;
}
@@ -4182,7 +4247,7 @@ export async function handleChatCore({
{
testStatus: "banned",
isActive: false,
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4213,7 +4278,7 @@ export async function handleChatCore({
) {
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4226,7 +4291,7 @@ export async function handleChatCore({
{
testStatus: "deactivated",
isActive: false,
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4250,7 +4315,7 @@ export async function handleChatCore({
errorConnectionId,
{
testStatus: "credits_exhausted",
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4298,7 +4363,7 @@ export async function handleChatCore({
rateLimitedUntil: kimiRateLimitResetAt,
backoffLevel: 0,
lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4328,7 +4393,7 @@ export async function handleChatCore({
errorConnectionId,
{
testStatus: "credits_exhausted",
lastError: message,
lastError: retainedProviderMessage,
lastErrorType: errorType,
errorCode: String(statusCode),
},
@@ -4344,14 +4409,14 @@ export async function handleChatCore({
// Normal 401 (token/session auth issue): keep account active for refresh/re-auth.
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
} else if (errorType === PROVIDER_ERROR_TYPES.OAUTH_INVALID_TOKEN) {
// OAuth 401 with invalid credentials - token refresh can recover
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4361,7 +4426,7 @@ export async function handleChatCore({
// Cloud Code 403 with stale project: not a ban, keep account active.
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
console.warn(
@@ -4377,7 +4442,7 @@ export async function handleChatCore({
const geoCooldownMs = COOLDOWN_MS.geoBlocked ?? 24 * 60 * 60 * 1000;
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
// T-PROBE: the 24h exclusion is a routing mutation — a probe must
@@ -4402,7 +4467,7 @@ export async function handleChatCore({
const byopCooldownMs = COOLDOWN_MS.gcpProjectRequired ?? 24 * 60 * 60 * 1000;
await updateProviderConnection(errorConnectionId, {
lastErrorType: errorType,
lastError: message,
lastError: retainedProviderMessage,
errorCode: statusCode,
});
try {
@@ -4448,7 +4513,13 @@ export async function handleChatCore({
}).catch(() => {});
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
const retainedErrMsg = formatProviderError(
new Error(retainedProviderMessage),
provider,
model,
statusCode
);
console.log(`${COLORS.red}[ERROR] ${retainedErrMsg}${COLORS.reset}`);
// Log Antigravity retry time if available
if (retryAfterMs && provider === "antigravity") {
@@ -4775,12 +4846,11 @@ export async function handleChatCore({
}
}
} catch (retryErr) {
log?.warn?.(
"RETRY",
`clinepass retry failed: ${
retryErr instanceof Error ? retryErr.message : String(retryErr)
}`
const retainedRetryError = redactVideoTranscriptSensitiveText(
retryErr instanceof Error ? retryErr.message : String(retryErr),
videoTranscriptSensitive
);
log?.warn?.("RETRY", `clinepass retry failed: ${retainedRetryError}`);
}
}
if (envError) {
@@ -4999,6 +5069,7 @@ export async function handleChatCore({
cacheReasoningFromAssistantMessage(msg, provider, model, {
scope: reasoningCacheScope,
historyMessages: Array.isArray(historyMessages) ? historyMessages : [],
videoTranscriptSensitive,
});
}
} catch {
@@ -5050,12 +5121,18 @@ export async function handleChatCore({
);
if (memoryOwnerId && memorySettings?.enabled && memorySettings.maxTokens > 0) {
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
const requestMemoryText = extractMemoryTextFromRequestBody(
body as Record<string, unknown>,
videoTranscriptSensitive,
{ trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints }
);
if (requestMemoryText) {
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
}
const memoryText = extractMemoryTextFromResponse(memoryExtractionResponse);
const memoryText = videoTranscriptSensitive
? ""
: extractMemoryTextFromResponse(memoryExtractionResponse);
if (memoryText) {
extractFacts(memoryText, memoryOwnerId, pipelineSessionId);
}
@@ -5095,6 +5172,7 @@ export async function handleChatCore({
provider,
responsePayloadFormat,
clientResponseFormat,
videoTranscriptSensitive,
});
const postCallGuardrails = await guardrailRegistry.runPostCallHooks(
translatedResponse,
@@ -5136,7 +5214,10 @@ export async function handleChatCore({
}
log?.warn?.(
"GUARDRAIL",
`Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}`
`Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${redactVideoTranscriptSensitiveText(
guardrailMessage,
videoTranscriptSensitive
)}`
);
finalizePendingScope(pendingScope, {
providerResponse: responseBody,
@@ -5239,6 +5320,7 @@ export async function handleChatCore({
apiKeyId: apiKeyInfo?.id ?? undefined,
usage,
log,
videoTranscriptSensitive,
});
// ── Phase 9.2: Save for idempotency ──
@@ -5318,6 +5400,7 @@ export async function handleChatCore({
apiKeyInfo,
headers: clientRawRequest?.headers,
response: { status: 200, data: translatedResponse },
videoTranscriptSensitive,
});
// Routing event (feedback foundation) — fire-and-forget, cheap.
@@ -5393,6 +5476,7 @@ export async function handleChatCore({
provider,
model,
log,
redactUpstreamDiagnosticForLog: videoTranscriptSensitive,
});
if (streamReadiness.ok === false) {
const { response: failureResponse, reason } = streamReadiness;
@@ -5524,6 +5608,7 @@ export async function handleChatCore({
cacheReasoningFromAssistantMessage(msg, provider, model, {
scope: reasoningCacheScope,
historyMessages: Array.isArray(historyMessages) ? historyMessages : [],
videoTranscriptSensitive,
});
}
} catch {
@@ -5556,6 +5641,7 @@ export async function handleChatCore({
status: normalizedStreamStatus,
error: streamError,
errorCode: streamErrorCode,
videoTranscriptSensitive,
});
// Track cache token metrics for streaming responses
@@ -5678,14 +5764,20 @@ export async function handleChatCore({
memorySettings.maxTokens > 0 &&
streamStatus === 200
) {
const requestMemoryText = extractMemoryTextFromRequestBody(body as Record<string, unknown>);
const requestMemoryText = extractMemoryTextFromRequestBody(
body as Record<string, unknown>,
videoTranscriptSensitive,
{ trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints }
);
if (requestMemoryText) {
extractFacts(requestMemoryText, memoryOwnerId, pipelineSessionId);
}
const streamedMemoryText = extractMemoryTextFromResponse(
(streamResponseBody ?? null) as Record<string, unknown> | null
);
const streamedMemoryText = videoTranscriptSensitive
? ""
: extractMemoryTextFromResponse(
(streamResponseBody ?? null) as Record<string, unknown> | null
);
if (streamedMemoryText) {
extractFacts(streamedMemoryText, memoryOwnerId, pipelineSessionId);
}
@@ -5702,6 +5794,7 @@ export async function handleChatCore({
apiKeyId: apiKeyInfo?.id ?? undefined,
streamUsage,
log,
videoTranscriptSensitive,
});
// Plugin onStreamComplete hook — fire-and-forget, fail-open (#9571)
@@ -5770,7 +5863,8 @@ export async function handleChatCore({
// openai-responses → openai translation still wants the namespace identity
// map for #7936-style round-trip closure when the client also speaks
// Responses (Codex CLI).
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
} else if (needsTranslation(targetFormat, clientResponseFormat)) {
// Standard translation for other providers
@@ -5800,7 +5894,8 @@ export async function handleChatCore({
clientResponseFormat,
}),
customToolNames,
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
} else {
log?.debug?.("STREAM", `Standard passthrough mode`);
@@ -5815,7 +5910,8 @@ export async function handleChatCore({
apiKeyInfo,
handleStreamFailure,
clientResponseFormat,
requestToolIdentityMap
requestToolIdentityMap,
redactStreamDiagnosticsForLog
);
}
@@ -5827,6 +5923,7 @@ export async function handleChatCore({
clientRawRequestHeaders: clientRawRequest?.headers,
clientResponseFormat,
echoModel,
redactStreamDiagnosticsForLog,
responseHeaders,
});
@@ -5842,6 +5939,7 @@ export async function handleChatCore({
apiKeyInfo,
headers: clientRawRequest?.headers,
response: { status: 200, streamed: true },
videoTranscriptSensitive,
});
return {

View File

@@ -15,6 +15,10 @@ import { logAuditEvent } from "@/lib/compliance";
import { emit } from "@/lib/events/eventBus";
import type { RequestCompletedPayload, RequestFailedPayload } from "@/lib/events/types";
import { saveCallLog } from "@/lib/usageDb";
import {
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
VIDEO_TRANSCRIPT_REDACTION_PIPELINE_KEY,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { FORMATS } from "../../translator/formats.ts";
import { takeEarlyKeepaliveBytes } from "../../utils/earlyKeepaliveByteBuffer.ts";
import { cloneBoundedChatLogPayload, truncateForLog } from "./logTruncation.ts";
@@ -68,7 +72,13 @@ export type PersistAttemptLogsContext = {
model: string | null | undefined;
skillRequestId: string;
detailedLoggingEnabled: boolean;
reqLogger: { getPipelinePayloads?: () => Record<string, unknown> | undefined } | null | undefined;
reqLogger:
| {
getPipelinePayloads?: () => Record<string, unknown> | undefined;
isVideoTranscriptSensitive?: () => boolean;
}
| null
| undefined;
pendingRequestId: unknown;
clientRawRequest: { endpoint?: string } | null | undefined;
requestedModel: unknown;
@@ -89,12 +99,34 @@ export type PersistAttemptLogsContext = {
* explicitly present (never synthesized from skillRequestId) — persisted as call_logs.session_tag
* for per-session cost attribution. */
sessionTag?: string | null;
/** Trusted request state derived from the Video Bridge guardrail result. */
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of transcript descriptions generated by the guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
};
function toConnectionId(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function omitSensitivePipelineResponse(value: unknown): Record<string, unknown> {
const record =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
return {
...(typeof record.timestamp === "string" ? { timestamp: record.timestamp } : {}),
...(typeof record.status === "number" && Number.isFinite(record.status)
? { status: record.status }
: {}),
...(record.headers !== undefined ? { headers: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } : {}),
...(record.statusText !== undefined
? { statusText: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER }
: {}),
body: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
};
}
function buildAccountRotationMeta(
provider: string | null | undefined,
initialConnectionId: string | null,
@@ -204,6 +236,8 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
correlationId,
modelPinned,
sessionTag,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
} = ctx;
const initialConnectionId = toConnectionId(connectionId);
const finalConnectionId = toConnectionId(credentials?.connectionId) || initialConnectionId;
@@ -212,8 +246,21 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
initialConnectionId,
finalConnectionId
);
const transcriptSensitive =
videoTranscriptSensitive === true || reqLogger?.isVideoTranscriptSensitive?.() === true;
const descriptionLogContext = {
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints ?? [],
};
const providerWarnings = extractProviderWarnings(providerResponse, clientResponse, responseBody);
const detectedProviderWarnings = extractProviderWarnings(
providerResponse,
clientResponse,
responseBody
);
const providerWarnings =
transcriptSensitive && detectedProviderWarnings.length > 0
? [VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER]
: detectedProviderWarnings;
if (providerWarnings.length > 0) {
logAuditEvent({
action: "provider.warning",
@@ -227,6 +274,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
model,
connectionId: finalConnectionId,
httpStatus: status,
warningCount: detectedProviderWarnings.length,
warnings: providerWarnings,
},
});
@@ -273,8 +321,51 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
};
}
}
if (transcriptSensitive) {
pipelinePayloads[VIDEO_TRANSCRIPT_REDACTION_PIPELINE_KEY] = true;
for (const requestStage of [
"clientRawRequest",
"openaiRequest",
"providerRequest",
] as const) {
if (pipelinePayloads[requestStage] !== undefined) {
pipelinePayloads[requestStage] = cloneBoundedChatLogPayload(
pipelinePayloads[requestStage],
0,
requestStage !== "clientRawRequest" ? descriptionLogContext : {}
) as Record<string, unknown>;
}
}
if (pipelinePayloads.providerResponse !== undefined || providerResponse !== undefined) {
pipelinePayloads.providerResponse = omitSensitivePipelineResponse(
pipelinePayloads.providerResponse ?? providerResponse
);
}
if (pipelinePayloads.clientResponse !== undefined || clientResponse !== undefined) {
pipelinePayloads.clientResponse = omitSensitivePipelineResponse(
pipelinePayloads.clientResponse ?? clientResponse
);
}
if (pipelinePayloads.error !== undefined || error) {
const errorRecord =
pipelinePayloads.error && typeof pipelinePayloads.error === "object"
? pipelinePayloads.error
: {};
pipelinePayloads.error = {
...(typeof errorRecord.timestamp === "string"
? { timestamp: errorRecord.timestamp }
: {}),
message: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
};
}
delete pipelinePayloads.streamChunks;
}
}
const responseBodyForLog = transcriptSensitive
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: responseBody;
saveCallLog({
id: pendingRequestId,
method: "POST",
@@ -290,10 +381,12 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
attachLogMeta(truncateForLog(body as Record<string, unknown>), {
...accountRotationMeta,
claudePromptCache: claudeCacheMeta,
})
}),
0,
descriptionLogContext
),
responseBody: cloneBoundedChatLogPayload(
attachLogMeta(truncateForLog(responseBody as Record<string, unknown>), {
attachLogMeta(truncateForLog(responseBodyForLog as Record<string, unknown>), {
...accountRotationMeta,
claudePromptCache: claudeCacheMeta
? {
@@ -305,7 +398,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
claudePromptCacheUsage: claudeCacheUsageMeta,
})
),
error: error || null,
error: transcriptSensitive && error ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : error || null,
sourceFormat,
targetFormat,
comboName,
@@ -332,7 +425,7 @@ export function persistAttemptLogs(args: PersistAttemptLogsArgs, ctx: PersistAtt
const lifecycle = resolveRequestLifecycleEvent({
traceId,
status,
error,
error: transcriptSensitive && error ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : error,
model,
provider,
comboName,

View File

@@ -17,8 +17,10 @@
*/
import { getExecutor } from "../../executors/index.ts";
import type { ExecuteInput } from "../../executors/base.ts";
import { isCliproxyapiDeepModeEnabled } from "../../executors/cliproxyapi.ts";
import { isDarioDeepModeEnabled } from "../../executors/dario.ts";
import { redactVideoTranscriptSensitiveText } from "../../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
import { getCachedSettings } from "@/lib/db/readCache";
import { getUpstreamProxyConfigCached } from "./comboContextCache.ts";
import type { FallbackBackend } from "@/lib/db/upstreamProxy";
@@ -48,6 +50,11 @@ function parseFallbackCodes(raw: unknown): number[] | null {
return parsed.length > 0 ? parsed : null;
}
function retainExecutorDiagnostic(error: unknown, input: ExecuteInput): string {
const message = error instanceof Error ? error.message : String(error);
return redactVideoTranscriptSensitiveText(message, input.videoTranscriptSensitive === true);
}
/**
* Reads the CLIProxyAPI-related settings shared by both the direct
* `mode: "cliproxyapi"` passthrough leg and the `mode: "fallback"` retry leg:
@@ -155,25 +162,20 @@ export async function resolveExecutorWithProxy(
const isRetryableStatus = (s: number) => fallbackCodes.includes(s) || s === 0;
const wrapper = Object.create(nativeExec);
wrapper.execute = async (input: {
model: string;
body: unknown;
stream: boolean;
credentials: unknown;
signal?: AbortSignal | null;
log?: unknown;
upstreamExtraHeaders?: Record<string, string> | null;
}) => {
wrapper.execute = async (input: ExecuteInput) => {
let result;
try {
result = await nativeExec.execute(input);
} catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);
log?.info?.("UPSTREAM_PROXY", `${prov} native error (${errMsg}), retrying via ${backendLabel}`);
const errMsg = retainExecutorDiagnostic(err, input);
log?.info?.(
"UPSTREAM_PROXY",
`${prov} native error (${errMsg}), retrying via ${backendLabel}`
);
try {
return await proxyExec.execute(input);
} catch (proxyErr) {
const proxyMsg = proxyErr instanceof Error ? proxyErr.message : String(proxyErr);
const proxyMsg = retainExecutorDiagnostic(proxyErr, input);
log?.error?.("UPSTREAM_PROXY", `${prov} ${backendLabel} fallback also failed: ${proxyMsg}`);
throw proxyErr;
}
@@ -189,7 +191,7 @@ export async function resolveExecutorWithProxy(
try {
return await proxyExec.execute(input);
} catch (proxyErr) {
const proxyMsg = proxyErr instanceof Error ? proxyErr.message : String(proxyErr);
const proxyMsg = retainExecutorDiagnostic(proxyErr, input);
log?.error?.("UPSTREAM_PROXY", `${prov} ${backendLabel} fallback also failed: ${proxyMsg}`);
throw proxyErr;
}

View File

@@ -124,6 +124,7 @@ export async function checkIdempotencyCache({
effectiveServiceTier,
startTime,
log,
videoTranscriptSensitive,
}: {
clientRawRequest: IdempotencyRequest;
provider: string;
@@ -132,7 +133,9 @@ export async function checkIdempotencyCache({
effectiveServiceTier: EffectiveServiceTier | null | undefined;
startTime: number;
log: LoggerLike;
videoTranscriptSensitive: boolean;
}): Promise<{ hit: { success: true; response: Response } | null; idempotencyKey: string | null }> {
if (videoTranscriptSensitive) return { hit: null, idempotencyKey: null };
// NEXA fusion-idempotency fix: namespace the raw header key (see composeIdempotencyKey).
const rawIdempotencyKey = getIdempotencyKey(clientRawRequest?.headers);
const idempotencyKey = composeIdempotencyKey({

View File

@@ -5,6 +5,10 @@ import {
getChatLogMaxObjectKeys,
getChatLogMaxBodyBytes,
} from "@/lib/logEnv";
import {
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { estimateSizeFast } from "../../utils/estimateSize.ts";
export const MEMORY_EXTRACTION_TEXT_LIMIT = 64 * 1024;
@@ -22,7 +26,7 @@ export function truncateChatLogText(value: string): string {
return `${head}\n[...truncated ${value.length - limit} chars...]\n${tail}`;
}
export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
function cloneBoundedChatLogPayloadValue(value: unknown, depth = 0): unknown {
if (value === null || value === undefined) return value;
if (typeof value === "string") return truncateChatLogText(value);
if (typeof value !== "object") return value;
@@ -32,7 +36,7 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
if (Array.isArray(value)) {
const retained = value.length > maxTailItems ? value.slice(-maxTailItems) : value;
const cloned = retained.map((item) => cloneBoundedChatLogPayload(item, depth + 1));
const cloned = retained.map((item) => cloneBoundedChatLogPayloadValue(item, depth + 1));
if (value.length > maxTailItems) {
return [
{
@@ -47,10 +51,11 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
}
const result: Record<string, unknown> = {};
const entries = Object.entries(value as Record<string, unknown>);
const record = value as Record<string, unknown>;
const entries = Object.entries(record);
const maxKeys = getChatLogMaxObjectKeys();
for (const [key, item] of maxKeys > 0 ? entries.slice(0, maxKeys) : entries) {
result[key] = cloneBoundedChatLogPayload(item, depth + 1);
result[key] = cloneBoundedChatLogPayloadValue(item, depth + 1);
}
if (maxKeys > 0 && entries.length > maxKeys) {
result._omniroute_truncated_keys = entries.length - maxKeys;
@@ -58,6 +63,16 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
return result;
}
export function cloneBoundedChatLogPayload(
value: unknown,
depth = 0,
descriptionContext: VideoTranscriptLogContext = {}
): unknown {
const transcriptSafeValue =
depth === 0 ? omitVideoTranscriptForLog(value, descriptionContext) : value;
return cloneBoundedChatLogPayloadValue(transcriptSafeValue, depth);
}
/**
* Truncate a large object for logging. If its JSON representation exceeds
* getChatLogMaxBodyBytes() (default 1MB; CHAT_LOG_MAX_BODY_KB env override),

View File

@@ -1,5 +1,39 @@
import {
containsVideoTranscriptForLog,
omitVideoTranscriptDerivedTextForMemory,
type VideoTranscriptLogContext,
} from "../../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
import { capMemoryExtractionText, MEMORY_EXTRACTION_TEXT_LIMIT } from "./logTruncation.ts";
function normalizeMemoryInputText(value: unknown, context: VideoTranscriptLogContext = {}): string {
if (typeof value !== "string") return "";
return omitVideoTranscriptDerivedTextForMemory(value, context).trim();
}
function extractMemoryTextPart(
part: Record<string, unknown>,
transcriptSensitive: boolean,
context: VideoTranscriptLogContext
): string {
try {
const rawText = typeof part?.text === "string" ? part.text : "";
if (!rawText) return "";
const retainedText = normalizeMemoryInputText(rawText, context);
if (!retainedText) return "";
if (
transcriptSensitive &&
containsVideoTranscriptForLog(part, context) &&
retainedText === rawText.trim()
) {
return "";
}
return retainedText;
} catch {
return "";
}
}
export function extractMemoryTextFromResponse(
response: Record<string, unknown> | null | undefined
): string {
@@ -29,9 +63,16 @@ export function extractMemoryTextFromResponse(
}
export function extractMemoryTextFromRequestBody(
body: Record<string, unknown> | null | undefined
body: Record<string, unknown> | null | undefined,
videoTranscriptSensitive = false,
context: VideoTranscriptLogContext = {}
): string {
if (!body || typeof body !== "object") return "";
// Re-check the structured body at the sink boundary. The explicit bit covers
// processed requests whose raw carrier was already replaced; trusted hashes
// identify only descriptions emitted by a modified Video Bridge guardrail.
const transcriptSensitive =
videoTranscriptSensitive || containsVideoTranscriptForLog(body, context);
const messages = Array.isArray(body.messages) ? body.messages : null;
if (messages && messages.length > 0) {
@@ -39,18 +80,16 @@ export function extractMemoryTextFromRequestBody(
const msg = messages[i] as Record<string, unknown>;
if (msg?.role !== "user") continue;
if (typeof msg.content === "string" && msg.content.trim().length > 0) {
return capMemoryExtractionText(msg.content.trim());
const messageText = normalizeMemoryInputText(msg.content, context);
if (messageText) {
return capMemoryExtractionText(messageText);
}
if (Array.isArray(msg.content)) {
const text = msg.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
return "";
})
.map((part: Record<string, unknown>) =>
extractMemoryTextPart(part, transcriptSensitive, context)
)
.filter(Boolean)
.join("\n")
.trim();
@@ -68,17 +107,15 @@ export function extractMemoryTextFromRequestBody(
if (role && role !== "user") continue;
if (itemType && itemType !== "message") continue;
if (typeof item?.content === "string" && item.content.trim()) {
return capMemoryExtractionText(item.content.trim());
const itemText = normalizeMemoryInputText(item?.content, context);
if (itemText) {
return capMemoryExtractionText(itemText);
}
if (Array.isArray(item?.content)) {
const text = item.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
return "";
})
.map((part: Record<string, unknown>) =>
extractMemoryTextPart(part, transcriptSensitive, context)
)
.filter(Boolean)
.join("\n")
.trim();
@@ -96,15 +133,14 @@ export function extractMemoryTextFromRequestBody(
if (role && role !== "user") return "";
if (itemType && itemType !== "message") return "";
if (typeof item?.content === "string") return item.content.trim();
if (typeof item?.content === "string") {
return normalizeMemoryInputText(item.content, context);
}
if (Array.isArray(item?.content)) {
return item.content
.map((part: Record<string, unknown>) => {
if (typeof part?.text === "string") return part.text.trim();
if (part?.type === "input_text" && typeof part?.text === "string")
return part.text.trim();
return "";
})
.map((part: Record<string, unknown>) =>
extractMemoryTextPart(part, transcriptSensitive, context)
)
.filter(Boolean)
.join("\n")
.trim();

View File

@@ -9,6 +9,8 @@
* byte-identical to the previous inline block.
*/
import { redactVideoTranscriptSensitiveText } from "@/lib/guardrails/videoTranscriptLogRedaction";
type LoggerLike =
{ info?: (...args: unknown[]) => void; debug?: (...args: unknown[]) => void } | null | undefined;
@@ -25,6 +27,7 @@ export async function runPluginOnRequestHook(args: {
apiKeyInfo: unknown;
headers?: Record<string, string | string[] | undefined>;
log?: LoggerLike;
videoTranscriptSensitive?: boolean;
}): Promise<PluginOnRequestGate> {
try {
const { runOnRequest } = await import("@/lib/plugins/hooks");
@@ -36,6 +39,7 @@ export async function runPluginOnRequestHook(args: {
apiKeyInfo: args.apiKeyInfo,
headers: args.headers,
metadata: {},
videoTranscriptSensitive: args.videoTranscriptSensitive === true,
};
const pluginResult = await runOnRequest(pluginCtx);
if (pluginResult?.blocked) {
@@ -55,10 +59,11 @@ export async function runPluginOnRequestHook(args: {
}
return { blocked: false, body: pluginResult?.body };
} catch (pluginErr) {
args.log?.debug?.(
"PLUGIN",
`onRequest hook error (non-fatal): ${pluginErr instanceof Error ? pluginErr.message : String(pluginErr)}`
const retainedPluginError = redactVideoTranscriptSensitiveText(
pluginErr instanceof Error ? pluginErr.message : String(pluginErr),
args.videoTranscriptSensitive === true
);
args.log?.debug?.("PLUGIN", `onRequest hook error (non-fatal): ${retainedPluginError}`);
return { blocked: false };
}
}

View File

@@ -26,6 +26,7 @@ export async function runPluginOnResponseHook(args: {
apiKeyInfo: unknown;
headers?: Record<string, string | string[] | undefined>;
response: PluginOnResponsePayload;
videoTranscriptSensitive?: boolean;
}): Promise<void> {
try {
const { runOnResponse } = await import("@/lib/plugins/hooks");
@@ -38,6 +39,7 @@ export async function runPluginOnResponseHook(args: {
apiKeyInfo: args.apiKeyInfo,
headers: args.headers,
metadata: {},
videoTranscriptSensitive: args.videoTranscriptSensitive === true,
},
args.response
).catch(() => {});

View File

@@ -36,6 +36,7 @@ export function buildPostCallGuardrailContext(
provider: string | null | undefined;
responsePayloadFormat: unknown;
clientResponseFormat: unknown;
videoTranscriptSensitive?: boolean;
},
resolveDisabledGuardrails: typeof defaultResolveDisabled = defaultResolveDisabled
): GuardrailContext {
@@ -57,5 +58,6 @@ export function buildPostCallGuardrailContext(
sourceFormat: optionalString(args.responsePayloadFormat),
stream: false,
targetFormat: optionalString(args.clientResponseFormat),
videoTranscriptSensitive: args.videoTranscriptSensitive === true,
};
}

View File

@@ -1,8 +1,4 @@
import {
generateSignature,
getCachedResponse,
isCacheableForRead,
} from "@/lib/semanticCache";
import { generateSignature, getCachedResponse, isCacheableForRead } from "@/lib/semanticCache";
import { calculateCost } from "@/lib/usage/costCalculator";
import { trackPendingRequest } from "@/lib/usageDb";
import { synthesizeOpenAiSseFromJson } from "../../utils/jsonToSse.ts";
@@ -25,6 +21,7 @@ export async function checkSemanticCache({
persistAttemptLogs,
apiKeyId,
cacheDefaultMode,
videoTranscriptSensitive,
}: {
semanticCacheEnabled: boolean;
// Only the fields this read path actually touches are named; everything else
@@ -42,7 +39,9 @@ export async function checkSemanticCache({
persistAttemptLogs: (args: unknown) => void;
apiKeyId?: string | null;
cacheDefaultMode?: "legacy" | "bypass" | null;
videoTranscriptSensitive: boolean;
}) {
if (videoTranscriptSensitive) return null;
// Per-key bypass: skip cache lookup entirely when the API key opts out.
if (cacheDefaultMode === "bypass") return null;
if (semanticCacheEnabled && isCacheableForRead(body, clientRawRequest?.headers)) {

View File

@@ -50,10 +50,12 @@ export function storeSemanticCacheResponse(
apiKeyId?: string;
usage?: UsageLike;
log?: LoggerLike;
videoTranscriptSensitive: boolean;
},
deps: SemanticCacheStoreDeps = DEFAULT_DEPS
): void {
if (
args.videoTranscriptSensitive ||
!args.enabled ||
!deps.isCacheableForWrite(args.body, args.headers) ||
!deps.isSmallEnoughForSemanticCache(args.translatedResponse)

View File

@@ -68,6 +68,7 @@ export function assembleStreamingPipeline(
clientRawRequestHeaders: HeadersLike;
clientResponseFormat: Parameters<typeof defaultShape>[0];
echoModel: string | null | undefined;
redactStreamDiagnosticsForLog?: boolean;
responseHeaders: Record<string, string>;
},
deps: StreamingPipelineDeps = DEFAULT_DEPS
@@ -83,7 +84,8 @@ export function assembleStreamingPipeline(
let piiStream = deps.pipeWithDisconnect(
args.providerResponse,
args.transformStream,
args.streamController
args.streamController,
{ redactStreamDiagnosticsForLog: args.redactStreamDiagnosticsForLog }
);
if (typeof args.createPiiTransform === "function") {
piiStream = piiStream.pipeThrough((args.createPiiTransform as () => TransformStream)());

View File

@@ -49,6 +49,7 @@ interface StreamingCacheArgs {
apiKeyId?: string;
streamUsage?: Record<string, unknown> | null;
log?: LoggerLike;
videoTranscriptSensitive: boolean;
}
function streamTokensSaved(streamUsage: Record<string, unknown> | null | undefined): number {
@@ -87,6 +88,7 @@ export function storeStreamingSemanticCacheResponse(
deps: StreamingSemanticCacheStoreDeps = DEFAULT_DEPS
): void {
if (
args.videoTranscriptSensitive ||
!args.enabled ||
args.streamStatus !== 200 ||
!args.streamResponseBody ||

View File

@@ -63,6 +63,10 @@ import {
} from "../../src/lib/db/contextHandoffs.ts";
import { extractSessionAffinityKey } from "@/sse/services/auth";
import { getHiddenModelsByProvider } from "@/models";
import {
containsVideoTranscriptForLog,
redactVideoTranscriptSensitiveText,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { resolveModelLockoutSettings } from "../../src/lib/resilience/modelLockoutSettings";
import { fetchCodexQuota } from "./codexQuotaFetcher.ts";
import { evaluateQuotaCutoff, getQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
@@ -702,7 +706,15 @@ export async function resolveTargetTimeoutMsForTarget(
*/
export async function handleComboChat(options: HandleComboChatOptions): Promise<Response> {
const traceInvocationId = options.invocationId ?? createInvocationId();
const response = await handleComboChatInner({ ...options, invocationId: traceInvocationId });
// Re-check a still-structured body at the service boundary. The explicit bit remains
// authoritative when the caller already ran Video Bridge and replaced the raw carrier.
const videoTranscriptSensitive =
options.videoTranscriptSensitive === true || containsVideoTranscriptForLog(options.body);
const response = await handleComboChatInner({
...options,
invocationId: traceInvocationId,
videoTranscriptSensitive,
});
response.headers.set("X-OmniRoute-Combo-Trace", traceInvocationId);
const trace = getComboTrace(traceInvocationId);
options.log.info(
@@ -732,6 +744,8 @@ async function handleComboChatInner({
sourceFormat = null,
endpointPath = null,
requestHeaders = null,
videoTranscriptSensitive = false,
videoTranscriptDescriptionFingerprints = [],
invocationId,
}: HandleComboChatOptions): Promise<Response> {
const comboCtx = createComboContext({ body, combo, settings, relayOptions, log });
@@ -783,6 +797,7 @@ async function handleComboChatInner({
handleSingleModelWithTimeout,
log,
hiddenModelsByProvider,
videoTranscriptSensitive,
});
if (pinnedDispatch) return pinnedDispatch;
}
@@ -805,6 +820,8 @@ async function handleComboChatInner({
signal,
apiKeyAllowedConnections,
hiddenModelsByProvider,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
perTargetAdmission,
deferContextOverflowWhenCompressible,
compressionExclusions,
@@ -861,6 +878,8 @@ async function handleComboChatInner({
signal,
apiKeyAllowedConnections,
hiddenModelsByProvider,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
perTargetAdmission,
deferContextOverflowWhenCompressible,
compressionExclusions,
@@ -889,6 +908,7 @@ async function handleComboChatInner({
allCombos,
signal,
hiddenModelsByProvider,
videoTranscriptSensitive,
clientManagedResponsesContext,
deferContextOverflowWhenCompressible,
compressionExclusions,
@@ -1681,9 +1701,13 @@ async function handleComboChatInner({
releaseQualityClone(qualityClone, result, quality);
if (!quality.valid) {
releaseRejectedQualityResponse(qualityClone, result);
const retainedQualityReason = redactVideoTranscriptSensitiveText(
quality.reason || "upstream response failed quality validation",
videoTranscriptSensitive
);
log.warn(
"COMBO",
`Model ${modelStr} returned 200 but failed quality check: ${quality.reason}`
`Model ${modelStr} returned 200 but failed quality check: ${retainedQualityReason}`
);
// #6692: a quality-rejected 200 never marks the connection row
// unhealthy, so the sticky pin's lazy headroom recheck would never
@@ -1736,7 +1760,7 @@ async function handleComboChatInner({
targetIndex: i,
provider,
model: modelStr,
error: `Quality: ${quality.reason}`,
error: `Quality: ${retainedQualityReason}`,
latencyMs: Date.now() - startTime,
});
observeFailure(false, target.executionKey);
@@ -1869,6 +1893,8 @@ async function handleComboChatInner({
prevModel,
currModel: modelStr,
universalConfig: universalHandoffConfig,
videoTranscriptSensitive,
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints,
handleSingleModel: handleSingleModelWithTimeout,
});
}
@@ -1920,6 +1946,8 @@ async function handleComboChatInner({
model: modelStr,
expiresAt: resetCandidates[0] || null,
config: relayConfig,
videoTranscriptSensitive,
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints,
handleSingleModel: handleSingleModelWithTimeout,
});
}
@@ -2443,7 +2471,9 @@ async function handleComboChatInner({
}
log.warn("COMBO", `Model ${modelStr} failed, trying next`, {
status: result.status,
errorBody: redactConnectionLabel(errorText),
errorBody: redactConnectionLabel(
redactVideoTranscriptSensitiveText(errorText, videoTranscriptSensitive)
),
});
// #5976: per-model-quota providers (Gemini, GitHub, etc.) multiplex models
@@ -2524,7 +2554,11 @@ async function handleComboChatInner({
}
})().catch((err) => {
const logError = log.error ?? log.warn;
logError("COMBO", `Speculative task error for target ${i}`, err);
logError(
"COMBO",
`Speculative task error for target ${i}`,
videoTranscriptSensitive ? redactVideoTranscriptSensitiveText(String(err), true) : err
);
// G2 (silent-stop fix): never leave the speculative loop waiting on an
// unresolved globalPromise. If a task throws unexpectedly (outside
// executeTarget's error handling) and no other task succeeds, the post-loop
@@ -2745,9 +2779,13 @@ async function handleComboChatInner({
});
if (decision.wait) {
const retainedComboFailure = redactVideoTranscriptSensitiveText(
msg,
videoTranscriptSensitive
);
log.info(
"COMBO",
`${strategy} cooldown wait: ${msg} — waiting ${Math.ceil(
`${strategy} cooldown wait: ${retainedComboFailure} — waiting ${Math.ceil(
decision.waitMs / 1000
)}s (reason=${decision.reason ?? "?"}) then retrying (attempt ${
comboCooldownAttempt + 1
@@ -2773,7 +2811,13 @@ async function handleComboChatInner({
// a config-class status like 403/422).
if (earliestRetryAfter && isRetryAfterEligibleStatus(status)) {
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter));
log.warn("COMBO", `All models failed | ${msg} (${retryHuman})`);
log.warn(
"COMBO",
`All models failed | ${redactVideoTranscriptSensitiveText(
msg,
videoTranscriptSensitive
)} (${retryHuman})`
);
return withQuotaExhaustionClassification(
unavailableResponse(status, msg, earliestRetryAfter, retryHuman),
observedFailure ? allObservedFailuresQuota : null
@@ -2784,7 +2828,10 @@ async function handleComboChatInner({
// `try-auto` recovery action via buildRecoveryHint so the OC plugin can show "→ Try
// model: auto" instead of an opaque 5xx. We pass the upstream retry-after seconds to
// the hint so the client can render a precise "wait Ns and retry" message.
log.warn("COMBO", `All models failed | ${msg}`);
log.warn(
"COMBO",
`All models failed | ${redactVideoTranscriptSensitiveText(msg, videoTranscriptSensitive)}`
);
const { pinClearedNow } = recordComboFailure(effectiveSessionId, combo.name);
if (pinClearedNow) {
log.info(
@@ -2888,6 +2935,7 @@ async function handleRoundRobinCombo({
requestHeaders = null,
relayOptions,
perTargetAdmission = null,
videoTranscriptSensitive = false,
}: HandleRoundRobinOptions): Promise<Response> {
const config = settings
? resolveComboConfig(combo, settings)
@@ -3286,24 +3334,20 @@ async function handleRoundRobinCombo({
"COMBO-RR",
`Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.`
);
return errorResponseWithComboDiagnostics(
503,
"Maximum combo retry limit reached",
{
poolSize: modelCount,
attempted: globalAttempts,
excluded: [
...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })),
...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))),
],
attemptOrder: rrOutcomes.map((o) => ({
provider: o.model.split("/")[0] || "unknown",
model: o.model,
})),
terminalReason: "max_attempts_exceeded",
recovery: buildRecoveryHint("max_attempts_exceeded"),
}
);
return errorResponseWithComboDiagnostics(503, "Maximum combo retry limit reached", {
poolSize: modelCount,
attempted: globalAttempts,
excluded: [
...[...exhaustedProviders].map((p) => ({ provider: p, reason: "exhausted" })),
...[...exhaustedConnections].map((c) => formatExhaustedConnectionKey(String(c))),
],
attemptOrder: rrOutcomes.map((o) => ({
provider: o.model.split("/")[0] || "unknown",
model: o.model,
})),
terminalReason: "max_attempts_exceeded",
recovery: buildRecoveryHint("max_attempts_exceeded"),
});
}
if (retry > 0) {
log.info(
@@ -3404,9 +3448,13 @@ async function handleRoundRobinCombo({
releaseQualityClone(rrClone, result, quality);
if (!quality.valid) {
releaseRejectedQualityResponse(rrClone, result);
const retainedQualityReason = redactVideoTranscriptSensitiveText(
quality.reason || "upstream response failed quality validation",
videoTranscriptSensitive
);
log.warn(
"COMBO-RR",
`${modelStr} returned 200 but failed quality check: ${quality.reason}`
`${modelStr} returned 200 but failed quality check: ${retainedQualityReason}`
);
// #6692: same rationale as handleComboChat's quality-fail branch —
// a quality-rejected 200 never marks the connection row unhealthy,
@@ -3758,7 +3806,9 @@ async function handleRoundRobinCombo({
if (offset > 0) fallbackCount++;
log.warn("COMBO-RR", `${modelStr} failed, trying next model`, {
status: result.status,
errorBody: redactConnectionLabel(errorText),
errorBody: redactConnectionLabel(
redactVideoTranscriptSensitiveText(errorText, videoTranscriptSensitive)
),
});
if (
@@ -3811,7 +3861,11 @@ async function handleRoundRobinCombo({
} catch (err) {
// G4: unexpected exception in the round-robin loop must never crash the
// request silently — surface a 500 instead of hanging the client.
log.error?.("COMBO-RR", "Unexpected error in round-robin loop", err);
log.error?.(
"COMBO-RR",
"Unexpected error in round-robin loop",
videoTranscriptSensitive ? redactVideoTranscriptSensitiveText(String(err), true) : err
);
return errorResponse(500, "Unexpected error in round-robin combo");
} finally {
if (rrLoopSafetyTimer) {
@@ -3907,11 +3961,20 @@ async function handleRoundRobinCombo({
if (earliestRetryAfter && isRetryAfterEligibleStatus(status)) {
const retryHuman = formatRetryAfter(toRetryAfterDisplayValue(earliestRetryAfter));
log.warn("COMBO-RR", `All models failed | ${msg} (${retryHuman})`);
log.warn(
"COMBO-RR",
`All models failed | ${redactVideoTranscriptSensitiveText(
msg,
videoTranscriptSensitive
)} (${retryHuman})`
);
return unavailableResponse(status, msg, earliestRetryAfter, retryHuman);
}
log.warn("COMBO-RR", `All models failed | ${msg}`);
log.warn(
"COMBO-RR",
`All models failed | ${redactVideoTranscriptSensitiveText(msg, videoTranscriptSensitive)}`
);
return new Response(JSON.stringify({ error: { message: msg } }), {
status,
headers: { "Content-Type": "application/json" },

View File

@@ -13,6 +13,7 @@
* Extracted from combo.ts as a pure move (#3501). No behaviour change.
*/
import { getCachedProviderConnections } from "../../../src/lib/db/readCache";
import { redactVideoTranscriptSensitiveText } from "../../../src/lib/guardrails/videoTranscriptLogRedaction";
import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker";
import { fisherYatesShuffle, getNextFromDeck } from "../../../src/shared/utils/shuffleDeck";
import { handleFusionChat, type FusionTuning } from "../fusion.ts";
@@ -84,6 +85,8 @@ type PreludeBaseOptionArgs = {
signal?: AbortSignal | null;
apiKeyAllowedConnections?: string[] | null;
hiddenModelsByProvider?: HiddenModelsByProvider;
videoTranscriptSensitive?: boolean;
videoTranscriptDescriptionFingerprints?: readonly string[];
clientManagedResponsesContext?: boolean;
/** #9654 Wave 2: per-target lane-aware admission probe (see HandleComboChatOptions). */
perTargetAdmission?: PerTargetAdmissionHook | null;
@@ -111,6 +114,8 @@ function buildBaseOptions(a: PreludeBaseOptionArgs): HandleComboChatOptions {
signal: a.signal,
apiKeyAllowedConnections: a.apiKeyAllowedConnections,
hiddenModelsByProvider: a.hiddenModelsByProvider,
videoTranscriptSensitive: a.videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints: a.videoTranscriptDescriptionFingerprints,
invocationId: a.invocationId,
clientManagedResponsesContext: a.clientManagedResponsesContext,
perTargetAdmission: a.perTargetAdmission,
@@ -221,8 +226,16 @@ async function evaluatePinnedResponse(args: {
clientRequestedStream: boolean;
config: ComboSetupConfig;
log: ComboLogger;
videoTranscriptSensitive?: boolean;
}): Promise<Response | null> {
const { pinnedResult, pinnedModel, clientRequestedStream, config, log } = args;
const {
pinnedResult,
pinnedModel,
clientRequestedStream,
config,
log,
videoTranscriptSensitive = false,
} = args;
if (pinnedResult.ok) {
let pinnedClone: Response;
try {
@@ -239,9 +252,13 @@ async function evaluatePinnedResponse(args: {
releaseQualityClone(pinnedClone, pinnedResult, pinnedQuality);
if (pinnedQuality.valid) return pinnedResult;
releaseRejectedQualityResponse(pinnedClone, pinnedResult);
const retainedReason = redactVideoTranscriptSensitiveText(
pinnedQuality.reason || "upstream response failed quality validation",
videoTranscriptSensitive
);
log.warn(
"COMBO",
`Pinned model ${pinnedModel} returned 200 but failed quality check: ${pinnedQuality.reason}, falling through to combo retry/fallback`
`Pinned model ${pinnedModel} returned 200 but failed quality check: ${retainedReason}, falling through to combo retry/fallback`
);
return null;
}
@@ -271,6 +288,7 @@ export async function tryPinnedModelDispatch(args: {
handleSingleModelWithTimeout: HandleSingleModel;
log: ComboLogger;
hiddenModelsByProvider?: HiddenModelsByProvider;
videoTranscriptSensitive?: boolean;
}): Promise<Response | null> {
const {
body,
@@ -282,6 +300,7 @@ export async function tryPinnedModelDispatch(args: {
handleSingleModelWithTimeout,
log,
hiddenModelsByProvider,
videoTranscriptSensitive = false,
} = args;
// The pin is read from session_model_history (a PRIOR turn) and may name a
// model that has since been removed from this combo, or a provider whose
@@ -341,9 +360,13 @@ export async function tryPinnedModelDispatch(args: {
modelPinned: true,
} as SingleModelTarget);
} catch (pinErr) {
const retainedPinError = redactVideoTranscriptSensitiveText(
pinErr instanceof Error ? pinErr.message : String(pinErr),
videoTranscriptSensitive
);
log.warn(
"COMBO",
`Pinned model ${pinnedModel} threw error: ${pinErr instanceof Error ? pinErr.message : String(pinErr)}, falling through to combo retry/fallback`
`Pinned model ${pinnedModel} threw error: ${retainedPinError}, falling through to combo retry/fallback`
);
}
if (pinnedResult) {
@@ -353,6 +376,7 @@ export async function tryPinnedModelDispatch(args: {
clientRequestedStream,
config,
log,
videoTranscriptSensitive,
});
if (accepted) return accepted;
}
@@ -395,6 +419,8 @@ export async function tryFusionDispatch(args: {
signal?: AbortSignal | null;
apiKeyAllowedConnections?: string[] | null;
hiddenModelsByProvider?: HiddenModelsByProvider;
videoTranscriptSensitive?: boolean;
videoTranscriptDescriptionFingerprints?: readonly string[];
perTargetAdmission?: PerTargetAdmissionHook | null;
deferContextOverflowWhenCompressible?: boolean;
compressionExclusions?: import("../compression/exclusions.ts").CompressionExclusions;
@@ -524,6 +550,7 @@ export async function tryFusionDispatch(args: {
perTargetAdmission: args.perTargetAdmission,
judgeModel,
tuning: fusionTuning,
videoTranscriptSensitive: args.videoTranscriptSensitive,
});
}
@@ -676,6 +703,8 @@ export async function tryRuntimeUnitDispatch(args: {
signal?: AbortSignal | null;
apiKeyAllowedConnections?: string[] | null;
hiddenModelsByProvider?: HiddenModelsByProvider;
videoTranscriptSensitive?: boolean;
videoTranscriptDescriptionFingerprints?: readonly string[];
perTargetAdmission?: PerTargetAdmissionHook | null;
deferContextOverflowWhenCompressible?: boolean;
compressionExclusions?: import("../compression/exclusions.ts").CompressionExclusions;

View File

@@ -114,6 +114,10 @@ export type HandleComboChatOptions = {
apiKeyAllowedConnections?: string[] | null;
nesting?: ComboNestingContext | null;
hiddenModelsByProvider?: HiddenModelsByProvider;
/** Request-scoped retention bit derived before guardrails can replace a video carrier. */
videoTranscriptSensitive?: boolean;
/** Exact bounded identities emitted by a modified Video Bridge guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
/** Native Responses clients (for example Codex CLI/Desktop) manage compaction themselves. */
clientManagedResponsesContext?: boolean;
/**

View File

@@ -5,6 +5,11 @@ import {
type HandoffPayload,
upsertHandoff,
} from "../../src/lib/db/contextHandoffs.ts";
import {
containsVideoTranscriptForLog,
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
} from "../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
import { estimateTokens } from "./contextManager.ts";
import { stripMarkdownCodeFence } from "../utils/aiSdkCompat.ts";
@@ -43,6 +48,23 @@ export type MessageLike = {
content?: unknown;
};
type HandoffTranscriptContext = VideoTranscriptLogContext & {
videoTranscriptSensitive?: boolean;
};
function retainHandoffMessages(
messages: MessageLike[],
context: HandoffTranscriptContext
): MessageLike[] | null {
const source = Array.isArray(messages) ? messages : [];
const transcriptSensitive =
context.videoTranscriptSensitive === true || containsVideoTranscriptForLog(source, context);
if (!transcriptSensitive) return source;
const retained = omitVideoTranscriptForLog(source, context);
return Array.isArray(retained) ? (retained as MessageLike[]) : null;
}
export interface ContextRelayConfig {
handoffModel?: string;
handoffThreshold?: number;
@@ -445,6 +467,10 @@ export function maybeGenerateHandoff(options: {
model: string;
expiresAt: string | null;
config?: ContextRelayConfig | null;
/** Trusted request bit for carriers already replaced by the Video Bridge guardrail. */
videoTranscriptSensitive?: boolean;
/** Exact bounded identities emitted by a modified Video Bridge guardrail. */
trustedDescriptionFingerprints?: readonly string[];
handleSingleModel: (body: Record<string, unknown>, modelStr: string) => Promise<Response>;
}): void {
if (!options.sessionId || !options.connectionId) return;
@@ -454,6 +480,12 @@ export function maybeGenerateHandoff(options: {
if (options.percentUsed < relayConfig.handoffThreshold) return;
if (options.percentUsed >= HANDOFF_EXHAUSTION_THRESHOLD) return;
const retainedMessages = retainHandoffMessages(options.messages, {
videoTranscriptSensitive: options.videoTranscriptSensitive,
trustedDescriptionFingerprints: options.trustedDescriptionFingerprints,
});
if (!retainedMessages) return;
cleanupExpiredHandoffs();
if (hasActiveHandoff(options.sessionId, options.comboName)) return;
const inflightKey = getInflightKey(options.sessionId, options.comboName);
@@ -463,6 +495,7 @@ export function maybeGenerateHandoff(options: {
setImmediate(() => {
generateHandoffAsync({
...options,
messages: retainedMessages,
sessionId: options.sessionId as string,
connectionId: options.connectionId as string,
config: relayConfig,
@@ -690,6 +723,10 @@ export function maybeGenerateUniversalHandoff(options: {
prevModel: string | null;
currModel: string;
universalConfig: UniversalHandoffConfig;
/** Trusted request bit for carriers already replaced by the Video Bridge guardrail. */
videoTranscriptSensitive?: boolean;
/** Exact bounded identities emitted by a modified Video Bridge guardrail. */
trustedDescriptionFingerprints?: readonly string[];
handleSingleModel: (body: Record<string, unknown>, modelStr: string) => Promise<Response>;
}): void {
const decision = shouldGenerateUniversalHandoff({
@@ -703,6 +740,12 @@ export function maybeGenerateUniversalHandoff(options: {
if (decision !== "generate") return;
if (!options.sessionId) return;
const retainedMessages = retainHandoffMessages(options.messages, {
videoTranscriptSensitive: options.videoTranscriptSensitive,
trustedDescriptionFingerprints: options.trustedDescriptionFingerprints,
});
if (!retainedMessages) return;
const inflightKey = getInflightKey(options.sessionId, options.comboName);
if (inflightHandoffGenerations.has(inflightKey)) return;
inflightHandoffGenerations.add(inflightKey);
@@ -713,7 +756,7 @@ export function maybeGenerateUniversalHandoff(options: {
generateUniversalHandoffAsync({
sessionId: options.sessionId as string,
comboName: options.comboName,
messages: options.messages,
messages: retainedMessages,
prevModel: options.prevModel || "unknown",
currModel: options.currModel,
handoffModel: options.universalConfig.handoffModel || options.currModel,

View File

@@ -20,6 +20,7 @@
*/
import { errorResponse, sanitizeErrorMessage } from "../utils/error.ts";
import { extractTextContent } from "../translator/helpers/geminiHelper.ts";
import { redactVideoTranscriptSensitiveText } from "../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
import type { PerTargetAdmissionHook } from "./admission/types.ts";
import type { ComboLogger, HandleSingleModel, ResolvedComboTarget } from "./combo/types.ts";
@@ -231,6 +232,8 @@ export type HandleFusionChatOptions = {
tuning?: FusionTuning | null;
/** #9654 Wave 2: per-target lane-aware admission probe (see HandleComboChatOptions). */
perTargetAdmission?: PerTargetAdmissionHook | null;
/** Request-scoped bit set before Video Bridge can replace the raw carrier. */
videoTranscriptSensitive?: boolean;
};
function getFusionModelString(model: FusionModel): string {
@@ -276,6 +279,7 @@ export async function handleFusionChat({
judgeTarget,
tuning,
perTargetAdmission,
videoTranscriptSensitive = false,
}: HandleFusionChatOptions): Promise<Response> {
const panel = Array.isArray(models) ? models.filter(Boolean) : [];
if (panel.length === 0) {
@@ -400,7 +404,10 @@ export async function handleFusionChat({
}
if (sentinel.__error) {
log.warn("FUSION", `Panel ${model} threw`, {
error: sanitizeErrorMessage(sentinel.__error as Error),
error: redactVideoTranscriptSensitiveText(
sanitizeErrorMessage(sentinel.__error as Error),
videoTranscriptSensitive
),
});
failures.push({ model, reason: "threw" });
continue;
@@ -428,7 +435,10 @@ export async function handleFusionChat({
}
} catch (e) {
log.warn("FUSION", `Panel ${model} unparseable`, {
error: sanitizeErrorMessage(e as Error),
error: redactVideoTranscriptSensitiveText(
sanitizeErrorMessage(e as Error),
videoTranscriptSensitive
),
});
failures.push({ model, reason: "unparseable" });
}

View File

@@ -140,6 +140,7 @@ type AssistantMessageLike = {
type AssistantMessageCacheContext = {
scope?: string;
historyMessages?: AssistantMessageLike[];
videoTranscriptSensitive?: boolean;
};
type ToolCallLike = {
@@ -334,6 +335,7 @@ export function cacheReasoningFromAssistantMessage(
model: string,
context?: AssistantMessageCacheContext
): number {
if (context?.videoTranscriptSensitive === true) return 0;
if (!message || message.role !== "assistant") {
return 0;
}

View File

@@ -1,5 +1,11 @@
import { getPendingById } from "@/lib/usage/usageHistory";
import { getChatLogMaxDepth, getChatLogArrayTailItems } from "@/lib/logEnv";
import {
containsVideoTranscriptForLog,
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { sanitizeErrorMessage } from "./error.ts";
type JsonRecord = Record<string, unknown>;
@@ -12,6 +18,7 @@ type HeaderInput =
| undefined;
export type RequestPipelinePayloads = {
_omnirouteVideoTranscriptRedacted?: true;
routeDecision?: JsonRecord;
clientRawRequest?: JsonRecord;
openaiRequest?: JsonRecord;
@@ -44,6 +51,7 @@ type RequestLogger = {
appendConvertedChunk: (chunk: string) => void;
logError: (error: unknown, requestBody?: unknown) => void;
getPipelinePayloads: () => RequestPipelinePayloads | null;
isVideoTranscriptSensitive: () => boolean;
};
type RequestLoggerOptions = {
@@ -55,6 +63,13 @@ type RequestLoggerOptions = {
model?: string;
provider?: string;
connectionId?: string | null;
/**
* Server-derived from a recognized video carrier, successful bridge result, or bounded
* unknown-sensitive detector overflow; never from delimiter-shaped prose alone.
*/
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of transcript descriptions generated by the guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
};
const DEFAULT_MAX_STREAM_CHUNK_BYTES = 128 * 1024;
@@ -159,7 +174,7 @@ function truncateLogString(value: string, maxLength = MAX_LOG_STRING_LENGTH): st
* recursing into an object's values, enabling the per-field exemption above.
* Top-level arrays (no key context) remain subject to truncation.
*/
export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null = null): unknown {
function cloneBoundedForLogValue(value: unknown, depth = 0, key: string | null = null): unknown {
if (value === null || value === undefined) return value;
if (typeof value === "string") return truncateLogString(value);
if (typeof value !== "object") return value;
@@ -178,12 +193,15 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
// item and rewrite originalLength with the truncated length (25 instead of the true 800), so
// the log would misreport how much was cut. Keep the original marker, re-bound only the tail.
if (isTruncatedArrayMarker(value[0])) {
return [value[0], ...value.slice(1).map((item) => cloneBoundedForLog(item, depth + 1))];
return [
value[0],
...value.slice(1).map((item) => cloneBoundedForLogValue(item, depth + 1, null)),
];
}
const exempt = key === "tools";
const shouldTruncate = !exempt && value.length > MAX_LOG_ARRAY_ITEMS;
const source = shouldTruncate ? value.slice(-MAX_LOG_ARRAY_ITEMS) : value;
const mapped = source.map((item) => cloneBoundedForLog(item, depth + 1));
const mapped = source.map((item) => cloneBoundedForLogValue(item, depth + 1, null));
if (shouldTruncate) {
return [
{
@@ -206,7 +224,7 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
([k]) => !(carried > 0 && k === TRUNCATED_KEYS_MARKER)
);
for (const [k, item] of entries.slice(0, MAX_LOG_OBJECT_KEYS)) {
result[k] = cloneBoundedForLog(item, depth + 1, k);
result[k] = cloneBoundedForLogValue(item, depth + 1, k);
}
const dropped = Math.max(0, entries.length - MAX_LOG_OBJECT_KEYS) + carried;
if (dropped > 0) {
@@ -215,6 +233,17 @@ export function cloneBoundedForLog(value: unknown, depth = 0, key: string | null
return result;
}
export function cloneBoundedForLog(
value: unknown,
depth = 0,
key: string | null = null,
descriptionContext: VideoTranscriptLogContext = {}
): unknown {
const transcriptSafeValue =
depth === 0 ? omitVideoTranscriptForLog(value, descriptionContext) : value;
return cloneBoundedForLogValue(transcriptSafeValue, depth, key);
}
function appendBoundedChunk(
chunks: string[],
bytes: { value: number; truncated: boolean },
@@ -277,7 +306,7 @@ function compactPipelinePayloads(
continue;
}
result[key as keyof RequestPipelinePayloads] = value;
(result as Record<string, unknown>)[key] = value;
}
return hasOwnValues(result) ? result : null;
@@ -298,6 +327,7 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
? Number(options.maxStreamChunkItems)
: DEFAULT_MAX_STREAM_CHUNK_ITEMS;
let pendingPushed = false;
let videoTranscriptSensitive = options.videoTranscriptSensitive === true;
const push = () => {
if (pendingPushed) return;
@@ -330,7 +360,7 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
};
const append = (arr: string[], bytes: { value: number; truncated: boolean }, chunk: string) => {
if (!captureChunks) return;
if (!captureChunks || videoTranscriptSensitive) return;
push();
const ts = new Date().toISOString().slice(11, 23);
appendBoundedChunk(arr, bytes, `[${ts}] ${chunk}`, maxBytes, maxItems);
@@ -348,6 +378,17 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
appendConvertedChunk(chunk: string) {
append(streamChunks.client, streamChunkBytes.client, chunk);
},
suppressVideoTranscriptChunks() {
videoTranscriptSensitive = true;
for (const chunks of Object.values(streamChunks)) chunks.splice(0, chunks.length);
for (const state of Object.values(streamChunkBytes)) {
state.value = 0;
state.truncated = false;
}
},
isVideoTranscriptSensitive() {
return videoTranscriptSensitive;
},
};
}
@@ -362,26 +403,58 @@ export async function createRequestLogger(
// so that active requests always have real-time stream data available via
// the /api/logs/active endpoint.
const chunkMethods = makeStreamChunkMethods(options, captureStreamChunks);
const descriptionLogContext: VideoTranscriptLogContext = {
trustedDescriptionFingerprints: options.videoTranscriptDescriptionFingerprints ?? [],
};
const suppressTranscriptChunksIfNeeded = (
descriptionContext: VideoTranscriptLogContext,
...values: unknown[]
): void => {
if (values.some((value) => containsVideoTranscriptForLog(value, descriptionContext))) {
chunkMethods.suppressVideoTranscriptChunks();
}
};
const cloneRequestBody = (
value: unknown,
descriptionContext: VideoTranscriptLogContext
): unknown => cloneBoundedForLog(value, 0, null, descriptionContext);
const cloneResponseBody = (value: unknown): unknown =>
chunkMethods.isVideoTranscriptSensitive()
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: cloneBoundedForLog(value);
if (options.enabled === false) {
let routeDecision: JsonRecord | null = null;
return {
sessionPath: null,
logClientRawRequest() {},
logClientRawRequest(_endpoint, body) {
suppressTranscriptChunksIfNeeded({}, body);
},
logRouteDecision(decision) {
routeDecision = cloneBoundedForLog(decision) as JsonRecord;
},
logOpenAIRequest() {},
logTargetRequest() {},
logProviderResponse() {},
logOpenAIRequest(body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
},
logTargetRequest(_url, _headers, body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
},
logProviderResponse(_status, _statusText, _headers, body) {
void body;
},
appendProviderChunk: chunkMethods.appendProviderChunk,
appendOpenAIChunk: chunkMethods.appendOpenAIChunk,
logConvertedResponse() {},
logConvertedResponse(body) {
void body;
},
appendConvertedChunk: chunkMethods.appendConvertedChunk,
logError() {},
logError(_error, requestBody) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, requestBody);
},
getPipelinePayloads() {
return routeDecision ? { routeDecision } : null;
},
isVideoTranscriptSensitive: chunkMethods.isVideoTranscriptSensitive,
};
}
@@ -393,11 +466,12 @@ export async function createRequestLogger(
sessionPath: null,
logClientRawRequest(endpoint, body, headers = {}) {
suppressTranscriptChunksIfNeeded({}, body);
payloads.clientRawRequest = {
timestamp: new Date().toISOString(),
endpoint,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, {}),
};
},
@@ -406,18 +480,20 @@ export async function createRequestLogger(
},
logOpenAIRequest(body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
payloads.openaiRequest = {
timestamp: new Date().toISOString(),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, descriptionLogContext),
};
},
logTargetRequest(url, headers, body) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, body);
payloads.providerRequest = {
timestamp: new Date().toISOString(),
url,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneRequestBody(body, descriptionLogContext),
};
},
@@ -427,7 +503,7 @@ export async function createRequestLogger(
status,
statusText,
headers: maskSensitiveHeaders(headers),
body: cloneBoundedForLog(body),
body: cloneResponseBody(body),
};
},
@@ -436,21 +512,25 @@ export async function createRequestLogger(
logConvertedResponse(body) {
payloads.clientResponse = {
timestamp: new Date().toISOString(),
body: cloneBoundedForLog(body),
body: cloneResponseBody(body),
};
},
appendConvertedChunk: chunkMethods.appendConvertedChunk,
logError(error, requestBody = null) {
suppressTranscriptChunksIfNeeded(descriptionLogContext, requestBody);
payloads.error = {
timestamp: new Date().toISOString(),
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
requestBody: cloneBoundedForLog(requestBody),
error: chunkMethods.isVideoTranscriptSensitive()
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
requestBody: cloneRequestBody(requestBody, descriptionLogContext),
};
},
getPipelinePayloads() {
return compactPipelinePayloads(payloads);
},
isVideoTranscriptSensitive: chunkMethods.isVideoTranscriptSensitive,
};
}

View File

@@ -1,6 +1,7 @@
import { VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb";
import { translateResponse, initState } from "../translator/index.ts";
import { FORMATS } from "../translator/formats.ts";
import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb";
import {
extractUsage,
hasValidUsage,
@@ -177,6 +178,8 @@ type StreamOptions = {
* codex-compatible `namespace` + `name` fields.
*/
requestToolIdentityMap?: Map<string, { namespace: string; name: string }> | null;
/** Omit request-sensitive transcript text from retained stream diagnostics only. */
redactStreamDiagnosticsForLog?: boolean;
};
type TranslateState = ReturnType<typeof initState> & {
@@ -654,7 +657,10 @@ export function createSSEStream(options: StreamOptions = {}) {
dropResponsesCommentary,
customToolNames = new Set<string>(),
requestToolIdentityMap = null,
redactStreamDiagnosticsForLog = false,
} = options;
const retainDiagnosticForLog = (value: unknown): unknown =>
redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
const signatureNamespace = connectionId;
// Request-body-size metric (for monitoring payload size distribution & correlation with TTFT).
// The size is JSON-serialised byte count; stored as a performance mark detail so monitoring
@@ -1005,7 +1011,10 @@ export function createSSEStream(options: StreamOptions = {}) {
try {
failureHandled = onFailure({ status: 502, message: msg, code: "empty_response" }) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (empty_response):`, e);
console.debug(
`[STREAM] onFailure callback error (empty_response):`,
retainDiagnosticForLog(e)
);
}
}
if (decrementPendingRequest && !failureHandled) {
@@ -1199,7 +1208,10 @@ export function createSSEStream(options: StreamOptions = {}) {
type: "timeout_error",
}) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (idle_timeout):`, e);
console.debug(
`[STREAM] onFailure callback error (idle_timeout):`,
retainDiagnosticForLog(e)
);
}
}
if (!failureHandled) {
@@ -1641,10 +1653,7 @@ export function createSSEStream(options: StreamOptions = {}) {
isResponsesCommentaryMessageItem
).items
: passthroughResponsesOutputItems;
const backfilled = backfillResponsesCompletedOutput(
parsed,
backfillCandidates
);
const backfilled = backfillResponsesCompletedOutput(parsed, backfillCandidates);
const usageNormalized = normalizeUsage(parsed);
if (
stripped ||
@@ -2040,7 +2049,7 @@ export function createSSEStream(options: StreamOptions = {}) {
try {
failureHandled = onFailure(failurePayload) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error:`, e);
console.debug(`[STREAM] onFailure callback error:`, retainDiagnosticForLog(e));
}
}
clearIdleTimer();
@@ -2624,7 +2633,10 @@ export function createSSEStream(options: StreamOptions = {}) {
),
});
} catch (e) {
console.debug(`[STREAM] onComplete callback error (${model || "unknown"}):`, e);
console.debug(
`[STREAM] onComplete callback error (${model || "unknown"}):`,
retainDiagnosticForLog(e)
);
}
} else {
clearPendingRequestFromStream();
@@ -2712,7 +2724,10 @@ export function createSSEStream(options: StreamOptions = {}) {
type: err.type,
}) === true;
} catch (e) {
console.debug(`[STREAM] onFailure callback error (${model || "unknown"}):`, e);
console.debug(
`[STREAM] onFailure callback error (${model || "unknown"}):`,
retainDiagnosticForLog(e)
);
}
}
@@ -2740,7 +2755,7 @@ export function createSSEStream(options: StreamOptions = {}) {
} catch (e) {
console.debug(
`[STREAM] onComplete callback error in error path (${model || "unknown"}):`,
e
retainDiagnosticForLog(e)
);
}
}
@@ -2945,14 +2960,18 @@ export function createSSEStream(options: StreamOptions = {}) {
} catch (e) {
console.debug(
`[STREAM] onComplete callback error in flush (${model || "unknown"}):`,
e
retainDiagnosticForLog(e)
);
}
} else {
clearPendingRequestFromStream();
}
} catch (error) {
console.log(`[STREAM] Error in flush (${model || "unknown"}):`, error.message || error);
const diagnostic = error instanceof Error ? error.message : error;
console.log(
`[STREAM] Error in flush (${model || "unknown"}):`,
retainDiagnosticForLog(diagnostic)
);
}
},
cancel(reason) {
@@ -2982,7 +3001,8 @@ export function createSSETransformStreamWithLogger(
copilotCompatibleReasoning = false,
suppressThinkClose = false,
customToolNames: ReadonlySet<string> = new Set(),
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null,
redactStreamDiagnosticsForLog = false
) {
return createSSEStream({
mode: STREAM_MODE.TRANSLATE,
@@ -3001,6 +3021,7 @@ export function createSSETransformStreamWithLogger(
suppressThinkClose,
customToolNames,
requestToolIdentityMap,
redactStreamDiagnosticsForLog,
});
}
@@ -3015,7 +3036,8 @@ export function createPassthroughStreamWithLogger(
apiKeyInfo: unknown = null,
onFailure: ((payload: StreamFailurePayload) => boolean | void | Promise<void>) | null = null,
clientResponseFormat: string | null = null,
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null
requestToolIdentityMap: Map<string, { namespace: string; name: string }> | null = null,
redactStreamDiagnosticsForLog = false
) {
return createSSEStream({
mode: STREAM_MODE.PASSTHROUGH,
@@ -3030,6 +3052,7 @@ export function createPassthroughStreamWithLogger(
onFailure,
clientResponseFormat,
requestToolIdentityMap,
redactStreamDiagnosticsForLog,
});
}

View File

@@ -92,6 +92,7 @@ export function finalizeStreamRequestLog({
status,
error,
errorCode,
videoTranscriptSensitive,
onWarn,
}: {
pendingRequestId: string;
@@ -103,6 +104,7 @@ export function finalizeStreamRequestLog({
status: number;
error?: string | null;
errorCode?: string | null;
videoTranscriptSensitive: boolean;
onWarn?: (error: unknown) => void;
}) {
try {
@@ -112,6 +114,7 @@ export function finalizeStreamRequestLog({
status,
error: error || null,
errorCode: errorCode || null,
videoTranscriptSensitive,
});
if (!completedById) {
finalizeMostRecentPendingRequest(model, provider, connectionId, {
@@ -120,6 +123,7 @@ export function finalizeStreamRequestLog({
status,
error: error || null,
errorCode: errorCode || null,
videoTranscriptSensitive,
});
}
} catch (error) {

View File

@@ -1,3 +1,4 @@
import { VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { trackPendingRequest } from "@/lib/usageDb";
import { STREAM_IDLE_TIMEOUT_MS } from "../config/constants.ts";
import { FORMATS } from "../translator/formats.ts";
@@ -39,6 +40,7 @@ type StreamControllerOptions = {
clientAbortSignal?: AbortSignal | null;
allowCompletedToolHandoffGrace?: boolean;
clientDisconnectGracePeriodMs?: number;
redactStreamDiagnosticsForLog?: boolean;
};
type StreamController = ReturnType<typeof createStreamController>;
@@ -243,6 +245,7 @@ export function createStreamController({
clientAbortSignal,
allowCompletedToolHandoffGrace = false,
clientDisconnectGracePeriodMs = 0,
redactStreamDiagnosticsForLog = false,
}: StreamControllerOptions = {}) {
const abortController = new AbortController();
const startTime = Date.now();
@@ -253,6 +256,9 @@ export function createStreamController({
let pendingRequestCleared = false;
let cleanupClientAbortSignal: (() => void) | null = null;
const retainDiagnosticForLog = (value: unknown): unknown =>
redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
const logStream = (status) => {
const duration = Date.now() - startTime;
const p = provider?.toUpperCase() || "UNKNOWN";
@@ -279,7 +285,7 @@ export function createStreamController({
} catch (e) {
console.error(
`[${getTimeString()}] [streamHandler] trackPendingRequest decrement failed — counter may drift`,
e
retainDiagnosticForLog(e)
);
}
};
@@ -317,7 +323,7 @@ export function createStreamController({
disconnected = true;
cleanupClientAbortListener();
logStream(`disconnect: ${reason}`);
logStream(`disconnect: ${String(retainDiagnosticForLog(reason))}`);
// Decrement pending request counter — the TransformStream flush() won't
// fire when the client aborts mid-stream, so we must clean up here.
@@ -390,7 +396,7 @@ export function createStreamController({
duration: Date.now() - startTime,
}) === true;
} catch (e) {
console.debug(`[STREAM-HANDLER] onError callback error:`, e);
console.debug(`[STREAM-HANDLER] onError callback error:`, retainDiagnosticForLog(e));
}
}
@@ -406,7 +412,7 @@ export function createStreamController({
}
if (error instanceof Error) {
logStream(`error: ${error.message}`);
logStream(`error: ${String(retainDiagnosticForLog(error.message))}`);
return;
}
logStream("error: unknown");
@@ -845,9 +851,11 @@ export function pipeWithDisconnect(
providerResponse: Response,
transformStream: TransformStream<Uint8Array, Uint8Array>,
streamController: StreamController,
opts: { stallTimeoutMs?: number } = {}
opts: { redactStreamDiagnosticsForLog?: boolean; stallTimeoutMs?: number } = {}
) {
const stallTimeoutMs = opts.stallTimeoutMs ?? DEFAULT_STREAM_STALL_TIMEOUT_MS;
const retainDiagnosticForLog = (value: unknown): unknown =>
opts.redactStreamDiagnosticsForLog ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
// Watchdog disabled — preserve legacy behavior verbatim.
if (!stallTimeoutMs || stallTimeoutMs <= 0) {
@@ -887,7 +895,10 @@ export function pipeWithDisconnect(
try {
streamController.handleError?.(stallError);
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog handleError failed:`, e);
console.debug(
`[STREAM-HANDLER] stall watchdog handleError failed:`,
retainDiagnosticForLog(e)
);
}
// Error the pipeline so the downstream reader unblocks. createDisconnect-
// AwareStream's catch block translates this into buildStreamErrorChunks
@@ -895,13 +906,16 @@ export function pipeWithDisconnect(
try {
upstreamTapController?.error(stallError);
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog upstream tap error failed:`, e);
console.debug(
`[STREAM-HANDLER] stall watchdog upstream tap error failed:`,
retainDiagnosticForLog(e)
);
}
// Abort the underlying fetch so upstream releases the connection.
try {
streamController.abort?.();
} catch (e) {
console.debug(`[STREAM-HANDLER] stall watchdog abort failed:`, e);
console.debug(`[STREAM-HANDLER] stall watchdog abort failed:`, retainDiagnosticForLog(e));
}
}, stallTimeoutMs);
};

View File

@@ -474,6 +474,8 @@ export async function ensureStreamReadiness(
provider?: string | null;
model?: string | null;
log?: StreamReadinessLogger | null;
/** Keep the diagnostic transient while retaining only its existence in logs. */
redactUpstreamDiagnosticForLog?: boolean;
}
): Promise<StreamReadinessResult> {
if (!response.body || options.timeoutMs <= 0) return { ok: true, response };
@@ -568,9 +570,13 @@ export async function ensureStreamReadiness(
const reason = upstreamDiagnostic
? `${classificationReason}: ${upstreamDiagnostic}`
: classificationReason;
const retainedReason =
upstreamDiagnostic && options.redactUpstreamDiagnosticForLog
? `${classificationReason}: [upstream diagnostic omitted]`
: reason;
options.log?.warn?.(
"STREAM",
`${reason} (${options.provider || "provider"}/${options.model || "unknown"})`
`${retainedReason} (${options.provider || "provider"}/${options.model || "unknown"})`
);
return {
ok: false,

View File

@@ -19,6 +19,8 @@ export interface GuardrailContext {
sourceFormat?: string | null;
stream?: boolean;
targetFormat?: string | null;
/** Server-owned bit resolved before any guardrail can replace the raw video carrier. */
videoTranscriptSensitive?: boolean;
}
export interface GuardrailResult<TValue = unknown> {

View File

@@ -10,6 +10,7 @@ import { VisionBridgeGuardrail } from "./visionBridge";
import { AudioBridgeGuardrail } from "./audioBridge";
import { VideoBridgeGuardrail } from "./videoBridge";
import { CredentialMaskerGuardrail } from "./credentialMasker";
import { VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER } from "./videoTranscriptLogRedaction";
/**
* `preCall`/`postCall` may legitimately return nothing — that is the documented
@@ -69,8 +70,29 @@ function coerceDisabledGuardrails(value: unknown) {
.filter(Boolean);
}
function getGuardrailLogger(context: GuardrailContext) {
return context.log || console;
function getGuardrailLogger(context: GuardrailContext): NonNullable<GuardrailContext["log"]> {
const source = context.log || console;
if (context.videoTranscriptSensitive !== true) return source;
const emit = (
level: "debug" | "info" | "warn" | "error",
tag: string,
_message: string,
_meta?: Record<string, unknown>
) => {
const target = source[level];
if (typeof target !== "function") return;
target.call(source, tag, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER, {
detail: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
});
};
return {
debug: (tag, message, meta) => emit("debug", tag, message, meta),
info: (tag, message, meta) => emit("info", tag, message, meta),
warn: (tag, message, meta) => emit("warn", tag, message, meta),
error: (tag, message, meta) => emit("error", tag, message, meta),
};
}
export function resolveDisabledGuardrails({
@@ -135,6 +157,8 @@ export class GuardrailRegistry {
async runPreCallHooks<TPayload = unknown>(payload: TPayload, context: GuardrailContext = {}) {
const logger = getGuardrailLogger(context);
const guardedContext =
context.videoTranscriptSensitive === true ? { ...context, log: logger } : context;
const results: GuardrailExecutionResult[] = [];
let currentPayload = payload;
@@ -151,7 +175,7 @@ export class GuardrailRegistry {
}
try {
const result = asGuardrailResult(await guardrail.preCall(currentPayload, context));
const result = asGuardrailResult(await guardrail.preCall(currentPayload, guardedContext));
const modified = result?.modifiedPayload !== undefined;
const meta = result?.meta || null;
@@ -211,6 +235,8 @@ export class GuardrailRegistry {
async runPostCallHooks<TResponse = unknown>(response: TResponse, context: GuardrailContext = {}) {
const logger = getGuardrailLogger(context);
const guardedContext =
context.videoTranscriptSensitive === true ? { ...context, log: logger } : context;
const results: GuardrailExecutionResult[] = [];
let currentResponse = response;
@@ -227,7 +253,7 @@ export class GuardrailRegistry {
}
try {
const result = asGuardrailResult(await guardrail.postCall(currentResponse, context));
const result = asGuardrailResult(await guardrail.postCall(currentResponse, guardedContext));
const modified = result?.modifiedResponse !== undefined;
const meta = result?.meta || null;

View File

@@ -18,6 +18,7 @@ import {
type BridgeCacheStore,
} from "./modalityBridge/bridgeCache";
import { recordBridgeUse } from "./modalityBridge/bridgeStats";
import { fingerprintVideoTranscriptDescription } from "./videoTranscriptLogRedaction";
import {
composeVideoFramePrompt,
describeVideoPart as defaultDescribeVideoPart,
@@ -714,6 +715,16 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
const videosProcessed = attemptedParts.length - failures;
const videosReplaced = descriptions.filter((description) => description !== null).length;
if (videosReplaced === 0) return { block: false };
const videoTranscriptDescriptionFingerprints = [
...new Set(
descriptions
.filter(
(description): description is string =>
typeof description === "string" && description.includes("transcript[source=")
)
.map(fingerprintVideoTranscriptDescription)
),
].sort();
return {
block: false,
@@ -731,6 +742,9 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
focusWindowsApplied,
focusHintsApplied,
transcriptCuesApplied,
...(videoTranscriptDescriptionFingerprints.length > 0
? { videoTranscriptDescriptionFingerprints }
: {}),
contactSheetsUsed,
audioFusionRuns,
audioFusionPartials,

View File

@@ -0,0 +1,512 @@
import { createHash } from "node:crypto";
export const VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER = "[omitted: video transcript]";
export const VIDEO_TRANSCRIPT_REDACTION_PIPELINE_KEY = "_omnirouteVideoTranscriptRedacted" as const;
const VIDEO_TRANSCRIPT_PAYLOAD_KEYS = new Set(["audioTranscript", "transcript"]);
const VIDEO_TRANSCRIPT_CARRIER_KEYS = new Set(["input_video", "source", "url", "video_url"]);
const VIDEO_BRIDGE_DESCRIPTION_PREFIX = "[Video description:";
// Security bound for the retained copy, deliberately above the default downstream log depth (20).
// Crossing it is unknown-as-sensitive: callers must discard the partial copy instead of leaking it.
const MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_DEPTH = 32;
// Aggregate bound across the whole retained graph, not a per-object width limit. The partial copy
// is discarded on overflow so attacker-controlled breadth cannot produce a large unsafe artifact.
const MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_ENTRIES = 10_000;
const VIDEO_TRANSCRIPT_CUE_PREFIX = "transcript[source=";
const VIDEO_TRANSCRIPT_CUE_SOURCES = ["audio-bridge", "client", "embedded"] as const;
const MAX_VIDEO_TRANSCRIPT_CUE_METADATA_CODE_UNITS = 1024;
const VIDEO_TRANSCRIPT_DESCRIPTION_FINGERPRINT_RE = /^sha256:([a-f0-9]{64}):(\d{1,8})$/;
const MAX_TRUSTED_DESCRIPTION_CODE_UNITS = 16 * 1024 * 1024;
const MAX_TRUSTED_DESCRIPTION_IDENTITIES = 64;
const MAX_TRUSTED_DESCRIPTION_HASH_CODE_UNITS = 64 * 1024 * 1024;
const MAX_TRUSTED_DESCRIPTION_PREFIX_OCCURRENCES = 128;
const MAX_TRUSTED_DESCRIPTION_CANDIDATE_HASHES = 512;
type JsonRecord = Record<string, unknown>;
export interface VideoTranscriptLogContext {
/** SHA-256 identities emitted only for descriptions produced by the Video Bridge guardrail. */
trustedDescriptionFingerprints?: readonly string[];
}
export interface VideoTranscriptLogSensitivityInput {
/** Original client body, before any guardrail is allowed to replace or remove media parts. */
rawRequestBody?: unknown;
/** Current request body after guardrail processing. */
processedBody?: unknown;
/** Validated identities emitted by a successful Video Bridge rewrite. */
trustedDescriptionFingerprints?: readonly string[];
}
export function fingerprintVideoTranscriptDescription(value: string): string {
return `sha256:${createHash("sha256").update(value).digest("hex")}:${value.length}`;
}
/** Keep raw provider text transient while replacing every retained/logged copy. */
export function redactVideoTranscriptSensitiveText(value: string, sensitive: boolean): string {
return sensitive ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : value;
}
type WalkFrame = {
directCarrier: boolean;
depth: number;
value: object;
};
type CloneFrame = WalkFrame & {
target: JsonRecord | unknown[];
};
type CloneMemo = {
carrier?: JsonRecord | unknown[];
ordinary?: JsonRecord | unknown[];
};
type TrustedDescriptionIdentity = {
fingerprint: string;
length: number;
};
type TrustedDescriptionRange = {
end: number;
start: number;
};
type VideoTranscriptCuePrefix = { kind: "valid"; textStart: number } | { kind: "malformed" };
function parseTrustedDescriptionIdentity(value: unknown): TrustedDescriptionIdentity | null {
if (typeof value !== "string") return null;
const match = VIDEO_TRANSCRIPT_DESCRIPTION_FINGERPRINT_RE.exec(value);
if (!match) return null;
const length = Number(match[2]);
if (!Number.isSafeInteger(length) || length <= 0 || length > MAX_TRUSTED_DESCRIPTION_CODE_UNITS) {
return null;
}
return { fingerprint: value, length };
}
function isJsonRecord(value: unknown): value is JsonRecord {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function isWhitespaceCodeUnit(value: string, index: number): boolean {
const codeUnit = value[index];
return codeUnit !== undefined && codeUnit.trim().length === 0;
}
function findNextVideoTranscriptCuePrefix(
value: string,
fromIndex: number,
end: number
): VideoTranscriptCuePrefix | null {
const start = value.indexOf(VIDEO_TRANSCRIPT_CUE_PREFIX, fromIndex);
if (start < 0 || start >= end) return null;
const sourceStart = start + VIDEO_TRANSCRIPT_CUE_PREFIX.length;
const source = VIDEO_TRANSCRIPT_CUE_SOURCES.find((candidate) =>
value.startsWith(candidate, sourceStart)
);
if (!source) return { kind: "malformed" };
const metadataStart = sourceStart + source.length;
const metadataLimit = Math.min(
end,
metadataStart + MAX_VIDEO_TRANSCRIPT_CUE_METADATA_CODE_UNITS + 1
);
let metadataEnd = -1;
for (let cursor = metadataStart; cursor < metadataLimit; cursor += 1) {
if (value[cursor] === "\r" || value[cursor] === "\n") break;
if (value[cursor] === "]") {
metadataEnd = cursor;
break;
}
}
if (metadataEnd < metadataStart) return { kind: "malformed" };
let cursor = metadataEnd + 1;
const whitespaceStart = cursor;
while (cursor < end && isWhitespaceCodeUnit(value, cursor)) cursor += 1;
if (
cursor === whitespaceStart ||
cursor + "text=".length > end ||
!value.startsWith("text=", cursor)
) {
return { kind: "malformed" };
}
return { kind: "valid", textStart: cursor + "text=".length };
}
function findQuotedStringEnd(value: string, quoteStart: number, end: number): number | null {
if (value[quoteStart] !== '"') return null;
let cursor = quoteStart + 1;
while (cursor < end) {
if (value[cursor] === '"') return cursor + 1;
if (value[cursor] === "\\") {
cursor += 2;
} else {
cursor += 1;
}
}
return null;
}
function redactSerializedVideoTranscriptCues(value: string): string {
const pieces: string[] = [];
let cursor = 0;
let searchFrom = 0;
while (searchFrom < value.length) {
const cue = findNextVideoTranscriptCuePrefix(value, searchFrom, value.length);
if (!cue) break;
if (cue.kind === "malformed") return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
const quotedEnd = findQuotedStringEnd(value, cue.textStart, value.length);
if (quotedEnd === null) return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
pieces.push(
value.slice(cursor, cue.textStart),
JSON.stringify(VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER)
);
cursor = quotedEnd;
searchFrom = quotedEnd;
}
// This parser is called only for a description whose exact fingerprint was emitted by the
// Video Bridge after applying at least one transcript cue. No parseable cue therefore means
// malformed trusted data: omit the whole retained range instead of returning it verbatim.
if (pieces.length === 0) return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
pieces.push(value.slice(cursor));
return pieces.join("");
}
/** Accept only identities emitted by a successful server-side Video Bridge rewrite. */
export function extractVideoTranscriptDescriptionFingerprints(results: unknown): string[] {
if (!Array.isArray(results)) return [];
const fingerprints = new Set<string>();
for (const result of results) {
if (!isJsonRecord(result) || result.guardrail !== "video-bridge" || result.modified !== true) {
continue;
}
const meta = isJsonRecord(result.meta) ? result.meta : null;
if (
!meta ||
typeof meta.transcriptCuesApplied !== "number" ||
meta.transcriptCuesApplied <= 0 ||
!Array.isArray(meta.videoTranscriptDescriptionFingerprints)
) {
continue;
}
for (const fingerprint of meta.videoTranscriptDescriptionFingerprints) {
const identity = parseTrustedDescriptionIdentity(fingerprint);
if (identity) fingerprints.add(identity.fingerprint);
}
}
return [...fingerprints].sort();
}
function isRecognizedVideoPart(record: JsonRecord): boolean {
const type = typeof record.type === "string" ? record.type : undefined;
// Explicit modality tags remain security carriers even when the media URL/data is malformed.
// Validation can reject them later, but malformed input must never bypass retained-log policy.
if (
type === "input_video" ||
type === "video" ||
type === "video_source" ||
type === "video_url"
) {
return true;
}
if (Object.hasOwn(record, "input_video") || Object.hasOwn(record, "video_url")) return true;
const source = isJsonRecord(record.source) ? record.source : undefined;
if (!source) return false;
const videoMediaType =
typeof source.media_type === "string" && source.media_type.toLowerCase().startsWith("video/");
return videoMediaType;
}
function findTrustedDescriptionRanges(
value: string,
context: VideoTranscriptLogContext
): TrustedDescriptionRange[] | null {
if (!value.includes(VIDEO_BRIDGE_DESCRIPTION_PREFIX)) return [];
const identities = context.trustedDescriptionFingerprints
?.map(parseTrustedDescriptionIdentity)
.filter((identity): identity is TrustedDescriptionIdentity => identity !== null);
if (!identities || identities.length === 0) return [];
// A successful request can only produce a bounded number of video descriptions.
// If internal metadata violates that contract, omit the whole retained string
// rather than risk leaking a cue or performing attacker-amplified hashing.
if (identities.length > MAX_TRUSTED_DESCRIPTION_IDENTITIES) return null;
const ranges: TrustedDescriptionRange[] = [];
let candidateHashes = 0;
let hashBudget = 0;
let prefixOccurrences = 0;
let start = value.indexOf(VIDEO_BRIDGE_DESCRIPTION_PREFIX);
while (start >= 0) {
prefixOccurrences += 1;
// Bound synchronous work independently of byte length: many tiny forged
// prefixes would otherwise amplify hashing and block the event loop. A
// retained copy fails closed while the live request remains untouched.
if (prefixOccurrences > MAX_TRUSTED_DESCRIPTION_PREFIX_OCCURRENCES) return null;
for (const identity of identities) {
const end = start + identity.length;
if (end > value.length) continue;
candidateHashes += 1;
if (candidateHashes > MAX_TRUSTED_DESCRIPTION_CANDIDATE_HASHES) return null;
hashBudget += identity.length;
if (hashBudget > MAX_TRUSTED_DESCRIPTION_HASH_CODE_UNITS) return null;
const candidate = value.slice(start, end);
if (fingerprintVideoTranscriptDescription(candidate) === identity.fingerprint) {
ranges.push({ end, start });
}
}
start = value.indexOf(VIDEO_BRIDGE_DESCRIPTION_PREFIX, start + 1);
}
ranges.sort((left, right) => left.start - right.start || left.end - right.end);
const merged: TrustedDescriptionRange[] = [];
for (const range of ranges) {
const previous = merged.at(-1);
if (previous && range.start < previous.end) {
previous.end = Math.max(previous.end, range.end);
} else {
merged.push({ ...range });
}
}
return merged;
}
export function omitVideoTranscriptFromLogString(
value: string,
context: VideoTranscriptLogContext = {}
): string {
const ranges = findTrustedDescriptionRanges(value, context);
if (ranges === null) return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
if (ranges.length === 0) return value;
let result = "";
let cursor = 0;
for (const range of ranges) {
result += value.slice(cursor, range.start);
result += redactSerializedVideoTranscriptCues(value.slice(range.start, range.end));
cursor = range.end;
}
return result + value.slice(cursor);
}
/**
* Remove complete trusted Video Bridge descriptions before Memory extraction.
* Logs may retain the non-transcript portion of a description, but durable
* user facts must come only from caller text, never from media-derived prose.
* Exact server-issued fingerprints let adjacent caller text survive even when
* both appear in the same string. Unknown/over-budget input fails closed.
*/
export function omitVideoTranscriptDerivedTextForMemory(
value: string,
context: VideoTranscriptLogContext = {}
): string {
try {
const ranges = findTrustedDescriptionRanges(value, context);
if (ranges === null) return "";
if (ranges.length === 0) return value;
let result = "";
let cursor = 0;
for (const range of ranges) {
result += value.slice(cursor, range.start);
cursor = range.end;
}
return result + value.slice(cursor);
} catch {
return "";
}
}
function fieldIsTranscript(key: string, carrier: boolean): boolean {
return carrier && VIDEO_TRANSCRIPT_PAYLOAD_KEYS.has(key);
}
function childIsDirectCarrier(key: string, carrierParent: boolean, value: unknown): boolean {
return carrierParent && VIDEO_TRANSCRIPT_CARRIER_KEYS.has(key) && isJsonRecord(value);
}
function* enumerableEntries(value: object): Generator<readonly [string, unknown]> {
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
yield [String(index), value[index]] as const;
}
return;
}
const record = value as JsonRecord;
for (const key in record) {
if (Object.hasOwn(record, key)) yield [key, record[key]] as const;
}
}
function walkContains(root: unknown, context: VideoTranscriptLogContext): boolean {
const stack: Array<WalkFrame | { directCarrier: boolean; depth: number; value: string }> = [];
if (typeof root === "function") return true;
if (typeof root === "string") stack.push({ directCarrier: false, depth: 0, value: root });
else if (root && typeof root === "object" && !ArrayBuffer.isView(root)) {
stack.push({ directCarrier: false, depth: 0, value: root });
}
const seen = new WeakMap<object, number>();
let scannedEntries = 0;
while (stack.length > 0) {
const frame = stack.pop()!;
if (frame.depth > MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_DEPTH) return true;
if (typeof frame.value === "string") {
const stringValue = frame.value;
const ranges = findTrustedDescriptionRanges(stringValue, context);
if (ranges === null) return true;
if (
ranges.some(
(range) => findNextVideoTranscriptCuePrefix(stringValue, range.start, range.end) !== null
)
) {
return true;
}
continue;
}
const record = Array.isArray(frame.value) ? null : (frame.value as JsonRecord);
const recognized = record ? isRecognizedVideoPart(record) : false;
const carrier = frame.directCarrier || recognized;
const seenBit = carrier ? 2 : 1;
const previousBits = seen.get(frame.value) ?? 0;
if ((previousBits & seenBit) !== 0) continue;
seen.set(frame.value, previousBits | seenBit);
for (const [key, value] of enumerableEntries(frame.value)) {
scannedEntries += 1;
if (scannedEntries > MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_ENTRIES) return true;
if (fieldIsTranscript(key, carrier)) {
return true;
}
// Functions are not valid JSON request values. In particular, an enumerable toJSON
// function could replace the retained clone with attacker-selected transcript content.
if (typeof value === "function") return true;
if (typeof value === "string") {
stack.push({
directCarrier: fieldIsTranscript(key, carrier),
depth: frame.depth + 1,
value,
});
} else if (value && typeof value === "object" && !ArrayBuffer.isView(value)) {
stack.push({
directCarrier: childIsDirectCarrier(key, carrier, value),
depth: frame.depth + 1,
value,
});
}
}
}
return false;
}
/** Detect transcript data only in recognized video carriers or a trusted bridge description. */
export function containsVideoTranscriptForLog(
value: unknown,
context: VideoTranscriptLogContext = {}
): boolean {
try {
return walkContains(value, context);
} catch {
return true;
}
}
/** Resolve sensitivity once from both sides of the guardrail boundary. */
export function resolveVideoTranscriptLogSensitivity({
rawRequestBody,
processedBody,
trustedDescriptionFingerprints = [],
}: VideoTranscriptLogSensitivityInput): boolean {
const hasTrustedDescription = trustedDescriptionFingerprints.some(
(fingerprint) => parseTrustedDescriptionIdentity(fingerprint) !== null
);
return (
hasTrustedDescription ||
containsVideoTranscriptForLog(rawRequestBody) ||
containsVideoTranscriptForLog(processedBody)
);
}
function prepareClone(
value: unknown,
depth: number,
directCarrier: boolean,
context: VideoTranscriptLogContext,
memo: WeakMap<object, CloneMemo>
): { frame?: CloneFrame; limitExceeded?: boolean; value: unknown } {
if (depth > MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_DEPTH) {
return { limitExceeded: true, value: VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER };
}
if (typeof value === "string") {
return { value: omitVideoTranscriptFromLogString(value, context) };
}
if (typeof value === "function") return { value: "[Function]" };
if (!value || typeof value !== "object") return { value };
if (ArrayBuffer.isView(value)) return { value: `[binary ${value.byteLength} bytes]` };
const record = Array.isArray(value) ? null : (value as JsonRecord);
const carrier = directCarrier || Boolean(record && isRecognizedVideoPart(record));
const memoKey = carrier ? "carrier" : "ordinary";
const previous = memo.get(value)?.[memoKey];
if (previous) return { value: "[Circular]" };
const target: JsonRecord | unknown[] = Array.isArray(value) ? [] : {};
const entry = memo.get(value) ?? {};
entry[memoKey] = target;
memo.set(value, entry);
return { frame: { directCarrier: carrier, depth, target, value }, value: target };
}
function omitVideoTranscriptForLogUnsafe(
payload: unknown,
context: VideoTranscriptLogContext
): unknown {
const memo = new WeakMap<object, CloneMemo>();
const prepared = prepareClone(payload, 0, false, context, memo);
if (prepared.limitExceeded) return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
if (!prepared.frame) return prepared.value;
const stack = [prepared.frame];
let clonedEntries = 0;
while (stack.length > 0) {
const frame = stack.pop()!;
for (const [key, value] of enumerableEntries(frame.value)) {
clonedEntries += 1;
if (clonedEntries > MAX_VIDEO_TRANSCRIPT_LOG_SECURITY_ENTRIES) {
return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
}
const targetKey: string | number = Array.isArray(frame.target) ? Number(key) : key;
if (fieldIsTranscript(key, frame.directCarrier)) {
(frame.target as JsonRecord)[targetKey] = VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
continue;
}
const child = prepareClone(
value,
frame.depth + 1,
childIsDirectCarrier(key, frame.directCarrier, value),
context,
memo
);
if (child.limitExceeded) return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
(frame.target as JsonRecord)[targetKey] = child.value;
if (child.frame) stack.push(child.frame);
}
}
return prepared.value;
}
/** Remove Video Bridge source fields and trusted generated segments from a bounded log copy. */
export function omitVideoTranscriptForLog(
payload: unknown,
context: VideoTranscriptLogContext = {}
): unknown {
try {
return omitVideoTranscriptForLogUnsafe(payload, context);
} catch {
return VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER;
}
}

View File

@@ -1,4 +1,10 @@
import { sanitizePII } from "./piiSanitizer";
import { omitVideoTranscriptForLog } from "./guardrails/videoTranscriptLogRedaction";
export {
omitVideoTranscriptForLog,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} from "./guardrails/videoTranscriptLogRedaction";
const SENSITIVE_KEYS = new Set([
"api_key",
@@ -162,7 +168,8 @@ export function sanitizePayloadPII(payload: unknown): unknown {
export function protectPayloadForLog(payload: unknown): unknown {
if (payload === null || payload === undefined) return null;
const normalized = normalizePayloadForLog(payload);
const reasoningOmitted = omitEncryptedReasoningForLog(normalized);
const transcriptOmitted = omitVideoTranscriptForLog(normalized);
const reasoningOmitted = omitEncryptedReasoningForLog(transcriptOmitted);
const piiSanitized = sanitizePayloadPII(reasoningOmitted);
return redactPayload(piiSanitized);
}

View File

@@ -8,9 +8,25 @@
*/
import { logger } from "../../../open-sse/utils/logger.ts";
import { redactVideoTranscriptSensitiveText } from "../guardrails/videoTranscriptLogRedaction.ts";
const log = logger("PLUGIN_HOOKS");
function isVideoTranscriptSensitiveContext(value: unknown): boolean {
return Boolean(
value &&
typeof value === "object" &&
(value as Record<string, unknown>).videoTranscriptSensitive === true
);
}
function retainedHookError(error: unknown, sensitive: boolean): string {
return redactVideoTranscriptSensitiveText(
error instanceof Error ? error.message : String(error),
sensitive
);
}
// ── Types ──
export type BlockingHookResult = {
@@ -143,6 +159,7 @@ export function unregisterHook(event: string, pluginName: string): void {
export async function emitHook(event: string, payload: unknown): Promise<void> {
const list = hooks.get(event);
if (!list || list.length === 0) return;
const videoTranscriptSensitive = isVideoTranscriptSensitiveContext(payload);
for (const reg of list) {
if (isRateLimited(reg.pluginName)) {
@@ -152,11 +169,10 @@ export async function emitHook(event: string, payload: unknown): Promise<void> {
try {
await reg.handler(payload);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
log.error("hook.handler_error", {
event,
pluginName: reg.pluginName,
error: message,
error: retainedHookError(err, videoTranscriptSensitive),
});
}
}
@@ -178,6 +194,7 @@ export async function emitHookBlocking(
}> {
const list = hooks.get(event) || [];
const ctx = (payload || {}) as Record<string, unknown>;
const videoTranscriptSensitive = isVideoTranscriptSensitiveContext(ctx);
let mergedBody: unknown = ctx.body;
let mergedMetadata: Record<string, unknown> = (ctx.metadata as Record<string, unknown>) || {};
@@ -209,11 +226,10 @@ export async function emitHookBlocking(
}
}
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
log.error("hook.blocking_handler_error", {
event,
pluginName: reg.pluginName,
error: message,
error: retainedHookError(err, videoTranscriptSensitive),
});
}
}
@@ -234,6 +250,8 @@ export interface PluginContext {
* client (trace ids, correlation ids, session markers). */
headers?: Record<string, string | string[] | undefined>;
metadata: Record<string, unknown>;
/** Server-owned retention bit; plugins still receive the live body as trusted processors. */
videoTranscriptSensitive?: boolean;
}
export interface PluginResult {
@@ -333,6 +351,7 @@ export async function runOnRequest(ctx: PluginContext): Promise<PluginResult> {
export async function runOnResponse(ctx: PluginContext, response: unknown): Promise<unknown> {
let currentResponse = response;
const list = hooks.get("onResponse") || [];
const videoTranscriptSensitive = ctx.videoTranscriptSensitive === true;
for (const reg of list) {
try {
const result = await reg.handler({ ...ctx, response: currentResponse });
@@ -345,8 +364,10 @@ export async function runOnResponse(ctx: PluginContext, response: unknown): Prom
currentResponse = (result as { response: unknown }).response;
}
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
log.error("hook.response_handler_error", { pluginName: reg.pluginName, error: message });
log.error("hook.response_handler_error", {
pluginName: reg.pluginName,
error: retainedHookError(err, videoTranscriptSensitive),
});
}
}
return currentResponse;

View File

@@ -11,16 +11,32 @@ export type PendingRequestScope = {
model: string;
provider: string;
connectionId: string | null;
videoTranscriptSensitive?: boolean;
videoTranscriptDescriptionFingerprints?: readonly string[];
};
export function updatePendingScope(scope: PendingRequestScope, metadata: PendingRequestMetadata) {
if (!updatePendingRequestById(scope.id || null, metadata)) {
updatePendingRequest(scope.model, scope.provider, scope.connectionId, metadata);
const protectedMetadata = {
...metadata,
...(scope.videoTranscriptSensitive ? { videoTranscriptSensitive: true } : {}),
...(scope.videoTranscriptDescriptionFingerprints?.length
? { videoTranscriptDescriptionFingerprints: scope.videoTranscriptDescriptionFingerprints }
: {}),
};
if (!updatePendingRequestById(scope.id || null, protectedMetadata)) {
updatePendingRequest(scope.model, scope.provider, scope.connectionId, protectedMetadata);
}
}
export function finalizePendingScope(scope: PendingRequestScope, metadata: PendingRequestMetadata) {
if (!finalizePendingRequestById(scope.id, metadata)) {
finalizePendingRequest(scope.model, scope.provider, scope.connectionId, metadata);
const protectedMetadata = {
...metadata,
...(scope.videoTranscriptSensitive ? { videoTranscriptSensitive: true } : {}),
...(scope.videoTranscriptDescriptionFingerprints?.length
? { videoTranscriptDescriptionFingerprints: scope.videoTranscriptDescriptionFingerprints }
: {}),
};
if (!finalizePendingRequestById(scope.id, protectedMetadata)) {
finalizePendingRequest(scope.model, scope.provider, scope.connectionId, protectedMetadata);
}
}

View File

@@ -8,6 +8,11 @@
*/
import { getDbInstance } from "../db/core";
import {
omitVideoTranscriptForLog,
type VideoTranscriptLogContext,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} from "../guardrails/videoTranscriptLogRedaction";
import { protectPayloadForLog } from "../logPayloads";
import {
resolveOrphanedUsageAccountIdentity,
@@ -55,6 +60,10 @@ export type PendingRequestMetadata = {
stageUpdatedAt?: number | null;
correlationId?: string | null;
sessionTag?: string | null;
/** Trusted request state; consumed during normalization and never retained. */
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of generated Video descriptions; never retained. */
videoTranscriptDescriptionFingerprints?: readonly string[];
};
export type PendingRequestDetail = {
id: string;
@@ -88,6 +97,14 @@ function normalizePendingMetadata(metadata?: PendingRequestMetadata): PendingReq
if (!metadata) return {};
const normalized: PendingRequestMetadata = {};
const transcriptSensitive = metadata.videoTranscriptSensitive === true;
const descriptionContext: VideoTranscriptLogContext = {
trustedDescriptionFingerprints: metadata.videoTranscriptDescriptionFingerprints ?? [],
};
const protectRequest = (value: unknown, logContext: VideoTranscriptLogContext = {}): unknown =>
truncatePendingPreview(protectPayloadForLog(omitVideoTranscriptForLog(value, logContext)));
const protectResponse = (value: unknown): unknown =>
transcriptSensitive ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : protectRequest(value);
if (metadata.clientEndpoint !== undefined) {
normalized.clientEndpoint = toStringOrNull(metadata.clientEndpoint) || null;
@@ -106,29 +123,25 @@ function normalizePendingMetadata(metadata?: PendingRequestMetadata): PendingReq
: null;
}
if (metadata.clientRequest !== undefined) {
normalized.clientRequest = truncatePendingPreview(protectPayloadForLog(metadata.clientRequest));
normalized.clientRequest = protectRequest(metadata.clientRequest, {});
}
if (metadata.providerRequest !== undefined) {
normalized.providerRequest = truncatePendingPreview(
protectPayloadForLog(metadata.providerRequest)
);
normalized.providerRequest = protectRequest(metadata.providerRequest, descriptionContext);
}
if (metadata.providerResponse !== undefined) {
normalized.providerResponse = truncatePendingPreview(
protectPayloadForLog(metadata.providerResponse)
);
normalized.providerResponse = protectResponse(metadata.providerResponse);
}
if (metadata.clientResponse !== undefined) {
normalized.clientResponse = truncatePendingPreview(
protectPayloadForLog(metadata.clientResponse)
);
normalized.clientResponse = protectResponse(metadata.clientResponse);
}
if (metadata.status !== undefined) {
const status = Number(metadata.status);
normalized.status = Number.isFinite(status) ? status : null;
}
if (metadata.error !== undefined) {
normalized.error = toStringOrNull(metadata.error) || null;
normalized.error = transcriptSensitive
? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER
: toStringOrNull(metadata.error) || null;
}
if (metadata.errorCode !== undefined) {
normalized.errorCode = toStringOrNull(metadata.errorCode) || null;

View File

@@ -6,6 +6,11 @@ export { buildClientRawRequest, resolveDispatchClientRawRequest };
import { normalizeReasoningRequest } from "@/shared/reasoning/effortStandardization";
import { isDetailedLoggingEnabled } from "@/lib/db/detailedLogs";
import { resolvePreviousResponseState } from "@/lib/db/responsesContinuationStore";
import {
extractVideoTranscriptDescriptionFingerprints,
redactVideoTranscriptSensitiveText,
resolveVideoTranscriptLogSensitivity,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
import { normalizeResponsesPreviousResponseIdMode } from "@omniroute/open-sse/utils/responsesStatePolicy.ts";
import { FORMATS } from "@omniroute/open-sse/translator/formats.ts";
import { resolveRoutingModel, RoutingModelOps } from "./resolveRoutingModel";
@@ -102,6 +107,7 @@ import { resolveConversationId } from "@omniroute/open-sse/services/conversation
import {
isAntigravityMissingProjectError,
isProviderBreakerFailureStatus,
resolveDailyQuotaModelViews,
resolveStreamReadinessClassificationError,
shouldTripProviderBreakerForResult,
} from "./chatPredicates";
@@ -690,6 +696,10 @@ async function handleChatImplementation(
clientRawRequest = chatAdmission.resolveClientRawAfterAdmission(clientRawRequest, () =>
deferredClientRawBody.withClientBody((clientBody) => buildClientRawRequest(request, clientBody))
);
let videoTranscriptSensitive = resolveVideoTranscriptLogSensitivity({
rawRequestBody: clientRawRequest?.body,
processedBody: body,
});
// Guardrail pre-call pipeline — prompt injection, PII masking, and future custom rules.
telemetry.startPhase("validate");
@@ -707,11 +717,15 @@ async function handleChatImplementation(
model: modelStr,
signal: request.signal,
stream: body?.stream === true,
videoTranscriptSensitive,
});
if (preCallGuardrails.blocked) {
log.warn("GUARDRAIL", "Request blocked during pre-call guardrails", {
guardrail: preCallGuardrails.guardrail,
message: preCallGuardrails.message,
message: redactVideoTranscriptSensitiveText(
preCallGuardrails.message || "Request rejected by guardrail",
videoTranscriptSensitive
),
});
return errorResponse(
HTTP_STATUS.BAD_REQUEST,
@@ -722,6 +736,14 @@ async function handleChatImplementation(
const modelBeforeGuardrails =
typeof body?.model === "string" && body.model.length > 0 ? body.model : modelStr;
body = preCallGuardrails.payload;
const videoTranscriptDescriptionFingerprints = extractVideoTranscriptDescriptionFingerprints(
preCallGuardrails.results
);
videoTranscriptSensitive = resolveVideoTranscriptLogSensitivity({
rawRequestBody: clientRawRequest?.body,
processedBody: body,
trustedDescriptionFingerprints: videoTranscriptDescriptionFingerprints,
});
({ body, modelStr } = await RoutingModelOps.reconcileGuardrailReroute({
body,
modelBeforeGuardrails,
@@ -757,7 +779,10 @@ async function handleChatImplementation(
// already treat null/undefined as "untracked" (see withConversationId).
log.warn("CHAT", "resolveConversationId failed, continuing without conversation tracking", {
correlationId: reqId,
error: error instanceof Error ? error.message : String(error),
error: redactVideoTranscriptSensitiveText(
error instanceof Error ? error.message : String(error),
videoTranscriptSensitive
),
});
}
@@ -1041,6 +1066,8 @@ async function handleChatImplementation(
sourceFormat,
endpointPath: new URL(request.url).pathname,
requestHeaders: request.headers,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
clientManagedResponsesContext:
sourceFormat === "openai-responses" &&
new URL(request.url).pathname.split("/").includes("responses") &&
@@ -1094,6 +1121,8 @@ async function handleChatImplementation(
reasoningIntent,
reasoningRequestTags: requestRoutingTags.tags,
managedLease,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
// #7360 follow-up: without this, a target dispatch abandoned by
// targetTimeoutRunner.ts's per-target timeout (comboTargetTimeoutMs)
// never learns it was abandoned — it only watches the ORIGINAL
@@ -1164,6 +1193,8 @@ async function handleChatImplementation(
forceLiveComboTest: isComboLiveTest,
conversationId,
managedLease,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
},
combo.strategy,
true
@@ -1181,7 +1212,13 @@ async function handleChatImplementation(
`Global fallback ${fallbackModel} also failed (${fallbackResponse.status})`
);
} catch (err: any) {
log.warn("GLOBAL_FALLBACK", `Global fallback error: ${err?.message || "unknown"}`);
log.warn(
"GLOBAL_FALLBACK",
`Global fallback error: ${redactVideoTranscriptSensitiveText(
err?.message || "unknown",
videoTranscriptSensitive
)}`
);
}
}
// ─────────────────────────────────────────────────────────────────────────
@@ -1209,6 +1246,7 @@ async function handleChatImplementation(
sessionTag: conversationId,
startTime: telemetry?.startTime,
requestBody: clientRawRequest?.body ?? null,
videoTranscriptSensitive,
});
} catch {}
}
@@ -1257,6 +1295,8 @@ async function handleChatImplementation(
reasoningIntent,
reasoningRequestTags: requestRoutingTags.tags,
managedLease,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
},
null,
false
@@ -1305,6 +1345,10 @@ async function handleSingleModelChat(
reasoningRequestTags?: string[];
reasoningTransportFallback?: "skip" | "drop";
managedLease?: ManagedLeaseDispatchContext | null;
/** Sensitive carrier/result state derived structurally, never from caller prose. */
videoTranscriptSensitive?: boolean;
/** Exact SHA-256 identities of transcript descriptions generated by the guardrail. */
videoTranscriptDescriptionFingerprints?: readonly string[];
/**
* Per-target abort signal from combo.ts's targetTimeoutRunner
* (comboTargetTimeoutMs) — see the #7360 follow-up comment at the
@@ -1316,6 +1360,9 @@ async function handleSingleModelChat(
comboStrategy: string | null = null,
isCombo: boolean = false
) {
const videoTranscriptDescriptionFingerprints =
runtimeOptions.videoTranscriptDescriptionFingerprints ?? [];
// 1. Resolve model → provider/model
const resolved = await resolveModelOrError(
modelStr,
@@ -1351,6 +1398,8 @@ async function handleSingleModelChat(
sourceFormat: sNetSourceFormat,
endpointPath: clientRawRequest?.endpoint || "",
requestHeaders: clientRawRequest?.headers,
videoTranscriptSensitive: runtimeOptions.videoTranscriptSensitive === true,
videoTranscriptDescriptionFingerprints,
clientManagedResponsesContext:
sNetSourceFormat === "openai-responses" &&
String(clientRawRequest?.endpoint || "")
@@ -1382,6 +1431,8 @@ async function handleSingleModelChat(
redirectCombo.config?.reasoningTransportFallback === "skip" ? "skip" : "drop",
conversationId: runtimeOptions?.conversationId ?? null,
managedLease: runtimeOptions.managedLease ?? null,
videoTranscriptSensitive: runtimeOptions.videoTranscriptSensitive === true,
videoTranscriptDescriptionFingerprints,
// #7360 follow-up — see the primary handleSingleModel closure above.
modelAbortSignal: target?.modelAbortSignal ?? null,
},
@@ -1486,6 +1537,7 @@ async function handleSingleModelChat(
correlationId: runtimeOptions?.correlationId ?? null,
sessionTag: runtimeOptions?.conversationId ?? null,
startTime: telemetry?.startTime,
videoTranscriptSensitive: runtimeOptions.videoTranscriptSensitive === true,
});
} catch {}
return gate;
@@ -1693,7 +1745,8 @@ async function handleSingleModelChat(
lastError,
lastStatus,
candidateAliases,
isCombo
isCombo,
runtimeOptions.videoTranscriptSensitive === true
);
const lastFailedConnectionId =
excludedConnectionIds.size > 0
@@ -1853,6 +1906,8 @@ async function handleSingleModelChat(
sessionAffinityKey: runtimeOptions.sessionAffinityKey ?? null,
reasoningTransportFallback: runtimeOptions.reasoningTransportFallback ?? "drop",
managedLease: runtimeOptions.managedLease ?? null,
videoTranscriptSensitive: runtimeOptions.videoTranscriptSensitive === true,
videoTranscriptDescriptionFingerprints,
},
runtimeOptions
);
@@ -1888,6 +1943,7 @@ async function handleSingleModelChat(
comboName,
clientRawRequest,
tlsFingerprintUsed,
videoTranscriptSensitive: runtimeOptions.videoTranscriptSensitive === true,
});
if (result.success) {
@@ -1990,7 +2046,13 @@ async function handleSingleModelChat(
provider,
model,
providerProfile,
{ isCombo }
{
isCombo,
retainedErrorText: redactVideoTranscriptSensitiveText(
classificationError,
runtimeOptions.videoTranscriptSensitive === true
),
}
);
if (shouldFallback && !hasForcedConnection) {
@@ -2039,7 +2101,13 @@ async function handleSingleModelChat(
provider,
model,
providerProfile,
{ isCombo }
{
isCombo,
retainedErrorText: redactVideoTranscriptSensitiveText(
result.error || ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE,
runtimeOptions.videoTranscriptSensitive === true
),
}
);
if (shouldFallback && !hasForcedConnection) {
@@ -2177,9 +2245,14 @@ async function handleSingleModelChat(
: classify429FromError({ status: result.status, message: errorStr })
: undefined;
if (result.status === 429 && isDailyQuotaExhausted(errorStr)) {
// Parse which model is quota-limited
const match = errorStr.match(/today's quota for model ([^,]+)/);
const limitedModel = match ? match[1].trim() : model;
// Keep the raw provider token for routing state, but never retain it when a sensitive
// upstream diagnostic could have echoed transcript text.
const { operationalModel: limitedModel, retainedModel: retainedLimitedModel } =
resolveDailyQuotaModelViews(
errorStr,
model,
runtimeOptions.videoTranscriptSensitive === true
);
const mlSettings = resolveModelLockoutSettings(runtimeOptions.cachedSettings);
if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) {
@@ -2200,7 +2273,7 @@ async function handleSingleModelChat(
"MODEL_DAILY_QUOTA",
JSON.stringify({
connection: credentials.connectionId.slice(0, 8),
model: limitedModel,
model: retainedLimitedModel,
cooldownMs: lockResult.cooldownMs,
failureCount: lockResult.failureCount,
})
@@ -2291,6 +2364,10 @@ async function handleSingleModelChat(
(failureKind === "rate_limit" || failureKind === "transient")
),
isCombo,
retainedErrorText: redactVideoTranscriptSensitiveText(
errorStr,
runtimeOptions.videoTranscriptSensitive === true
),
}
);

View File

@@ -28,6 +28,7 @@ import {
} from "@omniroute/open-sse/utils/proxyFetch.ts";
import { resolveProxyForConnection } from "@/lib/localDb";
import { hasBlockingProxyAssignment } from "@/lib/db/proxies";
import { redactVideoTranscriptSensitiveText } from "@/lib/guardrails/videoTranscriptLogRedaction";
import {
CircuitBreakerOpenError,
getCircuitBreaker,
@@ -36,10 +37,10 @@ import {
import { classify429FromError, type FailureKind } from "../../shared/utils/classify429";
import { resolveUseUpstream429BreakerHints } from "../../shared/utils/providerHints";
import { logProxyEvent } from "../../lib/proxyLogger";
import { logTranslationEvent } from "../../lib/translatorEvents";
import { getRuntimeProviderProfile } from "@omniroute/open-sse/services/accountFallback.ts";
export { safeLogEvents } from "./chatLogEvents";
// Models that explicitly cannot run on the codex/ChatGPT-Pro OAuth pool — when
// a caller writes `codex/deepseek-v4-pro` we transparently reroute to the
// canonical provider whose API key is configured. Saves callers from having
@@ -391,7 +392,6 @@ export function checkResourcePressureBeforeProviderWork(): ResourcePressureGuard
return null;
}
}
export async function executeChatWithBreaker({
bypassCircuitBreaker,
breaker,
@@ -425,6 +425,8 @@ export async function executeChatWithBreaker({
reasoningTransportFallback = "drop",
sessionAffinityKey = null,
managedLease = null,
videoTranscriptSensitive = false,
videoTranscriptDescriptionFingerprints = [],
}: ExecuteChatWithBreakerOptions): Promise<ExecuteChatWithBreakerResult> {
let tlsFingerprintUsed = false;
const normalizedTrafficType: TrafficType =
@@ -432,7 +434,6 @@ export async function executeChatWithBreaker({
? "shadow"
: "production";
const isShadowTraffic = normalizedTrafficType === "shadow";
// #5217: capture the proxy actually applied during execution so the caller can
// merge it into proxyInfo before the egress log (executors pinning a per-account
// proxy internally otherwise leave the egress log reading "direct").
@@ -484,6 +485,8 @@ export async function executeChatWithBreaker({
sessionAffinityKey,
reasoningTransportFallback,
managedLease,
videoTranscriptSensitive,
videoTranscriptDescriptionFingerprints,
skipResourcePressureGuard: true,
onCredentialsRefreshed: async (newCreds: any) => {
await updateProviderCredentials(credentials.connectionId, {
@@ -537,7 +540,13 @@ export async function executeChatWithBreaker({
provider,
model,
providerProfile,
{ isCombo }
{
isCombo,
retainedErrorText: redactVideoTranscriptSensitiveText(
String(failure?.message || failure?.code || "stream failure"),
videoTranscriptSensitive
),
}
);
},
})
@@ -609,7 +618,7 @@ export async function executeChatWithBreaker({
if (cbErr?.code === "PROXY_UNREACHABLE" || /proxy unreachable/i.test(cbErr?.message || "")) {
const detail = cbErr?.message || "Proxy unreachable";
log.warn("PROXY", detail);
log.warn("PROXY", redactVideoTranscriptSensitiveText(detail, videoTranscriptSensitive));
return {
result: {
success: false,
@@ -633,7 +642,8 @@ export function handleNoCredentials(
lastError: string | null,
lastStatus: number | null,
candidateAliases?: readonly string[],
isCombo: boolean = false
isCombo: boolean = false,
videoTranscriptSensitive: boolean = false
) {
if (credentials?.allRateLimited) {
const errorMsg = lastError || credentials.lastError || "Unavailable";
@@ -660,7 +670,8 @@ export function handleNoCredentials(
});
}
log.warn("CHAT", `[${provider}/${model}] ${errorMsg} (${credentials.retryAfterHuman})`);
const retainedErrorMsg = redactVideoTranscriptSensitiveText(errorMsg, videoTranscriptSensitive);
log.warn("CHAT", `[${provider}/${model}] ${retainedErrorMsg} (${credentials.retryAfterHuman})`);
return unavailableResponse(
status,
`[${provider}/${model}] ${errorMsg}`,
@@ -845,82 +856,6 @@ export function applyExecutorProxyToInfo(
};
}
// Async because the egress-IP lookup lazy-imports proxyEgress; callers treat
// this as fire-and-forget logging (the internal try/catch swallows everything).
export async function safeLogEvents({
result,
proxyInfo,
proxyLatency,
provider,
model,
sourceFormat,
targetFormat,
credentials,
comboName,
clientRawRequest,
tlsFingerprintUsed = false,
}) {
try {
const rawIp =
clientRawRequest?.headers?.["x-forwarded-for"] ||
clientRawRequest?.headers?.["x-real-ip"] ||
clientRawRequest?.headers?.["cf-connecting-ip"] ||
null;
const rawIpValue = Array.isArray(rawIp) ? rawIp[0] : rawIp;
const clientIp = typeof rawIpValue === "string" ? rawIpValue.split(",")[0].trim() : null;
// Resolve the egress IP (the IP the upstream actually saw) from cache — never
// blocking the request. Warm it in the background for next time. null until
// the first warm completes; direct (no proxy) is also tracked.
let egressIp: string | null = null;
try {
const { getCachedEgressIp, warmEgressIp } = await import("../../lib/proxyEgress");
const { proxyConfigToUrl } = await import("@omniroute/open-sse/utils/proxyDispatcher.ts");
const proxyUrl = proxyInfo?.proxy ? proxyConfigToUrl(proxyInfo.proxy) : null;
egressIp = getCachedEgressIp(proxyUrl);
warmEgressIp(proxyUrl);
} catch {
// egress visibility is best-effort; never break the request path
}
logProxyEvent({
status: result.success
? "success"
: result.status === 408 || result.status === 504
? "timeout"
: "error",
proxy: proxyInfo?.proxy || null,
level: proxyInfo?.level || "direct",
levelId: proxyInfo?.levelId || null,
provider,
targetUrl: `${provider}/${model}`,
clientIp,
egressIp,
latencyMs: proxyLatency,
error: result.success ? null : result.error || null,
connectionId: credentials.connectionId,
comboId: comboName || null,
account: credentials.connectionId?.slice(0, 8) || null,
tlsFingerprint: tlsFingerprintUsed,
});
} catch {}
try {
logTranslationEvent({
provider,
model,
sourceFormat,
targetFormat,
status: result.success ? "success" : "error",
statusCode: result.success ? 200 : result.status || 500,
latency: proxyLatency,
endpoint: clientRawRequest?.endpoint || "/v1/chat/completions",
connectionId: credentials.connectionId || null,
comboName: comboName || null,
});
} catch {}
}
export function withSessionHeader(response: Response, sessionId: string | null): Response {
if (!response || !sessionId) return response;

View File

@@ -0,0 +1,107 @@
import { redactVideoTranscriptSensitiveText } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { logProxyEvent } from "../../lib/proxyLogger";
import { logTranslationEvent } from "../../lib/translatorEvents";
type ProxyLogConfig = {
host: string;
port: number | string;
type: string;
};
export interface SafeLogEventsInput {
clientRawRequest?: {
endpoint?: string;
headers?: Record<string, string | string[] | undefined>;
} | null;
comboName?: string | null;
credentials: { connectionId?: string | null };
model: string;
provider: string;
proxyInfo?: {
level?: string;
levelId?: string | null;
proxy?: ProxyLogConfig | null;
} | null;
proxyLatency: number;
result: { error?: unknown; status?: number; success: boolean };
sourceFormat: string;
targetFormat: string;
tlsFingerprintUsed?: boolean;
videoTranscriptSensitive?: boolean;
}
/** Retain safe proxy/translation metadata without retaining a sensitive provider error echo. */
export async function safeLogEvents({
result,
proxyInfo,
proxyLatency,
provider,
model,
sourceFormat,
targetFormat,
credentials,
comboName,
clientRawRequest,
tlsFingerprintUsed = false,
videoTranscriptSensitive = false,
}: SafeLogEventsInput): Promise<void> {
try {
const rawIp =
clientRawRequest?.headers?.["x-forwarded-for"] ||
clientRawRequest?.headers?.["x-real-ip"] ||
clientRawRequest?.headers?.["cf-connecting-ip"] ||
null;
const rawIpValue = Array.isArray(rawIp) ? rawIp[0] : rawIp;
const clientIp = typeof rawIpValue === "string" ? rawIpValue.split(",")[0].trim() : null;
let egressIp: string | null = null;
try {
const { getCachedEgressIp, warmEgressIp } = await import("../../lib/proxyEgress");
const { proxyConfigToUrl } = await import("@omniroute/open-sse/utils/proxyDispatcher.ts");
const proxyUrl = proxyInfo?.proxy ? proxyConfigToUrl(proxyInfo.proxy) : null;
egressIp = getCachedEgressIp(proxyUrl);
warmEgressIp(proxyUrl);
} catch {
// Egress visibility is best-effort and never breaks the request path.
}
logProxyEvent({
account: credentials.connectionId?.slice(0, 8) || null,
clientIp,
comboId: comboName || null,
connectionId: credentials.connectionId,
egressIp,
error:
result.success || !result.error
? null
: redactVideoTranscriptSensitiveText(String(result.error), videoTranscriptSensitive),
latencyMs: proxyLatency,
level: proxyInfo?.level || "direct",
levelId: proxyInfo?.levelId || null,
provider,
proxy: proxyInfo?.proxy || null,
status: result.success
? "success"
: result.status === 408 || result.status === 504
? "timeout"
: "error",
targetUrl: `${provider}/${model}`,
tlsFingerprint: tlsFingerprintUsed,
});
} catch {}
try {
logTranslationEvent({
comboName: comboName || null,
connectionId: credentials.connectionId || null,
endpoint: clientRawRequest?.endpoint || "/v1/chat/completions",
latency: proxyLatency,
model,
provider,
sourceFormat,
status: result.success ? "success" : "error",
statusCode: result.success ? 200 : result.status || 500,
targetFormat,
});
} catch {}
}

View File

@@ -1,6 +1,8 @@
import { getTrustedLocalRateLimitResponse } from "@omniroute/open-sse/services/rateLimitManager/errors";
import { redactVideoTranscriptSensitiveText } from "@/lib/guardrails/videoTranscriptLogRedaction";
import { isLocalStreamLifecycleError } from "../../shared/utils/circuitBreaker";
import { isRequestScopedUpstreamFailure } from "./comboFailureLogging";
import { getTrustedLocalRateLimitResponse } from "@omniroute/open-sse/services/rateLimitManager/errors";
export const PROVIDER_BREAKER_FAILURE_STATUSES = new Set([408, 500, 502, 503, 504]);
@@ -69,3 +71,28 @@ export function resolveStreamReadinessClassificationError(
}
return fallback;
}
export interface DailyQuotaModelViews {
/** Exact provider token used by model-lockout state and future routing decisions. */
operationalModel: string;
/** Safe representation allowed in retained application logs. */
retainedModel: string;
}
/** Keep routing semantics raw while separating the retained representation at the parse seam. */
export function resolveDailyQuotaModelViews(
errorText: string,
resolvedModel: string,
videoTranscriptSensitive: boolean
): DailyQuotaModelViews {
const match = errorText.match(/today's quota for model ([^,]+)/);
const upstreamModel = match?.[1]?.trim();
const operationalModel = upstreamModel || resolvedModel;
return {
operationalModel,
retainedModel: redactVideoTranscriptSensitiveText(
operationalModel,
videoTranscriptSensitive && Boolean(upstreamModel)
),
};
}

View File

@@ -18,6 +18,11 @@
* never turn into a second failure on the response path.
*/
import { saveCallLog, saveRequestUsage } from "@/lib/usageDb";
import {
containsVideoTranscriptForLog,
omitVideoTranscriptForLog,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} from "@/lib/guardrails/videoTranscriptLogRedaction";
export interface RejectedRequestUsageInput {
status: number;
@@ -44,6 +49,8 @@ export interface RejectedRequestUsageInput {
* dashboard log detail had no request to inspect — see #7360 follow-up.
*/
requestBody?: unknown;
/** Trusted request-scoped signal; raw text remains available to routing before this boundary. */
videoTranscriptSensitive?: boolean;
}
export async function recordRejectedRequestUsage(input: RejectedRequestUsageInput): Promise<void> {
@@ -64,10 +71,20 @@ export async function recordRejectedRequestUsage(input: RejectedRequestUsageInpu
connectionId = undefined,
startTime,
requestBody = null,
videoTranscriptSensitive = false,
} = input;
const now = Date.now();
const duration = typeof startTime === "number" ? now - startTime : 0;
// This sink owns a request body and must defend itself if an upstream caller forgets the
// request-scoped bit. The explicit bit is still required after a guardrail replaced the raw
// carrier with generated text, which is why both sources participate.
const transcriptSensitive =
videoTranscriptSensitive || containsVideoTranscriptForLog(requestBody);
const retainedError = transcriptSensitive ? VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER : error || null;
const retainedRequestBody = transcriptSensitive
? omitVideoTranscriptForLog(requestBody)
: requestBody;
// 1. call_logs — preserves /dashboard/logs visibility (unchanged behavior).
await saveCallLog({
@@ -81,8 +98,8 @@ export async function recordRejectedRequestUsage(input: RejectedRequestUsageInpu
connectionId,
duration,
tokens: {},
error: error || null,
requestBody,
error: retainedError,
requestBody: retainedRequestBody,
comboName,
comboStepId,
comboExecutionKey,

View File

@@ -2607,10 +2607,11 @@ export async function markAccountUnavailable(
providerProfile = null,
options: {
persistUnavailableState?: boolean;
/** Caller is the combo engine — it records its own model-level lockouts. */
isCombo?: boolean;
retainedErrorText?: string;
} = {}
) {
const retainedErrorText = options.retainedErrorText ?? errorText;
const currentMutex = markMutexes.get(connectionId) || Promise.resolve();
let resolveMutex: (() => void) | undefined;
markMutexes.set(
@@ -2619,10 +2620,8 @@ export async function markAccountUnavailable(
resolveMutex = resolve;
})
);
try {
await currentMutex;
// STRICT_ZERO_COST: this connection just failed (whatever the reason) —
// drop any cached "SAFE" free-allowance reading for it immediately rather
// than waiting out the TTL, so the very next candidate-pool build reads a
@@ -2736,14 +2735,13 @@ export async function markAccountUnavailable(
// the opt-in setting probeCanDisable restores the historical behavior.
if (await shouldIsolateProbeFailures()) {
await updateProviderConnection(connectionId, {
// lastError kept RAW (full text) — maximal probe visibility; the
// divergence vs the normal path's slice(0,100) is intentional.
// Probe visibility keeps full retained text; sensitive callers supply an omission marker.
// backoffLevel is deliberately NOT written: a positive backoff
// triggers the selection-time auto-decay (resetConnectionBackoff,
// auth.ts getProviderCredentials) which wipes lastError back to
// NULL on the next attempt — silently destroying the probe record.
// The backoff is also routing state a probe must not touch (#9817).
lastError: errorText,
lastError: retainedErrorText,
lastErrorType: fallbackResult.reason || null,
errorCode: status,
lastErrorAt: new Date().toISOString(),
@@ -3148,7 +3146,7 @@ export async function markAccountUnavailable(
return { shouldFallback: true, cooldownMs: lockout.cooldownMs };
}
const errorMsg = describeUpstreamFailure(errorText);
const errorMsg = describeUpstreamFailure(retainedErrorText);
// T09: Codex per-scope lockout (do not block the whole account globally).
if (

View File

@@ -0,0 +1,41 @@
import assert from "node:assert/strict";
import test from "node:test";
import { resolveDailyQuotaModelViews } from "../../src/sse/handlers/chatPredicates.ts";
test("daily quota model uses the upstream model token for ordinary operational and retained views", () => {
assert.deepEqual(
resolveDailyQuotaModelViews(
"You have exceeded today's quota for model moonshotai/Kimi-K2.5, try tomorrow",
"Kimi-K2.5",
false
),
{
operationalModel: "moonshotai/Kimi-K2.5",
retainedModel: "moonshotai/Kimi-K2.5",
}
);
});
test("daily quota keeps the raw operational key but redacts its retained sensitive view", () => {
const transcriptSentinel = "PRIVATE_DAILY_QUOTA_TRANSCRIPT_SENTINEL";
const views = resolveDailyQuotaModelViews(
`You have exceeded today's quota for model ${transcriptSentinel}, try tomorrow`,
"server-resolved-model",
true
);
assert.equal(views.operationalModel, transcriptSentinel);
assert.equal(views.retainedModel, "[omitted: video transcript]");
assert.equal(views.retainedModel.includes(transcriptSentinel), false);
});
test("daily quota model falls back to the server-resolved model when no token is present", () => {
assert.deepEqual(
resolveDailyQuotaModelViews("daily quota exhausted", "server-resolved-model", true),
{
operationalModel: "server-resolved-model",
retainedModel: "server-resolved-model",
}
);
});

View File

@@ -6,7 +6,11 @@ import path from "node:path";
import net from "node:net";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chat-helpers-"));
const TEST_APP_LOG_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chat-helpers-log-"));
const TEST_APP_LOG_PATH = path.join(TEST_APP_LOG_DIR, "app.log");
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.APP_LOG_TO_FILE = "true";
process.env.APP_LOG_FILE_PATH = TEST_APP_LOG_PATH;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
@@ -22,6 +26,8 @@ const {
} = await import("../../src/sse/handlers/chatHelpers.ts");
const { getCircuitBreaker, resetAllCircuitBreakers, STATE } =
await import("../../src/shared/utils/circuitBreaker.ts");
const proxyLogger = await import("../../src/lib/proxyLogger.ts");
const { logger: appLogger } = await import("../../src/shared/utils/logger.ts");
// DATA_DIR must be fixed before these modules load; keep this test seam dynamic.
const { setTlsClientForTest } = await import("../../open-sse/utils/proxyFetch.ts");
@@ -32,6 +38,31 @@ async function resetStorage() {
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function readAppLogWhen(
predicate: (contents: string) => boolean,
timeoutMs = 4_000
): Promise<string> {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const contents = fs.existsSync(TEST_APP_LOG_PATH)
? fs.readFileSync(TEST_APP_LOG_PATH, "utf8")
: "";
if (predicate(contents)) return contents;
await new Promise((resolve) => setTimeout(resolve, 50));
}
return fs.existsSync(TEST_APP_LOG_PATH) ? fs.readFileSync(TEST_APP_LOG_PATH, "utf8") : "";
}
async function flushAppLogger(): Promise<void> {
await new Promise<void>((resolveFlush) => {
try {
appLogger.flush(() => resolveFlush());
} catch {
resolveFlush();
}
});
}
async function seedConnection(provider, overrides = {}) {
return providersDb.createProviderConnection({
provider,
@@ -420,6 +451,42 @@ test("handleNoCredentials returns Retry-After when every account is rate limited
assert.match(json.error.message, /\[openai\/gpt-4o-mini\] Quota exceeded/);
});
test("handleNoCredentials omits transcript-sensitive upstream text from application logs", async () => {
const rawCue = "PRIVATE_NO_CREDENTIALS_VIDEO_TRANSCRIPT_SENTINEL";
const provider = `fu05-${Math.random().toString(16).slice(2)}`;
const response = handleNoCredentials(
{
allRateLimited: true,
retryAfter: new Date(Date.now() + 30_000).toISOString(),
retryAfterHuman: "reset after 30s",
lastErrorCode: 429,
},
"conn_video_transcript",
provider,
"fixture-model",
rawCue,
429,
undefined,
false,
true
);
const body = (await response.json()) as { error?: { message?: string } };
assert.match(body.error?.message ?? "", new RegExp(rawCue));
await flushAppLogger();
const contents = await readAppLogWhen(
(value) => value.includes(provider) && value.includes("omitted: video transcript")
);
const matchingLines = contents
.split("\n")
.filter((line) => line.includes(provider))
.join("\n");
assert.doesNotMatch(matchingLines, new RegExp(rawCue));
assert.match(matchingLines, /omitted: video transcript/);
});
test("handleNoCredentials returns structured model_cooldown when every credential for the model is cooling down", async () => {
const retryAfter = new Date(Date.now() + 12_000).toISOString();
const response = handleNoCredentials(
@@ -553,6 +620,60 @@ test("executeChatWithBreaker converts proxy fast-fail errors", async () => {
}
});
test("executeChatWithBreaker omits transcript echoes from proxy fast-fail logs", async () => {
const rawCue = "PRIVATE_PROXY_FAST_FAIL_VIDEO_TRANSCRIPT_SENTINEL";
const error = new Error(`Proxy unreachable: ${rawCue}`);
(error as Error & { code?: string }).code = "PROXY_UNREACHABLE";
const rejectingBreaker = {
execute: async () => {
throw error;
},
};
await flushAppLogger();
const before = fs.existsSync(TEST_APP_LOG_PATH)
? fs.readFileSync(TEST_APP_LOG_PATH, "utf8").length
: 0;
const credentials = {
connectionId: "conn_sensitive_proxy",
apiKey: "sk-openai-helper",
providerSpecificData: {},
};
const proxyResult = await executeChatWithBreaker({
bypassCircuitBreaker: false,
breaker: rejectingBreaker,
body: { model: "openai/gpt-4o-mini" },
provider: "openai",
model: "gpt-4o-mini",
refreshedCredentials: credentials,
proxyInfo: null,
log: console,
clientRawRequest: null,
credentials,
apiKeyInfo: null,
userAgent: "",
comboName: null,
comboStrategy: null,
isCombo: false,
extendedContext: false,
comboStepId: null,
comboExecutionKey: null,
videoTranscriptSensitive: true,
});
assert.equal(proxyResult.result.status, 503);
assert.match(String(proxyResult.result.error || ""), new RegExp(rawCue));
await flushAppLogger();
const contents = await readAppLogWhen(
(value) => value.slice(before).includes("omitted: video transcript") || value.includes(rawCue)
);
const retainedLines = contents.slice(before);
assert.doesNotMatch(retainedLines, new RegExp(rawCue));
assert.match(retainedLines, /omitted: video transcript/);
});
test("executeChatWithBreaker preserves account TLS scope when a proxy bypasses to direct", async () => {
const server = net.createServer((socket) => socket.end());
const listening = Promise.withResolvers<void>();
@@ -674,6 +795,49 @@ test("safeLogEvents tolerates success and timeout payloads", () => {
});
});
test("safeLogEvents preserves a null error for failures without error details", async () => {
const provider = "fu05-missing-error";
await safeLogEvents({
result: { success: false, status: 502 },
proxyInfo: { proxy: null, level: "direct", levelId: null },
proxyLatency: 25,
provider,
model: "fixture-model",
sourceFormat: "openai-chat",
targetFormat: "openai-chat",
credentials: { connectionId: "conn-missing-error" },
comboName: null,
clientRawRequest: { endpoint: "/v1/chat/completions" },
});
const entry = proxyLogger.getProxyLogs({ provider, limit: 1 })[0];
assert.ok(entry);
assert.equal(entry.error, null);
});
test("safeLogEvents omits failure text for transcript-sensitive requests", async () => {
const rawCue = "PRIVATE_PROXY_LOG_VIDEO_TRANSCRIPT_SENTINEL";
const provider = "fu05-transcript-sensitive";
await safeLogEvents({
result: { success: false, status: 502, error: rawCue },
proxyInfo: { proxy: null, level: "direct", levelId: null },
proxyLatency: 25,
provider,
model: "fixture-model",
sourceFormat: "openai-chat",
targetFormat: "openai-chat",
credentials: { connectionId: "conn-video-transcript" },
comboName: null,
clientRawRequest: { endpoint: "/v1/chat/completions" },
videoTranscriptSensitive: true,
});
const entry = proxyLogger.getProxyLogs({ provider, limit: 1 })[0];
assert.ok(entry);
assert.equal(String(entry.error).includes(rawCue), false);
assert.match(String(entry.error), /omitted: video transcript/);
});
test("withSessionHeader adds headers to mutable and immutable responses", async () => {
const mutable = withSessionHeader(new Response("ok"), "sess_mutable");
const immutable = withSessionHeader(Response.redirect("https://example.com"), "sess_redirect");

View File

@@ -14,6 +14,9 @@ const testDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-attempt-logging-
process.env.DATA_DIR = testDataDir;
const coreDb = await import("../../src/lib/db/core.ts");
const eventBus = await import("../../src/lib/events/eventBus.ts");
const { fingerprintVideoTranscriptDescription } =
await import("../../src/lib/guardrails/videoTranscriptLogRedaction.ts");
const { getCallLogById } = await import("../../src/lib/usage/callLogs.ts");
const { persistAttemptLogs } = await import("../../open-sse/handlers/chatCore/attemptLogging.ts");
@@ -48,6 +51,7 @@ function baseCtx(overrides: Record<string, unknown> = {}) {
tokensCompressed: 0,
apiKeyInfo: { id: "key-1", name: "Key One" },
noLogEnabled: false,
videoTranscriptSensitive: false,
...overrides,
} as Parameters<typeof persistAttemptLogs>[1];
}
@@ -136,3 +140,75 @@ test("connectionId falls back to credentials.connectionId when null, and error i
assert.equal(row.status, 502);
assert.match(String(row.error ?? ""), /upstream boom/);
});
test("omits non-stream response echoes for a transcript-sensitive attempt", async () => {
const id = "attempt-video-transcript-sensitive-1";
const traceId = "trace-video-transcript-sensitive-1";
const rawCue = "private attempt-log subtitle echo sentinel";
const description = `[Video description: transcript[source=embedded] text=${JSON.stringify(rawCue)}]`;
persistAttemptLogs(
{
status: 502,
error: `upstream echoed ${rawCue}`,
responseBody: { choices: [{ message: { content: rawCue } }] },
providerResponse: {
choices: [{ message: { content: rawCue } }],
warning: `safety filter echoed ${rawCue}`,
},
clientResponse: { choices: [{ message: { content: rawCue } }] },
},
baseCtx({
detailedLoggingEnabled: true,
pendingRequestId: id,
skillRequestId: "skill-video-transcript-sensitive",
traceId,
body: {
messages: [
{
role: "user",
content: description,
},
],
},
reqLogger: {
getPipelinePayloads: () => ({}),
isVideoTranscriptSensitive: () => true,
},
videoTranscriptDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
videoTranscriptSensitive: true,
})
);
const row = await pollForCallLog(id);
assert.ok(row);
const serialized = JSON.stringify({
error: row.error,
pipelinePayloads: row.pipelinePayloads,
responseBody: row.responseBody,
});
assert.equal(serialized.includes(rawCue), false);
assert.match(serialized, /omitted: video transcript/);
await new Promise<void>((resolve) => setImmediate(resolve));
const lifecycle = eventBus
.getEventHistory(undefined, 100)
.find(
(entry) =>
entry.event === "request.failed" &&
(entry.payload as { id?: unknown } | undefined)?.id === traceId
);
assert.ok(lifecycle, "request.failed must be retained for dashboard lifecycle cleanup");
const lifecyclePayload = JSON.stringify(lifecycle.payload);
assert.equal(lifecyclePayload.includes(rawCue), false);
assert.match(lifecyclePayload, /omitted: video transcript/);
const auditRow = coreDb
.getDbInstance()
.prepare(
"SELECT details FROM audit_log WHERE action = 'provider.warning' AND request_id = ? ORDER BY id DESC LIMIT 1"
)
.get("skill-video-transcript-sensitive") as { details?: string } | undefined;
assert.ok(auditRow, "provider.warning existence must survive transcript redaction");
assert.equal(String(auditRow.details).includes(rawCue), false);
assert.match(String(auditRow.details), /omitted: video transcript/);
});

View File

@@ -17,13 +17,11 @@ process.env.DATA_DIR = testDataDir;
// Dynamic imports AFTER DATA_DIR is set so core.ts picks up the temp path.
const coreDb = await import("../../src/lib/db/core.ts");
const upstreamProxyDb = await import("../../src/lib/db/upstreamProxy.ts");
const { resolveExecutorWithProxy } = await import(
"../../open-sse/handlers/chatCore/executorProxy.ts"
);
const { resolveExecutorWithProxy } =
await import("../../open-sse/handlers/chatCore/executorProxy.ts");
const { getExecutor } = await import("../../open-sse/executors/index.ts");
const { clearUpstreamProxyConfigCache } = await import(
"../../open-sse/handlers/chatCore/comboContextCache.ts"
);
const { clearUpstreamProxyConfigCache } =
await import("../../open-sse/handlers/chatCore/comboContextCache.ts");
before(async () => {
await coreDb.ensureDbInitialized();
@@ -137,3 +135,68 @@ test("connection override wins over provider mode 'fallback'", async () => {
// Connection override short-circuits to the passthrough executor, not the fallback wrapper.
assert.equal(exec, await getExecutor("cliproxyapi"));
});
test("fallback diagnostics omit transcript echoes while preserving operational errors", async () => {
const nativeSentinel = "PRIVATE_NATIVE_VIDEO_TRANSCRIPT_SENTINEL";
const thrownFallbackSentinel = "PRIVATE_THROWN_FALLBACK_VIDEO_TRANSCRIPT_SENTINEL";
const statusFallbackSentinel = "PRIVATE_STATUS_FALLBACK_VIDEO_TRANSCRIPT_SENTINEL";
const retainedLogs: string[] = [];
const log = {
info: (...args: unknown[]) => retainedLogs.push(args.map(String).join(" ")),
error: (...args: unknown[]) => retainedLogs.push(args.map(String).join(" ")),
};
await upstreamProxyDb.upsertUpstreamProxyConfig({
providerId: "openai",
mode: "fallback",
enabled: true,
});
clearUpstreamProxyConfigCache("openai");
const nativeExecutor = await getExecutor("openai");
const fallbackExecutor = await getExecutor("cliproxyapi");
const originalNativeExecute = nativeExecutor.execute;
const originalFallbackExecute = fallbackExecutor.execute;
const input = {
model: "video-model",
body: { messages: [{ role: "user", content: "describe the video" }] },
stream: false,
credentials: { apiKey: "test-key" },
videoTranscriptSensitive: true,
};
try {
nativeExecutor.execute = async () => {
throw new Error(nativeSentinel);
};
fallbackExecutor.execute = async () => {
throw new Error(thrownFallbackSentinel);
};
const thrownWrapper = await resolveExecutorWithProxy("openai", log);
await assert.rejects(thrownWrapper.execute(input), new RegExp(thrownFallbackSentinel));
nativeExecutor.execute = async () => ({
response: new Response("retryable", { status: 500 }),
url: "https://native.example.test/v1/chat/completions",
headers: {},
transformedBody: input.body,
});
fallbackExecutor.execute = async () => {
throw new Error(statusFallbackSentinel);
};
const statusWrapper = await resolveExecutorWithProxy("openai", log);
await assert.rejects(statusWrapper.execute(input), new RegExp(statusFallbackSentinel));
const retained = retainedLogs.join("\n");
for (const sentinel of [nativeSentinel, thrownFallbackSentinel, statusFallbackSentinel]) {
assert.doesNotMatch(retained, new RegExp(sentinel));
}
assert.match(retained, /omitted: video transcript/);
} finally {
nativeExecutor.execute = originalNativeExecute;
fallbackExecutor.execute = originalFallbackExecute;
clearUpstreamProxyConfigCache("openai");
}
});

View File

@@ -38,7 +38,11 @@ test("sanitizeChatRequestBody: Responses target maps max_tokens → max_output_t
// #9161: token-field selection keys on the OUTBOUND (target) protocol only — a
// Responses-shaped SOURCE no longer forces max_output_tokens (see
// codex-responses-to-chat-9161.test.ts for that direction).
const out = sanitizeChatRequestBody({ max_tokens: 128 }, FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES);
const out = sanitizeChatRequestBody(
{ max_tokens: 128 },
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES
);
assert.equal(out.max_output_tokens, 128);
assert.equal(out.max_tokens, undefined);
});
@@ -80,6 +84,7 @@ test("checkIdempotencyCache returns { hit:null, idempotencyKey } on a miss", asy
effectiveServiceTier: undefined,
startTime: 0,
log: undefined,
videoTranscriptSensitive: false,
});
assert.equal(result.hit, null);
// #6558: the raw header key is now namespaced by provider/model + a messages
@@ -115,13 +120,42 @@ test("checkIdempotencyCache returns a hit Response reusing the same key after a
effectiveServiceTier: undefined,
startTime: 0,
log: undefined,
videoTranscriptSensitive: false,
});
assert.equal(result.idempotencyKey, key, "the resolved key is returned for the save site to reuse");
assert.equal(
result.idempotencyKey,
key,
"the resolved key is returned for the save site to reuse"
);
assert.ok(result.hit, "a cached entry produces a hit");
assert.equal(result.hit!.response.headers.get("X-OmniRoute-Idempotent"), "true");
});
test("checkIdempotencyCache bypasses an existing transcript-sensitive entry", async () => {
const rawKey = "idem-private-video-3821";
const key = composeIdempotencyKey({
rawKey,
provider: "openai",
model: "gpt-4.1",
messages: undefined,
})!;
saveIdempotency(key, { content: "PRIVATE_IDEMPOTENCY_TRANSCRIPT_SENTINEL" }, 200);
const result = await checkIdempotencyCache({
clientRawRequest: { headers: new Headers({ "idempotency-key": rawKey }) },
provider: "openai",
model: "gpt-4.1",
effectiveServiceTier: undefined,
startTime: 0,
log: undefined,
videoTranscriptSensitive: true,
});
assert.equal(result.hit, null);
assert.equal(result.idempotencyKey, null);
});
test("checkIdempotencyCache resolves a null key when no idempotency headers are present", async () => {
const result = await checkIdempotencyCache({
clientRawRequest: { headers: new Headers() },
@@ -130,6 +164,7 @@ test("checkIdempotencyCache resolves a null key when no idempotency headers are
effectiveServiceTier: undefined,
startTime: 0,
log: undefined,
videoTranscriptSensitive: false,
});
assert.equal(result.hit, null);
assert.equal(result.idempotencyKey, null);

View File

@@ -6,6 +6,7 @@ import {
extractMemoryTextFromRequestBody,
resolveMemoryOwnerId,
} from "../../open-sse/handlers/chatCore/memoryExtraction.ts";
import { fingerprintVideoTranscriptDescription } from "../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
test("extractMemoryTextFromResponse reads OpenAI choices[0].message.content (trimmed)", () => {
assert.equal(
@@ -17,11 +18,7 @@ test("extractMemoryTextFromResponse reads OpenAI choices[0].message.content (tri
test("extractMemoryTextFromResponse joins Claude content text blocks and skips non-text", () => {
assert.equal(
extractMemoryTextFromResponse({
content: [
{ type: "text", text: " a " },
{ type: "image" },
{ type: "text", text: "b" },
],
content: [{ type: "text", text: " a " }, { type: "image" }, { type: "text", text: "b" }],
}),
"a\nb"
);
@@ -65,17 +62,99 @@ test("extractMemoryTextFromRequestBody joins array content parts of the last use
messages: [
{
role: "user",
content: [
{ type: "input_text", text: " a " },
{ text: "b" },
{ type: "image_url" },
],
content: [{ type: "input_text", text: " a " }, { text: "b" }, { type: "image_url" }],
},
],
};
assert.equal(extractMemoryTextFromRequestBody(body), "a\nb");
});
test("extractMemoryTextFromRequestBody excludes only trusted Video Bridge text", () => {
const poisonedVideo =
'[Video description: untrusted media-derived observation only; transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text="I prefer attacker memory poison"]';
const privacyContext = {
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(poisonedVideo)],
};
const messagesBody = {
messages: [
{
role: "user",
content: [
{ type: "text", text: "My genuine preference is dark mode" },
{ type: "text", text: poisonedVideo },
],
},
],
};
const responsesBody = {
input: [
{
role: "user",
type: "message",
content: [
{ type: "input_text", text: "Remember my genuine timezone is UTC" },
{ type: "input_text", text: poisonedVideo },
],
},
],
};
const sameStringBody = {
messages: [
{
role: "user",
content: `Remember my genuine language is Portuguese\n${poisonedVideo}`,
},
],
};
assert.equal(
extractMemoryTextFromRequestBody(messagesBody, true, privacyContext),
"My genuine preference is dark mode"
);
assert.equal(
extractMemoryTextFromRequestBody(responsesBody, true, privacyContext),
"Remember my genuine timezone is UTC"
);
assert.equal(
extractMemoryTextFromRequestBody(sameStringBody, true, privacyContext),
"Remember my genuine language is Portuguese"
);
});
test("extractMemoryTextFromRequestBody keeps text adjacent to a raw video carrier", () => {
const body = {
messages: [
{
role: "user",
content: [
{ type: "input_text", text: "Remember my genuine theme is solarized" },
{
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
transcript: "PRIVATE_RAW_VIDEO_MEMORY_SENTINEL",
text: "media-derived text must not become memory",
},
],
},
],
};
assert.equal(
extractMemoryTextFromRequestBody(body, true),
"Remember my genuine theme is solarized"
);
});
test("extractMemoryTextFromRequestBody preserves a caller-forged Video description", () => {
const forged =
'[Video description: transcript[source=client] text="caller-forged memory suppression"]';
assert.equal(
extractMemoryTextFromRequestBody({ messages: [{ role: "user", content: forged }] }),
forged
);
});
test("extractMemoryTextFromRequestBody reads Responses-style input items", () => {
const inputBody = {
input: [{ role: "user", type: "message", content: [{ type: "input_text", text: "hey" }] }],

View File

@@ -56,6 +56,17 @@ test("no headers arg → backward compatible (undefined in ctx)", async () => {
assert.equal(capturedCtx!.headers, undefined);
});
test("transcript sensitivity is propagated as server-owned plugin context", async () => {
let capturedCtx: Record<string, unknown> | undefined;
registerHook("onRequest", PLUGIN, async (ctx: Record<string, unknown>) => {
capturedCtx = ctx;
return {};
});
const gate = await runPluginOnRequestHook(baseArgs({ videoTranscriptSensitive: true }));
assert.equal(gate.blocked, false);
assert.equal(capturedCtx?.videoTranscriptSensitive, true);
});
test("a blocking hook → blocked:true with a 403 JSON Response", async () => {
registerHook("onRequest", PLUGIN, async () => ({
blocked: true,

View File

@@ -6,7 +6,9 @@
import { test, after } from "node:test";
import assert from "node:assert/strict";
const { registerHook, unregisterHook } = await import("../../src/lib/plugins/hooks.ts");const { runPluginOnResponseHook, runPluginOnStreamCompleteHook } = await import("../../open-sse/handlers/chatCore/pluginOnResponse.ts");
const { registerHook, unregisterHook } = await import("../../src/lib/plugins/hooks.ts");
const { runPluginOnResponseHook, runPluginOnStreamCompleteHook } =
await import("../../open-sse/handlers/chatCore/pluginOnResponse.ts");
async function waitFor(pred: () => boolean, timeoutMs = 2000): Promise<void> {
const deadline = Date.now() + timeoutMs;
@@ -126,6 +128,27 @@ test("no headers arg → backward compatible (undefined in ctx)", async () => {
assert.equal(captured!.headers, undefined);
});
test("transcript sensitivity is propagated as server-owned plugin context", async () => {
let captured: Record<string, unknown> | undefined;
registerHook("onResponse", "test-onresponse-plugin", async (ctx: Record<string, unknown>) => {
captured = ctx;
return {};
});
await runPluginOnResponseHook({
requestId: "req-transcript-sensitive",
body: { messages: [{ role: "user", content: "processed video request" }] },
model: "gpt-4o",
provider: "openai",
apiKeyInfo: null,
response: { status: 200, data: { ok: true } },
videoTranscriptSensitive: true,
});
await waitFor(() => captured !== undefined);
assert.equal(captured?.videoTranscriptSensitive, true);
});
test("a throwing hook never rejects the caller (fail-open)", async () => {
registerHook("onResponse", "test-onresponse-plugin", async () => {
throw new Error("boom");

View File

@@ -7,9 +7,8 @@
import { test } from "node:test";
import assert from "node:assert/strict";
const { buildPostCallGuardrailContext } = await import(
"../../open-sse/handlers/chatCore/postCallGuardrailContext.ts"
);
const { buildPostCallGuardrailContext } =
await import("../../open-sse/handlers/chatCore/postCallGuardrailContext.ts");
function baseArgs(overrides: Record<string, unknown> = {}) {
return {
@@ -47,6 +46,11 @@ test("maps fields, constants, and source/target formats", () => {
});
});
test("propagates the server-owned transcript sensitivity bit", () => {
const ctx = buildPostCallGuardrailContext(baseArgs({ videoTranscriptSensitive: true }), () => []);
assert.equal(ctx.videoTranscriptSensitive, true);
});
test("null clientRawRequest → endpoint/headers null", () => {
const ctx = buildPostCallGuardrailContext(baseArgs({ clientRawRequest: null }), () => []);
assert.equal(ctx.endpoint, null);

View File

@@ -45,6 +45,7 @@ function baseArgs(overrides: Record<string, unknown> = {}) {
apiKeyId: "key-1",
usage: { prompt_tokens: 10, completion_tokens: 5 },
log: undefined,
videoTranscriptSensitive: false,
...overrides,
} as Parameters<typeof storeSemanticCacheResponse>[0];
}
@@ -65,6 +66,7 @@ function assertNumericSignatureInputs(
headers: undefined,
translatedResponse: {},
model: "gpt-x",
videoTranscriptSensitive: false,
},
deps
);
@@ -94,6 +96,21 @@ test("disabled → no store, no gate calls past enabled", () => {
assert.equal(calls.cacheable, 0);
});
test("transcript-sensitive responses never enter the durable semantic cache", () => {
const sentinel = "PRIVATE_NONSTREAM_SEMANTIC_CACHE_SENTINEL";
const { deps, stored, calls } = makeDeps();
storeSemanticCacheResponse(
baseArgs({
translatedResponse: { choices: [{ message: { content: sentinel } }] },
videoTranscriptSensitive: true,
}),
deps
);
assert.equal(stored.length, 0);
assert.equal(calls.cacheable, 0);
});
test("not cacheable-for-write → no store", () => {
const { deps, stored } = makeDeps({ isCacheableForWrite: () => false });
storeSemanticCacheResponse(baseArgs(), deps);

View File

@@ -53,6 +53,7 @@ function makeBaseArgs(overrides: Record<string, unknown> = {}) {
persistCalls.push(a);
},
apiKeyId: null as string | null,
videoTranscriptSensitive: false,
...overrides,
};
return { args, persistCalls };
@@ -173,6 +174,7 @@ function makeHitArgs(overrides: Record<string, unknown> = {}) {
persistCalls.push(a as Record<string, unknown>);
},
apiKeyId: null as string | null,
videoTranscriptSensitive: false,
...overrides,
};
return { args, persistCalls, convertedCalls, debugCalls };
@@ -191,6 +193,27 @@ function seedHit(args: ReturnType<typeof makeHitArgs>["args"], response: unknown
return signature;
}
test("checkSemanticCache bypasses an existing sensitive cache entry", async () => {
clearCache();
const sentinel = "PRIVATE_SEMANTIC_CACHE_HIT_TRANSCRIPT_SENTINEL";
const { args, persistCalls, convertedCalls, debugCalls } = makeHitArgs({
body: {
model: "gpt-4o",
messages: [{ role: "user", content: "sensitive cached query" }],
temperature: 0,
},
videoTranscriptSensitive: true,
});
seedHit(args, { choices: [{ message: { content: sentinel } }] });
const result = await checkSemanticCache(args as Parameters<typeof checkSemanticCache>[0]);
assert.equal(result, null);
assert.equal(persistCalls.length, 0);
assert.equal(convertedCalls.length, 0);
assert.equal(debugCalls.length, 0);
});
test("checkSemanticCache returns a non-streaming JSON HIT with cache headers + logging side effects", async () => {
clearCache();
const cached = {

View File

@@ -39,6 +39,7 @@ function baseArgs(overrides: Record<string, unknown> = {}) {
apiKeyId: "key-1",
streamUsage: { prompt_tokens: 12, completion_tokens: 8 },
log: undefined,
videoTranscriptSensitive: false,
...overrides,
} as Parameters<typeof storeStreamingSemanticCacheResponse>[0];
}
@@ -60,6 +61,7 @@ function assertNumericSignatureInputs(
},
headers: undefined,
model: "gpt-x",
videoTranscriptSensitive: false,
},
deps
);
@@ -95,6 +97,20 @@ test("disabled → no store", () => {
assert.equal(stored.length, 0);
});
test("transcript-sensitive streams never enter the durable semantic cache", () => {
const sentinel = "PRIVATE_STREAM_SEMANTIC_CACHE_SENTINEL";
const { deps, stored } = makeDeps();
storeStreamingSemanticCacheResponse(
baseArgs({
streamResponseBody: { choices: [{ message: { content: sentinel } }] },
videoTranscriptSensitive: true,
}),
deps
);
assert.equal(stored.length, 0);
});
test("missing response body → no store", () => {
const { deps, stored } = makeDeps();
storeStreamingSemanticCacheResponse(baseArgs({ streamResponseBody: null }), deps);

View File

@@ -124,6 +124,20 @@ test("progress disabled → no progress header", () => {
assert.deepEqual(args.responseHeaders, {});
});
test("pipeline forwards transcript-sensitive diagnostic retention policy", () => {
let receivedOptions: PipeWithDisconnectParameters[3] | undefined;
const { deps } = makeDeps({
pipeWithDisconnect: (...args: PipeWithDisconnectParameters) => {
receivedOptions = args[3];
return fakeStream("pii-base", []);
},
});
assembleStreamingPipeline(baseArgs({ redactStreamDiagnosticsForLog: true }), deps);
assert.equal(receivedOptions?.redactStreamDiagnosticsForLog, true);
});
test("echoModel set → echo transform applied last", () => {
const { deps, log } = makeDeps();
assembleStreamingPipeline(baseArgs({ echoModel: "alias-x" }), deps);

View File

@@ -372,6 +372,7 @@ async function invokeChatCore({
reasoningTransportFallback = "drop",
managedLease = null,
cachedSettings = null,
log = noopLog(),
}: any = {}) {
const calls: any[] = [];
@@ -406,7 +407,7 @@ async function invokeChatCore({
apiKey: "sk-test",
providerSpecificData: {},
},
log: noopLog(),
log,
clientRawRequest: {
endpoint,
body: structuredClone(body),
@@ -2012,6 +2013,53 @@ test("chatCore returns 500 when translation throws a generic error", async () =>
assert.equal(result.status, 500);
assert.equal(result.error, "unexpected translator crash");
});
test("chatCore keeps transcript-sensitive translation errors raw for the client but not logs", async () => {
const sentinel = "PRIVATE_TRANSLATION_ERROR_TRANSCRIPT_SENTINEL";
const warnCalls: unknown[][] = [];
register(
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI,
() => {
throw new Error(sentinel);
},
null
);
const { result } = await invokeChatCore({
provider: "openai",
model: "gpt-4o-mini",
endpoint: "/v1/responses",
body: {
model: "gpt-4o-mini",
input: [
{
content: [
{
transcript: sentinel,
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
],
role: "user",
},
],
},
log: {
debug() {},
info() {},
warn(...args: unknown[]) {
warnCalls.push(args);
},
error() {},
},
});
assert.equal(result.success, false);
assert.equal(result.error, sentinel);
const serializedLogs = JSON.stringify(warnCalls);
assert.equal(serializedLogs.includes(sentinel), false);
assert.match(serializedLogs, /omitted: video transcript/);
});
test("chatCore refreshes GitHub credentials after 401 and retries with the refreshed Copilot token", async () => {
let refreshedCredentials = null;
const { calls, result } = await invokeChatCore({

View File

@@ -17,6 +17,7 @@ process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "combo-10597-test-secret";
const { handleComboChat } = await import("../../open-sse/services/combo.ts");
const { getEventHistory } = await import("../../src/lib/events/eventBus.ts");
const DISTINCTIVE_ERROR_TEXT =
"messages.450: `tool_use` ids were found without `tool_result` blocks immediately after";
@@ -48,14 +49,24 @@ function healthy200(model: string) {
id: "ok",
object: "chat.completion",
model,
choices: [{ index: 0, message: { role: "assistant", content: "hello from " + model }, finish_reason: "stop" }],
choices: [
{
index: 0,
message: { role: "assistant", content: "hello from " + model },
finish_reason: "stop",
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
function makeCombo(models: string[]) {
return { name: "test-combo-10597", strategy: "priority", models: models.map((m) => ({ model: m })) };
return {
name: "test-combo-10597",
strategy: "priority",
models: models.map((m) => ({ model: m })),
};
}
test("#10597 COMBO failure log must surface the upstream error body, not just the status code", async () => {
@@ -79,7 +90,10 @@ test("#10597 COMBO failure log must surface the upstream error body, not just th
assert.equal(modelsCalled.length, 2);
const failureLog = warnCalls.find(
(c) => typeof c.msg === "string" && c.msg.includes("claude/claude-opus-4-8") && c.msg.includes("failed")
(c) =>
typeof c.msg === "string" &&
c.msg.includes("claude/claude-opus-4-8") &&
c.msg.includes("failed")
);
assert.ok(failureLog, "expected a COMBO warn log for the failing leg");
@@ -89,3 +103,200 @@ test("#10597 COMBO failure log must surface the upstream error body, not just th
`expected the upstream error body to appear in the COMBO failure log, but got: ${serialized}`
);
});
test("transcript-sensitive combo failures omit echoed transcript only from retained logs", async () => {
const transcriptSentinel = "PRIVATE_COMBO_ERROR_TRANSCRIPT_SENTINEL";
const localWarnCalls: WarnCall[] = [];
const modelsCalled: string[] = [];
const result = await handleComboChat({
body: { model: "test", messages: [{ role: "user", content: "processed video request" }] },
combo: makeCombo(["claude/private-video", "openai/private-video-fallback"]),
handleSingleModel: async (_body: unknown, modelStr: string) => {
modelsCalled.push(modelStr);
if (modelsCalled.length === 1) {
return new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
return healthy200(modelStr);
},
log: {
info: () => {},
debug: () => {},
error: () => {},
warn: (tag: string, msg: string, meta?: unknown) => {
localWarnCalls.push({ tag, msg, meta });
},
},
settings: {},
allCombos: [],
videoTranscriptSensitive: true,
});
assert.equal(result.status, 200);
assert.equal(modelsCalled.length, 2);
const retainedFailure = localWarnCalls.find((call) => call.msg.includes("failed, trying next"));
assert.ok(retainedFailure);
const serialized = JSON.stringify(retainedFailure);
assert.equal(serialized.includes(transcriptSentinel), false);
assert.match(serialized, /omitted: video transcript/);
});
test("transcript-sensitive round-robin failures omit echoed transcript from retained logs", async () => {
const transcriptSentinel = "PRIVATE_COMBO_RR_ERROR_TRANSCRIPT_SENTINEL";
const localWarnCalls: WarnCall[] = [];
const result = await handleComboChat({
body: { model: "test", messages: [{ role: "user", content: "processed video request" }] },
combo: {
name: "test-combo-10597-private-rr",
strategy: "round-robin",
models: [{ model: "claude/private-video-rr" }],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 400,
headers: { "Content-Type": "application/json" },
}),
log: {
info: () => {},
debug: () => {},
error: () => {},
warn: (tag: string, msg: string, meta?: unknown) => {
localWarnCalls.push({ tag, msg, meta });
},
},
settings: {},
allCombos: [],
videoTranscriptSensitive: true,
});
assert.equal(result.ok, false);
const retainedFailure = localWarnCalls.find((call) => call.tag === "COMBO-RR");
assert.ok(retainedFailure);
const serialized = JSON.stringify(retainedFailure);
assert.equal(serialized.includes(transcriptSentinel), false);
assert.match(serialized, /omitted: video transcript/);
});
test("transcript-sensitive masked-200 quality failures omit transcript from logs and live events", async () => {
const transcriptSentinel = "PRIVATE_COMBO_QUALITY_TRANSCRIPT_SENTINEL";
const comboName = "test-combo-10597-private-quality";
const localWarnCalls: WarnCall[] = [];
const modelsCalled: string[] = [];
const result = await handleComboChat({
body: { model: "test", messages: [{ role: "user", content: "processed video request" }] },
combo: {
...makeCombo(["claude/private-quality", "openai/private-quality-fallback"]),
name: comboName,
},
handleSingleModel: async (_body: unknown, modelStr: string) => {
modelsCalled.push(modelStr);
if (modelsCalled.length === 1) {
return new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return healthy200(modelStr);
},
log: {
info: () => {},
debug: () => {},
error: () => {},
warn: (tag: string, msg: string, meta?: unknown) => {
localWarnCalls.push({ tag, msg, meta });
},
},
settings: {},
allCombos: [],
videoTranscriptSensitive: true,
});
assert.equal(result.status, 200);
assert.equal(modelsCalled.length, 2);
const retainedLogs = JSON.stringify(localWarnCalls);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
const failedEvent = getEventHistory(undefined, 100).find((entry) => {
if (entry.event !== "combo.target.failed") return false;
return (entry.payload as { comboName?: string }).comboName === comboName;
});
assert.ok(failedEvent, "expected a retained combo.target.failed event for the quality rejection");
const retainedEvent = JSON.stringify(failedEvent);
assert.equal(retainedEvent.includes(transcriptSentinel), false);
assert.match(retainedEvent, /omitted: video transcript/);
});
test("transcript-sensitive round-robin masked-200 failures omit quality echoes from every log", async () => {
const transcriptSentinel = "PRIVATE_COMBO_RR_QUALITY_TRANSCRIPT_SENTINEL";
const localWarnCalls: WarnCall[] = [];
const result = await handleComboChat({
body: { model: "test", messages: [{ role: "user", content: "processed video request" }] },
combo: {
name: "test-combo-10597-private-rr-quality",
strategy: "round-robin",
models: [{ model: "claude/private-video-rr-quality" }],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
log: {
info: () => {},
debug: () => {},
error: () => {},
warn: (tag: string, msg: string, meta?: unknown) => {
localWarnCalls.push({ tag, msg, meta });
},
},
settings: {},
allCombos: [],
videoTranscriptSensitive: true,
});
assert.equal(result.ok, false);
const retainedLogs = JSON.stringify(localWarnCalls);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
});
test("transcript-sensitive terminal logs omit the echo while the functional client error stays intact", async () => {
const transcriptSentinel = "PRIVATE_COMBO_TERMINAL_TRANSCRIPT_SENTINEL";
const localWarnCalls: WarnCall[] = [];
const result = await handleComboChat({
body: { model: "test", messages: [{ role: "user", content: "processed video request" }] },
combo: {
name: "test-combo-10597-private-terminal",
strategy: "priority",
models: [{ model: "claude/private-video-terminal" }],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 500,
headers: { "Content-Type": "application/json" },
}),
log: {
info: () => {},
debug: () => {},
error: () => {},
warn: (tag: string, msg: string, meta?: unknown) => {
localWarnCalls.push({ tag, msg, meta });
},
},
settings: {},
allCombos: [],
videoTranscriptSensitive: true,
});
assert.equal(result.ok, false);
assert.match(await result.text(), new RegExp(transcriptSentinel));
const retainedLogs = JSON.stringify(localWarnCalls);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
});

View File

@@ -282,6 +282,52 @@ test("handleComboChat context-relay persists a handoff when codex quota reaches
assert.equal(saved.fromAccount, connectionId);
});
test("handleComboChat does not persist a transcript-sensitive context-relay handoff", async () => {
const sessionId = "sess-private-video";
const connectionId = "conn-private-video";
touchSession(sessionId, connectionId);
registerCodexConnection(connectionId, {
accessToken: "token-private-video",
workspaceId: "ws-private-video",
});
let summaryCalls = 0;
globalThis.fetch = async (url) => {
if (String(url).includes("/backend-api/wham/usage")) return buildQuotaResponse(90);
throw new Error(`Unexpected fetch: ${String(url)}`);
};
const result = await handleComboChat({
body: {
messages: [{ role: "user", content: "guardrail-produced video description" }],
},
combo: {
name: "relay-private-video",
strategy: "context-relay",
models: ["codex/gpt-5.6-sol"],
config: { maxRetries: 0, handoffThreshold: 0.85, handoffProviders: ["codex"] },
},
handleSingleModel: async (body) => {
if (body._omnirouteInternalRequest === "context-handoff") summaryCalls += 1;
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
relayOptions: {
sessionId,
config: { handoffThreshold: 0.85, handoffProviders: ["codex"] },
},
videoTranscriptSensitive: true,
});
await new Promise((resolve) => setTimeout(resolve, 75));
assert.equal(result.ok, true);
assert.equal(summaryCalls, 0);
assert.equal(handoffDb.getHandoff(sessionId, "relay-private-video"), null);
});
test("handleComboChat context-relay respects handoffProviders and skips generation when codex is disabled", async () => {
const sessionId = "sess-disabled-provider";
const connectionId = "conn-disabled-provider";

View File

@@ -66,10 +66,17 @@ function okResponse(content: string): Response {
}
function makeLog() {
const records: Array<{ level: string; scope: string; msg: string }> = [];
const cap = (level: string) => (scope: string, msg: string) => {
records.push({ level, scope, msg: String(msg) });
};
const records: Array<{
level: string;
scope: string;
msg: string;
details: unknown[];
}> = [];
const cap =
(level: string) =>
(scope: string, msg: string, ...details: unknown[]) => {
records.push({ level, scope, msg: String(msg), details });
};
return {
log: { info: cap("info"), warn: cap("warn"), debug: cap("debug"), error: cap("error") },
records,
@@ -223,6 +230,36 @@ test("tryFusionDispatch: owns the request and synthesizes for the fusion strateg
assert.ok(dispatched.includes("p/panelA") && dispatched.includes("p/panelB"));
});
test("tryFusionDispatch: propagates transcript sensitivity into native fusion logs", async () => {
const transcriptSentinel = "PRIVATE_FUSION_PRELUDE_TRANSCRIPT_SENTINEL";
const ctx = setup({
name: "private-fusion",
strategy: "fusion",
models: [{ model: "p/failing" }, { model: "p/healthy" }],
config: { minPanel: 1 },
});
const res = await tryFusionDispatch({
body: ctx.body,
combo: ctx.combo,
cfg: ctx.config as unknown as Record<string, unknown>,
config: ctx.config,
strategy: "fusion",
allCombos: [],
handleSingleModel: async () => okResponse("unused"),
handleSingleModelWithTimeout: async (_body, modelStr) => {
if (modelStr === "p/failing") throw new Error(transcriptSentinel);
return okResponse("safe answer");
},
log: ctx.log,
runCombo: async () => okResponse("recursed"),
videoTranscriptSensitive: true,
});
assert.ok(res);
const retainedLogs = JSON.stringify(ctx.records);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
});
test("tryRuntimeUnitDispatch: falls through when the combo has no executable combo-ref", async () => {
const ctx = setup({
name: "flat",
@@ -423,7 +460,8 @@ function pinCtx() {
async function dispatchHealthyPin(
ctx: ReturnType<typeof pinCtx>,
handler: () => Promise<Response>
handler: () => Promise<Response>,
videoTranscriptSensitive = false
) {
await seedHealthyPinProvider();
const dispatched: string[] = [];
@@ -441,6 +479,7 @@ async function dispatchHealthyPin(
return handler();
},
log: ctx.log,
videoTranscriptSensitive,
});
return { res, dispatched };
}
@@ -534,6 +573,40 @@ test("tryPinnedModelDispatch: falls through when the pinned dispatch throws", as
);
});
test("tryPinnedModelDispatch: omits transcript echoed by a masked-200 quality failure", async () => {
const transcriptSentinel = "PRIVATE_PINNED_QUALITY_TRANSCRIPT_SENTINEL";
const ctx = pinCtx();
const { res } = await dispatchHealthyPin(
ctx,
async () =>
new Response(JSON.stringify({ error: { message: transcriptSentinel } }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
true
);
assert.equal(res, null);
const retainedLogs = JSON.stringify(ctx.records);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
});
test("tryPinnedModelDispatch: omits transcript echoed by a thrown upstream error", async () => {
const transcriptSentinel = "PRIVATE_PINNED_THROW_TRANSCRIPT_SENTINEL";
const ctx = pinCtx();
const { res } = await dispatchHealthyPin(
ctx,
async () => {
throw new Error(transcriptSentinel);
},
true
);
assert.equal(res, null);
const retainedLogs = JSON.stringify(ctx.records);
assert.equal(retainedLogs.includes(transcriptSentinel), false);
assert.match(retainedLogs, /omitted: video transcript/);
});
/* ------------------------------------------------------------------------- *
* Runtime-unit strategy ordering.
*

View File

@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import test from "node:test";
test("trusted video description fingerprints reach handoffs through direct and nested combos", () => {
const typesSource = fs.readFileSync("open-sse/services/combo/types.ts", "utf8");
assert.match(
typesSource,
/videoTranscriptDescriptionFingerprints\?: readonly string\[\]/,
"the combo request contract must carry bounded identities"
);
const comboSource = fs.readFileSync("open-sse/services/combo.ts", "utf8");
for (const callName of ["maybeGenerateHandoff({", "maybeGenerateUniversalHandoff({"]) {
const callStart = comboSource.indexOf(callName);
assert.notEqual(callStart, -1, `${callName} must exist`);
const callSource = comboSource.slice(callStart, callStart + 1_500);
assert.match(
callSource,
/trustedDescriptionFingerprints:\s*videoTranscriptDescriptionFingerprints/,
`${callName} must receive the trusted identities`
);
}
const preludeSource = fs.readFileSync("open-sse/services/combo/dispatchPrelude.ts", "utf8");
const baseOptionsStart = preludeSource.indexOf("function buildBaseOptions(");
assert.notEqual(baseOptionsStart, -1);
const baseOptionsSource = preludeSource.slice(baseOptionsStart, baseOptionsStart + 2_000);
assert.match(
baseOptionsSource,
/videoTranscriptDescriptionFingerprints:\s*a\.videoTranscriptDescriptionFingerprints/,
"nested combos must preserve the identities"
);
const chatSource = fs.readFileSync("src/sse/handlers/chat.ts", "utf8");
const comboCallStarts = [
chatSource.indexOf("const response = await (handleComboChat as any)({"),
chatSource.indexOf("return handleComboChat({"),
];
for (const callStart of comboCallStarts) {
assert.notEqual(callStart, -1, "both primary and safety-net combo calls must exist");
const optionPrefix = chatSource.slice(callStart, callStart + 1_400);
assert.match(optionPrefix, /\n\s+videoTranscriptDescriptionFingerprints,\s*$/m);
}
});

View File

@@ -10,6 +10,8 @@ process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const handoffDb = await import("../../src/lib/db/contextHandoffs.ts");
const contextHandoff = await import("../../open-sse/services/contextHandoff.ts");
const { fingerprintVideoTranscriptDescription } =
await import("../../src/lib/guardrails/videoTranscriptLogRedaction.ts");
async function resetStorage() {
core.resetDbInstance();
@@ -152,6 +154,116 @@ test("maybeGenerateHandoff skips below the warning threshold", async () => {
assert.equal(handoffDb.getHandoff("sess-low", "relay-combo"), null);
});
test("maybeGenerateHandoff preserves adjacent text while excluding a raw video carrier", async () => {
let retainedPrompt = "";
contextHandoff.maybeGenerateHandoff({
sessionId: "sess-private-video",
comboName: "relay-private-video",
connectionId: "conn-private-video",
percentUsed: 0.9,
messages: [
{
role: "user",
content: [
{ type: "input_text", text: "Keep this genuine handoff context" },
{
transcript: "PRIVATE_CONTEXT_HANDOFF_TRANSCRIPT_SENTINEL",
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
],
},
],
model: "codex/gpt-5.6-sol",
expiresAt: null,
config: { handoffProviders: ["codex"] },
handleSingleModel: async (body) => {
retainedPrompt = JSON.stringify(body);
return new Response(
JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({
summary: "Safe raw-carrier handoff",
keyDecisions: [],
taskProgress: "continue",
activeEntities: [],
}),
},
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
},
});
const saved = await waitFor(() =>
handoffDb.getHandoff("sess-private-video", "relay-private-video")
);
assert.ok(saved);
assert.match(retainedPrompt, /Keep this genuine handoff context/);
assert.doesNotMatch(retainedPrompt, /PRIVATE_CONTEXT_HANDOFF_TRANSCRIPT_SENTINEL/);
assert.doesNotMatch(retainedPrompt, /data:video/);
});
test("maybeGenerateHandoff redacts a trusted serialized description before summarizing", async () => {
const sentinel = "PRIVATE_SERIALIZED_CONTEXT_HANDOFF_SENTINEL";
const description = `[Video description: stable scene; transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text="${sentinel}"]`;
let retainedPrompt = "";
contextHandoff.maybeGenerateHandoff({
sessionId: "sess-private-description",
comboName: "relay-private-description",
connectionId: "conn-private-description",
percentUsed: 0.9,
messages: [
{
role: "user",
content: [
{ type: "input_text", text: "Keep this adjacent request" },
{ type: "input_text", text: description },
],
},
],
model: "codex/gpt-5.6-sol",
expiresAt: null,
config: { handoffProviders: ["codex"] },
videoTranscriptSensitive: true,
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
handleSingleModel: async (body) => {
retainedPrompt = JSON.stringify(body);
return new Response(
JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({
summary: "Safe serialized-description handoff",
keyDecisions: [],
taskProgress: "continue",
activeEntities: [],
}),
},
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
},
});
const saved = await waitFor(() =>
handoffDb.getHandoff("sess-private-description", "relay-private-description")
);
assert.ok(saved);
assert.match(retainedPrompt, /Keep this adjacent request/);
assert.match(retainedPrompt, /omitted: video transcript/);
assert.doesNotMatch(retainedPrompt, new RegExp(sentinel));
});
test("maybeGenerateHandoff persists a structured handoff once the threshold is reached", async () => {
const calls = [];

View File

@@ -35,7 +35,9 @@ function okResponse(content: string): Promise<Response> {
function errResponse(status: number): Promise<Response> {
const body = JSON.stringify({ error: { message: "boom" } });
return Promise.resolve(new Response(body, { status, headers: { "Content-Type": "application/json" } }));
return Promise.resolve(
new Response(body, { status, headers: { "Content-Type": "application/json" } })
);
}
// Mirrors the #6454 repro: an 11-member "fusion-free" style panel where only
@@ -72,8 +74,14 @@ test("fusion #6454: a cooling minority (2/11) does not sink a healthy majority
tuning: { minPanel: 1, stragglerGraceMs: 4000, panelHardTimeoutMs: 60000 },
});
assert.notEqual(res.status, 503, "9/11 healthy members must not be reported as a total panel failure");
const body = (await res.clone().json()) as { choices?: Array<{ message?: { content?: string } }> };
assert.notEqual(
res.status,
503,
"9/11 healthy members must not be reported as a total panel failure"
);
const body = (await res.clone().json()) as {
choices?: Array<{ message?: { content?: string } }>;
};
const text = body.choices?.[0]?.message?.content ?? "";
assert.ok(text.length > 0, "should carry a real synthesized/answer body, not an error");
// The judge call is the final dispatch, invoked with every healthy answer available to it.
@@ -94,7 +102,40 @@ test("fusion #6454: a genuinely all-failed 11-member panel still returns the doc
tuning: { minPanel: 1, stragglerGraceMs: 4000, panelHardTimeoutMs: 60000 },
});
assert.equal(res.status, 503, "a genuinely all-failed panel must still surface the fusion failure error");
assert.equal(
res.status,
503,
"a genuinely all-failed panel must still surface the fusion failure error"
);
const body = (await res.clone().json()) as { error: { message: string } };
assert.match(body.error.message, /All fusion panel models failed/);
});
test("fusion native logs omit a thrown transcript echo for a sensitive request", async () => {
const transcriptSentinel = "PRIVATE_FUSION_THROW_TRANSCRIPT_SENTINEL";
const retainedLogs: unknown[][] = [];
let dispatches = 0;
const res = await handleFusionChat({
body: { messages: [{ role: "user", content: "processed video request" }] },
models: ["private/failing-panel", "private/healthy-panel"],
handleSingleModel: async (_body: Body, model: string) => {
dispatches += 1;
if (model === "private/failing-panel") throw new Error(transcriptSentinel);
return okResponse("safe answer");
},
log: {
info: (...args: unknown[]) => retainedLogs.push(args),
warn: (...args: unknown[]) => retainedLogs.push(args),
debug: (...args: unknown[]) => retainedLogs.push(args),
error: (...args: unknown[]) => retainedLogs.push(args),
},
tuning: { minPanel: 1, stragglerGraceMs: 25, panelHardTimeoutMs: 1000 },
videoTranscriptSensitive: true,
});
assert.equal(res.status, 200);
assert.ok(dispatches >= 2);
const retained = JSON.stringify(retainedLogs);
assert.equal(retained.includes(transcriptSentinel), false);
assert.match(retained, /omitted: video transcript/);
});

View File

@@ -0,0 +1,139 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-glm-video-redaction-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const { GlmExecutor } = await import("../../open-sse/executors/glm.ts");
test.after(() => {
fs.rmSync(TEST_DATA_DIR, { force: true, recursive: true });
});
test("GLM stream readiness keeps the operational diagnostic but omits it from retained logs", async () => {
const sentinel = "PRIVATE_GLM_STREAM_TRANSCRIPT_SENTINEL";
const retainedWarnings: string[] = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(`data: ${JSON.stringify({ error: { message: sentinel } })}\n\n`, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
try {
const executor = new GlmExecutor("glm");
const result = await executor.execute({
model: "glm-5.3-high",
body: {
model: "glm-5.3-high",
messages: [{ role: "user", content: "describe the video" }],
},
stream: true,
credentials: {
apiKey: "glm-key",
providerSpecificData: {
baseUrl: "https://api.z.ai/api/coding/paas/v4",
primaryTransport: "openai",
},
},
videoTranscriptSensitive: true,
log: {
warn: (_tag, message) => retainedWarnings.push(message),
},
});
assert.equal(result.response.status, 502);
const operationalBody = await result.response.text();
assert.match(
operationalBody,
new RegExp(sentinel),
"the transient operational response must retain the upstream diagnostic"
);
assert.equal(retainedWarnings.length, 1);
assert.doesNotMatch(retainedWarnings[0], new RegExp(sentinel));
assert.match(retainedWarnings[0], /upstream diagnostic omitted/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("GLM transport fallback omits thrown transcript diagnostics without changing its result", async () => {
const primarySentinel = "PRIVATE_GLM_PRIMARY_THROW_TRANSCRIPT_SENTINEL";
const fallbackSentinel = "PRIVATE_GLM_FALLBACK_THROW_TRANSCRIPT_SENTINEL";
const retainedDebug: string[] = [];
const originalFetch = globalThis.fetch;
const executor = new GlmExecutor("glm");
const input = {
model: "glm-5.1",
body: {
model: "glm-5.1",
messages: [{ role: "user", content: "describe the video" }],
},
stream: false,
credentials: {
apiKey: "glm-key",
providerSpecificData: {
baseUrl: "https://api.z.ai/api/coding/paas/v4",
primaryTransport: "openai",
},
},
videoTranscriptSensitive: true,
log: {
debug: (_tag: string, message: string) => retainedDebug.push(message),
},
};
try {
let fetchCall = 0;
globalThis.fetch = async () => {
fetchCall += 1;
if (fetchCall === 1) throw new Error(primarySentinel);
return new Response("fallback result", { status: 500 });
};
const fallbackResult = await executor.execute(input);
assert.equal(fallbackResult.response.status, 500);
fetchCall = 0;
globalThis.fetch = async () => {
fetchCall += 1;
if (fetchCall === 1) return new Response("primary result", { status: 500 });
throw new Error(fallbackSentinel);
};
const primaryResult = await executor.execute(input);
assert.equal(primaryResult.response.status, 500);
const retained = retainedDebug.join("\n");
assert.doesNotMatch(retained, new RegExp(primarySentinel));
assert.doesNotMatch(retained, new RegExp(fallbackSentinel));
assert.match(retained, /omitted: video transcript/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("chatCore propagates video transcript sensitivity to every executor dispatch", () => {
const source = fs.readFileSync("open-sse/handlers/chatCore.ts", "utf8");
const lines = source.split("\n");
const callLines = lines.flatMap((line, index) =>
line.includes("executor.execute({") ? [index] : []
);
assert.equal(callLines.length, 3, "the source contract expects all three executor dispatches");
for (const callLine of callLines) {
const contextEditingLine = lines.findIndex(
(line, index) => index >= callLine && line.includes("contextEditing:")
);
assert.ok(
contextEditingLine > callLine && contextEditingLine - callLine < 30,
"executor dispatch must have a bounded ExecuteInput object"
);
const callSource = lines.slice(callLine, contextEditingLine + 1).join("\n");
assert.match(callSource, /\n\s+videoTranscriptSensitive,\s*$/m);
}
});

View File

@@ -259,6 +259,89 @@ test("guardrail registry fails open when a guardrail throws", async () => {
assert.equal(warnings.length, 1);
});
test("guardrail registry sanitizes native pre-call logs for a transcript-sensitive request", async () => {
const transcriptSentinel = "PRIVATE_GUARDRAIL_PRECALL_TRANSCRIPT_SENTINEL";
class EchoingGuardrail extends BaseGuardrail {
constructor() {
super("echoing", { priority: 5 });
}
override async preCall(
_payload: unknown,
context: import("../../src/lib/guardrails/base.ts").GuardrailContext
) {
context.log?.warn?.("GUARDRAIL", `native echo: ${transcriptSentinel}`, {
transcriptEcho: transcriptSentinel,
});
return { meta: { transcriptEcho: transcriptSentinel } };
}
}
class ExplodingGuardrail extends BaseGuardrail {
constructor() {
super("exploding-private", { priority: 10 });
}
override async preCall() {
throw new Error(transcriptSentinel);
}
}
const retainedLogs: unknown[][] = [];
const capture = (...args: unknown[]) => retainedLogs.push(args);
const registry = new GuardrailRegistry();
registry.register(new EchoingGuardrail());
registry.register(new ExplodingGuardrail());
const result = await registry.runPreCallHooks(
{ safe: true },
{
log: { debug: capture, info: capture, warn: capture, error: capture },
videoTranscriptSensitive: true,
}
);
assert.equal(result.blocked, false);
const retained = JSON.stringify(retainedLogs);
assert.equal(retained.includes(transcriptSentinel), false);
assert.match(retained, /omitted: video transcript/);
});
test("guardrail registry sanitizes native post-call logs for a transcript-sensitive request", async () => {
const transcriptSentinel = "PRIVATE_GUARDRAIL_POSTCALL_TRANSCRIPT_SENTINEL";
class ExplodingPostGuardrail extends BaseGuardrail {
constructor() {
super("exploding-post-private", { priority: 5 });
}
override async postCall(
_response: unknown,
context: import("../../src/lib/guardrails/base.ts").GuardrailContext
) {
context.log?.warn?.("GUARDRAIL", `native echo: ${transcriptSentinel}`, {
transcriptEcho: transcriptSentinel,
});
throw new Error(transcriptSentinel);
}
}
const retainedLogs: unknown[][] = [];
const capture = (...args: unknown[]) => retainedLogs.push(args);
const registry = new GuardrailRegistry();
registry.register(new ExplodingPostGuardrail());
const result = await registry.runPostCallHooks(
{ choices: [] },
{
log: { debug: capture, info: capture, warn: capture, error: capture },
videoTranscriptSensitive: true,
}
);
assert.equal(result.blocked, false);
const retained = JSON.stringify(retainedLogs);
assert.equal(retained.includes(transcriptSentinel), false);
assert.match(retained, /omitted: video transcript/);
});
test("guardrail registry never fails open after the client request aborts", async () => {
class AbortedGuardrail extends BaseGuardrail {
constructor() {

View File

@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import test from "node:test";
import { VideoBridgeGuardrail } from "../../../src/lib/guardrails/videoBridge.ts";
@@ -122,6 +123,7 @@ test("preserves scene-aware sampler metadata in guardrail meta and the transpare
});
test("reports only validated transcript provenance in guardrail metadata", async () => {
const description = "[Video description: caption; transcript[source=client] spoken words]";
const bridge = new VideoBridgeGuardrail({
deps: {
getSettings: async () => ({
@@ -134,7 +136,7 @@ test("reports only validated transcript provenance in guardrail metadata", async
cues: [{ text: "spoken words", start: 1, end: 2, source: "client" }],
});
return {
description: "[Video description: caption; transcript[source=client] spoken words]",
description,
durationSeconds: 2,
framesRequested: 1,
framesUsed: 1,
@@ -170,6 +172,9 @@ test("reports only validated transcript provenance in guardrail metadata", async
{}
);
assert.equal(result.meta?.transcriptCuesApplied, 1);
assert.deepEqual(result.meta?.videoTranscriptDescriptionFingerprints, [
`sha256:${createHash("sha256").update(description).digest("hex")}:${description.length}`,
]);
});
test("converts Responses input using input_text while preserving sibling order", async () => {

View File

@@ -159,6 +159,52 @@ test("runOnResponse passes through if no modification", async () => {
assert.deepEqual(result, { original: true });
});
test("native plugin-hook error logs omit transcript echoes for a sensitive request", async () => {
const sentinels = [
"PRIVATE_PLUGIN_REQUEST_TRANSCRIPT_SENTINEL",
"PRIVATE_PLUGIN_RESPONSE_TRANSCRIPT_SENTINEL",
"PRIVATE_PLUGIN_ERROR_TRANSCRIPT_SENTINEL",
];
const retainedLogs: string[] = [];
const originalConsoleError = console.error;
console.error = (...args: unknown[]) => {
retainedLogs.push(args.map(String).join(" "));
};
try {
registerHook("onRequest", "private-request-plugin", (payload: unknown) => {
(payload as Record<string, unknown>).videoTranscriptSensitive = false;
throw new Error(sentinels[0]);
});
registerHook("onResponse", "private-response-plugin", (payload: unknown) => {
(payload as Record<string, unknown>).videoTranscriptSensitive = false;
throw new Error(sentinels[1]);
});
registerHook("onError", "private-error-plugin", (payload: unknown) => {
(payload as Record<string, unknown>).videoTranscriptSensitive = false;
throw new Error(sentinels[2]);
});
await emitHookBlocking("onRequest", { videoTranscriptSensitive: true });
await runOnResponse(
{
requestId: "private-request",
body: {},
model: "test",
metadata: {},
videoTranscriptSensitive: true,
},
{ ok: true }
);
await emitHook("onError", { videoTranscriptSensitive: true });
} finally {
console.error = originalConsoleError;
}
const retained = retainedLogs.join("\n");
for (const sentinel of sentinels) assert.equal(retained.includes(sentinel), false);
assert.match(retained, /omitted: video transcript/);
});
// ── runOnError ──
test("runOnError fires emitHook", async () => {

View File

@@ -85,8 +85,12 @@ test("extractErrorMessage stays available to toJsonErrorPayload's callers", () =
test("markAccountUnavailable routes lastError through the helper", () => {
const src = fs.readFileSync(new URL("../../src/sse/services/auth.ts", import.meta.url), "utf8");
assert.ok(
src.includes("describeUpstreamFailure(errorText)"),
"auth.ts must describe the failure instead of discarding non-string errors"
src.includes("const retainedErrorText = options.retainedErrorText ?? errorText"),
"auth.ts must retain the caller-selected safe error text"
);
assert.ok(
src.includes("describeUpstreamFailure(retainedErrorText)"),
"auth.ts must describe the retained failure instead of echoing the raw provider body"
);
assert.equal(
/typeof errorText === "string" \? errorText\.slice\(0, 100\) : "Provider error"/.test(src),

View File

@@ -182,6 +182,24 @@ describe("Reasoning Replay Cache — Service Layer", () => {
assert.equal(lookupReasoning("call_capture_2"), "Captured assistant reasoning");
});
it("should never cache reasoning echoed by a transcript-sensitive response", () => {
clearReasoningCacheAll();
const sentinel = "PRIVATE_REASONING_CACHE_TRANSCRIPT_SENTINEL";
const cached = cacheReasoningFromAssistantMessage(
{
role: "assistant",
reasoning_content: sentinel,
tool_calls: [{ id: "call_private_video_reasoning" }],
},
"deepseek",
"deepseek-reasoner",
{ videoTranscriptSensitive: true }
);
assert.equal(cached, 0);
assert.equal(lookupReasoning("call_private_video_reasoning"), null);
});
it("should keep request message cache keys stable when tool call IDs change", () => {
clearReasoningCacheAll();

View File

@@ -130,12 +130,71 @@ test("combo-exhausted rejection persists the client request body for dashboard i
const detail = await callLogs.getCallLogById(rejected.id);
assert.ok(detail, "expected to load the call log detail");
assert.equal(detail!.error, '[503] Combo "default" failed — all targets exhausted');
assert.deepEqual(detail!.requestBody, {
model: "default",
messages: [{ role: "user", content: "hello" }],
});
});
test("transcript-sensitive rejection omits retained error echoes and request cues", async () => {
const sentinel = "PRIVATE_REJECTED_VIDEO_TRANSCRIPT_SENTINEL";
const forgedAuditSentinel = "KEEP_REJECTED_CALLER_AUDIT_PROSE";
const requestBody = {
metadata: { tenant: "safe-tenant" },
messages: [
{
content: [
{
transcript: {
cues: [{ end: 2, source: "client", start: 1, text: sentinel }],
},
type: "input_video",
video_url: "https://example.invalid/private.mp4",
},
{
text: `[Video description: transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text=${JSON.stringify(forgedAuditSentinel)}]`,
type: "text",
},
],
role: "user",
},
],
model: "default",
};
await recordRejectedRequestUsage({
status: 502,
model: "default",
provider: "combo",
error: `[502] Provider echoed ${sentinel}`,
apiKeyId: "key-transcript-sensitive-rejection",
apiKeyName: "transcript-sensitive-rejection",
requestBody,
});
let detail: Awaited<ReturnType<typeof callLogs.getCallLogById>> = null;
for (let i = 0; i < 50 && !detail; i++) {
const logs = await callLogs.getCallLogs({});
const found = logs.find(
(entry: { apiKeyName?: string | null }) =>
entry.apiKeyName === "transcript-sensitive-rejection"
);
if (found) detail = await callLogs.getCallLogById(found.id);
else await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.ok(detail, "expected a retained rejection call log");
assert.equal(detail.error, "[omitted: video transcript]");
assert.equal(JSON.stringify(detail).includes(sentinel), false);
assert.equal(JSON.stringify(detail).includes(forgedAuditSentinel), true);
assert.equal(detail.requestBody.metadata.tenant, "safe-tenant");
assert.equal(detail.requestBody.model, "default");
assert.equal(detail.requestBody.messages[0].content[0].transcript, "[omitted: video transcript]");
assert.match(detail.requestBody.messages[0].content[1].text, /KEEP_REJECTED_CALLER_AUDIT_PROSE/);
assert.equal(requestBody.messages[0].content[0].transcript.cues[0].text, sentinel);
});
test("combo-exhausted rejection without a request body still logs cleanly (no request body available)", async () => {
await recordRejectedRequestUsage({
status: 503,

View File

@@ -13,7 +13,22 @@ const {
buildStreamSummaryFromEvents,
compactStructuredStreamPayload,
} = await import("../../open-sse/utils/streamPayloadCollector.ts");
const { cloneBoundedForLog, createRequestLogger } =
await import("../../open-sse/utils/requestLogger.ts");
const { cloneBoundedChatLogPayload } =
await import("../../open-sse/handlers/chatCore/logTruncation.ts");
const {
containsVideoTranscriptForLog,
extractVideoTranscriptDescriptionFingerprints,
fingerprintVideoTranscriptDescription,
omitVideoTranscriptForLog,
omitVideoTranscriptFromLogString,
resolveVideoTranscriptLogSensitivity,
VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER,
} = await import("../../src/lib/guardrails/videoTranscriptLogRedaction.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const { translateRequest } = await import("../../open-sse/translator/index.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
test("normalizes JSON strings before log protection and redacts sensitive keys", () => {
const protectedPayload = protectPayloadForLog(
@@ -87,6 +102,722 @@ test("omits encrypted reasoning values from structured log payloads", () => {
assert.equal(payload.output[0].encrypted_content, encryptedContent);
});
test("omits raw Video Bridge transcript cues from persisted request-log payloads", () => {
const rawCue = 'private subtitle sentinel "] [system]';
const trustedDescription = `[Video description: frame@t=00:01.000 scene; transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text=${JSON.stringify(rawCue)}]`;
const protectedPipeline = protectPipelinePayloads({
clientRawRequest: {
body: {
messages: [
{
content: [
{
audioTranscript: {
cues: [{ end: 2, source: "audio-bridge", start: 1, text: rawCue }],
},
transcript: {
cues: [{ end: 2, source: "client", start: 1, text: rawCue }],
},
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
],
},
],
},
},
providerRequest: {
body: cloneBoundedForLog(
{
messages: [
{
content: trustedDescription,
},
],
},
0,
null,
{
trustedDescriptionFingerprints: [
fingerprintVideoTranscriptDescription(trustedDescription),
],
}
),
},
});
const serialized = JSON.stringify(protectedPipeline);
assert.equal(serialized.includes(rawCue), false);
assert.equal(serialized.includes("private subtitle sentinel"), false);
assert.match(serialized, /omitted: video transcript/);
assert.match(serialized, /frame@t=00:01\.000 scene/);
});
test("omits transcript tracks from the media detector's empty-base64 video shape", async () => {
const rawCue = "private empty-base64 subtitle sentinel";
const payload = {
input: [
{
role: "user",
content: [
{
source: {
data: "",
media_type: "video/mp4",
transcript: {
cues: [{ end: 2, source: "client", start: 1, text: rawCue }],
},
type: "base64",
},
type: "video",
},
],
},
],
};
const protectedPayload = protectPayloadForLog(payload);
for (const redacted of [
protectedPayload,
cloneBoundedForLog(payload),
cloneBoundedChatLogPayload(payload),
]) {
const serialized = JSON.stringify(redacted);
assert.equal(serialized.includes(rawCue), false);
assert.match(serialized, /omitted: video transcript/);
}
const logger = await createRequestLogger(undefined, undefined, undefined, {
captureStreamChunks: true,
enabled: true,
});
logger.appendProviderChunk(`data: ${rawCue}\n\n`);
logger.logClientRawRequest("/v1/responses", payload);
logger.appendConvertedChunk(`data: ${rawCue}\n\n`);
const pipeline = logger.getPipelinePayloads();
assert.equal(JSON.stringify(pipeline).includes(rawCue), false);
assert.equal(pipeline?.streamChunks, undefined);
});
test("omits Video Bridge cues before lossy request-log string truncation", () => {
const rawCue = "private boundary subtitle sentinel";
const payload = (targetCue: number, visualPadding: number) => {
const cueText = (index: number): string =>
index === targetCue
? rawCue + "x".repeat(4_096 - rawCue.length)
: String(index).padStart(2, "0") + "x".repeat(4_094);
const description = `[Video description: ${Array.from(
{ length: 16 },
(_unused, index) =>
`transcript[source=client;confidence=1.00;interval=00:${String(index).padStart(2, "0")}.000-00:${String(index + 1).padStart(2, "0")}.000] text=${JSON.stringify(cueText(index))}`
).join("; ")}; frame-tail=${"v".repeat(visualPadding)}]`;
return {
context: {
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
},
value: { input: [{ content: description, role: "user" }] },
};
};
const requestLogNine = payload(9, 3_507);
const requestLogTen = payload(10, 7_710);
const chatLogNine = payload(9, 3_522);
const alternateChatLogNine = payload(9, 3_598);
const boundedPayloads = [
cloneBoundedForLog(requestLogNine.value, 0, null, requestLogNine.context),
cloneBoundedForLog(requestLogTen.value, 0, null, requestLogTen.context),
cloneBoundedChatLogPayload(chatLogNine.value, 0, chatLogNine.context),
cloneBoundedChatLogPayload(alternateChatLogNine.value, 0, alternateChatLogNine.context),
];
for (const bounded of boundedPayloads) {
const serialized = JSON.stringify(protectPayloadForLog(bounded));
assert.equal(serialized.includes(rawCue), false);
assert.match(serialized, /omitted: video transcript/);
}
});
test("suppresses bounded stream-chunk logs for a request carrying Video Bridge cues", async () => {
const rawCue = "private streamed subtitle sentinel";
const description =
`[Video description: transcript[source=embedded;confidence=1.00;` +
`interval=00:01.000-00:02.000] text=${JSON.stringify(rawCue)}]`;
const logger = await createRequestLogger(undefined, undefined, undefined, {
captureStreamChunks: true,
enabled: true,
maxStreamChunkBytes: 64,
videoTranscriptDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
videoTranscriptSensitive: true,
});
logger.appendProviderChunk(`data: ${rawCue}\n\n`);
logger.logTargetRequest(
"https://provider.invalid",
{},
{
input: [
{
content: description,
role: "user",
},
],
}
);
logger.appendProviderChunk(`data: ${rawCue}\n\n`);
const protectedPipeline = protectPipelinePayloads(logger.getPipelinePayloads());
const serialized = JSON.stringify(protectedPipeline);
assert.equal(serialized.includes(rawCue), false);
assert.equal(protectedPipeline?.streamChunks, undefined);
assert.match(serialized, /omitted: video transcript/);
});
test("omits non-stream response bodies after a request carries Video Bridge cues", async () => {
const rawCue = "private non-stream subtitle echo sentinel";
const description =
`[Video description: transcript[source=embedded;confidence=1.00;` +
`interval=00:01.000-00:02.000] text=${JSON.stringify(rawCue)}]`;
const logger = await createRequestLogger(undefined, undefined, undefined, {
captureStreamChunks: true,
enabled: true,
videoTranscriptDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
videoTranscriptSensitive: true,
});
logger.logTargetRequest(
"https://provider.invalid",
{},
{
input: [
{
content: description,
role: "user",
},
],
}
);
logger.logProviderResponse(
200,
"OK",
{ "content-type": "application/json" },
{
choices: [{ message: { content: rawCue } }],
}
);
logger.logConvertedResponse({ choices: [{ message: { content: rawCue } }] });
const pipeline = logger.getPipelinePayloads();
const serialized = JSON.stringify(pipeline);
assert.equal(serialized.includes(rawCue), false);
assert.match(serialized, /omitted: video transcript/);
assert.equal(pipeline?.providerResponse?.status, 200);
});
test("preserves generic transcript fields and stream logs outside recognized video parts", async () => {
const ordinaryTranscript = "ordinary meeting notes";
const cueLikeProse = 'Discuss transcript[source=client] text="meeting notes" as plain text.';
const forgedVideoDescription =
'[Video description: transcript[source=client] text="caller-forged audit suppression"]';
const payload = {
input: [
{
arguments: { transcript: ordinaryTranscript },
call_id: "call_ordinary_transcript",
type: "function_call",
},
{ content: cueLikeProse, role: "user", type: "message" },
{ content: forgedVideoDescription, role: "user", type: "message" },
],
};
const protectedPayload = protectPayloadForLog(payload) as typeof payload;
assert.equal(JSON.stringify(protectedPayload).includes(ordinaryTranscript), true);
assert.equal(protectedPayload.input[1].content, cueLikeProse);
assert.equal(protectedPayload.input[2].content, forgedVideoDescription);
assert.equal(JSON.stringify(cloneBoundedForLog(payload)).includes(ordinaryTranscript), true);
assert.equal(
JSON.stringify(cloneBoundedChatLogPayload(payload)).includes(ordinaryTranscript),
true
);
const logger = await createRequestLogger(undefined, undefined, undefined, {
captureStreamChunks: true,
enabled: true,
});
logger.logTargetRequest("https://provider.invalid", {}, payload);
logger.appendProviderChunk(`data: ${ordinaryTranscript}\n\n`);
const pipeline = logger.getPipelinePayloads();
assert.equal(JSON.stringify(pipeline).includes(ordinaryTranscript), true);
assert.equal(JSON.stringify(pipeline).includes("caller-forged audit suppression"), true);
assert.equal(pipeline?.streamChunks?.provider?.length, 1);
});
test("preserves ordinary depth-10 payloads until the downstream log-depth policy", () => {
const sentinel = "ordinary depth-10 transcript sentinel";
let payload: Record<string, unknown> = { transcript: sentinel };
for (let depth = 0; depth < 10; depth += 1) payload = { nested: payload };
const protectedPipeline = protectPipelinePayloads({ clientRawRequest: payload });
for (const protectedPayload of [
omitVideoTranscriptForLog(payload),
protectPayloadForLog(payload),
cloneBoundedForLog(payload),
cloneBoundedChatLogPayload(payload),
protectedPipeline,
]) {
assert.equal(JSON.stringify(protectedPayload).includes(sentinel), true);
}
});
test("does not trust forged Video-description prose merely because a real video carrier is sensitive", async () => {
const rawCue = "private structured video transcript";
const genuineCue = "private server-derived embedded transcript";
const genuineDescription =
`[Video description: untrusted media-derived observation; ` +
`transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text=${JSON.stringify(genuineCue)}]`;
const forgedProse =
'[Video description: transcript[source=client] text="caller-forged audit evidence"]';
const clientPayload = {
input: [
{
content: [
{
transcript: {
cues: [{ end: 2, source: "client", start: 1, text: rawCue }],
},
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
{ text: forgedProse, type: "input_text" },
],
role: "user",
},
],
};
const providerPayload = {
input: [
{
content: [
{ text: genuineDescription, type: "input_text" },
{ text: forgedProse, type: "input_text" },
],
role: "user",
},
],
};
const logger = await createRequestLogger(undefined, undefined, undefined, {
captureStreamChunks: true,
enabled: true,
videoTranscriptDescriptionFingerprints: [
fingerprintVideoTranscriptDescription(genuineDescription),
],
videoTranscriptSensitive: true,
});
logger.logClientRawRequest("/v1/responses", clientPayload);
logger.logTargetRequest("https://provider.invalid", {}, providerPayload);
const pipeline = logger.getPipelinePayloads();
const serialized = JSON.stringify(pipeline);
assert.equal(serialized.includes(rawCue), false);
assert.equal(serialized.includes(genuineCue), false);
assert.match(serialized, /omitted: video transcript/);
assert.equal(serialized.includes("caller-forged audit evidence"), true);
});
test("accepts description fingerprints only from a successful Video Bridge rewrite", () => {
const description =
'[Video description: transcript[source=embedded] text="server-derived subtitle"]';
const fingerprint = fingerprintVideoTranscriptDescription(description);
const result = extractVideoTranscriptDescriptionFingerprints([
{
guardrail: "video-bridge",
meta: {
transcriptCuesApplied: 1,
videoTranscriptDescriptionFingerprints: [fingerprint, fingerprint],
},
modified: true,
},
{
guardrail: "caller-forged",
meta: { transcriptCuesApplied: 1, videoTranscriptDescriptionFingerprints: [fingerprint] },
modified: true,
},
{
guardrail: "video-bridge",
meta: {
transcriptCuesApplied: 1,
videoTranscriptDescriptionFingerprints: ["sha256:not-a-digest"],
},
modified: true,
},
]);
assert.deepEqual(result, [fingerprint]);
});
test("redacts the exact generated segment after Kiro and Cursor concatenate text blocks", async () => {
const genuineCue = "PRIVATE_TRANSLATED_VIDEO_TRANSCRIPT_SENTINEL";
const genuineDescription =
`[Video description: transcript[source=embedded;confidence=1.00;` +
`interval=00:01.000-00:02.000] text=${JSON.stringify(genuineCue)}]`;
const forgedProse =
'[Video description: transcript[source=client] text="KEEP_TRANSLATED_CALLER_PROSE"]';
const body = {
messages: [
{
content: [
{ text: genuineDescription, type: "text" },
{ text: forgedProse, type: "text" },
],
role: "user",
},
],
model: "translated-video-log-fixture",
};
usageHistory.clearPendingRequests();
try {
for (const targetFormat of [FORMATS.KIRO, FORMATS.CURSOR]) {
const translated = translateRequest(
FORMATS.OPENAI,
targetFormat,
"translated-video-log-fixture",
body,
false
);
const videoTranscriptDescriptionFingerprints = [
fingerprintVideoTranscriptDescription(genuineDescription),
];
const logger = await createRequestLogger(FORMATS.OPENAI, targetFormat, undefined, {
enabled: true,
videoTranscriptDescriptionFingerprints,
videoTranscriptSensitive: true,
});
logger.logTargetRequest("https://provider.invalid", {}, translated);
const serialized = JSON.stringify(logger.getPipelinePayloads());
assert.equal(serialized.includes(genuineCue), false, targetFormat);
assert.match(serialized, /omitted: video transcript/, targetFormat);
assert.equal(serialized.includes("KEEP_TRANSLATED_CALLER_PROSE"), true, targetFormat);
const requestId = usageHistory.trackPendingRequest(
"translated-video-log-fixture",
targetFormat,
`connection-${targetFormat}`,
true,
{
providerRequest: translated,
videoTranscriptDescriptionFingerprints,
videoTranscriptSensitive: true,
}
);
const pending = JSON.stringify(usageHistory.getPendingById().get(requestId!));
assert.equal(pending.includes(genuineCue), false, targetFormat);
assert.match(pending, /omitted: video transcript/, targetFormat);
assert.equal(pending.includes("KEEP_TRANSLATED_CALLER_PROSE"), true, targetFormat);
}
} finally {
usageHistory.clearPendingRequests();
}
});
test("fails closed after a bounded number of forged description-prefix candidates", () => {
const genuineDescription =
'[Video description: transcript[source=embedded] text="bounded genuine subtitle"]';
const prefixFlood = Array.from(
{ length: 129 },
(_unused, index) => `[Video description: forged-prefix-${index}]`
).join("");
const omitted = omitVideoTranscriptForLog(
{ content: prefixFlood + genuineDescription },
{
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(genuineDescription)],
}
) as { content: string };
assert.equal(omitted.content, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
});
test("fails closed after a bounded number of trusted-description candidate hashes", () => {
const genuineDescription =
'[Video description: transcript[source=embedded] text="bounded hash subtitle"]';
const forgedPrefixes = Array.from(
{ length: 9 },
(_unused, index) => `[Video description: candidate-${index}]`
).join("");
const trustedDescriptionFingerprints = [
...Array.from({ length: 63 }, (_unused, index) =>
fingerprintVideoTranscriptDescription(`[Video description: identity-${index}]`)
),
fingerprintVideoTranscriptDescription(genuineDescription),
];
const omitted = omitVideoTranscriptForLog(
{ content: forgedPrefixes + genuineDescription },
{ trustedDescriptionFingerprints }
) as { content: string };
assert.equal(omitted.content, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
});
test("trusted malformed or over-budget serialized cues fail closed", () => {
const privateTranscript = "PRIVATE_MALFORMED_SERIALIZED_CUE_SENTINEL";
const malformedDescriptions = [
`[Video description: transcript[source=embedded${";metadata=x".repeat(100)}] text=${JSON.stringify(privateTranscript)}]`,
`[Video description: transcript[source=embedded text=${JSON.stringify(privateTranscript)}]`,
`[Video description: transcript[source=embedded]text=${JSON.stringify(privateTranscript)}]`,
`[Video description: transcript[source=embedded] payload=${JSON.stringify(privateTranscript)}]`,
`[Video description: transcript[source=embedded] text=${JSON.stringify("safe cue")}; transcript[source=audio-bridge${";metadata=x".repeat(100)}] text=${JSON.stringify(privateTranscript)}]`,
];
for (const description of malformedDescriptions) {
const context = {
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
};
const direct = omitVideoTranscriptFromLogString(description, context);
const structured = omitVideoTranscriptForLog({ content: description }, context) as {
content: string;
};
assert.equal(direct, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER, description);
assert.equal(structured.content, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER, description);
assert.equal(direct.includes(privateTranscript), false, description);
assert.equal(structured.content.includes(privateTranscript), false, description);
}
});
test("bounds cyclic objects and fails closed when the transcript-security depth is exceeded", () => {
const cyclic: Record<string, unknown> = { transcript: "ordinary cyclic notes" };
cyclic.self = cyclic;
let deep: Record<string, unknown> = { transcript: "ordinary deeply nested notes" };
for (let index = 0; index < 20_000; index += 1) deep = { child: deep };
assert.doesNotThrow(() => containsVideoTranscriptForLog(cyclic));
assert.doesNotThrow(() => containsVideoTranscriptForLog(deep));
assert.equal(containsVideoTranscriptForLog(cyclic), false);
assert.equal(containsVideoTranscriptForLog(deep), true);
const omittedCycle = omitVideoTranscriptForLog(cyclic);
const omittedDeep = omitVideoTranscriptForLog(deep);
assert.doesNotThrow(() => JSON.stringify(omittedCycle));
assert.equal(omittedDeep, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
});
test("fails closed when enumerable getters or proxies throw during transcript inspection", () => {
const throwingGetter: Record<string, unknown> = {};
Object.defineProperty(throwingGetter, "nested", {
enumerable: true,
get() {
throw new Error("PRIVATE_GETTER_TRANSCRIPT_SENTINEL");
},
});
const throwingProxy = new Proxy<Record<string, unknown>>(
{},
{
ownKeys() {
throw new Error("PRIVATE_PROXY_TRANSCRIPT_SENTINEL");
},
}
);
for (const payload of [throwingGetter, throwingProxy]) {
assert.equal(containsVideoTranscriptForLog(payload), true);
assert.equal(omitVideoTranscriptForLog(payload), VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
}
});
test("fails closed when an enumerable function can replace the retained JSON representation", () => {
const privateTranscript = "PRIVATE_TOJSON_VIDEO_TRANSCRIPT_SENTINEL";
const payload = {
safe: "retained diagnostic",
toJSON() {
return {
transcript: privateTranscript,
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
};
},
};
assert.equal(containsVideoTranscriptForLog(payload), true);
const omitted = omitVideoTranscriptForLog(payload) as Record<string, unknown>;
const serialized = JSON.stringify(omitted);
assert.equal(serialized.includes(privateTranscript), false);
assert.equal(omitted.safe, "retained diagnostic");
assert.notEqual(typeof omitted.toJSON, "function");
});
test("treats explicit malformed and nested video carriers as transcript-sensitive", () => {
const privateTranscript = "PRIVATE_MALFORMED_VIDEO_TRANSCRIPT_SENTINEL";
const payloads = [
{
transcript: privateTranscript,
type: "input_video",
},
{
transcript: privateTranscript,
type: "video_url",
video_url: "",
},
{
input_video: {
transcript: privateTranscript,
url: "data:video/mp4;base64,AA==",
},
type: "input_video",
},
{
input_video: {
url: { transcript: privateTranscript },
},
type: "input_video",
},
{
source: { transcript: privateTranscript },
type: "video_source",
},
{
source: {
media_type: "video/mp4",
transcript: privateTranscript,
},
type: "video",
},
];
for (const payload of payloads) {
assert.equal(containsVideoTranscriptForLog(payload), true, JSON.stringify(payload));
const serialized = JSON.stringify(omitVideoTranscriptForLog(payload));
assert.equal(serialized.includes(privateTranscript), false, serialized);
assert.match(serialized, /omitted: video transcript/);
}
});
test("keeps raw request sensitivity after a guardrail removes the original carrier", () => {
const rawRequestBody = {
messages: [
{
content: [
{
transcript: "PRIVATE_RAW_REQUEST_TRANSCRIPT_SENTINEL",
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
],
role: "user",
},
],
};
const processedBody = {
messages: [{ content: "guardrail replaced the media", role: "user" }],
};
assert.equal(resolveVideoTranscriptLogSensitivity({ processedBody, rawRequestBody }), true);
assert.equal(
resolveVideoTranscriptLogSensitivity({
processedBody: { metadata: { transcript: "ordinary audit label" } },
rawRequestBody: { metadata: { transcript: "ordinary caller label" } },
}),
false
);
});
test("fails closed at the aggregate traversal budget before a tail video transcript can leak", () => {
const privateTranscript = "private over-budget video transcript sentinel";
const payload: unknown[] = Array.from(
{ length: 10_001 },
(_unused, index) => `ordinary entry ${index}`
);
// The detector must treat a bounded security scan as unknown/sensitive even without a cue.
assert.equal(containsVideoTranscriptForLog(payload), true);
payload[payload.length - 1] = {
transcript: privateTranscript,
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
};
const omitted = omitVideoTranscriptForLog(payload);
assert.equal(omitted, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
assert.equal(JSON.stringify(omitted).includes(privateTranscript), false);
});
test("detects and omits an input-video transcript below nine ordinary objects", () => {
const privateTranscript = "private depth-9 video transcript sentinel";
let payload: Record<string, unknown> = {
transcript: privateTranscript,
type: "input_video",
video_url: { url: "data:video/mp4;base64,AA==" },
};
for (let depth = 0; depth < 9; depth += 1) payload = { nested: payload };
assert.equal(containsVideoTranscriptForLog(payload), true);
const omitted = JSON.stringify(omitVideoTranscriptForLog(payload));
assert.equal(omitted.includes(privateTranscript), false);
assert.match(omitted, /omitted: video transcript/);
});
test("redacts only direct transcript carriers within a recognized video part", () => {
const directCue = "private direct video subtitle";
const sourceCue = "private source video subtitle";
const unrelatedNestedTranscript = "ordinary nested metadata transcript";
const payload = {
type: "video",
source: {
data: "AA==",
media_type: "video/mp4",
transcript: sourceCue,
metadata: { transcript: unrelatedNestedTranscript },
type: "base64",
},
transcript: directCue,
metadata: { transcript: unrelatedNestedTranscript },
};
const omitted = omitVideoTranscriptForLog(payload) as typeof payload;
assert.equal(omitted.transcript, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
assert.equal(omitted.metadata.transcript, unrelatedNestedTranscript);
assert.equal(omitted.source.transcript, VIDEO_TRANSCRIPT_LOG_OMISSION_MARKER);
assert.equal(omitted.source.metadata.transcript, unrelatedNestedTranscript);
});
test("suppresses active stream chunks even when persisted pipeline logging is disabled", async () => {
const rawCue = "private active-log subtitle sentinel";
const model = "video-log-redaction-model";
const provider = "video-log-redaction-provider";
const connectionId = "video-log-redaction-connection";
usageHistory.clearPendingRequests();
try {
const requestId = usageHistory.trackPendingRequest(model, provider, connectionId, true);
const logger = await createRequestLogger(undefined, undefined, model, {
captureStreamChunks: true,
connectionId,
enabled: false,
model,
provider,
requestId,
});
logger.appendProviderChunk(`data: ${rawCue}\n\n`);
logger.logClientRawRequest("/v1/responses", {
input: [
{
audioTranscript: {
cues: [{ end: 2, source: "audio-bridge", start: 1, text: rawCue }],
},
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
],
});
logger.appendOpenAIChunk(`data: ${rawCue}\n\n`);
logger.appendConvertedChunk(`data: ${rawCue}\n\n`);
const chunks = usageHistory.getPendingById().get(requestId)?.streamChunks;
assert.deepEqual(chunks, { client: [], openai: [], provider: [] });
} finally {
usageHistory.clearPendingRequests();
}
});
test("omits encrypted reasoning split across captured SSE chunks", () => {
const encryptedContent = "opaque-replay-state".repeat(128);
const protectedPipeline = protectPipelinePayloads({

View File

@@ -8,7 +8,10 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reqlogger
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const { fingerprintVideoTranscriptDescription } =
await import("../../src/lib/guardrails/videoTranscriptLogRedaction.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const pendingRequestScope = await import("../../src/lib/usage/pendingRequestScope.ts");
const callLogs = await import("../../src/lib/usage/callLogs.ts");
test.after(() => {
@@ -61,6 +64,84 @@ test("trackPendingRequest creates a detail entry", () => {
assert.equal(detail.clientRequest.messages[0].content, "hi");
});
test("pending request metadata omits trusted Video Bridge cues and response echoes", () => {
usageHistory.clearPendingRequests();
const sentinel = "PRIVATE_ACTIVE_VIDEO_TRANSCRIPT_SENTINEL";
const description =
`[Video description: focus=full; untrusted media-derived observations; ` +
`transcript[source=embedded start=0 end=1 confidence=1] text="${sentinel}"]`;
const metadata = {
providerRequest: { messages: [{ role: "user", content: description }] },
stage: "registered",
videoTranscriptDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
videoTranscriptSensitive: true,
};
const requestId = usageHistory.trackPendingRequest(
"gpt-4",
"openai",
"conn-transcript",
true,
metadata
);
assert.ok(requestId);
const scope = {
id: requestId,
model: "gpt-4",
provider: "openai",
connectionId: "conn-transcript",
videoTranscriptDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
videoTranscriptSensitive: true,
};
pendingRequestScope.updatePendingScope(scope, {
providerRequest: { messages: [{ role: "user", content: description }] },
providerResponse: { output: sentinel },
clientResponse: { output: sentinel },
error: sentinel,
stage: "provider_response_started",
});
const detail = usageHistory.getPendingById().get(requestId);
const serialized = JSON.stringify(detail);
assert.doesNotMatch(serialized, new RegExp(sentinel));
assert.match(serialized, /\[omitted: video transcript\]/);
});
test("pending metadata keeps forged Video prose beside a structured transcript carrier", () => {
usageHistory.clearPendingRequests();
const rawCue = "PRIVATE_STRUCTURED_TRANSCRIPT_SENTINEL";
const forgedProse =
'[Video description: transcript[source=client] text="KEEP_CALLER_AUDIT_PROSE"]';
const request = {
input: [
{
content: [
{
transcript: {
cues: [{ end: 2, source: "client", start: 1, text: rawCue }],
},
type: "input_video",
video_url: "data:video/mp4;base64,AA==",
},
{ text: forgedProse, type: "input_text" },
],
role: "user",
},
],
};
const requestId = usageHistory.trackPendingRequest("gpt-4", "openai", "conn-forged", true, {
clientRequest: request,
providerRequest: request,
videoTranscriptSensitive: true,
});
assert.ok(requestId);
const serialized = JSON.stringify(usageHistory.getPendingById().get(requestId));
assert.equal(serialized.includes(rawCue), false);
assert.equal(serialized.includes("KEEP_CALLER_AUDIT_PROSE"), true);
});
test("trackPendingRequest decrements and removes detail on finish", () => {
usageHistory.clearPendingRequests();
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true);

View File

@@ -131,6 +131,155 @@ test("resolvePreviousResponseState reads output from a wrapped (streaming) clien
});
});
test("resolvePreviousResponseState continues from a safely redacted Video Bridge transcript", () => {
insertCallLog({
id: "log-video-redacted",
responseId: "resp_video_redacted",
apiKeyId: "key-1",
detailState: "ready",
artifactRelPath: "2026-01-01/log-video-redacted.json",
});
const request = {
body: {
input: [
{
role: "user",
content:
'[Video description: transcript[source=embedded] text="[omitted: video transcript]"]',
},
],
},
};
writeArtifact("2026-01-01/log-video-redacted.json", {
_omnirouteVideoTranscriptRedacted: true,
clientRawRequest: request,
providerRequest: request,
clientResponse: {
id: "resp_video_redacted",
output: [{ type: "message", role: "assistant", content: "summary" }],
},
});
assert.deepEqual(store.resolvePreviousResponseState("resp_video_redacted", "key-1"), {
input: request.body.input,
output: [{ type: "message", role: "assistant", content: "summary" }],
});
});
test("resolvePreviousResponseState continues when the prior output was safely redacted", () => {
insertCallLog({
id: "log-video-output-redacted",
responseId: "resp_video_output_redacted",
apiKeyId: "key-1",
detailState: "ready",
artifactRelPath: "2026-01-01/log-video-output-redacted.json",
});
const request = {
body: { input: [{ role: "user", content: "summarize the prior result" }] },
};
writeArtifact("2026-01-01/log-video-output-redacted.json", {
_omnirouteVideoTranscriptRedacted: true,
clientRawRequest: request,
providerRequest: request,
clientResponse: {
id: "resp_video_output_redacted",
output: [
{
type: "message",
role: "assistant",
content: '[Video description: text="[omitted: video transcript]"]',
},
],
},
});
assert.deepEqual(store.resolvePreviousResponseState("resp_video_output_redacted", "key-1"), {
input: request.body.input,
output: [
{
type: "message",
role: "assistant",
content: '[Video description: text="[omitted: video transcript]"]',
},
],
});
});
test("resolvePreviousResponseState continues from an empty-base64 transcript omission", () => {
insertCallLog({
id: "log-empty-base64-video-redacted",
responseId: "resp_empty_base64_video_redacted",
apiKeyId: "key-1",
detailState: "ready",
artifactRelPath: "2026-01-01/log-empty-base64-video-redacted.json",
});
const request = {
body: {
input: [
{
type: "video",
source: {
data: "",
media_type: "video/mp4",
transcript: "[omitted: video transcript]",
type: "base64",
},
},
],
},
};
writeArtifact("2026-01-01/log-empty-base64-video-redacted.json", {
_omnirouteVideoTranscriptRedacted: true,
clientRawRequest: request,
providerRequest: request,
clientResponse: {
id: "resp_empty_base64_video_redacted",
output: [{ type: "message", role: "assistant", content: "summary" }],
},
});
assert.deepEqual(
store.resolvePreviousResponseState("resp_empty_base64_video_redacted", "key-1"),
{
input: request.body.input,
output: [{ type: "message", role: "assistant", content: "summary" }],
}
);
});
test("resolvePreviousResponseState preserves ordinary transcript fields and marker-like prose", () => {
insertCallLog({
id: "log-ordinary-transcript",
responseId: "resp_ordinary_transcript",
apiKeyId: "key-1",
detailState: "ready",
artifactRelPath: "2026-01-01/log-ordinary-transcript.json",
});
const input = [
{
arguments: { transcript: "ordinary meeting notes" },
type: "function_call",
},
{
content:
'[Video description: caller prose with text="[omitted: video transcript]"] without a video.',
role: "user",
type: "message",
},
];
const output = [{ type: "message", role: "assistant", content: "ordinary reply" }];
writeArtifact("2026-01-01/log-ordinary-transcript.json", {
clientRawRequest: { body: { input } },
providerRequest: { body: { input } },
clientResponse: { id: "resp_ordinary_transcript", output },
});
assert.deepEqual(store.resolvePreviousResponseState("resp_ordinary_transcript", "key-1"), {
input,
output,
});
});
test("resolvePreviousResponseState returns null for an unknown response id", () => {
const result = store.resolvePreviousResponseState("resp_does_not_exist", "key-1");
assert.equal(result, null);

View File

@@ -0,0 +1,61 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-stream-retention-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const { finalizeStreamRequestLog } =
await import("../../open-sse/utils/streamFailureFinalization.ts");
test.after(() => {
usageHistory.clearPendingRequests();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { force: true, recursive: true });
});
test("stream finalization omits transcript-sensitive response echoes", () => {
usageHistory.clearPendingRequests();
const sentinel = "PRIVATE_STREAM_FINALIZATION_TRANSCRIPT_SENTINEL";
const requestId = usageHistory.trackPendingRequest(
"video-model",
"video-provider",
"video-connection",
true,
{ videoTranscriptSensitive: true }
);
assert.ok(requestId);
finalizeStreamRequestLog({
pendingRequestId: requestId,
model: "video-model",
provider: "video-provider",
connectionId: "video-connection",
providerResponse: { echo: sentinel },
clientResponse: { echo: sentinel },
status: 502,
error: sentinel,
videoTranscriptSensitive: true,
});
const retained = usageHistory.getCompletedDetails().get(requestId);
assert.ok(retained);
const serialized = JSON.stringify(retained);
assert.equal(serialized.includes(sentinel), false);
assert.match(serialized, /omitted: video transcript/);
});
test("chatCore propagates the request sensitivity bit into stream finalization", () => {
const source = fs.readFileSync("open-sse/handlers/chatCore.ts", "utf8");
const callStart = source.indexOf("streamFailure.finalizeStreamRequestLog({");
assert.notEqual(callStart, -1, "stream finalization call must exist");
const callEnd = source.indexOf("\n });", callStart);
assert.notEqual(callEnd, -1, "stream finalization call must be bounded");
const callSource = source.slice(callStart, callEnd);
assert.match(callSource, /\n\s+videoTranscriptSensitive,\s*$/m);
});

View File

@@ -51,9 +51,14 @@ test("pipeWithDisconnect stall watchdog logs instead of silently swallowing a th
};
try {
const stream = pipeWithDisconnect(new Response(source), new TransformStream(), streamController, {
stallTimeoutMs: 40,
});
const stream = pipeWithDisconnect(
new Response(source),
new TransformStream(),
streamController,
{
stallTimeoutMs: 40,
}
);
await readStreamText(stream);
} finally {
console.debug = originalDebug;
@@ -67,3 +72,45 @@ test("pipeWithDisconnect stall watchdog logs instead of silently swallowing a th
"a throwing handleError during the stall watchdog must be logged via console.debug, not swallowed"
);
});
test("pipeWithDisconnect omits transcript-sensitive stall watchdog diagnostics", async () => {
const privateCue = "PRIVATE_VIDEO_CUE_stall_watchdog_4c91";
const source = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("x"));
},
cancel() {},
});
const streamController = {
isConnected: () => true,
handleError() {
throw new Error(privateCue);
},
handleComplete() {},
abort() {},
};
const debugCalls = [];
const originalDebug = console.debug;
console.debug = (...args) => {
debugCalls.push(args);
};
try {
const stream = pipeWithDisconnect(
new Response(source),
new TransformStream(),
streamController,
{
redactStreamDiagnosticsForLog: true,
stallTimeoutMs: 40,
}
);
await readStreamText(stream);
} finally {
console.debug = originalDebug;
}
const retainedDiagnostics = debugCalls.flat().map(String).join(" ");
assert.equal(retainedDiagnostics.includes(privateCue), false);
assert.equal(retainedDiagnostics.includes("[omitted: video transcript]"), true);
});

View File

@@ -829,3 +829,38 @@ test("pipeWithDisconnect stall watchdog does not fire after normal stream comple
assert.equal(text, "ok");
assert.equal(onErrorCalled, false, "stall watchdog must be cleared on stream completion");
});
test("createStreamController redacts sensitive diagnostics without changing onError", () => {
const privateCue = "PRIVATE_VIDEO_CUE_controller_7f38";
const originalLog = console.log;
const originalDebug = console.debug;
const calls: unknown[][] = [];
let callbackMessage: string | null = null;
console.log = (...args: unknown[]) => {
calls.push(args);
};
console.debug = (...args: unknown[]) => {
calls.push(args);
};
try {
const controller = createStreamController({
redactStreamDiagnosticsForLog: true,
onError(event) {
callbackMessage = event.message;
throw new Error(`${privateCue}: callback failure`);
},
});
controller.handleError(new Error(privateCue));
} finally {
console.log = originalLog;
console.debug = originalDebug;
}
const retainedDiagnostics = calls.flat().map(String).join(" ");
assert.equal(callbackMessage, privateCue, "fallback classification must receive the real error");
assert.equal(retainedDiagnostics.includes(privateCue), false);
assert.equal(retainedDiagnostics.includes("[omitted: video transcript]"), true);
});

View File

@@ -80,6 +80,11 @@ test("#8142 onFailure throwing does not crash the stream and is logged", async (
loggedOnFailureThrow,
"a console.debug call referencing onFailure must be emitted when the callback throws"
);
assert.match(
debugCalls.flat().map(String).join(" "),
/boom from consumer onFailure handler/,
"ordinary stream diagnostics must retain their useful error message"
);
});
test("#8142 regression: onFailure returning normally logs nothing and behaves identically", async () => {
@@ -117,3 +122,112 @@ test("#8142 regression: onFailure returning normally logs nothing and behaves id
"the happy path (no throw) must not emit the onFailure-throw debug log — behavior-free regression guard"
);
});
test("sensitive onFailure diagnostics omit embedded transcript text", async () => {
const privateCue = "PRIVATE_VIDEO_CUE_onFailure_2e91";
const originalDebug = console.debug;
const debugCalls: unknown[][] = [];
console.debug = (...args: unknown[]) => {
debugCalls.push(args);
};
try {
await assert.rejects(
readTransformed([responseFailedChunk("upstream failed")], {
mode: "passthrough",
sourceFormat: FORMATS.OPENAI,
provider: "openai",
model: "gpt-test",
body: { messages: [{ role: "user", content: "hello" }] },
redactStreamDiagnosticsForLog: true,
onFailure() {
throw new Error(privateCue);
},
}),
/upstream failed/i
);
} finally {
console.debug = originalDebug;
}
const retainedDiagnostics = debugCalls.flat().map(String).join(" ");
assert.equal(retainedDiagnostics.includes(privateCue), false);
assert.equal(retainedDiagnostics.includes("[omitted: video transcript]"), true);
});
test("sensitive onComplete diagnostics omit embedded transcript text", async () => {
const privateCue = "PRIVATE_VIDEO_CUE_onComplete_8d42";
const originalDebug = console.debug;
const debugCalls: unknown[][] = [];
console.debug = (...args: unknown[]) => {
debugCalls.push(args);
};
try {
await readTransformed(
[
`data: ${JSON.stringify({
id: "chatcmpl-sensitive-log",
choices: [{ index: 0, delta: { content: "ok" }, finish_reason: "stop" }],
})}\n\n`,
"data: [DONE]\n\n",
],
{
mode: "passthrough",
sourceFormat: FORMATS.OPENAI,
provider: "openai",
model: "gpt-test",
body: { messages: [{ role: "user", content: "hello" }] },
redactStreamDiagnosticsForLog: true,
onComplete() {
throw new Error(privateCue);
},
}
);
} finally {
console.debug = originalDebug;
}
const retainedDiagnostics = debugCalls.flat().map(String).join(" ");
assert.equal(retainedDiagnostics.includes(privateCue), false);
assert.equal(retainedDiagnostics.includes("[omitted: video transcript]"), true);
});
test("sensitive flush diagnostics omit embedded transcript text", async () => {
const privateCue = "PRIVATE_VIDEO_CUE_flush_f601";
const originalLog = console.log;
const logCalls: unknown[][] = [];
console.log = (...args: unknown[]) => {
logCalls.push(args);
};
try {
await readTransformed(
[
`data: ${JSON.stringify({
id: "chatcmpl-sensitive-flush",
choices: [{ index: 0, delta: { content: "tail" }, finish_reason: "stop" }],
})}`,
],
{
mode: "passthrough",
sourceFormat: FORMATS.OPENAI,
provider: "openai",
model: "gpt-test",
body: { messages: [{ role: "user", content: "hello" }] },
redactStreamDiagnosticsForLog: true,
reqLogger: {
appendConvertedChunk() {
throw new Error(privateCue);
},
},
}
);
} finally {
console.log = originalLog;
}
const retainedDiagnostics = logCalls.flat().map(String).join(" ");
assert.equal(retainedDiagnostics.includes(privateCue), false);
assert.equal(retainedDiagnostics.includes("[omitted: video transcript]"), true);
});

View File

@@ -616,10 +616,7 @@ test("ensureStreamReadiness preserves sanitized error-only diagnostics on early
assert.equal(result.response.status, 502);
assert.equal(result.code, "STREAM_EARLY_EOF");
assert.equal(result.type, "stream_early_eof");
assert.equal(
result.classificationReason,
"Stream ended before producing a non-ping SSE event"
);
assert.equal(result.classificationReason, "Stream ended before producing a non-ping SSE event");
assert.equal(
result.upstreamDiagnostic,
"UPSTREAM_DETAIL quota exhausted; retry after 2s; empty content Bearer [REDACTED] <path>"
@@ -636,19 +633,38 @@ test("ensureStreamReadiness preserves sanitized error-only diagnostics on early
assert.equal(body.upstream_details.error.message, result.upstreamDiagnostic);
assert.equal(warnings.length, 1);
for (const surfaced of [
result.reason,
body.upstream_details.error.message,
warnings[0],
]) {
for (const surfaced of [result.reason, body.upstream_details.error.message, warnings[0]]) {
assert.match(surfaced, /UPSTREAM_DETAIL/);
assert.doesNotMatch(
surfaced,
/SECOND_DETAIL|TOP_SECRET|\/srv\/omniroute\/handler\.ts/
);
assert.doesNotMatch(surfaced, /SECOND_DETAIL|TOP_SECRET|\/srv\/omniroute\/handler\.ts/);
}
});
test("ensureStreamReadiness omits sensitive upstream diagnostics from retained logs", async () => {
const rawCue = "PRIVATE_STREAM_READINESS_VIDEO_TRANSCRIPT_SENTINEL";
const warnings: string[] = [];
const response = new Response(
streamFromChunks([`data: ${JSON.stringify({ error: { message: rawCue } })}\n\n`]),
{ status: 200, headers: { "Content-Type": "text/event-stream" } }
);
const result = await ensureStreamReadiness(response, {
timeoutMs: 100,
provider: "test-provider",
model: "test-model",
redactUpstreamDiagnosticForLog: true,
log: {
warn: (_tag, message) => warnings.push(message),
},
});
assert.equal(result.ok, false);
if (result.ok) assert.fail("error-only SSE payload must remain a readiness failure");
assert.match(result.reason, new RegExp(rawCue));
assert.equal(warnings.length, 1);
assert.doesNotMatch(warnings[0], new RegExp(rawCue));
assert.match(warnings[0], /upstream diagnostic omitted/);
});
test("stream-readiness diagnostics cannot reclassify Antigravity account exhaustion (#8972)", () => {
const classificationError = "Stream ended before producing a non-ping SSE event";
const diagnostic = "UPSTREAM_DETAIL quota exhausted; retry after 2s; empty content";

View File

@@ -8,6 +8,7 @@ import {
DEFAULT_UNIVERSAL_HANDOFF_CONFIG,
type HandoffPayload,
} from "../../open-sse/services/contextHandoff.ts";
import { fingerprintVideoTranscriptDescription } from "../../src/lib/guardrails/videoTranscriptLogRedaction.ts";
// ── resolveUniversalHandoffConfig ────────────────────────────────────────────
@@ -160,6 +161,49 @@ test("providerAllowlist: empty allowlist allows all providers", async () => {
assert.ok(calls.length > 0, "handleSingleModel MUST be called when allowlist is empty");
});
test("transcript-sensitive model switches generate from a safely redacted history", async () => {
const sentinel = "PRIVATE_UNIVERSAL_HANDOFF_TRANSCRIPT_SENTINEL";
const description = `[Video description: stable scene; transcript[source=embedded;confidence=1.00;interval=00:01.000-00:02.000] text="${sentinel}"]`;
const calls: Array<{ body: Record<string, unknown>; modelStr: string }> = [];
maybeGenerateUniversalHandoff({
sessionId: "ses_private_video",
comboName: "private-video-combo",
messages: [
{
role: "user",
content: [
{ type: "input_text", text: "Keep this universal handoff context" },
{ type: "input_text", text: description },
],
},
],
prevModel: "openai/gpt-4o",
currModel: "anthropic/claude-3-5-sonnet",
videoTranscriptSensitive: true,
trustedDescriptionFingerprints: [fingerprintVideoTranscriptDescription(description)],
universalConfig: {
...DEFAULT_UNIVERSAL_HANDOFF_CONFIG,
enabled: true,
providerAllowlist: [],
handoffModel: "anthropic/claude-3-5-sonnet",
},
handleSingleModel: async (body, modelStr) => {
calls.push({ body, modelStr });
return new Response(JSON.stringify({ choices: [{ message: { content: "{}" } }] }), {
status: 200,
headers: { "content-type": "application/json" },
});
},
});
await waitImmediate();
assert.strictEqual(calls.length, 1);
const retainedPrompt = JSON.stringify(calls[0].body);
assert.match(retainedPrompt, /Keep this universal handoff context/);
assert.match(retainedPrompt, /omitted: video transcript/);
assert.doesNotMatch(retainedPrompt, new RegExp(sentinel));
});
test("providerAllowlist: handoffModel takes precedence over currModel for allowlist check", async () => {
const calls: unknown[] = [];
await maybeGenerateUniversalHandoff({