3857 lines
139 KiB
TypeScript
3857 lines
139 KiB
TypeScript
/* METADATA
|
||
{
|
||
name: "operit_editor"
|
||
display_name: {
|
||
zh: "Operit平台编辑器"
|
||
en: "Operit Platform Editor"
|
||
}
|
||
description: {
|
||
zh: '''Operit 平台配置直改工具包:提供一组可直接读取与修改 Operit 平台设置的工具,覆盖 MCP、Skill、Sandbox Package、功能模型绑定、模型参数、上下文总结与 TTS/STT 语音服务配置。'''
|
||
en: '''Direct Operit platform configuration toolkit: a collection of tools for reading and directly modifying Operit platform settings, covering MCP, Skill, Sandbox Package, function-model bindings, model parameters, context-summary settings, and TTS/STT speech-service configuration.'''
|
||
}
|
||
|
||
enabledByDefault: true
|
||
|
||
"category": "Chat",
|
||
tools: [
|
||
{
|
||
name: "operit_editor"
|
||
description: {
|
||
zh: '''配置排查手册。
|
||
|
||
【触发条件】
|
||
- 用户提到 MCP/Skill 安装失败、无法启动、工具不出现、导入失败、重名冲突、配置文件怎么改
|
||
- 用户提到沙盒包(Package)开关、内置包列表、导入删除路径、包启用状态异常
|
||
- 用户让你排查 Operit 的插件配置路径、部署目录、开关状态、环境变量
|
||
- 用户提到功能模型绑定、模型配置新增/删除/修改、模型连接测试
|
||
- 用户提到 TTS/STT 语音服务不会配置、参数太多不会填、语音播报/语音识别不可用
|
||
- 问题核心是“配置和部署链路”,而不是普通问答
|
||
|
||
【MCP:安装与排查】
|
||
1) 配置目录:/sdcard/Download/Operit/mcp_plugins/
|
||
- 主配置:mcp_config.json
|
||
- 运行状态缓存:server_status.json(非实时快照,仅用于状态记录与工具缓存,不作为排查判定依据)
|
||
2) 两侧路径要严格区分:
|
||
- Android 侧是源目录(用户导入/存放目录),不是最终运行目录
|
||
- Linux 侧是最终运行目录:~/mcp_plugins/<pluginId最后一段>
|
||
- MCP 真正启动时,执行目录与命令都以 Linux 侧为准
|
||
3) 本地部署实际行为(代码逻辑):
|
||
- 创建目标目录
|
||
- 将 Android 侧插件目录复制到 Linux 侧目录
|
||
- 执行自动分析出的安装/构建命令(会跳过启动命令)
|
||
3.1) 本地插件识别规则:
|
||
- 这样识别是为兼容存储位置外层套目录的情况(如 `<pluginId>/<repo>-main/...`);Android 侧目录内至少要命中一个标志文件:`README.md`、`package.json`、`mcp.config.json`、`main.js`、`main.py`、`index.js`、`index.py`,否则可能被判定为未安装,进不了启动列表。
|
||
4) 命令型插件判定:
|
||
- 对于 command 为 npx/uvx/uv 的命令型插件,系统按“已部署”处理,仅做最小目录准备。
|
||
- 配置 Node 类命令型 MCP 时,mcp_config.json 里仍应按上游常见写法填写 `command: "npx"`;不要自行改写成 `pnpm` 或 `npm`。
|
||
- 软件内部启动这类 `npx` MCP 时,会自动把 `npx` 改写为 `pnpm dlx` 执行,并去掉 `-y`/`--yes` 一类确认参数。
|
||
- 因此这类 MCP 的实际运行依赖是 `pnpm`;Linux 终端里必须装有 `pnpm`,不能只装 `npm`。
|
||
- 不要把 command 改成 `npm`;当前已知这样可能触发 `double free` 报错。现阶段约定是:配置时按 `npx` 填,运行时由软件内部转成 `pnpm dlx`。
|
||
5) 系统启动行为(必须理解):
|
||
- 本地插件:系统会读取 mcp_config.json 中该插件的 command/args/env,使用 cwd=~/mcp_plugins/<shortName> 启动进程;可用性以“工具可调用/服务可响应”为准
|
||
- 远程插件:系统按 endpoint + connectionType(可选 bearerToken/headers)发起连接并校验连通
|
||
6) command 的实际执行位置(必须按此理解):
|
||
- 本地插件启动时,系统在 Linux 终端环境中启动进程;执行工作目录固定是 ~/mcp_plugins/<shortName>
|
||
- mcp_config.json 里填写的 command 就是在这个 Linux 工作目录上下文中被执行,不会在 Android 源目录执行
|
||
- args 里的相对路径,统一按该 cwd 解析
|
||
6.1) node 命令示例(按插件ID):
|
||
- 例:pluginId=owner/my-plugin,则 shortName=my-plugin,cwd=~/mcp_plugins/my-plugin
|
||
- 如果入口文件在 ~/mcp_plugins/my-plugin/dist/index.js,则写:
|
||
"command": "node"
|
||
"args": ["dist/index.js"]
|
||
- 这里 args 必须按 cwd 写相对路径,不要写 /sdcard/... 的 Android 路径
|
||
7) mcp_config.json 字段规范:
|
||
- 顶层必须有 mcpServers(对象)
|
||
- mcpServers 的 key 是 serverId(通常与插件ID一致,避免随意改名)
|
||
- pluginId(即 mcpServers 的 key)只允许 a-zA-Z_ 和空格
|
||
- 每个 server 常用字段:
|
||
- command(必填,启动命令)
|
||
- args(可选,字符串数组)
|
||
- env(可选,键值对)
|
||
- autoApprove(可选,数组)
|
||
- disabled(可选,true=禁用)
|
||
- MCP 的环境变量必须写在 mcpServers.<id>.env;`read_environment_variable` / `write_environment_variable` 不会读写这里。
|
||
8) mcp_config.json 路径写法规则:
|
||
- 不要把 Android 绝对路径写进本地插件启动参数(例如 /sdcard/...)
|
||
- 本地插件命令应按 Linux 运行目录编写,优先相对路径(因为 cwd 已固定到 ~/mcp_plugins/<shortName>)
|
||
- 如果必须写绝对路径,也应是 Linux 侧路径,不应写 Android 侧路径
|
||
9) 启用开关:
|
||
- 本地插件使用 mcpServers.<id>.disabled
|
||
- 远程插件使用 pluginMetadata.<id>.disabled
|
||
10) MCP 排查顺序(按顺序执行):
|
||
- 检查开关是否启用
|
||
- 检查本地部署目录是否存在且非空
|
||
- 检查 mcp_config.json 的 command/args/env 字段是否完整
|
||
- 检查 args 是否误写 Android 路径
|
||
- 检查 env 中所需 key/token
|
||
- 不要把 server_status.json 的 active 当作唯一依据;优先看工具是否可拉取、可调用
|
||
- 检查终端依赖(node/pnpm/python/uv)与 MCP 服务状态;其中 Node 类 `npx` MCP 实际依赖 `pnpm`,若缺少 `pnpm` 将无法启动
|
||
|
||
【Skill:安装与排查】
|
||
1) 目录:/sdcard/Download/Operit/skills/
|
||
2) 识别规则:每个 Skill 必须是一个文件夹,且包含 SKILL.md(skill.md 也可)。
|
||
3) 添加方式(按这个做):
|
||
- 先从可信来源下载 Skill(zip 或仓库源码均可)
|
||
- 把下载内容解压后,直接放到 /sdcard/Download/Operit/skills/
|
||
- 最终目录结构必须是 /sdcard/Download/Operit/skills/<skill_name>/,且该目录内有 SKILL.md
|
||
4) 元数据解析:
|
||
- 优先读取 frontmatter 中的 name/description
|
||
- 若缺失,回退读取文件前40行里的 name:/description:
|
||
5) AI 可见性:
|
||
- 列表开关关闭后,Skill 仍在本地,但 AI 不会使用
|
||
6) Skill 排查顺序:
|
||
- 先确认路径和 SKILL.md
|
||
- 再确认是否重名冲突
|
||
- 再确认开关是否开启
|
||
- 最后检查 SKILL.md 内容是否完整(步骤/约束/输出)
|
||
|
||
【Sandbox Package:安装与排查】
|
||
1) 沙盒包目录(外部):
|
||
- Android/data/com.ai.assistance.operit/files/packages
|
||
2) 内置包:
|
||
- 内置包来自应用内置资源,不在上述外部目录;删除内置包文件不是常规操作,通常只做开关管理。
|
||
3) 导入与删除:
|
||
- 导入:把 `.js` 或 `.toolpkg` 放入/导入到外部 packages 目录。
|
||
- 删除:外部包可直接按文件路径删除;删除前先确认是否仍被依赖。
|
||
4) 开关管理(优先使用工具):
|
||
- 先调用 list_sandbox_packages 获取“内置+外部”包列表与当前 enabled 状态。
|
||
- 再调用 set_sandbox_package_enabled(package_name, enabled) 执行启停。
|
||
5) 制作包文档:
|
||
- https://cdn.jsdelivr.net/gh/AAswordman/Operit@main/docs/SCRIPT_DEV_SKILL.md
|
||
- 该地址可直接通过 HTTP GET 请求访问,用于拉取原始 Markdown 文档内容。
|
||
6) 软件内调试烧录:
|
||
- 普通 `.js` 沙盒包优先用 `debug_install_js_package`。
|
||
- `ToolPkg` 优先用 `debug_install_toolpkg`;它会处理目录/manifest/.toolpkg 的打包或安装,并触发 ToolPkg 的刷新链路。
|
||
|
||
【Package 兼容模型(重要)】
|
||
- AI 看到的 package 是统一抽象,底层可能来自 MCP、Skill、Sandbox Package 任意一种。
|
||
- 可用包列表中的条目,不保证同类型;可能是三种类型混合出现。
|
||
- `use_package` 是三兼容入口:对 MCP/Skill/Sandbox Package 都可统一调用。
|
||
- `ping_mcp` 工具是 `use_package` 的直通封装,用于快速探测指定包是否可被加载。
|
||
|
||
【市场 Agent API(HTTP,只读)】
|
||
- 基础前缀:`https://api.operit.app/market-stats`
|
||
- 搜索:`/agent/search?q=<关键词>&type=mcp|skill|package|script&limit=10`
|
||
- 详情:`/agent/items/<type>/<id>`
|
||
- 安装计划:`/agent/items/<type>/<id>/install-plan`
|
||
- `package` / `script` 的 install_plan 通常会返回 `download_url`、`tracked_download_url`、`sha256`、`runtime_package_id`。
|
||
- `skill` 的 install_plan 通常会返回 `repository_url`。
|
||
- `mcp` 的 install_plan 可能返回 `config`(可直接作为 installConfig 参考)或 `repository_url`。
|
||
- 当前软件未对 JS 暴露统一的一键安装市场接口;需要安装时,按条目类型自行下载、解压、导入,必要时配合 `debug_install_js_package` / `debug_install_toolpkg` / `use_package`。
|
||
|
||
【功能模型与模型配置】
|
||
1) 模型配置(Model Config):
|
||
- 每个模型配置是一套完整连接参数(provider / endpoint / api key / model_name / custom_headers / 各能力开关)。
|
||
- 可以新增、删除、修改;删除默认配置 `default` 是禁止的。
|
||
2) 功能模型(Function Model):
|
||
- 每个功能类型(如 CHAT/SUMMARY/TRANSLATION 等)会绑定到一个模型配置。
|
||
- 当一个配置里 `model_name` 写了多个模型(逗号分隔),还要指定 `model_index` 选择第几个。
|
||
3) 关键工具(优先使用):
|
||
- `list_model_configs`:查看全部模型配置 + 当前功能绑定。
|
||
- `create_model_config`:新增模型配置(可带初始 provider/endpoint/key/model_name/custom_headers)。
|
||
- `update_model_config`:修改已有配置(按 config_id)。
|
||
- `delete_model_config`:删除配置(默认配置不可删)。
|
||
- `list_function_model_configs`:仅列出功能 -> 配置绑定关系(轻量)。
|
||
- `get_function_model_config`:查看某个功能当前绑定的单个配置详情。
|
||
- `set_function_model_config`:为功能指定配置与模型索引。
|
||
- `test_model_config_connection`:按设置页同等逻辑测试配置连通与多模态能力。
|
||
4) 配置修改后:
|
||
- 若该配置被某些功能使用,系统会刷新对应功能服务;绑定变更也会刷新目标功能服务。
|
||
5) 用户抱怨“输出总被截断”时:
|
||
- 可先用 `get_function_model_config` 查看对应功能绑定配置里的 `max_tokens` 参数。
|
||
- 对 DeepSeek 来说,默认常见是 4096;可将 `max_tokens_enabled` 打开并把 `max_tokens` 设到 8192 再测试。
|
||
- 对其他模型,先联网确认该模型可支持的输出上限后再设置。
|
||
|
||
【上下文总结模块(Context Summary)】
|
||
1) 用途:
|
||
- 控制会话上下文窗口与“何时触发总结”,用于降低上下文膨胀导致的丢信息、跑偏或响应不稳定。
|
||
- 只有当前对话功能模型(CHAT)所绑定配置里的上下文总结参数,会在实际对话中生效。
|
||
2) 核心机制说明:
|
||
- 软件内有一个 max 开关,即 `enable_max_context_mode`。
|
||
- 开启时使用 `max_context_length`,关闭时使用 `context_length`,目标是为用户节约 token,并按场景动态决定使用长度。
|
||
- `enable_summary` 控制是否自动总结;当上下文超过 `summary_token_threshold * 当前可用上下文长度` 时,会触发总结。
|
||
3) 建议排查顺序:
|
||
- 先用 `get_context_summary_config` 看当前功能绑定配置的上下文总结参数。
|
||
- 再用 `set_context_summary_config` 设置上下文总结参数(可传入参数覆盖默认值)。
|
||
- 若仍异常,再结合具体模型能力与业务负载做细调。
|
||
|
||
【TTS/STT 语音服务配置】
|
||
1) 配置入口:
|
||
- 在软件设置里的 Speech Services(语音服务)页面配置。
|
||
- 该页面会自动保存;改完后语音服务实例会自动重建。
|
||
2) TTS(文本转语音)可选引擎:
|
||
- `SIMPLE_TTS`:系统 TTS,基本无需填网络参数。
|
||
- `HTTP_TTS`:需重点填写 `url_template`、`headers`、`http_method`、`content_type`、`request_body`;若服务先返回 JSON / 字段 / 下载链接,再额外填写 `response_pipeline`。
|
||
- `OPENAI_WS_TTS`:需填写 `url_template`、`api_key`、`model_name`、`voice_id`。其中 `url_template` 应为 Realtime WebSocket 地址(例如 `wss://api.openai.com/v1/realtime`)。
|
||
- `SILICONFLOW_TTS`:需填写 `api_key`、`model_name`、`voice_id`。
|
||
- `MINIMAX_TTS`:需填写 `api_key`,可选填写 `url_template`、`model_name`、`voice_id`。默认接口为 `https://api.minimaxi.com/v1/t2a_v2`,内部固定按 `data.audio -> http_get` 解析音频。
|
||
- `MIMO_TTS`:需填写 `api_key`,可选填写 `url_template`、`model_name`、`voice_id`。预置音色使用 `mimo-v2.5-tts` 和短 `voice_id`;声音克隆使用 `mimo-v2.5-tts-voiceclone`,`voice_id` 填完整 `data:audio/...;base64,...` 音频样本。默认接口为 `https://api.xiaomimimo.com/v1/chat/completions`,内部固定按 `choices[0].message.audio.data -> base64_decode` 解析音频。
|
||
- `DOUBAO_TTS`:豆包 TTS,需填写 `url_template`、`api_key`(Token)、`model_name`(App ID)、`voice_id`(voice_type)。默认接口为 `https://openspeech.bytedance.com/api/v1/tts`,继承 HTTP TTS 队列和响应管线,内部固定按 `data -> base64_decode` 解析音频。
|
||
- `OPENAI_TTS`:需填写 `url_template`、`api_key`、`model_name`、`voice_id`。
|
||
- `VITS_TTS`:本地 VITS/Piper TTS。`tts_vits_package_path` 填本地模型包 `.zip` 或已解压目录,`tts_vits_speaker_id` 可选填数字 speaker id,`tts_vits_options` 可选填写 `sample_rate`、`threads`、`noise_scale`、`length_scale`、`noise_w`、`frontend`、`text_mode`、`speaker_count`、输入名和 blank/bos/eos token 等本地参数。
|
||
3) STT(语音转文本)可选引擎:
|
||
- `SHERPA_NCNN`:本地识别,通常无需 API Key。
|
||
- `OPENAI_STT`:需填写 `endpoint_url`、`api_key`、`model_name`。
|
||
- `DEEPGRAM_STT`:需填写 `endpoint_url`、`api_key`、`model_name`。
|
||
4) 最常见填错点(优先检查):
|
||
- `HTTP_TTS` 的 `headers` 不是合法 JSON(必须是对象)。
|
||
- `HTTP_TTS` 的模板没放 `{text}` 占位符(GET 通常在 URL,POST 通常在 body)。
|
||
- `HTTP_TTS` 的 `response_pipeline` 不是合法 JSON 数组,或步骤名 / `path` 填错。
|
||
- `OPENAI_WS_TTS` 把 HTTP 地址填成了 WebSocket 地址,或把 WebSocket 地址误填成 HTTP 地址。
|
||
- `VITS_TTS` 的 `tts_vits_package_path` 不是本地 `.zip` 模型包或已解压目录,或文件不存在。
|
||
- `VITS_TTS` 模型包里没有可识别的 `.onnx` / config JSON / lexicon,或配置里缺少 `sample_rate` / token 映射。
|
||
- `VITS_TTS` 的 `tts_vits_options` 不是合法 JSON(必须是对象),或把本地参数名 / 数值类型写错。
|
||
- TTS/STT 的 endpoint 路径写错(比如把 chat/completions 写成 audio 接口)。
|
||
- `model_name` 填了不存在的模型或与接口不匹配。
|
||
- 改完配置后没有重新测试语音播报或语音识别。
|
||
5) HTTP_TTS 占位符说明(按代码逻辑):
|
||
- 必填占位符:`{text}`。
|
||
- 当 `http_method=GET` 时,`{text}` 必须在 `url_template` 里。
|
||
- 当 `http_method=POST` 时,`{text}` 必须在 `request_body` 里。
|
||
- 可选占位符:`{rate}`、`{pitch}`、`{voice}`。
|
||
- 当前对外可稳定使用的占位符:`{text}`、`{rate}`、`{pitch}`、`{voice}`、`{apiKey}`、`{model}`、`{locale}`、`{uuid}`。
|
||
6) HTTP_TTS 响应处理说明(已发布版本兼容):
|
||
- `response_pipeline` 留空或传 `[]`:保持旧行为,直接把首个响应体当音频。
|
||
- 当服务端先返回 JSON、字段或下载链接时,再填写 `response_pipeline`,按步骤解析。
|
||
- 当前可用步骤:`parse_json`、`pick`、`parse_json_string`、`http_get`、`http_request_from_object`、`base64_decode`。
|
||
- 常见 JSON 下载链接场景:
|
||
- `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"audio_uri"},{"type":"http_get"}]`
|
||
- 若字段里还是 JSON 字符串:
|
||
- `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"data.payload"},{"type":"parse_json_string"},{"type":"pick","path":"audio.url"},{"type":"http_get"}]`
|
||
7) 可直接参考的最小模板:
|
||
- HTTP TTS(GET):
|
||
- `url_template`: `https://example.com/tts?text={text}`
|
||
- `headers`: `{}`
|
||
- `http_method`: `GET`
|
||
- `content_type`: `application/json`
|
||
- HTTP TTS(POST):
|
||
- `url_template`: `https://example.com/tts`
|
||
- `headers`: `{"Authorization":"Bearer <API_KEY>"}`
|
||
- `http_method`: `POST`
|
||
- `content_type`: `application/json`
|
||
- `request_body`: `{"text":"{text}"}`
|
||
- OpenAI STT(默认常见):
|
||
- `endpoint_url`: `https://api.openai.com/v1/audio/transcriptions`
|
||
- `model_name`: `whisper-1`
|
||
8) 排查顺序(建议):
|
||
- 先确认选中的引擎类型是否正确(TTS 与 STT 分开看)。
|
||
- 再检查 endpoint / key / model 三件套是否完整。
|
||
- 再检查 HTTP 模板字段(headers JSON、method、body、占位符、response_pipeline)。
|
||
- 至少做一次真实 TTS 播报测试;若还要排查 STT,再另外走语音识别链路。
|
||
9) 对应工具(可直接用):
|
||
- `get_speech_services_config`:获取当前 TTS/STT 配置快照(含引擎类型与关键字段)。
|
||
- `set_speech_services_config`:按字段修改 TTS/STT 配置(支持只改部分字段)。
|
||
- `test_tts_playback`:按当前 TTS 配置播放一次测试文本(支持临时覆盖语速/音调)。
|
||
|
||
【多模态输入规则】
|
||
1) 能力开关含义:
|
||
- 模型配置里的“支持 Tool Call / 识图 / 音频 / 视频”等开关,只是软件侧能力标识,不等于模型真实能力。
|
||
- 实际配置时,必须依据模型真实支持情况来开关,不能乱开。
|
||
2) 软件识图主链路:
|
||
- 当“对话功能模型”的模型配置开启识图且模型真实支持时:聊天附图会直接发给该模型识别。
|
||
- 当对话模型不支持识图时:软件会尝试走 OCR,或走“识图功能模型”进行识图中转。
|
||
3) 用户有识图需求时的可行条件:
|
||
- 条件 A:对话模型支持识图。
|
||
- 条件 B:对话模型不支持识图,但识图功能模型支持识图。
|
||
- 若识图功能模型也不支持识图:最终回退到 OCR。
|
||
|
||
【绘图输出说明】
|
||
- 绘图通过工具包实现。
|
||
- 软件内置了一些绘图包,可调用 list_sandbox_packages 查看。
|
||
- 通常只需启用其中一个可用绘图包即可,无需全部开启。
|
||
|
||
【执行原则】
|
||
- 严格按用户明确指示执行,不自行定义“问题”或追加未被要求的目标。
|
||
- 任何会改动用户配置的操作(开关、导入/删除、写环境变量、模型配置增删改、重启)都必须先得到用户明确确认。
|
||
- 用户没有明确要求执行某个工具时,不主动调用写入类工具。
|
||
- 回答优先基于本手册的路径与规则,避免泛化推断。'''
|
||
en: '''Configuration troubleshooting guide.
|
||
|
||
[Trigger conditions]
|
||
- The user mentions MCP/Skill install failure, startup failure, tools not appearing, import failure, duplicate name conflicts, or config editing
|
||
- The user mentions sandbox package toggles, built-in package listing, import/delete paths, or package enable-state issues
|
||
- The user asks to troubleshoot plugin config paths, deploy directories, enable switches, or environment variables
|
||
- The user mentions function model bindings, adding/deleting/updating model configs, or testing model connectivity
|
||
- The user says TTS/STT setup is confusing, too many fields to fill, or speech playback/recognition is not working
|
||
- The core issue is configuration/deployment flow rather than normal Q&A
|
||
|
||
[MCP: install and troubleshooting]
|
||
1) Config directory: /sdcard/Download/Operit/mcp_plugins/
|
||
- Main config: mcp_config.json
|
||
- Runtime status cache: server_status.json (non-realtime snapshot for status/tool cache only; not a troubleshooting source of truth)
|
||
2) Keep Android-side and Linux-side paths strictly separated:
|
||
- Android side is the source/import location, not the final runtime location
|
||
- Linux runtime directory is: ~/mcp_plugins/<last-segment-of-pluginId>
|
||
- Actual MCP startup always runs from the Linux side
|
||
3) Real deployment behavior (from implementation):
|
||
- Create target directory
|
||
- Copy plugin files from Android side to Linux side
|
||
- Execute auto-generated install/build commands (startup commands are skipped)
|
||
3.1) Local plugin recognition rule:
|
||
- This exists to handle nested storage layouts (for example `<pluginId>/<repo>-main/...`): the Android-side plugin directory must contain at least one marker file such as `README.md`, `package.json`, `mcp.config.json`, `main.js`, `main.py`, `index.js`, or `index.py`; otherwise it may be treated as not installed and never enter the startup list.
|
||
4) Command-based plugin handling:
|
||
- For command-based plugins using npx/uvx/uv, the system treats them as deployed and only performs minimal directory preparation.
|
||
- For Node-style command MCPs, keep the config in mcp_config.json aligned with upstream examples and still write `command: "npx"`; do not rewrite it to `pnpm` or `npm` yourself.
|
||
- When the app starts this kind of `npx` MCP, it internally rewrites `npx` to `pnpm dlx` and strips confirmation flags such as `-y` / `--yes`.
|
||
- That means the real runtime dependency for this kind of MCP is `pnpm`; `pnpm` must exist in the Linux terminal, and having only `npm` is not enough.
|
||
- Do not change the command to `npm`; this is known to trigger `double free` errors. The current convention is: configure it as `npx`, and let the app run it internally as `pnpm dlx`.
|
||
5) System startup behavior (critical):
|
||
- Local plugins: the app reads command/args/env from mcp_config.json and starts the process with cwd=~/mcp_plugins/<shortName>; availability should be judged by real tool call/response
|
||
- Remote plugins: the app connects using endpoint + connectionType (optional bearerToken/headers) and verifies connectivity
|
||
6) Actual execution location of command (must be understood this way):
|
||
- For local plugins, the process starts in the Linux terminal environment with fixed working directory: ~/mcp_plugins/<shortName>
|
||
- The command from mcp_config.json is executed in that Linux working-directory context, not in the Android source directory
|
||
- Any relative paths in args are resolved against that cwd
|
||
6.1) node command example (by plugin ID):
|
||
- Example: pluginId=owner/my-plugin, so shortName=my-plugin and cwd=~/mcp_plugins/my-plugin
|
||
- If the entry file is ~/mcp_plugins/my-plugin/dist/index.js, write:
|
||
"command": "node"
|
||
"args": ["dist/index.js"]
|
||
- args must be relative to cwd; do not use Android paths such as /sdcard/...
|
||
7) mcp_config.json field rules:
|
||
- Top level must contain mcpServers (object)
|
||
- Each key in mcpServers is a serverId (normally keep it aligned with plugin ID)
|
||
- pluginId (the mcpServers key) only allows letters a-zA-Z, underscore (_), and spaces
|
||
- Common server fields:
|
||
- command (required, startup command)
|
||
- args (optional, string array)
|
||
- env (optional, key-value map)
|
||
- autoApprove (optional, array)
|
||
- disabled (optional, true means disabled)
|
||
- MCP environment variables must be configured in mcpServers.<id>.env; `read_environment_variable` / `write_environment_variable` do not read or write these MCP env entries.
|
||
8) Path-writing rules in mcp_config.json:
|
||
- Do not put Android absolute paths (for example /sdcard/...) in local plugin startup args
|
||
- Write local plugin command/args for Linux runtime; prefer relative paths because cwd is fixed to ~/mcp_plugins/<shortName>
|
||
- If an absolute path is required, it must be a Linux path, not an Android path
|
||
9) Enable switch:
|
||
- Local plugin uses mcpServers.<id>.disabled
|
||
- Remote plugin uses pluginMetadata.<id>.disabled
|
||
10) MCP troubleshooting order:
|
||
- Check enable switch
|
||
- Check local deployed directory exists and is non-empty
|
||
- Check whether command/args/env in mcp_config.json are complete
|
||
- Check whether args incorrectly use Android paths
|
||
- Check required key/token in env
|
||
- Do not treat `active` in server_status.json as the single source of truth; prioritize whether tools can be fetched/called successfully
|
||
- Check terminal dependencies (node/pnpm/python/uv) and MCP service status; Node-style `npx` MCPs actually depend on `pnpm`, so missing `pnpm` will prevent startup
|
||
|
||
[Skill: install and troubleshooting]
|
||
1) Directory: /sdcard/Download/Operit/skills/
|
||
2) Recognition rule: each Skill must be a folder containing SKILL.md (skill.md is also accepted).
|
||
3) How to add a skill (use this workflow):
|
||
- Download the skill from a trusted source (zip or repository source code).
|
||
- Extract it, then place the folder directly under /sdcard/Download/Operit/skills/
|
||
- Final structure must be /sdcard/Download/Operit/skills/<skill_name>/ and that folder must contain SKILL.md
|
||
4) Metadata parsing:
|
||
- Prefer frontmatter name/description
|
||
- Fallback to name:/description: in the first 40 lines
|
||
5) AI visibility:
|
||
- If the list switch is off, the Skill remains local but is hidden from AI usage
|
||
6) Skill troubleshooting order:
|
||
- Verify path and SKILL.md
|
||
- Check duplicate-name conflict
|
||
- Check enable switch
|
||
- Check whether SKILL.md instructions are complete (steps/constraints/outputs)
|
||
|
||
[Sandbox Package: install and troubleshooting]
|
||
1) External sandbox packages directory:
|
||
- Android/data/com.ai.assistance.operit/files/packages
|
||
2) Built-in packages:
|
||
- Built-in packages come from app bundled assets, not from the external directory above; usually manage via enable/disable instead of file deletion.
|
||
3) Import and delete:
|
||
- Import: place/import `.js` or `.toolpkg` into the external packages directory.
|
||
- Delete: for external packages, delete by file path after confirming dependency impact.
|
||
4) Toggle management (prefer tools):
|
||
- Call list_sandbox_packages first to get built-in + external package list and current enabled state.
|
||
- Then call set_sandbox_package_enabled(package_name, enabled) to apply changes.
|
||
5) Package authoring guide:
|
||
- https://cdn.jsdelivr.net/gh/AAswordman/Operit@main/docs/SCRIPT_DEV_SKILL.md
|
||
- This URL can be accessed directly with an HTTP GET request to fetch the raw Markdown document.
|
||
6) In-app debug install:
|
||
- For plain `.js` sandbox packages, prefer `debug_install_js_package`.
|
||
- For `ToolPkg`, prefer `debug_install_toolpkg`; it handles packaging/install flow for folder/manifest/.toolpkg sources and triggers the ToolPkg refresh path.
|
||
|
||
[Package compatibility model (important)]
|
||
- The package list seen by AI is a unified abstraction, and each item may come from MCP, Skill, or Sandbox Package.
|
||
- Available package entries are not guaranteed to be a single type; mixed types are expected.
|
||
- `use_package` is tri-compatible and can be called uniformly for MCP/Skill/Sandbox Package.
|
||
- `ping_mcp` is a thin wrapper over `use_package` for quick package availability probing.
|
||
|
||
[Market agent API (HTTP, read-only)]
|
||
- Base prefix: `https://api.operit.app/market-stats`
|
||
- Search: `/agent/search?q=<query>&type=mcp|skill|package|script&limit=10`
|
||
- Detail: `/agent/items/<type>/<id>`
|
||
- Install plan: `/agent/items/<type>/<id>/install-plan`
|
||
- `package` / `script` install_plan usually returns `download_url`, `tracked_download_url`, `sha256`, and `runtime_package_id`.
|
||
- `skill` install_plan usually returns `repository_url`.
|
||
- `mcp` install_plan may return `config` (usable as installConfig reference) or `repository_url`.
|
||
- The app does not currently expose a unified one-click market install API to JS; when installation is needed, download/extract/import by item type and use `debug_install_js_package`, `debug_install_toolpkg`, or `use_package` when appropriate.
|
||
|
||
[Function model and model config]
|
||
1) Model config:
|
||
- Each model config is one full connection profile (provider / endpoint / api key / model_name / custom_headers / capability switches).
|
||
- Configs can be created, updated, and deleted; deleting the default `default` config is forbidden.
|
||
2) Function model binding:
|
||
- Each function type (for example CHAT/SUMMARY/TRANSLATION) is bound to one model config.
|
||
- If `model_name` contains multiple models (comma-separated), `model_index` selects which one to use.
|
||
3) Key tools (prefer these):
|
||
- `list_model_configs`: list all model configs and current function bindings.
|
||
- `create_model_config`: create a new model config (with optional initial provider/endpoint/key/model_name/custom_headers).
|
||
- `update_model_config`: update an existing config by config_id.
|
||
- `delete_model_config`: delete a config (default cannot be deleted).
|
||
- `list_function_model_configs`: list function -> config bindings only (lightweight).
|
||
- `get_function_model_config`: get one bound config detail by function type.
|
||
- `set_function_model_config`: assign config and model index for a function.
|
||
- `test_model_config_connection`: run settings-UI-equivalent connectivity/capability checks for a config.
|
||
4) After config changes:
|
||
- If a config is used by functions, corresponding function services are refreshed; binding updates also refresh target function service.
|
||
5) When users complain about truncated outputs:
|
||
- Use `get_function_model_config` to inspect `max_tokens` in the bound config for that function.
|
||
- For DeepSeek, a common default is 4096; enable `max_tokens_enabled` and try setting `max_tokens` to 8192.
|
||
- For other models, verify the supported output-token limit online before changing values.
|
||
|
||
[Context summary module]
|
||
1) Purpose:
|
||
- Controls context window behavior and summary-trigger conditions to reduce context bloat, drift, and unstable long-chat responses.
|
||
- Only context-summary settings on the config currently bound to CHAT are effective in real conversation.
|
||
2) Core mechanism:
|
||
- There is a max switch in the app: `enable_max_context_mode`.
|
||
- When enabled, it uses `max_context_length`; when disabled, it uses `context_length`, so context length can be chosen dynamically to save tokens.
|
||
- `enable_summary` controls automatic summarization. When context exceeds `summary_token_threshold * current available context length`, summarization is triggered.
|
||
3) Recommended troubleshooting flow:
|
||
- Use `get_context_summary_config` to inspect current context-summary fields for a function.
|
||
- Use `set_context_summary_config` to set context-summary fields (you can override default values via params).
|
||
- If issues remain, fine-tune based on real model capability and workload.
|
||
|
||
[TTS/STT speech-service configuration]
|
||
1) Where to configure:
|
||
- Configure it on the Speech Services page in app settings.
|
||
- The page auto-saves; after changes, speech-service instances are rebuilt automatically.
|
||
2) TTS (text-to-speech) engines:
|
||
- `SIMPLE_TTS`: system TTS, usually no network fields required.
|
||
- `HTTP_TTS`: mainly fill `url_template`, `headers`, `http_method`, `content_type`, `request_body`; if the service first returns JSON / fields / a download link, also fill `response_pipeline`.
|
||
- `OPENAI_WS_TTS`: fill `url_template`, `api_key`, `model_name`, `voice_id`. `url_template` should be a Realtime WebSocket endpoint such as `wss://api.openai.com/v1/realtime`.
|
||
- `SILICONFLOW_TTS`: fill `api_key`, `model_name`, `voice_id`.
|
||
- `MINIMAX_TTS`: fill `api_key`; optionally set `url_template`, `model_name`, and `voice_id`. Default endpoint is `https://api.minimaxi.com/v1/t2a_v2`, and audio is resolved from `data.audio` automatically.
|
||
- `MIMO_TTS`: fill `api_key`; optionally set `url_template`, `model_name`, and `voice_id`. Use `mimo-v2.5-tts` with a short `voice_id` for preset voices. Use `mimo-v2.5-tts-voiceclone` with a full `data:audio/...;base64,...` audio sample in `voice_id` for voice cloning. Default endpoint is `https://api.xiaomimimo.com/v1/chat/completions`, and audio is resolved from `choices[0].message.audio.data` with Base64 decoding.
|
||
- `DOUBAO_TTS`: Doubao TTS. Fill `url_template`, `api_key` (token), `model_name` (App ID), and `voice_id` (voice_type). Default endpoint is `https://openspeech.bytedance.com/api/v1/tts`; it inherits the HTTP TTS queue and response pipeline and resolves audio from `data` with Base64 decoding.
|
||
- `OPENAI_TTS`: fill `url_template`, `api_key`, `model_name`, `voice_id`.
|
||
- `VITS_TTS`: local VITS/Piper TTS. Set `tts_vits_package_path` to the local model package `.zip` or extracted package directory, optionally set `tts_vits_speaker_id` to a numeric speaker id, and use `tts_vits_options` for local options such as `sample_rate`, `threads`, `noise_scale`, `length_scale`, `noise_w`, `frontend`, `text_mode`, `speaker_count`, input names, and blank/bos/eos token settings.
|
||
3) STT (speech-to-text) engines:
|
||
- `SHERPA_NCNN`: local recognition, usually no API key required.
|
||
- `OPENAI_STT`: fill `endpoint_url`, `api_key`, `model_name`.
|
||
- `DEEPGRAM_STT`: fill `endpoint_url`, `api_key`, `model_name`.
|
||
4) Most common mistakes (check first):
|
||
- `headers` in `HTTP_TTS` is not valid JSON (must be an object).
|
||
- Missing `{text}` placeholder in HTTP TTS template (typically in URL for GET, in body for POST).
|
||
- `response_pipeline` in `HTTP_TTS` is not a valid JSON array, or a step name / `path` is incorrect.
|
||
- `OPENAI_WS_TTS` is configured with an HTTP URL instead of a WebSocket URL, or vice versa.
|
||
- `tts_vits_package_path` in `VITS_TTS` is not a local `.zip` model package or extracted package directory, or it does not exist.
|
||
- The `VITS_TTS` package has no recognizable `.onnx` / config JSON / lexicon, or the config is missing `sample_rate` / token mappings.
|
||
- `tts_vits_options` in `VITS_TTS` is not valid JSON, or local option names / numeric values are invalid.
|
||
- Wrong endpoint path for TTS/STT (for example using chat/completions instead of audio endpoints).
|
||
- `model_name` does not exist or does not match the API.
|
||
- No real retest after saving config.
|
||
5) HTTP_TTS placeholder rules (from implementation):
|
||
- Required placeholder: `{text}`.
|
||
- When `http_method=GET`, `{text}` must appear in `url_template`.
|
||
- When `http_method=POST`, `{text}` must appear in `request_body`.
|
||
- Optional placeholders: `{rate}`, `{pitch}`, `{voice}`.
|
||
- Stable placeholders for external configuration: `{text}`, `{rate}`, `{pitch}`, `{voice}`, `{apiKey}`, `{model}`, `{locale}`, `{uuid}`.
|
||
6) HTTP_TTS response handling notes (forward-compatible with released configs):
|
||
- Leave `response_pipeline` empty or use `[]` to keep the old behavior: the first response body is treated as audio directly.
|
||
- Only fill `response_pipeline` when the service returns JSON, nested fields, or a follow-up download URL before audio is available.
|
||
- Currently supported steps: `parse_json`, `pick`, `parse_json_string`, `http_get`, `http_request_from_object`, `base64_decode`.
|
||
- Common JSON download-link case:
|
||
- `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"audio_uri"},{"type":"http_get"}]`
|
||
- If the picked field is itself a JSON string:
|
||
- `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"data.payload"},{"type":"parse_json_string"},{"type":"pick","path":"audio.url"},{"type":"http_get"}]`
|
||
7) Minimal templates you can copy:
|
||
- HTTP TTS (GET):
|
||
- `url_template`: `https://example.com/tts?text={text}`
|
||
- `headers`: `{}`
|
||
- `http_method`: `GET`
|
||
- `content_type`: `application/json`
|
||
- HTTP TTS (POST):
|
||
- `url_template`: `https://example.com/tts`
|
||
- `headers`: `{"Authorization":"Bearer <API_KEY>"}`
|
||
- `http_method`: `POST`
|
||
- `content_type`: `application/json`
|
||
- `request_body`: `{"text":"{text}"}`
|
||
- OpenAI STT (common default):
|
||
- `endpoint_url`: `https://api.openai.com/v1/audio/transcriptions`
|
||
- `model_name`: `whisper-1`
|
||
8) Recommended troubleshooting order:
|
||
- Confirm the selected engine type first (TTS and STT separately).
|
||
- Check endpoint/key/model as a bundle.
|
||
- Then verify HTTP template fields (headers JSON, method, body, placeholder, response_pipeline).
|
||
- Run at least one real TTS playback test; if STT also needs troubleshooting, verify recognition in a separate speech flow.
|
||
9) Related tools:
|
||
- `get_speech_services_config`: fetch current TTS/STT config snapshot (engine types + key fields).
|
||
- `set_speech_services_config`: update TTS/STT config fields (partial update supported).
|
||
- `test_tts_playback`: play one test utterance with the current TTS config (supports temporary rate/pitch overrides).
|
||
|
||
[Multimodal input rules]
|
||
1) Meaning of capability switches:
|
||
- Switches like Tool Call / image / audio / video in model config are software-side capability markers, not proof of real model capability.
|
||
- Configure these switches according to actual model support; do not enable blindly.
|
||
2) Image understanding main path in app:
|
||
- If the chat-function model config enables image understanding and the model truly supports it, attached images are sent directly to that chat model.
|
||
- If the chat model does not support image understanding, the app will try OCR, or use the dedicated image-recognition function model as a relay.
|
||
3) Valid conditions when users need image understanding:
|
||
- Condition A: chat model supports image understanding.
|
||
- Condition B: chat model does not support it, but image-recognition function model supports it.
|
||
- If the image-recognition model also does not support it, the final fallback is OCR.
|
||
|
||
[Drawing output note]
|
||
- Drawing is implemented via package tools.
|
||
- The app includes several built-in drawing packages; use list_sandbox_packages to inspect them.
|
||
- Usually enabling one available drawing package is enough; no need to enable all of them.
|
||
|
||
[Execution principles]
|
||
- Follow the user's explicit instructions strictly; do not define your own "problem" or add unrequested goals.
|
||
- Any configuration-changing action (toggle/import/delete/write env/model config CRUD/restart) requires explicit user confirmation first.
|
||
- If the user did not explicitly ask to execute a specific tool, do not proactively run write-type tools.
|
||
- Answer with concrete paths/rules from this guide and avoid generic assumptions.'''
|
||
}
|
||
parameters: []
|
||
advice: true
|
||
},
|
||
{
|
||
name: "how_make_skill"
|
||
description: {
|
||
zh: '''返回如何制作 skill 的双语说明。'''
|
||
en: '''Return a bilingual guide for creating a skill.'''
|
||
}
|
||
parameters: []
|
||
},
|
||
{
|
||
name: "list_sandbox_packages"
|
||
description: {
|
||
zh: '''获取沙盒包列表(内置+外部)及当前启用状态、管理路径。'''
|
||
en: '''Get sandbox package list (built-in + external), current enabled states, and management paths.'''
|
||
}
|
||
parameters: []
|
||
},
|
||
{
|
||
name: "set_sandbox_package_enabled"
|
||
description: {
|
||
zh: '''设置沙盒包开关状态。'''
|
||
en: '''Set sandbox package enabled state.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "package_name"
|
||
description: {
|
||
zh: "沙盒包名称"
|
||
en: "Sandbox package name"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "enabled"
|
||
description: {
|
||
zh: "是否启用(true/false)"
|
||
en: "Enable state (true/false)"
|
||
}
|
||
type: boolean
|
||
required: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "debug_install_js_package"
|
||
description: {
|
||
zh: '''将 Android 侧的普通 `.js` 沙盒包直接烧录到外部 packages 目录,并刷新、启用、重新加载,便于在软件内调试。'''
|
||
en: '''Install an Android-side plain `.js` sandbox package into the external packages directory, refresh it, enable it, and reload it for in-app debugging.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "source_path"
|
||
description: {
|
||
zh: "Android 侧 `.js` 源文件路径"
|
||
en: "Android-side `.js` source file path"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "enable_after_install"
|
||
description: {
|
||
zh: "安装后是否自动启用,默认 true"
|
||
en: "Whether to enable it automatically after install, default true"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "activate_after_install"
|
||
description: {
|
||
zh: "安装后是否自动 use_package 重新加载,默认 true"
|
||
en: "Whether to automatically call use_package after install, default true"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "debug_install_toolpkg"
|
||
description: {
|
||
zh: '''根据 Android 侧的 ToolPkg 目录、manifest 或现成 `.toolpkg`,直接打包/烧录到外部 packages 目录,并触发 ToolPkg 安装与刷新链路。'''
|
||
en: '''Package or install an Android-side ToolPkg folder, manifest, or existing `.toolpkg` into the external packages directory and trigger the ToolPkg install/refresh flow.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "source_path"
|
||
description: {
|
||
zh: "Android 侧 ToolPkg 目录、manifest.json/manifest.hjson 或 `.toolpkg` 路径"
|
||
en: "Android-side ToolPkg folder, manifest.json/manifest.hjson, or `.toolpkg` path"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "reset_subpackage_states"
|
||
description: {
|
||
zh: "是否按 manifest 默认值重置子包启用状态,默认 true"
|
||
en: "Whether to reset subpackage enable states from manifest defaults, default true"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "activate_subpackages"
|
||
description: {
|
||
zh: "可选,逗号或换行分隔的子包 ID;安装后会自动启用并 use_package"
|
||
en: "Optional comma/newline separated subpackage IDs; they will be enabled and activated after install"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "wait_ms"
|
||
description: {
|
||
zh: "安装广播后等待并轮询刷新的毫秒数,默认 1500"
|
||
en: "Milliseconds to wait and poll for refresh after sending the install broadcast, default 1500"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "debug_run_sandbox_script"
|
||
description: {
|
||
zh: '''直接运行一段 sandbox script。可传 Android 侧 `source_path`,也可直接传 `source_code` 内联代码;会返回结构化执行结果与日志事件。'''
|
||
en: '''Run a sandbox script directly. Accepts either an Android-side `source_path` or inline `source_code`, and returns structured execution results with log events.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "source_path"
|
||
description: {
|
||
zh: "Android 侧脚本文件路径;与 source_code 二选一"
|
||
en: "Android-side script file path; use either this or source_code"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "source_code"
|
||
description: {
|
||
zh: "直接执行的内联 JavaScript 代码;与 source_path 二选一"
|
||
en: "Inline JavaScript code to execute directly; use either this or source_path"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "params_json"
|
||
description: {
|
||
zh: "传给脚本运行时的 JSON 参数字符串,默认 {}"
|
||
en: "JSON parameter string passed to the script runtime, default {}"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "env_file_path"
|
||
description: {
|
||
zh: "可选,Android 侧 env 文件路径"
|
||
en: "Optional Android-side env file path"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "script_label"
|
||
description: {
|
||
zh: "可选,仅用于内联代码模式下生成结果文件名和显示标识"
|
||
en: "Optional label used only for inline-code mode to name the result file and display path"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "wait_ms"
|
||
description: {
|
||
zh: "等待结构化结果文件的毫秒数,默认 15000"
|
||
en: "Milliseconds to wait for the structured result file, default 15000"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "read_environment_variable"
|
||
description: {
|
||
zh: '''读取指定环境变量当前值(仅用于沙盒包脚本环境变量排查,不用于 MCP 配置)。'''
|
||
en: '''Read current value of a specified environment variable (sandbox-package script env troubleshooting only, not MCP config env).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "key"
|
||
description: {
|
||
zh: "环境变量名"
|
||
en: "Environment variable key"
|
||
}
|
||
type: string
|
||
required: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "write_environment_variable"
|
||
description: {
|
||
zh: '''写入指定环境变量;value 为空时会清除该变量(仅用于沙盒包脚本环境变量,不用于 MCP 配置 env)。'''
|
||
en: '''Write a specified environment variable; empty value clears it (sandbox-package script env only, not MCP config env).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "key"
|
||
description: {
|
||
zh: "环境变量名"
|
||
en: "Environment variable key"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "value"
|
||
description: {
|
||
zh: "变量值;为空时清除该变量"
|
||
en: "Variable value; empty clears the variable"
|
||
}
|
||
type: string
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "restart_mcp_with_logs"
|
||
description: {
|
||
zh: '''触发一次 MCP 重启流程,返回每个插件的启动日志与状态摘要。'''
|
||
en: '''Trigger one MCP restart flow and return per-plugin startup logs with status summary.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "timeout_ms"
|
||
description: {
|
||
zh: "可选,最大等待时长(毫秒)"
|
||
en: "Optional max wait time in milliseconds"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "get_speech_services_config"
|
||
description: {
|
||
zh: '''获取当前 TTS/STT 语音服务配置快照。'''
|
||
en: '''Get current TTS/STT speech services config snapshot.'''
|
||
}
|
||
parameters: []
|
||
},
|
||
{
|
||
name: "set_speech_services_config"
|
||
description: {
|
||
zh: '''按字段更新 TTS/STT 语音服务配置(支持部分字段更新)。'''
|
||
en: '''Update TTS/STT speech services config by fields (partial update supported).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "tts_service_type"
|
||
description: {
|
||
zh: "可选,SIMPLE_TTS/HTTP_TTS/OPENAI_WS_TTS/SILICONFLOW_TTS/MINIMAX_TTS/MIMO_TTS/DOUBAO_TTS/OPENAI_TTS/VITS_TTS"
|
||
en: "Optional, SIMPLE_TTS/HTTP_TTS/OPENAI_WS_TTS/SILICONFLOW_TTS/MINIMAX_TTS/MIMO_TTS/DOUBAO_TTS/OPENAI_TTS/VITS_TTS"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_url_template"
|
||
description: {
|
||
zh: "可选,HTTP 类 TTS 的 URL 模板。HTTP 系列仅支持 `{text}`、`{rate}`、`{pitch}`、`{voice}`"
|
||
en: "Optional URL template for HTTP-style TTS providers. HTTP-style providers support only `{text}`, `{rate}`, `{pitch}`, `{voice}`"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_api_key"
|
||
description: {
|
||
zh: "可选,TTS API Key"
|
||
en: "Optional TTS API key"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_headers"
|
||
description: {
|
||
zh: "可选,HTTP 类 TTS headers 的 JSON 对象字符串"
|
||
en: "Optional JSON object string for HTTP-style TTS headers"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_http_method"
|
||
description: {
|
||
zh: "可选,GET/POST"
|
||
en: "Optional, GET/POST"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_request_body"
|
||
description: {
|
||
zh: "可选,HTTP 类 TTS 的 POST body 模板。仅支持 `{text}`、`{rate}`、`{pitch}`、`{voice}`"
|
||
en: "Optional POST body template for HTTP-style TTS providers. Supports only `{text}`, `{rate}`, `{pitch}`, `{voice}`"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_content_type"
|
||
description: {
|
||
zh: "可选,TTS Content-Type"
|
||
en: "Optional TTS content type"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_locale"
|
||
description: {
|
||
zh: "可选,TTS 语言标签,例如 zh-CN 或 en-US"
|
||
en: "Optional TTS locale tag, for example zh-CN or en-US"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_voice_id"
|
||
description: {
|
||
zh: "可选,TTS 音色 ID。MIMO voiceclone 可填写完整 data:audio/...;base64,... 音频样本"
|
||
en: "Optional TTS voice id. For MIMO voiceclone, this may be the full data:audio/...;base64,... audio sample"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_model_name"
|
||
description: {
|
||
zh: "可选,TTS 模型名"
|
||
en: "Optional TTS model name"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_vits_package_path"
|
||
description: {
|
||
zh: "可选,本地 VITS/Piper TTS 模型包路径,支持 .zip 文件或已解压目录"
|
||
en: "Optional local VITS/Piper TTS package path, supporting a .zip file or extracted package directory"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_vits_speaker_id"
|
||
description: {
|
||
zh: "可选,VITS/Piper TTS 模型包需要的数字 speaker id"
|
||
en: "Optional numeric speaker id required by the VITS/Piper TTS package"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_vits_options"
|
||
description: {
|
||
zh: "可选,VITS/Piper TTS 模型包参数 JSON 对象字符串"
|
||
en: "Optional JSON object string for VITS/Piper TTS package options"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_response_pipeline"
|
||
description: {
|
||
zh: "可选,HTTP TTS 响应处理管线 JSON 数组字符串。留空或 `[]` 时保持旧行为,直接把响应体当音频"
|
||
en: "Optional HTTP TTS response pipeline JSON array string. Leave empty or use `[]` to keep the old direct-audio behavior"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_cleaner_regexs"
|
||
description: {
|
||
zh: "可选,TTS 清理正则列表 JSON 数组字符串"
|
||
en: "Optional JSON array string for TTS cleaner regex list"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_speech_rate"
|
||
description: {
|
||
zh: "可选,TTS 语速"
|
||
en: "Optional TTS speech rate"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "tts_pitch"
|
||
description: {
|
||
zh: "可选,TTS 音调"
|
||
en: "Optional TTS pitch"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "stt_service_type"
|
||
description: {
|
||
zh: "可选,SHERPA_NCNN/OPENAI_STT/DEEPGRAM_STT"
|
||
en: "Optional, SHERPA_NCNN/OPENAI_STT/DEEPGRAM_STT"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "stt_endpoint_url"
|
||
description: {
|
||
zh: "可选,STT endpoint URL"
|
||
en: "Optional STT endpoint URL"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "stt_api_key"
|
||
description: {
|
||
zh: "可选,STT API Key"
|
||
en: "Optional STT API key"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "stt_model_name"
|
||
description: {
|
||
zh: "可选,STT 模型名"
|
||
en: "Optional STT model name"
|
||
}
|
||
type: string
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "test_tts_playback"
|
||
description: {
|
||
zh: '''按当前 TTS 配置播放一次测试文本。'''
|
||
en: '''Play one TTS test utterance with the current configuration.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "text"
|
||
description: {
|
||
zh: "必填,要播放的测试文本"
|
||
en: "Required test text to play"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "interrupt"
|
||
description: {
|
||
zh: "可选,播放前是否先中断当前播报"
|
||
en: "Optional, interrupt current playback before this test"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "speech_rate"
|
||
description: {
|
||
zh: "可选,仅本次测试生效的语速覆盖值"
|
||
en: "Optional speech-rate override for this test only"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "pitch"
|
||
description: {
|
||
zh: "可选,仅本次测试生效的音调覆盖值"
|
||
en: "Optional pitch override for this test only"
|
||
}
|
||
type: number
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "list_model_configs"
|
||
description: {
|
||
zh: '''列出全部模型配置及功能模型绑定关系。'''
|
||
en: '''List all model configs and function-model bindings.'''
|
||
}
|
||
parameters: []
|
||
},
|
||
{
|
||
name: "create_model_config"
|
||
description: {
|
||
zh: '''新增模型配置(可带初始化字段)。'''
|
||
en: '''Create a model config (optional initialization fields).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "name"
|
||
description: {
|
||
zh: "可选,配置名称"
|
||
en: "Optional config name"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_provider_type"
|
||
description: {
|
||
zh: "可选,提供商枚举名(如 OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP;其中 LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP 为本地模型链路)"
|
||
en: "Optional provider enum name (e.g. OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP; LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP are local-model providers)"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_endpoint"
|
||
description: {
|
||
zh: "可选,API端点"
|
||
en: "Optional API endpoint"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_key"
|
||
description: {
|
||
zh: "可选,API Key"
|
||
en: "Optional API key"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "model_name"
|
||
description: {
|
||
zh: "可选,模型名(多个可逗号分隔)"
|
||
en: "Optional model name (comma-separated for multiple models)"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_tokens_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 max_tokens 参数"
|
||
en: "Optional switch for max_tokens"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_tokens"
|
||
description: {
|
||
zh: "可选,max_tokens 数值"
|
||
en: "Optional max_tokens value"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "temperature_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 temperature 参数"
|
||
en: "Optional switch for temperature"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "temperature"
|
||
description: {
|
||
zh: "可选,temperature 数值"
|
||
en: "Optional temperature value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_p_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 top_p 参数"
|
||
en: "Optional switch for top_p"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_p"
|
||
description: {
|
||
zh: "可选,top_p 数值"
|
||
en: "Optional top_p value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_k_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 top_k 参数"
|
||
en: "Optional switch for top_k"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_k"
|
||
description: {
|
||
zh: "可选,top_k 数值"
|
||
en: "Optional top_k value"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "presence_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 presence_penalty 参数"
|
||
en: "Optional switch for presence_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "presence_penalty"
|
||
description: {
|
||
zh: "可选,presence_penalty 数值"
|
||
en: "Optional presence_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "frequency_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 frequency_penalty 参数"
|
||
en: "Optional switch for frequency_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "frequency_penalty"
|
||
description: {
|
||
zh: "可选,frequency_penalty 数值"
|
||
en: "Optional frequency_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "repetition_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 repetition_penalty 参数"
|
||
en: "Optional switch for repetition_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "repetition_penalty"
|
||
description: {
|
||
zh: "可选,repetition_penalty 数值"
|
||
en: "Optional repetition_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "custom_parameters"
|
||
description: {
|
||
zh: "可选,自定义参数 JSON 字符串"
|
||
en: "Optional custom parameters JSON string"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "custom_headers"
|
||
description: {
|
||
zh: "可选,自定义请求头 JSON 对象字符串"
|
||
en: "Optional custom request headers JSON object string"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "context_length"
|
||
description: {
|
||
zh: "可选,上下文长度倍率"
|
||
en: "Optional context length multiplier"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_context_length"
|
||
description: {
|
||
zh: "可选,最大上下文长度倍率"
|
||
en: "Optional max context length multiplier"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_max_context_mode"
|
||
description: {
|
||
zh: "可选,是否启用最大上下文模式"
|
||
en: "Optional max-context mode switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_token_threshold"
|
||
description: {
|
||
zh: "可选,总结触发阈值"
|
||
en: "Optional summary trigger threshold"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary"
|
||
description: {
|
||
zh: "可选,是否启用总结"
|
||
en: "Optional summary switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary_by_message_count"
|
||
description: {
|
||
zh: "可选,是否按消息数触发总结"
|
||
en: "Optional summary-by-message-count switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_message_count_threshold"
|
||
description: {
|
||
zh: "可选,按消息数总结的阈值"
|
||
en: "Optional message-count threshold for summary"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_image_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接图片处理"
|
||
en: "Optional direct image processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_audio_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接音频处理"
|
||
en: "Optional direct audio processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_video_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接视频处理"
|
||
en: "Optional direct video processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_google_search"
|
||
description: {
|
||
zh: "可选,是否启用 Google Search"
|
||
en: "Optional Google Search switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_claude_1h_prompt_cache"
|
||
description: {
|
||
zh: "可选,是否启用 Claude 1h Prompt Cache"
|
||
en: "Optional Claude 1h prompt cache switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_tool_call"
|
||
description: {
|
||
zh: "可选,是否开启Tool Call"
|
||
en: "Optional tool-call switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "mnn_forward_type"
|
||
description: {
|
||
zh: "可选,MNN 前向类型"
|
||
en: "Optional MNN forward type"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "mnn_thread_count"
|
||
description: {
|
||
zh: "可选,MNN 线程数"
|
||
en: "Optional MNN thread count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_thread_count"
|
||
description: {
|
||
zh: "可选,llama.cpp 线程数"
|
||
en: "Optional llama.cpp thread count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_context_size"
|
||
description: {
|
||
zh: "可选,llama.cpp 上下文长度"
|
||
en: "Optional llama.cpp context size"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_gpu_layers"
|
||
description: {
|
||
zh: "可选,llama.cpp GPU 层数"
|
||
en: "Optional llama.cpp GPU layer count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "request_limit_per_minute"
|
||
description: {
|
||
zh: "可选,每分钟请求限制"
|
||
en: "Optional request-per-minute limit"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_concurrent_requests"
|
||
description: {
|
||
zh: "可选,最大并发请求数"
|
||
en: "Optional max concurrent requests"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "update_model_config"
|
||
description: {
|
||
zh: '''按 config_id 修改模型配置。'''
|
||
en: '''Update model config by config_id.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "config_id"
|
||
description: {
|
||
zh: "目标配置ID"
|
||
en: "Target config id"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "name"
|
||
description: {
|
||
zh: "可选,配置名称"
|
||
en: "Optional config name"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_provider_type"
|
||
description: {
|
||
zh: "可选,提供商枚举名(如 OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP;其中 LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP 为本地模型链路)"
|
||
en: "Optional provider enum name (e.g. OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP; LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP are local-model providers)"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_endpoint"
|
||
description: {
|
||
zh: "可选,API端点"
|
||
en: "Optional API endpoint"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "api_key"
|
||
description: {
|
||
zh: "可选,API Key"
|
||
en: "Optional API key"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "model_name"
|
||
description: {
|
||
zh: "可选,模型名(多个可逗号分隔)"
|
||
en: "Optional model name (comma-separated for multiple models)"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_tokens_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 max_tokens 参数"
|
||
en: "Optional switch for max_tokens"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_tokens"
|
||
description: {
|
||
zh: "可选,max_tokens 数值"
|
||
en: "Optional max_tokens value"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "temperature_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 temperature 参数"
|
||
en: "Optional switch for temperature"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "temperature"
|
||
description: {
|
||
zh: "可选,temperature 数值"
|
||
en: "Optional temperature value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_p_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 top_p 参数"
|
||
en: "Optional switch for top_p"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_p"
|
||
description: {
|
||
zh: "可选,top_p 数值"
|
||
en: "Optional top_p value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_k_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 top_k 参数"
|
||
en: "Optional switch for top_k"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "top_k"
|
||
description: {
|
||
zh: "可选,top_k 数值"
|
||
en: "Optional top_k value"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "presence_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 presence_penalty 参数"
|
||
en: "Optional switch for presence_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "presence_penalty"
|
||
description: {
|
||
zh: "可选,presence_penalty 数值"
|
||
en: "Optional presence_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "frequency_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 frequency_penalty 参数"
|
||
en: "Optional switch for frequency_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "frequency_penalty"
|
||
description: {
|
||
zh: "可选,frequency_penalty 数值"
|
||
en: "Optional frequency_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "repetition_penalty_enabled"
|
||
description: {
|
||
zh: "可选,是否启用 repetition_penalty 参数"
|
||
en: "Optional switch for repetition_penalty"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "repetition_penalty"
|
||
description: {
|
||
zh: "可选,repetition_penalty 数值"
|
||
en: "Optional repetition_penalty value"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "custom_parameters"
|
||
description: {
|
||
zh: "可选,自定义参数 JSON 字符串"
|
||
en: "Optional custom parameters JSON string"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "custom_headers"
|
||
description: {
|
||
zh: "可选,自定义请求头 JSON 对象字符串"
|
||
en: "Optional custom request headers JSON object string"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "context_length"
|
||
description: {
|
||
zh: "可选,上下文长度倍率"
|
||
en: "Optional context length multiplier"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_context_length"
|
||
description: {
|
||
zh: "可选,最大上下文长度倍率"
|
||
en: "Optional max context length multiplier"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_max_context_mode"
|
||
description: {
|
||
zh: "可选,是否启用最大上下文模式"
|
||
en: "Optional max-context mode switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_token_threshold"
|
||
description: {
|
||
zh: "可选,总结触发阈值"
|
||
en: "Optional summary trigger threshold"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary"
|
||
description: {
|
||
zh: "可选,是否启用总结"
|
||
en: "Optional summary switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary_by_message_count"
|
||
description: {
|
||
zh: "可选,是否按消息数触发总结"
|
||
en: "Optional summary-by-message-count switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_message_count_threshold"
|
||
description: {
|
||
zh: "可选,按消息数总结的阈值"
|
||
en: "Optional message-count threshold for summary"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_image_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接图片处理"
|
||
en: "Optional direct image processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_audio_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接音频处理"
|
||
en: "Optional direct audio processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_direct_video_processing"
|
||
description: {
|
||
zh: "可选,是否启用直接视频处理"
|
||
en: "Optional direct video processing switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_google_search"
|
||
description: {
|
||
zh: "可选,是否启用 Google Search"
|
||
en: "Optional Google Search switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_claude_1h_prompt_cache"
|
||
description: {
|
||
zh: "可选,是否启用 Claude 1h Prompt Cache"
|
||
en: "Optional Claude 1h prompt cache switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_tool_call"
|
||
description: {
|
||
zh: "可选,是否开启Tool Call"
|
||
en: "Optional tool-call switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "mnn_forward_type"
|
||
description: {
|
||
zh: "可选,MNN 前向类型"
|
||
en: "Optional MNN forward type"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "mnn_thread_count"
|
||
description: {
|
||
zh: "可选,MNN 线程数"
|
||
en: "Optional MNN thread count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_thread_count"
|
||
description: {
|
||
zh: "可选,llama.cpp 线程数"
|
||
en: "Optional llama.cpp thread count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_context_size"
|
||
description: {
|
||
zh: "可选,llama.cpp 上下文长度"
|
||
en: "Optional llama.cpp context size"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "llama_gpu_layers"
|
||
description: {
|
||
zh: "可选,llama.cpp GPU 层数"
|
||
en: "Optional llama.cpp GPU layer count"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "request_limit_per_minute"
|
||
description: {
|
||
zh: "可选,每分钟请求限制"
|
||
en: "Optional request-per-minute limit"
|
||
}
|
||
type: integer
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_concurrent_requests"
|
||
description: {
|
||
zh: "可选,最大并发请求数"
|
||
en: "Optional max concurrent requests"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "delete_model_config"
|
||
description: {
|
||
zh: '''按 config_id 删除模型配置(默认配置不可删)。'''
|
||
en: '''Delete model config by config_id (default cannot be deleted).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "config_id"
|
||
description: {
|
||
zh: "目标配置ID"
|
||
en: "Target config id"
|
||
}
|
||
type: string
|
||
required: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "list_function_model_configs"
|
||
description: {
|
||
zh: '''仅列出功能模型绑定关系(功能 -> 配置 + 模型索引)。'''
|
||
en: '''List function model bindings only (function -> config + model index).'''
|
||
}
|
||
parameters: []
|
||
},
|
||
{
|
||
name: "get_function_model_config"
|
||
description: {
|
||
zh: '''查看某个功能当前绑定的单个模型配置详情。'''
|
||
en: '''Get one function's currently bound model config detail.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "function_type"
|
||
description: {
|
||
zh: "功能类型枚举名"
|
||
en: "Function type enum name"
|
||
}
|
||
type: string
|
||
required: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "get_context_summary_config"
|
||
description: {
|
||
zh: '''获取某个功能当前绑定模型配置中的上下文总结参数。'''
|
||
en: '''Get context-summary settings from the model config bound to a function.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "function_type"
|
||
description: {
|
||
zh: "可选,功能类型;默认 CHAT"
|
||
en: "Optional function type; default CHAT"
|
||
}
|
||
type: string
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "set_context_summary_config"
|
||
description: {
|
||
zh: '''为某个功能绑定配置设置上下文总结参数(可选覆盖默认值)。'''
|
||
en: '''Set context-summary settings for a function binding (optional overrides supported).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "function_type"
|
||
description: {
|
||
zh: "可选,功能类型;默认 CHAT"
|
||
en: "Optional function type; default CHAT"
|
||
}
|
||
type: string
|
||
required: false
|
||
},
|
||
{
|
||
name: "context_length"
|
||
description: {
|
||
zh: "可选,基础上下文长度"
|
||
en: "Optional base context length"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "max_context_length"
|
||
description: {
|
||
zh: "可选,最大上下文长度"
|
||
en: "Optional max context length"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_max_context_mode"
|
||
description: {
|
||
zh: "可选,是否启用最大上下文模式"
|
||
en: "Optional max-context-mode switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_token_threshold"
|
||
description: {
|
||
zh: "可选,总结触发 token 阈值(0~1)"
|
||
en: "Optional token-ratio threshold for summary trigger (0~1)"
|
||
}
|
||
type: number
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary"
|
||
description: {
|
||
zh: "可选,是否启用上下文总结"
|
||
en: "Optional context-summary switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "enable_summary_by_message_count"
|
||
description: {
|
||
zh: "可选,是否启用按消息条数触发总结"
|
||
en: "Optional message-count summary trigger switch"
|
||
}
|
||
type: boolean
|
||
required: false
|
||
},
|
||
{
|
||
name: "summary_message_count_threshold"
|
||
description: {
|
||
zh: "可选,按消息条数触发总结阈值"
|
||
en: "Optional message-count threshold for summary trigger"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "set_function_model_config"
|
||
description: {
|
||
zh: '''为功能指定模型配置与模型索引。'''
|
||
en: '''Set model config and model index for a function.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "function_type"
|
||
description: {
|
||
zh: "功能类型枚举名"
|
||
en: "Function type enum name"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "config_id"
|
||
description: {
|
||
zh: "模型配置ID"
|
||
en: "Model config id"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "model_index"
|
||
description: {
|
||
zh: "可选,模型索引"
|
||
en: "Optional model index"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "test_model_config_connection"
|
||
description: {
|
||
zh: '''按设置页同等逻辑测试某个模型配置。'''
|
||
en: '''Run settings-UI-equivalent tests for one model config.'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "config_id"
|
||
description: {
|
||
zh: "目标配置ID"
|
||
en: "Target config id"
|
||
}
|
||
type: string
|
||
required: true
|
||
},
|
||
{
|
||
name: "model_index"
|
||
description: {
|
||
zh: "可选,模型索引"
|
||
en: "Optional model index"
|
||
}
|
||
type: integer
|
||
required: false
|
||
}
|
||
]
|
||
},
|
||
{
|
||
name: "ping_mcp"
|
||
description: {
|
||
zh: '''直通 use_package 的探测工具:用于快速测试某个 package 是否可被加载(MCP/Skill/Sandbox 三兼容)。'''
|
||
en: '''A pass-through probe for use_package: quickly test whether a package can be loaded (tri-compatible across MCP/Skill/Sandbox).'''
|
||
}
|
||
parameters: [
|
||
{
|
||
name: "package_name"
|
||
description: {
|
||
zh: "要探测的包名"
|
||
en: "Package name to probe"
|
||
}
|
||
type: string
|
||
required: true
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}*/
|
||
|
||
const operitEditorPackage = (function () {
|
||
|
||
async function operit_editor(params: { query?: string }) {
|
||
try {
|
||
const { query } = params ?? {};
|
||
complete({
|
||
success: true,
|
||
message: "配置排查手册已加载(MCP/Skill/Sandbox Package/沙盒包调试烧录/功能模型与模型配置/TTS-STT语音服务),将按配置链路执行排查。",
|
||
data: {
|
||
query: query ?? ""
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function how_make_skill() {
|
||
try {
|
||
const locale = (getLang() ?? "").toLowerCase();
|
||
const lang: "zh" | "en" | "both" = locale.startsWith("zh") ? "zh" : locale.startsWith("en") ? "en" : "both";
|
||
|
||
const zh = `如何制作 skill(简版)
|
||
1. 先创建目录:/sdcard/Download/Operit/skills/<skill_name>/
|
||
2. 必备文件:SKILL.md
|
||
3. 在 SKILL.md 顶部用 Markdown 元数据(frontmatter)写 name、description,例如:
|
||
---
|
||
name: your_skill_name
|
||
description: 用一句话说明这个 skill 做什么
|
||
---
|
||
4. 元数据后再写正文:适用场景、执行步骤、约束边界、期望输出
|
||
5. 可选内容:scripts/、templates/、examples/、assets/;在 SKILL.md 里用相对路径引用
|
||
6. 实践建议:优先下载现成 skill,直接解压过来,并确保目录下有 SKILL.md。`;
|
||
|
||
const en = `How to make a skill (quick guide)
|
||
1. Create a directory: /sdcard/Download/Operit/skills/<skill_name>/
|
||
2. Required file: SKILL.md
|
||
3. At the top of SKILL.md, use Markdown metadata (frontmatter) for name and description, for example:
|
||
---
|
||
name: your_skill_name
|
||
description: one-line summary of what this skill does
|
||
---
|
||
4. After metadata, write the main sections: use cases, workflow steps, constraints, expected outputs
|
||
5. Optional content: scripts/, templates/, examples/, assets/; reference them from SKILL.md using relative paths
|
||
6. Practical tip: download an existing skill, extract it directly, and ensure the directory contains SKILL.md`;
|
||
|
||
const message = lang === "zh" ? zh : lang === "en" ? en : `${zh}\n\n---\n\n${en}`;
|
||
|
||
complete({
|
||
success: true,
|
||
message,
|
||
data: {
|
||
lang,
|
||
zh,
|
||
en
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function list_sandbox_packages() {
|
||
try {
|
||
const result = await Tools.SoftwareSettings.listSandboxPackages();
|
||
complete({
|
||
success: true,
|
||
message: `Sandbox package list fetched: ${String(result.totalCount)} package(s).`,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function set_sandbox_package_enabled(params?: { package_name?: string; enabled?: boolean | string | number }) {
|
||
try {
|
||
const packageName = params?.package_name ?? "";
|
||
const enabled = params?.enabled ?? false;
|
||
const result = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageName, enabled);
|
||
complete({
|
||
success: true,
|
||
message: result.message || "Sandbox package switch updated.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
const SANDBOX_EXTERNAL_PACKAGES_DIR = "/sdcard/Android/data/com.ai.assistance.operit/files/packages";
|
||
const TOOLPKG_DEBUG_INSTALL_ACTION = "com.ai.assistance.operit.DEBUG_INSTALL_TOOLPKG";
|
||
const TOOLPKG_DEBUG_INSTALL_COMPONENT =
|
||
"com.ai.assistance.operit/.core.tools.packTool.ToolPkgDebugInstallReceiver";
|
||
const SANDBOX_SCRIPT_EXECUTION_ACTION = "com.ai.assistance.operit.EXECUTE_JS";
|
||
const SANDBOX_SCRIPT_EXECUTION_COMPONENT =
|
||
"com.ai.assistance.operit/com.ai.assistance.operit.core.tools.javascript.ScriptExecutionReceiver";
|
||
const SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT = "script";
|
||
const SANDBOX_SCRIPT_EXECUTION_MODE_CODE = "code";
|
||
const SANDBOX_JS_TEMP_DIR = "/sdcard/Android/data/com.ai.assistance.operit/js_temp";
|
||
const DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS = 1500;
|
||
const DEFAULT_TOOLPKG_INSTALL_WAIT_MS = 1500;
|
||
const DEFAULT_SANDBOX_SCRIPT_WAIT_MS = 15000;
|
||
const JS_METADATA_BLOCK_PATTERN = /\/\*\s*METADATA([\s\S]*?)\*\//m;
|
||
const JS_PACKAGE_NAME_PATTERN = /^\s*["']?name["']?\s*:\s*["']([^"']+)["']/m;
|
||
const TOOLPKG_ID_PATTERN = /^\s*["']?toolpkg_id["']?\s*:\s*["']([^"']+)["']/m;
|
||
const TOOLPKG_MAIN_PATTERN = /^\s*["']?main["']?\s*:\s*["']([^"']+)["']/m;
|
||
const TOOLPKG_SUBPACKAGE_ID_PATTERN = /^\s*["']?id["']?\s*:\s*["']([^"']+)["']/gm;
|
||
const TOOLPKG_SKIP_DIR_NAMES = new Set([".git", "__pycache__"]);
|
||
const TOOLPKG_SKIP_FILE_NAMES = new Set([".DS_Store", "Thumbs.db"]);
|
||
|
||
type SandboxPackageEntry = SandboxPackageResultItem;
|
||
type SandboxPackagesPayload = SandboxPackagesResultData;
|
||
|
||
type SandboxRefreshResult = {
|
||
payload: SandboxPackagesPayload | null;
|
||
packageEntry: SandboxPackageEntry | null;
|
||
};
|
||
|
||
function collect_related_package_load_errors(
|
||
payload: SandboxPackagesPayload | null,
|
||
packageName: string,
|
||
...relatedPaths: Array<string | null | undefined>
|
||
): Record<string, string> {
|
||
const normalizedPackageName = normalize_package_key(packageName);
|
||
const normalizedPaths = relatedPaths.map((path) => String(path ?? "").trim()).filter(Boolean);
|
||
const packageLoadErrors = payload?.packageLoadErrors;
|
||
if (!packageLoadErrors) {
|
||
return {};
|
||
}
|
||
|
||
return Object.fromEntries(
|
||
Object.entries(packageLoadErrors).filter(([key, value]) => {
|
||
const normalizedKey = normalize_package_key(key);
|
||
const message = String(value ?? "");
|
||
return (
|
||
normalizedKey === normalizedPackageName ||
|
||
message.toLowerCase().includes(normalizedPackageName) ||
|
||
normalizedPaths.some((path) => message.includes(path))
|
||
);
|
||
})
|
||
);
|
||
}
|
||
|
||
type JsPackageSourceInfo = {
|
||
packageName: string;
|
||
metadataBlock: string;
|
||
};
|
||
|
||
type ToolPkgManifestSummary = {
|
||
packageId: string;
|
||
mainEntry: string;
|
||
subpackageIds: string[];
|
||
};
|
||
|
||
type ToolPkgResolvedSource = {
|
||
sourceKind: "folder" | "archive";
|
||
sourcePath: string;
|
||
folderPath: string;
|
||
manifestPath: string;
|
||
packageId: string;
|
||
mainEntry: string;
|
||
subpackageIds: string[];
|
||
archivePath?: string;
|
||
temporaryPaths: string[];
|
||
};
|
||
|
||
function normalize_android_path(raw?: string): string {
|
||
const normalized = String(raw ?? "").trim().replace(/\\/g, "/");
|
||
if (!normalized) return "";
|
||
if (/^[a-zA-Z]+:\/\//.test(normalized)) return normalized;
|
||
if (normalized.startsWith("/")) return normalized;
|
||
if (normalized.startsWith("sdcard/")) return `/${normalized}`;
|
||
if (normalized.startsWith("Android/") || normalized.startsWith("Download/")) {
|
||
return `/sdcard/${normalized}`;
|
||
}
|
||
return normalized;
|
||
}
|
||
|
||
function normalize_package_key(raw?: string): string {
|
||
return String(raw ?? "").trim().toLowerCase();
|
||
}
|
||
|
||
function normalize_directory_path(path: string): string {
|
||
const normalized = normalize_android_path(path).replace(/\/+/g, "/");
|
||
if (normalized === "/") return normalized;
|
||
return normalized.replace(/\/+$/, "");
|
||
}
|
||
|
||
function same_android_path(left: string, right: string): boolean {
|
||
return normalize_directory_path(left) === normalize_directory_path(right);
|
||
}
|
||
|
||
function path_dirname(path: string): string {
|
||
const normalized = normalize_directory_path(path);
|
||
const index = normalized.lastIndexOf("/");
|
||
if (index < 0) return "";
|
||
if (index === 0) return "/";
|
||
return normalized.slice(0, index);
|
||
}
|
||
|
||
function path_basename(path: string): string {
|
||
const normalized = normalize_directory_path(path);
|
||
const index = normalized.lastIndexOf("/");
|
||
return index >= 0 ? normalized.slice(index + 1) : normalized;
|
||
}
|
||
|
||
function path_join(...parts: string[]): string {
|
||
const filtered = parts
|
||
.map((part) => String(part ?? "").trim().replace(/\\/g, "/"))
|
||
.filter(Boolean);
|
||
if (filtered.length === 0) return "";
|
||
|
||
const leadingSlash = filtered[0].startsWith("/");
|
||
const joined = filtered.map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
||
return leadingSlash ? `/${joined}` : joined;
|
||
}
|
||
|
||
function safe_debug_file_stem(raw: string, fallback: string): string {
|
||
const normalized = String(raw ?? "").trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[_\.]+|[_\.]+$/g, "");
|
||
return normalized || fallback;
|
||
}
|
||
|
||
function parse_boolean_like(value: unknown, defaultValue: boolean): boolean {
|
||
if (value === undefined || value === null || value === "") return defaultValue;
|
||
if (typeof value === "boolean") return value;
|
||
if (typeof value === "number") return value !== 0;
|
||
|
||
const normalized = String(value).trim().toLowerCase();
|
||
if (!normalized) return defaultValue;
|
||
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
||
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
||
return defaultValue;
|
||
}
|
||
|
||
function parse_integer_like(value: unknown, defaultValue: number): number {
|
||
if (value === undefined || value === null || value === "") return defaultValue;
|
||
const parsed = Number(value);
|
||
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : defaultValue;
|
||
}
|
||
|
||
async function android_path_exists(path: string): Promise<boolean> {
|
||
const result = await Tools.Files.exists(path, "android");
|
||
return result.exists;
|
||
}
|
||
|
||
async function get_android_file_type(path: string): Promise<string> {
|
||
const result = await Tools.Files.info(path, "android");
|
||
return result.fileType.trim().toLowerCase();
|
||
}
|
||
|
||
async function ensure_android_directory(path: string) {
|
||
await Tools.Files.mkdir(path, true, "android");
|
||
}
|
||
|
||
async function delete_android_path_if_exists(path: string) {
|
||
if (!path) return;
|
||
if (!(await android_path_exists(path))) return;
|
||
await Tools.Files.deleteFile(path, true, "android");
|
||
}
|
||
|
||
async function cleanup_android_paths(paths: string[]) {
|
||
const uniquePaths = Array.from(new Set(paths.map((path) => normalize_android_path(path)).filter(Boolean)));
|
||
uniquePaths.sort((left, right) => right.length - left.length);
|
||
|
||
for (const path of uniquePaths) {
|
||
try {
|
||
await delete_android_path_if_exists(path);
|
||
} catch {
|
||
// Ignore cleanup failures.
|
||
}
|
||
}
|
||
}
|
||
|
||
async function read_android_text_file(path: string): Promise<string> {
|
||
const result = await Tools.Files.read({ path, environment: "android" });
|
||
return result.content;
|
||
}
|
||
|
||
function parse_json_record(raw: string): Record<string, unknown> | null {
|
||
if (!raw) return null;
|
||
try {
|
||
const parsed = JSON.parse(raw);
|
||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
||
return parsed as Record<string, unknown>;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function find_sandbox_package_entry(
|
||
payload: SandboxPackagesPayload | null,
|
||
packageName: string
|
||
): SandboxPackageEntry | null {
|
||
const targetKey = normalize_package_key(packageName);
|
||
const packages = payload?.packages ?? [];
|
||
return (
|
||
packages.find((entry) => normalize_package_key(entry?.packageName) === targetKey) ?? null
|
||
);
|
||
}
|
||
|
||
async function refresh_sandbox_packages_until(
|
||
packageName: string,
|
||
timeoutMs: number
|
||
): Promise<SandboxRefreshResult> {
|
||
const deadline = Date.now() + Math.max(0, timeoutMs);
|
||
let lastPayload: SandboxPackagesPayload | null = null;
|
||
let lastEntry: SandboxPackageEntry | null = null;
|
||
|
||
while (true) {
|
||
lastPayload = await Tools.SoftwareSettings.listSandboxPackages();
|
||
lastEntry = find_sandbox_package_entry(lastPayload, packageName);
|
||
if (lastEntry) {
|
||
return {
|
||
payload: lastPayload,
|
||
packageEntry: lastEntry
|
||
};
|
||
}
|
||
|
||
if (Date.now() >= deadline) {
|
||
return {
|
||
payload: lastPayload,
|
||
packageEntry: lastEntry
|
||
};
|
||
}
|
||
|
||
await Tools.System.sleep(Math.min(300, Math.max(50, deadline - Date.now())));
|
||
}
|
||
}
|
||
|
||
async function wait_for_android_file(path: string, timeoutMs: number): Promise<boolean> {
|
||
const deadline = Date.now() + Math.max(0, timeoutMs);
|
||
|
||
while (true) {
|
||
if (await android_path_exists(path)) {
|
||
return true;
|
||
}
|
||
if (Date.now() >= deadline) {
|
||
return false;
|
||
}
|
||
await Tools.System.sleep(Math.min(300, Math.max(50, deadline - Date.now())));
|
||
}
|
||
}
|
||
|
||
function parse_json_text(raw: string): unknown | null {
|
||
const text = String(raw ?? "").trim();
|
||
if (!text) return null;
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function extract_js_metadata_block(sourceText: string, sourcePath: string): string {
|
||
const match = JS_METADATA_BLOCK_PATTERN.exec(sourceText);
|
||
if (!match) {
|
||
throw new Error(`Missing METADATA block: ${sourcePath}`);
|
||
}
|
||
return match[1].trim();
|
||
}
|
||
|
||
function parse_js_package_source(sourceText: string, sourcePath: string): JsPackageSourceInfo {
|
||
const metadataBlock = extract_js_metadata_block(sourceText, sourcePath);
|
||
const packageName = JS_PACKAGE_NAME_PATTERN.exec(metadataBlock)?.[1]?.trim() ?? "";
|
||
if (!packageName) {
|
||
throw new Error(`Missing package metadata name: ${sourcePath}`);
|
||
}
|
||
return {
|
||
packageName,
|
||
metadataBlock
|
||
};
|
||
}
|
||
|
||
async function delete_duplicate_external_js_package_files(packageName: string, keepPath: string) {
|
||
const removedPaths: string[] = [];
|
||
const listing = await Tools.Files.list(SANDBOX_EXTERNAL_PACKAGES_DIR, "android");
|
||
|
||
for (const entry of listing?.entries ?? []) {
|
||
const entryName = String(entry?.name ?? "").trim();
|
||
if (!entryName || entry?.isDirectory || !entryName.toLowerCase().endsWith(".js")) {
|
||
continue;
|
||
}
|
||
|
||
const candidatePath = path_join(SANDBOX_EXTERNAL_PACKAGES_DIR, entryName);
|
||
if (same_android_path(candidatePath, keepPath)) {
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
const candidateText = await read_android_text_file(candidatePath);
|
||
const candidateInfo = parse_js_package_source(candidateText, candidatePath);
|
||
if (normalize_package_key(candidateInfo.packageName) !== normalize_package_key(packageName)) {
|
||
continue;
|
||
}
|
||
|
||
await Tools.Files.deleteFile(candidatePath, false, "android");
|
||
removedPaths.push(candidatePath);
|
||
} catch {
|
||
// Ignore files that cannot be parsed as sandbox packages.
|
||
}
|
||
}
|
||
|
||
return removedPaths;
|
||
}
|
||
|
||
function parse_toolpkg_manifest_text(text: string, manifestPath: string): ToolPkgManifestSummary {
|
||
let packageId = "";
|
||
let mainEntry = "";
|
||
let subpackageIds: string[] = [];
|
||
|
||
try {
|
||
const parsed = JSON.parse(text) as {
|
||
toolpkg_id?: unknown;
|
||
main?: unknown;
|
||
subpackages?: Array<{ id?: unknown }>;
|
||
};
|
||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||
packageId = String(parsed.toolpkg_id ?? "").trim();
|
||
mainEntry = String(parsed.main ?? "").trim();
|
||
if (Array.isArray(parsed.subpackages)) {
|
||
subpackageIds = parsed.subpackages
|
||
.map((subpackage) => String(subpackage?.id ?? "").trim())
|
||
.filter(Boolean);
|
||
}
|
||
}
|
||
} catch {
|
||
// HJSON-like manifests will fall back to regex parsing below.
|
||
}
|
||
|
||
if (!packageId) {
|
||
packageId = TOOLPKG_ID_PATTERN.exec(text)?.[1]?.trim() ?? "";
|
||
}
|
||
if (!mainEntry) {
|
||
mainEntry = TOOLPKG_MAIN_PATTERN.exec(text)?.[1]?.trim() ?? "";
|
||
}
|
||
if (subpackageIds.length === 0) {
|
||
const matches: string[] = [];
|
||
const pattern = new RegExp(TOOLPKG_SUBPACKAGE_ID_PATTERN.source, TOOLPKG_SUBPACKAGE_ID_PATTERN.flags);
|
||
let match: RegExpExecArray | null;
|
||
while ((match = pattern.exec(text)) !== null) {
|
||
const subpackageId = match[1]?.trim() ?? "";
|
||
if (subpackageId) {
|
||
matches.push(subpackageId);
|
||
}
|
||
}
|
||
subpackageIds = matches;
|
||
}
|
||
|
||
if (!packageId) {
|
||
throw new Error(`manifest.toolpkg_id is required: ${manifestPath}`);
|
||
}
|
||
if (!mainEntry) {
|
||
throw new Error(`manifest.main is required: ${manifestPath}`);
|
||
}
|
||
|
||
return {
|
||
packageId,
|
||
mainEntry: mainEntry.replace(/\\/g, "/").replace(/^\/+/, ""),
|
||
subpackageIds: Array.from(new Set(subpackageIds))
|
||
};
|
||
}
|
||
|
||
async function find_toolpkg_manifest_in_folder(folderPath: string): Promise<string> {
|
||
const manifestJson = path_join(folderPath, "manifest.json");
|
||
if (await android_path_exists(manifestJson)) {
|
||
return manifestJson;
|
||
}
|
||
|
||
const manifestHjson = path_join(folderPath, "manifest.hjson");
|
||
if (await android_path_exists(manifestHjson)) {
|
||
return manifestHjson;
|
||
}
|
||
|
||
throw new Error(`Missing manifest.json or manifest.hjson in folder: ${folderPath}`);
|
||
}
|
||
|
||
async function resolve_toolpkg_source(rawSourcePath: string): Promise<ToolPkgResolvedSource> {
|
||
const sourcePath = normalize_android_path(rawSourcePath);
|
||
if (!sourcePath) {
|
||
throw new Error("Missing required parameter: source_path");
|
||
}
|
||
if (!(await android_path_exists(sourcePath))) {
|
||
throw new Error(`Source path does not exist: ${sourcePath}`);
|
||
}
|
||
|
||
const sourceType = await get_android_file_type(sourcePath);
|
||
let sourceKind: "folder" | "archive" = "folder";
|
||
let folderPath = sourcePath;
|
||
let archivePath: string | undefined;
|
||
const temporaryPaths: string[] = [];
|
||
|
||
const lowerBaseName = path_basename(sourcePath).toLowerCase();
|
||
if (sourceType === "directory") {
|
||
folderPath = sourcePath;
|
||
} else if (sourceType === "file" && (lowerBaseName === "manifest.json" || lowerBaseName === "manifest.hjson")) {
|
||
folderPath = path_dirname(sourcePath);
|
||
} else if (sourceType === "file" && lowerBaseName.endsWith(".toolpkg")) {
|
||
sourceKind = "archive";
|
||
archivePath = sourcePath;
|
||
const tempExtractDir = path_join(
|
||
OPERIT_CLEAN_ON_EXIT_DIR,
|
||
`operit_editor_toolpkg_extract_${safe_debug_file_stem(lowerBaseName.replace(/\.toolpkg$/i, ""), "toolpkg")}_${Date.now()}`
|
||
);
|
||
await ensure_android_directory(tempExtractDir);
|
||
await Tools.Files.unzip(sourcePath, tempExtractDir, "android");
|
||
folderPath = tempExtractDir;
|
||
temporaryPaths.push(tempExtractDir);
|
||
} else {
|
||
throw new Error(
|
||
"ToolPkg source must be a folder, manifest.json/manifest.hjson, or an existing .toolpkg file"
|
||
);
|
||
}
|
||
|
||
const manifestPath = await find_toolpkg_manifest_in_folder(folderPath);
|
||
const manifestText = await read_android_text_file(manifestPath);
|
||
const manifest = parse_toolpkg_manifest_text(manifestText, manifestPath);
|
||
const mainPath = path_join(folderPath, manifest.mainEntry);
|
||
if (!(await android_path_exists(mainPath))) {
|
||
throw new Error(`manifest.main file does not exist: ${manifestPath} -> ${manifest.mainEntry}`);
|
||
}
|
||
|
||
return {
|
||
sourceKind,
|
||
sourcePath,
|
||
folderPath,
|
||
manifestPath,
|
||
packageId: manifest.packageId,
|
||
mainEntry: manifest.mainEntry,
|
||
subpackageIds: manifest.subpackageIds,
|
||
archivePath,
|
||
temporaryPaths
|
||
};
|
||
}
|
||
|
||
async function build_toolpkg_archive_from_folder(source: ToolPkgResolvedSource) {
|
||
const tempBuildDir = path_join(
|
||
OPERIT_CLEAN_ON_EXIT_DIR,
|
||
`operit_editor_toolpkg_build_${safe_debug_file_stem(source.packageId, "toolpkg")}_${Date.now()}`
|
||
);
|
||
await ensure_android_directory(tempBuildDir);
|
||
const archivePath = path_join(tempBuildDir, `${safe_debug_file_stem(source.packageId, "toolpkg")}.toolpkg`);
|
||
await Tools.Files.zip(source.folderPath, archivePath, "android", false);
|
||
|
||
if (!(await android_path_exists(archivePath))) {
|
||
throw new Error(`Failed to create ToolPkg archive: ${archivePath}`);
|
||
}
|
||
|
||
return {
|
||
archivePath,
|
||
temporaryPaths: [tempBuildDir]
|
||
};
|
||
}
|
||
|
||
function parse_requested_package_ids(raw?: string): string[] {
|
||
const input = String(raw ?? "").trim();
|
||
if (!input) return [];
|
||
return Array.from(new Set(input.split(/[\r\n,]+/).map((item) => item.trim()).filter(Boolean)));
|
||
}
|
||
|
||
async function debug_install_js_package(params?: {
|
||
source_path?: string;
|
||
enable_after_install?: boolean | string | number;
|
||
activate_after_install?: boolean | string | number;
|
||
}) {
|
||
const logs: string[] = [];
|
||
const logStep = (message: string) => {
|
||
logs.push(message);
|
||
};
|
||
const finish = (payload: { success: boolean; message: string; data?: Record<string, unknown> }) =>
|
||
complete({
|
||
...payload,
|
||
data: {
|
||
...(payload.data ?? {}),
|
||
logs
|
||
}
|
||
});
|
||
|
||
try {
|
||
const sourcePath = normalize_android_path(params?.source_path);
|
||
logStep(`Resolved source_path -> ${sourcePath || "<empty>"}`);
|
||
if (!sourcePath) {
|
||
finish({
|
||
success: false,
|
||
message: "Missing required parameter: source_path"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const sourceType = await get_android_file_type(sourcePath);
|
||
logStep(`Source type detected -> ${sourceType}`);
|
||
if (sourceType !== "file") {
|
||
finish({
|
||
success: false,
|
||
message: `JS source must be a file: ${sourcePath}`
|
||
});
|
||
return;
|
||
}
|
||
if (!sourcePath.toLowerCase().endsWith(".js")) {
|
||
finish({
|
||
success: false,
|
||
message: `JS debug install only supports .js files: ${sourcePath}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
const sourceText = await read_android_text_file(sourcePath);
|
||
const packageInfo = parse_js_package_source(sourceText, sourcePath);
|
||
logStep(`Parsed package info -> packageName=${packageInfo.packageName}`);
|
||
const enableAfterInstall = parse_boolean_like(params?.enable_after_install, true);
|
||
const activateAfterInstall = parse_boolean_like(params?.activate_after_install, true);
|
||
const shouldEnable = enableAfterInstall || activateAfterInstall;
|
||
logStep(
|
||
`Install options -> enableAfterInstall=${String(enableAfterInstall)}, activateAfterInstall=${String(
|
||
activateAfterInstall
|
||
)}, shouldEnable=${String(shouldEnable)}`
|
||
);
|
||
|
||
await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR);
|
||
const targetPath = path_join(
|
||
SANDBOX_EXTERNAL_PACKAGES_DIR,
|
||
`${safe_debug_file_stem(packageInfo.packageName, "debug_js_package")}.js`
|
||
);
|
||
logStep(`Target install path -> ${targetPath}`);
|
||
const copied = !same_android_path(sourcePath, targetPath);
|
||
if (copied) {
|
||
logStep("Source and target differ; replacing target file before copy.");
|
||
await delete_android_path_if_exists(targetPath);
|
||
await Tools.Files.copy(sourcePath, targetPath, false, "android", "android");
|
||
logStep("Package file copied to external sandbox directory.");
|
||
} else {
|
||
logStep("Source path already matches target path; skipping file copy.");
|
||
}
|
||
if (!(await android_path_exists(targetPath))) {
|
||
finish({
|
||
success: false,
|
||
message: `Installed JS package file is missing after copy: ${targetPath}`
|
||
});
|
||
return;
|
||
}
|
||
logStep("Verified installed JS package file exists.");
|
||
|
||
const removedDuplicateFiles = await delete_duplicate_external_js_package_files(packageInfo.packageName, targetPath);
|
||
logStep(`Duplicate cleanup completed -> removed ${removedDuplicateFiles.length} file(s).`);
|
||
const refresh = await refresh_sandbox_packages_until(
|
||
packageInfo.packageName,
|
||
DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS
|
||
);
|
||
logStep(
|
||
`Sandbox refresh completed -> found=${String(Boolean(refresh.packageEntry))}, builtIn=${String(
|
||
refresh.packageEntry?.isBuiltIn ?? false
|
||
)}`
|
||
);
|
||
if (!refresh.packageEntry) {
|
||
finish({
|
||
success: false,
|
||
message: `Sandbox package did not appear after refresh: ${packageInfo.packageName}`,
|
||
data: {
|
||
package_name: packageInfo.packageName,
|
||
source_path: sourcePath,
|
||
target_path: targetPath,
|
||
refresh_result: refresh.payload
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
if (refresh.packageEntry.isBuiltIn) {
|
||
finish({
|
||
success: false,
|
||
message: `External JS package '${packageInfo.packageName}' did not take precedence over a built-in package with the same name.`,
|
||
data: {
|
||
package: refresh.packageEntry,
|
||
source_path: sourcePath,
|
||
target_path: targetPath
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
let enableResult: SandboxPackageUpdateResultData | null = null;
|
||
if (shouldEnable) {
|
||
logStep(`Enabling sandbox package -> ${packageInfo.packageName}`);
|
||
enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageInfo.packageName, true);
|
||
logStep(`Enable result -> ${enableResult.message || "<empty>"}`);
|
||
} else {
|
||
logStep("Enable step skipped by configuration.");
|
||
}
|
||
|
||
let activateResult: string | null = null;
|
||
if (activateAfterInstall) {
|
||
logStep(`Activating package via use_package -> ${packageInfo.packageName}`);
|
||
activateResult = await Tools.System.usePackage(packageInfo.packageName);
|
||
logStep(`Activation result -> ${activateResult || "<empty>"}`);
|
||
} else {
|
||
logStep("Activation step skipped by configuration.");
|
||
}
|
||
|
||
finish({
|
||
success: true,
|
||
message: `Debug JS package installed: ${packageInfo.packageName}`,
|
||
data: {
|
||
package_name: packageInfo.packageName,
|
||
source_path: sourcePath,
|
||
target_path: targetPath,
|
||
copied,
|
||
removed_duplicate_files: removedDuplicateFiles,
|
||
package: refresh.packageEntry,
|
||
enable_after_install: shouldEnable,
|
||
activate_after_install: activateAfterInstall,
|
||
enable_result: enableResult,
|
||
activate_result: activateResult,
|
||
refresh_result: refresh.payload
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
logStep(`Execution failed -> ${get_error_message(error)}`);
|
||
finish({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function debug_install_toolpkg(params?: {
|
||
source_path?: string;
|
||
reset_subpackage_states?: boolean | string | number;
|
||
activate_subpackages?: string;
|
||
wait_ms?: number | string;
|
||
}) {
|
||
const cleanupPaths: string[] = [];
|
||
const logs: string[] = [];
|
||
const logStep = (message: string) => {
|
||
logs.push(message);
|
||
};
|
||
const finish = (payload: { success: boolean; message: string; data?: Record<string, unknown> }) =>
|
||
complete({
|
||
...payload,
|
||
data: {
|
||
...(payload.data ?? {}),
|
||
logs
|
||
}
|
||
});
|
||
let finalPayload: { success: boolean; message: string; data?: Record<string, unknown> } | null = null;
|
||
|
||
try {
|
||
const resolvedSource = await resolve_toolpkg_source(params?.source_path ?? "");
|
||
logStep(
|
||
`Resolved ToolPkg source -> kind=${resolvedSource.sourceKind}, packageId=${resolvedSource.packageId}, sourcePath=${resolvedSource.sourcePath}`
|
||
);
|
||
cleanupPaths.push(...resolvedSource.temporaryPaths);
|
||
if (resolvedSource.temporaryPaths.length > 0) {
|
||
logStep(`Registered temporary paths -> ${resolvedSource.temporaryPaths.join(", ")}`);
|
||
}
|
||
|
||
let archivePath = resolvedSource.archivePath ?? "";
|
||
if (resolvedSource.sourceKind === "folder") {
|
||
logStep("Source is a folder; building temporary .toolpkg archive.");
|
||
const builtArchive = await build_toolpkg_archive_from_folder(resolvedSource);
|
||
archivePath = builtArchive.archivePath;
|
||
cleanupPaths.push(...builtArchive.temporaryPaths);
|
||
logStep(`Built archive -> ${archivePath}`);
|
||
}
|
||
|
||
await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR);
|
||
const targetPath = path_join(
|
||
SANDBOX_EXTERNAL_PACKAGES_DIR,
|
||
`${safe_debug_file_stem(resolvedSource.packageId, "toolpkg")}.toolpkg`
|
||
);
|
||
logStep(`Target install path -> ${targetPath}`);
|
||
if (!same_android_path(archivePath, targetPath)) {
|
||
logStep("Archive path differs from target; replacing target archive before copy.");
|
||
await delete_android_path_if_exists(targetPath);
|
||
await Tools.Files.copy(archivePath, targetPath, false, "android", "android");
|
||
logStep("ToolPkg archive copied to external sandbox directory.");
|
||
} else {
|
||
logStep("Archive path already matches target path; skipping archive copy.");
|
||
}
|
||
if (!(await android_path_exists(targetPath))) {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `Installed ToolPkg archive is missing after copy: ${targetPath}`
|
||
};
|
||
return;
|
||
}
|
||
logStep("Verified installed ToolPkg archive exists.");
|
||
|
||
const resetSubpackageStates = parse_boolean_like(params?.reset_subpackage_states, true);
|
||
const waitMs = parse_integer_like(params?.wait_ms, DEFAULT_TOOLPKG_INSTALL_WAIT_MS);
|
||
logStep(
|
||
`Install options -> resetSubpackageStates=${String(resetSubpackageStates)}, waitMs=${String(waitMs)}`
|
||
);
|
||
const broadcastResult = await Tools.System.sendBroadcast({
|
||
action: TOOLPKG_DEBUG_INSTALL_ACTION,
|
||
component: TOOLPKG_DEBUG_INSTALL_COMPONENT,
|
||
extras: {
|
||
package_name: resolvedSource.packageId,
|
||
file_path: targetPath,
|
||
reset_subpackage_states: resetSubpackageStates
|
||
}
|
||
});
|
||
logStep(`Debug install broadcast dispatched -> ${broadcastResult.result || "<empty>"}`);
|
||
|
||
const refresh = await refresh_sandbox_packages_until(resolvedSource.packageId, waitMs);
|
||
const relatedLoadErrors = collect_related_package_load_errors(
|
||
refresh.payload,
|
||
resolvedSource.packageId,
|
||
resolvedSource.sourcePath,
|
||
archivePath,
|
||
targetPath
|
||
);
|
||
logStep(
|
||
`Sandbox refresh completed -> found=${String(Boolean(refresh.packageEntry))}, builtIn=${String(
|
||
refresh.packageEntry?.isBuiltIn ?? false
|
||
)}`
|
||
);
|
||
if (Object.keys(relatedLoadErrors).length > 0) {
|
||
logStep(`Related load errors -> ${JSON.stringify(relatedLoadErrors)}`);
|
||
}
|
||
if (!refresh.packageEntry) {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `ToolPkg container did not appear after debug install: ${resolvedSource.packageId}`,
|
||
data: {
|
||
package_name: resolvedSource.packageId,
|
||
source_path: resolvedSource.sourcePath,
|
||
archive_path: targetPath,
|
||
broadcast_result: broadcastResult,
|
||
refresh_result: refresh.payload,
|
||
related_load_errors: relatedLoadErrors
|
||
}
|
||
};
|
||
return;
|
||
}
|
||
if (refresh.packageEntry.isBuiltIn) {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `Debug ToolPkg '${resolvedSource.packageId}' is shadowed by a built-in package with the same name.`,
|
||
data: {
|
||
package: refresh.packageEntry,
|
||
broadcast_result: broadcastResult,
|
||
refresh_result: refresh.payload,
|
||
related_load_errors: relatedLoadErrors
|
||
}
|
||
};
|
||
return;
|
||
}
|
||
|
||
const requestedSubpackages = parse_requested_package_ids(params?.activate_subpackages);
|
||
const knownSubpackageKeys = new Set(resolvedSource.subpackageIds.map(normalize_package_key));
|
||
const unknownRequestedSubpackages =
|
||
knownSubpackageKeys.size === 0
|
||
? []
|
||
: requestedSubpackages.filter((subpackageId) => !knownSubpackageKeys.has(normalize_package_key(subpackageId)));
|
||
const activationTargets = requestedSubpackages.filter(
|
||
(subpackageId) => !unknownRequestedSubpackages.includes(subpackageId)
|
||
);
|
||
logStep(
|
||
`Subpackage activation plan -> requested=${requestedSubpackages.join(", ") || "<none>"}, targets=${activationTargets.join(", ") || "<none>"}, unknown=${unknownRequestedSubpackages.join(", ") || "<none>"}`
|
||
);
|
||
|
||
const subpackageResults: Array<{
|
||
subpackage_id: string;
|
||
enable_result: SandboxPackageUpdateResultData;
|
||
activate_result: string;
|
||
}> = [];
|
||
|
||
for (const subpackageId of activationTargets) {
|
||
logStep(`Enabling subpackage -> ${subpackageId}`);
|
||
const enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(subpackageId, true);
|
||
logStep(`Subpackage enable result [${subpackageId}] -> ${enableResult.message || "<empty>"}`);
|
||
const activateResult = await Tools.System.usePackage(subpackageId);
|
||
logStep(`Subpackage activate result [${subpackageId}] -> ${activateResult || "<empty>"}`);
|
||
subpackageResults.push({
|
||
subpackage_id: subpackageId,
|
||
enable_result: enableResult,
|
||
activate_result: activateResult
|
||
});
|
||
}
|
||
|
||
finalPayload = {
|
||
success: true,
|
||
message: `Debug ToolPkg installed: ${resolvedSource.packageId}`,
|
||
data: {
|
||
package_name: resolvedSource.packageId,
|
||
source_kind: resolvedSource.sourceKind,
|
||
source_path: resolvedSource.sourcePath,
|
||
manifest_path: resolvedSource.manifestPath,
|
||
main_entry: resolvedSource.mainEntry,
|
||
archive_path: targetPath,
|
||
subpackage_ids: resolvedSource.subpackageIds,
|
||
reset_subpackage_states: resetSubpackageStates,
|
||
requested_activate_subpackages: requestedSubpackages,
|
||
unknown_requested_subpackages: unknownRequestedSubpackages,
|
||
subpackage_results: subpackageResults,
|
||
package: refresh.packageEntry,
|
||
broadcast_result: broadcastResult,
|
||
refresh_result: refresh.payload,
|
||
related_load_errors: relatedLoadErrors
|
||
}
|
||
};
|
||
} catch (error: unknown) {
|
||
logStep(`Execution failed -> ${get_error_message(error)}`);
|
||
finalPayload = {
|
||
success: false,
|
||
message: get_error_message(error)
|
||
};
|
||
} finally {
|
||
if (cleanupPaths.length > 0) {
|
||
logStep(`Cleaning temporary paths -> ${cleanupPaths.join(", ")}`);
|
||
}
|
||
await cleanup_android_paths(cleanupPaths);
|
||
if (cleanupPaths.length > 0) {
|
||
logStep("Temporary path cleanup completed.");
|
||
}
|
||
if (finalPayload) {
|
||
finish(finalPayload);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function debug_run_sandbox_script(params?: {
|
||
source_path?: string;
|
||
source_code?: string;
|
||
params_json?: string;
|
||
env_file_path?: string;
|
||
script_label?: string;
|
||
wait_ms?: number | string;
|
||
}) {
|
||
const logs: string[] = [];
|
||
const logStep = (message: string) => {
|
||
logs.push(message);
|
||
};
|
||
const finish = (payload: { success: boolean; message: string; data?: Record<string, unknown> }) =>
|
||
complete({
|
||
...payload,
|
||
data: {
|
||
...(payload.data ?? {}),
|
||
logs
|
||
}
|
||
});
|
||
let finalPayload: { success: boolean; message: string; data?: Record<string, unknown> } | null = null;
|
||
|
||
try {
|
||
const sourcePath = normalize_android_path(params?.source_path);
|
||
const sourceCode = typeof params?.source_code === "string" ? params.source_code : "";
|
||
const hasInlineCode = sourceCode.trim().length > 0;
|
||
const waitMs = parse_integer_like(params?.wait_ms, DEFAULT_SANDBOX_SCRIPT_WAIT_MS);
|
||
const paramsJson = String(params?.params_json ?? "{}").trim() || "{}";
|
||
const parsedParams = parse_json_text(paramsJson);
|
||
const envFilePath = normalize_android_path(params?.env_file_path);
|
||
const scriptLabel = safe_debug_file_stem(String(params?.script_label ?? "").trim(), "sandbox_script");
|
||
|
||
logStep(
|
||
`Resolved input -> sourcePath=${sourcePath || "<empty>"}, hasInlineCode=${String(hasInlineCode)}, waitMs=${String(waitMs)}`
|
||
);
|
||
|
||
if (!sourcePath && !hasInlineCode) {
|
||
finalPayload = {
|
||
success: false,
|
||
message: "Either source_path or source_code is required."
|
||
};
|
||
return;
|
||
}
|
||
|
||
if (sourcePath) {
|
||
const sourceType = await get_android_file_type(sourcePath);
|
||
logStep(`Source type detected -> ${sourceType}`);
|
||
if (sourceType !== "file") {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `Sandbox script source must be a file: ${sourcePath}`
|
||
};
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!parsedParams || typeof parsedParams !== "object" || Array.isArray(parsedParams)) {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `params_json must be a JSON object: ${paramsJson}`
|
||
};
|
||
return;
|
||
}
|
||
logStep("params_json parsed successfully.");
|
||
|
||
if (envFilePath) {
|
||
const envType = await get_android_file_type(envFilePath);
|
||
logStep(`Env file type detected -> ${envType}`);
|
||
if (envType !== "file") {
|
||
finalPayload = {
|
||
success: false,
|
||
message: `env_file_path must be a file: ${envFilePath}`
|
||
};
|
||
return;
|
||
}
|
||
}
|
||
|
||
const executionMode = hasInlineCode ? SANDBOX_SCRIPT_EXECUTION_MODE_CODE : SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT;
|
||
const scriptIdentityPath =
|
||
sourcePath || path_join(SANDBOX_JS_TEMP_DIR, `${scriptLabel}_${Date.now()}.inline.js`);
|
||
logStep(`Execution mode -> ${executionMode}`);
|
||
logStep(`Execution target -> ${scriptIdentityPath}`);
|
||
|
||
const executionResult = await Tools.SoftwareSettings.executeSandboxScriptDirect({
|
||
source_path: sourcePath || undefined,
|
||
source_code: hasInlineCode ? sourceCode : undefined,
|
||
params_json: paramsJson,
|
||
env_file_path: envFilePath || undefined,
|
||
script_label: scriptLabel,
|
||
wait_ms: waitMs
|
||
});
|
||
logStep(
|
||
`Direct execution tool completed -> success=${String(Boolean(executionResult.success))}, durationMs=${String(
|
||
executionResult.durationMs ?? ""
|
||
)}`
|
||
);
|
||
|
||
finalPayload = {
|
||
success: executionResult.success,
|
||
message: executionResult.success
|
||
? "Sandbox script executed successfully."
|
||
: String(executionResult.error ?? "Sandbox script execution failed."),
|
||
data: {
|
||
execution_mode: executionMode,
|
||
source_path: sourcePath,
|
||
has_inline_code: hasInlineCode,
|
||
env_file_path: envFilePath || null,
|
||
params_json: paramsJson,
|
||
execution_result: executionResult
|
||
}
|
||
};
|
||
} catch (error: unknown) {
|
||
logStep(`Execution failed -> ${get_error_message(error)}`);
|
||
finalPayload = {
|
||
success: false,
|
||
message: get_error_message(error)
|
||
};
|
||
} finally {
|
||
finish(
|
||
finalPayload ?? {
|
||
success: false,
|
||
message: "Sandbox script execution did not produce a final result."
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
async function read_environment_variable(params?: { key?: string }) {
|
||
try {
|
||
const key = (params?.key ?? "").trim();
|
||
if (!key) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: key"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = await Tools.SoftwareSettings.readEnvironmentVariable(key);
|
||
|
||
complete({
|
||
success: true,
|
||
message: result.exists ? `Environment variable read: ${key}` : `Environment variable not set: ${key}`,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function write_environment_variable(params?: { key?: string; value?: string }) {
|
||
try {
|
||
const key = (params?.key ?? "").trim();
|
||
if (!key) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: key"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const value = params?.value ?? "";
|
||
const result = await Tools.SoftwareSettings.writeEnvironmentVariable(key, String(value));
|
||
|
||
complete({
|
||
success: true,
|
||
message: result.cleared ? `Environment variable cleared: ${key}` : `Environment variable written: ${key}`,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function restart_mcp_with_logs(params?: { timeout_ms?: number | string }) {
|
||
try {
|
||
const timeoutMs = params?.timeout_ms;
|
||
const result = await Tools.SoftwareSettings.restartMcpWithLogs(timeoutMs);
|
||
complete({
|
||
success: true,
|
||
message: result.timedOut
|
||
? `MCP restart timed out after ${String(result.elapsedMs)}ms.`
|
||
: `MCP restart completed: ${String(result.successCount)} success, ${String(result.failedCount)} failed.`,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function get_speech_services_config() {
|
||
try {
|
||
const result = await Tools.SoftwareSettings.getSpeechServicesConfig();
|
||
const parsed = result;
|
||
|
||
complete({
|
||
success: true,
|
||
message: "Speech services config fetched.",
|
||
data: {
|
||
parsed
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
type TtsServiceType =
|
||
| "SIMPLE_TTS"
|
||
| "HTTP_TTS"
|
||
| "OPENAI_WS_TTS"
|
||
| "SILICONFLOW_TTS"
|
||
| "MINIMAX_TTS"
|
||
| "MIMO_TTS"
|
||
| "DOUBAO_TTS"
|
||
| "OPENAI_TTS"
|
||
| "VITS_TTS";
|
||
|
||
type SttServiceType =
|
||
| "SHERPA_NCNN"
|
||
| "OPENAI_STT"
|
||
| "DEEPGRAM_STT";
|
||
|
||
type SpeechServicesConfigUpdateParams = {
|
||
tts_service_type?: TtsServiceType;
|
||
tts_url_template?: string;
|
||
tts_api_key?: string;
|
||
tts_headers?: string | Record<string, string>;
|
||
tts_http_method?: string;
|
||
tts_request_body?: string;
|
||
tts_content_type?: string;
|
||
tts_locale?: string;
|
||
tts_voice_id?: string;
|
||
tts_model_name?: string;
|
||
tts_vits_package_path?: string;
|
||
tts_vits_speaker_id?: string;
|
||
tts_vits_options?: string | Record<string, string>;
|
||
tts_response_pipeline?: string | Array<{
|
||
type: string;
|
||
path?: string;
|
||
headers?: Record<string, string>;
|
||
}>;
|
||
tts_cleaner_regexs?: string | string[];
|
||
tts_speech_rate?: number;
|
||
tts_pitch?: number;
|
||
stt_service_type?: SttServiceType;
|
||
stt_endpoint_url?: string;
|
||
stt_api_key?: string;
|
||
stt_model_name?: string;
|
||
};
|
||
|
||
async function set_speech_services_config(params?: SpeechServicesConfigUpdateParams) {
|
||
try {
|
||
const updates = { ...(params ?? {}) };
|
||
const result = await Tools.SoftwareSettings.setSpeechServicesConfig(updates);
|
||
const parsed = result;
|
||
|
||
complete({
|
||
success: true,
|
||
message: "Speech services config updated.",
|
||
data: {
|
||
updates,
|
||
parsed
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function test_tts_playback(params?: {
|
||
text?: string;
|
||
interrupt?: boolean;
|
||
speech_rate?: number;
|
||
pitch?: number;
|
||
}) {
|
||
try {
|
||
const text = (params?.text ?? "").trim();
|
||
if (!text) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: text"
|
||
});
|
||
return;
|
||
}
|
||
const options = { ...(params ?? {}) };
|
||
delete options.text;
|
||
const result = await Tools.SoftwareSettings.testTtsPlayback(text, options);
|
||
const success = result.playbackTriggered;
|
||
const detailMessage = result.errorMessage?.trim() || "TTS playback test failed.";
|
||
complete({
|
||
success,
|
||
message: success ? "TTS playback test triggered." : detailMessage,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function list_model_configs() {
|
||
try {
|
||
const result = await Tools.SoftwareSettings.listModelConfigs();
|
||
complete({
|
||
success: true,
|
||
message: "Model configs listed.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
type ModelConfigProviderType =
|
||
| "OPENAI"
|
||
| "OPENAI_RESPONSES"
|
||
| "OPENAI_RESPONSES_GENERIC"
|
||
| "OPENAI_GENERIC"
|
||
| "ANTHROPIC"
|
||
| "ANTHROPIC_GENERIC"
|
||
| "GOOGLE"
|
||
| "GEMINI_GENERIC"
|
||
| "BAIDU"
|
||
| "ALIYUN"
|
||
| "XUNFEI"
|
||
| "ZHIPU"
|
||
| "BAICHUAN"
|
||
| "MOONSHOT"
|
||
| "DEEPSEEK"
|
||
| "MISTRAL"
|
||
| "SILICONFLOW"
|
||
| "IFLOW"
|
||
| "OPENROUTER"
|
||
| "FOUR_ROUTER"
|
||
| "NOUS_PORTAL"
|
||
| "INFINIAI"
|
||
| "ALIPAY_BAILING"
|
||
| "DOUBAO"
|
||
| "NVIDIA"
|
||
| "LMSTUDIO"
|
||
| "OLLAMA"
|
||
| "OPENAI_LOCAL"
|
||
| "MNN"
|
||
| "LLAMA_CPP"
|
||
| "PPINFRA"
|
||
| "NOVITA"
|
||
| "OTHER"
|
||
| (string & {});
|
||
|
||
async function create_model_config(params?: {
|
||
name?: string;
|
||
api_provider_type?: ModelConfigProviderType;
|
||
api_endpoint?: string;
|
||
api_key?: string;
|
||
model_name?: string;
|
||
max_tokens_enabled?: boolean;
|
||
max_tokens?: number;
|
||
temperature_enabled?: boolean;
|
||
temperature?: number;
|
||
top_p_enabled?: boolean;
|
||
top_p?: number;
|
||
top_k_enabled?: boolean;
|
||
top_k?: number;
|
||
presence_penalty_enabled?: boolean;
|
||
presence_penalty?: number;
|
||
frequency_penalty_enabled?: boolean;
|
||
frequency_penalty?: number;
|
||
repetition_penalty_enabled?: boolean;
|
||
repetition_penalty?: number;
|
||
custom_parameters?: string | Record<string, unknown>;
|
||
custom_headers?: string | Record<string, string>;
|
||
context_length?: number;
|
||
max_context_length?: number;
|
||
enable_max_context_mode?: boolean;
|
||
summary_token_threshold?: number;
|
||
enable_summary?: boolean;
|
||
enable_summary_by_message_count?: boolean;
|
||
summary_message_count_threshold?: number;
|
||
enable_direct_image_processing?: boolean;
|
||
enable_direct_audio_processing?: boolean;
|
||
enable_direct_video_processing?: boolean;
|
||
enable_google_search?: boolean;
|
||
enable_claude_1h_prompt_cache?: boolean;
|
||
enable_tool_call?: boolean;
|
||
mnn_forward_type?: number;
|
||
mnn_thread_count?: number;
|
||
llama_thread_count?: number;
|
||
llama_context_size?: number;
|
||
llama_gpu_layers?: number;
|
||
request_limit_per_minute?: number;
|
||
max_concurrent_requests?: number;
|
||
}) {
|
||
try {
|
||
const options = { ...(params ?? {}) };
|
||
const result = await Tools.SoftwareSettings.createModelConfig(options);
|
||
complete({
|
||
success: true,
|
||
message: "Model config created.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function update_model_config(params?: {
|
||
config_id?: string;
|
||
name?: string;
|
||
api_provider_type?: ModelConfigProviderType;
|
||
api_endpoint?: string;
|
||
api_key?: string;
|
||
model_name?: string;
|
||
max_tokens_enabled?: boolean;
|
||
max_tokens?: number;
|
||
temperature_enabled?: boolean;
|
||
temperature?: number;
|
||
top_p_enabled?: boolean;
|
||
top_p?: number;
|
||
top_k_enabled?: boolean;
|
||
top_k?: number;
|
||
presence_penalty_enabled?: boolean;
|
||
presence_penalty?: number;
|
||
frequency_penalty_enabled?: boolean;
|
||
frequency_penalty?: number;
|
||
repetition_penalty_enabled?: boolean;
|
||
repetition_penalty?: number;
|
||
custom_parameters?: string | Record<string, unknown>;
|
||
custom_headers?: string | Record<string, string>;
|
||
context_length?: number;
|
||
max_context_length?: number;
|
||
enable_max_context_mode?: boolean;
|
||
summary_token_threshold?: number;
|
||
enable_summary?: boolean;
|
||
enable_summary_by_message_count?: boolean;
|
||
summary_message_count_threshold?: number;
|
||
enable_direct_image_processing?: boolean;
|
||
enable_direct_audio_processing?: boolean;
|
||
enable_direct_video_processing?: boolean;
|
||
enable_google_search?: boolean;
|
||
enable_claude_1h_prompt_cache?: boolean;
|
||
enable_tool_call?: boolean;
|
||
mnn_forward_type?: number;
|
||
mnn_thread_count?: number;
|
||
llama_thread_count?: number;
|
||
llama_context_size?: number;
|
||
llama_gpu_layers?: number;
|
||
request_limit_per_minute?: number;
|
||
max_concurrent_requests?: number;
|
||
}) {
|
||
try {
|
||
const configId = (params?.config_id ?? "").trim();
|
||
if (!configId) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: config_id"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const { config_id: _ignoredConfigId, ...updates } = params ?? {};
|
||
const result = await Tools.SoftwareSettings.updateModelConfig(configId, updates);
|
||
complete({
|
||
success: true,
|
||
message: "Model config updated.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function delete_model_config(params?: { config_id?: string }) {
|
||
try {
|
||
const configId = (params?.config_id ?? "").trim();
|
||
if (!configId) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: config_id"
|
||
});
|
||
return;
|
||
}
|
||
const result = await Tools.SoftwareSettings.deleteModelConfig(configId);
|
||
complete({
|
||
success: true,
|
||
message: "Model config deleted.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function list_function_model_configs() {
|
||
try {
|
||
const result = await Tools.SoftwareSettings.listFunctionModelConfigs();
|
||
complete({
|
||
success: true,
|
||
message: "Function model bindings listed.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function get_function_model_config(params?: { function_type?: string }) {
|
||
try {
|
||
const functionType = (params?.function_type ?? "").trim();
|
||
if (!functionType) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: function_type"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = await Tools.SoftwareSettings.getFunctionModelConfig(functionType);
|
||
const config = result?.config ?? {};
|
||
const {
|
||
contextLength: _contextLength,
|
||
maxContextLength: _maxContextLength,
|
||
enableMaxContextMode: _enableMaxContextMode,
|
||
summaryTokenThreshold: _summaryTokenThreshold,
|
||
enableSummary: _enableSummary,
|
||
enableSummaryByMessageCount: _enableSummaryByMessageCount,
|
||
summaryMessageCountThreshold: _summaryMessageCountThreshold,
|
||
...configWithoutContextSummary
|
||
} = config;
|
||
|
||
const filteredResult = {
|
||
...result,
|
||
config: configWithoutContextSummary
|
||
};
|
||
|
||
complete({
|
||
success: true,
|
||
message: "Function model config fetched.",
|
||
data: filteredResult
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
type ContextSummaryConfigSnapshot = {
|
||
id?: string;
|
||
name?: string;
|
||
contextLength?: number;
|
||
maxContextLength?: number;
|
||
enableMaxContextMode?: boolean;
|
||
summaryTokenThreshold?: number;
|
||
enableSummary?: boolean;
|
||
enableSummaryByMessageCount?: boolean;
|
||
summaryMessageCountThreshold?: number;
|
||
};
|
||
|
||
type FunctionModelConfigDetailLike = {
|
||
configId?: string;
|
||
configName?: string;
|
||
modelIndex?: number;
|
||
actualModelIndex?: number;
|
||
selectedModel?: string;
|
||
config?: ContextSummaryConfigSnapshot;
|
||
};
|
||
|
||
type ModelConfigUpdateResultLike = {
|
||
config?: ContextSummaryConfigSnapshot;
|
||
};
|
||
|
||
function get_error_message(error: unknown): string {
|
||
return error instanceof Error ? error.message : "Unknown error";
|
||
}
|
||
|
||
function pick_context_summary_fields(config?: ContextSummaryConfigSnapshot) {
|
||
return {
|
||
context_length: config?.contextLength,
|
||
max_context_length: config?.maxContextLength,
|
||
enable_max_context_mode: config?.enableMaxContextMode,
|
||
summary_token_threshold: config?.summaryTokenThreshold,
|
||
enable_summary: config?.enableSummary,
|
||
enable_summary_by_message_count: config?.enableSummaryByMessageCount,
|
||
summary_message_count_threshold: config?.summaryMessageCountThreshold
|
||
};
|
||
}
|
||
|
||
async function get_context_summary_config(params?: { function_type?: string }) {
|
||
try {
|
||
const functionType = (params?.function_type ?? "CHAT").trim().toUpperCase();
|
||
if (!functionType) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: function_type"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = (await Tools.SoftwareSettings.getFunctionModelConfig(
|
||
functionType
|
||
)) as FunctionModelConfigDetailLike;
|
||
const config = result.config;
|
||
if (!config) {
|
||
complete({
|
||
success: false,
|
||
message: `No bound config found for function_type: ${functionType}`,
|
||
data: result
|
||
});
|
||
return;
|
||
}
|
||
|
||
complete({
|
||
success: true,
|
||
message: `Context summary config fetched for ${functionType}.`,
|
||
data: {
|
||
function_type: functionType,
|
||
config_id: result.configId ?? config.id ?? "",
|
||
config_name: result.configName ?? config.name ?? "",
|
||
model_index: result.modelIndex ?? 0,
|
||
actual_model_index: result.actualModelIndex ?? 0,
|
||
selected_model: result.selectedModel ?? "",
|
||
context_summary: pick_context_summary_fields(config),
|
||
raw: result
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function set_context_summary_config(params?: {
|
||
function_type?: string;
|
||
context_length?: number;
|
||
max_context_length?: number;
|
||
enable_max_context_mode?: boolean;
|
||
summary_token_threshold?: number;
|
||
enable_summary?: boolean;
|
||
enable_summary_by_message_count?: boolean;
|
||
summary_message_count_threshold?: number;
|
||
}) {
|
||
try {
|
||
const functionType = (params?.function_type ?? "CHAT").trim().toUpperCase();
|
||
if (!functionType) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: function_type"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const binding = (await Tools.SoftwareSettings.getFunctionModelConfig(
|
||
functionType
|
||
)) as FunctionModelConfigDetailLike;
|
||
const configId = (binding.configId ?? binding.config?.id ?? "").trim();
|
||
if (!configId) {
|
||
complete({
|
||
success: false,
|
||
message: `No bound config found for function_type: ${functionType}`,
|
||
data: binding
|
||
});
|
||
return;
|
||
}
|
||
|
||
const defaultUpdates = {
|
||
context_length: 48,
|
||
max_context_length: 128,
|
||
enable_max_context_mode: true,
|
||
summary_token_threshold: 0.7,
|
||
enable_summary: true,
|
||
enable_summary_by_message_count: true,
|
||
summary_message_count_threshold: 16
|
||
};
|
||
|
||
const updates = {
|
||
...defaultUpdates,
|
||
...(params?.context_length === undefined ? {} : { context_length: params.context_length }),
|
||
...(params?.max_context_length === undefined
|
||
? {}
|
||
: { max_context_length: params.max_context_length }),
|
||
...(params?.enable_max_context_mode === undefined
|
||
? {}
|
||
: { enable_max_context_mode: params.enable_max_context_mode }),
|
||
...(params?.summary_token_threshold === undefined
|
||
? {}
|
||
: { summary_token_threshold: params.summary_token_threshold }),
|
||
...(params?.enable_summary === undefined ? {} : { enable_summary: params.enable_summary }),
|
||
...(params?.enable_summary_by_message_count === undefined
|
||
? {}
|
||
: { enable_summary_by_message_count: params.enable_summary_by_message_count }),
|
||
...(params?.summary_message_count_threshold === undefined
|
||
? {}
|
||
: { summary_message_count_threshold: params.summary_message_count_threshold })
|
||
};
|
||
|
||
const updateResult = (await Tools.SoftwareSettings.updateModelConfig(
|
||
configId,
|
||
updates
|
||
)) as ModelConfigUpdateResultLike;
|
||
|
||
complete({
|
||
success: true,
|
||
message: `Context summary config updated for ${functionType}.`,
|
||
data: {
|
||
function_type: functionType,
|
||
config_id: configId,
|
||
applied_updates: updates,
|
||
before: pick_context_summary_fields(binding.config),
|
||
after: pick_context_summary_fields(updateResult.config),
|
||
update_result: updateResult
|
||
}
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function set_function_model_config(params?: {
|
||
function_type?: string;
|
||
config_id?: string;
|
||
model_index?: number | string;
|
||
}) {
|
||
try {
|
||
const functionType = (params?.function_type ?? "").trim();
|
||
const configId = (params?.config_id ?? "").trim();
|
||
if (!functionType) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: function_type"
|
||
});
|
||
return;
|
||
}
|
||
if (!configId) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: config_id"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = await Tools.SoftwareSettings.setFunctionModelConfig(
|
||
functionType,
|
||
configId,
|
||
params?.model_index
|
||
);
|
||
complete({
|
||
success: true,
|
||
message: "Function model binding updated.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function test_model_config_connection(params?: { config_id?: string; model_index?: number | string }) {
|
||
try {
|
||
const configId = (params?.config_id ?? "").trim();
|
||
if (!configId) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: config_id"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = await Tools.SoftwareSettings.testModelConfigConnection(configId, params?.model_index);
|
||
const success = !!result?.success;
|
||
complete({
|
||
success,
|
||
message: success ? "Model config connection tests passed." : "Model config connection tests have failures.",
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
async function ping_mcp(params?: { package_name?: string }) {
|
||
try {
|
||
const packageName = (params?.package_name ?? "").trim();
|
||
if (!packageName) {
|
||
complete({
|
||
success: false,
|
||
message: "Missing required parameter: package_name"
|
||
});
|
||
return;
|
||
}
|
||
|
||
const result = await Tools.System.usePackage(packageName);
|
||
complete({
|
||
success: true,
|
||
message: `Package probe finished: ${packageName}`,
|
||
data: result
|
||
});
|
||
} catch (error: unknown) {
|
||
complete({
|
||
success: false,
|
||
message: get_error_message(error)
|
||
});
|
||
}
|
||
}
|
||
|
||
return {
|
||
operit_editor,
|
||
how_make_skill,
|
||
list_sandbox_packages,
|
||
set_sandbox_package_enabled,
|
||
debug_install_js_package,
|
||
debug_install_toolpkg,
|
||
debug_run_sandbox_script,
|
||
read_environment_variable,
|
||
write_environment_variable,
|
||
restart_mcp_with_logs,
|
||
get_speech_services_config,
|
||
set_speech_services_config,
|
||
test_tts_playback,
|
||
list_model_configs,
|
||
create_model_config,
|
||
update_model_config,
|
||
delete_model_config,
|
||
list_function_model_configs,
|
||
get_function_model_config,
|
||
get_context_summary_config,
|
||
set_context_summary_config,
|
||
set_function_model_config,
|
||
test_model_config_connection,
|
||
ping_mcp
|
||
};
|
||
})();
|
||
|
||
exports.operit_editor = operitEditorPackage.operit_editor;
|
||
exports.how_make_skill = operitEditorPackage.how_make_skill;
|
||
exports.list_sandbox_packages = operitEditorPackage.list_sandbox_packages;
|
||
exports.set_sandbox_package_enabled = operitEditorPackage.set_sandbox_package_enabled;
|
||
exports.debug_install_js_package = operitEditorPackage.debug_install_js_package;
|
||
exports.debug_install_toolpkg = operitEditorPackage.debug_install_toolpkg;
|
||
exports.debug_run_sandbox_script = operitEditorPackage.debug_run_sandbox_script;
|
||
exports.read_environment_variable = operitEditorPackage.read_environment_variable;
|
||
exports.write_environment_variable = operitEditorPackage.write_environment_variable;
|
||
exports.restart_mcp_with_logs = operitEditorPackage.restart_mcp_with_logs;
|
||
exports.get_speech_services_config = operitEditorPackage.get_speech_services_config;
|
||
exports.set_speech_services_config = operitEditorPackage.set_speech_services_config;
|
||
exports.test_tts_playback = operitEditorPackage.test_tts_playback;
|
||
exports.list_model_configs = operitEditorPackage.list_model_configs;
|
||
exports.create_model_config = operitEditorPackage.create_model_config;
|
||
exports.update_model_config = operitEditorPackage.update_model_config;
|
||
exports.delete_model_config = operitEditorPackage.delete_model_config;
|
||
exports.list_function_model_configs = operitEditorPackage.list_function_model_configs;
|
||
exports.get_function_model_config = operitEditorPackage.get_function_model_config;
|
||
exports.get_context_summary_config = operitEditorPackage.get_context_summary_config;
|
||
exports.set_context_summary_config = operitEditorPackage.set_context_summary_config;
|
||
exports.set_function_model_config = operitEditorPackage.set_function_model_config;
|
||
exports.test_model_config_connection = operitEditorPackage.test_model_config_connection;
|
||
exports.ping_mcp = operitEditorPackage.ping_mcp;
|
||
|
||
|
||
|
||
|