{"repo": "brsamayu/inner-cosmos", "files": {"scripts/demo/sync-kind-provider-secrets.ps1": {"add4b241338c47dd1ccbafa2880f7b484adb07b0": {"t": 1785595205.9648008, "n": 2880, "h": "19e6363923ba812a", "c": "[CmdletBinding()]\nparam(\n [string]$ExpectedContext = \"kind-kubedeploy\",\n [string]$Namespace = \"inner-cosmos-w3\"\n)\n\n$ErrorActionPreference = \"Stop\"\n$root = (Resolve-Path (Join-Path $PSScriptRoot \"..\\..\")).Path\n$context = (& kubectl config current-context).Trim()\nif ($LASTEXITCODE -ne 0 -or $context -ne $ExpectedContext) {\n throw \"Current context '$context' is not the expected disposable showcase '$ExpectedContext'.\"\n}\n\n$keyFiles = @(Get-ChildItem -LiteralPath $root -File |\n Where-Object { $_.Name.StartsWith(\"API\", [StringComparison]::OrdinalIgnoreCase) -and $_.Extension -eq \".txt\" } |\n Sort-Object Name |\n Select-Object -ExpandProperty FullName)\nif ($keyFiles.Count -eq 0) { throw \"No gitignored API*.txt operator file was found.\" }\n$lines = @($keyFiles | ForEach-Object { Get-Content -LiteralPath $_ -Encoding utf8 })\n\nfunction Match-Key([string]$linePattern, [string]$valuePattern, [string]$label) {\n $line = $lines | Where-Object { $_ -match $linePattern } | Select-Object -First 1\n if (-not $line -or $line -notmatch $valuePattern) { throw \"No valid $label key was found.\" }\n return $Matches[1].Trim().TrimEnd([char]0x3001)\n}\n\n$gemini = Match-Key \"^\\s*gemini\\s*:\" \"^\\s*gemini\\s*:\\s*(\\S+)\" \"Gemini\"\n$deepseek = Match-Key \"(?i)deepseek.*apikey\\s*:\" \"(?i)^.*apikey\\s*:\\s*(\\S+)\" \"DeepSeek\"\n$minimax = Match-Key \"(?i)^\\s*minimax\" \"(sk-[A-Za-z0-9._-]+)\" \"MiniMax\"\n$glm = Match-Key \"(?i)^\\s*glm\" \"([0-9a-fA-F]{32}\\.[A-Za-z0-9_-]+)\" \"GLM\"\n$mimo = Match-Key \"(?i)^\\s*mimo\" \"(tp-[A-Za-z0-9._-]+)\" \"MiMo\"\n$dashscope = Match-Key \"(?i)^\\s*(dashscope|qwen)\\s*:\" \"(?i)^\\s*(?:dashscope|qwen)\\s*:\\s*(\\S+)\" \"DashScope\"\n\n$patchFile = Join-Path ([IO.Path]::GetTempPath()) (\"inner-cosmos-provider-secret-\" + [Guid]::NewGuid() + \".json\")\ntry {\n $patchJson = @{\n stringData = @{\n LLM_API_KEY = $gemini\n GEMINI_API_KEY = $gemini\n DEEPSEEK_API_KEY = $deepseek\n MINIMAX_API_KEY = $minimax\n GLM_API_KEY = $glm\n MIMO_API_KEY = $mimo\n TTS_API_KEY = $dashscope\n }\n } | ConvertTo-Json -Depth 4 -Compress\n [IO.File]::WriteAllText($patchFile, $patchJson, [Text.UTF8Encoding]::new($false))\n & kubectl -n $Namespace patch secret inner-cosmos-runtime --type merge --patch-file $patchFile | Out-Null\n if ($LASTEXITCODE -ne 0) { throw \"Kubernetes provider secret patch failed.\" }\n} finally {\n Remove-Item -LiteralPath $patchFile -Force -ErrorAction SilentlyContinue\n $patchJson = $null\n $gemini = $deepseek = $minimax = $glm = $mimo = $dashscope = $null\n}\n\n& kubectl apply -f (Join-Path $root \"deploy\\k8s\\overlays\\kind-full\\app-config.yaml\") | Out-Null\nif ($LASTEXITCODE -ne 0) { throw \"kind provider ConfigMap apply failed.\" }\nWrite-Output \"KIND_PROVIDER_CONFIGURATION_SYNCED context=$context namespace=$Namespace provider=gemini embedding=false asr=mimo tts=true\"\n"}}, "scripts/demo/run-demo-server.sh": {"add4b241338c47dd1ccbafa2880f7b484adb07b0": {"t": 1785745970.478696, "n": 5010, "h": "893b6cf1bb05dfea", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with a REAL chat provider, reading keys at\n# runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep -Ei '^(dashscope|qwen):' \"$KEYS\" | head -1 | cut -d: -f2- | sed 's/、$//')\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nDS_KEY=\"$(grep -i 'deepseek' \"$KEYS\" | head -1 | sed -E 's/.*apikey[::] *//')\"\nif [ -z \"$QWEN_KEY\" ]; then\n echo \"ERROR: could not find the dashscope/qwen line in $KEYS (needed for TTS).\" >&2\n exit 1\nfi\n# Chat provider: DeepSeek by default (proven 0 schema-drift on this HEAD, see\n# evidence/innovation/INNO-EVAL-005). GLM (glm-4-flash) is available but exhibits ~3 first-attempt\n# JSON-parse failures per dual-kernel run, which can surface deterministic fallback text instead of\n# the model's real words -- so it is NOT the default for a demo. Override with DEMO_PROVIDER=glm.\nDEMO_PROVIDER=\"${DEMO_PROVIDER:-deepseek}\"\ncase \"$DEMO_PROVIDER\" in\n deepseek)\n if [ -z \"$DS_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=deepseek but no deepseek key in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"deepseek\"; CHAT_KEY=\"$DS_KEY\" ;;\n glm)\n if [ -z \"$GLM_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=glm but no glm: line in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"glm\"; CHAT_KEY=\"$GLM_KEY\"\n echo \"WARNING: GLM has known schema-drift (~3 fallbacks/run); DeepSeek is the recommended demo provider.\" >&2 ;;\n *) echo \"ERROR: DEMO_PROVIDER must be deepseek or glm (got: $DEMO_PROVIDER).\" >&2; exit 1 ;;\nesac\necho \"Loaded key: ${DEMO_PROVIDER}(len=${#CHAT_KEY}) — value not printed.\"\n\n# Keep provider embeddings off; TTS remains enabled.\nexport MEMORY_EMBEDDING_ENABLED=false\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real chat provider (DeepSeek by default; GLM optional via DEMO_PROVIDER=glm), dev profile (H2).\nexport LLM_MODE=dev\nexport LLM_PROVIDER=\"$CHAT_PROVIDER\"\nexport GLM_API_KEY=\"$GLM_KEY\" # present in env regardless; only used if LLM_PROVIDER=glm\nexport DEEPSEEK_API_KEY=\"$DS_KEY\" # likewise; only used if LLM_PROVIDER=deepseek\nexport LLM_ALLOW_FALLBACK=false # a demo must show the REAL provider, never silently fall back to Mock\n\n# --- Public-tunnel hardening (2026-07-24 8-agent delivery-readiness audit, P1-4/P1-5/P1-7) ---\n# The whole point of this script is to be reachable over a public Cloudflare Tunnel (see\n# docs/demo/DEMO-RUNBOOK.md), so it must not run with plain-localhost-only defaults.\nexport COOKIE_SECURE=true # tunnel is HTTPS end-to-end; never ship an insecure cookie publicly\nexport COOKIE_SAME_SITE=none # the demo APK's WebView origin (https://localhost) is cross-site from\n # the tunnel origin; SameSite=Lax silently drops the session cookie there\nexport INNER_COSMOS_SECURITY_TRUSTED_PROXY_ENABLED=true # honor X-Forwarded-For from cloudflared so rate limits are per-visitor,\n # not one shared bucket for every judge hitting the tunnel from localhost\nexport MANAGEMENT_HEALTH_REDIS_ENABLED=false # this demo path never starts Redis; without this the health\n # endpoint falsely reports DOWN even though the app works fine\n# Classroom experience data is explicit and public-demo-only. It creates three curated,\n# passwordless-entry showcase personas, while the well-known admin seed is disabled. The\n# controller also refuses to operate under the prod profile.\nexport SEED_ENABLED=true\nexport DEMO_PUBLIC_ENTRY_ENABLED=true\nexport DEMO_SEED_ADMIN_ENABLED=false\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=${DEMO_PROVIDER} + embedding disabled + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}, "6305b7ffe384b6039d071fcc05a9b6c36dafd814": {"t": 1785758374.4396384, "n": 5010, "h": "893b6cf1bb05dfea", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with a REAL chat provider, reading keys at\n# runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep -Ei '^(dashscope|qwen):' \"$KEYS\" | head -1 | cut -d: -f2- | sed 's/、$//')\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nDS_KEY=\"$(grep -i 'deepseek' \"$KEYS\" | head -1 | sed -E 's/.*apikey[::] *//')\"\nif [ -z \"$QWEN_KEY\" ]; then\n echo \"ERROR: could not find the dashscope/qwen line in $KEYS (needed for TTS).\" >&2\n exit 1\nfi\n# Chat provider: DeepSeek by default (proven 0 schema-drift on this HEAD, see\n# evidence/innovation/INNO-EVAL-005). GLM (glm-4-flash) is available but exhibits ~3 first-attempt\n# JSON-parse failures per dual-kernel run, which can surface deterministic fallback text instead of\n# the model's real words -- so it is NOT the default for a demo. Override with DEMO_PROVIDER=glm.\nDEMO_PROVIDER=\"${DEMO_PROVIDER:-deepseek}\"\ncase \"$DEMO_PROVIDER\" in\n deepseek)\n if [ -z \"$DS_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=deepseek but no deepseek key in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"deepseek\"; CHAT_KEY=\"$DS_KEY\" ;;\n glm)\n if [ -z \"$GLM_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=glm but no glm: line in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"glm\"; CHAT_KEY=\"$GLM_KEY\"\n echo \"WARNING: GLM has known schema-drift (~3 fallbacks/run); DeepSeek is the recommended demo provider.\" >&2 ;;\n *) echo \"ERROR: DEMO_PROVIDER must be deepseek or glm (got: $DEMO_PROVIDER).\" >&2; exit 1 ;;\nesac\necho \"Loaded key: ${DEMO_PROVIDER}(len=${#CHAT_KEY}) — value not printed.\"\n\n# Keep provider embeddings off; TTS remains enabled.\nexport MEMORY_EMBEDDING_ENABLED=false\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real chat provider (DeepSeek by default; GLM optional via DEMO_PROVIDER=glm), dev profile (H2).\nexport LLM_MODE=dev\nexport LLM_PROVIDER=\"$CHAT_PROVIDER\"\nexport GLM_API_KEY=\"$GLM_KEY\" # present in env regardless; only used if LLM_PROVIDER=glm\nexport DEEPSEEK_API_KEY=\"$DS_KEY\" # likewise; only used if LLM_PROVIDER=deepseek\nexport LLM_ALLOW_FALLBACK=false # a demo must show the REAL provider, never silently fall back to Mock\n\n# --- Public-tunnel hardening (2026-07-24 8-agent delivery-readiness audit, P1-4/P1-5/P1-7) ---\n# The whole point of this script is to be reachable over a public Cloudflare Tunnel (see\n# docs/demo/DEMO-RUNBOOK.md), so it must not run with plain-localhost-only defaults.\nexport COOKIE_SECURE=true # tunnel is HTTPS end-to-end; never ship an insecure cookie publicly\nexport COOKIE_SAME_SITE=none # the demo APK's WebView origin (https://localhost) is cross-site from\n # the tunnel origin; SameSite=Lax silently drops the session cookie there\nexport INNER_COSMOS_SECURITY_TRUSTED_PROXY_ENABLED=true # honor X-Forwarded-For from cloudflared so rate limits are per-visitor,\n # not one shared bucket for every judge hitting the tunnel from localhost\nexport MANAGEMENT_HEALTH_REDIS_ENABLED=false # this demo path never starts Redis; without this the health\n # endpoint falsely reports DOWN even though the app works fine\n# Classroom experience data is explicit and public-demo-only. It creates three curated,\n# passwordless-entry showcase personas, while the well-known admin seed is disabled. The\n# controller also refuses to operate under the prod profile.\nexport SEED_ENABLED=true\nexport DEMO_PUBLIC_ENTRY_ENABLED=true\nexport DEMO_SEED_ADMIN_ENABLED=false\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=${DEMO_PROVIDER} + embedding disabled + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}, "946cc55d547dc78388a6eb7f4fa8e73966b4d09e": {"t": 1785758377.2729313, "n": 5282, "h": "b705a00cb3597d45", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with REAL providers (GLM chat + Qwen embedding\n# + Qwen TTS), reading keys at runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep '^qwen:' \"$KEYS\" | head -1 | cut -d: -f2-)\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nDS_KEY=\"$(grep -i 'deepseek' \"$KEYS\" | head -1 | sed -E 's/.*apikey[::] *//')\"\nif [ -z \"$QWEN_KEY\" ]; then\n echo \"ERROR: could not find the qwen: line in $KEYS (needed for embedding + TTS).\" >&2\n exit 1\nfi\n# Chat provider: DeepSeek by default (proven 0 schema-drift on this HEAD, see\n# evidence/innovation/INNO-EVAL-005). GLM (glm-4-flash) is available but exhibits ~3 first-attempt\n# JSON-parse failures per dual-kernel run, which can surface deterministic fallback text instead of\n# the model's real words -- so it is NOT the default for a demo. Override with DEMO_PROVIDER=glm.\nDEMO_PROVIDER=\"${DEMO_PROVIDER:-deepseek}\"\ncase \"$DEMO_PROVIDER\" in\n deepseek)\n if [ -z \"$DS_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=deepseek but no deepseek key in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"deepseek\"; CHAT_KEY=\"$DS_KEY\" ;;\n glm)\n if [ -z \"$GLM_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=glm but no glm: line in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"glm\"; CHAT_KEY=\"$GLM_KEY\"\n echo \"WARNING: GLM has known schema-drift (~3 fallbacks/run); DeepSeek is the recommended demo provider.\" >&2 ;;\n *) echo \"ERROR: DEMO_PROVIDER must be deepseek or glm (got: $DEMO_PROVIDER).\" >&2; exit 1 ;;\nesac\necho \"Loaded keys: qwen(len=${#QWEN_KEY}) ${DEMO_PROVIDER}(len=${#CHAT_KEY}) — values not printed.\"\n\n# Point embedding + TTS at this account's private Aliyun/DashScope gateway (from API及文档.txt).\nexport MEMORY_EMBEDDING_ENABLED=true\nexport MEMORY_EMBEDDING_API_KEY=\"$QWEN_KEY\"\nexport MEMORY_EMBEDDING_BASE_URL=\"https://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\"\nexport MEMORY_EMBEDDING_MODEL=\"text-embedding-v4\"\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real chat provider (DeepSeek by default; GLM optional via DEMO_PROVIDER=glm), dev profile (H2).\nexport LLM_MODE=dev\nexport LLM_PROVIDER=\"$CHAT_PROVIDER\"\nexport GLM_API_KEY=\"$GLM_KEY\" # present in env regardless; only used if LLM_PROVIDER=glm\nexport DEEPSEEK_API_KEY=\"$DS_KEY\" # likewise; only used if LLM_PROVIDER=deepseek\nexport LLM_ALLOW_FALLBACK=false # a demo must show the REAL provider, never silently fall back to Mock\n\n# --- Public-tunnel hardening (2026-07-24 8-agent delivery-readiness audit, P1-4/P1-5/P1-7) ---\n# The whole point of this script is to be reachable over a public Cloudflare Tunnel (see\n# docs/demo/DEMO-RUNBOOK.md), so it must not run with plain-localhost-only defaults.\nexport COOKIE_SECURE=true # tunnel is HTTPS end-to-end; never ship an insecure cookie publicly\nexport COOKIE_SAME_SITE=none # the demo APK's WebView origin (https://localhost) is cross-site from\n # the tunnel origin; SameSite=Lax silently drops the session cookie there\nexport INNER_COSMOS_SECURITY_TRUSTED_PROXY_ENABLED=true # honor X-Forwarded-For from cloudflared so rate limits are per-visitor,\n # not one shared bucket for every judge hitting the tunnel from localhost\nexport MANAGEMENT_HEALTH_REDIS_ENABLED=false # this demo path never starts Redis; without this the health\n # endpoint falsely reports DOWN even though the app works fine\n# Classroom experience data is explicit and public-demo-only. It creates three curated,\n# passwordless-entry showcase personas, while the well-known admin seed is disabled. The\n# controller also refuses to operate under the prod profile.\nexport SEED_ENABLED=true\nexport DEMO_PUBLIC_ENTRY_ENABLED=true\nexport DEMO_SEED_ADMIN_ENABLED=false\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=${DEMO_PROVIDER} + real embedding + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}, "bfda6e0629bb7342d61ed63b4bbc662fc30d0b4b": {"t": 1785758379.1421645, "n": 5200, "h": "f399ff3cde5d053d", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with REAL providers (GLM chat + Qwen embedding\n# + Qwen TTS), reading keys at runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep '^qwen:' \"$KEYS\" | head -1 | cut -d: -f2-)\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nDS_KEY=\"$(grep -i 'deepseek' \"$KEYS\" | head -1 | sed -E 's/.*apikey[::] *//')\"\nif [ -z \"$QWEN_KEY\" ]; then\n echo \"ERROR: could not find the qwen: line in $KEYS (needed for embedding + TTS).\" >&2\n exit 1\nfi\n# Chat provider: DeepSeek by default (proven 0 schema-drift on this HEAD, see\n# evidence/innovation/INNO-EVAL-005). GLM (glm-4-flash) is available but exhibits ~3 first-attempt\n# JSON-parse failures per dual-kernel run, which can surface deterministic fallback text instead of\n# the model's real words -- so it is NOT the default for a demo. Override with DEMO_PROVIDER=glm.\nDEMO_PROVIDER=\"${DEMO_PROVIDER:-deepseek}\"\ncase \"$DEMO_PROVIDER\" in\n deepseek)\n if [ -z \"$DS_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=deepseek but no deepseek key in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"deepseek\"; CHAT_KEY=\"$DS_KEY\" ;;\n glm)\n if [ -z \"$GLM_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=glm but no glm: line in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"glm\"; CHAT_KEY=\"$GLM_KEY\"\n echo \"WARNING: GLM has known schema-drift (~3 fallbacks/run); DeepSeek is the recommended demo provider.\" >&2 ;;\n *) echo \"ERROR: DEMO_PROVIDER must be deepseek or glm (got: $DEMO_PROVIDER).\" >&2; exit 1 ;;\nesac\necho \"Loaded keys: qwen(len=${#QWEN_KEY}) ${DEMO_PROVIDER}(len=${#CHAT_KEY}) — values not printed.\"\n\n# Point embedding + TTS at this account's private Aliyun/DashScope gateway (from API及文档.txt).\nexport MEMORY_EMBEDDING_ENABLED=true\nexport MEMORY_EMBEDDING_API_KEY=\"$QWEN_KEY\"\nexport MEMORY_EMBEDDING_BASE_URL=\"https://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\"\nexport MEMORY_EMBEDDING_MODEL=\"text-embedding-v4\"\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real chat provider (DeepSeek by default; GLM optional via DEMO_PROVIDER=glm), dev profile (H2).\nexport LLM_MODE=dev\nexport LLM_PROVIDER=\"$CHAT_PROVIDER\"\nexport GLM_API_KEY=\"$GLM_KEY\" # present in env regardless; only used if LLM_PROVIDER=glm\nexport DEEPSEEK_API_KEY=\"$DS_KEY\" # likewise; only used if LLM_PROVIDER=deepseek\nexport LLM_ALLOW_FALLBACK=false # a demo must show the REAL provider, never silently fall back to Mock\n\n# --- Public-tunnel hardening (2026-07-24 8-agent delivery-readiness audit, P1-4/P1-5/P1-7) ---\n# The whole point of this script is to be reachable over a public Cloudflare Tunnel (see\n# docs/demo/DEMO-RUNBOOK.md), so it must not run with plain-localhost-only defaults.\nexport COOKIE_SECURE=true # tunnel is HTTPS end-to-end; never ship an insecure cookie publicly\nexport COOKIE_SAME_SITE=none # the demo APK's WebView origin (https://localhost) is cross-site from\n # the tunnel origin; SameSite=Lax silently drops the session cookie there\nexport INNER_COSMOS_SECURITY_TRUSTED_PROXY_ENABLED=true # honor X-Forwarded-For from cloudflared so rate limits are per-visitor,\n # not one shared bucket for every judge hitting the tunnel from localhost\nexport MANAGEMENT_HEALTH_REDIS_ENABLED=false # this demo path never starts Redis; without this the health\n # endpoint falsely reports DOWN even though the app works fine\n# Never combine this demo path with SPRING_PROFILES_ACTIVE=demo/mysql or SEED_ENABLED=true: those\n# profiles default seed-enabled:true, which would put a hardcoded admin/admin123 account on the\n# public tunnel. This script intentionally never sets either.\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=${DEMO_PROVIDER} + real embedding + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}, "e0175039c2dfe910bab73cfd8aa1269bf777a261": {"t": 1785758380.9934597, "n": 3740, "h": "6d0625346001106d", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with REAL providers (GLM chat + Qwen embedding\n# + Qwen TTS), reading keys at runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep '^qwen:' \"$KEYS\" | head -1 | cut -d: -f2-)\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nDS_KEY=\"$(grep -i 'deepseek' \"$KEYS\" | head -1 | sed -E 's/.*apikey[::] *//')\"\nif [ -z \"$QWEN_KEY\" ]; then\n echo \"ERROR: could not find the qwen: line in $KEYS (needed for embedding + TTS).\" >&2\n exit 1\nfi\n# Chat provider: DeepSeek by default (proven 0 schema-drift on this HEAD, see\n# evidence/innovation/INNO-EVAL-005). GLM (glm-4-flash) is available but exhibits ~3 first-attempt\n# JSON-parse failures per dual-kernel run, which can surface deterministic fallback text instead of\n# the model's real words -- so it is NOT the default for a demo. Override with DEMO_PROVIDER=glm.\nDEMO_PROVIDER=\"${DEMO_PROVIDER:-deepseek}\"\ncase \"$DEMO_PROVIDER\" in\n deepseek)\n if [ -z \"$DS_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=deepseek but no deepseek key in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"deepseek\"; CHAT_KEY=\"$DS_KEY\" ;;\n glm)\n if [ -z \"$GLM_KEY\" ]; then echo \"ERROR: DEMO_PROVIDER=glm but no glm: line in $KEYS.\" >&2; exit 1; fi\n CHAT_PROVIDER=\"glm\"; CHAT_KEY=\"$GLM_KEY\"\n echo \"WARNING: GLM has known schema-drift (~3 fallbacks/run); DeepSeek is the recommended demo provider.\" >&2 ;;\n *) echo \"ERROR: DEMO_PROVIDER must be deepseek or glm (got: $DEMO_PROVIDER).\" >&2; exit 1 ;;\nesac\necho \"Loaded keys: qwen(len=${#QWEN_KEY}) ${DEMO_PROVIDER}(len=${#CHAT_KEY}) — values not printed.\"\n\n# Point embedding + TTS at this account's private Aliyun/DashScope gateway (from API及文档.txt).\nexport MEMORY_EMBEDDING_ENABLED=true\nexport MEMORY_EMBEDDING_API_KEY=\"$QWEN_KEY\"\nexport MEMORY_EMBEDDING_BASE_URL=\"https://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\"\nexport MEMORY_EMBEDDING_MODEL=\"text-embedding-v4\"\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real chat provider (DeepSeek by default; GLM optional via DEMO_PROVIDER=glm), dev profile (H2).\nexport LLM_MODE=dev\nexport LLM_PROVIDER=\"$CHAT_PROVIDER\"\nexport GLM_API_KEY=\"$GLM_KEY\" # present in env regardless; only used if LLM_PROVIDER=glm\nexport DEEPSEEK_API_KEY=\"$DS_KEY\" # likewise; only used if LLM_PROVIDER=deepseek\nexport LLM_ALLOW_FALLBACK=false # a demo must show the REAL provider, never silently fall back to Mock\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=glm + real embedding + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}, "7b04498db8f17459bda4c2a398056d62c47bafd3": {"t": 1785758382.3681655, "n": 2382, "h": "5e441f37b7550cce", "c": "#!/usr/bin/env bash\n# Demo: start the Inner Cosmos backend on the laptop with REAL providers (GLM chat + Qwen embedding\n# + Qwen TTS), reading keys at runtime from the operator's gitignored API及文档.txt. Never writes a\n# key into any committed file. Use this for the in-class demo so the experience runs on real models.\n#\n# Usage: bash scripts/demo/run-demo-server.sh [PORT] (default 8080)\n# Env: DEMO_PORT, JAVA_HOME (defaults to C:\\Program Files\\Java\\jdk-21.0.10 on Windows)\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\ncd \"$ROOT\"\nPORT=\"${DEMO_PORT:-${1:-8080}}\"\nKEYS=\"$ROOT/API及文档.txt\"\n\nif [ ! -f \"$KEYS\" ]; then\n echo \"ERROR: $KEYS not found. Put the operator's real keys there (it is gitignored).\" >&2\n exit 1\nfi\n\n# Extract keys without echoing the values (length only, for confirmation).\nQWEN_KEY=\"$(grep '^qwen:' \"$KEYS\" | head -1 | cut -d: -f2-)\"\nGLM_KEY=\"$(grep -i '^glm' \"$KEYS\" | head -1 | sed -E 's/^[Gg][Ll][Mm][::] *//')\"\nif [ -z \"$QWEN_KEY\" ] || [ -z \"$GLM_KEY\" ]; then\n echo \"ERROR: could not find the qwen: and glm: lines in $KEYS.\" >&2\n exit 1\nfi\necho \"Loaded keys: qwen(len=${#QWEN_KEY}) glm(len=${#GLM_KEY}) — values not printed.\"\n\n# Point embedding + TTS at this account's private Aliyun/DashScope gateway (from API及文档.txt).\nexport MEMORY_EMBEDDING_ENABLED=true\nexport MEMORY_EMBEDDING_API_KEY=\"$QWEN_KEY\"\nexport MEMORY_EMBEDDING_BASE_URL=\"https://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\"\nexport MEMORY_EMBEDDING_MODEL=\"text-embedding-v4\"\nexport TTS_ENABLED=true\nexport TTS_API_KEY=\"$QWEN_KEY\"\nexport TTS_WS_URL=\"wss://llm-errus8cw2pf66bx9.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference\"\n\n# Real GLM chat provider (the project's default failover chain), dev profile (H2), on the chosen port.\nexport LLM_MODE=dev\nexport LLM_PROVIDER=glm\nexport GLM_API_KEY=\"$GLM_KEY\"\n\n# JAVA_HOME for the Maven wrapper on Windows.\nif [ -z \"${JAVA_HOME:-}\" ] && [ -d \"/c/Program Files/Java/jdk-21.0.10\" ]; then\n export JAVA_HOME=\"/c/Program Files/Java/jdk-21.0.10\"\nfi\n\necho \"Starting Inner Cosmos on :${PORT} with LLM_PROVIDER=glm + real embedding + real TTS...\"\necho \"Health check: http://localhost:${PORT}/actuator/health\"\necho \"App: http://localhost:${PORT}/app/aurora/\"\necho \"(Keep this terminal open. Stop with Ctrl+C.)\"\nexec ./mvnw.cmd spring-boot:run -Dspring-boot.run.jvmArguments=\"-Dserver.port=${PORT}\"\n"}}, "src/main/java/com/innercosmos/ai/client/DeepSeekLlmClient.java": {"add4b241338c47dd1ccbafa2880f7b484adb07b0": {"t": 1785746128.9898458, "n": 14156, "h": "438a2ad82e700b29", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-v4-flash\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n if (!request.retryEnabledOrDefault()) {\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed within the stage deadline and retry is disabled\",\n firstError);\n }\n log.warn(\"DeepSeek stage failed without retry, falling back to local mock: {}\", firstError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n log.warn(\"DeepSeek chat failed on first attempt, retrying once: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = requestBody(request, messages, true);\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n StringBuilder aggregated = new StringBuilder();\n try {\n HttpResponse> httpResponse = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n // Non-2xx (rate limit / bad request / auth): body is an error JSON, not SSE `data:`\n // lines. Without this guard those lines are all skipped, the stream ends empty, and the\n // caller emits a silent empty `done` -> user sees no reply. Route it into the fallback.\n httpResponse.body().close();\n throw new java.io.IOException(\"DeepSeek stream HTTP \" + httpResponse.statusCode());\n }\n httpResponse.body().forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n // Only drip the full fallback when nothing was streamed yet, otherwise we would duplicate\n // the partial content already delivered to the client.\n if (aggregated.length() == 0) {\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n if (aggregated.length() == 0) {\n // 2xx but no content deltas (empty/malformed stream) -> fall back so the turn still replies.\n log.warn(\"DeepSeek stream returned empty content, falling back to chat drip\");\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n\n /** Blocking-path fallback (retries then local mock) dripped out as stream tokens. */\n private String dripFromChat(LlmRequest request, SseEmitter emitter) throws Exception {\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = requestBody(request, messages, false);\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode choice = root.path(\"choices\").path(0);\n JsonNode message = choice.path(\"message\");\n JsonNode content = message.path(\"content\");\n String finishReason = choice.path(\"finish_reason\").asText(\"\");\n boolean reasoningPresent = !message.path(\"reasoning_content\").asText(\"\").isBlank();\n int completionTokens = root.path(\"usage\").path(\"completion_tokens\").asInt(-1);\n if (\"length\".equalsIgnoreCase(finishReason)) {\n throw new RuntimeException(\"DeepSeek completion exhausted output budget\"\n + \" (finish_reason=length, completion_tokens=\" + completionTokens\n + \", content_blank=\" + content.asText(\"\").isBlank()\n + \", reasoning_content_present=\" + reasoningPresent + \")\");\n }\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"DeepSeek completion contained no usable content\"\n + \" (finish_reason=\" + (finishReason.isBlank() ? \"unknown\" : finishReason)\n + \", completion_tokens=\" + completionTokens\n + \", reasoning_content_present=\" + reasoningPresent + \")\");\n }\n return content.asText();\n }\n\n Map requestBody(LlmRequest request, List> messages, boolean stream) {\n boolean thinking = Boolean.TRUE.equals(request.thinkingEnabled);\n Map body = new LinkedHashMap<>();\n body.put(\"model\", model);\n body.put(\"messages\", messages);\n body.put(\"max_tokens\", request.maxTokensOr(LlmClient.RESPONSE_MAX_TOKENS));\n body.put(\"stream\", stream);\n body.put(\"thinking\", Map.of(\"type\", thinking ? \"enabled\" : \"disabled\"));\n if (thinking && request.reasoningEffort != null && !request.reasoningEffort.isBlank()) {\n body.put(\"reasoning_effort\", request.reasoningEffort.trim());\n }\n // DeepSeek thinking mode does not support temperature. Omitting it avoids a misleading\n // no-op parameter while preserving the foreground speaker's tuned sampling.\n if (!thinking) {\n body.put(\"temperature\", request.temperature != null ? request.temperature : 0.72);\n }\n return body;\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "ead110236893cf89e66f97f6619243f43f2fc883": {"t": 1785758373.9649189, "n": 14156, "h": "438a2ad82e700b29", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-v4-flash\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n if (!request.retryEnabledOrDefault()) {\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed within the stage deadline and retry is disabled\",\n firstError);\n }\n log.warn(\"DeepSeek stage failed without retry, falling back to local mock: {}\", firstError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n log.warn(\"DeepSeek chat failed on first attempt, retrying once: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = requestBody(request, messages, true);\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n StringBuilder aggregated = new StringBuilder();\n try {\n HttpResponse> httpResponse = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n // Non-2xx (rate limit / bad request / auth): body is an error JSON, not SSE `data:`\n // lines. Without this guard those lines are all skipped, the stream ends empty, and the\n // caller emits a silent empty `done` -> user sees no reply. Route it into the fallback.\n httpResponse.body().close();\n throw new java.io.IOException(\"DeepSeek stream HTTP \" + httpResponse.statusCode());\n }\n httpResponse.body().forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n // Only drip the full fallback when nothing was streamed yet, otherwise we would duplicate\n // the partial content already delivered to the client.\n if (aggregated.length() == 0) {\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n if (aggregated.length() == 0) {\n // 2xx but no content deltas (empty/malformed stream) -> fall back so the turn still replies.\n log.warn(\"DeepSeek stream returned empty content, falling back to chat drip\");\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n\n /** Blocking-path fallback (retries then local mock) dripped out as stream tokens. */\n private String dripFromChat(LlmRequest request, SseEmitter emitter) throws Exception {\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = requestBody(request, messages, false);\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode choice = root.path(\"choices\").path(0);\n JsonNode message = choice.path(\"message\");\n JsonNode content = message.path(\"content\");\n String finishReason = choice.path(\"finish_reason\").asText(\"\");\n boolean reasoningPresent = !message.path(\"reasoning_content\").asText(\"\").isBlank();\n int completionTokens = root.path(\"usage\").path(\"completion_tokens\").asInt(-1);\n if (\"length\".equalsIgnoreCase(finishReason)) {\n throw new RuntimeException(\"DeepSeek completion exhausted output budget\"\n + \" (finish_reason=length, completion_tokens=\" + completionTokens\n + \", content_blank=\" + content.asText(\"\").isBlank()\n + \", reasoning_content_present=\" + reasoningPresent + \")\");\n }\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"DeepSeek completion contained no usable content\"\n + \" (finish_reason=\" + (finishReason.isBlank() ? \"unknown\" : finishReason)\n + \", completion_tokens=\" + completionTokens\n + \", reasoning_content_present=\" + reasoningPresent + \")\");\n }\n return content.asText();\n }\n\n Map requestBody(LlmRequest request, List> messages, boolean stream) {\n boolean thinking = Boolean.TRUE.equals(request.thinkingEnabled);\n Map body = new LinkedHashMap<>();\n body.put(\"model\", model);\n body.put(\"messages\", messages);\n body.put(\"max_tokens\", request.maxTokensOr(LlmClient.RESPONSE_MAX_TOKENS));\n body.put(\"stream\", stream);\n body.put(\"thinking\", Map.of(\"type\", thinking ? \"enabled\" : \"disabled\"));\n if (thinking && request.reasoningEffort != null && !request.reasoningEffort.isBlank()) {\n body.put(\"reasoning_effort\", request.reasoningEffort.trim());\n }\n // DeepSeek thinking mode does not support temperature. Omitting it avoids a misleading\n // no-op parameter while preserving the foreground speaker's tuned sampling.\n if (!thinking) {\n body.put(\"temperature\", request.temperature != null ? request.temperature : 0.72);\n }\n return body;\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "946cc55d547dc78388a6eb7f4fa8e73966b4d09e": {"t": 1785758375.4808555, "n": 13042, "h": "ada2db40129ac917", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-v4-flash\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n if (!request.retryEnabledOrDefault()) {\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed within the stage deadline and retry is disabled\",\n firstError);\n }\n log.warn(\"DeepSeek stage failed without retry, falling back to local mock: {}\", firstError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n log.warn(\"DeepSeek chat failed on first attempt, retrying once: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = requestBody(request, messages, true);\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n StringBuilder aggregated = new StringBuilder();\n try {\n HttpResponse> httpResponse = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n // Non-2xx (rate limit / bad request / auth): body is an error JSON, not SSE `data:`\n // lines. Without this guard those lines are all skipped, the stream ends empty, and the\n // caller emits a silent empty `done` -> user sees no reply. Route it into the fallback.\n httpResponse.body().close();\n throw new java.io.IOException(\"DeepSeek stream HTTP \" + httpResponse.statusCode());\n }\n httpResponse.body().forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n // Only drip the full fallback when nothing was streamed yet, otherwise we would duplicate\n // the partial content already delivered to the client.\n if (aggregated.length() == 0) {\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n if (aggregated.length() == 0) {\n // 2xx but no content deltas (empty/malformed stream) -> fall back so the turn still replies.\n log.warn(\"DeepSeek stream returned empty content, falling back to chat drip\");\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n\n /** Blocking-path fallback (retries then local mock) dripped out as stream tokens. */\n private String dripFromChat(LlmRequest request, SseEmitter emitter) throws Exception {\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = requestBody(request, messages, false);\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(request.timeoutMsOr(timeoutMs)))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n Map requestBody(LlmRequest request, List> messages, boolean stream) {\n boolean thinking = Boolean.TRUE.equals(request.thinkingEnabled);\n Map body = new LinkedHashMap<>();\n body.put(\"model\", model);\n body.put(\"messages\", messages);\n body.put(\"max_tokens\", request.maxTokensOr(LlmClient.RESPONSE_MAX_TOKENS));\n body.put(\"stream\", stream);\n body.put(\"thinking\", Map.of(\"type\", thinking ? \"enabled\" : \"disabled\"));\n // DeepSeek thinking mode does not support temperature. Omitting it avoids a misleading\n // no-op parameter while preserving the foreground speaker's tuned sampling.\n if (!thinking) {\n body.put(\"temperature\", request.temperature != null ? request.temperature : 0.72);\n }\n return body;\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "4863de92bc356c4f32fa16bf19b4d27b735957e3": {"t": 1785758377.4656754, "n": 11994, "h": "78e6bf573949dbe6", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", true\n );\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n StringBuilder aggregated = new StringBuilder();\n try {\n HttpResponse> httpResponse = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n // Non-2xx (rate limit / bad request / auth): body is an error JSON, not SSE `data:`\n // lines. Without this guard those lines are all skipped, the stream ends empty, and the\n // caller emits a silent empty `done` -> user sees no reply. Route it into the fallback.\n httpResponse.body().close();\n throw new java.io.IOException(\"DeepSeek stream HTTP \" + httpResponse.statusCode());\n }\n httpResponse.body().forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n // Only drip the full fallback when nothing was streamed yet, otherwise we would duplicate\n // the partial content already delivered to the client.\n if (aggregated.length() == 0) {\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n if (aggregated.length() == 0) {\n // 2xx but no content deltas (empty/malformed stream) -> fall back so the turn still replies.\n log.warn(\"DeepSeek stream returned empty content, falling back to chat drip\");\n return dripFromChat(request, emitter);\n }\n return aggregated.toString();\n }\n\n /** Blocking-path fallback (retries then local mock) dripped out as stream tokens. */\n private String dripFromChat(LlmRequest request, SseEmitter emitter) throws Exception {\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "2702941cc15c1887c3a402c46da4a551b346e26e": {"t": 1785758378.7478359, "n": 10625, "h": "99274b551581794e", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", true\n );\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n try {\n java.util.stream.Stream lines = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines()).body();\n StringBuilder aggregated = new StringBuilder();\n lines.forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n return aggregated.toString();\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", request.systemPromptOr(systemPrompt())));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "72eed75c1cf10ff6cec3c7916fd267b21b445084": {"t": 1785758380.2460892, "n": 10577, "h": "ea4ec467e89881f1", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", true\n );\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n try {\n java.util.stream.Stream lines = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines()).body();\n StringBuilder aggregated = new StringBuilder();\n lines.forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n return aggregated.toString();\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", request.temperature != null ? request.temperature : 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "28dbf0085b4422c46d8c347c27795f934d3291d4": {"t": 1785758381.669076, "n": 10473, "h": "de3611f5d48cfce2", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", true\n );\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n try {\n java.util.stream.Stream lines = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines()).body();\n StringBuilder aggregated = new StringBuilder();\n lines.forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n return aggregated.toString();\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", 0.72,\n \"max_tokens\", LlmClient.RESPONSE_MAX_TOKENS,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "eb19fb9b0e411f989a5e706fccea85f8d6a9ac80": {"t": 1785758382.8949568, "n": 10421, "h": "f888765fa8bb2917", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(120_000L);\n aiExecutor.execute(() -> {\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n String aggregated = streamRemote(request, emitter);\n if (aggregated == null) {\n return;\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n /**\n * Real provider SSE streaming (VS-003 §2). DeepSeek is OpenAI-compatible.\n */\n private String streamRemote(LlmRequest request, SseEmitter emitter) throws Exception {\n List> messages = buildMessages(request);\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", 0.72,\n \"max_tokens\", 900,\n \"stream\", true\n );\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .header(\"Accept\", \"text/event-stream\")\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n try {\n java.util.stream.Stream lines = httpClient.send(httpRequest,\n HttpResponse.BodyHandlers.ofLines()).body();\n StringBuilder aggregated = new StringBuilder();\n lines.forEach(line -> {\n if (line == null || !line.startsWith(\"data:\")) return;\n String payload = line.substring(5).trim();\n if (payload.isEmpty() || \"[DONE]\".equals(payload)) return;\n try {\n JsonNode delta = objectMapper.readTree(payload)\n .path(\"choices\").path(0).path(\"delta\").path(\"content\");\n if (!delta.isMissingNode() && !delta.asText().isEmpty()) {\n String token = delta.asText();\n aggregated.append(token);\n emitter.send(SseEmitter.event().name(\"token\")\n .data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n } catch (Exception parseEx) {\n log.debug(\"DeepSeek stream parse skip: {}\", parseEx.getMessage());\n }\n });\n return aggregated.toString();\n } catch (Exception remoteError) {\n log.warn(\"DeepSeek stream failed, falling back to chat drip: {}\", remoteError.getMessage());\n String full = chat(request);\n for (String token : full.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n }\n return full;\n }\n }\n\n private List> buildMessages(LlmRequest request) {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n return messages;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", 0.72,\n \"max_tokens\", 900,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "8459d5304acad56acc7de3031d1fc4dfa3745ca7": {"t": 1785758384.255275, "n": 7188, "h": "3d9781f4d2beff75", "c": "package com.innercosmos.ai.client;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.innercosmos.service.AiLogService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Executor;\n\npublic class DeepSeekLlmClient implements LlmClient {\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n private static final ObjectMapper objectMapper = new ObjectMapper();\n\n private final String apiKey;\n private final String baseUrl;\n private final String model;\n private final int timeoutMs;\n private final boolean allowFallback;\n private final AiLogService aiLogService;\n private final Executor aiExecutor;\n private final MockLlmClient fallback;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(String apiKey, String baseUrl, String model, int timeoutMs,\n boolean allowFallback, AiLogService aiLogService, Executor aiExecutor) {\n this.apiKey = apiKey;\n this.baseUrl = normalizeUrl(baseUrl);\n this.model = model == null || model.isBlank() ? \"deepseek-chat\" : model;\n this.timeoutMs = timeoutMs <= 0 ? 30000 : timeoutMs;\n this.allowFallback = allowFallback;\n this.aiLogService = aiLogService;\n this.aiExecutor = aiExecutor;\n this.fallback = new MockLlmClient(aiExecutor);\n this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();\n }\n\n @Override\n public String chat(LlmRequest request) {\n long start = System.currentTimeMillis();\n String response = null;\n boolean success = false;\n boolean fallbackUsed = false;\n String errorMessage = null;\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw new IllegalStateException(\"DeepSeek API key is empty\");\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception firstError) {\n errorMessage = firstError.getMessage();\n log.warn(\"DeepSeek chat failed on first attempt: {}\", firstError.getMessage());\n try {\n if (apiKey == null || apiKey.isBlank()) {\n throw firstError;\n }\n response = doChat(request);\n success = true;\n return response;\n } catch (Exception retryError) {\n errorMessage = retryError.getMessage();\n if (!allowFallback) {\n throw new RuntimeException(\"DeepSeek remote chat failed and fallback is disabled\", retryError);\n }\n log.error(\"DeepSeek chat failed on retry, falling back to local mock: {}\", retryError.getMessage());\n response = fallback.chat(request);\n fallbackUsed = true;\n success = true;\n return response;\n }\n } finally {\n aiLogService.recordDetailed(request.userId, request.moduleName, fallbackUsed ? \"MOCK\" : \"DEEPSEEK\",\n fallbackUsed ? \"mock-inner-cosmos\" : model, request.prompt, response, request.requestJson,\n response, success, fallbackUsed, fallbackUsed ? errorMessage : (success ? null : errorMessage),\n System.currentTimeMillis() - start);\n }\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n SseEmitter emitter = new SseEmitter(60_000L);\n aiExecutor.execute(() -> {\n try {\n String response = chat(request);\n for (String token : response.split(\"\")) {\n emitter.send(SseEmitter.event().name(\"token\").data(\"{\\\"content\\\":\\\"\" + escape(token) + \"\\\"}\"));\n Thread.sleep(16);\n }\n emitter.send(SseEmitter.event().name(\"done\").data(\"{\\\"message\\\":\\\"done\\\"}\"));\n emitter.complete();\n } catch (Exception exception) {\n emitter.completeWithError(exception);\n }\n });\n return emitter;\n }\n\n private String doChat(LlmRequest request) throws Exception {\n List> messages = new ArrayList<>();\n messages.add(Map.of(\"role\", \"system\", \"content\", systemPrompt()));\n if (request.recentMessages != null) {\n for (String recent : request.recentMessages) {\n if (recent != null && !recent.isBlank()) {\n messages.add(Map.of(\"role\", \"user\", \"content\", \"Context note: \" + recent));\n }\n }\n }\n messages.add(Map.of(\"role\", \"user\", \"content\", request.prompt == null ? \"\" : request.prompt));\n Map body = Map.of(\n \"model\", model,\n \"messages\", messages,\n \"temperature\", 0.72,\n \"max_tokens\", 900,\n \"stream\", false\n );\n\n HttpRequest httpRequest = HttpRequest.newBuilder()\n .uri(URI.create(baseUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .timeout(Duration.ofMillis(timeoutMs))\n .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))\n .build();\n HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {\n throw new RuntimeException(\"DeepSeek API returned status \" + httpResponse.statusCode() + \": \" + httpResponse.body());\n }\n JsonNode root = objectMapper.readTree(httpResponse.body());\n JsonNode content = root.path(\"choices\").path(0).path(\"message\").path(\"content\");\n if (content.isMissingNode() || content.asText().isBlank()) {\n throw new RuntimeException(\"Unexpected DeepSeek response format: \" + httpResponse.body());\n }\n return content.asText();\n }\n\n private String systemPrompt() {\n return \"You are Aurora in Inner Cosmos. Be warm, specific, safe, and reflective. \"\n + \"Use the user's language. Ask at most one question. Never diagnose.\";\n }\n\n private String normalizeUrl(String value) {\n if (value == null || value.isBlank()) return \"https://api.deepseek.com/chat/completions\";\n if (value.endsWith(\"/chat/completions\")) return value;\n return value.replaceAll(\"/+$\", \"\") + \"/chat/completions\";\n }\n\n private String escape(String token) {\n return token.replace(\"\\\\\", \"\\\\\\\\\")\n .replace(\"\\\"\", \"\\\\\\\"\")\n .replace(\"\\n\", \"\\\\n\")\n .replace(\"\\r\", \"\\\\r\")\n .replace(\"\\t\", \"\\\\t\");\n }\n}\n"}, "cbf4645290d5bf4718ab5a5fb971eb03ec0a0940": {"t": 1785758385.6672666, "n": 564, "h": "8e7d745cd87ea41d", "c": "package com.innercosmos.ai.client;\n\nimport com.innercosmos.exception.AiProviderException;\nimport org.springframework.web.servlet.mvc.method.annotation.SseEmitter;\n\npublic class DeepSeekLlmClient implements LlmClient {\n @Override\n public String chat(LlmRequest request) {\n throw new AiProviderException(\"DeepSeek remote mode is reserved for later configuration.\");\n }\n\n @Override\n public SseEmitter streamChat(LlmRequest request) {\n throw new AiProviderException(\"DeepSeek streaming mode is reserved for later configuration.\");\n }\n}\n"}}}}