mycode-sdk 0.8.6__tar.gz → 0.8.7__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.
Files changed (22) hide show
  1. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/PKG-INFO +1 -1
  2. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/pyproject.toml +1 -1
  3. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/agent.py +45 -5
  4. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/.gitignore +0 -0
  5. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/LICENSE +0 -0
  6. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/README.md +0 -0
  7. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/__init__.py +0 -0
  8. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/compact.py +0 -0
  9. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/hooks.py +0 -0
  10. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/messages.py +0 -0
  11. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/models.py +0 -0
  12. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/models_catalog.json +0 -0
  13. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/__init__.py +0 -0
  14. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/anthropic_like.py +0 -0
  15. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/base.py +0 -0
  16. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/gemini.py +0 -0
  17. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/openai_chat.py +0 -0
  18. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/providers/openai_responses.py +0 -0
  19. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/py.typed +0 -0
  20. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/session.py +0 -0
  21. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/tools.py +0 -0
  22. {mycode_sdk-0.8.6 → mycode_sdk-0.8.7}/src/mycode/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycode-sdk
3
- Version: 0.8.6
3
+ Version: 0.8.7
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mycode-sdk"
7
- version = "0.8.6"
7
+ version = "0.8.7"
8
8
  description = "Lightweight Python SDK for building AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -30,6 +30,8 @@ from mycode.messages import (
30
30
  ConversationMessage,
31
31
  build_message,
32
32
  flatten_message_text,
33
+ text_block,
34
+ thinking_block,
33
35
  tool_result_block,
34
36
  user_text_message,
35
37
  )
@@ -303,6 +305,7 @@ class Agent:
303
305
 
304
306
  task = asyncio.create_task(run_in_thread())
305
307
  was_cancelled = False
308
+ output_parts: list[str] = []
306
309
 
307
310
  while True:
308
311
  if self._cancel_event.is_set() and not was_cancelled:
@@ -319,6 +322,7 @@ class Agent:
319
322
  if output is None:
320
323
  break
321
324
  if not was_cancelled:
325
+ output_parts.append(output)
322
326
  yield Event("tool_output", {"tool_use_id": tool_id, "output": output})
323
327
 
324
328
  if was_cancelled:
@@ -326,7 +330,8 @@ class Agent:
326
330
  await task
327
331
  except Exception:
328
332
  pass
329
- result = ToolExecutionResult(output="error: cancelled", is_error=True)
333
+ output = "\n".join([*output_parts, "error: cancelled"]) if output_parts else "error: cancelled"
334
+ result = ToolExecutionResult(output=output, is_error=True)
330
335
  yield self._tool_done_event(tool_id, result)
331
336
  return
332
337
  else:
@@ -492,8 +497,10 @@ class Agent:
492
497
  return
493
498
 
494
499
  assistant_message: ConversationMessage | None = None
500
+ partial_content: list[dict[str, Any]] = []
495
501
  thinking_started_at: float | None = None
496
502
  thinking_duration_ms: int | None = None
503
+ provider_cancelled = False
497
504
  request = ProviderRequest(
498
505
  provider=self.provider,
499
506
  model=self.model,
@@ -513,14 +520,18 @@ class Agent:
513
520
  # Phase 1: ask the provider for exactly one assistant turn.
514
521
  async for provider_event in self._stream_provider_turn(adapter, request):
515
522
  if self._cancel_event.is_set():
516
- yield Event("error", {"message": "cancelled"})
517
- return
523
+ provider_cancelled = True
524
+ break
518
525
 
519
526
  if provider_event.type == "thinking_delta":
520
527
  delta_text = str(provider_event.data.get("text") or "")
521
528
  if delta_text:
522
529
  if thinking_started_at is None:
523
530
  thinking_started_at = time.monotonic()
531
+ if partial_content and partial_content[-1].get("type") == "thinking":
532
+ partial_content[-1]["text"] = f"{partial_content[-1].get('text') or ''}{delta_text}"
533
+ else:
534
+ partial_content.append(thinking_block(delta_text))
524
535
  yield Event("reasoning", {"delta": delta_text})
525
536
  continue
526
537
 
@@ -530,6 +541,10 @@ class Agent:
530
541
  if thinking_started_at is not None and thinking_duration_ms is None:
531
542
  thinking_duration_ms = max(0, int((time.monotonic() - thinking_started_at) * 1000))
532
543
  yield Event("reasoning_done", {"duration_ms": thinking_duration_ms})
544
+ if partial_content and partial_content[-1].get("type") == "text":
545
+ partial_content[-1]["text"] = f"{partial_content[-1].get('text') or ''}{delta_text}"
546
+ else:
547
+ partial_content.append(text_block(delta_text))
533
548
  yield Event("text", {"delta": delta_text})
534
549
  continue
535
550
 
@@ -548,13 +563,38 @@ class Agent:
548
563
  assistant_message = message
549
564
 
550
565
  except asyncio.CancelledError:
551
- yield Event("error", {"message": "cancelled"})
552
- return
566
+ provider_cancelled = True
553
567
  except Exception as exc:
554
568
  logger.exception("Provider request failed")
555
569
  yield Event("error", {"message": str(exc)})
556
570
  return
557
571
 
572
+ if provider_cancelled:
573
+ if partial_content:
574
+ if thinking_started_at is not None and thinking_duration_ms is None:
575
+ thinking_duration_ms = max(0, int((time.monotonic() - thinking_started_at) * 1000))
576
+ if thinking_duration_ms is not None:
577
+ for block in reversed(partial_content):
578
+ if block.get("type") != "thinking":
579
+ continue
580
+ raw_meta = block.get("meta")
581
+ meta = cast(dict[str, Any], raw_meta) if isinstance(raw_meta, dict) else {}
582
+ block["meta"] = {**meta, "duration_ms": thinking_duration_ms}
583
+ break
584
+ cancelled_message = build_message(
585
+ "assistant",
586
+ [dict(block) for block in partial_content],
587
+ meta={
588
+ "provider": self.provider,
589
+ "model": self.model,
590
+ "context_window": self.context_window,
591
+ },
592
+ )
593
+ self.messages.append(cancelled_message)
594
+ await persist(cancelled_message)
595
+ yield Event("error", {"message": "cancelled"})
596
+ return
597
+
558
598
  if not assistant_message:
559
599
  yield Event("error", {"message": "provider produced no assistant message"})
560
600
  return
File without changes
File without changes
File without changes