Skip to content
Merged

Dev #400

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions lib/messages/prune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,33 +179,41 @@ const filterCompressedRanges = (
const msgId = msg.info.id

// Check if there's a summary to inject at this anchor point
const summary = state.compressSummaries?.find((s) => s.anchorMessageId === msgId)
const summary = state.compressSummaries?.find((s) => s?.anchorMessageId === msgId)
if (summary) {
// Find user message for variant and as base for synthetic message
const msgIndex = messages.indexOf(msg)
const userMessage = getLastUserMessage(messages, msgIndex)

if (userMessage) {
const userInfo = userMessage.info as UserMessage
const summaryContent = summary.summary
const summarySeed = `${summary.blockId}:${summary.anchorMessageId}`
result.push(
createSyntheticUserMessage(
userMessage,
summaryContent,
userInfo.variant,
summarySeed,
),
)

logger.info("Injected compress summary", {
const rawSummaryContent = (summary as { summary?: unknown }).summary
if (typeof rawSummaryContent !== "string" || rawSummaryContent.length === 0) {
logger.warn("Skipping malformed compress summary", {
anchorMessageId: msgId,
summaryLength: summary.summary.length,
blockId: (summary as { blockId?: unknown }).blockId,
})
} else {
logger.warn("No user message found for compress summary", {
anchorMessageId: msgId,
})
// Find user message for variant and as base for synthetic message
const msgIndex = messages.indexOf(msg)
const userMessage = getLastUserMessage(messages, msgIndex)

if (userMessage) {
const userInfo = userMessage.info as UserMessage
const summaryContent = rawSummaryContent
const summarySeed = `${summary.blockId}:${summary.anchorMessageId}`
result.push(
createSyntheticUserMessage(
userMessage,
summaryContent,
userInfo.variant,
summarySeed,
),
)

logger.info("Injected compress summary", {
anchorMessageId: msgId,
summaryLength: summaryContent.length,
})
} else {
logger.warn("No user message found for compress summary", {
anchorMessageId: msgId,
})
}
}
}

Expand Down
76 changes: 31 additions & 45 deletions scripts/opencode-find-session
Original file line number Diff line number Diff line change
@@ -1,53 +1,35 @@
#!/usr/bin/env python3
"""
Find OpenCode session IDs by title search.
Find OpenCode session IDs by title search using the OpenCode database.
Returns matching session IDs ordered by last usage time.

Usage: opencode-find-session <search-term> [--exact] [--json]
"""

import json
import argparse
from pathlib import Path
from datetime import datetime

from opencode_api import APIError, add_api_arguments, create_client_from_args, list_sessions_across_projects

def get_all_sessions(storage: Path) -> list[dict]:
"""Get all sessions with their metadata."""
session_dir = storage / "session"
message_dir = storage / "message"

if not session_dir.exists():
return []


def get_all_sessions(client, session_list_limit: int) -> list[dict]:
"""Get all sessions with normalized metadata."""
api_sessions = list_sessions_across_projects(client, per_project_limit=session_list_limit)
sessions = []

for app_dir in session_dir.iterdir():
if not app_dir.is_dir():
continue

for session_file in app_dir.glob("*.json"):
try:
session = json.loads(session_file.read_text())
session_id = session_file.stem

# Get last modified time from message directory
msg_path = message_dir / session_id
if msg_path.exists():
mtime = msg_path.stat().st_mtime
else:
mtime = session_file.stat().st_mtime

sessions.append({
"id": session_id,
"title": session.get("title", "Untitled"),
"created_at": session.get("createdAt"),
"last_used": mtime,
"last_used_iso": datetime.fromtimestamp(mtime).isoformat()
})
except (json.JSONDecodeError, IOError):
pass

for session in api_sessions:
time_data = session.get("time", {})
updated_ms = time_data.get("updated") or time_data.get("created") or 0
last_used = updated_ms / 1000 if updated_ms else 0
sessions.append(
{
"id": session.get("id", ""),
"title": session.get("title", "Untitled"),
"created_at": time_data.get("created"),
"last_used": last_used,
"last_used_iso": datetime.fromtimestamp(last_used).isoformat() if last_used else None,
}
)
return sessions


Expand Down Expand Up @@ -101,6 +83,7 @@ def main():
)
parser.add_argument(
"search_term",
nargs="?",
type=str,
help="Text to search for in session titles"
)
Expand All @@ -119,16 +102,19 @@ def main():
action="store_true",
help="Show all sessions (ignore search term)"
)
add_api_arguments(parser)
args = parser.parse_args()

storage = Path.home() / ".local/share/opencode/storage"

if not storage.exists():
print("Error: OpenCode storage not found at", storage)

if not args.all and not args.search_term:
parser.error("search_term is required unless --all is used")

try:
with create_client_from_args(args) as client:
sessions = get_all_sessions(client, args.session_list_limit)
except APIError as err:
print(f"Error: {err}")
return 1

sessions = get_all_sessions(storage)


if args.all:
results = sorted(sessions, key=lambda s: s["last_used"], reverse=True)
else:
Expand Down
98 changes: 98 additions & 0 deletions scripts/opencode-get-message
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
Get full OpenCode message payload(s) by message ID from the OpenCode database.

Usage:
opencode-get-message <message-id> [message-id ...]
opencode-get-message --session <session-id> <message-id> [message-id ...]
"""

import argparse
import json

from opencode_api import APIError, add_api_arguments, create_client_from_args, list_sessions_across_projects


def normalize_message_payload(payload: dict) -> dict:
"""Normalize API response into expected output shape."""
info = payload.get("info", {})
parts = payload.get("parts", [])
return {"info": info, "parts": parts}


def not_found_result(message_id: str) -> dict:
return {"id": message_id, "error": "message_not_found"}


def get_message_for_session(client, session: dict, message_id: str) -> dict:
"""Get a message payload within a known session."""
session_id = session.get("id", "")
directory = session.get("directory")
try:
payload = client.get_session_message(session_id, message_id, directory=directory)
return normalize_message_payload(payload)
except APIError as err:
if err.status_code == 404:
return not_found_result(message_id)
raise


def find_messages_without_session(client, message_ids: list[str], scan_sessions: int, session_list_limit: int) -> list[dict]:
"""Search recent sessions for requested message IDs."""
wanted = set(message_ids)
found: dict[str, dict] = {}

sessions = list_sessions_across_projects(client, per_project_limit=session_list_limit)
if scan_sessions > 0:
sessions = sessions[:scan_sessions]

for session in sessions:
if not wanted:
break
messages = client.get_session_messages(session.get("id", ""), directory=session.get("directory"))
for message in messages:
info = message.get("info", {})
mid = info.get("id")
if mid in wanted:
found[mid] = normalize_message_payload(message)
wanted.remove(mid)

return [found.get(message_id, not_found_result(message_id)) for message_id in message_ids]


def main() -> int:
parser = argparse.ArgumentParser(description="Get full OpenCode message payload by message ID")
parser.add_argument("--session", "-s", type=str, default=None, help="Session ID for direct lookup")
parser.add_argument(
"--scan-sessions",
type=int,
default=200,
help="When --session is omitted, scan this many recent sessions for message IDs",
)
parser.add_argument("message_ids", nargs="+", help="One or more message IDs")
add_api_arguments(parser)
args = parser.parse_args()

try:
with create_client_from_args(args) as client:
if args.session:
session = client.get_session(args.session)
results = [get_message_for_session(client, session, message_id) for message_id in args.message_ids]
else:
results = find_messages_without_session(
client,
args.message_ids,
scan_sessions=args.scan_sessions,
session_list_limit=args.session_list_limit,
)
except APIError as err:
print(f"Error: {err}")
return 1

output = results[0] if len(results) == 1 else results
print(json.dumps(output, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading