- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
本文围绕 Operit 在 2026-08-09 完成的「Kotlin 与 TypeScript 桥接接口对齐」任务展开,说明该 Android AI Agent 如何保证脚本运行时(QuickJS/ToolPkg)看到的 TypeScript 声明与 Kotlin 侧@JavascriptInterface真实注入能力严格一致:包括移除没有 Kotlin 实现的死声明、补齐Tools.Net.browserTakeScreenshot类型,以及为 Compose DSL 增加AiChat与AdaptiveSidePanel两个渲染节点。读者读完可以掌握 Operit 脚本桥接的类型维护方法论、参数归一化约定与"无编译静态双向核对"的验证技巧,并能在自己的 ToolPkg 脚本中正确使用这些公开接口。
一、任务背景:声明与运行时脱节带来的三个问题
Operit 的脚本体系由多层组成:Android 侧 Kotlin 代码通过@JavascriptInterface向 QuickJS 脚本运行时暴露桥接方法,脚本开发者则通过examples/types/下的.d.ts声明文件获得类型提示。这套体系中,Kotlin 注入实现与 TypeScript 声明必须一一对应,否则会出现"类型提示缺失"或"提示了根本不存在的方法"两类问题。
本次对齐任务(任务索引)清点出三个具体问题:
examples/types/network.d.ts缺少 Kotlin 已注入的方法:JsTools已向Tools.Net注入了browserTakeScreenshot,但类型声明中没有对应成员,脚本作者无法获得提示,也无法获得参数校验的约束信息;examples/types/core.d.ts保留了两个没有 Kotlin 实现的死声明:NativeInterface命名空间中的setResult与setError在 Kotlin 侧并无对应@JavascriptInterface实现,属于历史遗留,脚本开发者若调用将直接失败;- Compose DSL 声明缺少已确认需要公开的节点:ToolPkg 脚本可以声明式渲染 Compose UI,但
AiChat(嵌入宿主 AI 聊天界面)与AdaptiveSidePanel(自适应侧栏)两个节点在 TypeScript 声明和 Kotlin 渲染器两侧都没有对齐。
任务的目标很明确:让运行时与类型声明在"公开接口"这个边界上完全一致,并同步更新面向脚本开发者的文档。
二、公开接口清点:NativeInterface的边界在哪里
桥接接口清点(1_NativeBridgeInventory.md)首先回答了"哪些方法应该出现在公共类型里"这个边界问题。
JsEngine通过@JavascriptInterface暴露的桥接方法并不全是公开脚本接口——其中一部分由 ToolPkg 运行时与运行时内部包装使用,属于内部桥接,不应进入公共NativeInterface类型。判断标准是:公开NativeInterface类型只描述脚本开发者可以直接使用的桥接方法,因此setResult、setError这种没有 Kotlin 实现的旧成员必须移除,而内部桥接方法也绝不能因为"存在注入"就被复制进core.d.ts。
对齐后,core.d.ts 中NativeInterface命名空间的公共成员为:
| 成员 | 说明 |
|---|---|
callTool(toolType, toolName, paramsJson) | 同步调用工具(legacy 方式),返回 ToolResult 的 JSON 字符串 |
callToolAsync(callbackId, toolType, toolName, paramsJson) | 异步调用工具,回调携带 ToolResult |
callToolAsyncStreaming(callbackId, intermediateCallbackId, toolType, toolName, paramsJson) | 流式异步调用 |
logInfo(message)/logError(message)/logDebug(message, data) | 三类日志桥接 |
registerToolPkgToolboxUiModule(specJson) | 注册 ToolPkg 工具箱 UI 模块 |
registerToolPkgAppLifecycleHook(specJson) | 注册应用生命周期钩子 |
registerToolPkgMessageProcessingPlugin(specJson) | 注册消息处理插件 |
registerToolPkgXmlRenderPlugin(specJson) | 注册 XML 渲染插件 |
getPluginConfigDir(pluginId) | 解析插件持久化配置目录(/sdcard/Download/Operit/plugins/<id>下的绝对路径) |
registerInputMenuTogglePlugin(specJson) | 注册输入菜单开关插件 |
移除setResult与setError后,其余公共声明均能映射到 Kotlin 的@JavascriptInterface实现,ToolPkg 与运行时内部桥接也未混入公共声明。
三、Tools.Net.browserTakeScreenshot:从类型声明到参数归一化
3.1 TypeScript 声明
对齐后,network.d.ts 中browserTakeScreenshot的声明为:
function browserTakeScreenshot(options: { type?: string; // 图片格式,省略时默认 "png" element?: string; // 快照元素描述 ref?: string; // 快照元素引用,需与 element 成对提供 fullPage?: boolean; // 是否截取完整页面 }): Promise<string>;返回值是Promise<string>——即截图保存后的文件路径字符串。声明注释同时强调:提供ref时必须附带与之匹配的快照element描述。
3.2 Kotlin 注入侧的参数归一化
类型声明只是契约的一半,另一半在 Kotlin 注入实现中。JsTools.kt(app/src/main/java/com/ai/assistance/operit/core/tools/javascript/JsTools.kt)向Tools.Net注入该方法的实现,其参数归一化逻辑与声明严格对应:
- 入参校验:
options必须是一个普通对象,否则抛出browserTakeScreenshot only accepts one options object; type:转字符串并trim(),空值兜底为"png";element/ref:分别转字符串(ref额外trim()),并且二者必须成对出现——ref存在而element缺失时报element is required when ref is provided,反之报ref is required when element is provided;fullPage:归一化为布尔值(!!params.fullPage);- 最终调用
toolCall("browser_take_screenshot", params)进入标准浏览器会话工具链。
3.3 Kotlin 工具实现侧的约束
browser_take_screenshot工具在浏览器会话工具中落地(StandardBrowserSessionTools.kt),Kotlin 侧还补充了声明层看不到的运行约束:
type只接受png/jpeg/jpg,否则报type must be png or jpeg;element与ref成对校验(element != null && ref == null时报错);fullPage与ref互斥:fullPage为真且提供ref时报fullPage cannot be used with element screenshots——即"截取完整页面"与"截取指定元素"不可同时使用;- 截图通过
takeScreenshot落盘,返回的ToolResult中result字段携带Saved screenshot to <路径>,同时附上当前打开的标签页列表与页面状态,供 Agent 继续决策。
面向脚本开发者的文档 docs/doc-src/package-dev/network.md 已同步更新,其中明确说明"ref与element必须成对提供,fullPage控制是否截取完整页面"。一个典型用法:
const path = await Tools.Net.browserTakeScreenshot({ type: 'png', fullPage: true }); console.log(path); // 截图保存路径四、Compose DSL 新节点:AiChat与AdaptiveSidePanel
脚本开发者除了调用工具,还可以通过 Compose DSL 以声明方式渲染 UI。本次对齐在 TypeScript 声明与 Kotlin 渲染器两侧同时补齐了两个节点(2_TypeDeclarationsAndDocs.md)。
4.1 TypeScript 声明
compose-dsl.d.ts 中新增:
/** Embeds the host AI chat surface without its workspace panel. */ export interface AiChatProps extends ComposeCommonProps {} /** Controls a responsive trailing panel around the supplied screen content. */ export interface AdaptiveSidePanelProps extends ComposeCommonProps { open: boolean; side: ComposeChildren; onOpenChanged: (open: boolean) => void; defaultWidth?: number; minWidth?: number; minContentWidth?: number; // ... }并在节点工厂中注册(compose-dsl.d.ts):
AiChat: ComposeNodeFactory<AiChatProps>; AdaptiveSidePanel: ComposeNodeFactory<AdaptiveSidePanelProps>;AiChat语义为"嵌入宿主 AI 聊天界面(不含工作区面板)",用于在 ToolPkg 自定义界面中直接嵌入聊天能力;AdaptiveSidePanel语义为"围绕给定屏幕内容控制响应式尾部面板":open控制开关、side为侧栏内容、onOpenChanged为状态变更回调,defaultWidth/minWidth/minContentWidth控制面板宽度行为。
4.2 Kotlin 渲染器实现
Kotlin 侧渲染分发位于 ToolPkgComposeDslScreen.kt:节点类型归一化后,aichat分发到renderAiChatNode,adaptivesidepanel分发到renderAdaptiveSidePanelNode。
renderAiChatNode(L2477-L2484)在应用通用修饰符后直接嵌入AIChatScreen(embedded = true)——即"嵌入模式"的聊天界面;renderAdaptiveSidePanelNode(L2487-L2619)实现了双布局自适应:- 宽屏布局:内容区与侧栏区并排(
Row+weight(1f)),侧栏展开时提供 3dp 宽、56dp 高的拖拽手柄,通过detectDragGestures实时调整面板宽度,并在minWidth/maxWidth范围内coerceIn收敛; - 窄屏布局:内容区全屏铺底,侧栏从右侧滑出,背景叠加
Color.Black.copy(alpha = 0.18f)半透明遮罩,点击遮罩触发onOpenChanged(false)关闭——这正是任务文档记录的"宽屏拖拽调宽、窄屏遮罩关闭"能力来源。
- 宽屏布局:内容区与侧栏区并排(
onOpenChanged为必填动作属性,渲染器通过ToolPkgComposeDslParser.extractActionId提取动作 ID,缺失或空白都会直接抛错。
五、静态双向核对:不跑编译的验证方法论
本任务遵循执行约束——不运行编译、构建或测试命令(index.md),验证完全采用静态核对,这也是它最有方法论价值的地方。核对范围(3_StaticVerification.md)包括四件事:
- 检查
core.d.ts的NativeInterface不包含没有 Kotlin 实现的旧成员; - 比较
JsTools.kt注入的浏览器截图方法与network.d.ts的Net成员; - 比较
compose-dsl.d.ts的新增节点与 Kotlin Compose DSL 节点分发; - 审查文档和类型文件的差异。
完成标准与核对结果:
core.d.ts中每个NativeInterface成员均能在 Kotlin@JavascriptInterface中找到实现;setResult与setError未出现在任何公开类型声明或开发者文档中;browserTakeScreenshot的四个参数(type/element/ref/fullPage)与 Kotlin 归一化逻辑一致;- Compose DSL 类型工厂与 Kotlin 渲染节点各 88 个,双向集合无差异——以集合比较的方式确认"声明了的一定能渲染,能渲染的一定已声明";
git diff --check未发现空白错误。
这套方法可以直接迁移到其他"声明文件 ↔ 运行时实现"双端同步的维护场景:先清点注入面,再定义公开边界,然后做双向集合比较,最后用 diff 审查收尾。
六、对齐后的收益:脚本开发者的实际体验
所有公开类型都通过 examples/types/index.d.ts 统一再导出(export * from './core'、export * from './network'、export * from './compose-dsl'等),脚本作者只需引用这一个入口即可获得完整类型提示:
- 写
Tools.Net.browserTakeScreenshot时能获得参数结构、默认值与返回值提示,不再"盲写"; - 写 Compose DSL 时可以使用
AiChat嵌入聊天、用AdaptiveSidePanel做响应式侧栏,且有完整的 props 类型约束; - 不会再看到
NativeInterface.setResult/setError这类调用即失败的幽灵方法。
从源码结构看,这正是 Operit "Kotlin 运行时注入 → TypeScript 声明 → 开发者文档"三层一致性的一个典型维护闭环:任何新增的@JavascriptInterface注入或 Compose DSL 节点,都应同步完成类型声明、文档更新与双向静态核对,才能保证脚本生态的类型可靠与可检索性。
相关文件索引
- 任务索引与执行约束:docs/TODO/kt_ts_bridge_alignment_20260809/index.md
- 公开接口清点:1_NativeBridgeInventory.md
- 类型声明与文档更新:2_TypeDeclarationsAndDocs.md
- 静态双向核对:3_StaticVerification.md
- 桥接类型声明:examples/types/core.d.ts、examples/types/network.d.ts、examples/types/compose-dsl.d.ts、统一入口 examples/types/index.d.ts
- Kotlin 注入实现:JsTools.kt、浏览器会话工具 StandardBrowserSessionTools.kt、Compose DSL 渲染器 ToolPkgComposeDslScreen.kt
- 开发者文档:docs/doc-src/package-dev/network.md
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
Operit Kotlin 与 TypeScript 桥接接口对齐:NativeInterface 公开接口清点与类型声明修复实战
Operit Kotlin 与 TypeScript 桥接接口对齐:NativeInterface 公开接口清点与类型声明修复实战 导读 本文围绕 Operit
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化IntelliJ Platform Kotlin UI DSL v2 实战指南:用声明式 Kotlin 构建对话框、设置页与表单化工具窗口
IntelliJ Platform Kotlin UI DSL v2 实战指南:用声明式 Kotlin 构建对话框、设置页与表单化工具窗口 本指南以仓库内 .a
开发工具IDE代码编辑器TypeScript 声明合并与类型扩展:同名声明合并与接口继承的完整实战指南
TypeScript 声明合并与类型扩展:同名声明合并与接口继承的完整实战指南 导读 在 TypeScript 的类型系统中,"合并(Merging)"与"扩展
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考