tools / qwen_bridge.py

Adapts Ghost Agent's async tools to Qwen Agent's synchronous BaseTool interface, threading the runtime context through ContextVars.

Why

Qwen Agent's ReAct loop calls tools synchronously. Ghost Agent's tools are async and need access to live context (sandbox, memory, scratchpad). The bridge:

  1. Detects whether an event loop is already running and, if so, runs the coroutine in a worker thread with a fresh loop (_run_coro_blocking, line 14-55).
  2. Threads the active GhostContext via contextvars.ContextVar (_CTX), so sibling async tasks each see their own context (line 58-88).

Bridged tools

Qwen tool classBackend
GhostFileSystem.call()tool_file_system
GhostExecute.call()tool_execute
GhostKnowledgeBase.call()tool_knowledge_base

Named parameters are extracted from the Qwen call; unknown extras are passed via **extra.

Back-compat

GLOBAL_CONTEXT is exposed as a property that returns _CTX.get(), so older callsites continue to work without per-task isolation.

Transitive soundfile dependency

qwen-agent's utils/utils.py does import soundfile at module load, so importing anything under qwen_agent.* (including qwen_agent.tools.base used by this module) fails with ModuleNotFoundError: No module named 'soundfile' unless the transitive dep is installed. The default agent path (ghost_agent.main) does NOT touch qwen_bridge; only the core/agent_qwen.py variant does.

Covered by tests/test_qwen_bridge_import_error.py.