mycode-sdk 0.7.6__tar.gz → 0.8.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.
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/PKG-INFO +1 -1
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/pyproject.toml +1 -1
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/agent.py +51 -24
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/session.py +8 -62
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/.gitignore +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/LICENSE +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/README.md +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/__init__.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/messages.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/models.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/models_catalog.json +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/__init__.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/anthropic_like.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/base.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/gemini.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/openai_chat.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/providers/openai_responses.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/py.typed +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/tools.py +0 -0
- {mycode_sdk-0.7.6 → mycode_sdk-0.8.0}/src/mycode/utils.py +0 -0
|
@@ -23,6 +23,7 @@ from mycode.messages import (
|
|
|
23
23
|
ConversationMessage,
|
|
24
24
|
build_message,
|
|
25
25
|
flatten_message_text,
|
|
26
|
+
text_block,
|
|
26
27
|
tool_result_block,
|
|
27
28
|
user_text_message,
|
|
28
29
|
)
|
|
@@ -30,10 +31,13 @@ from mycode.models import infer_provider_from_model, resolve_model_metadata
|
|
|
30
31
|
from mycode.providers import get_provider_adapter
|
|
31
32
|
from mycode.providers.base import ProviderAdapter, ProviderRequest, ProviderStreamEvent
|
|
32
33
|
from mycode.session import (
|
|
34
|
+
COMPACT_ACK,
|
|
33
35
|
COMPACT_SUMMARY_PROMPT,
|
|
36
|
+
CONTINUATION_FOOTER,
|
|
37
|
+
CONTINUATION_HEADER,
|
|
34
38
|
DEFAULT_COMPACT_THRESHOLD,
|
|
39
|
+
TRANSCRIPT_HINT,
|
|
35
40
|
SessionStore,
|
|
36
|
-
apply_compact,
|
|
37
41
|
build_compact_event,
|
|
38
42
|
should_compact,
|
|
39
43
|
)
|
|
@@ -500,7 +504,7 @@ class Agent:
|
|
|
500
504
|
provider=self.provider,
|
|
501
505
|
model=self.model,
|
|
502
506
|
session_id=self.session_id,
|
|
503
|
-
messages=self.messages,
|
|
507
|
+
messages=self._project_for_provider(self.messages),
|
|
504
508
|
system=self.system,
|
|
505
509
|
tools=self.tools.definitions,
|
|
506
510
|
max_tokens=self.max_tokens,
|
|
@@ -631,11 +635,14 @@ class Agent:
|
|
|
631
635
|
return
|
|
632
636
|
if should_compact(total_tokens, self.context_window, self.compact_threshold):
|
|
633
637
|
try:
|
|
634
|
-
async for event in self._compact(adapter, persist
|
|
638
|
+
async for event in self._compact(adapter, persist):
|
|
635
639
|
yield event
|
|
636
640
|
except asyncio.CancelledError:
|
|
637
|
-
|
|
641
|
+
yield Event("error", {"message": "cancelled"})
|
|
642
|
+
return
|
|
638
643
|
except Exception:
|
|
644
|
+
# Best-effort: transient failures retry next threshold check;
|
|
645
|
+
# persistent ones surface from phase 1 of the next turn.
|
|
639
646
|
logger.warning(
|
|
640
647
|
"Context compaction failed, continuing without compaction",
|
|
641
648
|
exc_info=True,
|
|
@@ -680,19 +687,52 @@ class Agent:
|
|
|
680
687
|
# Context compaction
|
|
681
688
|
# ------------------------------------------------------------------
|
|
682
689
|
|
|
690
|
+
def _project_for_provider(
|
|
691
|
+
self,
|
|
692
|
+
messages: list[ConversationMessage],
|
|
693
|
+
) -> list[ConversationMessage]:
|
|
694
|
+
"""Replace pre-compact history with a summary continuation."""
|
|
695
|
+
|
|
696
|
+
last_compact = -1
|
|
697
|
+
for i, message in enumerate(messages):
|
|
698
|
+
if message.get("role") == "compact":
|
|
699
|
+
last_compact = i
|
|
700
|
+
|
|
701
|
+
if last_compact < 0:
|
|
702
|
+
return messages
|
|
703
|
+
|
|
704
|
+
summary_text = ""
|
|
705
|
+
for block in messages[last_compact].get("content") or []:
|
|
706
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
707
|
+
summary_text = str(block.get("text") or "")
|
|
708
|
+
break
|
|
709
|
+
|
|
710
|
+
tail = [m for m in messages[last_compact + 1 :] if m.get("role") != "compact"]
|
|
711
|
+
# No tail or assistant-led tail = mid-loop; append a resume instruction
|
|
712
|
+
# and skip the ack. A user-led tail needs the ack to keep alternation.
|
|
713
|
+
continue_now = not tail or tail[0].get("role") == "assistant"
|
|
714
|
+
|
|
715
|
+
parts = [CONTINUATION_HEADER, summary_text]
|
|
716
|
+
if self._store and self.session_id:
|
|
717
|
+
parts.append(TRANSCRIPT_HINT.format(path=self._store.messages_path(self.session_id)))
|
|
718
|
+
if continue_now:
|
|
719
|
+
parts.append(CONTINUATION_FOOTER)
|
|
720
|
+
|
|
721
|
+
projected = [build_message("user", [text_block("\n\n".join(parts))])]
|
|
722
|
+
if not continue_now:
|
|
723
|
+
projected.append(build_message("assistant", [text_block(COMPACT_ACK)]))
|
|
724
|
+
projected.extend(tail)
|
|
725
|
+
return projected
|
|
726
|
+
|
|
683
727
|
async def _compact(
|
|
684
728
|
self,
|
|
685
729
|
adapter: ProviderAdapter,
|
|
686
730
|
persist: PersistCallback,
|
|
687
|
-
*,
|
|
688
|
-
continue_now: bool,
|
|
689
731
|
) -> AsyncIterator[Event]:
|
|
690
|
-
"""Generate a conversation summary and
|
|
691
|
-
|
|
692
|
-
compacted_count = len(self.messages)
|
|
732
|
+
"""Generate a conversation summary and append a compact marker."""
|
|
693
733
|
|
|
694
734
|
# Ask the same provider for a summary — no tools, just text generation.
|
|
695
|
-
compact_messages =
|
|
735
|
+
compact_messages = self._project_for_provider(self.messages) + [user_text_message(COMPACT_SUMMARY_PROMPT)]
|
|
696
736
|
request = ProviderRequest(
|
|
697
737
|
provider=self.provider,
|
|
698
738
|
model=self.model,
|
|
@@ -726,24 +766,11 @@ class Agent:
|
|
|
726
766
|
summary_text,
|
|
727
767
|
provider=self.provider,
|
|
728
768
|
model=self.model,
|
|
729
|
-
compacted_count=compacted_count,
|
|
730
769
|
total_tokens=summary_total_tokens,
|
|
731
770
|
)
|
|
732
771
|
|
|
733
772
|
# Persist the compact event (append-only — original messages stay in JSONL).
|
|
734
773
|
await persist(compact_event)
|
|
735
|
-
|
|
736
774
|
self.messages.append(compact_event)
|
|
737
|
-
self.messages = apply_compact(
|
|
738
|
-
self.messages,
|
|
739
|
-
transcript_path=str(self._store.messages_path(self.session_id)) if self._store else None,
|
|
740
|
-
continue_now=continue_now,
|
|
741
|
-
)
|
|
742
775
|
|
|
743
|
-
yield Event(
|
|
744
|
-
"compact",
|
|
745
|
-
{
|
|
746
|
-
"message": f"Context compacted ({compacted_count} messages → summary)",
|
|
747
|
-
"compacted_count": compacted_count,
|
|
748
|
-
},
|
|
749
|
-
)
|
|
776
|
+
yield Event("compact", {})
|
|
@@ -24,7 +24,7 @@ from mycode.messages import ConversationMessage, build_message, flatten_message_
|
|
|
24
24
|
# Session format and compacting defaults
|
|
25
25
|
# ---------------------------------------------------------------------
|
|
26
26
|
|
|
27
|
-
MESSAGE_FORMAT_VERSION =
|
|
27
|
+
MESSAGE_FORMAT_VERSION = 7
|
|
28
28
|
DEFAULT_COMPACT_THRESHOLD = 0.8
|
|
29
29
|
DEFAULT_SESSION_TITLE = "New chat"
|
|
30
30
|
|
|
@@ -60,13 +60,13 @@ other identifiers verbatim — never paraphrase them.
|
|
|
60
60
|
- Keep it concise but complete.\
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
CONTINUATION_HEADER = "This session is being continued from a previous conversation that was compacted to fit the context window. The summary below covers the earlier portion of the conversation."
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
TRANSCRIPT_HINT = "For verbatim details not captured in this summary (exact code snippets, error messages, or earlier output), read the original conversation log at: {path}"
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
CONTINUATION_FOOTER = 'Resume directly from where the work left off. Do not acknowledge this summary, do not recap, and do not preface with "I\'ll continue" or similar.'
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
COMPACT_ACK = "Acknowledged."
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
# ---------------------------------------------------------------------
|
|
@@ -100,7 +100,6 @@ def build_compact_event(
|
|
|
100
100
|
*,
|
|
101
101
|
provider: str,
|
|
102
102
|
model: str,
|
|
103
|
-
compacted_count: int,
|
|
104
103
|
total_tokens: int | None = None,
|
|
105
104
|
) -> ConversationMessage:
|
|
106
105
|
"""Build the compact event stored in session JSONL."""
|
|
@@ -108,60 +107,12 @@ def build_compact_event(
|
|
|
108
107
|
meta: dict[str, Any] = {
|
|
109
108
|
"provider": provider,
|
|
110
109
|
"model": model,
|
|
111
|
-
"compacted_count": compacted_count,
|
|
112
110
|
}
|
|
113
111
|
if total_tokens is not None:
|
|
114
112
|
meta["total_tokens"] = total_tokens
|
|
115
113
|
return build_message("compact", [text_block(summary_text)], meta=meta)
|
|
116
114
|
|
|
117
115
|
|
|
118
|
-
def apply_compact(
|
|
119
|
-
messages: list[ConversationMessage],
|
|
120
|
-
*,
|
|
121
|
-
transcript_path: str | None = None,
|
|
122
|
-
continue_now: bool | None = None,
|
|
123
|
-
) -> list[ConversationMessage]:
|
|
124
|
-
"""Replace the latest compact event with a synthetic summary view.
|
|
125
|
-
|
|
126
|
-
``continue_now`` omits the ack and leaves a user instruction last so the
|
|
127
|
-
agent loop can immediately request the next assistant response.
|
|
128
|
-
"""
|
|
129
|
-
|
|
130
|
-
# Only the newest compact event matters. Older history before it is no
|
|
131
|
-
# longer visible once the summary replaces that earlier conversation.
|
|
132
|
-
last_compact_index: int | None = None
|
|
133
|
-
for index, message in enumerate(messages):
|
|
134
|
-
if message.get("role") == "compact":
|
|
135
|
-
last_compact_index = index
|
|
136
|
-
|
|
137
|
-
if last_compact_index is None:
|
|
138
|
-
return messages
|
|
139
|
-
|
|
140
|
-
summary_text = ""
|
|
141
|
-
for block in messages[last_compact_index].get("content") or []:
|
|
142
|
-
if isinstance(block, dict) and block.get("type") == "text":
|
|
143
|
-
summary_text = str(block.get("text") or "")
|
|
144
|
-
break
|
|
145
|
-
|
|
146
|
-
tail = messages[last_compact_index + 1 :]
|
|
147
|
-
if continue_now is None:
|
|
148
|
-
# During live tool-loop compaction the next persisted message is the
|
|
149
|
-
# assistant continuation. Waiting compaction has no tail yet.
|
|
150
|
-
continue_now = bool(tail and tail[0].get("role") == "assistant")
|
|
151
|
-
|
|
152
|
-
parts = [_CONTINUATION_HEADER, summary_text]
|
|
153
|
-
if transcript_path:
|
|
154
|
-
parts.append(_TRANSCRIPT_HINT.format(path=transcript_path))
|
|
155
|
-
if continue_now:
|
|
156
|
-
parts.append(_CONTINUATION_FOOTER)
|
|
157
|
-
|
|
158
|
-
result = [build_message("user", [text_block("\n\n".join(parts))], meta={"synthetic": True})]
|
|
159
|
-
if not continue_now:
|
|
160
|
-
result.append(build_message("assistant", [text_block(_COMPACT_ACK)], meta={"synthetic": True}))
|
|
161
|
-
result.extend(tail)
|
|
162
|
-
return result
|
|
163
|
-
|
|
164
|
-
|
|
165
116
|
def build_rewind_event(rewind_to: int) -> ConversationMessage:
|
|
166
117
|
"""Build a rewind marker to append to session JSONL."""
|
|
167
118
|
|
|
@@ -341,16 +292,11 @@ class SessionStore:
|
|
|
341
292
|
except FileNotFoundError:
|
|
342
293
|
pass
|
|
343
294
|
|
|
344
|
-
#
|
|
345
|
-
#
|
|
346
|
-
# 2) rewind truncates that visible list by message index
|
|
295
|
+
# Visible state = raw JSONL minus rewound tails. `compact` markers
|
|
296
|
+
# stay inline; the agent substitutes them when calling the provider.
|
|
347
297
|
# Orphan tool_use blocks (e.g. left open by a server crash) are
|
|
348
298
|
# closed by the provider adapter at replay time, not here.
|
|
349
|
-
visible_messages =
|
|
350
|
-
raw_messages,
|
|
351
|
-
transcript_path=str(self.messages_path(session_id)),
|
|
352
|
-
)
|
|
353
|
-
visible_messages = apply_rewind(visible_messages)
|
|
299
|
+
visible_messages = apply_rewind(raw_messages)
|
|
354
300
|
|
|
355
301
|
return {"session": self._summary(session_id, meta), "messages": visible_messages}
|
|
356
302
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|