// 编排与出报告:对应 veridrop core/runner.py + protocols/anthropic/runner.py + web/jobs.py 里拼 DetectionReport 的那段。 // // 按档位与型号挑检测项 → 主动的并发跑(总超时)→ 没有流式请求时补一个最小的流式探针量首字 → 被动的出结果 → // 算总分、判定、摘要(上游余额 / 型号不可用时整轮作废、0 分)。 import { AnthropicClient, ThrottledClient, type FetchLike } from "./client.js"; import { MODE_DETECTORS } from "./config.js"; import { ActiveDetector, PassiveDetector, type BaseDetector } from "./detectors/base.js"; import { buildAll } from "./detectors/index.js"; import { computeTotal, configForMode, effectiveVerdict, fatalRunError, maskApiKey, summaryText, type DetectionReport, type DetectorResult, type ExecutionConfig, type Mode, type PerformanceMetrics, } from "./models.js"; export const TIER_TITLE = "加密级验证"; export const TIER_MESSAGE = "Claude thinking signature 来自 Anthropic 服务端签名。通过该项时,它是当前检测集中最高可信度的真伪信号。"; export interface RunOutcome { results: DetectorResult[]; performance: PerformanceMetrics; } /** core/runner.py Runner.run。 */ export async function runDetectors( base: AnthropicClient, detectors: readonly BaseDetector[], config: ExecutionConfig, model: string, abort?: AbortController, ): Promise { const modeSet = MODE_DETECTORS[config.mode]; const selected = detectors.filter((d) => modeSet.has(d.name) && d.appliesTo(model)); const active = selected.filter((d): d is ActiveDetector => d instanceof ActiveDetector); const passive = selected.filter((d): d is PassiveDetector => d instanceof PassiveDetector); const client = new ThrottledClient(base, passive, config.maxConcurrent); const selectedNames = new Set(selected.map((d) => d.name)); const skipped: DetectorResult[] = []; for (const d of detectors) { if (selectedNames.has(d.name)) continue; skipped.push(d.skip(modeSet.has(d.name) ? "model-excluded" : "mode-excluded")); } for (const d of selected) d.config = config; const started = performance.now(); let timer: ReturnType | undefined; const timeout = new Promise<"timeout">((resolve) => { timer = setTimeout(() => resolve("timeout"), config.overallTimeoutS * 1000); }); const all = Promise.all(active.map((d) => d.timedRun(client, model))); const raced = await Promise.race([all, timeout]); if (timer !== undefined) clearTimeout(timer); let activeResults: DetectorResult[]; if (raced === "timeout") { abort?.abort(); // 在飞的请求一并中止(Python 里 wait_for 会取消这些任务) activeResults = active.map((d) => d.result("error", 0, { timeout_s: config.overallTimeoutS }, null, "overall timeout")); } else { activeResults = raced; } const totalLatencyMs = Math.trunc(performance.now() - started); // 快速档没有流式检测项,首字会是空的:补一个 max_tokens 5 的最小流式请求。完整 / 标准档已经有样本,不多花。 if (client.ttftSamplesMs.length === 0 && raced !== "timeout") { try { for await (const _ of client.messagesStream({ model, max_tokens: 5, messages: [{ role: "user", content: "ok" }] })) { // 只要首字时间 } } catch { // 探针失败不影响整轮 } } const passiveResults = passive.map((d) => d.finalize()); return { results: [...activeResults, ...passiveResults, ...skipped], performance: { total_latency_ms: totalLatencyMs, ttft_ms: client.ttftSamplesMs.length ? Math.min(...client.ttftSamplesMs) : null, tokens_per_second: null, usage: client.totalUsage, request_count: client.requestCount, backoff_events: client.backoffEvents, }, }; } export interface RunOptions { baseUrl: string; apiKey: string; model: string; mode?: Mode; maxConcurrent?: number; timeoutS?: number; fetchImpl?: FetchLike; now?: () => Date; } /** 跑一整轮并拼成报告(web/jobs.py)。API key 只出现在 api_key_masked。 */ export async function runDetection(opts: RunOptions): Promise { const mode = opts.mode ?? "full"; const config = configForMode(mode, { maxConcurrent: opts.maxConcurrent ?? 3, requestTimeoutS: opts.timeoutS ?? 30, }); const abort = new AbortController(); const base = new AnthropicClient(opts.baseUrl, opts.apiKey, { timeoutS: config.requestTimeoutS, fetchImpl: opts.fetchImpl, signal: abort.signal, }); const outcome = await runDetectors(base, buildAll(), config, opts.model, abort); const runError = fatalRunError(outcome.results); const score = runError ? 0 : computeTotal(outcome.results); const verdict = effectiveVerdict(score, outcome.results); const summary = runError ?? summaryText(score, verdict); let selfId: string | null = null; let brands: string[] = []; for (const r of outcome.results) { if (r.name !== "identity") continue; const text = r.details["response_text"]; if (typeof text === "string" && text.trim()) selfId = text.trim(); const b = r.details["detected_non_anthropic_brands"]; if (Array.isArray(b)) brands = b.filter((x): x is string => typeof x === "string"); break; } return { protocol: "anthropic", tier: "cryptographic", tier_title: TIER_TITLE, tier_message: TIER_MESSAGE, base_url: opts.baseUrl, api_key_masked: maskApiKey(opts.apiKey), target_model: opts.model, mode, timestamp: (opts.now ?? (() => new Date()))().toISOString(), total_score: score, verdict, results: outcome.results, performance: outcome.performance, summary, run_error: runError, self_reported_identity: selfId, detected_non_anthropic_brands: brands, }; }