tandem-client 0.3.23__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.
@@ -0,0 +1 @@
1
+ test-venv/
@@ -0,0 +1,23 @@
1
+ # Python SDK Migration Guide (v0.1.0)
2
+
3
+ ## Overview
4
+ The `tandem_client` Python SDK has been significantly hardened using Pydantic `AliasChoices` and Discriminator fields to normalize API variance into completely canonical pythonic structure.
5
+
6
+ ## Breaking Changes
7
+
8
+ ### Canonical Property Names (snake_case)
9
+ Models no longer return undefined variance of properties like `sessionID` or `runID`. The SDK parses and maps all properties efficiently into their standard Python snake_case equivalents under strict Pydantic parsing rules.
10
+
11
+ - `session.sessionID` or `sessionId` -> `session.session_id`
12
+ - `run.runID` or `runId` -> `run.run_id`
13
+ - `createdAtMs` -> `created_at_ms`
14
+ - `updatedAtMs` -> `updated_at_ms`
15
+ - `requiresApproval` -> `requires_approval`
16
+
17
+ You no longer need to check `if event.run_id or event.runID`. Simply use `.run_id`.
18
+
19
+ ### `EngineEvent` Discriminated Union
20
+ The SSE stream no longer yields a flat `EngineEvent` object allowing arbitrary properties. `EngineEvent` is now a strongly typed Pydantic V2 Union with the delimiter `type`.
21
+
22
+ ### Validation
23
+ Invalid responses from the Tandem engine will throw a descriptive `TandemValidationError` highlighting the required properties missed, stopping bad downstream state immediately.
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: tandem-client
3
+ Version: 0.3.23
4
+ Summary: Python client for the Tandem autonomous agent engine HTTP + SSE API
5
+ License: MIT
6
+ Keywords: agent,ai,autonomous,llm,sse,tandem
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: httpx-sse>=0.4
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: pydantic>=2.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
13
+ Requires-Dist: pytest>=8.0; extra == 'dev'
14
+ Requires-Dist: respx>=0.21; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # tandem-client
18
+
19
+ Python client for the [Tandem](https://tandem.frumu.ai/) autonomous agent engine HTTP + SSE API.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install tandem-client
25
+ ```
26
+
27
+ Python 3.10+ required.
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ import asyncio
33
+ from tandem_client import TandemClient
34
+
35
+ async def main():
36
+ async with TandemClient(
37
+ base_url="http://localhost:39731",
38
+ token="your-engine-token", # from `tandem-engine token generate`
39
+ ) as client:
40
+ # 1. Create a session
41
+ session_id = await client.sessions.create(
42
+ title="My agent",
43
+ directory="/path/to/my/project",
44
+ )
45
+
46
+ # 2. Start an async run
47
+ run = await client.sessions.prompt_async(
48
+ session_id, "Summarize the README and list the top 3 TODOs"
49
+ )
50
+
51
+ # 3. Stream the response
52
+ async for event in client.stream(session_id, run.run_id):
53
+ if event.type == "session.response":
54
+ print(event.properties.get("delta", ""), end="", flush=True)
55
+ if event.type in ("run.complete", "run.failed"):
56
+ break
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ## Sync usage (scripts)
62
+
63
+ ```python
64
+ from tandem_client import SyncTandemClient
65
+
66
+ client = SyncTandemClient(base_url="http://localhost:39731", token="...")
67
+ session_id = client.sessions.create(title="My agent")
68
+ run = client.sessions.prompt_async(session_id, "Analyze this folder")
69
+ print(f"Run started: {run.run_id}")
70
+ # Note: stream() is async-only; use the async client to receive events
71
+ client.close()
72
+ ```
73
+
74
+ ## API
75
+
76
+ ### `TandemClient(base_url, token, *, timeout=20.0)`
77
+
78
+ Use as an async context manager or call `await client.aclose()` manually.
79
+
80
+ | Method | Description |
81
+ |--------|-------------|
82
+ | `await client.health()` | Check engine readiness |
83
+ | `client.stream(session_id, run_id?)` | Async generator of `EngineEvent` |
84
+ | `client.global_stream()` | Stream all engine events |
85
+ | `await client.list_tool_ids()` | List all registered tool IDs |
86
+
87
+ ---
88
+
89
+ ### `client.sessions`
90
+
91
+ | Method | Description |
92
+ |--------|-------------|
93
+ | `create(title?, directory?, provider?, model?)` | Create session, returns `session_id` |
94
+ | `list(q?, page?, page_size?, archived?, scope?, workspace?)` | List sessions |
95
+ | `get(session_id)` | Get session details |
96
+ | `delete(session_id)` | Delete a session |
97
+ | `messages(session_id)` | Get message history |
98
+ | `active_run(session_id)` | Get active run state |
99
+ | `prompt_async(session_id, prompt)` | Start async run, returns `PromptAsyncResult(run_id)` |
100
+
101
+ ### `client.routines`
102
+
103
+ | Method | Description |
104
+ |--------|-------------|
105
+ | `list(family?)` | List routines or automations |
106
+ | `create(options, family?)` | Create a scheduled routine |
107
+ | `delete(routine_id, family?)` | Delete a routine |
108
+ | `run_now(routine_id, family?)` | Trigger a routine immediately |
109
+ | `list_runs(family?, limit?)` | List recent run records |
110
+ | `list_artifacts(run_id, family?)` | List run artifacts |
111
+
112
+ **Create a cron routine:**
113
+ ```python
114
+ await client.routines.create({
115
+ "name": "Daily digest",
116
+ "schedule": "0 8 * * *",
117
+ "prompt": "Summarize today's activity and write a report to daily-digest.md",
118
+ "allowed_tools": ["read", "write", "websearch"],
119
+ })
120
+ ```
121
+
122
+ ### `client.mcp`
123
+
124
+ ```python
125
+ await client.mcp.add("arcade", "https://mcp.arcade.ai/mcp")
126
+ await client.mcp.connect("arcade")
127
+ tools = await client.mcp.list_tools()
128
+ ```
129
+
130
+ | Method | Description |
131
+ |--------|-------------|
132
+ | `list()` | List registered MCP servers |
133
+ | `list_tools()` | List discovered tools |
134
+ | `add(name, transport, *, headers?, enabled?)` | Register an MCP server |
135
+ | `connect(name)` | Connect and discover tools |
136
+ | `disconnect(name)` | Disconnect |
137
+ | `refresh(name)` | Re-discover tools |
138
+ | `set_enabled(name, enabled)` | Enable/disable |
139
+
140
+ ### `client.channels`
141
+
142
+ ```python
143
+ await client.channels.put("telegram", {"token": "bot:xxx", "allowed_users": ["@you"]})
144
+ status = await client.channels.status()
145
+ print(status.telegram.connected)
146
+ ```
147
+
148
+ ### `client.permissions`
149
+
150
+ ```python
151
+ snapshot = await client.permissions.list()
152
+ for req in snapshot.requests:
153
+ await client.permissions.reply(req.id, "allow")
154
+ ```
155
+
156
+ ### `client.providers`
157
+
158
+ ```python
159
+ catalog = await client.providers.catalog()
160
+ await client.providers.set_defaults("openrouter", "anthropic/claude-3.7-sonnet")
161
+ await client.providers.set_api_key("openrouter", "sk-or-...")
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Common event types
167
+
168
+ | `event.type` | Description |
169
+ |-------------|-------------|
170
+ | `session.response` | Text delta in `event.properties["delta"]` |
171
+ | `session.tool_call` | Tool invocation |
172
+ | `session.tool_result` | Tool result |
173
+ | `run.complete` | Run finished successfully |
174
+ | `run.failed` | Run failed |
175
+ | `permission.request` | Approval needed |
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,163 @@
1
+ # tandem-client
2
+
3
+ Python client for the [Tandem](https://tandem.frumu.ai/) autonomous agent engine HTTP + SSE API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install tandem-client
9
+ ```
10
+
11
+ Python 3.10+ required.
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ import asyncio
17
+ from tandem_client import TandemClient
18
+
19
+ async def main():
20
+ async with TandemClient(
21
+ base_url="http://localhost:39731",
22
+ token="your-engine-token", # from `tandem-engine token generate`
23
+ ) as client:
24
+ # 1. Create a session
25
+ session_id = await client.sessions.create(
26
+ title="My agent",
27
+ directory="/path/to/my/project",
28
+ )
29
+
30
+ # 2. Start an async run
31
+ run = await client.sessions.prompt_async(
32
+ session_id, "Summarize the README and list the top 3 TODOs"
33
+ )
34
+
35
+ # 3. Stream the response
36
+ async for event in client.stream(session_id, run.run_id):
37
+ if event.type == "session.response":
38
+ print(event.properties.get("delta", ""), end="", flush=True)
39
+ if event.type in ("run.complete", "run.failed"):
40
+ break
41
+
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ ## Sync usage (scripts)
46
+
47
+ ```python
48
+ from tandem_client import SyncTandemClient
49
+
50
+ client = SyncTandemClient(base_url="http://localhost:39731", token="...")
51
+ session_id = client.sessions.create(title="My agent")
52
+ run = client.sessions.prompt_async(session_id, "Analyze this folder")
53
+ print(f"Run started: {run.run_id}")
54
+ # Note: stream() is async-only; use the async client to receive events
55
+ client.close()
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### `TandemClient(base_url, token, *, timeout=20.0)`
61
+
62
+ Use as an async context manager or call `await client.aclose()` manually.
63
+
64
+ | Method | Description |
65
+ |--------|-------------|
66
+ | `await client.health()` | Check engine readiness |
67
+ | `client.stream(session_id, run_id?)` | Async generator of `EngineEvent` |
68
+ | `client.global_stream()` | Stream all engine events |
69
+ | `await client.list_tool_ids()` | List all registered tool IDs |
70
+
71
+ ---
72
+
73
+ ### `client.sessions`
74
+
75
+ | Method | Description |
76
+ |--------|-------------|
77
+ | `create(title?, directory?, provider?, model?)` | Create session, returns `session_id` |
78
+ | `list(q?, page?, page_size?, archived?, scope?, workspace?)` | List sessions |
79
+ | `get(session_id)` | Get session details |
80
+ | `delete(session_id)` | Delete a session |
81
+ | `messages(session_id)` | Get message history |
82
+ | `active_run(session_id)` | Get active run state |
83
+ | `prompt_async(session_id, prompt)` | Start async run, returns `PromptAsyncResult(run_id)` |
84
+
85
+ ### `client.routines`
86
+
87
+ | Method | Description |
88
+ |--------|-------------|
89
+ | `list(family?)` | List routines or automations |
90
+ | `create(options, family?)` | Create a scheduled routine |
91
+ | `delete(routine_id, family?)` | Delete a routine |
92
+ | `run_now(routine_id, family?)` | Trigger a routine immediately |
93
+ | `list_runs(family?, limit?)` | List recent run records |
94
+ | `list_artifacts(run_id, family?)` | List run artifacts |
95
+
96
+ **Create a cron routine:**
97
+ ```python
98
+ await client.routines.create({
99
+ "name": "Daily digest",
100
+ "schedule": "0 8 * * *",
101
+ "prompt": "Summarize today's activity and write a report to daily-digest.md",
102
+ "allowed_tools": ["read", "write", "websearch"],
103
+ })
104
+ ```
105
+
106
+ ### `client.mcp`
107
+
108
+ ```python
109
+ await client.mcp.add("arcade", "https://mcp.arcade.ai/mcp")
110
+ await client.mcp.connect("arcade")
111
+ tools = await client.mcp.list_tools()
112
+ ```
113
+
114
+ | Method | Description |
115
+ |--------|-------------|
116
+ | `list()` | List registered MCP servers |
117
+ | `list_tools()` | List discovered tools |
118
+ | `add(name, transport, *, headers?, enabled?)` | Register an MCP server |
119
+ | `connect(name)` | Connect and discover tools |
120
+ | `disconnect(name)` | Disconnect |
121
+ | `refresh(name)` | Re-discover tools |
122
+ | `set_enabled(name, enabled)` | Enable/disable |
123
+
124
+ ### `client.channels`
125
+
126
+ ```python
127
+ await client.channels.put("telegram", {"token": "bot:xxx", "allowed_users": ["@you"]})
128
+ status = await client.channels.status()
129
+ print(status.telegram.connected)
130
+ ```
131
+
132
+ ### `client.permissions`
133
+
134
+ ```python
135
+ snapshot = await client.permissions.list()
136
+ for req in snapshot.requests:
137
+ await client.permissions.reply(req.id, "allow")
138
+ ```
139
+
140
+ ### `client.providers`
141
+
142
+ ```python
143
+ catalog = await client.providers.catalog()
144
+ await client.providers.set_defaults("openrouter", "anthropic/claude-3.7-sonnet")
145
+ await client.providers.set_api_key("openrouter", "sk-or-...")
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Common event types
151
+
152
+ | `event.type` | Description |
153
+ |-------------|-------------|
154
+ | `session.response` | Text delta in `event.properties["delta"]` |
155
+ | `session.tool_call` | Tool invocation |
156
+ | `session.tool_result` | Tool result |
157
+ | `run.complete` | Run finished successfully |
158
+ | `run.failed` | Run failed |
159
+ | `permission.request` | Approval needed |
160
+
161
+ ## License
162
+
163
+ MIT
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tandem-client"
7
+ version = "0.3.23"
8
+ description = "Python client for the Tandem autonomous agent engine HTTP + SSE API"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ keywords = ["tandem", "ai", "agent", "llm", "sse", "autonomous"]
13
+ dependencies = [
14
+ "httpx>=0.27",
15
+ "httpx-sse>=0.4",
16
+ "pydantic>=2.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.0",
22
+ "pytest-asyncio>=0.23",
23
+ "respx>=0.21",
24
+ ]
25
+
26
+ [tool.pytest.ini_options]
27
+ asyncio_mode = "auto"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["tandem_client"]
31
+
32
+
@@ -0,0 +1,179 @@
1
+ """
2
+ tandem_client — Python client for the Tandem autonomous agent engine.
3
+
4
+ Full coverage of the Tandem HTTP + SSE API.
5
+
6
+ Async (recommended)::
7
+
8
+ from tandem_client import TandemClient
9
+
10
+ async with TandemClient(base_url="http://localhost:39731", token="...") as client:
11
+ session_id = await client.sessions.create(title="My agent")
12
+ run = await client.sessions.prompt_async(session_id, "Summarize README.md")
13
+ async for event in client.stream(session_id, run.run_id):
14
+ if event.type == "session.response":
15
+ print(event.properties.get("delta", ""), end="", flush=True)
16
+ if event.type in ("run.complete", "run.failed"):
17
+ break
18
+
19
+ Sync (scripts)::
20
+
21
+ from tandem_client import SyncTandemClient
22
+
23
+ client = SyncTandemClient(base_url="http://localhost:39731", token="...")
24
+ session_id = client.sessions.create(title="My agent")
25
+ """
26
+
27
+ from .client import PromptAsyncResult, SyncTandemClient, TandemClient
28
+ from .types import (
29
+ AgentTeamApprovalsResponse,
30
+ AgentTeamInstance,
31
+ AgentTeamInstancesResponse,
32
+ AgentTeamMissionsResponse,
33
+ AgentTeamSpawnApproval,
34
+ AgentTeamSpawnResponse,
35
+ AgentTeamTemplate,
36
+ AgentTeamTemplatesResponse,
37
+ ArtifactRecord,
38
+ ChannelConfigEntry,
39
+ ChannelStatusEntry,
40
+ ChannelsConfigResponse,
41
+ ChannelsStatusResponse,
42
+ DefinitionCreateResponse,
43
+ DefinitionListResponse,
44
+ EngineEvent,
45
+ EngineMessage,
46
+ MemoryAuditEntry,
47
+ MemoryAuditResponse,
48
+ MemoryItem,
49
+ MemoryListResponse,
50
+ MemoryPromoteResponse,
51
+ MemoryPutResponse,
52
+ MemorySearchResponse,
53
+ MemorySearchResult,
54
+ MessagePart,
55
+ MissionCreateResponse,
56
+ MissionEventResponse,
57
+ MissionGetResponse,
58
+ MissionListResponse,
59
+ MissionRecord,
60
+ PermissionRequestRecord,
61
+ PermissionRule,
62
+ PermissionSnapshotResponse,
63
+ ProviderCatalog,
64
+ ProviderConfigEntry,
65
+ ProviderEntry,
66
+ ProviderModelEntry,
67
+ ProvidersConfigResponse,
68
+ QuestionRecord,
69
+ QuestionsListResponse,
70
+ ResourceListResponse,
71
+ ResourceRecord,
72
+ ResourceWriteResponse,
73
+ RoutineHistoryEntry,
74
+ RoutineHistoryResponse,
75
+ RoutineRecord,
76
+ RunArtifactsResponse,
77
+ RunNowResponse,
78
+ RunRecord,
79
+ SessionDiff,
80
+ SessionListResponse,
81
+ SessionRecord,
82
+ SessionRunState,
83
+ SessionRunStateResponse,
84
+ SessionTodo,
85
+ SkillImportResponse,
86
+ SkillRecord,
87
+ SkillsListResponse,
88
+ SkillTemplate,
89
+ SkillTemplatesResponse,
90
+ SystemHealth,
91
+ ToolExecuteResult,
92
+ ToolSchema,
93
+ )
94
+
95
+ __all__ = [
96
+ # Clients
97
+ "TandemClient",
98
+ "SyncTandemClient",
99
+ "PromptAsyncResult",
100
+ # Health
101
+ "SystemHealth",
102
+ # Sessions
103
+ "SessionRecord",
104
+ "SessionListResponse",
105
+ "SessionRunState",
106
+ "SessionRunStateResponse",
107
+ "SessionDiff",
108
+ "SessionTodo",
109
+ # Messages
110
+ "EngineMessage",
111
+ "MessagePart",
112
+ # Permissions
113
+ "PermissionRule",
114
+ "PermissionRequestRecord",
115
+ "PermissionSnapshotResponse",
116
+ # Questions
117
+ "QuestionRecord",
118
+ "QuestionsListResponse",
119
+ # Providers
120
+ "ProviderEntry",
121
+ "ProviderModelEntry",
122
+ "ProviderCatalog",
123
+ "ProviderConfigEntry",
124
+ "ProvidersConfigResponse",
125
+ # Channels
126
+ "ChannelConfigEntry",
127
+ "ChannelStatusEntry",
128
+ "ChannelsConfigResponse",
129
+ "ChannelsStatusResponse",
130
+ # Memory
131
+ "MemoryItem",
132
+ "MemoryPutResponse",
133
+ "MemorySearchResult",
134
+ "MemorySearchResponse",
135
+ "MemoryListResponse",
136
+ "MemoryPromoteResponse",
137
+ "MemoryAuditEntry",
138
+ "MemoryAuditResponse",
139
+ # Skills
140
+ "SkillRecord",
141
+ "SkillsListResponse",
142
+ "SkillImportResponse",
143
+ "SkillTemplate",
144
+ "SkillTemplatesResponse",
145
+ # Resources
146
+ "ResourceRecord",
147
+ "ResourceListResponse",
148
+ "ResourceWriteResponse",
149
+ # Routines & Automations
150
+ "RoutineRecord",
151
+ "DefinitionListResponse",
152
+ "DefinitionCreateResponse",
153
+ "RunNowResponse",
154
+ "RunRecord",
155
+ "RunArtifactsResponse",
156
+ "RoutineHistoryEntry",
157
+ "RoutineHistoryResponse",
158
+ # Agent Teams
159
+ "AgentTeamTemplate",
160
+ "AgentTeamTemplatesResponse",
161
+ "AgentTeamInstance",
162
+ "AgentTeamInstancesResponse",
163
+ "AgentTeamMissionsResponse",
164
+ "AgentTeamSpawnApproval",
165
+ "AgentTeamApprovalsResponse",
166
+ "AgentTeamSpawnResponse",
167
+ # Missions
168
+ "MissionRecord",
169
+ "MissionCreateResponse",
170
+ "MissionListResponse",
171
+ "MissionGetResponse",
172
+ "MissionEventResponse",
173
+ # Tools
174
+ "ToolSchema",
175
+ "ToolExecuteResult",
176
+ # Events
177
+ "EngineEvent",
178
+ "ArtifactRecord",
179
+ ]