mycode-sdk 0.9.3__tar.gz → 0.9.5__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.9.3 → mycode_sdk-0.9.5}/PKG-INFO +6 -6
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/pyproject.toml +7 -7
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/agent.py +29 -41
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/messages.py +13 -22
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/models.py +14 -4
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/models_catalog.json +243 -215
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/anthropic_like.py +6 -15
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/base.py +20 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/openai_chat.py +15 -27
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/openai_responses.py +5 -11
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/session.py +10 -23
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/tools.py +10 -24
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/utils.py +0 -16
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/.gitignore +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/LICENSE +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/README.md +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/__init__.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/attachments.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/compact.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/__init__.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/providers/gemini.py +0 -0
- {mycode_sdk-0.9.3 → mycode_sdk-0.9.5}/src/mycode/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-sdk
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
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
|
|
@@ -18,11 +18,11 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Software Development
|
|
20
20
|
Requires-Python: >=3.12
|
|
21
|
-
Requires-Dist: anthropic>=0.
|
|
22
|
-
Requires-Dist: google-genai>=2.
|
|
23
|
-
Requires-Dist: griffelib>=2.
|
|
24
|
-
Requires-Dist: openai>=2.
|
|
25
|
-
Requires-Dist: pydantic>=2.13.
|
|
21
|
+
Requires-Dist: anthropic>=0.116.0
|
|
22
|
+
Requires-Dist: google-genai>=2.11.0
|
|
23
|
+
Requires-Dist: griffelib>=2.1.0
|
|
24
|
+
Requires-Dist: openai>=2.45.0
|
|
25
|
+
Requires-Dist: pydantic>=2.13.4
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
28
|
# mycode-sdk
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling>=1.31.0"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mycode-sdk"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.5"
|
|
8
8
|
description = "Lightweight Python SDK for building AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -23,11 +23,11 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
keywords = ["agent", "llm", "anthropic", "openai", "gemini", "sdk"]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"anthropic>=0.
|
|
27
|
-
"google-genai>=2.
|
|
28
|
-
"griffelib>=2.
|
|
29
|
-
"openai>=2.
|
|
30
|
-
"pydantic>=2.13.
|
|
26
|
+
"anthropic>=0.116.0",
|
|
27
|
+
"google-genai>=2.11.0",
|
|
28
|
+
"griffelib>=2.1.0",
|
|
29
|
+
"openai>=2.45.0",
|
|
30
|
+
"pydantic>=2.13.4",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
@@ -152,16 +152,10 @@ class Agent:
|
|
|
152
152
|
# (e.g. bash spill files) live next to the session JSONL; without a
|
|
153
153
|
# session, fall back to a tempdir scoped to ``session_id``.
|
|
154
154
|
if session_dir is not None:
|
|
155
|
-
tool_output_dir = session_dir / self.session_id / "tool-output"
|
|
155
|
+
self.tool_output_dir = session_dir / self.session_id / "tool-output"
|
|
156
156
|
else:
|
|
157
|
-
tool_output_dir = Path(tempfile.gettempdir()) / "mycode" / self.session_id / "tool-output"
|
|
157
|
+
self.tool_output_dir = Path(tempfile.gettempdir()) / "mycode" / self.session_id / "tool-output"
|
|
158
158
|
self.tools = ToolExecutor(tools)
|
|
159
|
-
self.tool_ctx = ToolContext(
|
|
160
|
-
executor=self.tools,
|
|
161
|
-
cwd=self.cwd,
|
|
162
|
-
tool_output_dir=tool_output_dir,
|
|
163
|
-
supports_image_input=False,
|
|
164
|
-
)
|
|
165
159
|
|
|
166
160
|
self.refresh_capabilities(
|
|
167
161
|
max_tokens=max_tokens,
|
|
@@ -201,7 +195,6 @@ class Agent:
|
|
|
201
195
|
self.supports_reasoning: bool | None = meta.supports_reasoning
|
|
202
196
|
self.supports_image_input: bool = bool(meta.supports_image_input)
|
|
203
197
|
self.supports_pdf_input: bool = bool(meta.supports_pdf_input)
|
|
204
|
-
self.tool_ctx.supports_image_input = self.supports_image_input
|
|
205
198
|
|
|
206
199
|
def cancel(self) -> None:
|
|
207
200
|
"""Request cancellation of the in-flight turn."""
|
|
@@ -330,11 +323,11 @@ class Agent:
|
|
|
330
323
|
output = "\n".join([*output_parts, "error: cancelled"]) if output_parts else "error: cancelled"
|
|
331
324
|
yield self._error_done(tool_id, output)
|
|
332
325
|
return
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
result = await task
|
|
329
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
330
|
+
result = ToolExecutionResult(output=f"error: {exc}", is_error=True)
|
|
338
331
|
|
|
339
332
|
yield await self._finish_tool_call(tool_id, hook_ctx, result)
|
|
340
333
|
|
|
@@ -360,13 +353,11 @@ class Agent:
|
|
|
360
353
|
*,
|
|
361
354
|
emit: Callable[[str], None] | None = None,
|
|
362
355
|
) -> ToolContext:
|
|
363
|
-
"""Build a per-call ToolContext from the base context."""
|
|
364
|
-
|
|
365
356
|
return ToolContext(
|
|
366
357
|
executor=self.tools,
|
|
367
|
-
cwd=self.
|
|
368
|
-
tool_output_dir=self.
|
|
369
|
-
supports_image_input=self.
|
|
358
|
+
cwd=self.cwd,
|
|
359
|
+
tool_output_dir=self.tool_output_dir,
|
|
360
|
+
supports_image_input=self.supports_image_input,
|
|
370
361
|
tool_call_id=tool_id,
|
|
371
362
|
emit=emit,
|
|
372
363
|
)
|
|
@@ -402,15 +393,12 @@ class Agent:
|
|
|
402
393
|
|
|
403
394
|
provider_stream: AsyncIterator[ProviderStreamEvent] = adapter.stream_turn(request)
|
|
404
395
|
|
|
405
|
-
async def next_provider_event() -> ProviderStreamEvent:
|
|
406
|
-
return await anext(provider_stream)
|
|
407
|
-
|
|
408
396
|
try:
|
|
409
397
|
while True:
|
|
410
398
|
if self._cancel_event.is_set():
|
|
411
399
|
raise asyncio.CancelledError
|
|
412
400
|
|
|
413
|
-
self._provider_event_task = asyncio.
|
|
401
|
+
self._provider_event_task = asyncio.ensure_future(anext(provider_stream))
|
|
414
402
|
try:
|
|
415
403
|
yield await self._provider_event_task
|
|
416
404
|
except StopAsyncIteration:
|
|
@@ -529,7 +517,10 @@ class Agent:
|
|
|
529
517
|
adapter = get_provider_adapter(self.provider)
|
|
530
518
|
|
|
531
519
|
turn_number = 0
|
|
532
|
-
while
|
|
520
|
+
while True:
|
|
521
|
+
if self.max_turns is not None and turn_number >= self.max_turns:
|
|
522
|
+
yield Event("error", {"message": "max_turns reached"})
|
|
523
|
+
return
|
|
533
524
|
turn_number += 1
|
|
534
525
|
if self._cancel_event.is_set():
|
|
535
526
|
yield Event("error", {"message": "cancelled"})
|
|
@@ -632,13 +623,15 @@ class Agent:
|
|
|
632
623
|
|
|
633
624
|
total_tokens = meta.get("total_tokens")
|
|
634
625
|
if total_tokens:
|
|
635
|
-
|
|
636
|
-
"
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
626
|
+
yield Event(
|
|
627
|
+
"usage",
|
|
628
|
+
{
|
|
629
|
+
"total_tokens": total_tokens,
|
|
630
|
+
"model": meta.get("model") or self.model,
|
|
631
|
+
"provider": meta.get("provider") or self.provider,
|
|
632
|
+
"context_window": meta["context_window"],
|
|
633
|
+
},
|
|
634
|
+
)
|
|
642
635
|
|
|
643
636
|
tool_calls = [
|
|
644
637
|
block
|
|
@@ -665,11 +658,10 @@ class Agent:
|
|
|
665
658
|
)
|
|
666
659
|
)
|
|
667
660
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return
|
|
661
|
+
if self._cancel_event.is_set():
|
|
662
|
+
# Skip remaining tool calls; the results collected so
|
|
663
|
+
# far are still persisted below.
|
|
664
|
+
break
|
|
673
665
|
|
|
674
666
|
tool_result_message = build_message("user", tool_results)
|
|
675
667
|
self.messages.append(tool_result_message)
|
|
@@ -693,11 +685,7 @@ class Agent:
|
|
|
693
685
|
)
|
|
694
686
|
|
|
695
687
|
if not tool_calls:
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
else:
|
|
699
|
-
yield Event("error", {"message": "max_turns reached"})
|
|
700
|
-
return
|
|
688
|
+
return
|
|
701
689
|
|
|
702
690
|
def run(
|
|
703
691
|
self,
|
|
@@ -25,18 +25,20 @@ ContentBlock = dict[str, Any]
|
|
|
25
25
|
ConversationMessage = dict[str, Any]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def
|
|
29
|
-
|
|
28
|
+
def _set_meta(block: dict[str, Any], meta: dict[str, Any] | None) -> dict[str, Any]:
|
|
29
|
+
"""Attach a copy of ``meta`` under the shared ``meta`` key, if non-empty."""
|
|
30
|
+
|
|
30
31
|
if meta:
|
|
31
32
|
block["meta"] = dict(meta)
|
|
32
33
|
return block
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
def text_block(text: str, *, meta: dict[str, Any] | None = None) -> ContentBlock:
|
|
37
|
+
return _set_meta({"type": "text", "text": text}, meta)
|
|
38
|
+
|
|
39
|
+
|
|
35
40
|
def thinking_block(text: str, *, meta: dict[str, Any] | None = None) -> ContentBlock:
|
|
36
|
-
|
|
37
|
-
if meta:
|
|
38
|
-
block["meta"] = dict(meta)
|
|
39
|
-
return block
|
|
41
|
+
return _set_meta({"type": "thinking", "text": text}, meta)
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
def image_block(
|
|
@@ -49,9 +51,7 @@ def image_block(
|
|
|
49
51
|
block: ContentBlock = {"type": "image", "data": data, "mime_type": mime_type}
|
|
50
52
|
if name:
|
|
51
53
|
block["name"] = name
|
|
52
|
-
|
|
53
|
-
block["meta"] = dict(meta)
|
|
54
|
-
return block
|
|
54
|
+
return _set_meta(block, meta)
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def document_block(
|
|
@@ -64,9 +64,7 @@ def document_block(
|
|
|
64
64
|
block: ContentBlock = {"type": "document", "data": data, "mime_type": mime_type}
|
|
65
65
|
if name:
|
|
66
66
|
block["name"] = name
|
|
67
|
-
|
|
68
|
-
block["meta"] = dict(meta)
|
|
69
|
-
return block
|
|
67
|
+
return _set_meta(block, meta)
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
def tool_use_block(
|
|
@@ -82,9 +80,7 @@ def tool_use_block(
|
|
|
82
80
|
"name": name,
|
|
83
81
|
"input": dict(input or {}),
|
|
84
82
|
}
|
|
85
|
-
|
|
86
|
-
block["meta"] = dict(meta)
|
|
87
|
-
return block
|
|
83
|
+
return _set_meta(block, meta)
|
|
88
84
|
|
|
89
85
|
|
|
90
86
|
def tool_result_block(
|
|
@@ -114,9 +110,7 @@ def tool_result_block(
|
|
|
114
110
|
block["metadata"] = dict(metadata)
|
|
115
111
|
if content:
|
|
116
112
|
block["content"] = [dict(item) for item in content]
|
|
117
|
-
|
|
118
|
-
block["meta"] = dict(meta)
|
|
119
|
-
return block
|
|
113
|
+
return _set_meta(block, meta)
|
|
120
114
|
|
|
121
115
|
|
|
122
116
|
def user_text_message(text: str, *, meta: dict[str, Any] | None = None) -> ConversationMessage:
|
|
@@ -129,10 +123,7 @@ def build_message(
|
|
|
129
123
|
*,
|
|
130
124
|
meta: dict[str, Any] | None = None,
|
|
131
125
|
) -> ConversationMessage:
|
|
132
|
-
|
|
133
|
-
if meta:
|
|
134
|
-
message["meta"] = dict(meta)
|
|
135
|
-
return message
|
|
126
|
+
return _set_meta({"role": role, "content": blocks}, meta)
|
|
136
127
|
|
|
137
128
|
|
|
138
129
|
def assistant_message(
|
|
@@ -66,16 +66,26 @@ def resolve_model_metadata(
|
|
|
66
66
|
*,
|
|
67
67
|
provider: str,
|
|
68
68
|
model: str,
|
|
69
|
-
|
|
69
|
+
context_window: int | None = None,
|
|
70
|
+
max_output_tokens: int | None = None,
|
|
71
|
+
supports_reasoning: bool | None = None,
|
|
72
|
+
supports_image_input: bool | None = None,
|
|
73
|
+
supports_pdf_input: bool | None = None,
|
|
70
74
|
) -> ModelMetadata:
|
|
71
75
|
"""Return catalog metadata for ``(provider, model)`` with non-None overrides layered on top.
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
so callers can apply their own fallback defaults.
|
|
77
|
+
Missing overrides and an absent catalog entry both leave the corresponding
|
|
78
|
+
fields at ``None`` so callers can apply their own fallback defaults.
|
|
76
79
|
"""
|
|
77
80
|
|
|
78
81
|
base = lookup_model_metadata(provider_type=provider, model=model) or ModelMetadata(provider=provider, model=model)
|
|
82
|
+
overrides = {
|
|
83
|
+
"context_window": context_window,
|
|
84
|
+
"max_output_tokens": max_output_tokens,
|
|
85
|
+
"supports_reasoning": supports_reasoning,
|
|
86
|
+
"supports_image_input": supports_image_input,
|
|
87
|
+
"supports_pdf_input": supports_pdf_input,
|
|
88
|
+
}
|
|
79
89
|
return replace(base, **{k: v for k, v in overrides.items() if v is not None})
|
|
80
90
|
|
|
81
91
|
|