- OpenCode Go/Zen 自 2026-09-05 起要求每个对话携带稳定的 x-opencode-session 头,用于会话亲和路由与 prompt 缓存优化 - 个人空间「模型与思考」新增「外部会话标识」开关(默认关闭,opt-in) - 开启后按对话惰性生成随机 ID(uuid4 hex)并持久化到对话 metadata,深压缩后重置 - 覆盖主对话/传统与多智能体子智能体/三个审核智能体(一次性 ID)/标题生成(复用主对话 ID) - 仅对 opencode.ai 域名下发,不向其他 provider 泄露对话标识 - 所有对外模型请求统一携带 User-Agent: Astrion/1.0(新增 config/version.py)
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
# ========== api_client.py ==========
|
||
# utils/api_client.py - OpenAI-compatible API 客户端(支持Web模式)
|
||
|
||
import httpx
|
||
import json
|
||
import asyncio
|
||
import base64
|
||
import mimetypes
|
||
import os
|
||
from typing import List, Dict, Optional, AsyncGenerator, Any, Callable
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from typing import Tuple
|
||
try:
|
||
from config import (
|
||
OUTPUT_FORMATS,
|
||
DEFAULT_RESPONSE_MAX_TOKENS,
|
||
LOGS_DIR,
|
||
)
|
||
except ImportError:
|
||
import sys
|
||
from pathlib import Path
|
||
project_root = Path(__file__).resolve().parents[1]
|
||
if str(project_root) not in sys.path:
|
||
sys.path.insert(0, str(project_root))
|
||
from config import (
|
||
OUTPUT_FORMATS,
|
||
DEFAULT_RESPONSE_MAX_TOKENS,
|
||
LOGS_DIR,
|
||
)
|
||
|
||
from utils.log_rotation import append_line, prune_dir
|
||
|
||
|
||
|
||
from utils.api_client.utils import _api_dump_enabled
|
||
|
||
class APIClientBaseMixin:
|
||
def __init__(self, thinking_mode: bool = True, web_mode: bool = False):
|
||
# 模型 API 配置(端点 / 密钥 / 模型 ID)由 apply_profile 从 custom_models 注入;
|
||
# 这里仅初始化为空,未应用任何模型档案前不可直接发请求。
|
||
self.fast_api_config = {
|
||
"base_url": "",
|
||
"api_key": "",
|
||
"model_id": ""
|
||
}
|
||
self.thinking_api_config = {
|
||
"base_url": "",
|
||
"api_key": "",
|
||
"model_id": ""
|
||
}
|
||
self.fast_max_tokens = None
|
||
self.thinking_max_tokens = None
|
||
self.fast_extra_params: Dict = {}
|
||
self.thinking_extra_params: Dict = {}
|
||
# 上下文预算(由上层在每次请求前设置)
|
||
self.current_context_tokens: int = 0
|
||
self.max_context_tokens: Optional[int] = None
|
||
self.default_context_window: Optional[int] = None
|
||
self.thinking_mode = thinking_mode # True=思考模式(每次请求都用思考配置), False=快速模式
|
||
self.web_mode = web_mode # Web模式标志,用于禁用print输出
|
||
# 兼容旧代码路径
|
||
self.api_base_url = self.fast_api_config["base_url"]
|
||
self.api_key = self.fast_api_config["api_key"]
|
||
self.model_id = self.fast_api_config["model_id"]
|
||
self.model_key = None # 由宿主终端注入,便于做模型兼容处理
|
||
self.model_multimodal = "none" # 由模型 profile 注入,model_key 不可用时用于能力兜底
|
||
self.project_path: Optional[str] = None
|
||
# 推理强度(reasoning effort):None=默认(不传参);由会话级设置注入
|
||
self.reasoning_effort: Optional[str] = None
|
||
self.supports_reasoning_effort = False # 当前模型是否支持推理强度,由 apply_profile 注入
|
||
# 最近一次API错误详情
|
||
self.last_error_info: Optional[Dict[str, Any]] = None
|
||
# 附加请求头解析器:每次发请求时以 base_url 为参数回调,返回需合并的
|
||
# 额外请求头(如 x-opencode-session)。由宿主终端/子智能体按业务注入,
|
||
# 默认 None 不附加任何额外头。
|
||
self.extra_headers_resolver: Optional[Callable[[Optional[str]], Dict[str, str]]] = None
|
||
# 请求体落盘目录(跟随 LOGS_DIR,默认 ~/.astrion/<mode>/logs/api_requests)
|
||
self.request_dump_dir = Path(LOGS_DIR) / "api_requests"
|
||
self.debug_log_path = Path(LOGS_DIR) / "api_debug.log"
|
||
|
||
def _resolve_env_value(self, name: str) -> Optional[str]:
|
||
"""从环境变量获取配置(已由 config/__init__.py 统一注入)。"""
|
||
value = os.environ.get(name)
|
||
if value is None:
|
||
return None
|
||
value = value.strip()
|
||
return value or None
|
||
|
||
def _print(self, message: str, end: str = "\n", flush: bool = False):
|
||
"""安全的打印函数,在Web模式下不输出"""
|
||
if not self.web_mode:
|
||
print(message, end=end, flush=flush)
|