mycode-sdk 0.7.4__tar.gz → 0.7.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.7.4 → mycode_sdk-0.7.5}/PKG-INFO +1 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/pyproject.toml +1 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/agent.py +61 -62
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/messages.py +5 -4
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/anthropic_like.py +12 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/gemini.py +4 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/openai_chat.py +4 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/openai_responses.py +4 -1
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/session.py +12 -12
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/.gitignore +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/LICENSE +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/README.md +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/__init__.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/models.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/models_catalog.json +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/__init__.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/providers/base.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/py.typed +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/tools.py +0 -0
- {mycode_sdk-0.7.4 → mycode_sdk-0.7.5}/src/mycode/utils.py +0 -0
|
@@ -183,7 +183,7 @@ class Agent:
|
|
|
183
183
|
supports_pdf_input=supports_pdf_input,
|
|
184
184
|
)
|
|
185
185
|
self.max_tokens: int = meta.max_output_tokens or 16_384
|
|
186
|
-
self.context_window: int
|
|
186
|
+
self.context_window: int = meta.context_window or 128_000
|
|
187
187
|
self.supports_reasoning: bool | None = meta.supports_reasoning
|
|
188
188
|
self.supports_image_input: bool = bool(meta.supports_image_input)
|
|
189
189
|
self.supports_pdf_input: bool = bool(meta.supports_pdf_input)
|
|
@@ -570,62 +570,83 @@ class Agent:
|
|
|
570
570
|
block["meta"] = {**meta, "duration_ms": thinking_duration_ms}
|
|
571
571
|
break
|
|
572
572
|
|
|
573
|
+
# Stamp context_window onto the persisted assistant message so
|
|
574
|
+
# rewinds and refreshed clients can render token-usage % without
|
|
575
|
+
# re-resolving model metadata.
|
|
576
|
+
meta = cast(dict[str, Any], assistant_message.setdefault("meta", {}))
|
|
577
|
+
meta["context_window"] = self.context_window
|
|
578
|
+
|
|
573
579
|
self.messages.append(assistant_message)
|
|
574
580
|
await persist(assistant_message)
|
|
575
581
|
|
|
576
|
-
|
|
577
|
-
|
|
582
|
+
total_tokens = meta.get("total_tokens")
|
|
583
|
+
if total_tokens:
|
|
584
|
+
payload: dict[str, Any] = {
|
|
585
|
+
"total_tokens": total_tokens,
|
|
586
|
+
"model": meta.get("model") or self.model,
|
|
587
|
+
"provider": meta.get("provider") or self.provider,
|
|
588
|
+
"context_window": meta["context_window"],
|
|
589
|
+
}
|
|
590
|
+
yield Event("usage", payload)
|
|
591
|
+
|
|
578
592
|
tool_calls = [
|
|
579
593
|
block
|
|
580
594
|
for block in assistant_message.get("content") or []
|
|
581
595
|
if isinstance(block, dict) and block.get("type") == "tool_use"
|
|
582
596
|
]
|
|
583
|
-
if
|
|
584
|
-
|
|
597
|
+
if tool_calls:
|
|
598
|
+
tool_results: list[dict[str, Any]] = []
|
|
599
|
+
for tool_call in tool_calls:
|
|
600
|
+
async for event in self._run_tool_call(tool_call):
|
|
601
|
+
yield event
|
|
602
|
+
|
|
603
|
+
if event.type != "tool_done":
|
|
604
|
+
continue
|
|
605
|
+
|
|
606
|
+
d = event.data
|
|
607
|
+
output = str(d.get("output") or "")
|
|
608
|
+
metadata = d.get("metadata") if isinstance(d.get("metadata"), dict) else None
|
|
609
|
+
content = d.get("content")
|
|
610
|
+
tool_results.append(
|
|
611
|
+
tool_result_block(
|
|
612
|
+
tool_use_id=str(d.get("tool_use_id") or ""),
|
|
613
|
+
output=output,
|
|
614
|
+
metadata=metadata,
|
|
615
|
+
is_error=bool(d.get("is_error")),
|
|
616
|
+
content=content if isinstance(content, list) else None,
|
|
617
|
+
)
|
|
618
|
+
)
|
|
585
619
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
620
|
+
if self._cancel_event.is_set():
|
|
621
|
+
tool_result_message = build_message("user", tool_results)
|
|
622
|
+
self.messages.append(tool_result_message)
|
|
623
|
+
await persist(tool_result_message)
|
|
624
|
+
return
|
|
590
625
|
|
|
591
|
-
|
|
592
|
-
|
|
626
|
+
tool_result_message = build_message("user", tool_results)
|
|
627
|
+
self.messages.append(tool_result_message)
|
|
628
|
+
await persist(tool_result_message)
|
|
593
629
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
content=content if isinstance(content, list) else None,
|
|
605
|
-
)
|
|
630
|
+
if self._cancel_event.is_set():
|
|
631
|
+
return
|
|
632
|
+
if should_compact(total_tokens, self.context_window, self.compact_threshold):
|
|
633
|
+
try:
|
|
634
|
+
async for event in self._compact(adapter, persist):
|
|
635
|
+
yield event
|
|
636
|
+
except (Exception, asyncio.CancelledError):
|
|
637
|
+
logger.warning(
|
|
638
|
+
"Context compaction failed, continuing without compaction",
|
|
639
|
+
exc_info=True,
|
|
606
640
|
)
|
|
607
641
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
self.messages.append(tool_result_message)
|
|
611
|
-
await persist(tool_result_message)
|
|
612
|
-
return
|
|
613
|
-
|
|
614
|
-
tool_result_message = build_message("user", tool_results)
|
|
615
|
-
self.messages.append(tool_result_message)
|
|
616
|
-
await persist(tool_result_message)
|
|
642
|
+
if not tool_calls:
|
|
643
|
+
break
|
|
617
644
|
|
|
618
645
|
else:
|
|
619
646
|
# while loop exhausted max_turns without breaking
|
|
620
647
|
yield Event("error", {"message": "max_turns reached"})
|
|
621
648
|
return
|
|
622
649
|
|
|
623
|
-
# Turn completed normally (assistant stopped calling tools).
|
|
624
|
-
# Check whether context compaction is needed.
|
|
625
|
-
if not self._cancel_event.is_set():
|
|
626
|
-
async for event in self._compact_if_needed(adapter, persist):
|
|
627
|
-
yield event
|
|
628
|
-
|
|
629
650
|
def run(
|
|
630
651
|
self,
|
|
631
652
|
user_input: str | ConversationMessage,
|
|
@@ -657,28 +678,6 @@ class Agent:
|
|
|
657
678
|
# Context compaction
|
|
658
679
|
# ------------------------------------------------------------------
|
|
659
680
|
|
|
660
|
-
async def _compact_if_needed(
|
|
661
|
-
self,
|
|
662
|
-
adapter: ProviderAdapter,
|
|
663
|
-
persist: PersistCallback,
|
|
664
|
-
) -> AsyncIterator[Event]:
|
|
665
|
-
"""Check token usage and run compaction if above threshold."""
|
|
666
|
-
|
|
667
|
-
usage: dict[str, Any] | None = None
|
|
668
|
-
for message in reversed(self.messages):
|
|
669
|
-
if message.get("role") == "assistant":
|
|
670
|
-
usage = (message.get("meta") or {}).get("usage")
|
|
671
|
-
break
|
|
672
|
-
|
|
673
|
-
if not should_compact(usage, self.context_window, self.compact_threshold):
|
|
674
|
-
return
|
|
675
|
-
|
|
676
|
-
try:
|
|
677
|
-
async for event in self._compact(adapter, persist):
|
|
678
|
-
yield event
|
|
679
|
-
except (Exception, asyncio.CancelledError):
|
|
680
|
-
logger.warning("Context compaction failed, continuing without compaction", exc_info=True)
|
|
681
|
-
|
|
682
681
|
async def _compact(
|
|
683
682
|
self,
|
|
684
683
|
adapter: ProviderAdapter,
|
|
@@ -720,13 +719,13 @@ class Agent:
|
|
|
720
719
|
logger.warning("Compaction produced empty summary")
|
|
721
720
|
return
|
|
722
721
|
|
|
723
|
-
|
|
722
|
+
summary_total_tokens = (summary_message.get("meta") or {}).get("total_tokens")
|
|
724
723
|
compact_event = build_compact_event(
|
|
725
724
|
summary_text,
|
|
726
725
|
provider=self.provider,
|
|
727
726
|
model=self.model,
|
|
728
727
|
compacted_count=compacted_count,
|
|
729
|
-
|
|
728
|
+
total_tokens=summary_total_tokens,
|
|
730
729
|
)
|
|
731
730
|
|
|
732
731
|
# Persist the compact event (append-only — original messages stay in JSONL).
|
|
@@ -12,7 +12,8 @@ details.
|
|
|
12
12
|
Metadata contract:
|
|
13
13
|
|
|
14
14
|
- assistant message `meta` keeps normalized top-level fields only:
|
|
15
|
-
`provider`, `model`, `provider_message_id`, `stop_reason`, `
|
|
15
|
+
`provider`, `model`, `provider_message_id`, `stop_reason`, `total_tokens`,
|
|
16
|
+
`context_window` (see docs/sessions.md for `total_tokens` semantics)
|
|
16
17
|
- provider-specific assistant message extras live under `meta.native`
|
|
17
18
|
- provider-specific block replay hints live under `block.meta.native`
|
|
18
19
|
- local display metadata, such as `block.meta.duration_ms`, is never sent
|
|
@@ -146,7 +147,7 @@ def assistant_message(
|
|
|
146
147
|
model: str | None = None,
|
|
147
148
|
provider_message_id: str | None = None,
|
|
148
149
|
stop_reason: str | None = None,
|
|
149
|
-
|
|
150
|
+
total_tokens: int | None = None,
|
|
150
151
|
native_meta: dict[str, Any] | None = None,
|
|
151
152
|
) -> ConversationMessage:
|
|
152
153
|
"""Build a normalized assistant message with shared metadata fields."""
|
|
@@ -160,8 +161,8 @@ def assistant_message(
|
|
|
160
161
|
meta["provider_message_id"] = provider_message_id
|
|
161
162
|
if stop_reason:
|
|
162
163
|
meta["stop_reason"] = stop_reason
|
|
163
|
-
if
|
|
164
|
-
meta["
|
|
164
|
+
if total_tokens is not None:
|
|
165
|
+
meta["total_tokens"] = total_tokens
|
|
165
166
|
if native_meta:
|
|
166
167
|
native = omit_none(native_meta)
|
|
167
168
|
if native:
|
|
@@ -219,13 +219,24 @@ class AnthropicLikeAdapter(ProviderAdapter):
|
|
|
219
219
|
native_meta["stop_sequence"] = stop_sequence
|
|
220
220
|
if service_tier := getattr(message, "service_tier", None):
|
|
221
221
|
native_meta["service_tier"] = service_tier
|
|
222
|
+
|
|
223
|
+
# No `total_tokens` field — compute it from input + cache + output parts.
|
|
224
|
+
raw_usage = dump_model(getattr(message, "usage", None)) or {}
|
|
225
|
+
prompt_tokens = (
|
|
226
|
+
(raw_usage.get("input_tokens") or 0)
|
|
227
|
+
+ (raw_usage.get("cache_creation_input_tokens") or 0)
|
|
228
|
+
+ (raw_usage.get("cache_read_input_tokens") or 0)
|
|
229
|
+
)
|
|
230
|
+
output_tokens = raw_usage.get("output_tokens") or 0
|
|
231
|
+
total_tokens = prompt_tokens + output_tokens or None
|
|
232
|
+
|
|
222
233
|
return assistant_message(
|
|
223
234
|
blocks,
|
|
224
235
|
provider=self.provider_id,
|
|
225
236
|
model=getattr(message, "model", None),
|
|
226
237
|
provider_message_id=getattr(message, "id", None),
|
|
227
238
|
stop_reason=getattr(message, "stop_reason", None),
|
|
228
|
-
|
|
239
|
+
total_tokens=total_tokens,
|
|
229
240
|
native_meta=native_meta,
|
|
230
241
|
)
|
|
231
242
|
|
|
@@ -92,6 +92,9 @@ class GoogleGeminiAdapter(ProviderAdapter):
|
|
|
92
92
|
except Exception:
|
|
93
93
|
pass
|
|
94
94
|
|
|
95
|
+
raw_usage = usage or {}
|
|
96
|
+
total_tokens = raw_usage.get("total_token_count") or None
|
|
97
|
+
|
|
95
98
|
yield ProviderStreamEvent(
|
|
96
99
|
"message_done",
|
|
97
100
|
{
|
|
@@ -101,7 +104,7 @@ class GoogleGeminiAdapter(ProviderAdapter):
|
|
|
101
104
|
model=response_model or request.model,
|
|
102
105
|
provider_message_id=response_id,
|
|
103
106
|
stop_reason=str(finish_reason) if finish_reason else None,
|
|
104
|
-
|
|
107
|
+
total_tokens=total_tokens,
|
|
105
108
|
native_meta={"finish_message": str(finish_message)} if finish_message else None,
|
|
106
109
|
)
|
|
107
110
|
},
|
|
@@ -135,13 +135,16 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
135
135
|
)
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
+
raw_usage = dump_model(usage) or {}
|
|
139
|
+
total_tokens = raw_usage.get("total_tokens") or None
|
|
140
|
+
|
|
138
141
|
final_message = assistant_message(
|
|
139
142
|
blocks,
|
|
140
143
|
provider=self.provider_id,
|
|
141
144
|
model=response_model or request.model,
|
|
142
145
|
provider_message_id=response_id,
|
|
143
146
|
stop_reason=finish_reason,
|
|
144
|
-
|
|
147
|
+
total_tokens=total_tokens,
|
|
145
148
|
)
|
|
146
149
|
yield ProviderStreamEvent("message_done", {"message": final_message})
|
|
147
150
|
|
|
@@ -361,12 +361,15 @@ class OpenAIResponsesAdapter(ProviderAdapter):
|
|
|
361
361
|
)
|
|
362
362
|
)
|
|
363
363
|
|
|
364
|
+
raw_usage = dump_model(getattr(response, "usage", None)) or {}
|
|
365
|
+
total_tokens = raw_usage.get("total_tokens") or None
|
|
366
|
+
|
|
364
367
|
return assistant_message(
|
|
365
368
|
blocks,
|
|
366
369
|
provider=self.provider_id,
|
|
367
370
|
model=getattr(response, "model", None),
|
|
368
371
|
provider_message_id=getattr(response, "id", None),
|
|
369
372
|
stop_reason=getattr(response, "status", None),
|
|
370
|
-
|
|
373
|
+
total_tokens=total_tokens,
|
|
371
374
|
native_meta={"output_items": dumped_output_items} if dumped_output_items else None,
|
|
372
375
|
)
|
|
@@ -67,20 +67,20 @@ def _now() -> str:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def should_compact(
|
|
70
|
-
|
|
70
|
+
last_total_tokens: int | None,
|
|
71
71
|
context_window: int | None,
|
|
72
72
|
threshold: float,
|
|
73
73
|
) -> bool:
|
|
74
|
-
"""
|
|
74
|
+
"""True when the latest call's `total_tokens` ≥ `context_window × threshold`.
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
`total_tokens` already covers the next API call's prompt floor, so it is
|
|
77
|
+
the right input here. The `(1 - threshold)` headroom is reserved for the
|
|
78
|
+
compact LLM call itself (see docs/sessions.md).
|
|
79
|
+
"""
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
return input_tokens >= context_window * threshold
|
|
81
|
+
if not last_total_tokens or not context_window or threshold <= 0:
|
|
82
|
+
return False
|
|
83
|
+
return last_total_tokens >= context_window * threshold
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def build_compact_event(
|
|
@@ -89,7 +89,7 @@ def build_compact_event(
|
|
|
89
89
|
provider: str,
|
|
90
90
|
model: str,
|
|
91
91
|
compacted_count: int,
|
|
92
|
-
|
|
92
|
+
total_tokens: int | None = None,
|
|
93
93
|
) -> ConversationMessage:
|
|
94
94
|
"""Build the compact event stored in session JSONL."""
|
|
95
95
|
|
|
@@ -98,8 +98,8 @@ def build_compact_event(
|
|
|
98
98
|
"model": model,
|
|
99
99
|
"compacted_count": compacted_count,
|
|
100
100
|
}
|
|
101
|
-
if
|
|
102
|
-
meta["
|
|
101
|
+
if total_tokens is not None:
|
|
102
|
+
meta["total_tokens"] = total_tokens
|
|
103
103
|
return build_message("compact", [text_block(summary_text)], meta=meta)
|
|
104
104
|
|
|
105
105
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|