mycode-sdk 0.7.1__tar.gz → 0.7.3__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.1 → mycode_sdk-0.7.3}/PKG-INFO +1 -1
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/pyproject.toml +1 -1
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/agent.py +21 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/messages.py +2 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/base.py +1 -1
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/openai_chat.py +32 -11
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/.gitignore +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/LICENSE +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/README.md +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/__init__.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/hooks.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/models.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/models_catalog.json +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/__init__.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/anthropic_like.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/gemini.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/providers/openai_responses.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/py.typed +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/session.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/tools.py +0 -0
- {mycode_sdk-0.7.1 → mycode_sdk-0.7.3}/src/mycode/utils.py +0 -0
|
@@ -11,6 +11,7 @@ import asyncio
|
|
|
11
11
|
import logging
|
|
12
12
|
import os
|
|
13
13
|
import tempfile
|
|
14
|
+
import time
|
|
14
15
|
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
|
|
15
16
|
from dataclasses import dataclass, field
|
|
16
17
|
from pathlib import Path
|
|
@@ -493,6 +494,8 @@ class Agent:
|
|
|
493
494
|
return
|
|
494
495
|
|
|
495
496
|
assistant_message: ConversationMessage | None = None
|
|
497
|
+
thinking_started_at: float | None = None
|
|
498
|
+
thinking_duration_ms: int | None = None
|
|
496
499
|
request = ProviderRequest(
|
|
497
500
|
provider=self.provider,
|
|
498
501
|
model=self.model,
|
|
@@ -518,12 +521,17 @@ class Agent:
|
|
|
518
521
|
if provider_event.type == "thinking_delta":
|
|
519
522
|
delta_text = str(provider_event.data.get("text") or "")
|
|
520
523
|
if delta_text:
|
|
524
|
+
if thinking_started_at is None:
|
|
525
|
+
thinking_started_at = time.monotonic()
|
|
521
526
|
yield Event("reasoning", {"delta": delta_text})
|
|
522
527
|
continue
|
|
523
528
|
|
|
524
529
|
if provider_event.type == "text_delta":
|
|
525
530
|
delta_text = str(provider_event.data.get("text") or "")
|
|
526
531
|
if delta_text:
|
|
532
|
+
if thinking_started_at is not None and thinking_duration_ms is None:
|
|
533
|
+
thinking_duration_ms = max(0, int((time.monotonic() - thinking_started_at) * 1000))
|
|
534
|
+
yield Event("reasoning_done", {"duration_ms": thinking_duration_ms})
|
|
527
535
|
yield Event("text", {"delta": delta_text})
|
|
528
536
|
continue
|
|
529
537
|
|
|
@@ -533,6 +541,10 @@ class Agent:
|
|
|
533
541
|
if provider_event.type != "message_done":
|
|
534
542
|
continue
|
|
535
543
|
|
|
544
|
+
if thinking_started_at is not None and thinking_duration_ms is None:
|
|
545
|
+
thinking_duration_ms = max(0, int((time.monotonic() - thinking_started_at) * 1000))
|
|
546
|
+
yield Event("reasoning_done", {"duration_ms": thinking_duration_ms})
|
|
547
|
+
|
|
536
548
|
message = provider_event.data.get("message")
|
|
537
549
|
if isinstance(message, dict):
|
|
538
550
|
assistant_message = message
|
|
@@ -549,6 +561,15 @@ class Agent:
|
|
|
549
561
|
yield Event("error", {"message": "provider produced no assistant message"})
|
|
550
562
|
return
|
|
551
563
|
|
|
564
|
+
if thinking_duration_ms is not None:
|
|
565
|
+
for block in reversed(assistant_message.get("content") or []):
|
|
566
|
+
if not isinstance(block, dict) or block.get("type") != "thinking":
|
|
567
|
+
continue
|
|
568
|
+
raw_meta = block.get("meta")
|
|
569
|
+
meta = cast(dict[str, Any], raw_meta) if isinstance(raw_meta, dict) else {}
|
|
570
|
+
block["meta"] = {**meta, "duration_ms": thinking_duration_ms}
|
|
571
|
+
break
|
|
572
|
+
|
|
552
573
|
self.messages.append(assistant_message)
|
|
553
574
|
await persist(assistant_message)
|
|
554
575
|
|
|
@@ -15,6 +15,8 @@ Metadata contract:
|
|
|
15
15
|
`provider`, `model`, `provider_message_id`, `stop_reason`, `usage`
|
|
16
16
|
- provider-specific assistant message extras live under `meta.native`
|
|
17
17
|
- provider-specific block replay hints live under `block.meta.native`
|
|
18
|
+
- local display metadata, such as `block.meta.duration_ms`, is never sent
|
|
19
|
+
upstream; provider adapters must explicitly project only supported fields
|
|
18
20
|
"""
|
|
19
21
|
|
|
20
22
|
from __future__ import annotations
|
|
@@ -205,7 +205,7 @@ def repair_messages_for_replay(
|
|
|
205
205
|
block_type = raw_block.get("type")
|
|
206
206
|
if block_type in {"text", "thinking"}:
|
|
207
207
|
text = str(raw_block.get("text") or "")
|
|
208
|
-
if text:
|
|
208
|
+
if text or get_native_meta(raw_block):
|
|
209
209
|
content.append(dict(raw_block))
|
|
210
210
|
continue
|
|
211
211
|
|
|
@@ -79,9 +79,10 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
79
79
|
|
|
80
80
|
delta = choice.delta
|
|
81
81
|
reasoning_delta, reasoning_meta_update = self._extract_reasoning_delta(delta)
|
|
82
|
+
if reasoning_meta_update:
|
|
83
|
+
thinking_native_meta.update(reasoning_meta_update)
|
|
82
84
|
if reasoning_delta:
|
|
83
85
|
thinking_parts.append(reasoning_delta)
|
|
84
|
-
thinking_native_meta.update(reasoning_meta_update)
|
|
85
86
|
yield ProviderStreamEvent("thinking_delta", {"text": reasoning_delta})
|
|
86
87
|
|
|
87
88
|
if delta.content:
|
|
@@ -104,7 +105,7 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
104
105
|
raise ValueError(str(exc)) from exc
|
|
105
106
|
|
|
106
107
|
blocks = []
|
|
107
|
-
if thinking_parts:
|
|
108
|
+
if thinking_parts or thinking_native_meta:
|
|
108
109
|
blocks.append(
|
|
109
110
|
thinking_block(
|
|
110
111
|
"".join(thinking_parts),
|
|
@@ -286,32 +287,52 @@ class OpenAIChatAdapter(ProviderAdapter):
|
|
|
286
287
|
reasoning_field = str(native_meta.get("reasoning_field") or "")
|
|
287
288
|
if reasoning_field == "reasoning_details":
|
|
288
289
|
return {"reasoning_details": native_meta.get("reasoning_details") or []}
|
|
290
|
+
if reasoning_field == "reasoning":
|
|
291
|
+
return {"reasoning": thinking_text or None}
|
|
292
|
+
if reasoning_field == "reasoning_content":
|
|
293
|
+
return {"reasoning_content": thinking_text or None}
|
|
289
294
|
return {"reasoning_content": thinking_text} if thinking_text else {}
|
|
290
295
|
|
|
291
296
|
def _extract_reasoning_delta(self, delta: Any) -> tuple[str, dict[str, Any]]:
|
|
292
297
|
# Third-party providers surface reasoning through non-standard extras.
|
|
293
298
|
# We check both the delta root and model_extra to cover both patterns.
|
|
294
|
-
# Known fields: reasoning_content
|
|
299
|
+
# Known fields: reasoning, reasoning_content, reasoning_details.
|
|
295
300
|
for source in (delta, getattr(delta, "model_extra", None) or {}):
|
|
296
301
|
if isinstance(source, dict):
|
|
302
|
+
has_reasoning = "reasoning" in source
|
|
303
|
+
reasoning = source.get("reasoning")
|
|
304
|
+
has_reasoning_content = "reasoning_content" in source
|
|
297
305
|
reasoning_content = source.get("reasoning_content")
|
|
306
|
+
has_reasoning_details = "reasoning_details" in source
|
|
298
307
|
reasoning_details = source.get("reasoning_details")
|
|
299
308
|
else:
|
|
309
|
+
has_reasoning = hasattr(source, "reasoning")
|
|
310
|
+
reasoning = getattr(source, "reasoning", None)
|
|
311
|
+
has_reasoning_content = hasattr(source, "reasoning_content")
|
|
300
312
|
reasoning_content = getattr(source, "reasoning_content", None)
|
|
313
|
+
has_reasoning_details = hasattr(source, "reasoning_details")
|
|
301
314
|
reasoning_details = getattr(source, "reasoning_details", None)
|
|
302
315
|
|
|
303
|
-
if
|
|
304
|
-
return
|
|
316
|
+
if has_reasoning:
|
|
317
|
+
return (
|
|
318
|
+
reasoning if isinstance(reasoning, str) else "",
|
|
319
|
+
{"reasoning_field": "reasoning"},
|
|
320
|
+
)
|
|
305
321
|
|
|
306
|
-
if
|
|
322
|
+
if has_reasoning_content:
|
|
323
|
+
return (
|
|
324
|
+
reasoning_content if isinstance(reasoning_content, str) else "",
|
|
325
|
+
{"reasoning_field": "reasoning_content"},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if has_reasoning_details and isinstance(reasoning_details, list):
|
|
307
329
|
reasoning_text = "".join(
|
|
308
330
|
str(item.get("text") or "") for item in reasoning_details if isinstance(item, dict)
|
|
309
331
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
332
|
+
return reasoning_text, {
|
|
333
|
+
"reasoning_field": "reasoning_details",
|
|
334
|
+
"reasoning_details": reasoning_details,
|
|
335
|
+
}
|
|
315
336
|
|
|
316
337
|
return "", {}
|
|
317
338
|
|
|
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
|