返回源码地图

packages/experimental/computer-use-cua-driver-native/src/index.ts

main snapshot · da00f7f5358f · 正文引用章节 12;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1/**
2 * Computer use through the in-process Cua Driver native SDK and its own tools.
3 * @module @deepseek-ai/dsh-experimental-computer-use-cua-driver-native
4 */
5
6import type { Context } from '@deepseek-ai/cordis'
7import Schema from '@deepseek-ai/schemastery'
8import { ComputerUseProviderName } from '@deepseek-ai/dsh-computer-use/brand'
9import { createMcpToolDefinition } from '@deepseek-ai/dsh-mcp-client'
10import { z } from 'zod'
11import type { CuaDriver as NativeDriver } from '@trycua/cua-driver'
12import type {} from '@deepseek-ai/dsh-computer-use'
13import type {} from '@deepseek-ai/dsh-system-prompt'
14import type {} from '@deepseek-ai/dsh-tools'
15
16/** Cordis plugin identity for the native Cua Driver provider. */
17export const name = 'experimental-computer-use-cua-driver-native'
18
19/** Services required before the native runtime can publish tools. */
20export const inject = ['computerUse', 'tools', 'systemPrompt']
21
22/** The native provider uses the installed SDK's same-process defaults. */
23export const Config = Schema.object({})
24
25const ToolCatalog = z.object({
26 tools: z.array(z.object({
27 name: z.string().min(1),
28 description: z.string().optional(),
29 inputSchema: z.record(z.string(), z.unknown()),
30 outputSchema: z.unknown().optional(),
31 })),
32})
33
34/** DeepSeek's function-name alphabet and maximum length are protocol constants. */
35const TOOL_NAME = /^[A-Za-z0-9_-]{1,64}$/u
36
37const GUIDANCE = `Cua Driver native computer-use tools operate the host desktop. Discover the exact app and window, then get a fresh window snapshot before acting. Use element_token from that snapshot, or coordinates from its screenshot. A new snapshot of that window invalidates its earlier element tokens. Select either target or the legacy pid/window_id fields; do not combine them.
38
39Prefer background delivery. A refusal does not authorize a foreground retry. Verify the requested outcome from fresh state after an action; a delivered click alone does not prove the outcome. After cancellation, inspect current state before retrying because completed input is not rolled back. Other sessions and applications may change the same desktop.
40
41On macOS, cursor-overlay operations may return facility_unavailable even when screenshots and input work.`
42
43/**
44 * Own one native runtime and expose its catalog through the MCP result adapter.
45 * Startup failures roll back every registration. Unload removes tools, aborts
46 * calls and image admission, awaits settlement and SDK shutdown, then releases computer use.
47 * @param ctx - context providing the exclusive registration and tool services.
48 * @returns after native import, runtime creation, and tool discovery complete.
49 */
50export async function apply(ctx: Context): Promise<void> {
51 const lifetime = new AbortController()
52 const pending = new Set<Promise<unknown>>()
53 let driver: NativeDriver | undefined
54 // Cordis announces disposal before it awaits asynchronous plugin startup.
55 ctx.on('internal/plugin', (fiber) => {
56 if (fiber === ctx.fiber && fiber.uid === null) lifetime.abort()
57 }, { global: true })
58 let ready: Promise<void> = Promise.resolve()
59 const dispose = ctx.effect(function* () {
60 yield ctx.computerUse.register(ComputerUseProviderName('cua-driver-native'))
61 yield async () => {
62 lifetime.abort()
63 // apply() reports startup failure; teardown still owns its native handle.
64 await ready.catch(() => {})
65 await Promise.allSettled(pending)
66 if (driver !== undefined) {
67 await driver.shutdown()
68 driver.uniffiDestroy()
69 }
70 }
71 const child = ctx.plugin({
72 name: 'computer-use-cua-driver-native-runtime',
73 inject: ['tools', 'systemPrompt'],
74 apply: mountRuntime,
75 })
76 yield child.dispose
77 ready = Promise.resolve(child).then(() => {})
78 }, 'computer-use-cua-driver-native.runtime')
79 try {
80 await ready
81 } catch (error) {
82 await dispose()
83 throw error
84 }
85
86 /** The child owns tool registrations; the outer effect owns native teardown. */
87 async function mountRuntime(inner: Context): Promise<void> {
88 const { CuaDriver } = await import('@trycua/cua-driver')
89 lifetime.signal.throwIfAborted()
90 // The generated constructor returns its class with an owned binding handle,
91 // but declares only CuaDriverLike, which omits uniffiDestroy().
92 const activeDriver = driver = CuaDriver.create(undefined) as NativeDriver
93 const catalog = ToolCatalog.parse(JSON.parse(await activeDriver.listToolsJson({ signal: lifetime.signal })))
94 lifetime.signal.throwIfAborted()
95 const names = new Set<string>()
96 for (const tool of catalog.tools) {
97 const publicName = `cua_driver_native__${tool.name}`
98 if (!TOOL_NAME.test(publicName)) {
99 throw new Error(`Cua Driver tool "${tool.name}" exceeds the supported function-name format`)
100 }
101 if (names.has(publicName)) throw new Error(`Cua Driver listed tool "${tool.name}" more than once`)
102 names.add(publicName)
103 const definition = createMcpToolDefinition(inner, {
104 name: publicName,
105 rawName: tool.name,
106 description: tool.description ?? '',
107 inputSchema: tool.inputSchema,
108 outputSchema: tool.outputSchema,
109 async call(args, execution) {
110 const combined = AbortSignal.any([execution.signal, lifetime.signal])
111 combined.throwIfAborted()
112 const result = await activeDriver.callTool(tool.name, JSON.stringify(args), { signal: combined })
113 combined.throwIfAborted()
114 return JSON.parse(result.rawJson) as unknown
115 },
116 })
117 inner.tools.register(definition)
118 }
119 inner.on('tools/execute', async (exec, next) => {
120 if (!names.has(exec.name)) return next()
121 const upstream = exec.signal
122 exec.signal = AbortSignal.any([upstream, lifetime.signal])
123 const operation = Promise.resolve().then(next)
124 pending.add(operation)
125 try {
126 return await operation
127 } finally {
128 pending.delete(operation)
129 exec.signal = upstream
130 }
131 })
132 inner.systemPrompt.section({
133 name: 'computer-use:cua-driver-native',
134 order: inner.systemPrompt.getSectionOrder('TOOL_COMPUTER_USE'),
135 text: GUIDANCE,
136 })
137 }
138}