repr-cli 0.2.15__py3-none-any.whl → 0.2.17__py3-none-any.whl
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.
- repr/__init__.py +1 -1
- repr/api.py +363 -62
- repr/auth.py +47 -38
- repr/change_synthesis.py +478 -0
- repr/cli.py +4103 -267
- repr/config.py +119 -11
- repr/configure.py +889 -0
- repr/cron.py +419 -0
- repr/dashboard/__init__.py +9 -0
- repr/dashboard/build.py +126 -0
- repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
- repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
- repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
- repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
- repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
- repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
- repr/dashboard/dist/favicon.svg +4 -0
- repr/dashboard/dist/index.html +14 -0
- repr/dashboard/manager.py +234 -0
- repr/dashboard/server.py +1298 -0
- repr/db.py +980 -0
- repr/hooks.py +3 -2
- repr/loaders/__init__.py +22 -0
- repr/loaders/base.py +156 -0
- repr/loaders/claude_code.py +287 -0
- repr/loaders/clawdbot.py +313 -0
- repr/loaders/gemini_antigravity.py +381 -0
- repr/mcp_server.py +1196 -0
- repr/models.py +503 -0
- repr/openai_analysis.py +25 -0
- repr/session_extractor.py +481 -0
- repr/storage.py +360 -0
- repr/story_synthesis.py +1296 -0
- repr/templates.py +68 -4
- repr/timeline.py +710 -0
- repr/tools.py +17 -8
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/METADATA +50 -10
- repr_cli-0.2.17.dist-info/RECORD +52 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/WHEEL +1 -1
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/entry_points.txt +1 -0
- repr_cli-0.2.15.dist-info/RECORD +0 -26
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/top_level.txt +0 -0
repr/hooks.py
CHANGED
|
@@ -516,12 +516,13 @@ def _spawn_background_generate(repo_path: Path) -> None:
|
|
|
516
516
|
log_file = log_dir / "auto_generate.log"
|
|
517
517
|
|
|
518
518
|
# Build command - use sys.executable to find repr command
|
|
519
|
-
# repr generate --
|
|
519
|
+
# repr generate --weeks 1 --json --batch-size <queue_size>
|
|
520
|
+
# Note: We rely on default model config for synthesis
|
|
520
521
|
cmd = [
|
|
521
522
|
sys.executable, "-m", "repr",
|
|
522
523
|
"generate",
|
|
523
524
|
"--repo", str(repo_path),
|
|
524
|
-
"--
|
|
525
|
+
"--days", "7", # limit context to 1 week for quick update
|
|
525
526
|
"--json",
|
|
526
527
|
]
|
|
527
528
|
|
repr/loaders/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session loaders for different AI assistant formats.
|
|
3
|
+
|
|
4
|
+
Supported formats:
|
|
5
|
+
- Claude Code: ~/.claude/projects/<project>/*.jsonl
|
|
6
|
+
- Clawdbot: ~/.clawdbot/agents/<id>/sessions/*.jsonl
|
|
7
|
+
- Gemini Antigravity: ~/.gemini/antigravity/brain/<conversation-id>/
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .claude_code import ClaudeCodeLoader
|
|
11
|
+
from .clawdbot import ClawdbotLoader
|
|
12
|
+
from .gemini_antigravity import GeminiAntigravityLoader
|
|
13
|
+
from .base import SessionLoader, detect_session_source, load_sessions_for_project
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"SessionLoader",
|
|
17
|
+
"ClaudeCodeLoader",
|
|
18
|
+
"ClawdbotLoader",
|
|
19
|
+
"GeminiAntigravityLoader",
|
|
20
|
+
"detect_session_source",
|
|
21
|
+
"load_sessions_for_project",
|
|
22
|
+
]
|
repr/loaders/base.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base session loader interface and utilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterator
|
|
10
|
+
|
|
11
|
+
from ..models import Session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SessionLoader(ABC):
|
|
15
|
+
"""Abstract base class for session loaders."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def name(self) -> str:
|
|
20
|
+
"""Loader name (e.g., 'claude_code', 'clawdbot')."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def find_sessions(
|
|
25
|
+
self,
|
|
26
|
+
project_path: str | Path,
|
|
27
|
+
since: datetime | None = None,
|
|
28
|
+
) -> list[Path]:
|
|
29
|
+
"""
|
|
30
|
+
Find session files for a project.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_path: Path to the project/repo
|
|
34
|
+
since: Only return sessions after this time
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of session file paths
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def load_session(self, path: Path) -> Session | None:
|
|
43
|
+
"""
|
|
44
|
+
Load a single session from a file.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
path: Path to the session file
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Session object or None if loading fails
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def load_sessions(
|
|
55
|
+
self,
|
|
56
|
+
project_path: str | Path,
|
|
57
|
+
since: datetime | None = None,
|
|
58
|
+
) -> Iterator[Session]:
|
|
59
|
+
"""
|
|
60
|
+
Load all sessions for a project.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
project_path: Path to the project/repo
|
|
64
|
+
since: Only return sessions after this time
|
|
65
|
+
|
|
66
|
+
Yields:
|
|
67
|
+
Session objects
|
|
68
|
+
"""
|
|
69
|
+
for path in self.find_sessions(project_path, since):
|
|
70
|
+
session = self.load_session(path)
|
|
71
|
+
if session:
|
|
72
|
+
yield session
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def detect_session_source(project_path: str | Path) -> list[str]:
|
|
76
|
+
"""
|
|
77
|
+
Detect which session sources are available for a project.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
project_path: Path to the project/repo
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of available sources: ["claude_code", "clawdbot", "gemini_antigravity"]
|
|
84
|
+
"""
|
|
85
|
+
from .claude_code import ClaudeCodeLoader
|
|
86
|
+
from .clawdbot import ClawdbotLoader
|
|
87
|
+
from .gemini_antigravity import GeminiAntigravityLoader
|
|
88
|
+
|
|
89
|
+
sources = []
|
|
90
|
+
project_path = Path(project_path).resolve()
|
|
91
|
+
|
|
92
|
+
# Check Claude Code
|
|
93
|
+
claude_loader = ClaudeCodeLoader()
|
|
94
|
+
if claude_loader.find_sessions(project_path):
|
|
95
|
+
sources.append("claude_code")
|
|
96
|
+
|
|
97
|
+
# Check Clawdbot
|
|
98
|
+
clawdbot_loader = ClawdbotLoader()
|
|
99
|
+
if clawdbot_loader.find_sessions(project_path):
|
|
100
|
+
sources.append("clawdbot")
|
|
101
|
+
|
|
102
|
+
# Check Gemini Antigravity
|
|
103
|
+
gemini_loader = GeminiAntigravityLoader()
|
|
104
|
+
if gemini_loader.find_sessions(project_path):
|
|
105
|
+
sources.append("gemini_antigravity")
|
|
106
|
+
|
|
107
|
+
return sources
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_sessions_for_project(
|
|
111
|
+
project_path: str | Path,
|
|
112
|
+
sources: list[str] | None = None,
|
|
113
|
+
since: datetime | None = None,
|
|
114
|
+
days_back: int | None = None,
|
|
115
|
+
) -> list[Session]:
|
|
116
|
+
"""
|
|
117
|
+
Load sessions from all sources for a project.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
project_path: Path to the project/repo
|
|
121
|
+
sources: Specific sources to use, or None for auto-detect
|
|
122
|
+
since: Only return sessions after this time
|
|
123
|
+
days_back: Alternative to since: load sessions from last N days
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of sessions sorted by start time
|
|
127
|
+
"""
|
|
128
|
+
from .claude_code import ClaudeCodeLoader
|
|
129
|
+
from .clawdbot import ClawdbotLoader
|
|
130
|
+
from .gemini_antigravity import GeminiAntigravityLoader
|
|
131
|
+
|
|
132
|
+
# Calculate since from days_back if provided
|
|
133
|
+
if days_back is not None and since is None:
|
|
134
|
+
since = datetime.now() - timedelta(days=days_back)
|
|
135
|
+
|
|
136
|
+
# Auto-detect sources if not specified
|
|
137
|
+
if sources is None:
|
|
138
|
+
sources = detect_session_source(project_path)
|
|
139
|
+
|
|
140
|
+
# Load from all sources
|
|
141
|
+
loaders = {
|
|
142
|
+
"claude_code": ClaudeCodeLoader(),
|
|
143
|
+
"clawdbot": ClawdbotLoader(),
|
|
144
|
+
"gemini_antigravity": GeminiAntigravityLoader(),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
all_sessions = []
|
|
148
|
+
for source in sources:
|
|
149
|
+
if source in loaders:
|
|
150
|
+
loader = loaders[source]
|
|
151
|
+
for session in loader.load_sessions(project_path, since):
|
|
152
|
+
all_sessions.append(session)
|
|
153
|
+
|
|
154
|
+
# Sort by start time
|
|
155
|
+
all_sessions.sort(key=lambda s: s.started_at)
|
|
156
|
+
return all_sessions
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session loader for Claude Code format.
|
|
3
|
+
|
|
4
|
+
Claude Code stores sessions as JSONL files in:
|
|
5
|
+
~/.claude/projects/<project-path-encoded>/*.jsonl
|
|
6
|
+
|
|
7
|
+
Each line is a JSON object with fields like:
|
|
8
|
+
- type: "user", "assistant", "progress", "tool_use", etc.
|
|
9
|
+
- sessionId: UUID of the session
|
|
10
|
+
- uuid: UUID of this message
|
|
11
|
+
- timestamp: ISO timestamp
|
|
12
|
+
- message: {role, content} for user/assistant types
|
|
13
|
+
- cwd: Working directory
|
|
14
|
+
- gitBranch: Current git branch
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Iterator
|
|
22
|
+
|
|
23
|
+
from ..models import (
|
|
24
|
+
ContentBlock,
|
|
25
|
+
ContentBlockType,
|
|
26
|
+
MessageRole,
|
|
27
|
+
Session,
|
|
28
|
+
SessionMessage,
|
|
29
|
+
)
|
|
30
|
+
from .base import SessionLoader
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ClaudeCodeLoader(SessionLoader):
|
|
34
|
+
"""Loader for Claude Code session files."""
|
|
35
|
+
|
|
36
|
+
CLAUDE_HOME = Path.home() / ".claude" / "projects"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def name(self) -> str:
|
|
40
|
+
return "claude_code"
|
|
41
|
+
|
|
42
|
+
def _encode_project_path(self, project_path: Path) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Encode a project path to Claude Code's directory naming convention.
|
|
45
|
+
|
|
46
|
+
Claude Code uses path with / replaced by - and leading -.
|
|
47
|
+
e.g., /Users/mendrika/Projects/foo -> -Users-mendrika-Projects-foo
|
|
48
|
+
"""
|
|
49
|
+
path_str = str(project_path.resolve())
|
|
50
|
+
# Replace path separators with dashes, remove leading slash
|
|
51
|
+
encoded = path_str.replace("/", "-")
|
|
52
|
+
if encoded.startswith("-"):
|
|
53
|
+
pass # Keep leading dash
|
|
54
|
+
else:
|
|
55
|
+
encoded = "-" + encoded
|
|
56
|
+
return encoded
|
|
57
|
+
|
|
58
|
+
def _decode_project_path(self, encoded: str) -> Path:
|
|
59
|
+
"""
|
|
60
|
+
Decode Claude Code's directory name back to a path.
|
|
61
|
+
"""
|
|
62
|
+
# Replace dashes with slashes, but skip leading dash
|
|
63
|
+
if encoded.startswith("-"):
|
|
64
|
+
decoded = encoded[1:].replace("-", "/")
|
|
65
|
+
else:
|
|
66
|
+
decoded = encoded.replace("-", "/")
|
|
67
|
+
return Path("/" + decoded)
|
|
68
|
+
|
|
69
|
+
def find_sessions(
|
|
70
|
+
self,
|
|
71
|
+
project_path: str | Path,
|
|
72
|
+
since: datetime | None = None,
|
|
73
|
+
) -> list[Path]:
|
|
74
|
+
"""Find Claude Code session files for a project."""
|
|
75
|
+
project_path = Path(project_path).resolve()
|
|
76
|
+
|
|
77
|
+
if not self.CLAUDE_HOME.exists():
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
# Try exact encoded path match
|
|
81
|
+
encoded = self._encode_project_path(project_path)
|
|
82
|
+
project_dir = self.CLAUDE_HOME / encoded
|
|
83
|
+
|
|
84
|
+
if not project_dir.exists():
|
|
85
|
+
# Try to find by scanning directories (handles path variations)
|
|
86
|
+
for dir_path in self.CLAUDE_HOME.iterdir():
|
|
87
|
+
if dir_path.is_dir():
|
|
88
|
+
decoded = self._decode_project_path(dir_path.name)
|
|
89
|
+
# Check if project_path is a prefix or match
|
|
90
|
+
try:
|
|
91
|
+
if project_path == decoded or decoded.is_relative_to(project_path):
|
|
92
|
+
project_dir = dir_path
|
|
93
|
+
break
|
|
94
|
+
if project_path.is_relative_to(decoded):
|
|
95
|
+
project_dir = dir_path
|
|
96
|
+
break
|
|
97
|
+
except (ValueError, TypeError):
|
|
98
|
+
continue
|
|
99
|
+
else:
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
# Find all .jsonl files
|
|
103
|
+
session_files = []
|
|
104
|
+
for file_path in project_dir.glob("*.jsonl"):
|
|
105
|
+
if since is not None:
|
|
106
|
+
# Quick check: file modification time
|
|
107
|
+
mtime = datetime.fromtimestamp(file_path.stat().st_mtime)
|
|
108
|
+
# Handle timezone-aware since
|
|
109
|
+
if since.tzinfo is not None:
|
|
110
|
+
from datetime import timezone
|
|
111
|
+
mtime = mtime.replace(tzinfo=timezone.utc)
|
|
112
|
+
if mtime < since:
|
|
113
|
+
continue
|
|
114
|
+
session_files.append(file_path)
|
|
115
|
+
|
|
116
|
+
return sorted(session_files)
|
|
117
|
+
|
|
118
|
+
def load_session(self, path: Path) -> Session | None:
|
|
119
|
+
"""Load a Claude Code session from a JSONL file."""
|
|
120
|
+
try:
|
|
121
|
+
messages = []
|
|
122
|
+
session_id = None
|
|
123
|
+
cwd = None
|
|
124
|
+
git_branch = None
|
|
125
|
+
model = None
|
|
126
|
+
started_at = None
|
|
127
|
+
ended_at = None
|
|
128
|
+
|
|
129
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
130
|
+
for line in f:
|
|
131
|
+
line = line.strip()
|
|
132
|
+
if not line:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
entry = json.loads(line)
|
|
137
|
+
except json.JSONDecodeError:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
entry_type = entry.get("type")
|
|
141
|
+
timestamp_str = entry.get("timestamp")
|
|
142
|
+
|
|
143
|
+
# Extract session metadata
|
|
144
|
+
if session_id is None and entry.get("sessionId"):
|
|
145
|
+
session_id = entry["sessionId"]
|
|
146
|
+
if cwd is None and entry.get("cwd"):
|
|
147
|
+
cwd = entry["cwd"]
|
|
148
|
+
if git_branch is None and entry.get("gitBranch"):
|
|
149
|
+
git_branch = entry["gitBranch"]
|
|
150
|
+
|
|
151
|
+
# Track timestamps
|
|
152
|
+
if timestamp_str:
|
|
153
|
+
try:
|
|
154
|
+
ts = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
|
|
155
|
+
if started_at is None or ts < started_at:
|
|
156
|
+
started_at = ts
|
|
157
|
+
if ended_at is None or ts > ended_at:
|
|
158
|
+
ended_at = ts
|
|
159
|
+
except (ValueError, TypeError):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
# Process user/assistant messages
|
|
163
|
+
if entry_type == "user":
|
|
164
|
+
msg_data = entry.get("message", {})
|
|
165
|
+
content = self._parse_content(msg_data.get("content", ""))
|
|
166
|
+
if content:
|
|
167
|
+
messages.append(SessionMessage(
|
|
168
|
+
timestamp=timestamp_str or started_at or datetime.now(),
|
|
169
|
+
role=MessageRole.USER,
|
|
170
|
+
content=content,
|
|
171
|
+
uuid=entry.get("uuid"),
|
|
172
|
+
))
|
|
173
|
+
|
|
174
|
+
elif entry_type == "assistant":
|
|
175
|
+
msg_data = entry.get("message", {})
|
|
176
|
+
content_raw = msg_data.get("content", [])
|
|
177
|
+
content = self._parse_assistant_content(content_raw)
|
|
178
|
+
|
|
179
|
+
# Extract model
|
|
180
|
+
if model is None and msg_data.get("model"):
|
|
181
|
+
model = msg_data["model"]
|
|
182
|
+
|
|
183
|
+
if content:
|
|
184
|
+
messages.append(SessionMessage(
|
|
185
|
+
timestamp=timestamp_str or ended_at or datetime.now(),
|
|
186
|
+
role=MessageRole.ASSISTANT,
|
|
187
|
+
content=content,
|
|
188
|
+
uuid=entry.get("uuid"),
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
if not session_id or not messages:
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
return Session(
|
|
195
|
+
id=session_id,
|
|
196
|
+
started_at=started_at or datetime.now(),
|
|
197
|
+
ended_at=ended_at,
|
|
198
|
+
channel="cli", # Claude Code is CLI-based
|
|
199
|
+
messages=messages,
|
|
200
|
+
cwd=cwd,
|
|
201
|
+
git_branch=git_branch,
|
|
202
|
+
model=model,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
# Log error but don't crash
|
|
207
|
+
import sys
|
|
208
|
+
print(f"Error loading Claude Code session {path}: {e}", file=sys.stderr)
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def _parse_content(self, content) -> list[ContentBlock]:
|
|
212
|
+
"""Parse message content into ContentBlocks."""
|
|
213
|
+
blocks = []
|
|
214
|
+
|
|
215
|
+
if isinstance(content, str):
|
|
216
|
+
if content.strip():
|
|
217
|
+
blocks.append(ContentBlock(
|
|
218
|
+
type=ContentBlockType.TEXT,
|
|
219
|
+
text=content,
|
|
220
|
+
))
|
|
221
|
+
elif isinstance(content, list):
|
|
222
|
+
for item in content:
|
|
223
|
+
if isinstance(item, str):
|
|
224
|
+
if item.strip():
|
|
225
|
+
blocks.append(ContentBlock(
|
|
226
|
+
type=ContentBlockType.TEXT,
|
|
227
|
+
text=item,
|
|
228
|
+
))
|
|
229
|
+
elif isinstance(item, dict):
|
|
230
|
+
item_type = item.get("type", "text")
|
|
231
|
+
if item_type == "text":
|
|
232
|
+
text = item.get("text", "")
|
|
233
|
+
if text.strip():
|
|
234
|
+
blocks.append(ContentBlock(
|
|
235
|
+
type=ContentBlockType.TEXT,
|
|
236
|
+
text=text,
|
|
237
|
+
))
|
|
238
|
+
elif item_type == "tool_use":
|
|
239
|
+
blocks.append(ContentBlock(
|
|
240
|
+
type=ContentBlockType.TOOL_CALL,
|
|
241
|
+
name=item.get("name"),
|
|
242
|
+
input=item.get("input"),
|
|
243
|
+
))
|
|
244
|
+
|
|
245
|
+
return blocks
|
|
246
|
+
|
|
247
|
+
def _parse_assistant_content(self, content) -> list[ContentBlock]:
|
|
248
|
+
"""Parse assistant message content including tool calls and thinking."""
|
|
249
|
+
blocks = []
|
|
250
|
+
|
|
251
|
+
if isinstance(content, list):
|
|
252
|
+
for item in content:
|
|
253
|
+
if isinstance(item, dict):
|
|
254
|
+
item_type = item.get("type", "text")
|
|
255
|
+
|
|
256
|
+
if item_type == "text":
|
|
257
|
+
text = item.get("text", "")
|
|
258
|
+
if text.strip():
|
|
259
|
+
blocks.append(ContentBlock(
|
|
260
|
+
type=ContentBlockType.TEXT,
|
|
261
|
+
text=text,
|
|
262
|
+
))
|
|
263
|
+
elif item_type == "tool_use":
|
|
264
|
+
blocks.append(ContentBlock(
|
|
265
|
+
type=ContentBlockType.TOOL_CALL,
|
|
266
|
+
name=item.get("name"),
|
|
267
|
+
input=item.get("input"),
|
|
268
|
+
))
|
|
269
|
+
elif item_type == "thinking":
|
|
270
|
+
thinking = item.get("thinking", "")
|
|
271
|
+
if thinking.strip():
|
|
272
|
+
blocks.append(ContentBlock(
|
|
273
|
+
type=ContentBlockType.THINKING,
|
|
274
|
+
text=thinking,
|
|
275
|
+
))
|
|
276
|
+
elif isinstance(item, str) and item.strip():
|
|
277
|
+
blocks.append(ContentBlock(
|
|
278
|
+
type=ContentBlockType.TEXT,
|
|
279
|
+
text=item,
|
|
280
|
+
))
|
|
281
|
+
elif isinstance(content, str) and content.strip():
|
|
282
|
+
blocks.append(ContentBlock(
|
|
283
|
+
type=ContentBlockType.TEXT,
|
|
284
|
+
text=content,
|
|
285
|
+
))
|
|
286
|
+
|
|
287
|
+
return blocks
|