mycode-sdk 0.10.0__tar.gz → 0.11.1__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.
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/PKG-INFO +5 -3
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/README.md +4 -2
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/pyproject.toml +1 -1
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/agent.py +57 -8
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/models_catalog.json +187 -129
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/__init__.py +9 -1
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/anthropic_like.py +8 -2
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/openai_chat.py +25 -2
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/tools.py +85 -11
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/.gitignore +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/LICENSE +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/__init__.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/attachments.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/compact.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/messages.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/models.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/base.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/gemini.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/providers/openai_responses.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/py.typed +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/session.py +0 -0
- {mycode_sdk-0.10.0 → mycode_sdk-0.11.1}/src/mycode/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Lightweight Python SDK for building AI agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/legibet/mycode
|
|
6
6
|
Project-URL: Repository, https://github.com/legibet/mycode
|
|
@@ -71,7 +71,7 @@ print(result.text)
|
|
|
71
71
|
|
|
72
72
|
## Multi-turn conversations
|
|
73
73
|
|
|
74
|
-
Call `achat()` or `run()` again on the same `Agent`
|
|
74
|
+
Call `achat()` or `run()` again on the same `Agent` to continue the conversation:
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
77
|
agent = Agent(model="claude-sonnet-4-6", api_key="...")
|
|
@@ -117,7 +117,7 @@ agent = Agent(
|
|
|
117
117
|
)
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
Construct another `Agent` with the same `(session_dir, session_id)`
|
|
120
|
+
Construct another `Agent` with the same `(session_dir, session_id)` to load the conversation history.
|
|
121
121
|
|
|
122
122
|
## Built-in tools
|
|
123
123
|
|
|
@@ -167,6 +167,8 @@ def summarize_file(ctx: ToolContext, path: str) -> str:
|
|
|
167
167
|
return result.output.splitlines()[0] if result.output else ""
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
Async tools use `await ctx.aread()`, `await ctx.awrite()`, `await ctx.aedit()`, and `await ctx.abash()`. Use `await ctx.acall(name, args)` to call another registered tool by name.
|
|
171
|
+
|
|
170
172
|
## Tool hooks
|
|
171
173
|
|
|
172
174
|
Inspect or replace tool calls before they run. Return `None` from `before_tool` to let the tool execute, or a `ToolExecutionResult` to skip it:
|
|
@@ -44,7 +44,7 @@ print(result.text)
|
|
|
44
44
|
|
|
45
45
|
## Multi-turn conversations
|
|
46
46
|
|
|
47
|
-
Call `achat()` or `run()` again on the same `Agent`
|
|
47
|
+
Call `achat()` or `run()` again on the same `Agent` to continue the conversation:
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
50
|
agent = Agent(model="claude-sonnet-4-6", api_key="...")
|
|
@@ -90,7 +90,7 @@ agent = Agent(
|
|
|
90
90
|
)
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
Construct another `Agent` with the same `(session_dir, session_id)`
|
|
93
|
+
Construct another `Agent` with the same `(session_dir, session_id)` to load the conversation history.
|
|
94
94
|
|
|
95
95
|
## Built-in tools
|
|
96
96
|
|
|
@@ -140,6 +140,8 @@ def summarize_file(ctx: ToolContext, path: str) -> str:
|
|
|
140
140
|
return result.output.splitlines()[0] if result.output else ""
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
Async tools use `await ctx.aread()`, `await ctx.awrite()`, `await ctx.aedit()`, and `await ctx.abash()`. Use `await ctx.acall(name, args)` to call another registered tool by name.
|
|
144
|
+
|
|
143
145
|
## Tool hooks
|
|
144
146
|
|
|
145
147
|
Inspect or replace tool calls before they run. Return `None` from `before_tool` to let the tool execute, or a `ToolExecutionResult` to skip it:
|
|
@@ -87,6 +87,7 @@ class Agent:
|
|
|
87
87
|
compact_threshold: float | None = None,
|
|
88
88
|
reasoning_effort: str | None = None,
|
|
89
89
|
supports_reasoning: bool | None = None,
|
|
90
|
+
supports_reasoning_effort: bool = False,
|
|
90
91
|
supports_image_input: bool | None = None,
|
|
91
92
|
supports_pdf_input: bool | None = None,
|
|
92
93
|
system: str = "",
|
|
@@ -127,11 +128,16 @@ class Agent:
|
|
|
127
128
|
self.temperature = float(temperature)
|
|
128
129
|
self.compact_threshold = compact_threshold if compact_threshold is not None else DEFAULT_COMPACT_THRESHOLD
|
|
129
130
|
self.reasoning_effort = reasoning_effort
|
|
131
|
+
# Whether the endpoint accepts the effort knob. Comes from provider
|
|
132
|
+
# config, not the catalog, so it stays out of refresh_capabilities.
|
|
133
|
+
self.supports_reasoning_effort = supports_reasoning_effort
|
|
130
134
|
|
|
131
135
|
self.system = system
|
|
132
136
|
self.hooks = hooks or Hooks()
|
|
133
137
|
self._cancel_event = asyncio.Event()
|
|
138
|
+
self._event_loop: asyncio.AbstractEventLoop | None = None
|
|
134
139
|
self._provider_event_task: asyncio.Future[ProviderStreamEvent] | None = None
|
|
140
|
+
self._active_tool_task: asyncio.Task[ToolExecutionResult] | None = None
|
|
135
141
|
|
|
136
142
|
# History resolution:
|
|
137
143
|
# - messages is None → auto-resume from disk if the session exists
|
|
@@ -201,10 +207,24 @@ class Agent:
|
|
|
201
207
|
def cancel(self) -> None:
|
|
202
208
|
"""Request cancellation of the in-flight turn."""
|
|
203
209
|
|
|
210
|
+
try:
|
|
211
|
+
running_loop = asyncio.get_running_loop()
|
|
212
|
+
except RuntimeError:
|
|
213
|
+
running_loop = None
|
|
214
|
+
|
|
215
|
+
loop = self._event_loop
|
|
216
|
+
if loop is not None and loop.is_running() and running_loop is not loop:
|
|
217
|
+
loop.call_soon_threadsafe(self._cancel_in_loop)
|
|
218
|
+
return
|
|
219
|
+
self._cancel_in_loop()
|
|
220
|
+
|
|
221
|
+
def _cancel_in_loop(self) -> None:
|
|
204
222
|
self._cancel_event.set()
|
|
205
223
|
self.tools.cancel_active()
|
|
206
224
|
if self._provider_event_task and not self._provider_event_task.done():
|
|
207
225
|
self._provider_event_task.cancel()
|
|
226
|
+
if self._active_tool_task and not self._active_tool_task.done():
|
|
227
|
+
self._active_tool_task.cancel()
|
|
208
228
|
|
|
209
229
|
def clear(self) -> None:
|
|
210
230
|
"""Drop the in-memory conversation history."""
|
|
@@ -215,6 +235,23 @@ class Agent:
|
|
|
215
235
|
# Tool execution
|
|
216
236
|
# ------------------------------------------------------------------
|
|
217
237
|
|
|
238
|
+
async def _execute_tool(
|
|
239
|
+
self,
|
|
240
|
+
spec: ToolSpec,
|
|
241
|
+
args: dict[str, Any],
|
|
242
|
+
ctx: ToolContext,
|
|
243
|
+
) -> ToolExecutionResult:
|
|
244
|
+
if not spec.is_async:
|
|
245
|
+
return await self.tools.aexecute(spec.name, args, ctx)
|
|
246
|
+
|
|
247
|
+
task = asyncio.create_task(self.tools.aexecute(spec.name, args, ctx))
|
|
248
|
+
self._active_tool_task = task
|
|
249
|
+
try:
|
|
250
|
+
return await task
|
|
251
|
+
finally:
|
|
252
|
+
if self._active_tool_task is task:
|
|
253
|
+
self._active_tool_task = None
|
|
254
|
+
|
|
218
255
|
async def _run_tool_call(self, tool_use: dict[str, Any]) -> AsyncIterator[Event]:
|
|
219
256
|
"""Run one tool call and emit the standard tool events."""
|
|
220
257
|
|
|
@@ -261,13 +298,23 @@ class Agent:
|
|
|
261
298
|
return
|
|
262
299
|
|
|
263
300
|
if spec.streams_output:
|
|
264
|
-
async for event in self._run_streaming_tool(
|
|
301
|
+
async for event in self._run_streaming_tool(
|
|
302
|
+
tool_id=tool_id,
|
|
303
|
+
spec=spec,
|
|
304
|
+
args=args,
|
|
305
|
+
hook_ctx=hook_ctx,
|
|
306
|
+
):
|
|
265
307
|
yield event
|
|
266
308
|
return
|
|
267
309
|
|
|
268
310
|
try:
|
|
269
311
|
ctx = self._ctx_for_call(tool_id)
|
|
270
|
-
result = await
|
|
312
|
+
result = await self._execute_tool(spec, args, ctx)
|
|
313
|
+
except asyncio.CancelledError:
|
|
314
|
+
if self._cancel_event.is_set():
|
|
315
|
+
yield self._error_done(tool_id, "error: cancelled")
|
|
316
|
+
return
|
|
317
|
+
raise
|
|
271
318
|
except Exception as exc: # pragma: no cover - defensive
|
|
272
319
|
result = ToolExecutionResult(output=f"error: {exc}", is_error=True)
|
|
273
320
|
|
|
@@ -277,7 +324,7 @@ class Agent:
|
|
|
277
324
|
self,
|
|
278
325
|
*,
|
|
279
326
|
tool_id: str,
|
|
280
|
-
|
|
327
|
+
spec: ToolSpec,
|
|
281
328
|
args: dict[str, Any],
|
|
282
329
|
hook_ctx: ToolHookContext,
|
|
283
330
|
) -> AsyncIterator[Event]:
|
|
@@ -291,13 +338,13 @@ class Agent:
|
|
|
291
338
|
|
|
292
339
|
ctx = self._ctx_for_call(tool_id, emit=on_output)
|
|
293
340
|
|
|
294
|
-
async def
|
|
341
|
+
async def run_tool() -> ToolExecutionResult:
|
|
295
342
|
try:
|
|
296
|
-
return await
|
|
343
|
+
return await self._execute_tool(spec, args, ctx)
|
|
297
344
|
finally:
|
|
298
345
|
loop.call_soon_threadsafe(output_queue.put_nowait, None)
|
|
299
346
|
|
|
300
|
-
task = asyncio.create_task(
|
|
347
|
+
task = asyncio.create_task(run_tool())
|
|
301
348
|
was_cancelled = False
|
|
302
349
|
output_parts: list[str] = []
|
|
303
350
|
|
|
@@ -319,8 +366,8 @@ class Agent:
|
|
|
319
366
|
output_parts.append(output)
|
|
320
367
|
yield Event("tool_output", {"tool_use_id": tool_id, "output": output})
|
|
321
368
|
|
|
322
|
-
if was_cancelled:
|
|
323
|
-
with suppress(Exception):
|
|
369
|
+
if was_cancelled or (self._cancel_event.is_set() and task.cancelled()):
|
|
370
|
+
with suppress(asyncio.CancelledError, Exception):
|
|
324
371
|
await task
|
|
325
372
|
output = "\n".join([*output_parts, "error: cancelled"]) if output_parts else "error: cancelled"
|
|
326
373
|
yield self._error_done(tool_id, output)
|
|
@@ -490,6 +537,7 @@ class Agent:
|
|
|
490
537
|
async def persist(message: ConversationMessage) -> None:
|
|
491
538
|
await self._persist_message(message, on_persist)
|
|
492
539
|
|
|
540
|
+
self._event_loop = asyncio.get_running_loop()
|
|
493
541
|
self._cancel_event.clear()
|
|
494
542
|
|
|
495
543
|
user_message: ConversationMessage
|
|
@@ -742,6 +790,7 @@ class Agent:
|
|
|
742
790
|
:meth:`cancel` stops the summary request.
|
|
743
791
|
"""
|
|
744
792
|
|
|
793
|
+
self._event_loop = asyncio.get_running_loop()
|
|
745
794
|
self._cancel_event.clear()
|
|
746
795
|
adapter = get_provider_adapter(self.provider)
|
|
747
796
|
return await self._compact(adapter, on_persist)
|