fix: gate transcript sanitization by provider
This commit is contained in:
117
src/agents/transcript-policy.ts
Normal file
117
src/agents/transcript-policy.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { isAntigravityClaude, isGoogleModelApi } from "./pi-embedded-helpers/google.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
import type { ToolCallIdMode } from "./tool-call-id.js";
|
||||
|
||||
export type TranscriptSanitizeMode = "full" | "images-only";
|
||||
|
||||
export type TranscriptPolicy = {
|
||||
sanitizeMode: TranscriptSanitizeMode;
|
||||
sanitizeToolCallIds: boolean;
|
||||
toolCallIdMode?: ToolCallIdMode;
|
||||
repairToolUseResultPairing: boolean;
|
||||
enforceToolCallLast: boolean;
|
||||
preserveSignatures: boolean;
|
||||
sanitizeThoughtSignatures?: {
|
||||
allowBase64Only?: boolean;
|
||||
includeCamelCase?: boolean;
|
||||
};
|
||||
normalizeAntigravityThinkingBlocks: boolean;
|
||||
applyGoogleTurnOrdering: boolean;
|
||||
validateGeminiTurns: boolean;
|
||||
validateAnthropicTurns: boolean;
|
||||
stripFinalTags: boolean;
|
||||
allowSyntheticToolResults: boolean;
|
||||
};
|
||||
|
||||
const MISTRAL_MODEL_HINTS = [
|
||||
"mistral",
|
||||
"mixtral",
|
||||
"codestral",
|
||||
"pixtral",
|
||||
"devstral",
|
||||
"ministral",
|
||||
"mistralai",
|
||||
];
|
||||
const OPENAI_MODEL_APIS = new Set([
|
||||
"openai",
|
||||
"openai-completions",
|
||||
"openai-responses",
|
||||
"openai-codex-responses",
|
||||
]);
|
||||
const OPENAI_PROVIDERS = new Set(["openai", "openai-codex"]);
|
||||
|
||||
function isOpenAiApi(modelApi?: string | null): boolean {
|
||||
if (!modelApi) return false;
|
||||
return OPENAI_MODEL_APIS.has(modelApi);
|
||||
}
|
||||
|
||||
function isOpenAiProvider(provider?: string | null): boolean {
|
||||
if (!provider) return false;
|
||||
return OPENAI_PROVIDERS.has(normalizeProviderId(provider));
|
||||
}
|
||||
|
||||
function isAnthropicApi(modelApi?: string | null, provider?: string | null): boolean {
|
||||
if (modelApi === "anthropic-messages") return true;
|
||||
const normalized = normalizeProviderId(provider ?? "");
|
||||
return normalized === "anthropic" || normalized === "minimax";
|
||||
}
|
||||
|
||||
function isMistralModel(params: { provider?: string | null; modelId?: string | null }): boolean {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
if (provider === "mistral") return true;
|
||||
const modelId = (params.modelId ?? "").toLowerCase();
|
||||
if (!modelId) return false;
|
||||
return MISTRAL_MODEL_HINTS.some((hint) => modelId.includes(hint));
|
||||
}
|
||||
|
||||
export function resolveTranscriptPolicy(params: {
|
||||
modelApi?: string | null;
|
||||
provider?: string | null;
|
||||
modelId?: string | null;
|
||||
}): TranscriptPolicy {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const modelId = params.modelId ?? "";
|
||||
const isGoogle = isGoogleModelApi(params.modelApi);
|
||||
const isAnthropic = isAnthropicApi(params.modelApi, provider);
|
||||
const isOpenAi = isOpenAiProvider(provider) || (!provider && isOpenAiApi(params.modelApi));
|
||||
const isMistral = isMistralModel({ provider, modelId });
|
||||
const isOpenRouterGemini =
|
||||
(provider === "openrouter" || provider === "opencode") &&
|
||||
modelId.toLowerCase().includes("gemini");
|
||||
const isAntigravityClaudeModel = isAntigravityClaude({
|
||||
api: params.modelApi,
|
||||
provider,
|
||||
modelId,
|
||||
});
|
||||
|
||||
const needsNonImageSanitize = isGoogle || isAnthropic || isMistral || isOpenRouterGemini;
|
||||
|
||||
const sanitizeToolCallIds = isGoogle || isMistral;
|
||||
const toolCallIdMode: ToolCallIdMode | undefined = isMistral
|
||||
? "strict9"
|
||||
: sanitizeToolCallIds
|
||||
? "strict"
|
||||
: undefined;
|
||||
const repairToolUseResultPairing = isGoogle || isAnthropic;
|
||||
const enforceToolCallLast = isAnthropic;
|
||||
const sanitizeThoughtSignatures = isOpenRouterGemini
|
||||
? { allowBase64Only: true, includeCamelCase: true }
|
||||
: undefined;
|
||||
const normalizeAntigravityThinkingBlocks = isAntigravityClaudeModel;
|
||||
|
||||
return {
|
||||
sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only",
|
||||
sanitizeToolCallIds: !isOpenAi && sanitizeToolCallIds,
|
||||
toolCallIdMode,
|
||||
repairToolUseResultPairing: !isOpenAi && repairToolUseResultPairing,
|
||||
enforceToolCallLast: !isOpenAi && enforceToolCallLast,
|
||||
preserveSignatures: isAntigravityClaudeModel,
|
||||
sanitizeThoughtSignatures: isOpenAi ? undefined : sanitizeThoughtSignatures,
|
||||
normalizeAntigravityThinkingBlocks,
|
||||
applyGoogleTurnOrdering: !isOpenAi && isGoogle,
|
||||
validateGeminiTurns: !isOpenAi && isGoogle,
|
||||
validateAnthropicTurns: !isOpenAi && isAnthropic,
|
||||
stripFinalTags: !isOpenAi && (isGoogle || isAnthropic),
|
||||
allowSyntheticToolResults: !isOpenAi && (isGoogle || isAnthropic),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user