dooers-workers 0.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 (48) hide show
  1. dooers_workers-0.2.0/.github/workflows/publish.yml +49 -0
  2. dooers_workers-0.2.0/.gitignore +5 -0
  3. dooers_workers-0.2.0/PKG-INFO +228 -0
  4. dooers_workers-0.2.0/README.md +212 -0
  5. dooers_workers-0.2.0/docs/plans/2026-02-05-analytics-settings-design.md +645 -0
  6. dooers_workers-0.2.0/examples/fastapi_anthropic.py +105 -0
  7. dooers_workers-0.2.0/examples/fastapi_basic.py +28 -0
  8. dooers_workers-0.2.0/examples/fastapi_langchain.py +53 -0
  9. dooers_workers-0.2.0/examples/fastapi_langgraph.py +79 -0
  10. dooers_workers-0.2.0/examples/fastapi_openai.py +104 -0
  11. dooers_workers-0.2.0/examples/fastapi_openai_agents.py +68 -0
  12. dooers_workers-0.2.0/examples/fastapi_tools.py +51 -0
  13. dooers_workers-0.2.0/examples/fastapi_vertex.py +58 -0
  14. dooers_workers-0.2.0/examples/fastapi_whatsapp_webhook.py +78 -0
  15. dooers_workers-0.2.0/pyproject.toml +52 -0
  16. dooers_workers-0.2.0/requirements.txt +5 -0
  17. dooers_workers-0.2.0/scripts/pcu.py +377 -0
  18. dooers_workers-0.2.0/src/dooers/__init__.py +73 -0
  19. dooers_workers-0.2.0/src/dooers/broadcast.py +180 -0
  20. dooers_workers-0.2.0/src/dooers/config.py +22 -0
  21. dooers_workers-0.2.0/src/dooers/features/__init__.py +0 -0
  22. dooers_workers-0.2.0/src/dooers/features/analytics/__init__.py +12 -0
  23. dooers_workers-0.2.0/src/dooers/features/analytics/collector.py +219 -0
  24. dooers_workers-0.2.0/src/dooers/features/analytics/models.py +50 -0
  25. dooers_workers-0.2.0/src/dooers/features/analytics/worker_analytics.py +100 -0
  26. dooers_workers-0.2.0/src/dooers/features/settings/__init__.py +12 -0
  27. dooers_workers-0.2.0/src/dooers/features/settings/broadcaster.py +97 -0
  28. dooers_workers-0.2.0/src/dooers/features/settings/models.py +72 -0
  29. dooers_workers-0.2.0/src/dooers/features/settings/worker_settings.py +85 -0
  30. dooers_workers-0.2.0/src/dooers/handlers/__init__.py +16 -0
  31. dooers_workers-0.2.0/src/dooers/handlers/memory.py +105 -0
  32. dooers_workers-0.2.0/src/dooers/handlers/request.py +12 -0
  33. dooers_workers-0.2.0/src/dooers/handlers/response.py +66 -0
  34. dooers_workers-0.2.0/src/dooers/handlers/router.py +957 -0
  35. dooers_workers-0.2.0/src/dooers/migrations/__init__.py +3 -0
  36. dooers_workers-0.2.0/src/dooers/migrations/schemas.py +126 -0
  37. dooers_workers-0.2.0/src/dooers/persistence/__init__.py +9 -0
  38. dooers_workers-0.2.0/src/dooers/persistence/base.py +42 -0
  39. dooers_workers-0.2.0/src/dooers/persistence/postgres.py +459 -0
  40. dooers_workers-0.2.0/src/dooers/persistence/sqlite.py +433 -0
  41. dooers_workers-0.2.0/src/dooers/protocol/__init__.py +108 -0
  42. dooers_workers-0.2.0/src/dooers/protocol/frames.py +298 -0
  43. dooers_workers-0.2.0/src/dooers/protocol/models.py +72 -0
  44. dooers_workers-0.2.0/src/dooers/protocol/parser.py +19 -0
  45. dooers_workers-0.2.0/src/dooers/registry.py +101 -0
  46. dooers_workers-0.2.0/src/dooers/server.py +162 -0
  47. dooers_workers-0.2.0/src/dooers/settings.py +3 -0
  48. dooers_workers-0.2.0/uv.lock +587 -0
