Skip to content

Commit 8b53781

Browse files
authored
Merge pull request #400 from Opencode-DCP/dev
Dev
2 parents 49d5c61 + 9da5765 commit 8b53781

5 files changed

Lines changed: 520 additions & 368 deletions

File tree

lib/messages/prune.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -179,33 +179,41 @@ const filterCompressedRanges = (
179179
const msgId = msg.info.id
180180

181181
// Check if there's a summary to inject at this anchor point
182-
const summary = state.compressSummaries?.find((s) => s.anchorMessageId === msgId)
182+
const summary = state.compressSummaries?.find((s) => s?.anchorMessageId === msgId)
183183
if (summary) {
184-
// Find user message for variant and as base for synthetic message
185-
const msgIndex = messages.indexOf(msg)
186-
const userMessage = getLastUserMessage(messages, msgIndex)
187-
188-
if (userMessage) {
189-
const userInfo = userMessage.info as UserMessage
190-
const summaryContent = summary.summary
191-
const summarySeed = `${summary.blockId}:${summary.anchorMessageId}`
192-
result.push(
193-
createSyntheticUserMessage(
194-
userMessage,
195-
summaryContent,
196-
userInfo.variant,
197-
summarySeed,
198-
),
199-
)
200-
201-
logger.info("Injected compress summary", {
184+
const rawSummaryContent = (summary as { summary?: unknown }).summary
185+
if (typeof rawSummaryContent !== "string" || rawSummaryContent.length === 0) {
186+
logger.warn("Skipping malformed compress summary", {
202187
anchorMessageId: msgId,
203-
summaryLength: summary.summary.length,
188+
blockId: (summary as { blockId?: unknown }).blockId,
204189
})
205190
} else {
206-
logger.warn("No user message found for compress summary", {
207-
anchorMessageId: msgId,
208-
})
191+
// Find user message for variant and as base for synthetic message
192+
const msgIndex = messages.indexOf(msg)
193+
const userMessage = getLastUserMessage(messages, msgIndex)
194+
195+
if (userMessage) {
196+
const userInfo = userMessage.info as UserMessage
197+
const summaryContent = rawSummaryContent
198+
const summarySeed = `${summary.blockId}:${summary.anchorMessageId}`
199+
result.push(
200+
createSyntheticUserMessage(
201+
userMessage,
202+
summaryContent,
203+
userInfo.variant,
204+
summarySeed,
205+
),
206+
)
207+
208+
logger.info("Injected compress summary", {
209+
anchorMessageId: msgId,
210+
summaryLength: summaryContent.length,
211+
})
212+
} else {
213+
logger.warn("No user message found for compress summary", {
214+
anchorMessageId: msgId,
215+
})
216+
}
209217
}
210218
}
211219

scripts/opencode-find-session

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,35 @@
11
#!/usr/bin/env python3
22
"""
3-
Find OpenCode session IDs by title search.
3+
Find OpenCode session IDs by title search using the OpenCode database.
44
Returns matching session IDs ordered by last usage time.
55
66
Usage: opencode-find-session <search-term> [--exact] [--json]
77
"""
88

99
import json
1010
import argparse
11-
from pathlib import Path
1211
from datetime import datetime
1312

13+
from opencode_api import APIError, add_api_arguments, create_client_from_args, list_sessions_across_projects
1414

15-
def get_all_sessions(storage: Path) -> list[dict]:
16-
"""Get all sessions with their metadata."""
17-
session_dir = storage / "session"
18-
message_dir = storage / "message"
19-
20-
if not session_dir.exists():
21-
return []
22-
15+
16+
def get_all_sessions(client, session_list_limit: int) -> list[dict]:
17+
"""Get all sessions with normalized metadata."""
18+
api_sessions = list_sessions_across_projects(client, per_project_limit=session_list_limit)
2319
sessions = []
24-
25-
for app_dir in session_dir.iterdir():
26-
if not app_dir.is_dir():
27-
continue
28-
29-
for session_file in app_dir.glob("*.json"):
30-
try:
31-
session = json.loads(session_file.read_text())
32-
session_id = session_file.stem
33-
34-
# Get last modified time from message directory
35-
msg_path = message_dir / session_id
36-
if msg_path.exists():
37-
mtime = msg_path.stat().st_mtime
38-
else:
39-
mtime = session_file.stat().st_mtime
40-
41-
sessions.append({
42-
"id": session_id,
43-
"title": session.get("title", "Untitled"),
44-
"created_at": session.get("createdAt"),
45-
"last_used": mtime,
46-
"last_used_iso": datetime.fromtimestamp(mtime).isoformat()
47-
})
48-
except (json.JSONDecodeError, IOError):
49-
pass
50-
20+
for session in api_sessions:
21+
time_data = session.get("time", {})
22+
updated_ms = time_data.get("updated") or time_data.get("created") or 0
23+
last_used = updated_ms / 1000 if updated_ms else 0
24+
sessions.append(
25+
{
26+
"id": session.get("id", ""),
27+
"title": session.get("title", "Untitled"),
28+
"created_at": time_data.get("created"),
29+
"last_used": last_used,
30+
"last_used_iso": datetime.fromtimestamp(last_used).isoformat() if last_used else None,
31+
}
32+
)
5133
return sessions
5234

5335

@@ -101,6 +83,7 @@ def main():
10183
)
10284
parser.add_argument(
10385
"search_term",
86+
nargs="?",
10487
type=str,
10588
help="Text to search for in session titles"
10689
)
@@ -119,16 +102,19 @@ def main():
119102
action="store_true",
120103
help="Show all sessions (ignore search term)"
121104
)
105+
add_api_arguments(parser)
122106
args = parser.parse_args()
123-
124-
storage = Path.home() / ".local/share/opencode/storage"
125-
126-
if not storage.exists():
127-
print("Error: OpenCode storage not found at", storage)
107+
108+
if not args.all and not args.search_term:
109+
parser.error("search_term is required unless --all is used")
110+
111+
try:
112+
with create_client_from_args(args) as client:
113+
sessions = get_all_sessions(client, args.session_list_limit)
114+
except APIError as err:
115+
print(f"Error: {err}")
128116
return 1
129-
130-
sessions = get_all_sessions(storage)
131-
117+
132118
if args.all:
133119
results = sorted(sessions, key=lambda s: s["last_used"], reverse=True)
134120
else:

scripts/opencode-get-message

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Get full OpenCode message payload(s) by message ID from the OpenCode database.
4+
5+
Usage:
6+
opencode-get-message <message-id> [message-id ...]
7+
opencode-get-message --session <session-id> <message-id> [message-id ...]
8+
"""
9+
10+
import argparse
11+
import json
12+
13+
from opencode_api import APIError, add_api_arguments, create_client_from_args, list_sessions_across_projects
14+
15+
16+
def normalize_message_payload(payload: dict) -> dict:
17+
"""Normalize API response into expected output shape."""
18+
info = payload.get("info", {})
19+
parts = payload.get("parts", [])
20+
return {"info": info, "parts": parts}
21+
22+
23+
def not_found_result(message_id: str) -> dict:
24+
return {"id": message_id, "error": "message_not_found"}
25+
26+
27+
def get_message_for_session(client, session: dict, message_id: str) -> dict:
28+
"""Get a message payload within a known session."""
29+
session_id = session.get("id", "")
30+
directory = session.get("directory")
31+
try:
32+
payload = client.get_session_message(session_id, message_id, directory=directory)
33+
return normalize_message_payload(payload)
34+
except APIError as err:
35+
if err.status_code == 404:
36+
return not_found_result(message_id)
37+
raise
38+
39+
40+
def find_messages_without_session(client, message_ids: list[str], scan_sessions: int, session_list_limit: int) -> list[dict]:
41+
"""Search recent sessions for requested message IDs."""
42+
wanted = set(message_ids)
43+
found: dict[str, dict] = {}
44+
45+
sessions = list_sessions_across_projects(client, per_project_limit=session_list_limit)
46+
if scan_sessions > 0:
47+
sessions = sessions[:scan_sessions]
48+
49+
for session in sessions:
50+
if not wanted:
51+
break
52+
messages = client.get_session_messages(session.get("id", ""), directory=session.get("directory"))
53+
for message in messages:
54+
info = message.get("info", {})
55+
mid = info.get("id")
56+
if mid in wanted:
57+
found[mid] = normalize_message_payload(message)
58+
wanted.remove(mid)
59+
60+
return [found.get(message_id, not_found_result(message_id)) for message_id in message_ids]
61+
62+
63+
def main() -> int:
64+
parser = argparse.ArgumentParser(description="Get full OpenCode message payload by message ID")
65+
parser.add_argument("--session", "-s", type=str, default=None, help="Session ID for direct lookup")
66+
parser.add_argument(
67+
"--scan-sessions",
68+
type=int,
69+
default=200,
70+
help="When --session is omitted, scan this many recent sessions for message IDs",
71+
)
72+
parser.add_argument("message_ids", nargs="+", help="One or more message IDs")
73+
add_api_arguments(parser)
74+
args = parser.parse_args()
75+
76+
try:
77+
with create_client_from_args(args) as client:
78+
if args.session:
79+
session = client.get_session(args.session)
80+
results = [get_message_for_session(client, session, message_id) for message_id in args.message_ids]
81+
else:
82+
results = find_messages_without_session(
83+
client,
84+
args.message_ids,
85+
scan_sessions=args.scan_sessions,
86+
session_list_limit=args.session_list_limit,
87+
)
88+
except APIError as err:
89+
print(f"Error: {err}")
90+
return 1
91+
92+
output = results[0] if len(results) == 1 else results
93+
print(json.dumps(output, indent=2))
94+
return 0
95+
96+
97+
if __name__ == "__main__":
98+
raise SystemExit(main())

0 commit comments

Comments
 (0)