smooai-smooth-operator 1.2.0__tar.gz

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.
Files changed (54) hide show
  1. smooai_smooth_operator-1.2.0/.gitignore +41 -0
  2. smooai_smooth_operator-1.2.0/.gitkeep +0 -0
  3. smooai_smooth_operator-1.2.0/PKG-INFO +164 -0
  4. smooai_smooth_operator-1.2.0/README.md +152 -0
  5. smooai_smooth_operator-1.2.0/core/README.md +1 -0
  6. smooai_smooth_operator-1.2.0/core/pyproject.toml +37 -0
  7. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/__init__.py +98 -0
  8. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/agent.py +673 -0
  9. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/cast.py +124 -0
  10. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/checkpoint.py +46 -0
  11. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/compaction.py +66 -0
  12. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/cost.py +80 -0
  13. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/human_gate.py +74 -0
  14. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/knowledge.py +75 -0
  15. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/llm_provider.py +232 -0
  16. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/memory.py +56 -0
  17. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/rerank.py +61 -0
  18. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/thread.py +55 -0
  19. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/tool_search.py +133 -0
  20. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/vector.py +92 -0
  21. smooai_smooth_operator-1.2.0/core/src/smooth_operator_core/workflow.py +122 -0
  22. smooai_smooth_operator-1.2.0/core/tests/test_agent.py +75 -0
  23. smooai_smooth_operator-1.2.0/core/tests/test_cast.py +168 -0
  24. smooai_smooth_operator-1.2.0/core/tests/test_checkpoint.py +76 -0
  25. smooai_smooth_operator-1.2.0/core/tests/test_compaction.py +60 -0
  26. smooai_smooth_operator-1.2.0/core/tests/test_cost.py +80 -0
  27. smooai_smooth_operator-1.2.0/core/tests/test_evals.py +170 -0
  28. smooai_smooth_operator-1.2.0/core/tests/test_human_gate.py +202 -0
  29. smooai_smooth_operator-1.2.0/core/tests/test_llm_provider.py +123 -0
  30. smooai_smooth_operator-1.2.0/core/tests/test_memory.py +67 -0
  31. smooai_smooth_operator-1.2.0/core/tests/test_parallel_tools.py +150 -0
  32. smooai_smooth_operator-1.2.0/core/tests/test_rerank.py +96 -0
  33. smooai_smooth_operator-1.2.0/core/tests/test_retry.py +45 -0
  34. smooai_smooth_operator-1.2.0/core/tests/test_stream.py +126 -0
  35. smooai_smooth_operator-1.2.0/core/tests/test_subagent.py +66 -0
  36. smooai_smooth_operator-1.2.0/core/tests/test_thread.py +133 -0
  37. smooai_smooth_operator-1.2.0/core/tests/test_tool_search.py +162 -0
  38. smooai_smooth_operator-1.2.0/core/tests/test_vector.py +66 -0
  39. smooai_smooth_operator-1.2.0/core/tests/test_workflow.py +126 -0
  40. smooai_smooth_operator-1.2.0/core/uv.lock +481 -0
  41. smooai_smooth_operator-1.2.0/pyproject.toml +52 -0
  42. smooai_smooth_operator-1.2.0/scripts/generate.py +215 -0
  43. smooai_smooth_operator-1.2.0/src/smooth_operator/__init__.py +134 -0
  44. smooai_smooth_operator-1.2.0/src/smooth_operator/_generated.py +1644 -0
  45. smooai_smooth_operator-1.2.0/src/smooth_operator/client.py +470 -0
  46. smooai_smooth_operator-1.2.0/src/smooth_operator/transport.py +152 -0
  47. smooai_smooth_operator-1.2.0/src/smooth_operator/types.py +230 -0
  48. smooai_smooth_operator-1.2.0/src/smooth_operator/validate.py +149 -0
  49. smooai_smooth_operator-1.2.0/tests/test_client.py +381 -0
  50. smooai_smooth_operator-1.2.0/tests/test_conformance.py +78 -0
  51. smooai_smooth_operator-1.2.0/tests/test_domain.py +125 -0
  52. smooai_smooth_operator-1.2.0/tests/test_e2e_live.py +207 -0
  53. smooai_smooth_operator-1.2.0/tests/test_robustness.py +204 -0
  54. smooai_smooth_operator-1.2.0/uv.lock +906 -0
