Initial project import

This commit is contained in:
XM
2026-09-18 03:46:58 +08:00
commit b600da4b76
209 changed files with 45904 additions and 0 deletions
+307
View File
@@ -0,0 +1,307 @@
from __future__ import annotations
import html
import json
import re
import time
import uuid
from collections.abc import Callable, Iterable, Iterator
from dataclasses import dataclass
from typing import Any
from services.account_service import account_service
from services.openai_backend_api import OpenAIBackendAPI
from services.protocol.conversation import count_message_tokens, count_text_tokens, normalize_messages
from services.protocol.openai_v1_chat_complete import collect_chat_content, stream_text_chat_completion
XML_TOOL_RULE = """Tool output adapter: when calling tools, output ONLY this XML and no prose/markdown:
<tool_calls><tool_call><tool_name>TOOL_NAME</tool_name><parameters><PARAM><![CDATA[value]]></PARAM></parameters></tool_call></tool_calls>"""
@dataclass
class MessageRequest:
backend: OpenAIBackendAPI
messages: list[dict[str, Any]]
model: str
tools: Any = None
def _tool_meta(tool: dict[str, object]) -> tuple[str, str, object]:
fn = tool.get("function") if isinstance(tool.get("function"), dict) else {}
name = str(tool.get("name") or fn.get("name") or "").strip()
desc = str(tool.get("description") or fn.get("description") or "").strip()
schema = tool.get("input_schema") or tool.get("parameters") or fn.get("input_schema") or fn.get("parameters") or {}
return name, desc, schema
def build_tool_prompt(tools: object) -> str:
if not isinstance(tools, list):
return ""
blocks = []
for tool in tools:
if not isinstance(tool, dict):
continue
name, desc, schema = _tool_meta(tool)
if name:
blocks.append(f"Tool: {name}\nDescription: {desc}\nParameters: {json.dumps(schema, ensure_ascii=False)}")
if not blocks:
return ""
return "Available tools:\n" + "\n".join(blocks) + """
Tool use rules:
- If the user asks to list/read/search files, inspect project state, run a command, or answer from local code, you MUST call a suitable tool first. Do not say you cannot access files.
- To call tools, output ONLY XML and no prose/markdown:
<tool_calls><tool_call><tool_name>TOOL_NAME</tool_name><parameters><PARAM><![CDATA[value]]></PARAM></parameters></tool_call></tool_calls>
- Put parameters under <parameters> using the exact schema names.
""".strip()
def merge_system(system: object, extra: str) -> object:
system = compact_system(system)
if _has_claude_code_system(system):
extra = XML_TOOL_RULE
if not extra:
return system
if isinstance(system, str) and system.strip():
return f"{system.strip()}\n\n{extra}"
if isinstance(system, list):
return [*system, {"type": "text", "text": extra}]
return extra
def _has_claude_code_system(system: object) -> bool:
if isinstance(system, str):
return "You are Claude Code" in system
if isinstance(system, list):
return any(isinstance(item, dict) and "You are Claude Code" in str(item.get("text") or "") for item in system)
return False
def compact_system(system: object) -> object:
if isinstance(system, str):
return _compact_system_text(system)
if isinstance(system, list):
result = []
for item in system:
if isinstance(item, dict) and str(item.get("type") or "") == "text":
copied = dict(item)
copied["text"] = _compact_system_text(str(item.get("text") or ""))
result.append(copied)
else:
result.append(item)
return result
return system
def _compact_system_text(text: str) -> str:
return text or ""
def _compact_message_text(text: str) -> str:
return text or ""
def preprocess_payload(payload: dict[str, object], text_mapper: Callable[[str], str] | None = None) -> dict[str, object]:
payload["messages"] = preprocess_messages(payload.get("messages"), text_mapper)
payload["system"] = merge_system(payload.get("system"), build_tool_prompt(payload.get("tools")))
return payload
def message_request(body: dict[str, Any]) -> MessageRequest:
payload = preprocess_payload(dict(body))
model = str(payload.get("model") or "auto").strip() or "auto"
return MessageRequest(
backend=OpenAIBackendAPI(access_token=account_service.get_text_access_token(model=model)),
messages=normalize_messages(payload.get("messages"), payload.get("system")),
model=model,
tools=payload.get("tools"),
)
def preprocess_messages(messages: object, text_mapper: Callable[[str], str] | None = None) -> object:
if not isinstance(messages, list):
return messages
mapper = text_mapper or (lambda text: text)
result = []
for message in messages:
if not isinstance(message, dict):
continue
item = dict(message)
content = item.get("content")
if isinstance(content, str):
item["content"] = _compact_message_text(mapper(content))
elif isinstance(content, list):
item["content"] = [_preprocess_block(block, mapper) for block in content]
result.append(item)
return result
def _preprocess_block(block: object, text_mapper: Callable[[str], str]) -> object:
if not isinstance(block, dict):
return block
block_type = str(block.get("type") or "")
if block_type == "text":
item = dict(block)
item["text"] = _compact_message_text(text_mapper(str(block.get("text") or "")))
return item
if block_type == "tool_use":
return {"type": "text", "text": f"<tool_calls><tool_call><tool_name>{block.get('name') or ''}</tool_name><parameters>{json.dumps(block.get('input') or {}, ensure_ascii=False)}</parameters></tool_call></tool_calls>"}
if block_type == "tool_result":
return {"type": "text", "text": f"Tool result {block.get('tool_use_id') or ''}: {block.get('content') or ''}"}
return block
def message_response(model: str, text: str, input_tokens: int, output_tokens: int, tools: object = None) -> dict[str, object]:
content, stop_reason = content_blocks(text, tools)
return {
"id": f"msg_{uuid.uuid4()}",
"type": "message",
"role": "assistant",
"model": model,
"content": content,
"stop_reason": stop_reason,
"stop_sequence": None,
"usage": {"input_tokens": input_tokens, "output_tokens": output_tokens},
}
def content_blocks(text: str, tools: object = None) -> tuple[list[dict[str, object]], str]:
calls = parse_tool_calls(text) if isinstance(tools, list) and tools else []
text = strip_tool_markup(text)
if calls:
content = ([{"type": "text", "text": text}] if text else []) + [{"type": "tool_use", "id": f"toolu_{uuid.uuid4()}", "name": name, "input": args} for name, args in calls]
return content, "tool_use"
return [{"type": "text", "text": text}], "end_turn"
def strip_tool_markup(text: str) -> str:
return re.sub(r"(?is)<tool_calls\b[^>]*>.*?</tool_calls>|<tool_call\b[^>]*>.*?</tool_call>|<function_call\b[^>]*>.*?</function_call>|<invoke\b[^>]*>.*?</invoke>", "", text or "").strip()
def streamable_text(text: str) -> str:
text = text or ""
match = re.search(r"(?is)<tool_calls\b|<tool_call\b|<function_call\b|<invoke\b", text)
return text[:match.start()].rstrip() if match else text
def parse_tool_calls(text: str) -> list[tuple[str, dict[str, object]]]:
text = re.sub(r"(?is)```.*?```", "", text or "").strip()
blocks = re.findall(r"(?is)<tool_call\b[^>]*>(.*?)</tool_call>|<function_call\b[^>]*>(.*?)</function_call>|<invoke\b[^>]*>(.*?)</invoke>", text)
result = []
for block in (next((part for part in match if part), "") for match in blocks):
name = xml_value(block, "tool_name") or xml_value(block, "name") or xml_value(block, "function")
params = xml_value(block, "parameters") or xml_value(block, "input") or xml_value(block, "arguments") or "{}"
if name:
result.append((name, parse_tool_params(params)))
return result
def xml_value(text: str, tag: str) -> str:
match = re.search(rf"(?is)<{tag}\b[^>]*>(.*?)</{tag}>", text)
if not match:
return ""
value = match.group(1).strip()
cdata = re.fullmatch(r"(?is)<!\[CDATA\[(.*?)]]>", value)
return html.unescape(cdata.group(1) if cdata else value).strip()
def parse_tool_params(raw: str) -> dict[str, object]:
raw = raw.strip()
try:
parsed = json.loads(raw)
return parsed if isinstance(parsed, dict) else {}
except Exception:
return {m.group(1): parse_tool_value(m.group(2)) for m in re.finditer(r"(?is)<([\w.-]+)\b[^>]*>(.*?)</\1>", raw)}
def parse_tool_value(raw: str) -> object:
value = xml_value(f"<x>{raw}</x>", "x")
try:
return json.loads(value)
except Exception:
return value
def stream_events(chunks: Iterable[dict[str, object]], model: str, input_tokens: int, output_tokens: Callable[[str], int], tools: object = None) -> Iterator[dict[str, object]]:
message_id = f"msg_{uuid.uuid4()}"
created = int(time.time())
current_text = ""
streamed_text = ""
tool_mode = isinstance(tools, list) and bool(tools)
tool_started = False
text_open = False
yield {"type": "message_start", "message": {"id": message_id, "type": "message", "role": "assistant", "model": model, "content": [], "stop_reason": None, "stop_sequence": None, "usage": {"input_tokens": input_tokens, "output_tokens": 0}}}
if not tool_mode:
text_open = True
yield {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
for chunk in chunks:
choice = (chunk.get("choices") or [{}])[0]
delta = choice.get("delta") or {}
text_delta = delta.get("content", "") if isinstance(delta, dict) else ""
if text_delta:
current_text += text_delta
if not tool_started:
visible_text = current_text if not tool_mode else streamable_text(current_text)
if visible_text.startswith(streamed_text):
text_delta = visible_text[len(streamed_text):]
if text_delta:
if not text_open:
text_open = True
yield {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
streamed_text = visible_text
yield {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": text_delta}}
tool_started = tool_mode and visible_text != current_text
if choice.get("finish_reason"):
content, stop_reason = content_blocks(current_text, tools)
if text_open:
yield {"type": "content_block_stop", "index": 0}
if stop_reason == "tool_use":
start_index = 1 if text_open else 0
if content and content[0]["type"] == "text":
remaining = str(content[0].get("text") or "")[len(streamed_text):]
if remaining:
if not text_open:
yield {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
yield {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": remaining}}
if not text_open:
yield {"type": "content_block_stop", "index": 0}
start_index = 1
content = content[1:]
yield from _stream_buffered_blocks(content, start_index)
yield {"type": "message_delta", "delta": {"stop_reason": stop_reason, "stop_sequence": None}, "usage": {"output_tokens": output_tokens(current_text)}}
break
yield {"type": "message_stop", "created": created}
def _stream_buffered_blocks(content: list[dict[str, object]], start_index: int = 0) -> Iterator[dict[str, object]]:
for offset, block in enumerate(content):
index = start_index + offset
if block["type"] == "tool_use":
start = {"type": "tool_use", "id": block["id"], "name": block["name"], "input": {}}
delta = {"type": "input_json_delta", "partial_json": json.dumps(block.get("input") or {}, ensure_ascii=False)}
else:
start = {"type": "text", "text": ""}
delta = {"type": "text_delta", "text": block.get("text") or ""}
yield {"type": "content_block_start", "index": index, "content_block": start}
yield {"type": "content_block_delta", "index": index, "delta": delta}
yield {"type": "content_block_stop", "index": index}
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
request = message_request(body)
if body.get("stream"):
return stream_events(
stream_text_chat_completion(request.backend, request.messages, request.model),
request.model,
count_message_tokens(request.messages, request.model),
lambda text: count_text_tokens(text, request.model),
request.tools,
)
text = collect_chat_content(stream_text_chat_completion(request.backend, request.messages, request.model))
return message_response(
request.model,
text,
count_message_tokens(request.messages, request.model),
count_text_tokens(text, request.model),
request.tools,
)