( context: QuickJSContext, toolInvoker: SandboxToolInvoker, pendingDeferreds: Set<QuickJSDeferredPromise>, runPromise: RunPromise, )
| 248 | type RunPromise = <A, E>(effect: Effect.Effect<A, E>) => Promise<A>; |
| 249 | |
| 250 | const createToolBridge = ( |
| 251 | context: QuickJSContext, |
| 252 | toolInvoker: SandboxToolInvoker, |
| 253 | pendingDeferreds: Set<QuickJSDeferredPromise>, |
| 254 | runPromise: RunPromise, |
| 255 | ): QuickJSHandle => |
| 256 | context.newFunction("__executor_invokeTool", (pathHandle, argsHandle) => { |
| 257 | const path = context.getString(pathHandle); |
| 258 | const args = |
| 259 | argsHandle === undefined || context.typeof(argsHandle) === "undefined" |
| 260 | ? undefined |
| 261 | : context.dump(argsHandle); |
| 262 | const deferred = context.newPromise(); |
| 263 | pendingDeferreds.add(deferred); |
| 264 | deferred.settled.finally(() => { |
| 265 | pendingDeferreds.delete(deferred); |
| 266 | }); |
| 267 | |
| 268 | void runPromise(toolInvoker.invoke({ path, args })).then( |
| 269 | (value) => { |
| 270 | if (!deferred.alive) { |
| 271 | return; |
| 272 | } |
| 273 | |
| 274 | const serialized = serializeJson(value, `Tool result for ${path}`); |
| 275 | if (typeof serialized === "undefined") { |
| 276 | deferred.resolve(); |
| 277 | return; |
| 278 | } |
| 279 | |
| 280 | const valueHandle = context.newString(serialized); |
| 281 | deferred.resolve(valueHandle); |
| 282 | valueHandle.dispose(); |
| 283 | }, |
| 284 | (cause) => { |
| 285 | if (!deferred.alive) { |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | // The reject path is reserved for true infra defects. The |
| 290 | // upstream tool-invoker has already replaced the message with |
| 291 | // an opaque generic plus a correlation id, but defensively log |
| 292 | // the cause here and emit a stable generic if upstream changes. |
| 293 | const message = sandboxDefectMessage(cause); |
| 294 | try { |
| 295 | console.error("[executor:quickjs] tool dispatch defect", { path, cause }); |
| 296 | } catch { |
| 297 | /* ignore logger failures */ |
| 298 | } |
| 299 | const errorHandle = context.newError(message); |
| 300 | deferred.reject(errorHandle); |
| 301 | errorHandle.dispose(); |
| 302 | }, |
| 303 | ); |
| 304 | |
| 305 | return deferred.handle; |
| 306 | }); |
| 307 |
no test coverage detected