feat: OpenMesh 基础平台与 MD/PDF 转换技能
- 后端: coworker 智能体框架, WS API, 文件上传, 附件处理 - 前端: Open WebUI, 文件全量走 upload API (含 MD/TXT/JSON 等文本类) - 技能: md-to-office (pandoc + wkhtmltopdf) - 修复: 上传文件路径丢失, Agent 搜索浪费, 输出文件跑到 uploads/ - 打包: PyInstaller one-dir, 预打包 pandoc/wkhtmltopdf/chromium
This commit is contained in:
87
coworker/tools/todo.py
Normal file
87
coworker/tools/todo.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Todo / plan tool — a structured task list the agent maintains and the UI renders.
|
||||
|
||||
Most of the "organized agent" feel in interactive work. Low risk, auto-approved. The list
|
||||
is held in a `TodoList` the surface can read; `todo_write` replaces it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import aisuite as ai
|
||||
|
||||
_STATUSES = {"pending", "in_progress", "done"}
|
||||
|
||||
# Explicit schema — the array-of-objects shape can't be auto-generated reliably, and
|
||||
# providers reject a bare `list` annotation. Registered via `__coworker_schema__`.
|
||||
#
|
||||
# The parameter is `todos`, NOT `items`: a top-level argument key named "items" shadows
|
||||
# minijinja's `.items()` map method in at least one hosted chat template (Together's
|
||||
# GLM-5.2, 2026-07-21 — "object is not callable"), 400-ing every request that replays
|
||||
# the call. Any key name that isn't a minijinja map method is safe; never rename back.
|
||||
_TODO_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "todo_write",
|
||||
"description": "Replace the task list. Provide the full list of todos each call.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string"},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "in_progress", "done"],
|
||||
},
|
||||
},
|
||||
"required": ["content", "status"],
|
||||
},
|
||||
}
|
||||
},
|
||||
"required": ["todos"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TodoList:
|
||||
items: list[dict] = field(default_factory=list)
|
||||
|
||||
|
||||
def todo_tools(todo: TodoList) -> list:
|
||||
def todo_write(todos: list = None, items: list = None) -> dict:
|
||||
"""Replace the task list. Each todo is an object with `content` and a `status`
|
||||
of pending, in_progress, or done."""
|
||||
# `items` stays accepted (models that free-style the old name; queued replays).
|
||||
normalized = []
|
||||
for entry in (todos if todos is not None else items) or []:
|
||||
if isinstance(entry, dict):
|
||||
status = entry.get("status", "pending")
|
||||
if status == "completed": # common model alias for our "done"
|
||||
status = "done"
|
||||
normalized.append(
|
||||
{
|
||||
"content": str(entry.get("content", "")),
|
||||
"status": status if status in _STATUSES else "pending",
|
||||
}
|
||||
)
|
||||
else:
|
||||
normalized.append({"content": str(entry), "status": "pending"})
|
||||
todo.items = normalized
|
||||
return {"count": len(normalized), "todos": normalized}
|
||||
|
||||
wrapped = ai.tool(
|
||||
todo_write,
|
||||
metadata=ai.ToolMetadata(
|
||||
category="planning",
|
||||
risk_level="low",
|
||||
capabilities=["todo"],
|
||||
),
|
||||
)
|
||||
wrapped.__coworker_schema__ = _TODO_SCHEMA
|
||||
return [wrapped]
|
||||
Reference in New Issue
Block a user