mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-08 23:33:16 +08:00
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Callable, Optional
|
|
|
|
from oclaw.runtime.chat.tool_runtime import (
|
|
ToolExecutionConfig,
|
|
ToolExecutionContext,
|
|
ToolExecutor,
|
|
)
|
|
from oclaw.runtime.orchestration.trace import new_span_id
|
|
from oclaw.platform.llm.chat_models import LLMToolCall
|
|
from oclaw.runtime.tools.base import ToolRegistry
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SkillExecutionContext:
|
|
store: Any
|
|
tools: ToolRegistry
|
|
session_id: str
|
|
lang: str = "zh"
|
|
user_text: str = ""
|
|
specialist: str = "oclaw"
|
|
trace_id: str | None = None
|
|
parent_span_id: str | None = None
|
|
workspace_owner_session_id: str | None = None
|
|
path_policy_tenant_id: str | None = None
|
|
path_policy_user_id: str | None = None
|
|
run_id: str | None = None
|
|
attempt_no: int | None = None
|
|
turn_uuid: str | None = None
|
|
|
|
|
|
class SkillExecutor:
|
|
"""Skill-oriented execution bridge.
|
|
|
|
Phase-1 delegates execution to ToolExecutor while emitting skill_ui events.
|
|
"""
|
|
|
|
def __init__(self, *, config: ToolExecutionConfig | None = None):
|
|
self._tool_exec = ToolExecutor(config=config or ToolExecutionConfig())
|
|
|
|
@staticmethod
|
|
def _trace(ctx: SkillExecutionContext, *, event_type: str, payload: dict[str, Any]) -> None:
|
|
if not str(ctx.trace_id or "").strip():
|
|
return
|
|
try:
|
|
ctx.store.add_trace_event(
|
|
session_id=ctx.session_id,
|
|
trace_id=str(ctx.trace_id),
|
|
span_id=new_span_id(),
|
|
parent_span_id=ctx.parent_span_id,
|
|
event_type=str(event_type),
|
|
payload={
|
|
"pipeline": "oclaw_skill_executor",
|
|
"trace_id": str(ctx.trace_id or ""),
|
|
"run_id": str(ctx.run_id or ""),
|
|
"attempt_no": int(ctx.attempt_no) if ctx.attempt_no is not None else None,
|
|
**dict(payload or {}),
|
|
},
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
def execute_skill_uses(
|
|
self,
|
|
*,
|
|
ctx: SkillExecutionContext,
|
|
assistant_msg_id: int,
|
|
skill_uses: list[LLMToolCall],
|
|
on_tool_ui: Optional[Callable[[str, dict[str, Any]], None]] = None,
|
|
on_skill_ui: Optional[Callable[[str, dict[str, Any]], None]] = None,
|
|
should_stop: Optional[Callable[[], bool]] = None,
|
|
signature_budget: int = 2,
|
|
) -> tuple[list[dict[str, Any]], dict[str, tuple[dict[str, Any], int]]]:
|
|
for su in skill_uses or []:
|
|
self._trace(
|
|
ctx,
|
|
event_type="skill_selected",
|
|
payload={
|
|
"skill_name": str(getattr(su, "name", "") or ""),
|
|
"skill_call_id": str(getattr(su, "id", "") or ""),
|
|
},
|
|
)
|
|
|
|
def _emit(event: str, payload: dict[str, Any]) -> None:
|
|
if on_tool_ui:
|
|
on_tool_ui(event, payload)
|
|
if on_skill_ui and on_skill_ui is not on_tool_ui:
|
|
ev = str(event or "")
|
|
if ev.startswith("tool_"):
|
|
ev = "skill_" + ev[len("tool_") :]
|
|
on_skill_ui(ev, payload)
|
|
|
|
tool_messages, results_by_id = self._tool_exec.execute_tool_uses(
|
|
ctx=ToolExecutionContext(
|
|
store=ctx.store,
|
|
tools=ctx.tools,
|
|
session_id=ctx.session_id,
|
|
lang=ctx.lang,
|
|
user_text=ctx.user_text,
|
|
specialist=ctx.specialist,
|
|
task_kind="turn",
|
|
policy_engine=None,
|
|
trace_id=ctx.trace_id,
|
|
parent_span_id=ctx.parent_span_id,
|
|
workspace_owner_session_id=ctx.workspace_owner_session_id,
|
|
path_policy_tenant_id=ctx.path_policy_tenant_id,
|
|
path_policy_user_id=ctx.path_policy_user_id,
|
|
turn_uuid=ctx.turn_uuid,
|
|
),
|
|
assistant_msg_id=assistant_msg_id,
|
|
tool_uses=skill_uses,
|
|
on_tool_ui=_emit,
|
|
should_stop=should_stop,
|
|
signature_budget=signature_budget,
|
|
)
|
|
for su in skill_uses or []:
|
|
result, dur = results_by_id.get(str(getattr(su, "id", "") or ""), ({}, 0))
|
|
self._trace(
|
|
ctx,
|
|
event_type="skill_executed",
|
|
payload={
|
|
"skill_name": str(getattr(su, "name", "") or ""),
|
|
"skill_call_id": str(getattr(su, "id", "") or ""),
|
|
"ok": bool((result or {}).get("ok")) if isinstance(result, dict) else None,
|
|
"duration_ms": int(dur or 0),
|
|
"error_code": str((result or {}).get("error_code") or "") if isinstance(result, dict) else "",
|
|
},
|
|
)
|
|
return tool_messages, results_by_id
|
|
|
|
|
|
__all__ = ["SkillExecutionContext", "SkillExecutor"]
|
|
|