callai-cli 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
callai/api.py ADDED
@@ -0,0 +1,134 @@
1
+ """The Python API: run a prompt through a harness from code (DESIGN.md §4).
2
+
3
+ ``run()`` returns the same :class:`Result` as ``-o json``; ``on_event`` gets the same events as
4
+ ``-o stream-json``. Unlike the CLI it reads no ``CALLAI_*`` variables and no config file: every
5
+ setting is an argument, so a call means the same thing on every machine.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable, Sequence
11
+ from typing import Any
12
+
13
+ from . import runner
14
+ from .adapters import Adapter, get_adapter
15
+ from .request import Request
16
+ from .result import Completed, Result
17
+
18
+ EventHandler = Callable[[dict[str, Any]], None]
19
+
20
+
21
+ def run(
22
+ harness_name: str,
23
+ prompt: str,
24
+ *,
25
+ model: str | None = None,
26
+ cwd: str | None = None,
27
+ timeout: float | None = None,
28
+ permission: str = "default",
29
+ system_prompt: str | None = None,
30
+ extra_args: Sequence[str] = (),
31
+ resume: str | None = None,
32
+ continue_session: bool = False,
33
+ binary: str | None = None,
34
+ on_event: EventHandler | None = None,
35
+ ) -> Result:
36
+ """Run one prompt through a harness and return its normalised result.
37
+
38
+ A harness that runs but fails (bad model, refused tool, non-zero exit) is a result with
39
+ ``is_error=True``, not an exception. Exceptions are for runs that couldn't happen or finish:
40
+ :class:`~callai.errors.UsageError` (bad arguments, or an option the harness can't honour),
41
+ :class:`~callai.errors.HarnessNotFound`, :class:`~callai.errors.HarnessTimeout` and
42
+ :class:`~callai.errors.Interrupted`, all subclasses of :class:`~callai.errors.CallaiError`.
43
+
44
+ Args:
45
+ harness_name: One of ``callai.adapters.HARNESS_NAMES``, e.g. ``"codex"``.
46
+ prompt: The prompt.
47
+ model: Model passed to the harness verbatim (no aliases), or ``None`` for its default.
48
+ cwd: Working directory for the harness, or ``None`` for the current one.
49
+ timeout: Seconds before the harness is killed, or ``None`` for no limit.
50
+ permission: ``"default"``, ``"read-only"``, ``"auto"`` or ``"bypass"`` (DESIGN.md §7).
51
+ system_prompt: Extra system prompt, where the harness supports one.
52
+ extra_args: Arguments passed to the harness unchanged.
53
+ resume: Session id to continue (a previous result's ``session_id``).
54
+ continue_session: Continue the harness's most recent session.
55
+ binary: Executable to run instead of the harness's usual one on PATH.
56
+ on_event: Called with each event as it happens: ``start``, then ``text``/``tool_use``/
57
+ ``tool_result``, then ``result`` (DESIGN.md §5). Runs the harness in its streaming mode.
58
+
59
+ Returns:
60
+ The normalised result.
61
+ """
62
+ adapter = get_adapter(harness_name)
63
+ req = Request(
64
+ harness_name=harness_name,
65
+ prompt=prompt,
66
+ model=model,
67
+ output_format="json" if on_event is None else "stream-json",
68
+ cwd=cwd,
69
+ timeout=timeout,
70
+ permission=permission,
71
+ system_prompt=system_prompt,
72
+ extra_args=tuple(extra_args),
73
+ resume=resume,
74
+ continue_session=continue_session,
75
+ )
76
+ argv = adapter.build_argv(req)
77
+ if binary is not None:
78
+ argv = [binary, *argv[1:]]
79
+ result = adapter.parse(run_harness(adapter, req, argv, on_event=on_event), req)
80
+ if on_event is not None:
81
+ on_event({"type": "result", **result.to_dict()})
82
+ return result
83
+
84
+
85
+ def run_harness(
86
+ adapter: Adapter,
87
+ req: Request,
88
+ argv: Sequence[str],
89
+ *,
90
+ verbose: bool = False,
91
+ on_event: EventHandler | None = None,
92
+ ) -> Completed:
93
+ """Run a built command line, turning stdout into events as it arrives when asked to.
94
+
95
+ Shared by :func:`run` and the CLI. With ``on_event``, the binary is looked up first (a missing
96
+ one raises before any event), then ``start`` and each line's events are sent; ``result`` is the
97
+ caller's, since only it parses the output.
98
+
99
+ Args:
100
+ adapter: The harness's adapter.
101
+ req: The request (for the environment, working directory and timeout).
102
+ argv: The command line; ``argv[0]`` is the executable.
103
+ verbose: Copy the harness's stderr to ours as it arrives.
104
+ on_event: Event callback, or ``None``.
105
+
106
+ Returns:
107
+ The raw outcome.
108
+ """
109
+ argv = list(argv)
110
+ on_line = None
111
+ if on_event is not None:
112
+ runner.resolve_binary(argv[0], harness_name=adapter.name, install_url=adapter.install_url)
113
+ on_event({"type": "start", "harness_name": adapter.name, "model": req.model, "argv": argv})
114
+ state: dict[str, Any] = {}
115
+
116
+ def on_line(line: str) -> None:
117
+ """Send the events for one harness stdout line.
118
+
119
+ Args:
120
+ line: The line.
121
+ """
122
+ for event in adapter.stream_events(line, state):
123
+ on_event(event)
124
+
125
+ return runner.run(
126
+ argv,
127
+ cwd=req.cwd,
128
+ timeout=req.timeout,
129
+ verbose=verbose,
130
+ harness_name=adapter.name,
131
+ install_url=adapter.install_url,
132
+ env=adapter.build_env(req),
133
+ on_stdout_line=on_line,
134
+ )