Commit Graph

66 Commits

Author SHA1 Message Date
rcourtman 895b78bd89 Make Assistant tool choice model-owned 2026-05-15 11:27:15 +01:00
rcourtman 9329258f8b Correct the DeepSeek tool_choice coercion rationale
The previous comment claimed DeepSeek's API aliases v4-flash/v4-pro to
deepseek-reasoner, justifying the auto coercion via the legacy
reasoner's known 400 behavior. That had the alias direction inverted:
per DeepSeek's pricing page, deepseek-chat and deepseek-reasoner are
deprecated aliases for v4-flash's non-thinking and thinking modes
respectively, not the other way around.

The coercion itself is empirically correct, though. Live preflight
against deepseek-v4-flash with tool_choice=required produces a
deterministic HTTP 400 ("provider rejected forced tool selection") in
275ms. The behavior is consistent across the DeepSeek user community
- multiple downstream projects (pydantic-ai #5193, claude-code-router
#1378, opencode #24190, others) confirm V4 models reject forced tool
selection despite DeepSeek's chat-completion docs listing required as
a supported value. Server reality disagrees with documentation.

This commit updates only the comments in openai.go and openai_test.go
to point at the empirical evidence and the community confirmation.
The coercion behavior is unchanged.
2026-05-11 14:46:08 +01:00
rcourtman 46145df925 Coerce DeepSeek tool_choice to "auto" so Patrol stops failing
DeepSeek's API server-side aliases deepseek-v4-flash and
deepseek-v4-pro to deepseek-reasoner, which rejects forced
tool_choice with HTTP 400 ("deepseek-reasoner does not support
this tool_choice"). Pulse's classifier then surfaced this as
"Selected model does not support Patrol tools," misdirecting
diagnosis to the model rather than the request shape.

supportsForcedToolChoice now returns false for any DeepSeek
client, so every DeepSeek model falls back to tool_choice
"auto" regardless of how DeepSeek routes the requested ID.
The ai-runtime contract is updated to match: the
provider-transport boundary now coerces forced tool_choice for
every direct DeepSeek model ID, not only unknown ones.

Patrol verified end-to-end: 20 tool calls, 9 findings, prior
runtime failure auto-resolved.
2026-05-10 00:04:13 +01:00
rcourtman d5735a1b92 Keep direct DeepSeek Patrol models selectable 2026-05-08 11:57:04 +01:00
rcourtman f88f0fcf1a Align DeepSeek V4 Patrol readiness 2026-05-08 11:34:07 +01:00
rcourtman 92b2a6c7be Align DeepSeek provider surface 2026-05-08 01:32:58 +01:00
rcourtman f8047177ef Update DeepSeek Patrol defaults 2026-05-08 01:24:50 +01:00
rcourtman 3b077b8ef6 Support DeepSeek V4 tool turns in Patrol
- coerce direct DeepSeek forced tool choices to auto for compatible requests
- preserve DeepSeek reasoning content across tool-result turns
- normalize Patrol runtime failure detail before run-history serialization
2026-05-07 22:30:27 +01:00
rcourtman 01474a18b6 Fail closed on incomplete OpenAI SSE streams
Keep the buffered EOF compatibility path for OpenAI-compatible streams that omit [DONE] but provide a terminal finish_reason, while rejecting truncated tool-call streams before they can produce executable tool calls.

Refs #1411

Refs #1412
2026-05-05 22:10:50 +01:00
rcourtman 01ea5ebb6e Pass keep_alive=30s to Ollama requests
Pass keep_alive=30s on both Chat and ChatStream requests so Ollama unloads the model shortly after Patrol or one-shot analysis bursts instead of refreshing the default 5-minute residency window on every request.

Tests assert both streaming and non-streaming Ollama request bodies include the keep_alive field.

Fixes #1425
2026-04-30 14:20:12 +01:00
rcourtman ded190dcab Retire hosted AI quickstart runtime 2026-04-28 16:11:27 +01:00
rcourtman b1e179479d Retire self-hosted AI quickstart surfaces 2026-04-28 15:49:18 +01:00
rcourtman 94a5ab1b59 Enforce data policy at AI model boundary 2026-04-25 21:13:53 +01:00
rcourtman 160d8126f2 Restrict Ollama provider outbound transport 2026-04-22 05:25:57 +01:00
rcourtman 5e83877503 Flush OpenAI SSE buffers on EOF
Share OpenAI ChatStream finalization across DONE and clean EOF so buffered final chunks still produce tool calls and done events for OpenAI-compatible providers.\n\nFixes #1411
2026-04-15 17:37:51 +01:00
rcourtman 12c30da168 Own quickstart state and bill Patrol per run 2026-04-03 19:45:38 +01:00
rcourtman 830215e4c3 Bill quickstart by Patrol execution 2026-04-03 19:00:40 +01:00
rcourtman b9d8f79740 Harden quickstart contract compatibility 2026-04-03 18:11:33 +01:00
rcourtman 0d25939921 Use a Pulse-owned alias for hosted quickstart models 2026-04-03 10:44:58 +01:00
rcourtman 57cc212f34 Replace Patrol quickstart with server bootstrap 2026-04-02 23:15:06 +01:00
rcourtman 73597f8b1a Forward-port Ollama runtime auth continuity 2026-04-01 14:38:39 +01:00
rcourtman 6f7ceee404 fix(ai): point quickstart to owned license endpoint 2026-03-25 17:33:51 +00:00
rcourtman 778a2577b6 feat: Pulse v6 release 2026-03-18 16:06:30 +00:00
rcourtman 499ab812e3 Fix post-release regressions and lock v5 to single-tenant runtime 2026-03-05 23:46:35 +00:00
rcourtman 5bd0563283 test(providers): update Ollama integration tests for timeout parameter 2026-03-01 23:28:16 +00:00
rcourtman 86a7c2283c Revert "Detect incompatible models that don't support function calling"
This reverts commit 11a72ee263.
2026-02-03 13:36:30 +00:00
rcourtman c6318a8484 Revert "Simplify incompatible model error message"
This reverts commit c58fe81700.
2026-02-03 13:36:30 +00:00
rcourtman c58fe81700 Simplify incompatible model error message 2026-02-03 13:30:54 +00:00
rcourtman 11a72ee263 Detect incompatible models that don't support function calling
When local LLM servers (LM Studio, llama.cpp) receive tool definitions
but the model doesn't support function calling, they output internal
control tokens like <|channel|>, <|im_start|>, etc. instead of proper
responses.

This change detects these control tokens during streaming and returns
a clear error message explaining that the model doesn't support function
calling and recommending compatible models (Llama 3.1+, Mistral, Qwen).

This is better than the previous approach of offering a "disable tools"
option, which would have crippled Pulse Assistant/Patrol functionality.
Users need to use compatible models for the AI features to work properly.

Related to #1154
2026-02-03 13:28:37 +00:00
rcourtman e3556455c6 Revert "Sanitize LLM control tokens from OpenAI-compatible responses"
This reverts commit e5eb15918e.
2026-02-03 13:14:33 +00:00
rcourtman e5eb15918e Sanitize LLM control tokens from OpenAI-compatible responses
Some local models (llama.cpp, LM Studio) output internal control tokens
like <|channel|>, <|constrain|>, <|message|> instead of using proper
function calling. These tokens leak into the UI creating a poor UX.

This adds sanitization to strip these control tokens from both streaming
and non-streaming responses before they reach the user.
2026-02-03 13:12:17 +00:00
rcourtman 9d83e4e1d1 fix(ai): fix ollama streaming timeouts and ensure consistent tool call responses 2026-02-01 16:28:24 +00:00
rcourtman 95a0d7a6bd feat(backend): implement AI Patrol, Investigation, and system-wide refactors 2026-01-30 19:02:14 +00:00
rcourtman e85ec858fd fix(ai): discovery transient error handling, agentic loop detection, and read-only classification
- Discovery: classify transient errors (429, timeout, connection refused, etc.)
  and return IsError:true so models stop retrying rate-limited calls
- Agentic loop: detect identical tool calls repeated >3 times and block with
  LOOP_DETECTED error, forcing the model to try a different approach
- OpenAI provider: skip tool_choice for DeepSeek Reasoner which doesn't support it
- Read-only classifier: fix curl -I case sensitivity (uppercase flags lowered),
  add iostat/vmstat/mpstat/sar/lxc-ls/lxc-info/nc -z to allowlist,
  fix 2>&1 false positive in input redirect detection
2026-01-29 18:29:54 +00:00
rcourtman fdc81525bb fix(ai): sanitize Gemini message ordering for function calls
Gemini requires that model messages with function calls must be
immediately followed by user messages with function responses.
When message pruning or errors leave orphaned function calls,
Gemini rejects the request.

Add sanitizeGeminiContents() to:
- Strip orphaned function calls (keeping text content)
- Remove orphaned function responses without preceding calls
- Log when sanitization occurs for debugging
2026-01-29 08:19:41 +00:00
rcourtman 641d29a16b Update AI providers for tool call improvements
Provider updates across all supported backends:
- Anthropic: Better tool call handling
- OpenAI: Improved response parsing
- Gemini: Enhanced compatibility
- Ollama: Local model support improvements

Includes test updates for OpenAI provider.
2026-01-28 16:51:18 +00:00
rcourtman 27f1a11acb feat: add AI Intelligence system with investigation and forecasting
Major new AI capabilities for infrastructure monitoring:

Investigation System:
- Autonomous finding investigation with configurable autonomy levels
- Investigation orchestrator with rate limiting and guardrails
- Safety checks for read-only mode enforcement
- Chat-based investigation with approval workflows

Forecasting & Remediation:
- Trend forecasting for resource capacity planning
- Remediation engine for generating fix proposals
- Circuit breaker for AI operation protection

Unified Findings:
- Unified store bridging alerts and AI findings
- Correlation and root cause analysis
- Incident coordinator with metrics recording

New Frontend:
- AI Intelligence page with patrol controls
- Investigation drawer for finding details
- Unified findings panel with actions

Supporting Infrastructure:
- Learning store for user preference tracking
- Proxmox event ingestion and correlation
- Enhanced patrol with investigation triggers
2026-01-24 22:41:43 +00:00
rcourtman 1ac53fa9f1 test: add stream, restart, and fallback tests for AI handlers and providers 2026-01-22 22:33:33 +00:00
rcourtman c76903c3f8 feat: improve Gemini provider with streaming and error handling
- Add streaming response support
- Improve error handling and recovery
- Enhance provider interface
2026-01-22 22:30:49 +00:00
rcourtman 422efdde61 Restore UI improvements and refine Docker/Hosts display
- Restore 'mini' mode for StackedDiskBar.
- Restore layout fixes (fixed table layout, mobile columns) for Docker and Hosts tables.
- Remove 'Ask AI' and AI context selection features.
- Docker: Use compact 'Cube' icon for Podman pods to prevent name obstruction.
- Docker: Show concise image names (strip registry URL).
- Backend: Include pending fixes for AI providers.
2026-01-22 18:03:35 +00:00
rcourtman defe298ddd Refactor: AI provider and executor multi-tenancy support
- Updated AI providers and tests for context/tenant awareness
- Refactored tool executor for multi-tenant state handling
- Added new tests for Docker control and update tools
2026-01-22 16:51:45 +00:00
rcourtman 96b7370f7b test: improve coverage for API, AI, Alerts, and Frontend Utils
- Add comprehensive tests for internal/api/config_handlers.go (Phases 1-3)
- Improve test coverage for AI tools, chat service, and session management
- Enhance alert and notification tests (ResolvedAlert, Webhook)
- Add frontend unit tests for utils (searchHistory, tagColors, temperature, url)
- Add proximity client API tests
2026-01-20 15:52:39 +00:00
rcourtman 4fe3d7df77 feat(ai): Add streaming support and notable models to AI providers
- Add ChatStream method to all providers (Anthropic, OpenAI, Gemini, Ollama)
  for real-time streaming of AI responses with tool call support
- Add StreamingProvider interface with StreamEvent types for content,
  thinking, tool_start, tool_end, done, and error events
- Add notable models feature that fetches model metadata from models.dev
  to identify recent/recommended models (within last 3 months)
- Add Notable field to ModelInfo struct to flag "latest and greatest" models
- Add SupportsThinking method to check for extended reasoning capability

The streaming support enables real-time AI chat responses instead of
waiting for complete responses. The notable models feature helps users
identify which models are current and recommended.
2026-01-19 19:10:58 +00:00
rcourtman 6019e3e77e fix: normalize custom OpenAI-compatible API URLs (#1067)
Users providing base URLs like "https://openrouter.ai/api/v1" were
getting HTML error responses because the client used the URL directly
without appending "/chat/completions".

- Normalize baseURL in NewOpenAIClient to ensure it ends with /chat/completions
- Fix modelsEndpoint() to derive /models from the normalized baseURL
- Add tests for URL normalization with various endpoint formats
2026-01-09 09:13:36 +00:00
rcourtman 9e339957c6 fix: Update runtime config when toggling Docker update actions setting
The DisableDockerUpdateActions setting was being saved to disk but not
updated in h.config, causing the UI toggle to appear to revert on page
refresh since the API returned the stale runtime value.

Related to #1023
2026-01-03 11:14:17 +00:00
rcourtman 83935fa871 feat(ai): enhance AI Patrol with baseline anomaly detection and correlation learning
This update integrates learned baselines into the heuristic analysis to detect abnormal behavior and records significant events (migrations, restarts, spikes) for correlation analysis. Also fixed syntax errors in Ollama integration tests.
2026-01-01 23:00:43 +00:00
rcourtman 3fdf753a5b Enhance devcontainer and CI workflows
- Add persistent volume mounts for Go/npm caches (faster rebuilds)
- Add shell config with helpful aliases and custom prompt
- Add comprehensive devcontainer documentation
- Add pre-commit hooks for Go formatting and linting
- Use go-version-file in CI workflows instead of hardcoded versions
- Simplify docker compose commands with --wait flag
- Add gitignore entries for devcontainer auth files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:29:15 +00:00
rcourtman 85382a79e9 feat: Add configurable AI request timeout for slow hardware. Related to #880
Adds RequestTimeoutSeconds to AI config (default 300s / 5 min).
Users with low-power hardware running Ollama can increase this
value in Settings to prevent timeouts on slower inference.
2025-12-24 10:52:11 +00:00
rcourtman 65e38fac91 test: improve test coverage for AI, license, config, and monitoring packages
New test files:
- internal/ai/providers/gemini_test.go: Comprehensive Gemini provider tests
- internal/api/ai_intelligence_handlers_test.go: AI intelligence endpoint tests
- internal/api/ai_patrol_handlers_test.go: AI patrol endpoint tests
- internal/api/license_handlers_test.go: License API handler tests
- internal/api/security_oidc_response_test.go: OIDC response formatting tests
- internal/config/ai_config_test.go: AI configuration function tests
- internal/config/persistence_ai_test.go: AI config persistence tests
- internal/config/persistence_extended_test.go: Extended persistence tests
- internal/license/persistence_test.go: License persistence tests
- internal/license/pubkey_test.go: Public key handling tests
- internal/monitoring/host_agent_temps_test.go: Temperature processing tests

Enhanced existing files:
- internal/api/updates_test.go: Added update handler tests
- internal/license/license_test.go: Added Service method tests

Coverage improvements:
- ai/providers: 57.3% -> 73.0% (+15.7%)
- license: 78.3% -> 85.9% (+7.6%)
- config: 49.7% -> 53.9% (+4.2%)
- monitoring: 49.8% -> 50.8% (+1.0%)
- api: 28.4% -> 29.8% (+1.4%)
2025-12-19 22:49:30 +00:00
rcourtman c91307be94 fix: guest URL icon now appears/disappears immediately after AI sets/removes it
The issue was a SolidJS reactivity problem in the Dashboard component.
When guestMetadata signal was accessed inside a For loop callback and
assigned to a plain variable, SolidJS lost reactive tracking.

Changed from:
  const metadata = guestMetadata()[guestId] || ...
  customUrl={metadata?.customUrl}

To:
  const getMetadata = () => guestMetadata()[guestId] || ...
  customUrl={getMetadata()?.customUrl}

This ensures SolidJS properly tracks the signal dependency when the
getter function is called directly in JSX props.
2025-12-18 14:42:47 +00:00