omega-coding-agent 0.1.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.
- omega_agent/__init__.py +19 -0
- omega_agent/agent_events.py +166 -0
- omega_agent/cancellation.py +52 -0
- omega_agent/events.py +157 -0
- omega_agent/harness.py +497 -0
- omega_agent/hooks.py +112 -0
- omega_agent/loop.py +190 -0
- omega_agent/provider.py +56 -0
- omega_agent/session/__init__.py +34 -0
- omega_agent/session/entries.py +48 -0
- omega_agent/session/jsonl.py +99 -0
- omega_agent/session/store.py +310 -0
- omega_agent/session/tree.py +101 -0
- omega_agent/tool_runner.py +116 -0
- omega_agent/tools.py +97 -0
- omega_agent/types.py +222 -0
- omega_ai/__init__.py +14 -0
- omega_ai/anthropic.py +709 -0
- omega_ai/fake.py +184 -0
- omega_ai/openai.py +580 -0
- omega_ai/openai_codex.py +720 -0
- omega_ai/provider.py +22 -0
- omega_ai/retry.py +146 -0
- omega_coding/__init__.py +18 -0
- omega_coding/approval.py +374 -0
- omega_coding/auth.py +384 -0
- omega_coding/builtin_tools.py +812 -0
- omega_coding/cli.py +1001 -0
- omega_coding/clipboard.py +224 -0
- omega_coding/commands.py +904 -0
- omega_coding/compact.py +377 -0
- omega_coding/context.py +187 -0
- omega_coding/cost.py +123 -0
- omega_coding/env.py +86 -0
- omega_coding/evals.py +137 -0
- omega_coding/eventlog.py +160 -0
- omega_coding/file_lock.py +57 -0
- omega_coding/headless.py +125 -0
- omega_coding/history.py +42 -0
- omega_coding/models.py +560 -0
- omega_coding/oauth.py +519 -0
- omega_coding/paths.py +159 -0
- omega_coding/redact.py +194 -0
- omega_coding/status.py +316 -0
- omega_coding/subagent.py +185 -0
- omega_coding/system_prompt.py +125 -0
- omega_coding/truncate.py +175 -0
- omega_coding/tui/__init__.py +13 -0
- omega_coding/tui/adapter.py +130 -0
- omega_coding/tui/app.py +1111 -0
- omega_coding/tui/approval.py +146 -0
- omega_coding/tui/banner.py +272 -0
- omega_coding/tui/config.py +81 -0
- omega_coding/tui/login.py +161 -0
- omega_coding/tui/state.py +299 -0
- omega_coding/tui/themes/__init__.py +267 -0
- omega_coding/tui/themes/high-contrast.json +33 -0
- omega_coding/tui/themes/oxblood-dark.json +33 -0
- omega_coding/tui/themes/oxblood-light.json +33 -0
- omega_coding/tui/themes/slate.json +33 -0
- omega_coding/tui/widgets.py +766 -0
- omega_coding/version.py +42 -0
- omega_coding_agent-0.1.0.dist-info/METADATA +447 -0
- omega_coding_agent-0.1.0.dist-info/RECORD +67 -0
- omega_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- omega_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- omega_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
omega_agent/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""omega_agent — Layer 2, the portable core.
|
|
2
|
+
|
|
3
|
+
The loop, the harness, the two event vocabularies, the message model, and the
|
|
4
|
+
provider *contract*. What it knows about: messages, events, tools, turns.
|
|
5
|
+
|
|
6
|
+
What it does not know about, and must never learn: **files, shells, terminals,
|
|
7
|
+
and vendors.** Those are `omega_coding` and `omega_ai` respectively, and both of
|
|
8
|
+
them import this package rather than the other way round.
|
|
9
|
+
|
|
10
|
+
`provider.py` living here is the point of the whole arrangement. The consumer
|
|
11
|
+
defines the interface and adapters conform to it — reverse that and Anthropic's
|
|
12
|
+
shape becomes the shape of the system. `omega_ai/provider.py` is a re-export, so
|
|
13
|
+
adapters can import from their own package without owning the contract, which is
|
|
14
|
+
exactly what Tau does.
|
|
15
|
+
|
|
16
|
+
`tests/test_layers.py` enforces all of this rather than trusting it.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""The ten agent events — Layer 2's vocabulary.
|
|
2
|
+
|
|
3
|
+
Tier 1 had one event vocabulary and the loop handed it straight upward. That was
|
|
4
|
+
honest for a tier whose only consumer was a `print` statement, but it has a cost
|
|
5
|
+
the CLI made visible: **tool results had no event at all.** `cli.py` reported them
|
|
6
|
+
by watching the transcript list grow, after the fact.
|
|
7
|
+
|
|
8
|
+
So there are two vocabularies, and the split is the point:
|
|
9
|
+
|
|
10
|
+
| | the **12** (`events.py`) | the **10** (this file) |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| about | one model response arriving | the whole run's progress |
|
|
13
|
+
| granularity | one token | "a tool started" |
|
|
14
|
+
| audience | Layer 2 | Layer 4 — a UI |
|
|
15
|
+
|
|
16
|
+
They nest four deep: **agent ▸ turn ▸ message ▸ tool execution.** A renderer can
|
|
17
|
+
indent on that structure without tracking any state of its own.
|
|
18
|
+
|
|
19
|
+
Nothing is lost by moving up a level. Every one of the twelve is carried by
|
|
20
|
+
exactly one of these ten, on the `stream_event` field of the three `message_*`
|
|
21
|
+
events — so a consumer that wants token deltas still gets them, and a consumer
|
|
22
|
+
that only wants "what is happening" can ignore them.
|
|
23
|
+
|
|
24
|
+
Pi and Tau converged on these ten names, as they did on the twelve. Copied
|
|
25
|
+
verbatim rather than improved.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Annotated, Any, Literal
|
|
31
|
+
|
|
32
|
+
from pydantic import Field
|
|
33
|
+
|
|
34
|
+
from omega_agent.events import AssistantMessageEvent
|
|
35
|
+
from omega_agent.types import AssistantMessage, ToolCall, ToolResultMessage, WireModel
|
|
36
|
+
|
|
37
|
+
#: The four nesting levels, outermost first. Exposed so a renderer can derive
|
|
38
|
+
#: indentation from the vocabulary instead of hardcoding it.
|
|
39
|
+
AGENT_EVENT_NESTING = ("agent", "turn", "message", "tool_execution")
|
|
40
|
+
|
|
41
|
+
#: Why a run ended. `stop` is the only success: the model stopped asking for
|
|
42
|
+
#: tools. The other three are all "it did not finish", distinguished because a
|
|
43
|
+
#: UI should say which.
|
|
44
|
+
AgentEndReason = Literal["stop", "max_turns", "error", "aborted"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ----------------------------------------------------------------- agent scope
|
|
48
|
+
|
|
49
|
+
class AgentStartEvent(WireModel):
|
|
50
|
+
"""The run begins. Carries nothing — the loop's inputs are the caller's."""
|
|
51
|
+
|
|
52
|
+
type: Literal["agent_start"] = "agent_start"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AgentEndEvent(WireModel):
|
|
56
|
+
"""The run is over, for any reason.
|
|
57
|
+
|
|
58
|
+
Exactly one of these is emitted per run, including on failure. Tier 1
|
|
59
|
+
signalled failure by yielding a bare provider `error` event, which meant a
|
|
60
|
+
consumer had to distinguish "this turn failed" from "the run is over".
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
type: Literal["agent_end"] = "agent_end"
|
|
64
|
+
reason: AgentEndReason
|
|
65
|
+
error_message: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------ turn scope
|
|
69
|
+
|
|
70
|
+
class TurnStartEvent(WireModel):
|
|
71
|
+
type: Literal["turn_start"] = "turn_start"
|
|
72
|
+
turn: int
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TurnEndEvent(WireModel):
|
|
76
|
+
type: Literal["turn_end"] = "turn_end"
|
|
77
|
+
turn: int
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# --------------------------------------------------------------- message scope
|
|
81
|
+
|
|
82
|
+
# All three carry `stream_event`, the underlying one of the twelve. That is what
|
|
83
|
+
# keeps the coarser vocabulary from being a downgrade: a renderer wanting text
|
|
84
|
+
# deltas reads `stream_event`, one wanting progress ignores it.
|
|
85
|
+
|
|
86
|
+
class MessageStartEvent(WireModel):
|
|
87
|
+
type: Literal["message_start"] = "message_start"
|
|
88
|
+
message: AssistantMessage
|
|
89
|
+
stream_event: AssistantMessageEvent
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MessageUpdateEvent(WireModel):
|
|
93
|
+
type: Literal["message_update"] = "message_update"
|
|
94
|
+
message: AssistantMessage
|
|
95
|
+
stream_event: AssistantMessageEvent
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MessageEndEvent(WireModel):
|
|
99
|
+
"""The response is complete — or failed, which is also complete.
|
|
100
|
+
|
|
101
|
+
`message` is the final assistant message either way. A stream that produced
|
|
102
|
+
500 tokens and then died reports both, as in Tier 1.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
type: Literal["message_end"] = "message_end"
|
|
106
|
+
message: AssistantMessage
|
|
107
|
+
stream_event: AssistantMessageEvent
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# -------------------------------------------------------- tool execution scope
|
|
111
|
+
|
|
112
|
+
class ToolExecutionStartEvent(WireModel):
|
|
113
|
+
"""A tool is about to run. **The event Tier 1 could not emit.**"""
|
|
114
|
+
|
|
115
|
+
type: Literal["tool_execution_start"] = "tool_execution_start"
|
|
116
|
+
tool_call: ToolCall
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ToolExecutionUpdateEvent(WireModel):
|
|
120
|
+
"""Progress from a tool that is still running.
|
|
121
|
+
|
|
122
|
+
Nothing emits this in Tier 2 — no built-in tool reports progress yet. It is
|
|
123
|
+
declared because the vocabulary is ten, not nine, and because a long
|
|
124
|
+
`run_shell` streaming its output is the obvious next use. Same reasoning as
|
|
125
|
+
Tier 1's `CancellationToken`: the seam ships before the thing that fills it.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
type: Literal["tool_execution_update"] = "tool_execution_update"
|
|
129
|
+
tool_call_id: str
|
|
130
|
+
details: dict[str, Any] | None = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ToolExecutionEndEvent(WireModel):
|
|
134
|
+
type: Literal["tool_execution_end"] = "tool_execution_end"
|
|
135
|
+
tool_call: ToolCall
|
|
136
|
+
result: ToolResultMessage
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
AgentEvent = Annotated[
|
|
140
|
+
AgentStartEvent
|
|
141
|
+
| AgentEndEvent
|
|
142
|
+
| TurnStartEvent
|
|
143
|
+
| TurnEndEvent
|
|
144
|
+
| MessageStartEvent
|
|
145
|
+
| MessageUpdateEvent
|
|
146
|
+
| MessageEndEvent
|
|
147
|
+
| ToolExecutionStartEvent
|
|
148
|
+
| ToolExecutionUpdateEvent
|
|
149
|
+
| ToolExecutionEndEvent,
|
|
150
|
+
Field(discriminator="type"),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
AGENT_EVENT_TYPES = frozenset(
|
|
154
|
+
{
|
|
155
|
+
"agent_start",
|
|
156
|
+
"agent_end",
|
|
157
|
+
"turn_start",
|
|
158
|
+
"turn_end",
|
|
159
|
+
"message_start",
|
|
160
|
+
"message_update",
|
|
161
|
+
"message_end",
|
|
162
|
+
"tool_execution_start",
|
|
163
|
+
"tool_execution_update",
|
|
164
|
+
"tool_execution_end",
|
|
165
|
+
}
|
|
166
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""A cancellation token you can actually set.
|
|
2
|
+
|
|
3
|
+
`types.py` defines `CancellationToken` as a Protocol — one method, "should I
|
|
4
|
+
stop?" — and Tier 1 threaded it through the provider interface, the loop, and
|
|
5
|
+
every tool executor without ever creating one. The seam was complete and inert.
|
|
6
|
+
This is the twenty lines that fill it.
|
|
7
|
+
|
|
8
|
+
Kept separate from `harness.py` on purpose. The harness *owns* an instance, but
|
|
9
|
+
the token itself has no dependencies and no knowledge of a conversation, which
|
|
10
|
+
makes it trivially testable and reusable by anything that needs to be
|
|
11
|
+
interruptible.
|
|
12
|
+
|
|
13
|
+
**It latches.** Once cancelled it stays cancelled until `reset()`, so a check
|
|
14
|
+
that happens slightly late still sees the cancellation. The alternative — a
|
|
15
|
+
signal that clears itself when read — loses interrupts that arrive between two
|
|
16
|
+
checks, which is the worst kind of bug to debug.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CancelSignal:
|
|
23
|
+
"""Set by whoever handles the interrupt; read by whoever can act on it.
|
|
24
|
+
|
|
25
|
+
Deliberately not `asyncio.Event`. An Event's natural use is `await
|
|
26
|
+
event.wait()`, which invites code that blocks until cancellation. What is
|
|
27
|
+
wanted here is the opposite: a cheap, synchronous "am I still wanted?" that
|
|
28
|
+
a tool can poll inside a loop, and that a signal handler can set without
|
|
29
|
+
needing an event loop to hand.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__slots__ = ("_cancelled",)
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._cancelled = False
|
|
36
|
+
|
|
37
|
+
def is_cancelled(self) -> bool:
|
|
38
|
+
"""The whole `CancellationToken` protocol."""
|
|
39
|
+
return self._cancelled
|
|
40
|
+
|
|
41
|
+
def cancel(self) -> None:
|
|
42
|
+
"""Request a stop. Idempotent, and safe to call from a signal handler."""
|
|
43
|
+
self._cancelled = True
|
|
44
|
+
|
|
45
|
+
def reset(self) -> None:
|
|
46
|
+
"""Clear the request, ready for the next turn.
|
|
47
|
+
|
|
48
|
+
The harness calls this before every run. Without it the first Ctrl-C
|
|
49
|
+
would cancel every later turn too — an agent that appears to break
|
|
50
|
+
permanently the first time you interrupt it.
|
|
51
|
+
"""
|
|
52
|
+
self._cancelled = False
|
omega_agent/events.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""The twelve stream events — Layer 1's vocabulary.
|
|
2
|
+
|
|
3
|
+
This file is the contract between the provider layer and everything above it.
|
|
4
|
+
Pi and Tau, written in different languages by different people, converged on
|
|
5
|
+
exactly these twelve names and these exact `type` strings. That convergence is
|
|
6
|
+
the strongest evidence in either codebase that the vocabulary *is* the
|
|
7
|
+
architecture, so the names are copied verbatim rather than improved.
|
|
8
|
+
|
|
9
|
+
Deliberately a separate module from `types.py`. Tier 2 introduces a second,
|
|
10
|
+
coarser vocabulary — the ten *agent* events — which will live in its own file
|
|
11
|
+
beside this one. Keeping them apart means you can always tell at a glance which
|
|
12
|
+
layer an event belongs to.
|
|
13
|
+
|
|
14
|
+
Shape of a well-formed stream:
|
|
15
|
+
|
|
16
|
+
start
|
|
17
|
+
text_start → text_delta* → text_end
|
|
18
|
+
thinking_start → thinking_delta* → thinking_end
|
|
19
|
+
toolcall_start → toolcall_delta* → toolcall_end
|
|
20
|
+
done | error
|
|
21
|
+
|
|
22
|
+
Two invariants the provider layer owes upward, enforced in the adapter:
|
|
23
|
+
|
|
24
|
+
* exactly one `start`
|
|
25
|
+
* exactly one terminal event — `done` or `error` — even if the vendor
|
|
26
|
+
stream dies silently
|
|
27
|
+
|
|
28
|
+
Buying those here, once, means the loop never needs a timeout, a sentinel, or a
|
|
29
|
+
"what if the stream just stops" branch.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import Annotated, Literal
|
|
35
|
+
|
|
36
|
+
from pydantic import Field
|
|
37
|
+
|
|
38
|
+
from omega_agent.types import AssistantMessage, ToolCall, WireModel
|
|
39
|
+
|
|
40
|
+
# Every streaming event carries `partial`: the whole message as it exists so far.
|
|
41
|
+
# It costs a copy per event and buys stateless consumers — a renderer can ignore
|
|
42
|
+
# the delta entirely and just redraw `partial`.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AssistantStartEvent(WireModel):
|
|
46
|
+
type: Literal["start"] = "start"
|
|
47
|
+
partial: AssistantMessage
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------------ text
|
|
51
|
+
|
|
52
|
+
class TextStartEvent(WireModel):
|
|
53
|
+
type: Literal["text_start"] = "text_start"
|
|
54
|
+
content_index: int
|
|
55
|
+
partial: AssistantMessage
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TextDeltaEvent(WireModel):
|
|
59
|
+
type: Literal["text_delta"] = "text_delta"
|
|
60
|
+
content_index: int
|
|
61
|
+
delta: str
|
|
62
|
+
partial: AssistantMessage
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TextEndEvent(WireModel):
|
|
66
|
+
type: Literal["text_end"] = "text_end"
|
|
67
|
+
content_index: int
|
|
68
|
+
content: str
|
|
69
|
+
partial: AssistantMessage
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# -------------------------------------------------------------------- thinking
|
|
73
|
+
|
|
74
|
+
class ThinkingStartEvent(WireModel):
|
|
75
|
+
type: Literal["thinking_start"] = "thinking_start"
|
|
76
|
+
content_index: int
|
|
77
|
+
partial: AssistantMessage
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ThinkingDeltaEvent(WireModel):
|
|
81
|
+
type: Literal["thinking_delta"] = "thinking_delta"
|
|
82
|
+
content_index: int
|
|
83
|
+
delta: str
|
|
84
|
+
partial: AssistantMessage
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ThinkingEndEvent(WireModel):
|
|
88
|
+
type: Literal["thinking_end"] = "thinking_end"
|
|
89
|
+
content_index: int
|
|
90
|
+
content: str
|
|
91
|
+
partial: AssistantMessage
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------------- tool calls
|
|
95
|
+
|
|
96
|
+
class ToolCallStartEvent(WireModel):
|
|
97
|
+
type: Literal["toolcall_start"] = "toolcall_start"
|
|
98
|
+
content_index: int
|
|
99
|
+
partial: AssistantMessage
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ToolCallDeltaEvent(WireModel):
|
|
103
|
+
type: Literal["toolcall_delta"] = "toolcall_delta"
|
|
104
|
+
content_index: int
|
|
105
|
+
delta: str
|
|
106
|
+
partial: AssistantMessage
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ToolCallEndEvent(WireModel):
|
|
110
|
+
type: Literal["toolcall_end"] = "toolcall_end"
|
|
111
|
+
content_index: int
|
|
112
|
+
tool_call: ToolCall
|
|
113
|
+
partial: AssistantMessage
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# -------------------------------------------------------------------- terminal
|
|
117
|
+
|
|
118
|
+
DoneReason = Literal["stop", "length", "toolUse"]
|
|
119
|
+
ErrorReason = Literal["aborted", "error"]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AssistantDoneEvent(WireModel):
|
|
123
|
+
type: Literal["done"] = "done"
|
|
124
|
+
reason: DoneReason
|
|
125
|
+
message: AssistantMessage
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AssistantErrorEvent(WireModel):
|
|
129
|
+
"""A failure, delivered as data rather than raised.
|
|
130
|
+
|
|
131
|
+
A stream that emitted 500 tokens and *then* failed has two things to report.
|
|
132
|
+
An exception carries only the failure and discards the tokens; this carries
|
|
133
|
+
both, because `error` holds the message accumulated so far.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
type: Literal["error"] = "error"
|
|
137
|
+
reason: ErrorReason
|
|
138
|
+
error: AssistantMessage
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
AssistantMessageEvent = Annotated[
|
|
142
|
+
AssistantStartEvent
|
|
143
|
+
| TextStartEvent
|
|
144
|
+
| TextDeltaEvent
|
|
145
|
+
| TextEndEvent
|
|
146
|
+
| ThinkingStartEvent
|
|
147
|
+
| ThinkingDeltaEvent
|
|
148
|
+
| ThinkingEndEvent
|
|
149
|
+
| ToolCallStartEvent
|
|
150
|
+
| ToolCallDeltaEvent
|
|
151
|
+
| ToolCallEndEvent
|
|
152
|
+
| AssistantDoneEvent
|
|
153
|
+
| AssistantErrorEvent,
|
|
154
|
+
Field(discriminator="type"),
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
TERMINAL_EVENT_TYPES = frozenset({"done", "error"})
|