oc_chat
Loading...
Searching...
No Matches
oc_chat.tools.ask_user Namespace Reference

Functions

None bind_interaction (Callable[[str], str|None]|None read_fn)
str _render_prompt (str question, list[str]|None options, bool allow_free_text)
str _retry_text (int attempt)
dict ask_user (str question, list[str]|None options=None, bool allow_free_text=True)
dict _refused ()

Variables

Callable _read = None
int _MAX_ATTEMPTS = 3
dict TOOLS
list SCHEMAS

Detailed Description

ask_user: structured mid-turn questions with option answers (plan 163).

Purpose
-------
Provides :func:`ask_user` so a worker can ask the operator a question
MID-turn and continue on the answer — the Claude-style question flow,
in-terminal. The answer returns as the tool result; the same ReAct
iteration continues.

The tool reads through a bridge (:func:`bind_interaction`) that the
orchestrator wires to the prompt driver at REPL startup. When unbound
(batch runner, benchmarks, tests) it returns an immediate refusal so
headless runs never block.

Responsibilities
----------------
- :func:`ask_user` — present a question with optional numbered options,
  read one answer, validate, and return a structured dict.
- :func:`bind_interaction` — the bridge seam; stores a
  ``Callable[[str], str | None]`` that the orchestrator wires to
  ``prompt_driver.read_user_input`` at merge.
- Prompt rendering is internal; the operator sees a single-line menu
  prompt (Enter-submits, no multiline chat contract).

Diagnostics
-----------
Domain: OC_CHAT.TOOLS
Levels:
    L2 — lifecycle events
    L3 — semantic details
    L4 — deep tracing

Contracts
---------
- **Unbound → immediate refusal:** when ``_read is None``, returns
  ``{"answered": False, "reason": "no interactive operator in this
  session mode"}`` without blocking. A tool that blocks a headless run
  is a regression.
- **Option picks are 1-based:** the operator types a number; the result
  carries ``choice_index`` (1-based) and ``choice`` (the option text).
- **Max 3 validation attempts:** an out-of-range pick re-prompts; after
  3 failures the tool returns ``{"answered": False, "reason": "no
  valid answer (max attempts exhausted)"}``. The model must handle a
  refusal — never loop forever.
- **Ctrl+C → ``answered: False``:** when the bound ``read_fn`` returns
  ``None`` (the driver's cancel contract), the tool returns immediately
  with ``reason: "operator cancelled (Ctrl+C)"``.
- **Free text when no options:** returns ``text`` in the result.
- **Free text with options + ``allow_free_text=True``:** a non-numeric
  answer is treated as free text rather than an invalid pick.
- **The REPL wiring line is NOT this module's responsibility** (F14
  pattern — the orchestrator wires at merge).

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)

See Also
--------
- oc_chat/docs/conventions/TOOL_CONTRACT.md §5 — per-tool reference.
- oc_chat/docs/conventions/CONTRACTS.md §3 — TOOLS / SCHEMAS shape.
- oc_ui/terminal/input.py:PromptDriver.read_user_input — the menu-path
  signature this tool's bridge is wired to at merge.

Function Documentation

◆ _refused()

dict oc_chat.tools.ask_user._refused ( )
protected
Return the exhausted-attempts refusal dict.

Definition at line 272 of file ask_user.py.

Here is the caller graph for this function:

◆ _render_prompt()

str oc_chat.tools.ask_user._render_prompt ( str question,
list[str] | None options,
bool allow_free_text )
protected
Build the initial prompt string.

Definition at line 108 of file ask_user.py.

Here is the caller graph for this function:

◆ _retry_text()

str oc_chat.tools.ask_user._retry_text ( int attempt)
protected
Prompt suffix shown on retry after an invalid pick.

Definition at line 126 of file ask_user.py.

Here is the caller graph for this function:

◆ ask_user()

dict oc_chat.tools.ask_user.ask_user ( str question,
list[str] | None options = None,
bool allow_free_text = True )
Ask the operator a question mid-turn and return the answer.

Parameters
----------
question:
    The question text displayed to the operator.
options:
    Optional list of pre-defined answers.  The operator picks by
    number (1-based).  When ``None``, only free-text input.
allow_free_text:
    When ``True`` (default) and *options* are given, a non-numeric
    answer is treated as free text instead of an invalid pick.

Returns
-------
dict with keys:
    ``answered`` (bool) — ``True`` when a valid answer was received.
    ``choice_index`` (int | None) — 1-based option index.
    ``choice`` (str | None) — the option text.
    ``text`` (str | None) — free-text answer.
    ``reason`` (str) — present only when ``answered`` is ``False``.

Definition at line 138 of file ask_user.py.

Here is the call graph for this function:

◆ bind_interaction()

None oc_chat.tools.ask_user.bind_interaction ( Callable[[str], str | None] | None read_fn)
Bind (or unbind) the interaction callback.

Call with a callable to wire the tool to a prompt driver; call with
``None`` to unbind (tests, teardown).  The orchestrator wires the
one-line bind at REPL startup (plan 163 §Wiring).

Definition at line 87 of file ask_user.py.

Variable Documentation

◆ _MAX_ATTEMPTS

int oc_chat.tools.ask_user._MAX_ATTEMPTS = 3
protected

Definition at line 105 of file ask_user.py.

◆ _read

Callable oc_chat.tools.ask_user._read = None
protected

Definition at line 83 of file ask_user.py.

◆ SCHEMAS

list oc_chat.tools.ask_user.SCHEMAS

Definition at line 291 of file ask_user.py.

◆ TOOLS

dict oc_chat.tools.ask_user.TOOLS
Initial value:
1= {
2 "ask_user": ask_user,
3}

Definition at line 287 of file ask_user.py.