Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
34 KiB
JSON
1 line
34 KiB
JSON
{"repo": "alanho2025/kiwi-ai-interview-agent", "files": {"backend/src/services/agenticSafeguards/safeguardShared.js": {"d7d497976be123e6652b2ff2a3efe17d833b2bac": {"t": 1785745552.3772738, "n": 3052, "h": "cbaf1878ecc72023", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so mock tests can opt into review while real flows always run safeguards.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nconst isExplicitFalse = (value) => ['false', '0', 'no', 'off'].includes(String(value || '').toLowerCase());\n\nexport const shouldRunAgenticSafeguard = () => {\n if (isExplicitFalse(process.env.AGENTIC_SAFEGUARDS_ENABLED)) return false;\n if (isExplicitFalse(process.env.ENABLE_AGENTIC_SAFEGUARDS)) return false;\n return true;\n};\n\nexport const assertSafeguardProviderConfigured = () => {\n if (isMockAiMode()) return;\n if (!process.env.DEEPSEEK_API_KEY) {\n throw new Error('DEEPSEEK_API_KEY is required because agentic safeguards are mandatory outside mock mode.');\n }\n};\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}, "9df6b75a68c49d7dcca673d2335216229c5fe547": {"t": 1785754154.7211595, "n": 3052, "h": "cbaf1878ecc72023", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so mock tests can opt into review while real flows always run safeguards.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nconst isExplicitFalse = (value) => ['false', '0', 'no', 'off'].includes(String(value || '').toLowerCase());\n\nexport const shouldRunAgenticSafeguard = () => {\n if (isExplicitFalse(process.env.AGENTIC_SAFEGUARDS_ENABLED)) return false;\n if (isExplicitFalse(process.env.ENABLE_AGENTIC_SAFEGUARDS)) return false;\n return true;\n};\n\nexport const assertSafeguardProviderConfigured = () => {\n if (isMockAiMode()) return;\n if (!process.env.DEEPSEEK_API_KEY) {\n throw new Error('DEEPSEEK_API_KEY is required because agentic safeguards are mandatory outside mock mode.');\n }\n};\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}, "ea669e14f3e75ddca2d937710be535a2257a1cc3": {"t": 1785754156.010973, "n": 2776, "h": "bb96e7873f14a5da", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so mock tests can opt into review while real flows always run safeguards.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nexport const shouldRunAgenticSafeguard = () => true;\n\nexport const assertSafeguardProviderConfigured = () => {\n if (isMockAiMode()) return;\n if (!process.env.DEEPSEEK_API_KEY) {\n throw new Error('DEEPSEEK_API_KEY is required because agentic safeguards are mandatory outside mock mode.');\n }\n};\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}, "18bed71a5847192b5ecafa4a75948f8e89228959": {"t": 1785754158.5541346, "n": 2869, "h": "c3a35adcc4080ba8", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so mock tests can opt into review while real flows always run safeguards.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nexport const shouldRunAgenticSafeguard = () => {\n if (isMockAiMode()) return process.env.ENABLE_AGENTIC_SAFEGUARDS === 'true';\n return true;\n};\n\nexport const assertSafeguardProviderConfigured = () => {\n if (isMockAiMode()) return;\n if (!process.env.DEEPSEEK_API_KEY) {\n throw new Error('DEEPSEEK_API_KEY is required because agentic safeguards are mandatory outside mock mode.');\n }\n};\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}, "531672aa130aac96f7e02e3b993dca4765c77ce6": {"t": 1785754159.8292193, "n": 2572, "h": "6fea71f2f6dfab6a", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so normal dev/test flows can enable or skip DeepSeek review predictably.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nexport const shouldRunAgenticSafeguard = () => process.env.ENABLE_AGENTIC_SAFEGUARDS === 'true';\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}, "b1b18125f406f9a4c067befa3d6f2b0d1d6b4157": {"t": 1786067404.3869553, "n": 3052, "h": "cbaf1878ecc72023", "c": "/**\n * File responsibility: Shared helpers for controlled agentic safeguards.\n * Main responsibilities:\n * - Keep safeguard verdict names stable across JD parsing and CV-JD matching.\n * - Centralise feature flags so mock tests can opt into review while real flows always run safeguards.\n */\n\nexport const SAFEGUARD_VERDICTS = {\n PASS: 'pass',\n REVISE: 'revise',\n REJECT: 'reject',\n};\n\nexport const isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nexport const isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\n\nconst isExplicitFalse = (value) => ['false', '0', 'no', 'off'].includes(String(value || '').toLowerCase());\n\nexport const shouldRunAgenticSafeguard = () => {\n if (isExplicitFalse(process.env.AGENTIC_SAFEGUARDS_ENABLED)) return false;\n if (isExplicitFalse(process.env.ENABLE_AGENTIC_SAFEGUARDS)) return false;\n return true;\n};\n\nexport const assertSafeguardProviderConfigured = () => {\n if (isMockAiMode()) return;\n if (!process.env.DEEPSEEK_API_KEY) {\n throw new Error('DEEPSEEK_API_KEY is required because agentic safeguards are mandatory outside mock mode.');\n }\n};\n\nexport const getMaxSafeguardReparseAttempts = () => {\n const configured = Number(process.env.AGENTIC_SAFEGUARD_MAX_REPARSE_ATTEMPTS || 1);\n return Number.isFinite(configured) && configured >= 0 ? Math.min(configured, 2) : 1;\n};\n\nexport const buildSkippedSafeguardResult = (reason) => ({\n verdict: SAFEGUARD_VERDICTS.PASS,\n confidence: 1,\n blockOutput: false,\n blockMatch: false,\n skipped: true,\n repairApplied: false,\n parseAttempts: 1,\n finalStatus: 'safeguard_skipped',\n issues: [],\n reparseInstructions: [],\n reason,\n});\n\nexport const normalizeVerdict = (value = '') => {\n const normalized = String(value || '').toLowerCase().trim();\n if (Object.values(SAFEGUARD_VERDICTS).includes(normalized)) return normalized;\n return SAFEGUARD_VERDICTS.REVISE;\n};\n\nexport const ensureArray = (value) => (Array.isArray(value) ? value.filter(Boolean) : []);\n\nexport const normalizeIssue = (issue = {}) => ({\n field: String(issue.field || 'unknown'),\n severity: ['low', 'medium', 'high'].includes(issue.severity) ? issue.severity : 'medium',\n problem: String(issue.problem || issue.reason || 'Unspecified safeguard issue.'),\n action: String(issue.action || issue.suggestedAction || ''),\n});\n\nexport const normalizeSafeguardReview = (review = {}, fallback = {}) => {\n const verdict = normalizeVerdict(review.verdict || fallback.verdict);\n return {\n verdict,\n confidence: Number.isFinite(Number(review.confidence)) ? Number(review.confidence) : (fallback.confidence ?? 0.5),\n blockOutput: Boolean(review.blockOutput ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n blockMatch: Boolean(review.blockMatch ?? (verdict !== SAFEGUARD_VERDICTS.PASS)),\n issues: ensureArray(review.issues || fallback.issues).map(normalizeIssue),\n reparseInstructions: ensureArray(review.reparseInstructions || fallback.reparseInstructions).map(String),\n reasoning: String(review.reasoning || review.reason || fallback.reasoning || ''),\n };\n};\n"}}, "backend/src/services/deepseekService.js": {"d7d497976be123e6652b2ff2a3efe17d833b2bac": {"t": 1786068264.5854049, "n": 11635, "h": "f349b3ba02c59778", "c": "/**\n * File responsibility: Service module.\n * Main responsibilities:\n * - Keep HTTP, business logic, persistence, and formatting concerns separated to reduce change impact.\n * - Main file role: deepseekService should encapsulate domain behaviour behind small callable functions with predictable inputs and outputs.\n * - Prefer extending behaviour by adding small helpers or sibling modules instead of growing one large file.\n * Maintenance notes:\n * - Keep this file focused on one layer of responsibility.\n * - Prefer composition and small helpers over repeated inline logic.\n */\n\nimport { AsyncLocalStorage } from 'async_hooks';\n\n/**\n * Per-request storage for usage tracking context (userId, sessionId).\n * Set by API middleware; consumed by autoRecordUsage.\n */\nconst usageContextStorage = new AsyncLocalStorage();\n\nexport const LLM_CONFIGS = Object.freeze({\n JSON_STRICT: Object.freeze({ temperature: 0, top_p: 1 }),\n EVALUATOR: Object.freeze({ temperature: 0, top_p: 1 }),\n GROUNDED_WRITING: Object.freeze({ temperature: 0.2, top_p: 0.9 }),\n INTERVIEWER_NATURAL: Object.freeze({ temperature: 0.4, top_p: 0.9 }),\n});\n\nconst isMockAiMode = () => process.env.AI_TEST_MODE === 'mock';\nconst isRealAiMode = () => process.env.AI_TEST_MODE === 'real';\nconst buildMockDeepSeekResponse = () => 'This is a mock response from DeepSeek. Please set DEEPSEEK_API_KEY to run real AI eval.';\n\nconst getDeepSeekTimeoutMs = () => {\n const configured = Number(process.env.DEEPSEEK_TIMEOUT_MS || 30000);\n return Number.isFinite(configured) && configured > 0 ? configured : 30000;\n};\n\nconst buildTimeoutSignal = (overrideTimeoutMs) => (\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(overrideTimeoutMs || getDeepSeekTimeoutMs())\n : undefined\n);\n\nconst resolveDeepSeekApiKey = () => {\n const apiKey = process.env.DEEPSEEK_API_KEY;\n if (apiKey) return apiKey;\n if (isMockAiMode()) return null;\n if (isRealAiMode()) {\n throw new Error('DEEPSEEK_API_KEY is required when AI_TEST_MODE=real. Real eval must not silently use mock output.');\n }\n console.warn('DEEPSEEK_API_KEY is missing. Using mock response.');\n return null;\n};\n\nconst normalizeGenerationConfig = (config = {}) => {\n const normalized = {};\n if (Number.isFinite(Number(config.temperature))) normalized.temperature = Number(config.temperature);\n if (Number.isFinite(Number(config.top_p))) normalized.top_p = Number(config.top_p);\n return normalized;\n};\n\nconst chooseDefaultGenerationConfig = ({ usageMetadata = {}, streaming = false } = {}) => {\n const stage = String(usageMetadata.stage || '').toLowerCase();\n const operation = String(usageMetadata.operation || '').toLowerCase();\n const feature = String(usageMetadata.feature || '').toLowerCase();\n\n if (operation === 'llm_json') return LLM_CONFIGS.JSON_STRICT;\n\n if (\n stage === 'cv_jd_match'\n || stage === 'jd_parse'\n || feature.includes('extract')\n || feature.includes('parse')\n || feature.includes('judge')\n || feature.includes('critic')\n || feature.includes('selector')\n || feature.includes('understanding')\n || feature.includes('interview_plan')\n || feature.includes('values_enrichment')\n || feature.includes('motivation_fit')\n || feature.includes('baseline')\n || feature.includes('evaluation')\n ) {\n return LLM_CONFIGS.EVALUATOR;\n }\n\n if (\n stage === 'report_generated'\n || stage === 'report_qa'\n || feature.includes('candidate_feedback')\n || feature.includes('report_rewrite')\n || feature.includes('coaching')\n ) {\n return LLM_CONFIGS.GROUNDED_WRITING;\n }\n\n if (streaming || stage === 'interview') return LLM_CONFIGS.INTERVIEWER_NATURAL;\n\n return LLM_CONFIGS.GROUNDED_WRITING;\n};\n\nconst resolveGenerationConfig = ({ usageMetadata = {}, temperature, top_p, generationConfig = {}, streaming = false } = {}) => ({\n ...chooseDefaultGenerationConfig({ usageMetadata, streaming }),\n ...normalizeGenerationConfig(generationConfig),\n ...normalizeGenerationConfig({ temperature, top_p }),\n});\n\nconst buildChatPayload = ({ prompt, systemInstruction, usageMetadata = {}, temperature, top_p, generationConfig, stream = false, streamOptions = null } = {}) => {\n const payload = {\n model: 'deepseek-chat',\n messages: [\n { role: 'system', content: systemInstruction || 'You are a helpful assistant.' },\n { role: 'user', content: prompt },\n ],\n ...resolveGenerationConfig({ usageMetadata, temperature, top_p, generationConfig, streaming: stream }),\n };\n\n if (stream) payload.stream = true;\n if (streamOptions) payload.stream_options = streamOptions;\n return payload;\n};\n\n/**\n * Extract token usage from a DeepSeek API response.\n * Returns { promptTokens, completionTokens } or null if unavailable.\n */\nexport const extractUsage = (data) => {\n if (data?.usage?.prompt_tokens != null && data?.usage?.completion_tokens != null) {\n return {\n promptTokens: data.usage.prompt_tokens,\n completionTokens: data.usage.completion_tokens,\n promptCacheHitTokens: data.usage.prompt_cache_hit_tokens || data.usage.prompt_tokens_details?.cached_tokens || 0,\n promptCacheMissTokens: data.usage.prompt_cache_miss_tokens ?? null,\n };\n }\n return null;\n};\n\n/** Return the current request's usage context store. */\nexport const getUsageContextStore = () => usageContextStorage.getStore();\n\nexport const runWithUsageContextPatch = (context = {}, execute) => {\n const currentContext = getUsageContextStore() || {};\n return usageContextStorage.run({ ...currentContext, ...context }, execute);\n};\n\n/**\n * Express middleware that wraps downstream handlers in an AsyncLocalStorage run context.\n * Usage: api.use(usageContextMiddleware);\n */\nexport const usageContextMiddleware = (req, _res, next) => {\n const ctx = req.user?.id\n ? { userId: req.user.id, sessionId: req.params?.sessionId || req.body?.sessionId || null }\n : null;\n usageContextStorage.run(ctx, () => next());\n};\n\n\nexport const autoRecordUsage = async (usage, action = 'callDeepSeek', metadata = {}) => {\n if (!usage) return;\n const ctx = getUsageContextStore();\n if (!ctx?.userId) return;\n const stage = metadata.stage || ctx.stage || 'interview';\n const operation = metadata.operation || (action === 'callDeepSeekJson' ? 'llm_json' : 'llm_chat');\n\n try {\n const [{ recordTokenUsage }, { recordLlmUsage }] = await Promise.all([\n import('./usageTrackingService.js'),\n import('./aiUsageTrackingService.js'),\n ]);\n\n const [, llmUsageEvent] = await Promise.all([\n recordTokenUsage({\n userId: ctx.userId,\n sessionId: ctx.sessionId || null,\n action,\n promptTokens: usage.promptTokens,\n completionTokens: usage.completionTokens,\n }),\n recordLlmUsage({\n userId: ctx.userId,\n sessionId: ctx.sessionId || null,\n stage,\n operation,\n action,\n usage,\n metadata,\n }),\n ]);\n if (typeof ctx.harnessUsageCollector === 'function') {\n ctx.harnessUsageCollector({\n provider: 'deepseek',\n model: 'deepseek-chat',\n capabilityId: ctx.harnessCapabilityId || null,\n promptTokens: usage.promptTokens,\n completionTokens: usage.completionTokens,\n estimatedCost: llmUsageEvent?.estimatedCost ?? null,\n });\n }\n } catch (err) {\n console.warn('Failed to record AI usage:', err?.message);\n }\n};\n\nexport const callDeepSeek = async (\n prompt,\n systemInstruction = '',\n {\n skipAutoRecord = false,\n usageMetadata = {},\n temperature,\n top_p,\n generationConfig,\n timeoutMs,\n maxRetries = 2,\n backoffMs = 100,\n } = {},\n) => {\n const apiKey = resolveDeepSeekApiKey();\n if (!apiKey) {\n return { content: buildMockDeepSeekResponse(), usage: null };\n }\n\n let lastError = null;\n for (let attempt = 0; attempt <= maxRetries; attempt += 1) {\n try {\n const response = await fetch('https://api.deepseek.com/chat/completions', {\n method: 'POST',\n signal: buildTimeoutSignal(timeoutMs),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify(buildChatPayload({\n prompt,\n systemInstruction,\n usageMetadata,\n temperature,\n top_p,\n generationConfig,\n })),\n });\n\n if (response.status === 429 && attempt < maxRetries) {\n const retryAfterSec = Number(response.headers.get('retry-after') || 0);\n const delay = retryAfterSec > 0 ? retryAfterSec * 1000 : backoffMs * (2 ** attempt);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n if (!response.ok) {\n throw new Error(`DeepSeek API error: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n const usage = extractUsage(data);\n if (!skipAutoRecord) await autoRecordUsage(usage, 'callDeepSeek', usageMetadata);\n return {\n content: data.choices[0]?.message?.content || '',\n usage,\n };\n } catch (error) {\n lastError = error;\n if (attempt < maxRetries) {\n await new Promise((resolve) => setTimeout(resolve, backoffMs * (2 ** attempt)));\n }\n }\n }\n\n console.error('DeepSeek API Error after retries:', lastError);\n throw lastError;\n};\n\n/**\n * Purpose: Execute the main responsibility for callDeepSeekStream.\n * Inputs: Uses the function parameters defined below and expects callers to pass validated data for this layer.\n * Returns: Returns an async generator yielding text chunks as they arrive from the DeepSeek stream.\n */\nexport const callDeepSeekStream = async function* (\n prompt,\n systemInstruction = '',\n {\n usageMetadata = {},\n temperature,\n top_p,\n generationConfig,\n timeoutMs,\n } = {},\n) {\n const apiKey = resolveDeepSeekApiKey();\n if (!apiKey) {\n yield buildMockDeepSeekResponse();\n return;\n }\n\n const response = await fetch('https://api.deepseek.com/chat/completions', {\n method: 'POST',\n signal: buildTimeoutSignal(timeoutMs),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`\n },\n body: JSON.stringify(buildChatPayload({\n prompt,\n systemInstruction,\n usageMetadata,\n temperature,\n top_p,\n generationConfig,\n stream: true,\n streamOptions: { include_usage: true },\n }))\n });\n\n if (!response.ok) {\n throw new Error(`DeepSeek API error: ${response.statusText}`);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n let streamUsage = null;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n \n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n \n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith('data: ') && trimmed !== 'data: [DONE]') {\n try {\n const data = JSON.parse(trimmed.slice(6));\n const chunk = data.choices?.[0]?.delta?.content || '';\n if (chunk) yield chunk;\n // Capture usage from the final chunk (DeepSeek sends it when stream_options.include_usage is true)\n if (data.usage) {\n streamUsage = extractUsage(data);\n }\n } catch {\n // ignore incomplete JSON chunks\n }\n }\n }\n }\n\n // Record streaming usage after the generator completes\n await autoRecordUsage(streamUsage, 'callDeepSeek', usageMetadata);\n};\n"}}, "backend/tests/robustness/llm/deepseekOrchestratorRobustness.test.js": {"d7d497976be123e6652b2ff2a3efe17d833b2bac": {"t": 1786068269.2657006, "n": 3314, "h": "3a51b65b333dfca3", "c": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\nimport { callDeepSeekJson, parseJsonSafely, repairMalformedJson } from '../../../src/services/agenticSafeguards/deepseekJsonClient.js';\nimport { callDeepSeek, extractUsage, LLM_CONFIGS } from '../../../src/services/deepseekService.js';\n\ndescribe('Phase 1 - F-62 & F-64: DeepSeek LLM Orchestrator Production Robustness', () => {\n beforeEach(() => {\n vi.clearAllMocks();\n delete process.env.AI_TEST_MODE;\n delete process.env.DEEPSEEK_API_KEY;\n });\n\n afterEach(() => {\n vi.restoreAllMocks();\n });\n\n it('strips markdown fencing and repairs malformed JSON with trailing commas or unclosed braces', () => {\n const rawFenced = '```json\\n{\\n \"selectedAction\": \"ask_follow_up\",\\n \"confidence\": 0.95,\\n}\\n```';\n const parsed = parseJsonSafely(rawFenced);\n\n expect(parsed).toEqual({\n selectedAction: 'ask_follow_up',\n confidence: 0.95,\n });\n\n const unclosedJson = '{\"selectedAction\": \"topic_switch\", \"confidence\": 0.8';\n expect(repairMalformedJson(unclosedJson)).toBe('{\"selectedAction\": \"topic_switch\", \"confidence\": 0.8}');\n expect(parseJsonSafely(unclosedJson)).toEqual({ selectedAction: 'topic_switch', confidence: 0.8 });\n });\n\n it('retries on HTTP 429 rate limits with exponential backoff before succeeding', async () => {\n process.env.DEEPSEEK_API_KEY = 'mock-test-key';\n\n const mockFetch = vi.spyOn(global, 'fetch')\n .mockResolvedValueOnce({\n ok: false,\n status: 429,\n statusText: 'Too Many Requests',\n headers: new Headers({ 'retry-after': '0' }),\n })\n .mockResolvedValueOnce({\n ok: true,\n status: 200,\n json: async () => ({\n choices: [{ message: { content: '{\"status\":\"success\"}' } }],\n usage: { prompt_tokens: 10, completion_tokens: 5 },\n }),\n });\n\n const response = await callDeepSeek('Test prompt', 'System instruction', { backoffMs: 1, maxRetries: 2 });\n expect(response.content).toBe('{\"status\":\"success\"}');\n expect(mockFetch).toHaveBeenCalledTimes(2);\n });\n\n it('falls back to default fallback payload when JSON parsing repeatedly fails', async () => {\n process.env.AI_TEST_MODE = 'mock';\n\n const fallback = { selectedAction: 'ask_fallback_question', confidence: 0.5 };\n const result = await callDeepSeekJson({\n prompt: 'Invalid JSON test',\n fallback,\n maxRetries: 1,\n });\n\n expect(result).toEqual(expect.objectContaining({\n selectedAction: 'ask_fallback_question',\n confidence: 0.5,\n }));\n expect(result.error).toBeDefined();\n });\n\n it('extracts token usage metrics including prompt cache hit/miss tokens correctly', () => {\n const apiResponse = {\n usage: {\n prompt_tokens: 150,\n completion_tokens: 45,\n prompt_cache_hit_tokens: 100,\n prompt_cache_miss_tokens: 50,\n },\n };\n\n const usage = extractUsage(apiResponse);\n expect(usage).toEqual({\n promptTokens: 150,\n completionTokens: 45,\n promptCacheHitTokens: 100,\n promptCacheMissTokens: 50,\n });\n });\n\n it('enforces JSON_STRICT configuration generation defaults for llm_json operations', () => {\n expect(LLM_CONFIGS.JSON_STRICT).toEqual({\n temperature: 0,\n top_p: 1,\n });\n });\n});\n"}}}} |