Textual UI adapter for agent execution.
Prefix for synthetic human messages (e.g. interrupt cancellation notices).
Such messages are written to the messages channel for the agent's benefit on
resume but are not user-authored, so they are filtered out of both the rendered
transcript and a thread's initial prompt. Shared here so the single producer
(textual_adapter) and its consumers (app, sessions) agree on one literal.
Per-model dict key: the (provider, model_name) pair.
Pairing the provider with the model name keeps the same model served by
different providers (e.g. gpt-5.5 via openai vs azure) in separate rows
instead of collapsing them. The key is always built from the same values stored
on the corresponding ModelStats, so key and fields never diverge.
Sentinel tool_output used when formatting/coercing a tool result raises.
Lets both surfaces keep the terminal tool.result dispatch unconditional
without re-touching the offending content (whose __str__/__repr__ may itself
raise), so a hook consumer still sees the result rather than a dropped event.
Terminal status of a tool call, mirroring ToolMessage.status.
Format a token count into a human-readable short string.
Print a model-usage stats table to a Rich console.
Each row shows the serving provider alongside the model name. When the session spans multiple models each gets its own row with a totals row appended; single-model sessions show one row.
Build the tool.error hook payload (schema documented in hooks).
Build the tool.result hook payload (schema documented in hooks).
tool_output is capped to HOOK_TOOL_OUTPUT_LIMIT here so both surfaces
apply the identical cap regardless of where the raw output originates. When
the cap fires the value ends with TOOL_OUTPUT_TRUNCATION_MARKER (counted
within the cap, so the result never exceeds the limit) so a consumer can tell
a capped result from a short one. tool_args is intentionally not truncated
(see HOOK_TOOL_OUTPUT_LIMIT).
Build the tool.use hook payload (schema documented in hooks).
Classify buffered tool calls that never emitted a tool.use.
Both surfaces log the same end-of-stream diagnostic for tool calls still in
their buffer map when the stream ends: those whose args never parsed, and
those whose args parsed but whose tool_id stayed None (so tool.use was
gated out). Sharing the classification here keeps the two diagnostics from
drifting; each surface still emits its own log lines. parse_args is safe to
re-run (idempotent bar its one-shot warned latch).
Map a raw ToolMessage.status to the two-value hook domain, fail-closed.
"error" and "success" pass through. Any other present value — a future
provider status, an explicit None, or a typo — is unexpected and treated as
"error" (and logged), so an audit or notification hook is never told a
non-successful tool succeeded. Callers pass
getattr(message, "status", "success"), so a missing status arrives as
"success" and is not warned about.
Compute a stable key for buffering an in-progress streamed tool call.
Prefers the streaming index (stable across fragments of one call), then
the tool-call id, falling back to a positional placeholder so unrelated
id-less calls don't collide.
Build the LangGraph stream config dict.
Stamps the shared coding-agent-v1 trace-metadata contract (LSEN-277) via
build_coding_agent_metadata — identity block, plugin/runtime versions,
turn markers, and repo/git/cwd attribution — onto metadata. Metadata set
here propagates trace-wide to every run in the graph (root, llm, tool, and
subagent subgraphs), which is exactly what the contract's "always" and
"where-known" keys require, so the helper output is stamped once here.
Scope-restricted contract keys are deliberately not emitted. approval_policy
(root/interrupted only) and ls_subagent_id / ls_subagent_type (subagent
only) cannot live in this trace-wide metadata: LangGraph propagates each key
to all descendant runs (per-key config merge, langgraph#7926 /
deepagents#3634), so they would leak onto run types outside their contract
appliesTo set and fail validation. This runtime exposes no clean
per-run-type metadata seam to scope them, so they are omitted by design
rather than leaked. (Subagent runs still inherit the parent/root thread_id
and all required keys, satisfying the contract's grouping rule.)
Also injects the dcode version into metadata["lc_versions"] so LangSmith
traces can be correlated with specific releases. create_deep_agent supplies
the SDK version through the compiled graph config, and LangChain merges
nested metadata dictionaries so both versions survive at stream time.
Also records dcode_client_deepagents_version as a dcode-client diagnostic.
This describes the Deep Agents package installed alongside the TUI, which
can differ from a remote graph's Deep Agents runtime version.
Get the glyph set for the current charset mode.
Fire matching hook commands with payload serialized as JSON on stdin.
The event name is automatically injected into the payload under the
"event" key so callers don't need to duplicate it.
The blocking subprocess work is offloaded to a thread so the caller's
event loop is never stalled. Matching hooks run concurrently, each bounded
by HOOK_SUBPROCESS_TIMEOUT. Errors are logged and never propagated.
Schedule dispatch_hook as a background task with a strong reference.
Use this instead of bare create_task(dispatch_hook(...)) to prevent the
task from being garbage collected before completion.
Safe to call from sync code as long as an event loop is running.
Extract @file mentions and return the text with resolved file paths.
Parses @file mentions from the input text and resolves them to absolute
file paths. Files that do not exist or cannot be resolved are excluded with
a warning printed to the console.
Email addresses (e.g., user@example.com) are automatically excluded by
detecting email-like characters before the @ symbol.
Backslash-escaped spaces in paths (e.g., @my\ folder/file.txt) are
unescaped before resolution. Tilde paths (e.g., @~/file.txt) are expanded
via Path.expanduser(). Only regular files are returned; directories are
excluded.
This function does not raise exceptions; invalid paths are handled internally with a console warning.
Create multimodal message content with text, images, and videos.
Convert ToolMessage content into a printable string.
Execute a task with output directed to Textual UI.
This is the Textual-compatible version of execute_task() that uses the TextualUIAdapter for all UI operations.
A question to ask the user.
Request payload sent via interrupt when asking the user questions.
Client-facing builder for the per-run graph context payload.
Callers populate this and pass it via context= to astream/ainvoke.
ConfigurableModelMiddleware and the interrupt_on when predicate read
it from request.runtime.context. In-process LangGraph coerces it into
CLIContextSchema (the registered context_schema); over the API it stays
a plain dict — which is why consumers handle both shapes.
Token stats for a single model within a session.
Stats accumulated over a single agent turn (or full session).
In-progress state for a single streamed tool call.
args and args_parts are two representations of the arguments used one at
a time, depending on whether the provider delivers the value whole or in
JSON string fragments.
Collect file operation metrics during an interaction.
Track pasted images and videos in the current conversation.
Widget displaying an app message.
Widget displaying an assistant message with markdown support.
Uses MarkdownStream for smoother streaming instead of re-rendering
the full content on each update. Once a stream finishes, the message
is re-rendered from the complete source via Markdown.update() to
work around Textualize/textual#6518: MarkdownFence._update_from_block
refreshes the visible Label but leaves _highlighted_code pinned to
the first chunk, so any later recompose (click, focus change, theme
update) re-yields the stale value and wrapped fenced-code bodies vanish.
A full re-parse rebuilds every fence with correct internal state.
Streamed tokens are coalesced in _pending_append and flushed to the
MarkdownStream on a throttled timer (_STREAM_FLUSH_INTERVAL). Writing
every token immediately forced a markdown re-parse per chunk on the UI
event loop, which starved keyboard input while the model streamed.
Batching the writes keeps the event loop free so typing stays responsive.
Widget displaying a diff with syntax highlighting.
Widget displaying a summarization completion notification.
Widget displaying a tool call with collapsible output.
Tool outputs are shown as a 3-line preview by default. Press Ctrl+O to expand/collapse the full output. Shows an animated "Running..." indicator while the tool is executing.
Adapter for rendering agent output to Textual widgets.
This adapter provides an abstraction layer between the agent execution and the Textual UI, allowing streaming output to be rendered as widgets.
Discriminated union for the ask_user widget Future result.
Valid spinner display states, or None to hide.
Key for buffering an in-progress streamed tool call.
The streaming index (an int) when present, else the tool-call id (a
str), else a unique placeholder string (see tool_call_buffer_key). Exported
so both surfaces annotate their buffer maps identically rather than each
spelling the union — the exact drift this module exists to prevent.