Compare commits
5 Commits
f0c1af986d
...
7bbc484053
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bbc484053 | |||
| 45b88728f3 | |||
| 0ec372051a | |||
| 75bf912f60 | |||
| 1b3ff232c4 |
+81
-1
@@ -65,6 +65,11 @@ _GEMINI_CACHE_TTL = 3600
|
||||
_anthropic_client = None
|
||||
_anthropic_history: list[dict] = []
|
||||
_anthropic_history_lock = threading.Lock()
|
||||
|
||||
_deepseek_client = None
|
||||
_deepseek_history: list[dict] = []
|
||||
_deepseek_history_lock = threading.Lock()
|
||||
|
||||
_send_lock = threading.Lock()
|
||||
|
||||
_gemini_cli_adapter = None
|
||||
@@ -159,10 +164,10 @@ def _load_credentials() -> dict:
|
||||
f"Create a credentials.toml with:\n"
|
||||
f" [gemini]\n api_key = \"your-key\"\n"
|
||||
f" [anthropic]\n api_key = \"your-key\"\n"
|
||||
f" [deepseek]\n api_key = \"your-key\"\n"
|
||||
f"Or set SLOP_CREDENTIALS env var to a custom path."
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ provider errors
|
||||
|
||||
class ProviderError(Exception):
|
||||
@@ -241,6 +246,21 @@ def _classify_gemini_error(exc: Exception) -> ProviderError:
|
||||
return ProviderError("unknown", "gemini", exc)
|
||||
|
||||
|
||||
def _classify_deepseek_error(exc: Exception) -> ProviderError:
|
||||
body = str(exc).lower()
|
||||
if "429" in body or "rate" in body:
|
||||
return ProviderError("rate_limit", "deepseek", exc)
|
||||
if "401" in body or "403" in body or "auth" in body or "api key" in body:
|
||||
return ProviderError("auth", "deepseek", exc)
|
||||
if "402" in body or "balance" in body or "billing" in body:
|
||||
return ProviderError("balance", "deepseek", exc)
|
||||
if "quota" in body or "limit exceeded" in body:
|
||||
return ProviderError("quota", "deepseek", exc)
|
||||
if "connection" in body or "timeout" in body or "network" in body:
|
||||
return ProviderError("network", "deepseek", exc)
|
||||
return ProviderError("unknown", "deepseek", exc)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ provider setup
|
||||
|
||||
def set_provider(provider: str, model: str):
|
||||
@@ -280,6 +300,11 @@ def reset_session():
|
||||
_anthropic_client = None
|
||||
with _anthropic_history_lock:
|
||||
_anthropic_history = []
|
||||
|
||||
_deepseek_client = None
|
||||
with _deepseek_history_lock:
|
||||
_deepseek_history = []
|
||||
|
||||
_CACHED_ANTHROPIC_TOOLS = None
|
||||
file_cache.reset_client()
|
||||
|
||||
@@ -308,6 +333,8 @@ def list_models(provider: str) -> list[str]:
|
||||
return _list_gemini_models(creds["gemini"]["api_key"])
|
||||
elif provider == "anthropic":
|
||||
return _list_anthropic_models()
|
||||
elif provider == "deepseek":
|
||||
return _list_deepseek_models(creds["deepseek"]["api_key"])
|
||||
return []
|
||||
|
||||
|
||||
@@ -340,6 +367,14 @@ def _list_anthropic_models() -> list[str]:
|
||||
raise _classify_anthropic_error(exc) from exc
|
||||
|
||||
|
||||
def _list_deepseek_models(api_key: str) -> list[str]:
|
||||
"""
|
||||
List available DeepSeek models.
|
||||
"""
|
||||
# For now, return the models specified in the requirements
|
||||
return ["deepseek-chat", "deepseek-reasoner", "deepseek-v3", "deepseek-r1"]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ tool definition
|
||||
|
||||
TOOL_NAME = "run_powershell"
|
||||
@@ -1385,6 +1420,41 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
raise _classify_anthropic_error(exc) from exc
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ deepseek
|
||||
|
||||
def _ensure_deepseek_client():
|
||||
global _deepseek_client
|
||||
if _deepseek_client is None:
|
||||
creds = _load_credentials()
|
||||
# Placeholder for Dedicated DeepSeek SDK instantiation
|
||||
# import deepseek
|
||||
# _deepseek_client = deepseek.DeepSeek(api_key=creds["deepseek"]["api_key"])
|
||||
pass
|
||||
|
||||
|
||||
def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
file_items: list[dict] | None = None,
|
||||
discussion_history: str = "") -> str:
|
||||
"""
|
||||
Placeholder implementation for DeepSeek provider.
|
||||
Aligns with Gemini/Anthropic patterns for history and tool calling.
|
||||
"""
|
||||
try:
|
||||
_ensure_deepseek_client()
|
||||
mcp_client.configure(file_items or [], [base_dir])
|
||||
|
||||
# TODO: Implement full DeepSeek logic in Phase 2
|
||||
# 1. Build system prompt with context
|
||||
# 2. Manage _deepseek_history
|
||||
# 3. Handle reasoning traces for R1
|
||||
# 4. Handle tool calling loop
|
||||
|
||||
raise ValueError("DeepSeek provider is currently in the infrastructure phase and not yet fully implemented.")
|
||||
|
||||
except Exception as e:
|
||||
raise _classify_deepseek_error(e) from e
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ unified send
|
||||
|
||||
def send(
|
||||
@@ -1413,6 +1483,8 @@ def send(
|
||||
return _send_gemini_cli(md_content, user_message, base_dir, file_items, discussion_history)
|
||||
elif _provider == "anthropic":
|
||||
return _send_anthropic(md_content, user_message, base_dir, file_items, discussion_history)
|
||||
elif _provider == "deepseek":
|
||||
return _send_deepseek(md_content, user_message, base_dir, file_items, discussion_history)
|
||||
raise ValueError(f"unknown provider: {_provider}")
|
||||
|
||||
def get_history_bleed_stats(md_content: str | None = None) -> dict:
|
||||
@@ -1524,6 +1596,14 @@ def get_history_bleed_stats(md_content: str | None = None) -> dict:
|
||||
"current": current_tokens,
|
||||
"percentage": percentage,
|
||||
}
|
||||
elif _provider == "deepseek":
|
||||
# Placeholder for DeepSeek token estimation
|
||||
return {
|
||||
"provider": "deepseek",
|
||||
"limit": 64000, # Common limit for deepseek
|
||||
"current": 0,
|
||||
"percentage": 0,
|
||||
}
|
||||
|
||||
# Default empty state
|
||||
return {
|
||||
|
||||
@@ -9,12 +9,12 @@ To serve as an expert-level utility for personal developer use on small projects
|
||||
- **Manual "Vibe Coding" Assistant:** Serving as an auxiliary, multi-provider assistant that natively interacts with the codebase via sandboxed PowerShell scripts and MCP-like file tools, emphasizing manual developer oversight and explicit confirmation.
|
||||
|
||||
## Key Features
|
||||
- **Multi-Provider Integration:** Supports both Gemini and Anthropic with seamless switching.
|
||||
- **Multi-Provider Integration:** Supports Gemini, Anthropic, and DeepSeek with seamless switching.
|
||||
- **4-Tier Hierarchical Multi-Model Architecture:** Orchestrates an intelligent cascade of specialized models to isolate cognitive loads and minimize token burn.
|
||||
- **Tier 1 (Orchestrator):** Product alignment and high-level strategy using `gemini-3.1-pro-preview`.
|
||||
- **Tier 2 (Tech Lead):** Architectural design and technical planning using `gemini-3-flash-preview`.
|
||||
- **Tier 3 (Worker):** Focused implementation and surgical code changes using `gemini-2.5-flash-lite`.
|
||||
- **Tier 4 (QA):** Bug reproduction, test analysis, and error translation using `gemini-2.5-flash-lite`.
|
||||
- **Tier 3 (Worker):** Focused implementation and surgical code changes using `gemini-2.5-flash-lite` or `deepseek-v3`.
|
||||
- **Tier 4 (QA):** Bug reproduction, test analysis, and error translation using `gemini-2.5-flash-lite` or `deepseek-v3`.
|
||||
- **MMA Delegation Engine:** Utilizes the `mma-exec` CLI and `mma.ps1` helper to route tasks, ensuring each tier receives role-scoped context (e.g., Orchestrators get Product docs; Workers get Workflow specs).
|
||||
- **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context.
|
||||
- **Strict Memory Siloing:** Employs AST-based interface extraction and "Context Amnesia" to provide workers only with the absolute minimum context required, preventing hallucination loops.
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
|
||||
- **google-genai:** For Google Gemini API interaction and explicit context caching.
|
||||
- **anthropic:** For Anthropic Claude API interaction, supporting ephemeral prompt caching.
|
||||
- **DeepSeek (Dedicated SDK):** Integrated for high-performance codegen and reasoning (Phase 2).
|
||||
- **Gemini CLI:** Integrated as a headless backend provider, utilizing a custom subprocess adapter and bridge script for tool execution control.
|
||||
- **Gemini 3.1 Pro Preview:** Tier 1 Orchestrator model for complex reasoning.
|
||||
- **Gemini 3 Flash Preview:** Tier 2 Tech Lead model for rapid architectural planning.
|
||||
- **Gemini 2.5 Flash Lite:** High-performance, low-latency model for Tier 3 Workers and Tier 4 QA.
|
||||
- **DeepSeek-V3:** Tier 3 Worker model optimized for code implementation.
|
||||
- **DeepSeek-R1:** Specialized reasoning model for complex logical chains and "thinking" traces.
|
||||
|
||||
## Configuration & Tooling
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Implementation Plan: DeepSeek API Provider Support
|
||||
|
||||
## Phase 1: Infrastructure & Common Logic
|
||||
- [ ] Task: Initialize MMA Environment `activate_skill mma-orchestrator`
|
||||
- [ ] Task: Update `credentials.toml` schema and configuration logic in `project_manager.py` to support `deepseek`
|
||||
- [ ] Task: Define the `DeepSeekProvider` interface in `ai_client.py` and align with existing provider patterns
|
||||
- [ ] Task: Conductor - User Manual Verification 'Infrastructure & Common Logic' (Protocol in workflow.md)
|
||||
## Phase 1: Infrastructure & Common Logic [checkpoint: 0ec3720]
|
||||
- [x] Task: Initialize MMA Environment `activate_skill mma-orchestrator` 1b3ff23
|
||||
- [x] Task: Update `credentials.toml` schema and configuration logic in `project_manager.py` to support `deepseek` 1b3ff23
|
||||
- [x] Task: Define the `DeepSeekProvider` interface in `ai_client.py` and align with existing provider patterns 1b3ff23
|
||||
- [x] Task: Conductor - User Manual Verification 'Infrastructure & Common Logic' (Protocol in workflow.md) 1b3ff23
|
||||
|
||||
## Phase 2: DeepSeek API Client Implementation
|
||||
- [ ] Task: Write failing tests for `DeepSeekProvider` model selection and basic completion
|
||||
|
||||
@@ -29,7 +29,7 @@ from pydantic import BaseModel
|
||||
from imgui_bundle import imgui, hello_imgui, immapp
|
||||
|
||||
CONFIG_PATH = Path("config.toml")
|
||||
PROVIDERS = ["gemini", "anthropic", "gemini_cli"]
|
||||
PROVIDERS = ["gemini", "anthropic", "gemini_cli", "deepseek"]
|
||||
COMMS_CLAMP_CHARS = 300
|
||||
|
||||
def load_config() -> dict:
|
||||
|
||||
+2
-1
@@ -101,6 +101,7 @@ def default_project(name: str = "unnamed") -> dict:
|
||||
"files": {"base_dir": ".", "paths": []},
|
||||
"screenshots": {"base_dir": ".", "paths": []},
|
||||
"gemini_cli": {"binary_path": "gemini"},
|
||||
"deepseek": {"reasoning_effort": "medium"},
|
||||
"agent": {
|
||||
"tools": {
|
||||
"run_powershell": True,
|
||||
@@ -113,7 +114,7 @@ def default_project(name: str = "unnamed") -> dict:
|
||||
}
|
||||
},
|
||||
"discussion": {
|
||||
"roles": ["User", "AI", "Vendor API", "System"],
|
||||
"roles": ["User", "AI", "Vendor API", "System", "Reasoning"],
|
||||
"active": "main",
|
||||
"discussions": {"main": default_discussion()},
|
||||
},
|
||||
|
||||
+15
-6
@@ -120,6 +120,10 @@ def execute_agent(role: str, prompt: str, docs: list[str]) -> str:
|
||||
|
||||
# Advanced Context: Dependency skeletons for Tier 3
|
||||
injected_context = ""
|
||||
# Whitelist of modules that sub-agents have "unfettered" (full) access to.
|
||||
# These will be provided in full if imported, instead of just skeletons.
|
||||
UNFETTERED_MODULES = ['mcp_client', 'project_manager']
|
||||
|
||||
if role in ['tier3', 'tier3-worker']:
|
||||
for doc in docs:
|
||||
if doc.endswith('.py') and os.path.exists(doc):
|
||||
@@ -129,15 +133,20 @@ def execute_agent(role: str, prompt: str, docs: list[str]) -> str:
|
||||
dep_file = f"{dep}.py"
|
||||
if os.path.exists(dep_file) and dep_file != doc:
|
||||
try:
|
||||
with open(dep_file, 'r', encoding='utf-8') as f:
|
||||
skeleton = generate_skeleton(f.read())
|
||||
injected_context += f"\n\nDEPENDENCY SKELETON: {dep_file}\n{skeleton}\n"
|
||||
if dep in UNFETTERED_MODULES:
|
||||
with open(dep_file, 'r', encoding='utf-8') as f:
|
||||
full_content = f.read()
|
||||
injected_context += f"\n\nFULL MODULE CONTEXT: {dep_file}\n{full_content}\n"
|
||||
else:
|
||||
with open(dep_file, 'r', encoding='utf-8') as f:
|
||||
skeleton = generate_skeleton(f.read())
|
||||
injected_context += f"\n\nDEPENDENCY SKELETON: {dep_file}\n{skeleton}\n"
|
||||
except Exception as e:
|
||||
print(f"Error generating skeleton for {dep_file}: {e}")
|
||||
print(f"Error gathering context for {dep_file}: {e}")
|
||||
|
||||
# Check for token-bloat safety: if injected_context is too large, truncate it
|
||||
if len(injected_context) > 10000:
|
||||
injected_context = injected_context[:10000] + "... [TRUNCATED FOR COMMAND LINE LIMITS]"
|
||||
if len(injected_context) > 15000:
|
||||
injected_context = injected_context[:15000] + "... [TRUNCATED FOR COMMAND LINE LIMITS]"
|
||||
|
||||
# MMA Protocol: Tier 3 and 4 are stateless and tool-less.
|
||||
system_directive = f"STRICT SYSTEM DIRECTIVE: You are a stateless {role}. " \
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import pytest
|
||||
import os
|
||||
import tomllib
|
||||
import tomli_w
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Ensure project root is in path
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
import ai_client
|
||||
import project_manager
|
||||
|
||||
def test_credentials_error_mentions_deepseek(monkeypatch):
|
||||
"""
|
||||
Verify that the error message shown when credentials.toml is missing
|
||||
includes deepseek instructions.
|
||||
"""
|
||||
# Monkeypatch SLOP_CREDENTIALS to a non-existent file
|
||||
monkeypatch.setenv("SLOP_CREDENTIALS", "non_existent_credentials_file.toml")
|
||||
|
||||
with pytest.raises(FileNotFoundError) as excinfo:
|
||||
ai_client._load_credentials()
|
||||
|
||||
err_msg = str(excinfo.value)
|
||||
assert "[deepseek]" in err_msg
|
||||
assert "api_key" in err_msg
|
||||
|
||||
def test_default_project_includes_reasoning_role():
|
||||
"""
|
||||
Verify that 'Reasoning' is included in the default discussion roles
|
||||
to support DeepSeek-R1 reasoning traces.
|
||||
"""
|
||||
proj = project_manager.default_project("test")
|
||||
roles = proj["discussion"]["roles"]
|
||||
assert "Reasoning" in roles
|
||||
|
||||
def test_gui_providers_list():
|
||||
"""
|
||||
Check if 'deepseek' is in the GUI's provider list.
|
||||
"""
|
||||
import gui_2
|
||||
assert "deepseek" in gui_2.PROVIDERS
|
||||
|
||||
def test_deepseek_model_listing():
|
||||
"""
|
||||
Verify that list_models for deepseek returns expected models.
|
||||
"""
|
||||
models = ai_client.list_models("deepseek")
|
||||
assert "deepseek-chat" in models
|
||||
assert "deepseek-reasoner" in models
|
||||
|
||||
def test_gui_provider_list_via_hooks(live_gui):
|
||||
"""
|
||||
Verify 'deepseek' is present in the GUI provider list using API hooks.
|
||||
"""
|
||||
from api_hook_client import ApiHookClient
|
||||
import time
|
||||
client = ApiHookClient()
|
||||
assert client.wait_for_server(timeout=10)
|
||||
|
||||
# Attempt to set provider to deepseek to verify it's an allowed value
|
||||
client.set_value('current_provider', 'deepseek')
|
||||
time.sleep(0.5)
|
||||
assert client.get_value('current_provider') == 'deepseek'
|
||||
Reference in New Issue
Block a user