F0: establish task backend input and native job spine
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
# 提示词助手与任务执行
|
||||
|
||||
本术语表约定提示词助手的对话职责、任务执行职责及其能力边界。
|
||||
|
||||
## Language
|
||||
|
||||
**对话模型(Speaker)**:与用户交流的模型,负责理解需求、澄清信息及表达任务进度和结果。
|
||||
_Avoid_: 任务后端、执行器
|
||||
|
||||
**任务后端(Task Backend)**:接受委派任务并组织执行的能力主体,可由内置推理模型或外部 Coding Agent 承担。
|
||||
_Avoid_: OpenCode 工具、后台工具
|
||||
|
||||
**任务(Task)**:具有明确目标、独立进度和执行结果的一次委派工作。
|
||||
_Avoid_: 对话、工具调用、后端连接
|
||||
|
||||
**内置推理后端(Reasoner)**:由平台管理推理过程、任务上下文及可用工具的任务后端。
|
||||
_Avoid_: 对话模型的高强度模式
|
||||
|
||||
**外部编码代理(Coding Agent)**:在外部执行环境中管理推理、工具和项目工作的任务后端;OpenCode 是其中一种实现。
|
||||
_Avoid_: 普通 HTTP 工具
|
||||
|
||||
**工具(Tool)**:可供模型调用的一项具体操作,其可用范围由该模型承担的职责及授权决定。
|
||||
_Avoid_: 任务后端、技能
|
||||
|
||||
**技能(Skill)**:供任务后端加载的可复用任务说明和工作方法,使用其中的操作仍需相应执行权限。
|
||||
_Avoid_: 权限、沙箱
|
||||
|
||||
**执行策略(Execution Policy)**:一次任务适用的能力许可、执行环境、时间限制、并发约束和取消规则。
|
||||
_Avoid_: 连接凭据、任务提示词
|
||||
@@ -44,7 +44,7 @@ class TaskService:
|
||||
self._listeners.clear()
|
||||
self._jobs.clear()
|
||||
|
||||
async def submit(self, *, assistant_id, conversation_id, tool, instruction, invocation_id):
|
||||
async def submit(self, *, assistant_id, conversation_id, instruction, invocation_id, backend=None, tool=None):
|
||||
if not self.runtime:
|
||||
raise ValueError("后台任务服务尚未启动")
|
||||
if not assistant_id or not conversation_id:
|
||||
@@ -52,15 +52,25 @@ class TaskService:
|
||||
text = instruction.strip()
|
||||
if not text or len(text) > 12000:
|
||||
raise ValueError("任务指令必须为 1-12000 个字符")
|
||||
if tool.type != "task":
|
||||
raise ValueError("请选择后台任务工具")
|
||||
if backend is not None:
|
||||
if backend.type != "opencode":
|
||||
raise ValueError("该任务后端尚未接通")
|
||||
backend_id = backend.id
|
||||
connection = deepcopy(backend.config)
|
||||
connection["_backend_type"] = backend.type
|
||||
secrets = deepcopy(backend.secrets)
|
||||
else:
|
||||
# F0 transition: retired when saved Tool bindings migrate in F1.
|
||||
if tool is None or tool.type != "task":
|
||||
raise ValueError("请选择任务后端")
|
||||
backend_id, connection, secrets = tool.id, deepcopy(tool.definition["config"]), deepcopy(tool.secrets)
|
||||
async with self._submit_lock:
|
||||
record = await self.repository.create(TaskRecord(
|
||||
id="task_" + uuid4().hex,
|
||||
assistant_id=assistant_id, conversation_id=conversation_id,
|
||||
tool_id=tool.id, instruction=text,
|
||||
tool_id=backend_id, instruction=text,
|
||||
origin_key=f"{conversation_id}:{invocation_id}"[:256],
|
||||
connection=deepcopy(tool.definition["config"]), secrets=deepcopy(tool.secrets),
|
||||
connection=connection, secrets=secrets,
|
||||
))
|
||||
if not record.job_id and record.status == "queued":
|
||||
await self._dispatch(record)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""F0: explicit simulated backend, real application-owned Pipecat Jobs."""
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
|
||||
from services.background_tasks.service import TaskService
|
||||
from tests.test_background_tasks import MemoryRepository, SimulatedExecutor
|
||||
|
||||
|
||||
class TaskBackendSpineTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_backend_resource_reaches_native_job_without_tool_definition(self):
|
||||
SimulatedExecutor.release = asyncio.Event()
|
||||
SimulatedExecutor.confirms_stop = True
|
||||
service = TaskService(MemoryRepository(), SimulatedExecutor)
|
||||
backend = SimpleNamespace(id="tb_smoke", type="opencode", config={"url": "https://simulated.test", "directory": "/demo", "timeout_seconds": 30}, secrets={})
|
||||
await service.start()
|
||||
try:
|
||||
task = await service.submit(assistant_id="asst_smoke", conversation_id="conv_smoke", backend=backend, instruction="SIMULATED task", invocation_id="smoke")
|
||||
SimulatedExecutor.release.set()
|
||||
async with asyncio.timeout(3):
|
||||
while (await service.get(task["id"])).status != "succeeded":
|
||||
await asyncio.sleep(.005)
|
||||
record = await service.get(task["id"])
|
||||
self.assertEqual(record.connection["_backend_type"], "opencode")
|
||||
self.assertTrue(record.job_id)
|
||||
artifact = Path(__file__).resolve().parents[2] / "implement-stage/task-backends/backend-spine-smoke.json"
|
||||
artifact.write_text(json.dumps({"simulated_remote": True, "native_job": "completed", "backend_id": backend.id, "tool_definition_required": False}) + "\n")
|
||||
finally:
|
||||
await service.close()
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: 提示词助手的对话模型与任务后端
|
||||
description: 将任务后端从普通工具资源中迁出的适配草案。
|
||||
---
|
||||
|
||||
# 提示词助手的对话模型与任务后端
|
||||
|
||||
状态:设计草案。当前已实现版本仍通过 task 工具资源连接 OpenCode;本文描述下一版迁移目标,不表示新配置或内置 Reasoner 已可使用。
|
||||
|
||||
## 范围与职责
|
||||
|
||||
仅提示词 Pipeline。一个助手配置一个 Speaker,可选一个任务后端:内置 Reasoner 或外部 OpenCode。外部 Coding Agent 首版只接 OpenCode。
|
||||
|
||||
Speaker 使用现有 ASR → 对话 LLM → TTS 链路,负责交流、澄清、委派与播报。任务后端有独立上下文和执行策略。简单对话由 Speaker 完成,需要较强推理或执行能力时委派任务;任务执行期间 Speaker 继续响应用户。
|
||||
|
||||
```text
|
||||
用户 ⇄ ASR / Speaker / TTS
|
||||
│ 委派、查询、补充输入、取消
|
||||
▼
|
||||
TaskService + 应用级 Coordinator
|
||||
│ Pipecat 原生 Jobs
|
||||
┌───────┴────────┐
|
||||
▼ ▼
|
||||
内置 Reasoner OpenCode 后端
|
||||
BackendLLMWorker HTTP / SSE 适配
|
||||
独立 LLMContext 远端 session
|
||||
│ │
|
||||
平台 ToolExecutor 自己的工具、技能与环境
|
||||
└───────┬────────┘
|
||||
▼
|
||||
任务事件、结果、待回答问题
|
||||
▼
|
||||
数据库存储 / 网页 / Speaker
|
||||
```
|
||||
|
||||
产品配置使用「任务后端」资源。模型仍可通过内建 function calling 发起委派,这是模型与平台的通信方式;管理员不再创建 OpenCode Tool、不填写委派函数名,也不把它放入普通工具绑定列表。
|
||||
|
||||
## 资源与配置
|
||||
|
||||
新增 `TaskBackendResource`,与模型资源、工具资源并列。首版资源类型为 `reasoner | opencode`,用明确的配置分支,无需通用插件系统。
|
||||
|
||||
| 配置位置 | 内置 Reasoner | OpenCode |
|
||||
| --- | --- | --- |
|
||||
| 后端资源 | 名称、说明、启用状态 | 名称、说明、启用状态 |
|
||||
| 连接与模型 | 引用现有 LLM ModelResource;独立模型凭据由资源解析 | 服务地址、服务鉴权、工作目录、远端 agent;可验证支持时选择远端模型/variant |
|
||||
| 推理配置 | 该模型支持的推理强度或预算 | 采用远端模型/agent 的配置及能力 |
|
||||
| 执行策略 | 平台允许的服务端工具、任务时限、取消规则 | 任务时限、取消规则;权限及沙箱以远端实际环境为准 |
|
||||
| 技能 | 平台管理并向该 Reasoner 加载的任务说明 | 远端已安装的原生技能及 agent 配置 |
|
||||
|
||||
助手新增 `taskBackend` 配置,包含 `backendId`、该助手的任务指令、执行策略和技能选择;连接信息留在可复用的后端资源内。第一版不增加第三个模型角色或多后端路由。
|
||||
|
||||
Speaker 继续引用现有 LLM 模型绑定。Reasoner 引用独立的 LLM 资源,即使选中同一个模型资源,也必须创建独立的模型服务实例、上下文和运行配置,避免推理强度、工具或对话历史互相影响。
|
||||
|
||||
推理强度根据模型实际能力显示:例如模型可能支持 low/medium/high,也可能仅支持 thinking budget。配置由 provider 适配到真实参数,不静默忽略不支持的选项。当前模型工厂主要支持 OpenAI-compatible 接口;未支持的 provider 应明确拒绝,不能仅增加下拉选项。
|
||||
|
||||
## Pipecat 复用与任务生命周期
|
||||
|
||||
当前安装的 Pipecat 1.13.0 包含 `BackendLLMWorker`,管理后台 LLM 上下文、多步工具调用和输出;`LLMContextWorker` 封装上下文聚合器。内置 Reasoner 应优先使用这些组件,工具执行桥接现有 ToolExecutor,不另写推理循环。
|
||||
|
||||
`LLMWithBackend` / `BackendConnector` 已提供前台委派和后台消息传送语义,可复用其公开能力;`LLMWithBackend` 当前为 alpha,且默认会把 attach 会话绑定到媒体 Pipeline 生命周期。直接替换当前 LLM 槽会改变断线行为,必须保留应用级拥有者。
|
||||
|
||||
应用级 Coordinator 拥有后端 attach/执行 Jobs,媒体 Pipeline 只订阅或解除订阅。内置 Reasoner 按任务创建独立 worker/context,任务后端资源只复用配置,不共享所有助手的任务历史。取消一个任务只能影响该任务。
|
||||
|
||||
Pipecat 继续负责 Worker/Job 派发、串行执行、超时和取消。TaskService 负责业务任务 ID、访问归属、后端选择、配置快照、记录与事件投影。OpenCode 适配器继续负责 HTTP/SSE、结果关联、远端 abort 和重新观察。
|
||||
|
||||
统一后端边界可保持很小:执行并发出事件、查询状态、回复待处理输入、请求取消。恢复和运行中补充消息按后端实际能力处理,不要求两种后端假装支持相同能力。
|
||||
|
||||
- 原生 attach/idle、普通模型输出及远端接收回执都不是业务任务成功证明。内置 Reasoner 需通过明确的完成/待输入/失败语义返回结果;平台可注册任务内的完成和提问控制函数,复用原生工具回调,不自行实现推理循环。
|
||||
- 任务提交携带目标及必要上下文。新任务冻结后端配置和策略,模型只能提交任务要求,不能更改地址、凭据、工作目录或授权。
|
||||
- 进度保存在任务记录与网页;Speaker 接收适合用户的结果、问题及必要状态通知,不默认播报推理过程或原始工具日志。
|
||||
- 语音插话保留后台任务。用户明确取消时,取消原生 Job 并核查执行后端;未确认远端停止时仍显示停止待确认。
|
||||
- OpenCode 重启恢复仍使用已保存的 session/message 句柄重新观察,不重发未知提交。内置 Reasoner 首版在应用重启后标记中断并保留已存结果,不自动重放可能有副作用的工具调用;不声称原生 Job 自动持久恢复。
|
||||
- 运行中任意修改目标需要后端能力支持。首版共同提供查询、取消、回答待处理问题;OpenCode 不自动重发目标来模拟实时 steer。
|
||||
|
||||
首版仍固定任务并发为 1,使用原生串行 Job,不开放尚未实现的任意并发配置。已有远端任务状态未确认时,保留当前阻止新编码提交的业务规则;其后可按后端/工作区拆分原生 worker。
|
||||
|
||||
新任务建议持久化任务截止时间:超时覆盖排队和执行,重启后向原生 Job 传入剩余时间。现有历史任务继续保留原有观察时限语义,不追溯修改历史含义。
|
||||
|
||||
## 工具、技能与权限
|
||||
|
||||
Speaker 的工具用于当前对话中的即时操作,例如显示消息和拍照按钮。Reasoner 使用单独配置的服务端 HTTP/MCP 工具白名单;客户端工具依赖浏览器连接,不能直接成为断线后继续执行的后台能力。现有 ToolExecutor 没有通用 Shell/文件执行环境,首版编码工作由 OpenCode 执行。
|
||||
|
||||
技能是工作方法,不能增加执行权限。内置 Reasoner 可先加载平台保存的选中技能文本;实际工具仍按策略校验。OpenCode 优先使用其原生 SKILL.md 发现、加载及权限机制,首版远端技能由管理员部署,平台不承诺自动上传或同步技能文件。
|
||||
|
||||
服务鉴权凭据、模型 API 凭据和执行权限是三个独立概念:OpenCode 服务密码用于访问服务,模型凭据由远端管理,文件/Shell 能力取决于远端 agent 权限和运行账号。
|
||||
|
||||
工作目录不等于沙箱。内置 Reasoner 的工具许可由平台执行桥校验;OpenCode 的文件/Shell/技能限制必须由远端 agent/session 或部署环境实际执行。无法经适配器应用或核查的策略应显示「由远端管理」,不能提供看似生效的授权、沙箱开关。首版不建设容器编排系统,也不为了任务执行而自动授予全部权限。
|
||||
|
||||
## 前端适配
|
||||
|
||||
提示词编辑页保留当前设计系统,增加以下配置区:
|
||||
|
||||
1. 模型与语音:现有 LLM 字段明确为「对话模型 Speaker」,保留 ASR/TTS。
|
||||
2. 任务后端:关闭 / 内置 Reasoner / 外部 OpenCode;选择后端资源,按类型展示连接和模型配置。
|
||||
3. 执行策略:展示能实际执行的策略与后端环境说明;首版并发固定 1。
|
||||
4. 技能:Reasoner 选择平台技能;OpenCode 展示远端技能配置来源与已配置说明。
|
||||
|
||||
资源库新增「任务后端」,工具页移除创建 task 工具的入口。「外部工具」继续管理普通工具,任务配置不再要求管理员填写函数名或 instruction 参数绑定。现有后台任务卡片保留,统一显示两种后端的结果、待输入和取消状态。
|
||||
|
||||
## 当前代码的迁移位置
|
||||
|
||||
| 当前模块 | 调整目标 |
|
||||
| --- | --- |
|
||||
| schemas.py / models.py / db.models | TaskBackendResource、助手 taskBackend 配置、任务 backend_id/type/快照 |
|
||||
| config_resolver.py | 分别解析 Speaker、任务后端、Reasoner 工具及技能;敏感连接参数只在服务端组装 |
|
||||
| background_tasks/service.py | submit 接收解析后的任务后端配置,按类型选择执行后端,移除 RuntimeTool 依赖 |
|
||||
| brains/prompt_tasks.py | 按助手 taskBackend 配置注册平台任务控制入口,使用统一任务状态与通知 |
|
||||
| brains/prompt_brain.py | 根据 taskBackend 启用委派指导,普通工具循环仅处理普通工具 |
|
||||
| background_tasks/native.py | 保留应用级原生 Job 宿主,内置 Reasoner 接入 BackendLLMWorker |
|
||||
| background_tasks/opencode.py | 保留现有协议逻辑,增加经验证可用的 agent/model 选择 |
|
||||
| routes/background_tasks.py | 从绑定后端解析配置,提交不再要求 toolId;保留查询/取消/输入接口 |
|
||||
| 前端工具页 / 提示词编辑器 / api.ts | 新任务后端资源入口、独立配置区、移除 task ToolBinding |
|
||||
|
||||
迁移已有 task 工具时,复制连接与凭据到后端资源,把助手绑定转为 taskBackend 引用。原 instruction 的固定值/变量绑定需明确迁为任务指令预设,不能静默丢弃;无法直接迁移的配置应给出具体提示。历史任务保留原 task_id、结果和冻结连接信息,补后端来源,正在执行的任务继续按原句柄观察。
|
||||
|
||||
建议按三个可验收阶段落地:
|
||||
|
||||
1. 迁出 task 工具资源和助手绑定,保持 OpenCode 执行与历史恢复可用。
|
||||
2. 实现内置 Reasoner 的真实执行路径,复用原生 BackendLLMWorker 和现有服务端工具;前端只展示已实现的能力。
|
||||
3. 补齐策略生效校验、技能加载、迁移回归和两种后端的共同验收。
|
||||
|
||||
验收重点为 Speaker 无需等待任务结束才能继续交流;任务上下文隔离;不支持的推理配置明确拒绝;任务状态未知不重复执行;语音断线不取消任务;明确取消可核查;后台不能执行依赖已断开浏览器的客户端工具;技能不能扩大权限;历史连接与指令配置不丢失。
|
||||
|
||||
## 核查依据
|
||||
|
||||
- 本地已安装 Pipecat 1.13.0 源码:workers/llm/backend_llm_worker.py、llm_context_worker.py、pipeline/llm_with_backend.py。
|
||||
- [Pipecat BackendLLMWorker](https://reference-server.pipecat.ai/en/latest/api/pipecat.workers.llm.backend_llm_worker.html)
|
||||
- [Pipecat LLMWithBackend](https://reference-server.pipecat.ai/en/latest/api/pipecat.pipeline.llm_with_backend.html)
|
||||
- [OpenCode Server](https://opencode.ai/docs/server/)
|
||||
- [OpenCode 权限](https://opencode.ai/docs/permissions/)
|
||||
- [OpenCode 技能](https://opencode.ai/docs/skills/)
|
||||
- [OpenCode 模型配置](https://opencode.ai/docs/models/)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Assumption Ledger — Speaker / Task Backend
|
||||
<!-- ASK mode: never -->
|
||||
|
||||
| ID | Under-determined | Chosen | Class | Source |
|
||||
|----|------------------|--------|-------|--------|
|
||||
| B-001 | 支持范围 | 仅提示词 Pipeline;可关闭后端,或选择 reasoner/opencode;一个助手最多一个任务后端 | semantic | user |
|
||||
| B-002 | 产品资源 | 独立 TaskBackendResource,普通 Tool 仅 HTTP/MCP/client;Speaker 委派由平台内建函数自动注册 | interface | user |
|
||||
| B-003 | 配置形状 | 后端API使用 id/name/type/description/config/secrets/enabled;助手 taskBackend 使用 backendId/instructionSource/instructions;接口 camelCase | interface | default |
|
||||
| B-004 | 策略归属 | 模型、工具白名单、技能和时限配置在可复用后端资源;助手绑定可补充任务指令。提交时冻结完整解析快照 | semantic | default |
|
||||
| B-005 | 内置执行 | 复用 BackendLLMWorker 与现有 ToolExecutor;每个任务独立模型实例、上下文和变量,应用级拥有者发原生 Jobs | semantic | user |
|
||||
| B-006 | Reasoner权限 | 仅资源显式绑定的 HTTP/MCP 服务端工具;拒绝 client/system/task 和循环委派;技能不增加权限 | semantic | default |
|
||||
| B-007 | 推理强度 | provider_default 或 low/medium/high;非默认值要求模型资源显式声明 supportsReasoningEffort,按 reasoning_effort 参数发送,不支持时拒绝 | semantic | default |
|
||||
| B-008 | 技能 | Reasoner 使用后端资源中的命名技能正文;OpenCode 使用远端原生技能/agent,平台仅保存说明,不伪装远程安装或权限隔离 | semantic | default |
|
||||
| B-009 | 外部选择 | OpenCode保留地址/目录/服务鉴权,新增可选 agent 与 modelProviderId/modelId;推理强度及沙箱由远端管理 | interface | default |
|
||||
| B-010 | 执行结果 | Reasoner通过明确完成/提问控制函数返回业务结果;idle、普通文本、接收回执不能直接证明成功 | semantic | default |
|
||||
| B-011 | 生命周期与取消 | 语音断线继续;取消停止当前任务;原生取消不代表外部工具或远端副作用被回滚,需核查远端停止 | semantic | user |
|
||||
| B-012 | 恢复 | OpenCode只观察已有句柄;内置Reasoner重启标记中断,不自动重放工具;历史任务保留旧恢复语义 | semantic | default |
|
||||
| B-013 | 时限与并发 | 新任务保存截止时间,排队和执行均计时;全局原生串行并发1,保留远端状态未确认时阻止新提交策略 | semantic | default |
|
||||
| B-014 | 迁移 | 自动复制旧task工具连接/凭据至后端资源,迁移助手绑定和instruction来源;保留历史task_id、连接快照和旧时限语义 | semantic | default |
|
||||
| B-015 | 自动测试与鉴权 | 沿用管理员鉴权;自动测试不得访问真实后端任务,使用显式模拟;无真实服务地址不声称联调完成 | semantic | default |
|
||||
| B-016 | MVP执行环境 | 内置Reasoner不提供宿主机Shell/文件沙箱;编码由OpenCode处理,界面明确实际权限由执行端配置 | semantic | default |
|
||||
| B-017 | F0过渡 | Walking skeleton短暂兼容旧tool提交入口以保持累计检查;迁移阶段移除该过渡入口,不保留为新产品接口 | interface | default |
|
||||
| B-018 | 回退迁移 | 0027为数据语义迁移,downgrade明确拒绝,需备份恢复;不能把新的Reasoner配置静默回退成旧task工具 | semantic | default |
|
||||
@@ -0,0 +1,19 @@
|
||||
# Build Note — Speaker / Task Backend
|
||||
|
||||
| Rung | Feature | Acceptance command | Tier | Status |
|
||||
|------|---------|--------------------|------|--------|
|
||||
| F0 | 新后端输入→原生Job→记录主线 | cd backend && .venv/bin/python -m unittest tests.test_task_backend_spine | MUST | passed |
|
||||
| F1 | 后端资源、助手配置、旧数据迁移 | cd backend && .venv/bin/python -m unittest tests.test_task_backend_resources | MUST | pending |
|
||||
| F2 | 原生Reasoner、OpenCode、委派桥 | cd backend && .venv/bin/python -m unittest tests.test_reasoner_backend tests.test_prompt_background_tasks tests.test_background_tasks | MUST | pending |
|
||||
| F3 | 任务后端资源页与提示词编辑器 | cd frontend && npx tsc --noEmit | MUST | pending |
|
||||
| F4 | 全量回归、隔离数据库迁移与审阅 | make test | MUST | pending |
|
||||
|
||||
## Run record
|
||||
- F0:新后端资源输入 + 原生Jobs + 旧生命周期累计测试14项,exit 0。显式模拟执行器;backend-spine-smoke.json 已生成。
|
||||
|
||||
## Deferred
|
||||
- 真实模型/OpenCode联调:尚无服务地址与凭据。
|
||||
- 多进程调度、任意并发、远程技能同步及沙箱编排:超出本版。
|
||||
|
||||
## Blockers
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Speaker 与任务后端第一版
|
||||
|
||||
Base commit: b9c9d141b97e182a1af863dbb0922709dee349cd。
|
||||
|
||||
目标:仅提示词 Pipeline,将 task 工具迁为独立任务后端资源。Speaker 使用原有对话链路;可选内置 Reasoner 或外部 OpenCode。输入为后端连接、独立模型、执行策略、技能及委派目标;输出为任务回执、持久事件、网页任务结果和 Speaker 通知。
|
||||
|
||||
成功命令:仓库根目录 `make test`;隔离 PostgreSQL 验证旧配置迁移与持久化;显式模拟模型/远端验证真实原生 Jobs,不声称完成真实模型服务联调。
|
||||
|
||||
范围:单应用进程、任务并发固定1、不新增 Workflow/Realtime 支持,不部署远端沙箱或自动上传技能。
|
||||
@@ -0,0 +1 @@
|
||||
{"simulated_remote": true, "native_job": "completed", "backend_id": "tb_smoke", "tool_definition_required": false}
|
||||
Reference in New Issue
Block a user