@@ -0,0 +1,49 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.11"
18
+
19
+ - name: Install build dependencies
20
+ run: pip install build
21
+
22
+ - name: Build package
23
+ run: python -m build
24
+
25
+ - name: Upload distribution artifacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: python-package-distributions
29
+ path: dist/
30
+
31
+ publish:
32
+ name: Publish to PyPI
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: pypi
37
+ url: https://pypi.org/p/dooers-workers
38
+ permissions:
39
+ id-token: write
40
+
41
+ steps:
42
+ - name: Download distribution artifacts
43
+ uses: actions/download-artifact@v4
44
+ with:
45
+ name: python-package-distributions
46
+ path: dist/
47
+
48
+ - name: Publish to PyPI
49
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,5 @@
1
+ .venv
2
+ venv
3
+ __pycache__
4
+ .claude
5
+ .ruff_cache
@@ -0,0 +1,228 @@
1
+ Metadata-Version: 2.4
2
+ Name: dooers-workers
3
+ Version: 0.2.0
4
+ Summary: Python SDK for dooers.ai chat protocol
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: aiosqlite>=0.22.1
7
+ Requires-Dist: asyncpg>=0.31.0
8
+ Requires-Dist: httpx>=0.28.1
9
+ Requires-Dist: pydantic>=2.12.5
10
+ Requires-Dist: sqlalchemy>=2.0.46
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
13
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
14
+ Requires-Dist: ruff>=0.15.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # dooers-workers
18
+
19
+ Python SDK for dooers.ai chat protocol.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install dooers-workers
25
+ ```
26
+
27
+ Or from source:
28
+
29
+ ```bash
30
+ pip install git+https://github.com/dooers/dooers-workers.git
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```python
36
+ from fastapi import FastAPI, WebSocket
37
+ from openai import AsyncOpenAI
38
+ from dooers import WorkerServer, WorkerConfig
39
+
40
+ app = FastAPI()
41
+ openai = AsyncOpenAI()
42
+
43
+ worker_server = WorkerServer(WorkerConfig(
44
+ database_url="sqlite:///worker.db",
45
+ database_type="sqlite",
46
+ ))
47
+
48
+
49
+ async def agent_handler(request, response, memory, analytics, settings):
50
+ yield response.run_start()
51
+
52
+ # Track custom event
53
+ await analytics.track("llm.called", data={"model": "gpt-4o-mini"})
54
+
55
+ completion = await openai.chat.completions.create(
56
+ model="gpt-4o-mini",
57
+ messages=[{"role": "user", "content": request.message}],
58
+ )
59
+
60
+ yield response.text(completion.choices[0].message.content)
61
+ yield response.run_end()
62
+
63
+
64
+ @app.websocket("/ws")
65
+ async def ws(websocket: WebSocket):
66
+ await websocket.accept()
67
+ await worker_server.handle(websocket, agent_handler)
68
+ ```
69
+
70
+ ## agent_handler
71
+
72
+ ```python
73
+ async def agent_handler(request, response, memory, analytics, settings):
74
+ ...
75
+ ```
76
+
77
+ ### Request
78
+
79
+ ```python
80
+ request.message # str
81
+ request.content # list[ContentPart]
82
+ request.thread_id # str
83
+ request.event_id # str
84
+ request.user_id # str | None
85
+ ```
86
+
87
+ ### Memory
88
+
89
+ ```python
90
+ # Get formatted history (defaults to OpenAI format)
91
+ messages = await memory.get_history(limit=50)
92
+
93
+ # Specify format for different providers
94
+ messages = await memory.get_history(format="openai")
95
+ messages = await memory.get_history(format="anthropic")
96
+ messages = await memory.get_history(format="google")
97
+ messages = await memory.get_history(format="cohere")
98
+ messages = await memory.get_history(format="voyage")
99
+
100
+ # Get raw ThreadEvent objects
101
+ events = await memory.get_history_raw(limit=50)
102
+ ```
103
+
104
+ ### Response
105
+
106
+ ```python
107
+ yield response.text("Hello, I'm your assistant.")
108
+ yield response.image(url, mime_type?, alt?)
109
+ yield response.document(url, filename, mime_type)
110
+ yield response.tool_call(name, args)
111
+ yield response.tool_result(name, result)
112
+ yield response.run_start(agent_id?)
113
+ yield response.run_end(status?, error?)
114
+ ```
115
+
116
+ ### Analytics
117
+
118
+ ```python
119
+ await analytics.track("event.name", data={"key": "value"})
120
+ await analytics.like("event", target_id, reason?)
121
+ await analytics.dislike("event", target_id, reason?)
122
+ ```
123
+
124
+ ### Settings
125
+
126
+ ```python
127
+ value = await settings.get("field_id")
128
+ all_values = await settings.get_all()
129
+ await settings.set("field_id", new_value)
130
+ ```
131
+
132
+ ## worker_server
133
+
134
+ ### Persistence
135
+
136
+ Direct database access for threads, events, and settings.
137
+
138
+ ```python
139
+
140
+ persistence = worker_server.persistence
141
+
142
+ thread = await persistence.get_thread(thread_id)
143
+ await persistence.create_thread(thread)
144
+ await persistence.update_thread(thread)
145
+ threads = await persistence.list_threads(worker_id, user_id, cursor, limit)
146
+
147
+ await persistence.create_event(event)
148
+ events = await persistence.get_events(thread_id, after_event_id, limit)
149
+
150
+ settings = await persistence.get_settings(worker_id)
151
+ await persistence.update_setting(worker_id, field_id, value)
152
+ ```
153
+
154
+ ### Broadcast
155
+
156
+ Pushes events to WebSocket subscribers. `send_event` also persists the event and updates the thread timestamp.
157
+
158
+ ```python
159
+
160
+ broadcast = worker_server.broadcast
161
+
162
+ # Persist + broadcast a message (returns event, connections notified)
163
+ event, count = await broadcast.send_event(worker_id, thread_id, content, actor)
164
+
165
+ # Broadcast a thread change (no persistence, notification only)
166
+ count = await broadcast.send_thread_update(worker_id, thread)
167
+
168
+ # Create thread + broadcast (convenience)
169
+ thread, count = await broadcast.create_thread_and_broadcast(worker_id, user_id, title)
170
+ ```
171
+
172
+ ## Settings Schema
173
+
174
+ Define configurable settings for your worker:
175
+
176
+ ```python
177
+ from dooers import (
178
+ WorkerConfig,
179
+ WorkerServer,
180
+ SettingsSchema,
181
+ SettingsField,
182
+ SettingsFieldType,
183
+ SettingsSelectOption,
184
+ )
185
+
186
+ schema = SettingsSchema(
187
+ fields=[
188
+ SettingsField(
189
+ id="model",
190
+ type=SettingsFieldType.SELECT,
191
+ label="Model",
192
+ value="gpt-4o-mini",
193
+ options=[
194
+ SettingsSelectOption(value="gpt-4o-mini", label="GPT-4o Mini"),
195
+ SettingsSelectOption(value="gpt-4o", label="GPT-4o"),
196
+ ],
197
+ ),
198
+ SettingsField(
199
+ id="temperature",
200
+ type=SettingsFieldType.NUMBER,
201
+ label="Temperature",
202
+ value=0.7,
203
+ min=0,
204
+ max=2,
205
+ ),
206
+ ]
207
+ )
208
+
209
+ worker_server = WorkerServer(WorkerConfig(
210
+ database_url="sqlite:///worker.db",
211
+ database_type="sqlite",
212
+ settings_schema=schema,
213
+ ))
214
+ ```
215
+
216
+ ### Field Types
217
+
218
+ - `TEXT` - Single-line text input
219
+ - `NUMBER` - Numeric input with optional min/max
220
+ - `SELECT` - Dropdown selection
221
+ - `CHECKBOX` - Boolean toggle
222
+ - `TEXTAREA` - Multi-line text input
223
+ - `PASSWORD` - Password input (hidden)
224
+ - `EMAIL` - Email input
225
+ - `DATE` - Date picker
226
+ - `IMAGE` - Display-only image (e.g., QR codes)
227
+
228
+
@@ -0,0 +1,212 @@
1
+ # dooers-workers
2
+
3
+ Python SDK for dooers.ai chat protocol.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install dooers-workers
9
+ ```
10
+
11
+ Or from source:
12
+
13
+ ```bash
14
+ pip install git+https://github.com/dooers/dooers-workers.git
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from fastapi import FastAPI, WebSocket
21
+ from openai import AsyncOpenAI
22
+ from dooers import WorkerServer, WorkerConfig
23
+
24
+ app = FastAPI()
25
+ openai = AsyncOpenAI()
26
+
27
+ worker_server = WorkerServer(WorkerConfig(
28
+ database_url="sqlite:///worker.db",
29
+ database_type="sqlite",
30
+ ))
31
+
32
+
33
+ async def agent_handler(request, response, memory, analytics, settings):
34
+ yield response.run_start()
35
+
36
+ # Track custom event
37
+ await analytics.track("llm.called", data={"model": "gpt-4o-mini"})
38
+
39
+ completion = await openai.chat.completions.create(
40
+ model="gpt-4o-mini",
41
+ messages=[{"role": "user", "content": request.message}],
42
+ )
43
+
44
+ yield response.text(completion.choices[0].message.content)
45
+ yield response.run_end()
46
+
47
+
48
+ @app.websocket("/ws")
49
+ async def ws(websocket: WebSocket):
50
+ await websocket.accept()
51
+ await worker_server.handle(websocket, agent_handler)
52
+ ```
53
+
54
+ ## agent_handler
55
+
56
+ ```python
57
+ async def agent_handler(request, response, memory, analytics, settings):
58
+ ...
59
+ ```
60
+
61
+ ### Request
62
+
63
+ ```python
64
+ request.message # str
65
+ request.content # list[ContentPart]
66
+ request.thread_id # str
67
+ request.event_id # str
68
+ request.user_id # str | None
69
+ ```
70
+
71
+ ### Memory
72
+
73
+ ```python
74
+ # Get formatted history (defaults to OpenAI format)
75
+ messages = await memory.get_history(limit=50)
76
+
77
+ # Specify format for different providers
78
+ messages = await memory.get_history(format="openai")
79
+ messages = await memory.get_history(format="anthropic")
80
+ messages = await memory.get_history(format="google")
81
+ messages = await memory.get_history(format="cohere")
82
+ messages = await memory.get_history(format="voyage")
83
+
84
+ # Get raw ThreadEvent objects
85
+ events = await memory.get_history_raw(limit=50)
86
+ ```
87
+
88
+ ### Response
89
+
90
+ ```python
91
+ yield response.text("Hello, I'm your assistant.")
92
+ yield response.image(url, mime_type?, alt?)
93
+ yield response.document(url, filename, mime_type)
94
+ yield response.tool_call(name, args)
95
+ yield response.tool_result(name, result)
96
+ yield response.run_start(agent_id?)
97
+ yield response.run_end(status?, error?)
98
+ ```
99
+
100
+ ### Analytics
101
+
102
+ ```python
103
+ await analytics.track("event.name", data={"key": "value"})
104
+ await analytics.like("event", target_id, reason?)
105
+ await analytics.dislike("event", target_id, reason?)
106
+ ```
107
+
108
+ ### Settings
109
+
110
+ ```python
111
+ value = await settings.get("field_id")
112
+ all_values = await settings.get_all()
113
+ await settings.set("field_id", new_value)
114
+ ```
115
+
116
+ ## worker_server
117
+
118
+ ### Persistence
119
+
120
+ Direct database access for threads, events, and settings.
121
+
122
+ ```python
123
+
124
+ persistence = worker_server.persistence
125
+
126
+ thread = await persistence.get_thread(thread_id)
127
+ await persistence.create_thread(thread)
128
+ await persistence.update_thread(thread)
129
+ threads = await persistence.list_threads(worker_id, user_id, cursor, limit)
130
+
131
+ await persistence.create_event(event)
132
+ events = await persistence.get_events(thread_id, after_event_id, limit)
133
+
134
+ settings = await persistence.get_settings(worker_id)
135
+ await persistence.update_setting(worker_id, field_id, value)
136
+ ```
137
+
138
+ ### Broadcast
139
+
140
+ Pushes events to WebSocket subscribers. `send_event` also persists the event and updates the thread timestamp.
141
+
142
+ ```python
143
+
144
+ broadcast = worker_server.broadcast
145
+
146
+ # Persist + broadcast a message (returns event, connections notified)
147
+ event, count = await broadcast.send_event(worker_id, thread_id, content, actor)
148
+
149
+ # Broadcast a thread change (no persistence, notification only)
150
+ count = await broadcast.send_thread_update(worker_id, thread)
151
+
152
+ # Create thread + broadcast (convenience)
153
+ thread, count = await broadcast.create_thread_and_broadcast(worker_id, user_id, title)
154
+ ```
155
+
156
+ ## Settings Schema
157
+
158
+ Define configurable settings for your worker:
159
+
160
+ ```python
161
+ from dooers import (
162
+ WorkerConfig,
163
+ WorkerServer,
164
+ SettingsSchema,
165
+ SettingsField,
166
+ SettingsFieldType,
167
+ SettingsSelectOption,
168
+ )
169
+
170
+ schema = SettingsSchema(
171
+ fields=[
172
+ SettingsField(
173
+ id="model",
174
+ type=SettingsFieldType.SELECT,
175
+ label="Model",
176
+ value="gpt-4o-mini",
177
+ options=[
178
+ SettingsSelectOption(value="gpt-4o-mini", label="GPT-4o Mini"),
179
+ SettingsSelectOption(value="gpt-4o", label="GPT-4o"),
180
+ ],
181
+ ),
182
+ SettingsField(
183
+ id="temperature",
184
+ type=SettingsFieldType.NUMBER,
185
+ label="Temperature",
186
+ value=0.7,
187
+ min=0,
188
+ max=2,
189
+ ),
190
+ ]
191
+ )
192
+
193
+ worker_server = WorkerServer(WorkerConfig(
194
+ database_url="sqlite:///worker.db",
195
+ database_type="sqlite",
196
+ settings_schema=schema,
197
+ ))
198
+ ```
199
+
200
+ ### Field Types
201
+
202
+ - `TEXT` - Single-line text input
203
+ - `NUMBER` - Numeric input with optional min/max
204
+ - `SELECT` - Dropdown selection
205
+ - `CHECKBOX` - Boolean toggle
206
+ - `TEXTAREA` - Multi-line text input
207
+ - `PASSWORD` - Password input (hidden)
208
+ - `EMAIL` - Email input
209
+ - `DATE` - Date picker
210
+ - `IMAGE` - Display-only image (e.g., QR codes)
211
+
212
+