- 新增 operit_runtime.py: 跨平台 Python 运行时 - 实现 Tools.System/Files/Net/UI/Memory/FFmpeg/Workflow/Chat/SoftwareSettings/Tasker - 实现 OkHttp 链式 HTTP 客户端兼容层 - 实现 complete()/exports/console 全局对象 - 实现 OperitScriptLoader 脚本加载器 - 支持 Windows/macOS/Linux 三平台 - 新增 operit_py/ 目录: 47 个 Python 翻译脚本 - 搜索类: duckduckgo, google_search, tavily, zhipu_search, various_search, crossref - 绘图类: openai_draw, qwen_draw, xai_draw, minimax_draw, siliconflow_draw, zhipu_draw, nanobanana_draw - 媒体类: ffmpeg, file_converter, douyin_download, bilibili_tools, jmcomic - 文件类: extended_file_tools, hex_editor, reader, beeimg_image_uploader - 网络类: extended_http_tools, network_test - 自动化类: browser, automatic_ui_base, automatic_ui_subagent, automatic_bilibili_assistant, automatic_xiaohongshu_assistant, automatic_baidu_map_assistant - 系统类: system_tools, super_admin, daily_life, operit_editor - 对话类: ai_chat, extended_chat, qq_intelligent - 工作流类: workflow, vflow_trigger, tasker - 工具类: time, code_runner, quick_start, extended_memory_tools, baidu_map, 12306, pdf_vision_parser - 测试结果: 47/47 语法检查通过, 38 脚本加载成功, 280 个工具注册 - Android 专属功能已降级处理(保留接口,返回不支持提示)
1070 lines
54 KiB
Python
1070 lines
54 KiB
Python
# 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": "配置排查手册", "parameters": [], "advice": true },
|
||
# { "name": "how_make_skill", "description": "返回如何制作 skill 的双语说明", "parameters": [] },
|
||
# { "name": "list_sandbox_packages", "description": "获取沙盒包列表(内置+外部)及当前启用状态", "parameters": [] },
|
||
# { "name": "set_sandbox_package_enabled", "description": "设置沙盒包开关状态", "parameters": [ { "name": "package_name", "type": "string", "required": true }, { "name": "enabled", "type": "boolean", "required": true } ] },
|
||
# { "name": "debug_install_js_package", "description": "将 Android 侧的普通 .js 沙盒包直接烧录到外部 packages 目录", "parameters": [ { "name": "source_path", "type": "string", "required": true }, { "name": "enable_after_install", "type": "boolean", "required": false }, { "name": "activate_after_install", "type": "boolean", "required": false } ] },
|
||
# { "name": "debug_install_toolpkg", "description": "根据 Android 侧的 ToolPkg 目录、manifest 或现成 .toolpkg,直接打包/烧录到外部 packages 目录", "parameters": [ { "name": "source_path", "type": "string", "required": true }, { "name": "reset_subpackage_states", "type": "boolean", "required": false }, { "name": "activate_subpackages", "type": "string", "required": false }, { "name": "wait_ms", "type": "integer", "required": false } ] },
|
||
# { "name": "debug_run_sandbox_script", "description": "直接运行一段 sandbox script", "parameters": [ { "name": "source_path", "type": "string", "required": false }, { "name": "source_code", "type": "string", "required": false }, { "name": "params_json", "type": "string", "required": false }, { "name": "env_file_path", "type": "string", "required": false }, { "name": "script_label", "type": "string", "required": false }, { "name": "wait_ms", "type": "integer", "required": false } ] },
|
||
# { "name": "read_environment_variable", "description": "读取指定环境变量当前值", "parameters": [ { "name": "key", "type": "string", "required": true } ] },
|
||
# { "name": "write_environment_variable", "description": "写入指定环境变量", "parameters": [ { "name": "key", "type": "string", "required": true }, { "name": "value", "type": "string", "required": false } ] },
|
||
# { "name": "restart_mcp_with_logs", "description": "触发一次 MCP 重启流程,返回每个插件的启动日志与状态摘要", "parameters": [ { "name": "timeout_ms", "type": "integer", "required": false } ] },
|
||
# { "name": "get_speech_services_config", "description": "获取当前 TTS/STT 语音服务配置快照", "parameters": [] },
|
||
# { "name": "set_speech_services_config", "description": "按字段更新 TTS/STT 语音服务配置", "parameters": [] },
|
||
# { "name": "test_tts_playback", "description": "按当前 TTS 配置播放一次测试文本", "parameters": [ { "name": "text", "type": "string", "required": true }, { "name": "interrupt", "type": "boolean", "required": false }, { "name": "speech_rate", "type": "number", "required": false }, { "name": "pitch", "type": "number", "required": false } ] },
|
||
# { "name": "list_model_configs", "description": "列出全部模型配置及功能模型绑定关系", "parameters": [] },
|
||
# { "name": "create_model_config", "description": "新增模型配置", "parameters": [] },
|
||
# { "name": "update_model_config", "description": "按 config_id 修改模型配置", "parameters": [ { "name": "config_id", "type": "string", "required": true } ] },
|
||
# { "name": "delete_model_config", "description": "删除模型配置", "parameters": [ { "name": "config_id", "type": "string", "required": true } ] },
|
||
# { "name": "list_function_model_configs", "description": "列出功能 -> 配置绑定关系", "parameters": [] },
|
||
# { "name": "get_function_model_config", "description": "查看某个功能当前绑定的单个配置详情", "parameters": [ { "name": "function_type", "type": "string", "required": true } ] },
|
||
# { "name": "get_context_summary_config", "description": "查看上下文总结参数", "parameters": [ { "name": "function_type", "type": "string", "required": false } ] },
|
||
# { "name": "set_context_summary_config", "description": "设置上下文总结参数", "parameters": [ { "name": "function_type", "type": "string", "required": false } ] },
|
||
# { "name": "set_function_model_config", "description": "为功能指定配置与模型索引", "parameters": [ { "name": "function_type", "type": "string", "required": true }, { "name": "config_id", "type": "string", "required": true }, { "name": "model_index", "type": "number", "required": false } ] },
|
||
# { "name": "test_model_config_connection", "description": "按设置页同等逻辑测试配置连通与多模态能力", "parameters": [ { "name": "config_id", "type": "string", "required": true }, { "name": "model_index", "type": "number", "required": false } ] },
|
||
# { "name": "ping_mcp", "description": "快速探测指定包是否可被加载", "parameters": [ { "name": "package_name", "type": "string", "required": true } ] }
|
||
# ]
|
||
# }
|
||
|
||
import json
|
||
import re
|
||
import time
|
||
import math
|
||
from datetime import datetime
|
||
|
||
Error = Exception
|
||
|
||
# Android 专属常量(Android-only paths)
|
||
SANDBOX_EXTERNAL_PACKAGES_DIR = "/sdcard/Android/data/com.ai.assistance.operit/files/packages" # Android-only
|
||
TOOLPKG_DEBUG_INSTALL_ACTION = "com.ai.assistance.operit.DEBUG_INSTALL_TOOLPKG"
|
||
TOOLPKG_DEBUG_INSTALL_COMPONENT = "com.ai.assistance.operit/.core.tools.packTool.ToolPkgDebugInstallReceiver"
|
||
SANDBOX_SCRIPT_EXECUTION_ACTION = "com.ai.assistance.operit.EXECUTE_JS"
|
||
SANDBOX_SCRIPT_EXECUTION_COMPONENT = "com.ai.assistance.operit/com.ai.assistance.operit.core.tools.javascript.ScriptExecutionReceiver"
|
||
SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT = "script"
|
||
SANDBOX_SCRIPT_EXECUTION_MODE_CODE = "code"
|
||
SANDBOX_JS_TEMP_DIR = "/sdcard/Android/data/com.ai.assistance.operit/js_temp" # Android-only
|
||
DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS = 1500
|
||
DEFAULT_TOOLPKG_INSTALL_WAIT_MS = 1500
|
||
DEFAULT_SANDBOX_SCRIPT_WAIT_MS = 15000
|
||
|
||
JS_METADATA_BLOCK_PATTERN = re.compile(r'/\*\s*METADATA([\s\S]*?)\*/', re.MULTILINE)
|
||
JS_PACKAGE_NAME_PATTERN = re.compile(r'^\s*["\']?name["\']?\s*:\s*["\']([^"\']+)["\']', re.MULTILINE)
|
||
TOOLPKG_ID_PATTERN = re.compile(r'^\s*["\']?toolpkg_id["\']?\s*:\s*["\']([^"\']+)["\']', re.MULTILINE)
|
||
TOOLPKG_MAIN_PATTERN = re.compile(r'^\s*["\']?main["\']?\s*:\s*["\']([^"\']+)["\']', re.MULTILINE)
|
||
TOOLPKG_SUBPACKAGE_ID_PATTERN = re.compile(r'^\s*["\']?id["\']?\s*:\s*["\']([^"\']+)["\']', re.MULTILINE)
|
||
TOOLPKG_SKIP_DIR_NAMES = {".git", "__pycache__"}
|
||
TOOLPKG_SKIP_FILE_NAMES = {".DS_Store", "Thumbs.db"}
|
||
|
||
|
||
def get_error_message(error):
|
||
return str(error) if isinstance(error, Exception) else "Unknown error"
|
||
|
||
|
||
def normalize_android_path(raw=None):
|
||
normalized = str(raw if raw is not None else "").strip().replace("\\", "/")
|
||
if not normalized:
|
||
return ""
|
||
if re.match(r'^[a-zA-Z]+://', normalized):
|
||
return normalized
|
||
if normalized.startswith("/"):
|
||
return normalized
|
||
if normalized.startswith("sdcard/"):
|
||
return f"/{normalized}"
|
||
if normalized.startswith("Android/") or normalized.startswith("Download/"):
|
||
return f"/sdcard/{normalized}"
|
||
return normalized
|
||
|
||
|
||
def normalize_package_key(raw=None):
|
||
return str(raw if raw is not None else "").strip().lower()
|
||
|
||
|
||
def normalize_directory_path(path):
|
||
normalized = normalize_android_path(path)
|
||
normalized = re.sub(r'/+', '/', normalized)
|
||
if normalized == "/":
|
||
return normalized
|
||
return normalized.rstrip("/")
|
||
|
||
|
||
def same_android_path(left, right):
|
||
return normalize_directory_path(left) == normalize_directory_path(right)
|
||
|
||
|
||
def path_dirname(path):
|
||
normalized = normalize_directory_path(path)
|
||
index = normalized.rfind("/")
|
||
if index < 0:
|
||
return ""
|
||
if index == 0:
|
||
return "/"
|
||
return normalized[:index]
|
||
|
||
|
||
def path_basename(path):
|
||
normalized = normalize_directory_path(path)
|
||
index = normalized.rfind("/")
|
||
return normalized[index + 1:] if index >= 0 else normalized
|
||
|
||
|
||
def path_join(*parts):
|
||
filtered = [str(p if p is not None else "").strip().replace("\\", "/") for p in parts]
|
||
filtered = [p for p in filtered if p]
|
||
if len(filtered) == 0:
|
||
return ""
|
||
leadingSlash = filtered[0].startswith("/")
|
||
joined = "/".join([re.sub(r'^/+|/+$', '', p) for p in filtered if p])
|
||
return f"/{joined}" if leadingSlash else joined
|
||
|
||
|
||
def safe_debug_file_stem(raw, fallback):
|
||
normalized = re.sub(r'[^A-Za-z0-9._-]+', '_', str(raw if raw is not None else "").strip())
|
||
normalized = re.sub(r'^[_.]+|[_.]+$', '', normalized)
|
||
return normalized or fallback
|
||
|
||
|
||
def parse_boolean_like(value, defaultValue):
|
||
if value is None or value == "":
|
||
return defaultValue
|
||
if isinstance(value, bool):
|
||
return value
|
||
if isinstance(value, (int, float)):
|
||
return value != 0
|
||
normalized = str(value).strip().lower()
|
||
if not normalized:
|
||
return defaultValue
|
||
if normalized in ("true", "1", "yes", "on"):
|
||
return True
|
||
if normalized in ("false", "0", "no", "off"):
|
||
return False
|
||
return defaultValue
|
||
|
||
|
||
def parse_integer_like(value, defaultValue):
|
||
if value is None or value == "":
|
||
return defaultValue
|
||
try:
|
||
parsed = float(value)
|
||
if math.isfinite(parsed) and parsed >= 0:
|
||
return int(parsed)
|
||
return defaultValue
|
||
except (ValueError, TypeError):
|
||
return defaultValue
|
||
|
||
|
||
async def android_path_exists(path):
|
||
result = await Tools.Files.exists(path, "android")
|
||
return result.exists
|
||
|
||
|
||
async def get_android_file_type(path):
|
||
result = await Tools.Files.info(path, "android")
|
||
return result.fileType.strip().lower()
|
||
|
||
|
||
async def ensure_android_directory(path):
|
||
await Tools.Files.mkdir(path, True, "android")
|
||
|
||
|
||
async def delete_android_path_if_exists(path):
|
||
if not path:
|
||
return
|
||
if not await android_path_exists(path):
|
||
return
|
||
await Tools.Files.deleteFile(path, True, "android")
|
||
|
||
|
||
async def cleanup_android_paths(paths):
|
||
uniquePaths = list(set(normalize_android_path(p) for p in paths if normalize_android_path(p)))
|
||
uniquePaths.sort(key=lambda x: len(x), reverse=True)
|
||
for path in uniquePaths:
|
||
try:
|
||
await delete_android_path_if_exists(path)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
async def read_android_text_file(path):
|
||
result = await Tools.Files.read({"path": path, "environment": "android"})
|
||
return result.content
|
||
|
||
|
||
def parse_json_record(raw):
|
||
if not raw:
|
||
return None
|
||
try:
|
||
parsed = json.loads(raw)
|
||
if not parsed or not isinstance(parsed, dict) or isinstance(parsed, list):
|
||
return None
|
||
return parsed
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def find_sandbox_package_entry(payload, packageName):
|
||
if not payload:
|
||
return None
|
||
targetKey = normalize_package_key(packageName)
|
||
packages = payload.get("packages", []) if payload else []
|
||
for entry in packages:
|
||
if normalize_package_key(entry.get("packageName", "") if entry else "") == targetKey:
|
||
return entry
|
||
return None
|
||
|
||
|
||
def collect_related_package_load_errors(payload, packageName, *relatedPaths):
|
||
normalizedPackageName = normalize_package_key(packageName)
|
||
normalizedPaths = [str(p or "").strip() for p in relatedPaths if str(p or "").strip()]
|
||
packageLoadErrors = payload.get("packageLoadErrors") if payload else None
|
||
if not packageLoadErrors:
|
||
return {}
|
||
result = {}
|
||
for key, value in packageLoadErrors.items():
|
||
normalizedKey = normalize_package_key(key)
|
||
message = str(value or "")
|
||
if (normalizedKey == normalizedPackageName or
|
||
message.lower().find(normalizedPackageName) >= 0 or
|
||
any(p in message for p in normalizedPaths)):
|
||
result[key] = value
|
||
return result
|
||
|
||
|
||
async def refresh_sandbox_packages_until(packageName, timeoutMs):
|
||
deadline = time.time() * 1000 + max(0, timeoutMs)
|
||
lastPayload = None
|
||
lastEntry = None
|
||
while True:
|
||
lastPayload = await Tools.SoftwareSettings.listSandboxPackages()
|
||
lastEntry = find_sandbox_package_entry(lastPayload, packageName)
|
||
if lastEntry:
|
||
return {"payload": lastPayload, "packageEntry": lastEntry}
|
||
if time.time() * 1000 >= deadline:
|
||
return {"payload": lastPayload, "packageEntry": lastEntry}
|
||
await Tools.System.sleep(min(300, max(50, deadline - time.time() * 1000)))
|
||
|
||
|
||
async def wait_for_android_file(path, timeoutMs):
|
||
deadline = time.time() * 1000 + max(0, timeoutMs)
|
||
while True:
|
||
if await android_path_exists(path):
|
||
return True
|
||
if time.time() * 1000 >= deadline:
|
||
return False
|
||
await Tools.System.sleep(min(300, max(50, deadline - time.time() * 1000)))
|
||
|
||
|
||
def parse_json_text(raw):
|
||
text = str(raw if raw is not None else "").strip()
|
||
if not text:
|
||
return None
|
||
try:
|
||
return json.loads(text)
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def extract_js_metadata_block(sourceText, sourcePath):
|
||
match = JS_METADATA_BLOCK_PATTERN.search(sourceText)
|
||
if not match:
|
||
raise Error(f"Missing METADATA block: {sourcePath}")
|
||
return match.group(1).strip()
|
||
|
||
|
||
def parse_js_package_source(sourceText, sourcePath):
|
||
metadataBlock = extract_js_metadata_block(sourceText, sourcePath)
|
||
match = JS_PACKAGE_NAME_PATTERN.search(metadataBlock)
|
||
packageName = match.group(1).strip() if match else ""
|
||
if not packageName:
|
||
raise Error(f"Missing package metadata name: {sourcePath}")
|
||
return {"packageName": packageName, "metadataBlock": metadataBlock}
|
||
|
||
|
||
async def delete_duplicate_external_js_package_files(packageName, keepPath):
|
||
removedPaths = []
|
||
listing = await Tools.Files.list(SANDBOX_EXTERNAL_PACKAGES_DIR, "android")
|
||
entries = listing.entries if listing and hasattr(listing, "entries") else []
|
||
for entry in entries:
|
||
entryName = str(entry.get("name", "") if isinstance(entry, dict) else getattr(entry, "name", "")).strip()
|
||
if not entryName:
|
||
continue
|
||
isDir = entry.get("isDirectory", False) if isinstance(entry, dict) else getattr(entry, "isDirectory", False)
|
||
if isDir or not entryName.lower().endswith(".js"):
|
||
continue
|
||
candidatePath = path_join(SANDBOX_EXTERNAL_PACKAGES_DIR, entryName)
|
||
if same_android_path(candidatePath, keepPath):
|
||
continue
|
||
try:
|
||
candidateText = await read_android_text_file(candidatePath)
|
||
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.append(candidatePath)
|
||
except Exception:
|
||
pass
|
||
return removedPaths
|
||
|
||
|
||
def parse_toolpkg_manifest_text(text, manifestPath):
|
||
packageId = ""
|
||
mainEntry = ""
|
||
subpackageIds = []
|
||
|
||
try:
|
||
parsed = json.loads(text)
|
||
if parsed and isinstance(parsed, dict) and not isinstance(parsed, list):
|
||
packageId = str(parsed.get("toolpkg_id", "")).strip()
|
||
mainEntry = str(parsed.get("main", "")).strip()
|
||
if isinstance(parsed.get("subpackages"), list):
|
||
subpackageIds = [str(sp.get("id", "")).strip() for sp in parsed["subpackages"] if str(sp.get("id", "")).strip()]
|
||
except Exception:
|
||
pass
|
||
|
||
if not packageId:
|
||
match = TOOLPKG_ID_PATTERN.search(text)
|
||
packageId = match.group(1).strip() if match else ""
|
||
if not mainEntry:
|
||
match = TOOLPKG_MAIN_PATTERN.search(text)
|
||
mainEntry = match.group(1).strip() if match else ""
|
||
if len(subpackageIds) == 0:
|
||
matches = []
|
||
for m in TOOLPKG_SUBPACKAGE_ID_PATTERN.finditer(text):
|
||
subpackageId = m.group(1).strip()
|
||
if subpackageId:
|
||
matches.append(subpackageId)
|
||
subpackageIds = matches
|
||
|
||
if not packageId:
|
||
raise Error(f"manifest.toolpkg_id is required: {manifestPath}")
|
||
if not mainEntry:
|
||
raise Error(f"manifest.main is required: {manifestPath}")
|
||
|
||
return {
|
||
"packageId": packageId,
|
||
"mainEntry": mainEntry.replace("\\", "/").lstrip("/"),
|
||
"subpackageIds": list(set(subpackageIds))
|
||
}
|
||
|
||
|
||
async def find_toolpkg_manifest_in_folder(folderPath):
|
||
manifestJson = path_join(folderPath, "manifest.json")
|
||
if await android_path_exists(manifestJson):
|
||
return manifestJson
|
||
manifestHjson = path_join(folderPath, "manifest.hjson")
|
||
if await android_path_exists(manifestHjson):
|
||
return manifestHjson
|
||
raise Error(f"Missing manifest.json or manifest.hjson in folder: {folderPath}")
|
||
|
||
|
||
async def resolve_toolpkg_source(rawSourcePath):
|
||
sourcePath = normalize_android_path(rawSourcePath)
|
||
if not sourcePath:
|
||
raise Error("Missing required parameter: source_path")
|
||
if not await android_path_exists(sourcePath):
|
||
raise Error(f"Source path does not exist: {sourcePath}")
|
||
|
||
sourceType = await get_android_file_type(sourcePath)
|
||
sourceKind = "folder"
|
||
folderPath = sourcePath
|
||
archivePath = None
|
||
temporaryPaths = []
|
||
|
||
lowerBaseName = path_basename(sourcePath).lower()
|
||
if sourceType == "directory":
|
||
folderPath = sourcePath
|
||
elif sourceType == "file" and lowerBaseName in ("manifest.json", "manifest.hjson"):
|
||
folderPath = path_dirname(sourcePath)
|
||
elif sourceType == "file" and lowerBaseName.endswith(".toolpkg"):
|
||
sourceKind = "archive"
|
||
archivePath = sourcePath
|
||
_safe_stem = safe_debug_file_stem(re.sub(r'\.toolpkg$', '', lowerBaseName, flags=re.IGNORECASE), 'toolpkg')
|
||
tempExtractDir = path_join(
|
||
OPERIT_CLEAN_ON_EXIT_DIR,
|
||
f"operit_editor_toolpkg_extract_{_safe_stem}_{int(time.time() * 1000)}"
|
||
)
|
||
await ensure_android_directory(tempExtractDir)
|
||
await Tools.Files.unzip(sourcePath, tempExtractDir, "android")
|
||
folderPath = tempExtractDir
|
||
temporaryPaths.append(tempExtractDir)
|
||
else:
|
||
raise Error("ToolPkg source must be a folder, manifest.json/manifest.hjson, or an existing .toolpkg file")
|
||
|
||
manifestPath = await find_toolpkg_manifest_in_folder(folderPath)
|
||
manifestText = await read_android_text_file(manifestPath)
|
||
manifest = parse_toolpkg_manifest_text(manifestText, manifestPath)
|
||
mainPath = path_join(folderPath, manifest["mainEntry"])
|
||
if not await android_path_exists(mainPath):
|
||
raise Error(f"manifest.main file does not exist: {manifestPath} -> {manifest['mainEntry']}")
|
||
|
||
return {
|
||
"sourceKind": sourceKind,
|
||
"sourcePath": sourcePath,
|
||
"folderPath": folderPath,
|
||
"manifestPath": manifestPath,
|
||
"packageId": manifest["packageId"],
|
||
"mainEntry": manifest["mainEntry"],
|
||
"subpackageIds": manifest["subpackageIds"],
|
||
"archivePath": archivePath,
|
||
"temporaryPaths": temporaryPaths
|
||
}
|
||
|
||
|
||
async def build_toolpkg_archive_from_folder(source):
|
||
tempBuildDir = path_join(
|
||
OPERIT_CLEAN_ON_EXIT_DIR,
|
||
f"operit_editor_toolpkg_build_{safe_debug_file_stem(source['packageId'], 'toolpkg')}_{int(time.time() * 1000)}"
|
||
)
|
||
await ensure_android_directory(tempBuildDir)
|
||
archivePath = path_join(tempBuildDir, f"{safe_debug_file_stem(source['packageId'], 'toolpkg')}.toolpkg")
|
||
await Tools.Files.zip(source["folderPath"], archivePath, "android", False)
|
||
if not await android_path_exists(archivePath):
|
||
raise Error(f"Failed to create ToolPkg archive: {archivePath}")
|
||
return {"archivePath": archivePath, "temporaryPaths": [tempBuildDir]}
|
||
|
||
|
||
def parse_requested_package_ids(raw=None):
|
||
input_str = str(raw if raw is not None else "").strip()
|
||
if not input_str:
|
||
return []
|
||
return list(set(item.strip() for item in re.split(r'[\r\n,]+', input_str) if item.strip()))
|
||
|
||
|
||
async def operit_editor(params):
|
||
try:
|
||
query = (params or {}).get("query", "")
|
||
complete({
|
||
"success": True,
|
||
"message": "配置排查手册已加载(MCP/Skill/Sandbox Package/沙盒包调试烧录/功能模型与模型配置/TTS-STT语音服务),将按配置链路执行排查。",
|
||
"data": {"query": query}
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def how_make_skill():
|
||
try:
|
||
locale = (getLang() or "").lower()
|
||
lang = "zh" if locale.startswith("zh") else ("en" if locale.startswith("en") else "both")
|
||
|
||
zh = """如何制作 skill(简版)
|
||
1. 先创建目录:/sdcard/Download/Operit/skills/<skill_name>/
|
||
2. 必备文件:SKILL.md
|
||
3. 在 SKILL.md 顶部用 Markdown 元数据(frontmatter)写 name、description
|
||
4. 元数据后再写正文:适用场景、执行步骤、约束边界、期望输出
|
||
5. 可选内容:scripts/、templates/、examples/、assets/
|
||
6. 实践建议:优先下载现成 skill,直接解压过来,并确保目录下有 SKILL.md。"""
|
||
|
||
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
|
||
4. After metadata, write the main sections: use cases, workflow steps, constraints, expected outputs
|
||
5. Optional content: scripts/, templates/, examples/, assets/
|
||
6. Practical tip: download an existing skill, extract it directly, and ensure the directory contains SKILL.md"""
|
||
|
||
message = zh if lang == "zh" else (en if lang == "en" else f"{zh}\n\n---\n\n{en}")
|
||
complete({"success": True, "message": message, "data": {"lang": lang, "zh": zh, "en": en}})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def list_sandbox_packages():
|
||
try:
|
||
result = await Tools.SoftwareSettings.listSandboxPackages()
|
||
complete({
|
||
"success": True,
|
||
"message": f"Sandbox package list fetched: {result.totalCount} package(s).",
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def set_sandbox_package_enabled(params=None):
|
||
try:
|
||
packageName = (params or {}).get("package_name", "")
|
||
enabled = (params or {}).get("enabled", False)
|
||
result = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageName, enabled)
|
||
complete({
|
||
"success": True,
|
||
"message": result.message or "Sandbox package switch updated.",
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def debug_install_js_package(params=None):
|
||
logs = []
|
||
def logStep(message):
|
||
logs.append(message)
|
||
def finish(payload):
|
||
data = dict(payload.get("data", {}))
|
||
data["logs"] = logs
|
||
complete({**payload, "data": data})
|
||
|
||
try:
|
||
sourcePath = normalize_android_path((params or {}).get("source_path"))
|
||
logStep(f"Resolved source_path -> {sourcePath or '<empty>'}")
|
||
if not sourcePath:
|
||
finish({"success": False, "message": "Missing required parameter: source_path"})
|
||
return
|
||
|
||
sourceType = await get_android_file_type(sourcePath)
|
||
logStep(f"Source type detected -> {sourceType}")
|
||
if sourceType != "file":
|
||
finish({"success": False, "message": f"JS source must be a file: {sourcePath}"})
|
||
return
|
||
if not sourcePath.lower().endswith(".js"):
|
||
finish({"success": False, "message": f"JS debug install only supports .js files: {sourcePath}"})
|
||
return
|
||
|
||
sourceText = await read_android_text_file(sourcePath)
|
||
packageInfo = parse_js_package_source(sourceText, sourcePath)
|
||
logStep(f"Parsed package info -> packageName={packageInfo['packageName']}")
|
||
enableAfterInstall = parse_boolean_like((params or {}).get("enable_after_install"), True)
|
||
activateAfterInstall = parse_boolean_like((params or {}).get("activate_after_install"), True)
|
||
shouldEnable = enableAfterInstall or activateAfterInstall
|
||
logStep(f"Install options -> enableAfterInstall={enableAfterInstall}, activateAfterInstall={activateAfterInstall}, shouldEnable={shouldEnable}")
|
||
|
||
await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR)
|
||
targetPath = path_join(SANDBOX_EXTERNAL_PACKAGES_DIR, f"{safe_debug_file_stem(packageInfo['packageName'], 'debug_js_package')}.js")
|
||
logStep(f"Target install path -> {targetPath}")
|
||
copied = not 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 not await android_path_exists(targetPath):
|
||
finish({"success": False, "message": f"Installed JS package file is missing after copy: {targetPath}"})
|
||
return
|
||
logStep("Verified installed JS package file exists.")
|
||
|
||
removedDuplicateFiles = await delete_duplicate_external_js_package_files(packageInfo["packageName"], targetPath)
|
||
logStep(f"Duplicate cleanup completed -> removed {len(removedDuplicateFiles)} file(s).")
|
||
refresh = await refresh_sandbox_packages_until(packageInfo["packageName"], DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS)
|
||
logStep(f"Sandbox refresh completed -> found={bool(refresh['packageEntry'])}, builtIn={refresh['packageEntry'].get('isBuiltIn', False) if refresh['packageEntry'] else False}")
|
||
if not refresh["packageEntry"]:
|
||
finish({"success": False, "message": f"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"].get("isBuiltIn"):
|
||
finish({"success": False, "message": f"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
|
||
|
||
enableResult = None
|
||
if shouldEnable:
|
||
logStep(f"Enabling sandbox package -> {packageInfo['packageName']}")
|
||
enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageInfo["packageName"], True)
|
||
logStep(f"Enable result -> {enableResult.message or '<empty>'}")
|
||
else:
|
||
logStep("Enable step skipped by configuration.")
|
||
|
||
activateResult = None
|
||
if activateAfterInstall:
|
||
logStep(f"Activating package via use_package -> {packageInfo['packageName']}")
|
||
activateResult = await Tools.System.usePackage(packageInfo["packageName"])
|
||
logStep(f"Activation result -> {activateResult or '<empty>'}")
|
||
else:
|
||
logStep("Activation step skipped by configuration.")
|
||
|
||
finish({"success": True, "message": f"Debug JS package installed: {packageInfo['packageName']}",
|
||
"data": {"package_name": packageInfo["packageName"], "source_path": sourcePath, "target_path": targetPath,
|
||
"copied": 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"]}})
|
||
except Exception as error:
|
||
logStep(f"Execution failed -> {get_error_message(error)}")
|
||
finish({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def debug_install_toolpkg(params=None):
|
||
cleanupPaths = []
|
||
logs = []
|
||
def logStep(message):
|
||
logs.append(message)
|
||
def finish(payload):
|
||
data = dict(payload.get("data", {}))
|
||
data["logs"] = logs
|
||
complete({**payload, "data": data})
|
||
finalPayload = None
|
||
|
||
try:
|
||
resolvedSource = await resolve_toolpkg_source((params or {}).get("source_path", ""))
|
||
logStep(f"Resolved ToolPkg source -> kind={resolvedSource['sourceKind']}, packageId={resolvedSource['packageId']}, sourcePath={resolvedSource['sourcePath']}")
|
||
cleanupPaths.extend(resolvedSource["temporaryPaths"])
|
||
if len(resolvedSource["temporaryPaths"]) > 0:
|
||
logStep(f"Registered temporary paths -> {', '.join(resolvedSource['temporaryPaths'])}")
|
||
|
||
archivePath = resolvedSource.get("archivePath") or ""
|
||
if resolvedSource["sourceKind"] == "folder":
|
||
logStep("Source is a folder; building temporary .toolpkg archive.")
|
||
builtArchive = await build_toolpkg_archive_from_folder(resolvedSource)
|
||
archivePath = builtArchive["archivePath"]
|
||
cleanupPaths.extend(builtArchive["temporaryPaths"])
|
||
logStep(f"Built archive -> {archivePath}")
|
||
|
||
await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR)
|
||
targetPath = path_join(SANDBOX_EXTERNAL_PACKAGES_DIR, f"{safe_debug_file_stem(resolvedSource['packageId'], 'toolpkg')}.toolpkg")
|
||
logStep(f"Target install path -> {targetPath}")
|
||
if not 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 not await android_path_exists(targetPath):
|
||
finalPayload = {"success": False, "message": f"Installed ToolPkg archive is missing after copy: {targetPath}"}
|
||
return
|
||
logStep("Verified installed ToolPkg archive exists.")
|
||
|
||
resetSubpackageStates = parse_boolean_like((params or {}).get("reset_subpackage_states"), True)
|
||
waitMs = parse_integer_like((params or {}).get("wait_ms"), DEFAULT_TOOLPKG_INSTALL_WAIT_MS)
|
||
logStep(f"Install options -> resetSubpackageStates={resetSubpackageStates}, waitMs={waitMs}")
|
||
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(f"Debug install broadcast dispatched -> {broadcastResult.result or '<empty>'}")
|
||
|
||
refresh = await refresh_sandbox_packages_until(resolvedSource["packageId"], waitMs)
|
||
relatedLoadErrors = collect_related_package_load_errors(refresh["payload"], resolvedSource["packageId"], resolvedSource["sourcePath"], archivePath, targetPath)
|
||
logStep(f"Sandbox refresh completed -> found={bool(refresh['packageEntry'])}, builtIn={refresh['packageEntry'].get('isBuiltIn', False) if refresh['packageEntry'] else False}")
|
||
if len(relatedLoadErrors) > 0:
|
||
logStep(f"Related load errors -> {json.dumps(relatedLoadErrors)}")
|
||
if not refresh["packageEntry"]:
|
||
finalPayload = {"success": False, "message": f"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"].get("isBuiltIn"):
|
||
finalPayload = {"success": False, "message": f"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
|
||
|
||
requestedSubpackages = parse_requested_package_ids((params or {}).get("activate_subpackages"))
|
||
knownSubpackageKeys = set(normalize_package_key(sid) for sid in resolvedSource["subpackageIds"])
|
||
unknownRequestedSubpackages = ([] if len(knownSubpackageKeys) == 0
|
||
else [sid for sid in requestedSubpackages if normalize_package_key(sid) not in knownSubpackageKeys])
|
||
activationTargets = [sid for sid in requestedSubpackages if sid not in unknownRequestedSubpackages]
|
||
logStep(f"Subpackage activation plan -> requested={', '.join(requestedSubpackages) or '<none>'}, targets={', '.join(activationTargets) or '<none>'}, unknown={', '.join(unknownRequestedSubpackages) or '<none>'}")
|
||
|
||
subpackageResults = []
|
||
for subpackageId in activationTargets:
|
||
logStep(f"Enabling subpackage -> {subpackageId}")
|
||
enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(subpackageId, True)
|
||
logStep(f"Subpackage enable result [{subpackageId}] -> {enableResult.message or '<empty>'}")
|
||
activateResult = await Tools.System.usePackage(subpackageId)
|
||
logStep(f"Subpackage activate result [{subpackageId}] -> {activateResult or '<empty>'}")
|
||
subpackageResults.append({"subpackage_id": subpackageId, "enable_result": enableResult, "activate_result": activateResult})
|
||
|
||
finalPayload = {"success": True, "message": f"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}}
|
||
except Exception as error:
|
||
logStep(f"Execution failed -> {get_error_message(error)}")
|
||
finalPayload = {"success": False, "message": get_error_message(error)}
|
||
finally:
|
||
if len(cleanupPaths) > 0:
|
||
logStep(f"Cleaning temporary paths -> {', '.join(cleanupPaths)}")
|
||
await cleanup_android_paths(cleanupPaths)
|
||
if len(cleanupPaths) > 0:
|
||
logStep("Temporary path cleanup completed.")
|
||
if finalPayload:
|
||
finish(finalPayload)
|
||
|
||
|
||
async def debug_run_sandbox_script(params=None):
|
||
logs = []
|
||
def logStep(message):
|
||
logs.append(message)
|
||
def finish(payload):
|
||
data = dict(payload.get("data", {}))
|
||
data["logs"] = logs
|
||
complete({**payload, "data": data})
|
||
finalPayload = None
|
||
|
||
try:
|
||
sourcePath = normalize_android_path((params or {}).get("source_path"))
|
||
sourceCode = params.get("source_code", "") if params and isinstance(params.get("source_code"), str) else ""
|
||
hasInlineCode = len(sourceCode.strip()) > 0
|
||
waitMs = parse_integer_like((params or {}).get("wait_ms"), DEFAULT_SANDBOX_SCRIPT_WAIT_MS)
|
||
paramsJson = str((params or {}).get("params_json", "{}")).strip() or "{}"
|
||
parsedParams = parse_json_text(paramsJson)
|
||
envFilePath = normalize_android_path((params or {}).get("env_file_path"))
|
||
scriptLabel = safe_debug_file_stem(str((params or {}).get("script_label", "")).strip(), "sandbox_script")
|
||
|
||
logStep(f"Resolved input -> sourcePath={sourcePath or '<empty>'}, hasInlineCode={hasInlineCode}, waitMs={waitMs}")
|
||
|
||
if not sourcePath and not hasInlineCode:
|
||
finalPayload = {"success": False, "message": "Either source_path or source_code is required."}
|
||
return
|
||
|
||
if sourcePath:
|
||
sourceType = await get_android_file_type(sourcePath)
|
||
logStep(f"Source type detected -> {sourceType}")
|
||
if sourceType != "file":
|
||
finalPayload = {"success": False, "message": f"Sandbox script source must be a file: {sourcePath}"}
|
||
return
|
||
|
||
if not parsedParams or not isinstance(parsedParams, dict) or isinstance(parsedParams, list):
|
||
finalPayload = {"success": False, "message": f"params_json must be a JSON object: {paramsJson}"}
|
||
return
|
||
logStep("params_json parsed successfully.")
|
||
|
||
if envFilePath:
|
||
envType = await get_android_file_type(envFilePath)
|
||
logStep(f"Env file type detected -> {envType}")
|
||
if envType != "file":
|
||
finalPayload = {"success": False, "message": f"env_file_path must be a file: {envFilePath}"}
|
||
return
|
||
|
||
executionMode = SANDBOX_SCRIPT_EXECUTION_MODE_CODE if hasInlineCode else SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT
|
||
scriptIdentityPath = sourcePath or path_join(SANDBOX_JS_TEMP_DIR, f"{scriptLabel}_{int(time.time() * 1000)}.inline.js")
|
||
logStep(f"Execution mode -> {executionMode}")
|
||
logStep(f"Execution target -> {scriptIdentityPath}")
|
||
|
||
executionResult = await Tools.SoftwareSettings.executeSandboxScriptDirect({
|
||
"source_path": sourcePath or None,
|
||
"source_code": sourceCode if hasInlineCode else None,
|
||
"params_json": paramsJson,
|
||
"env_file_path": envFilePath or None,
|
||
"script_label": scriptLabel,
|
||
"wait_ms": waitMs
|
||
})
|
||
logStep(f"Direct execution tool completed -> success={bool(executionResult.success)}, durationMs={executionResult.durationMs if hasattr(executionResult, 'durationMs') else ''}")
|
||
|
||
finalPayload = {
|
||
"success": executionResult.success,
|
||
"message": "Sandbox script executed successfully." if executionResult.success else str(getattr(executionResult, "error", "Sandbox script execution failed.")),
|
||
"data": {"execution_mode": executionMode, "source_path": sourcePath, "has_inline_code": hasInlineCode,
|
||
"env_file_path": envFilePath or None, "params_json": paramsJson, "execution_result": executionResult}
|
||
}
|
||
except Exception as error:
|
||
logStep(f"Execution failed -> {get_error_message(error)}")
|
||
finalPayload = {"success": False, "message": get_error_message(error)}
|
||
finally:
|
||
finish(finalPayload or {"success": False, "message": "Sandbox script execution did not produce a final result."})
|
||
|
||
|
||
async def read_environment_variable(params=None):
|
||
try:
|
||
key = ((params or {}).get("key", "") or "").strip()
|
||
if not key:
|
||
complete({"success": False, "message": "Missing required parameter: key"})
|
||
return
|
||
result = await Tools.SoftwareSettings.readEnvironmentVariable(key)
|
||
complete({
|
||
"success": True,
|
||
"message": f"Environment variable read: {key}" if result.exists else f"Environment variable not set: {key}",
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def write_environment_variable(params=None):
|
||
try:
|
||
key = ((params or {}).get("key", "") or "").strip()
|
||
if not key:
|
||
complete({"success": False, "message": "Missing required parameter: key"})
|
||
return
|
||
value = (params or {}).get("value", "")
|
||
result = await Tools.SoftwareSettings.writeEnvironmentVariable(key, str(value))
|
||
complete({
|
||
"success": True,
|
||
"message": f"Environment variable cleared: {key}" if result.cleared else f"Environment variable written: {key}",
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def restart_mcp_with_logs(params=None):
|
||
try:
|
||
timeoutMs = (params or {}).get("timeout_ms") if params else None
|
||
result = await Tools.SoftwareSettings.restartMcpWithLogs(timeoutMs)
|
||
complete({
|
||
"success": True,
|
||
"message": (f"MCP restart timed out after {result.elapsedMs}ms." if result.timedOut
|
||
else f"MCP restart completed: {result.successCount} success, {result.failedCount} failed."),
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def get_speech_services_config():
|
||
try:
|
||
result = await Tools.SoftwareSettings.getSpeechServicesConfig()
|
||
complete({"success": True, "message": "Speech services config fetched.", "data": {"parsed": result}})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def set_speech_services_config(params=None):
|
||
try:
|
||
updates = dict(params or {})
|
||
result = await Tools.SoftwareSettings.setSpeechServicesConfig(updates)
|
||
complete({"success": True, "message": "Speech services config updated.", "data": {"updates": updates, "parsed": result}})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def test_tts_playback(params=None):
|
||
try:
|
||
text = ((params or {}).get("text", "") or "").strip()
|
||
if not text:
|
||
complete({"success": False, "message": "Missing required parameter: text"})
|
||
return
|
||
options = dict(params or {})
|
||
options.pop("text", None)
|
||
result = await Tools.SoftwareSettings.testTtsPlayback(text, options)
|
||
success = result.playbackTriggered
|
||
detailMessage = (result.errorMessage or "").strip() or "TTS playback test failed."
|
||
complete({"success": success, "message": "TTS playback test triggered." if success else detailMessage, "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def list_model_configs():
|
||
try:
|
||
result = await Tools.SoftwareSettings.listModelConfigs()
|
||
complete({"success": True, "message": "Model configs listed.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def create_model_config(params=None):
|
||
try:
|
||
options = dict(params or {})
|
||
result = await Tools.SoftwareSettings.createModelConfig(options)
|
||
complete({"success": True, "message": "Model config created.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def update_model_config(params=None):
|
||
try:
|
||
configId = ((params or {}).get("config_id", "") or "").strip()
|
||
if not configId:
|
||
complete({"success": False, "message": "Missing required parameter: config_id"})
|
||
return
|
||
updates = dict(params or {})
|
||
updates.pop("config_id", None)
|
||
result = await Tools.SoftwareSettings.updateModelConfig(configId, updates)
|
||
complete({"success": True, "message": "Model config updated.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def delete_model_config(params=None):
|
||
try:
|
||
configId = ((params or {}).get("config_id", "") or "").strip()
|
||
if not configId:
|
||
complete({"success": False, "message": "Missing required parameter: config_id"})
|
||
return
|
||
result = await Tools.SoftwareSettings.deleteModelConfig(configId)
|
||
complete({"success": True, "message": "Model config deleted.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def list_function_model_configs():
|
||
try:
|
||
result = await Tools.SoftwareSettings.listFunctionModelConfigs()
|
||
complete({"success": True, "message": "Function model bindings listed.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
def pick_context_summary_fields(config):
|
||
if not config:
|
||
config = {}
|
||
return {
|
||
"context_length": config.get("contextLength"),
|
||
"max_context_length": config.get("maxContextLength"),
|
||
"enable_max_context_mode": config.get("enableMaxContextMode"),
|
||
"summary_token_threshold": config.get("summaryTokenThreshold"),
|
||
"enable_summary": config.get("enableSummary"),
|
||
"enable_summary_by_message_count": config.get("enableSummaryByMessageCount"),
|
||
"summary_message_count_threshold": config.get("summaryMessageCountThreshold")
|
||
}
|
||
|
||
|
||
async def get_function_model_config(params=None):
|
||
try:
|
||
functionType = ((params or {}).get("function_type", "") or "").strip()
|
||
if not functionType:
|
||
complete({"success": False, "message": "Missing required parameter: function_type"})
|
||
return
|
||
result = await Tools.SoftwareSettings.getFunctionModelConfig(functionType)
|
||
config = (result.config if result and hasattr(result, "config") else {}) or {}
|
||
contextSummaryKeys = {"contextLength", "maxContextLength", "enableMaxContextMode", "summaryTokenThreshold",
|
||
"enableSummary", "enableSummaryByMessageCount", "summaryMessageCountThreshold"}
|
||
configWithoutContextSummary = {k: v for k, v in config.items() if k not in contextSummaryKeys}
|
||
filteredResult = {**result.__dict__} if hasattr(result, "__dict__") else dict(result)
|
||
filteredResult["config"] = configWithoutContextSummary
|
||
complete({"success": True, "message": "Function model config fetched.", "data": filteredResult})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def get_context_summary_config(params=None):
|
||
try:
|
||
functionType = ((params or {}).get("function_type", "CHAT") or "CHAT").strip().upper()
|
||
if not functionType:
|
||
complete({"success": False, "message": "Missing required parameter: function_type"})
|
||
return
|
||
result = await Tools.SoftwareSettings.getFunctionModelConfig(functionType)
|
||
config = (result.config if result and hasattr(result, "config") else {}) or {}
|
||
if not config:
|
||
complete({"success": False, "message": f"No bound config found for function_type: {functionType}", "data": result})
|
||
return
|
||
complete({
|
||
"success": True,
|
||
"message": f"Context summary config fetched for {functionType}.",
|
||
"data": {
|
||
"function_type": functionType,
|
||
"config_id": getattr(result, "configId", "") or config.get("id", ""),
|
||
"config_name": getattr(result, "configName", "") or config.get("name", ""),
|
||
"model_index": getattr(result, "modelIndex", 0),
|
||
"actual_model_index": getattr(result, "actualModelIndex", 0),
|
||
"selected_model": getattr(result, "selectedModel", ""),
|
||
"context_summary": pick_context_summary_fields(config),
|
||
"raw": result
|
||
}
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def set_context_summary_config(params=None):
|
||
try:
|
||
functionType = ((params or {}).get("function_type", "CHAT") or "CHAT").strip().upper()
|
||
if not functionType:
|
||
complete({"success": False, "message": "Missing required parameter: function_type"})
|
||
return
|
||
binding = await Tools.SoftwareSettings.getFunctionModelConfig(functionType)
|
||
bindingConfig = (binding.config if binding and hasattr(binding, "config") else {}) or {}
|
||
configId = (getattr(binding, "configId", "") or bindingConfig.get("id", "") or "").strip()
|
||
if not configId:
|
||
complete({"success": False, "message": f"No bound config found for function_type: {functionType}", "data": binding})
|
||
return
|
||
|
||
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
|
||
}
|
||
updates = dict(defaultUpdates)
|
||
p = params or {}
|
||
for key in ["context_length", "max_context_length", "enable_max_context_mode", "summary_token_threshold",
|
||
"enable_summary", "enable_summary_by_message_count", "summary_message_count_threshold"]:
|
||
if key in p and p[key] is not None:
|
||
updates[key] = p[key]
|
||
|
||
updateResult = await Tools.SoftwareSettings.updateModelConfig(configId, updates)
|
||
updateResultConfig = (updateResult.config if updateResult and hasattr(updateResult, "config") else {}) or {}
|
||
complete({
|
||
"success": True,
|
||
"message": f"Context summary config updated for {functionType}.",
|
||
"data": {
|
||
"function_type": functionType, "config_id": configId, "applied_updates": updates,
|
||
"before": pick_context_summary_fields(bindingConfig),
|
||
"after": pick_context_summary_fields(updateResultConfig),
|
||
"update_result": updateResult
|
||
}
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def set_function_model_config(params=None):
|
||
try:
|
||
functionType = ((params or {}).get("function_type", "") or "").strip()
|
||
configId = ((params or {}).get("config_id", "") or "").strip()
|
||
if not functionType:
|
||
complete({"success": False, "message": "Missing required parameter: function_type"})
|
||
return
|
||
if not configId:
|
||
complete({"success": False, "message": "Missing required parameter: config_id"})
|
||
return
|
||
result = await Tools.SoftwareSettings.setFunctionModelConfig(functionType, configId, (params or {}).get("model_index"))
|
||
complete({"success": True, "message": "Function model binding updated.", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def test_model_config_connection(params=None):
|
||
try:
|
||
configId = ((params or {}).get("config_id", "") or "").strip()
|
||
if not configId:
|
||
complete({"success": False, "message": "Missing required parameter: config_id"})
|
||
return
|
||
result = await Tools.SoftwareSettings.testModelConfigConnection(configId, (params or {}).get("model_index"))
|
||
success = bool(result and result.success)
|
||
complete({
|
||
"success": success,
|
||
"message": "Model config connection tests passed." if success else "Model config connection tests have failures.",
|
||
"data": result
|
||
})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
async def ping_mcp(params=None):
|
||
try:
|
||
packageName = ((params or {}).get("package_name", "") or "").strip()
|
||
if not packageName:
|
||
complete({"success": False, "message": "Missing required parameter: package_name"})
|
||
return
|
||
result = await Tools.System.usePackage(packageName)
|
||
complete({"success": True, "message": f"Package probe finished: {packageName}", "data": result})
|
||
except Exception as error:
|
||
complete({"success": False, "message": get_error_message(error)})
|
||
|
||
|
||
# 导出
|
||
exports.operit_editor = operit_editor
|
||
exports.how_make_skill = how_make_skill
|
||
exports.list_sandbox_packages = list_sandbox_packages
|
||
exports.set_sandbox_package_enabled = set_sandbox_package_enabled
|
||
exports.debug_install_js_package = debug_install_js_package
|
||
exports.debug_install_toolpkg = debug_install_toolpkg
|
||
exports.debug_run_sandbox_script = debug_run_sandbox_script
|
||
exports.read_environment_variable = read_environment_variable
|
||
exports.write_environment_variable = write_environment_variable
|
||
exports.restart_mcp_with_logs = restart_mcp_with_logs
|
||
exports.get_speech_services_config = get_speech_services_config
|
||
exports.set_speech_services_config = set_speech_services_config
|
||
exports.test_tts_playback = test_tts_playback
|
||
exports.list_model_configs = list_model_configs
|
||
exports.create_model_config = create_model_config
|
||
exports.update_model_config = update_model_config
|
||
exports.delete_model_config = delete_model_config
|
||
exports.list_function_model_configs = list_function_model_configs
|
||
exports.get_function_model_config = get_function_model_config
|
||
exports.get_context_summary_config = get_context_summary_config
|
||
exports.set_context_summary_config = set_context_summary_config
|
||
exports.set_function_model_config = set_function_model_config
|
||
exports.test_model_config_connection = test_model_config_connection
|
||
exports.ping_mcp = ping_mcp
|