mycode-sdk 0.5.4__tar.gz → 0.5.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycode-sdk
3
- Version: 0.5.4
3
+ Version: 0.5.6
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.5.4"
7
+ version = "0.5.6"
8
8
  description = "Lightweight Python SDK for building AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -7,6 +7,8 @@ runtime ships four built-in coding tools (``read``, ``write``, ``edit``,
7
7
  silently exposing file system and shell access.
8
8
  """
9
9
 
10
+ from importlib import metadata
11
+
10
12
  from mycode.agent import Agent, Event, PersistCallback, RunResult
11
13
  from mycode.messages import (
12
14
  ContentBlock,
@@ -33,7 +35,8 @@ from mycode.tools import (
33
35
  tool,
34
36
  )
35
37
 
36
- __version__ = "0.4.0"
38
+ # The package metadata in mycode/pyproject.toml is the single version source.
39
+ __version__ = metadata.version("mycode-sdk")
37
40
 
38
41
  # Built-in tool specs exposed as module-level constants so callers can pick
39
42
  # which ones to register (``tools=[read_tool, bash_tool]``) rather than
@@ -103,7 +103,7 @@ def tool_result_block(
103
103
  `output` is replayed back to providers on later turns.
104
104
  `content` carries multimodal blocks (e.g. images) that providers should
105
105
  replay alongside the text. `metadata` is an optional structured payload
106
- for UI consumption (e.g. edit diff line numbers).
106
+ for UI consumption (e.g. edit patch and line stats).
107
107
  """
108
108
 
109
109
  block: ContentBlock = {
@@ -32,7 +32,7 @@ from base64 import b64encode
32
32
  from collections import deque
33
33
  from collections.abc import Callable, Sequence
34
34
  from dataclasses import dataclass
35
- from difflib import SequenceMatcher
35
+ from difflib import SequenceMatcher, unified_diff
36
36
  from mimetypes import guess_type
37
37
  from pathlib import Path
38
38
  from typing import Any, Literal, TextIO, cast, get_args, get_origin, overload
@@ -67,9 +67,9 @@ class ToolExecutionResult:
67
67
  - **provider** — gets ``output`` (a text summary) and, for multimodal
68
68
  returns, ``content`` (structured blocks such as an image). ``output``
69
69
  is replayed on later turns.
70
- - **UI** — reads ``metadata`` for structured rendering (e.g. edit diff
71
- line numbers). When ``metadata`` is absent, the UI falls back to
72
- ``output`` as a one-line summary.
70
+ - **UI** — reads ``metadata`` for structured rendering (e.g. edit patch
71
+ and stats). When ``metadata`` is absent, the UI falls back to ``output``
72
+ as a one-line summary.
73
73
  """
74
74
 
75
75
  output: str
@@ -637,52 +637,34 @@ def _run_edit(ctx: ToolContext, args: dict[str, Any]) -> ToolExecutionResult:
637
637
  except Exception as exc:
638
638
  return ToolExecutionResult(output=f"error: failed to write file: {exc}", is_error=True)
639
639
 
640
- # Per-edit metadata for the web diff view and +N −M suffix. ``matches``
641
- # is sorted by original offset; track cumulative char shift to compute
642
- # correct line numbers in the updated text. ``added_lines`` /
643
- # ``removed_lines`` are authoritative diff stats derived from
644
- # ``SequenceMatcher`` so TUI and web display identical numbers.
645
- updated_lines = updated.splitlines()
646
- edit_metas: list[dict[str, Any]] = []
647
- char_shift = 0
648
- context_lines = 3
649
-
650
- for start, end, new_text, _ in matches:
651
- old_snippet = text[start:end]
652
- new_start = start + char_shift
653
- start_line = updated[:new_start].count("\n") + 1
654
- old_lines_list = old_snippet.splitlines()
655
- new_lines_list = new_text.splitlines()
656
- old_lc = len(old_lines_list) or 1
657
- new_lc = len(new_lines_list) or 1
658
-
659
- added = 0
660
- removed = 0
661
- for tag, i1, i2, j1, j2 in SequenceMatcher(None, old_lines_list, new_lines_list).get_opcodes():
662
- if tag in ("replace", "delete"):
663
- removed += i2 - i1
664
- if tag in ("replace", "insert"):
665
- added += j2 - j1
666
-
667
- si = start_line - 1
668
- before = updated_lines[max(0, si - context_lines) : si]
669
- after = updated_lines[si + new_lc : si + new_lc + context_lines]
670
-
671
- edit_metas.append(
672
- {
673
- "start_line": start_line,
674
- "old_line_count": old_lc,
675
- "new_line_count": new_lc,
676
- "added_lines": added,
677
- "removed_lines": removed,
678
- "context_before": before,
679
- "context_after": after,
680
- }
640
+ # The UI renders this standard patch directly; stats are counted from the
641
+ # same patch so TUI and web show the same +N/-N numbers.
642
+ patch_lines = list(
643
+ unified_diff(
644
+ text.splitlines(),
645
+ updated.splitlines(),
646
+ fromfile=f"a/{path}",
647
+ tofile=f"b/{path}",
648
+ n=3,
649
+ lineterm="",
681
650
  )
682
- char_shift += len(new_text) - (end - start)
651
+ )
652
+ patch = "\n".join(patch_lines)
653
+ if patch:
654
+ patch += "\n"
655
+ patch_body = patch_lines[2:]
656
+ added_lines = sum(1 for line in patch_body if line.startswith("+"))
657
+ removed_lines = sum(1 for line in patch_body if line.startswith("-"))
683
658
 
684
659
  summary = f"Updated {path}" if len(edits) == 1 else f"Updated {path} ({len(edits)} edits)"
685
- return ToolExecutionResult(output=summary, metadata={"edits": edit_metas})
660
+ return ToolExecutionResult(
661
+ output=summary,
662
+ metadata={
663
+ "patch": patch,
664
+ "added_lines": added_lines,
665
+ "removed_lines": removed_lines,
666
+ },
667
+ )
686
668
 
687
669
 
688
670
  def _closest_line_hint(text: str, needle: str) -> str | None:
File without changes
File without changes
File without changes