api2cursor/adapters/helpers.py
2026-03-22 08:24:19 +08:00

155 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""适配器公共辅助函数
收敛多个适配器都在重复实现的 CC 格式构建逻辑:
- CC 消息/Usage/Tool Call/Stream Chunk 的标准构造
- 内容扁平化、JSON 安全解析、工具输出序列化
"""
from __future__ import annotations
import json
from typing import Any
from utils.http import gen_id
JsonDict = dict[str, Any]
# ═══════════════════════════════════════════════════════════
# CC 格式标准构造
# ═══════════════════════════════════════════════════════════
def build_cc_message(
content_text: str,
reasoning_text: str = '',
tool_calls: list[JsonDict] | None = None,
) -> JsonDict:
"""构造标准的 CC assistant 消息。"""
message: JsonDict = {
'role': 'assistant',
'content': content_text or None,
}
if reasoning_text:
message['reasoning_content'] = reasoning_text
if tool_calls:
message['tool_calls'] = tool_calls
return message
def build_cc_usage(input_tokens: int, output_tokens: int) -> JsonDict:
"""构造标准的 CC usage 字典。"""
return {
'prompt_tokens': input_tokens,
'completion_tokens': output_tokens,
'total_tokens': input_tokens + output_tokens,
}
def build_cc_tool_call(
call_id: str,
name: str,
arguments: str,
*,
index: int | None = None,
) -> JsonDict:
"""构造标准的 CC tool_call 结构。"""
tc: JsonDict = {
'id': call_id or gen_id('call_'),
'type': 'function',
'function': {
'name': name,
'arguments': arguments,
},
}
if index is not None:
tc['index'] = index
return tc
def make_cc_chunk(
chunk_id: str,
delta: JsonDict,
finish_reason: str | None = None,
model: str = '',
) -> JsonDict:
"""构造标准的 CC 流式 chunk。"""
choice: JsonDict = {'index': 0, 'delta': delta}
if finish_reason:
choice['finish_reason'] = finish_reason
return {
'id': chunk_id,
'object': 'chat.completion.chunk',
'model': model,
'choices': [choice],
}
def build_cc_response(
response_id: str,
message: JsonDict,
finish_reason: str,
usage: JsonDict,
model: str = '',
) -> JsonDict:
"""构造标准的 CC 非流式响应。"""
return {
'id': response_id,
'object': 'chat.completion',
'model': model,
'choices': [{
'index': 0,
'message': message,
'finish_reason': finish_reason,
}],
'usage': usage,
}
# ═══════════════════════════════════════════════════════════
# 通用文本/JSON 处理
# ═══════════════════════════════════════════════════════════
def extract_text(content: Any) -> str:
"""从多种内容格式中提取并拼接纯文本。
支持字符串、内容块列表OpenAI/Anthropic/Responses 风格)。
"""
if isinstance(content, str):
return content
if not isinstance(content, list):
return str(content) if content is not None else ''
parts: list[str] = []
for part in content:
if isinstance(part, str):
parts.append(part)
elif isinstance(part, dict):
part_type = part.get('type', '')
if part_type in ('text', 'output_text', 'input_text'):
parts.append(part.get('text', ''))
elif part_type == 'refusal':
parts.append(part.get('refusal', ''))
elif 'text' in part and not part_type:
parts.append(part['text'])
return '\n'.join(parts) if parts else ''
def parse_json_safe(text: Any, fallback: Any = None) -> Any:
"""安全解析 JSON失败时返回 fallback。"""
if not isinstance(text, str):
return text if text is not None else (fallback if fallback is not None else {})
try:
return json.loads(text)
except (json.JSONDecodeError, ValueError):
return fallback if fallback is not None else {}
def stringify_content(content: Any) -> str:
"""将任意内容序列化为字符串。"""
if isinstance(content, str):
return content
if content is None:
return ''
return json.dumps(content, ensure_ascii=False)