@@ -0,0 +1,41 @@
1
+ # Rust
2
+ target/
3
+ **/*.rs.bk
4
+ # Cargo.lock IS committed: this workspace ships a binary (smooth-operator-server)
5
+ # and the Dockerfile builds with `cargo build --locked`, which requires it.
6
+
7
+ # Node / TypeScript
8
+ node_modules/
9
+ dist/
10
+ *.tsbuildinfo
11
+ .turbo/
12
+
13
+ # Go
14
+ /go/bin/
15
+
16
+ # .NET
17
+ bin/
18
+ obj/
19
+
20
+ # Python
21
+ __pycache__/
22
+ *.pyc
23
+ .venv/
24
+ .mypy_cache/
25
+ .ruff_cache/
26
+
27
+ # SST / deploy
28
+ .sst/
29
+ .open-next/
30
+ cdk.out/
31
+
32
+ # Env / secrets
33
+ .env
34
+ .env.local
35
+ *.pem
36
+
37
+ # OS / editor
38
+ .DS_Store
39
+ .idea/
40
+ .vscode/*
41
+ !.vscode/extensions.json
File without changes
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: smooai-smooth-operator
3
+ Version: 1.2.0
4
+ Summary: Python protocol types and native async WebSocket client for the smooth-operator protocol. Generated from the language-neutral JSON Schemas in spec/.
5
+ License: MIT
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: jsonschema>=4.21
8
+ Requires-Dist: pydantic[email]>=2
9
+ Provides-Extra: websockets
10
+ Requires-Dist: websockets>=12; extra == 'websockets'
11
+ Description-Content-Type: text/markdown
12
+
13
+ <p align="center"><img src="../assets/smooth-logo.svg" alt="Smooth" width="360" /></p>
14
+
15
+ <p align="center"><strong>smooth-operator — Python client.</strong> A native, fully-async WebSocket client for the smooth-operator protocol, with pydantic v2 models.</p>
16
+
17
+ <p align="center">
18
+ <a href="../LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" /></a>
19
+ <img src="https://img.shields.io/badge/tests-26%20passing-success" alt="26 tests passing" />
20
+ <img src="https://img.shields.io/badge/serverless%20%C2%B7%20polyglot%20%C2%B7%20TDD-6f42c1" alt="serverless · polyglot · TDD" />
21
+ <a href="https://lom.smoo.ai"><img src="https://img.shields.io/badge/hosted-lom.smoo.ai-0aa" alt="lom.smoo.ai" /></a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## What is this?
27
+
28
+ The **native async Python client** for the [smooth-operator](../docs/PROTOCOL.md) WebSocket protocol. The pydantic v2 models in `smooth_operator._generated` are generated from the language-neutral JSON Schemas in [`../spec/`](../spec) (and committed), using pydantic discriminated unions so events deserialize to the right concrete type. The wire is camelCase; you work in idiomatic snake_case.
29
+
30
+ ---
31
+
32
+ ## 30-second quickstart
33
+
34
+ ```bash
35
+ uv add smooai-smooth-operator # PyPI publish pending — install from the local path today
36
+ ```
37
+
38
+ Until this package is published to PyPI, install it from a sibling checkout
39
+ (`uv add ../smooth-operator/python`, or `pip install -e path/to/smooth-operator/python`).
40
+ The PyPI distribution name is **`smooai-smooth-operator`** (the import package stays
41
+ `smooth_operator`) — don't `pip install smooth-operator` from the public index until
42
+ the SmooAI release lands.
43
+
44
+ ```python
45
+ import asyncio
46
+ from smooth_operator import SmoothAgentClient
47
+
48
+ async def main():
49
+ client = SmoothAgentClient(url="ws://127.0.0.1:8787/ws")
50
+ await client.connect()
51
+
52
+ session = await client.create_conversation_session(agent_id=agent_id, user_name="Alice")
53
+
54
+ turn = client.send_message(session_id=session.session_id, message="How long is your return window?")
55
+ final = await turn # the terminal eventual_response
56
+ print(final.data.payload.message_id)
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ (Point `url` at your own [`smooth-operator-server`](../rust/README.md) or the hosted endpoint.)
62
+
63
+ ---
64
+
65
+ ## Watch it stream
66
+
67
+ `send_message` returns a turn you can `async for` over for live events **and** `await` for the authoritative terminal response.
68
+
69
+ ```python
70
+ turn = client.send_message(session_id=session.session_id, message="Where is my order?")
71
+
72
+ async for event in turn:
73
+ if event.type == "stream_chunk":
74
+ print(f"\n ↳ node: {event.node}") # workflow node boundary
75
+ elif event.type == "stream_token":
76
+ print(event.token, end="", flush=True) # tokens, live
77
+ elif event.type == "write_confirmation_required":
78
+ # HITL: approve, and the resumed stream flows back into this same turn.
79
+ await client.confirm_tool_action(
80
+ session_id=session.session_id, request_id=turn.request_id, approved=True
81
+ )
82
+
83
+ final = await turn # the terminal eventual_response
84
+ print("\nmessageId:", final.data.payload.message_id)
85
+ ```
86
+
87
+ ```mermaid
88
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','actorBkg':'#0b1426','actorBorder':'#2b3a52','actorTextColor':'#e6edf6','signalColor':'#7c8aa0','signalTextColor':'#e6edf6','noteBkgColor':'#f49f0a','noteTextColor':'#1a0f00','noteBorderColor':'#ff6b6c','fontFamily':'ui-sans-serif, system-ui, sans-serif'}}}%%
89
+ sequenceDiagram
90
+ participant App
91
+ participant C as SmoothAgentClient
92
+ participant S as Service
93
+ App->>C: send_message(...)
94
+ C->>S: { action: send_message }
95
+ S-->>C: immediate_response (202)
96
+ S-->>C: stream_token / stream_chunk …
97
+ S-->>C: eventual_response (200)
98
+ C-->>App: async-for yields events · await resolves final
99
+ ```
100
+
101
+ ---
102
+
103
+ ## camelCase wire, snake_case Python
104
+
105
+ The JSON wire form is camelCase (`requestId`, `sessionId`); the pydantic models use snake_case attributes with camelCase aliases and `populate_by_name = True`. So you construct/access with `session.session_id`, and `model_dump(by_alias=True)` emits the camelCase wire form.
106
+
107
+ ---
108
+
109
+ ## Polyglot — one spec, five clients
110
+
111
+ ```mermaid
112
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
113
+ flowchart LR
114
+ SPEC["spec/ (JSON Schema)"] --> PY["Python<br/>smooth_operator"]
115
+ SPEC --> TS["TypeScript"]
116
+ SPEC --> GO["Go"]
117
+ SPEC --> NET[".NET (+ MEAI IChatClient facade)"]
118
+ SPEC --> RS["Rust"]
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Test-driven by default
124
+
125
+ > **Nothing here is vibe-coded — it's verified against a real LLM gateway.**
126
+
127
+ ```mermaid
128
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
129
+ flowchart TD
130
+ J["🎯 LLM-as-judge quality evals (Rust harness)"]
131
+ E["🌐 Live cross-language E2E — this client boots the real server + drives a real claude-haiku-4-5 turn"]
132
+ C["🧪 Conformance fixtures (shared across all 5 clients)"]
133
+ U["⚡ Unit tests (discriminated-union parsing, alias round-trip, correlation)"]
134
+ J --> E --> C --> U
135
+ ```
136
+
137
+ **26 tests.** The live cross-language E2E boots a real `smooth-operator-server` subprocess (KB seeded) and drives a real `claude-haiku-4-5` turn over WebSocket: ≥1 streamed event, a knowledge-grounded "17", per-session memory.
138
+
139
+ **A real bug the live E2E caught (mocks masked it):** `agentId` is UUID-typed in `spec/`, so pydantic rejected a bare string the lenient Go/TS clients accepted — surfacing a real cross-client `string`-vs-`UUID` alignment gap. A mock fixture using a valid UUID would have hidden it.
140
+
141
+ **The proof story:** an LLM-as-judge scored a multi-turn answer **1/5** (the runtime forgot turn 1's context); the failing eval drove a per-session-memory fix; **it now scores 5/5** — a regression a substring test would have missed. See [`docs/EVALS.md`](../docs/EVALS.md).
142
+
143
+ Live tests are **gated, never silently skipped** — `SMOOTH_AGENT_E2E=1` + `SMOOAI_GATEWAY_KEY` to run; skip cleanly otherwise.
144
+
145
+ ```bash
146
+ uv run pytest # no creds
147
+ SMOOTH_AGENT_E2E=1 uv run pytest -m e2e # live cross-language E2E
148
+ ```
149
+
150
+ ## Develop & regenerate
151
+
152
+ ```bash
153
+ uv sync
154
+ uv run python -c "import smooth_operator"
155
+ uv run python scripts/generate.py # regen pydantic models from ../spec via datamodel-code-generator
156
+ ```
157
+
158
+ ## Smoo-powered or bring-your-own
159
+
160
+ Point `url` at the hosted **[lom.smoo.ai](https://lom.smoo.ai)** endpoint, or at your own self-hosted `smooth-operator-server` — same protocol, same client.
161
+
162
+ ## License
163
+
164
+ MIT © 2026 Smoo AI
@@ -0,0 +1,152 @@
1
+ <p align="center"><img src="../assets/smooth-logo.svg" alt="Smooth" width="360" /></p>
2
+
3
+ <p align="center"><strong>smooth-operator — Python client.</strong> A native, fully-async WebSocket client for the smooth-operator protocol, with pydantic v2 models.</p>
4
+
5
+ <p align="center">
6
+ <a href="../LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" /></a>
7
+ <img src="https://img.shields.io/badge/tests-26%20passing-success" alt="26 tests passing" />
8
+ <img src="https://img.shields.io/badge/serverless%20%C2%B7%20polyglot%20%C2%B7%20TDD-6f42c1" alt="serverless · polyglot · TDD" />
9
+ <a href="https://lom.smoo.ai"><img src="https://img.shields.io/badge/hosted-lom.smoo.ai-0aa" alt="lom.smoo.ai" /></a>
10
+ </p>
11
+
12
+ ---
13
+
14
+ ## What is this?
15
+
16
+ The **native async Python client** for the [smooth-operator](../docs/PROTOCOL.md) WebSocket protocol. The pydantic v2 models in `smooth_operator._generated` are generated from the language-neutral JSON Schemas in [`../spec/`](../spec) (and committed), using pydantic discriminated unions so events deserialize to the right concrete type. The wire is camelCase; you work in idiomatic snake_case.
17
+
18
+ ---
19
+
20
+ ## 30-second quickstart
21
+
22
+ ```bash
23
+ uv add smooai-smooth-operator # PyPI publish pending — install from the local path today
24
+ ```
25
+
26
+ Until this package is published to PyPI, install it from a sibling checkout
27
+ (`uv add ../smooth-operator/python`, or `pip install -e path/to/smooth-operator/python`).
28
+ The PyPI distribution name is **`smooai-smooth-operator`** (the import package stays
29
+ `smooth_operator`) — don't `pip install smooth-operator` from the public index until
30
+ the SmooAI release lands.
31
+
32
+ ```python
33
+ import asyncio
34
+ from smooth_operator import SmoothAgentClient
35
+
36
+ async def main():
37
+ client = SmoothAgentClient(url="ws://127.0.0.1:8787/ws")
38
+ await client.connect()
39
+
40
+ session = await client.create_conversation_session(agent_id=agent_id, user_name="Alice")
41
+
42
+ turn = client.send_message(session_id=session.session_id, message="How long is your return window?")
43
+ final = await turn # the terminal eventual_response
44
+ print(final.data.payload.message_id)
45
+
46
+ asyncio.run(main())
47
+ ```
48
+
49
+ (Point `url` at your own [`smooth-operator-server`](../rust/README.md) or the hosted endpoint.)
50
+
51
+ ---
52
+
53
+ ## Watch it stream
54
+
55
+ `send_message` returns a turn you can `async for` over for live events **and** `await` for the authoritative terminal response.
56
+
57
+ ```python
58
+ turn = client.send_message(session_id=session.session_id, message="Where is my order?")
59
+
60
+ async for event in turn:
61
+ if event.type == "stream_chunk":
62
+ print(f"\n ↳ node: {event.node}") # workflow node boundary
63
+ elif event.type == "stream_token":
64
+ print(event.token, end="", flush=True) # tokens, live
65
+ elif event.type == "write_confirmation_required":
66
+ # HITL: approve, and the resumed stream flows back into this same turn.
67
+ await client.confirm_tool_action(
68
+ session_id=session.session_id, request_id=turn.request_id, approved=True
69
+ )
70
+
71
+ final = await turn # the terminal eventual_response
72
+ print("\nmessageId:", final.data.payload.message_id)
73
+ ```
74
+
75
+ ```mermaid
76
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','actorBkg':'#0b1426','actorBorder':'#2b3a52','actorTextColor':'#e6edf6','signalColor':'#7c8aa0','signalTextColor':'#e6edf6','noteBkgColor':'#f49f0a','noteTextColor':'#1a0f00','noteBorderColor':'#ff6b6c','fontFamily':'ui-sans-serif, system-ui, sans-serif'}}}%%
77
+ sequenceDiagram
78
+ participant App
79
+ participant C as SmoothAgentClient
80
+ participant S as Service
81
+ App->>C: send_message(...)
82
+ C->>S: { action: send_message }
83
+ S-->>C: immediate_response (202)
84
+ S-->>C: stream_token / stream_chunk …
85
+ S-->>C: eventual_response (200)
86
+ C-->>App: async-for yields events · await resolves final
87
+ ```
88
+
89
+ ---
90
+
91
+ ## camelCase wire, snake_case Python
92
+
93
+ The JSON wire form is camelCase (`requestId`, `sessionId`); the pydantic models use snake_case attributes with camelCase aliases and `populate_by_name = True`. So you construct/access with `session.session_id`, and `model_dump(by_alias=True)` emits the camelCase wire form.
94
+
95
+ ---
96
+
97
+ ## Polyglot — one spec, five clients
98
+
99
+ ```mermaid
100
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
101
+ flowchart LR
102
+ SPEC["spec/ (JSON Schema)"] --> PY["Python<br/>smooth_operator"]
103
+ SPEC --> TS["TypeScript"]
104
+ SPEC --> GO["Go"]
105
+ SPEC --> NET[".NET (+ MEAI IChatClient facade)"]
106
+ SPEC --> RS["Rust"]
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Test-driven by default
112
+
113
+ > **Nothing here is vibe-coded — it's verified against a real LLM gateway.**
114
+
115
+ ```mermaid
116
+ %%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
117
+ flowchart TD
118
+ J["🎯 LLM-as-judge quality evals (Rust harness)"]
119
+ E["🌐 Live cross-language E2E — this client boots the real server + drives a real claude-haiku-4-5 turn"]
120
+ C["🧪 Conformance fixtures (shared across all 5 clients)"]
121
+ U["⚡ Unit tests (discriminated-union parsing, alias round-trip, correlation)"]
122
+ J --> E --> C --> U
123
+ ```
124
+
125
+ **26 tests.** The live cross-language E2E boots a real `smooth-operator-server` subprocess (KB seeded) and drives a real `claude-haiku-4-5` turn over WebSocket: ≥1 streamed event, a knowledge-grounded "17", per-session memory.
126
+
127
+ **A real bug the live E2E caught (mocks masked it):** `agentId` is UUID-typed in `spec/`, so pydantic rejected a bare string the lenient Go/TS clients accepted — surfacing a real cross-client `string`-vs-`UUID` alignment gap. A mock fixture using a valid UUID would have hidden it.
128
+
129
+ **The proof story:** an LLM-as-judge scored a multi-turn answer **1/5** (the runtime forgot turn 1's context); the failing eval drove a per-session-memory fix; **it now scores 5/5** — a regression a substring test would have missed. See [`docs/EVALS.md`](../docs/EVALS.md).
130
+
131
+ Live tests are **gated, never silently skipped** — `SMOOTH_AGENT_E2E=1` + `SMOOAI_GATEWAY_KEY` to run; skip cleanly otherwise.
132
+
133
+ ```bash
134
+ uv run pytest # no creds
135
+ SMOOTH_AGENT_E2E=1 uv run pytest -m e2e # live cross-language E2E
136
+ ```
137
+
138
+ ## Develop & regenerate
139
+
140
+ ```bash
141
+ uv sync
142
+ uv run python -c "import smooth_operator"
143
+ uv run python scripts/generate.py # regen pydantic models from ../spec via datamodel-code-generator
144
+ ```
145
+
146
+ ## Smoo-powered or bring-your-own
147
+
148
+ Point `url` at the hosted **[lom.smoo.ai](https://lom.smoo.ai)** endpoint, or at your own self-hosted `smooth-operator-server` — same protocol, same client.
149
+
150
+ ## License
151
+
152
+ MIT © 2026 Smoo AI
@@ -0,0 +1 @@
1
+ # smooth-operator-core (Python)
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "smooai-smooth-operator-core"
3
+ version = "1.2.0"
4
+ description = "Native Python implementation of the smooth-operator agent engine — an in-process, OpenAI-compatible agentic tool-calling loop with knowledge grounding. The Python sibling of the Rust reference engine and the C# core."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ # The engine drives any OpenAI-compatible chat endpoint via the official SDK
10
+ # (pointed at the gateway). This is the IChatClient analogue.
11
+ "openai>=1.40",
12
+ ]
13
+
14
+ [dependency-groups]
15
+ dev = [
16
+ "pytest>=8",
17
+ "pytest-asyncio>=0.23",
18
+ "ruff>=0.4",
19
+ ]
20
+
21
+ [build-system]
22
+ requires = ["hatchling"]
23
+ build-backend = "hatchling.build"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["src/smooth_operator_core"]
27
+
28
+ [tool.pytest.ini_options]
29
+ asyncio_mode = "auto"
30
+ testpaths = ["tests"]
31
+
32
+ [tool.ruff]
33
+ line-length = 120
34
+ target-version = "py311"
35
+
36
+ [tool.ruff.lint]
37
+ extend-select = ["I"]
@@ -0,0 +1,98 @@
1
+ """smooth-operator-core (Python): a native, in-process agent engine.
2
+
3
+ The Phase-0 Python sibling of the Rust reference engine and the C# core — an
4
+ agentic tool-calling loop over any OpenAI-compatible chat client, with in-memory
5
+ knowledge grounding. See ``docs/Architecture/Python Core.md``.
6
+ """
7
+
8
+ from .agent import (
9
+ AgentOptions,
10
+ AgentRunResponse,
11
+ DoneEvent,
12
+ FunctionTool,
13
+ SmoothAgent,
14
+ StreamEvent,
15
+ TextEvent,
16
+ Tool,
17
+ ToolCallEvent,
18
+ ToolResultEvent,
19
+ delegate_tool,
20
+ )
21
+ from .cast import Cast, Clearance, OperatorRole, RoleKind
22
+ from .checkpoint import Checkpoint, CheckpointStore, InMemoryCheckpointStore
23
+ from .cost import CostBudget, CostTracker, ModelPricing, Usage
24
+ from .human_gate import (
25
+ DelegateHumanGate,
26
+ HumanApprovalRequest,
27
+ HumanApprovalResponse,
28
+ HumanDecision,
29
+ HumanGate,
30
+ )
31
+ from .knowledge import InMemoryKnowledge, Knowledge, KnowledgeHit
32
+ from .llm_provider import (
33
+ LlmProvider,
34
+ MockLlmProvider,
35
+ RecordedCall,
36
+ text_response,
37
+ tool_call_response,
38
+ usage,
39
+ )
40
+ from .memory import InMemoryMemory, Memory, MemoryEntry
41
+ from .rerank import LexicalReranker, NoopReranker, Reranker
42
+ from .thread import SmoothAgentThread
43
+ from .tool_search import ToolSearch
44
+ from .vector import Embedder, HashEmbedder, VectorKnowledge
45
+ from .workflow import END, Workflow, WorkflowError
46
+
47
+ __all__ = [
48
+ "AgentOptions",
49
+ "AgentRunResponse",
50
+ "Cast",
51
+ "DoneEvent",
52
+ "StreamEvent",
53
+ "TextEvent",
54
+ "ToolCallEvent",
55
+ "ToolResultEvent",
56
+ "usage",
57
+ "Checkpoint",
58
+ "CheckpointStore",
59
+ "Clearance",
60
+ "CostBudget",
61
+ "CostTracker",
62
+ "DelegateHumanGate",
63
+ "Embedder",
64
+ "FunctionTool",
65
+ "delegate_tool",
66
+ "HashEmbedder",
67
+ "HumanApprovalRequest",
68
+ "HumanApprovalResponse",
69
+ "HumanDecision",
70
+ "HumanGate",
71
+ "InMemoryCheckpointStore",
72
+ "InMemoryKnowledge",
73
+ "InMemoryMemory",
74
+ "Knowledge",
75
+ "KnowledgeHit",
76
+ "LexicalReranker",
77
+ "LlmProvider",
78
+ "Memory",
79
+ "MemoryEntry",
80
+ "MockLlmProvider",
81
+ "ModelPricing",
82
+ "NoopReranker",
83
+ "OperatorRole",
84
+ "RecordedCall",
85
+ "Reranker",
86
+ "RoleKind",
87
+ "SmoothAgent",
88
+ "SmoothAgentThread",
89
+ "Tool",
90
+ "ToolSearch",
91
+ "Usage",
92
+ "VectorKnowledge",
93
+ "Workflow",
94
+ "WorkflowError",
95
+ "END",
96
+ "text_response",
97
+ "tool_call_response",
98
+ ]