anywhere-cli 0.1.0__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.
- anywhere_cli-0.1.0.dist-info/METADATA +110 -0
- anywhere_cli-0.1.0.dist-info/RECORD +36 -0
- anywhere_cli-0.1.0.dist-info/WHEEL +4 -0
- anywhere_cli-0.1.0.dist-info/entry_points.txt +3 -0
- connector/__init__.py +3 -0
- connector/adapter.py +39 -0
- connector/attachments.py +36 -0
- connector/capabilities.py +334 -0
- connector/claude/__init__.py +8 -0
- connector/claude/history_adapter.py +642 -0
- connector/claude/normalized.py +23 -0
- connector/claude/normalizers.py +97 -0
- connector/claude/path_utils.py +13 -0
- connector/claude/preferences.py +38 -0
- connector/claude/sdk_adapter.py +1377 -0
- connector/claude/timeline_identity.py +47 -0
- connector/claude/timeline_reducer.py +379 -0
- connector/claude/trust.py +69 -0
- connector/cli.py +149 -0
- connector/codex/__init__.py +3 -0
- connector/codex/adapter.py +951 -0
- connector/codex/history.py +199 -0
- connector/codex/reducer.py +1223 -0
- connector/codex/rpc.py +260 -0
- connector/launch.py +104 -0
- connector/local/__init__.py +6 -0
- connector/local/common.py +118 -0
- connector/local/file_ops.py +122 -0
- connector/local/ops.py +83 -0
- connector/local/shell.py +225 -0
- connector/local/terminal.py +389 -0
- connector/local_ops.py +5 -0
- connector/protocol.py +26 -0
- connector/runtime.py +1002 -0
- connector/sync_state.py +155 -0
- connector/time.py +7 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import hashlib
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CODEX_HISTORY_ITEM_TYPES = {
|
|
13
|
+
"message",
|
|
14
|
+
"reasoning",
|
|
15
|
+
"function_call",
|
|
16
|
+
"function_call_output",
|
|
17
|
+
"custom_tool_call",
|
|
18
|
+
"custom_tool_call_output",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(slots=True)
|
|
23
|
+
class CodexHistoryItem:
|
|
24
|
+
turn_id: str | None
|
|
25
|
+
item: dict[str, Any]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_timeline_history(
|
|
29
|
+
thread_id: str,
|
|
30
|
+
*,
|
|
31
|
+
rollout_path: str | Path | None = None,
|
|
32
|
+
sessions_root: Path | None = None,
|
|
33
|
+
) -> list[CodexHistoryItem]:
|
|
34
|
+
path = _usable_rollout_path(rollout_path)
|
|
35
|
+
if path is None:
|
|
36
|
+
path = find_rollout_path(thread_id, sessions_root=sessions_root)
|
|
37
|
+
if path is None:
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
items: list[CodexHistoryItem] = []
|
|
41
|
+
current_turn_id: str | None = None
|
|
42
|
+
try:
|
|
43
|
+
with path.open("r", encoding="utf-8", errors="ignore") as file:
|
|
44
|
+
for line_no, line in enumerate(file, start=1):
|
|
45
|
+
try:
|
|
46
|
+
record = json.loads(line)
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
logger.trace("codex history line is not json path={} line={}", path, line_no)
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
record_type = record.get("type")
|
|
52
|
+
payload = record.get("payload")
|
|
53
|
+
if isinstance(payload, dict):
|
|
54
|
+
turn_id = _turn_id_from_record(record_type, payload)
|
|
55
|
+
if turn_id:
|
|
56
|
+
current_turn_id = turn_id
|
|
57
|
+
|
|
58
|
+
if record_type != "response_item" or not isinstance(payload, dict):
|
|
59
|
+
event_item = _event_item(record_type, payload)
|
|
60
|
+
if event_item is not None:
|
|
61
|
+
event_item["_historyLine"] = line_no
|
|
62
|
+
items.append(CodexHistoryItem(turn_id=current_turn_id, item=event_item))
|
|
63
|
+
continue
|
|
64
|
+
item = _response_item(payload)
|
|
65
|
+
if item is None:
|
|
66
|
+
continue
|
|
67
|
+
item = dict(item)
|
|
68
|
+
item["_historyLine"] = line_no
|
|
69
|
+
items.append(CodexHistoryItem(turn_id=current_turn_id, item=item))
|
|
70
|
+
except OSError as exc:
|
|
71
|
+
logger.warning("failed to read codex history path={} error={}", path, exc)
|
|
72
|
+
return []
|
|
73
|
+
return items
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_tool_history(
|
|
77
|
+
thread_id: str,
|
|
78
|
+
*,
|
|
79
|
+
rollout_path: str | Path | None = None,
|
|
80
|
+
sessions_root: Path | None = None,
|
|
81
|
+
) -> list[CodexHistoryItem]:
|
|
82
|
+
return [
|
|
83
|
+
entry
|
|
84
|
+
for entry in read_timeline_history(thread_id, rollout_path=rollout_path, sessions_root=sessions_root)
|
|
85
|
+
if entry.item.get("type") in {"function_call", "function_call_output", "custom_tool_call", "custom_tool_call_output"}
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _usable_rollout_path(value: str | Path | None) -> Path | None:
|
|
90
|
+
if value is None:
|
|
91
|
+
return None
|
|
92
|
+
path = Path(value).expanduser()
|
|
93
|
+
if path.is_file():
|
|
94
|
+
return path
|
|
95
|
+
logger.trace("codex rollout path is not readable path={}", path)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def find_rollout_path(thread_id: str, *, sessions_root: Path | None = None) -> Path | None:
|
|
100
|
+
root = sessions_root or Path.home() / ".codex" / "sessions"
|
|
101
|
+
if not root.exists():
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
matches = sorted(root.rglob(f"rollout-*{thread_id}.jsonl"), key=lambda path: path.stat().st_mtime, reverse=True)
|
|
105
|
+
if matches:
|
|
106
|
+
return matches[0]
|
|
107
|
+
|
|
108
|
+
# Older or hand-written fixtures may not include the thread id in the filename.
|
|
109
|
+
for path in sorted(root.rglob("*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True):
|
|
110
|
+
if _jsonl_has_session_id(path, thread_id):
|
|
111
|
+
return path
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _jsonl_has_session_id(path: Path, thread_id: str) -> bool:
|
|
116
|
+
try:
|
|
117
|
+
with path.open("r", encoding="utf-8", errors="ignore") as file:
|
|
118
|
+
for line in file:
|
|
119
|
+
try:
|
|
120
|
+
record = json.loads(line)
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
continue
|
|
123
|
+
if record.get("type") != "session_meta":
|
|
124
|
+
continue
|
|
125
|
+
payload = record.get("payload")
|
|
126
|
+
return isinstance(payload, dict) and payload.get("id") == thread_id
|
|
127
|
+
except OSError:
|
|
128
|
+
return False
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _turn_id_from_record(record_type: Any, payload: dict[str, Any]) -> str | None:
|
|
133
|
+
if record_type == "turn_context" and isinstance(payload.get("turn_id"), str):
|
|
134
|
+
return payload["turn_id"]
|
|
135
|
+
if record_type == "event_msg" and payload.get("type") == "task_started" and isinstance(payload.get("turn_id"), str):
|
|
136
|
+
return payload["turn_id"]
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _response_item(payload: dict[str, Any]) -> dict[str, Any] | None:
|
|
141
|
+
item_type = payload.get("type")
|
|
142
|
+
if item_type not in CODEX_HISTORY_ITEM_TYPES:
|
|
143
|
+
return None
|
|
144
|
+
if item_type == "message":
|
|
145
|
+
role = payload.get("role")
|
|
146
|
+
if role == "user":
|
|
147
|
+
return {**payload, "type": "userMessage"}
|
|
148
|
+
if role == "assistant":
|
|
149
|
+
return {**payload, "type": "agentMessage"}
|
|
150
|
+
return None
|
|
151
|
+
return dict(payload)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _event_item(record_type: Any, payload: dict[str, Any]) -> dict[str, Any] | None:
|
|
155
|
+
if record_type != "event_msg":
|
|
156
|
+
return None
|
|
157
|
+
event_type = payload.get("type")
|
|
158
|
+
if event_type == "task_started":
|
|
159
|
+
return {
|
|
160
|
+
"type": "turnStart",
|
|
161
|
+
"id": f"turn-start-{payload.get('turn_id')}",
|
|
162
|
+
"status": "running",
|
|
163
|
+
"_derivedKey": "turn-start",
|
|
164
|
+
}
|
|
165
|
+
if event_type in {"task_complete", "turn_aborted"}:
|
|
166
|
+
result = "interrupted" if event_type == "turn_aborted" else "completed"
|
|
167
|
+
return {
|
|
168
|
+
"type": "turnEnd",
|
|
169
|
+
"id": f"turn-end-{payload.get('turn_id')}",
|
|
170
|
+
"status": result,
|
|
171
|
+
"result": result,
|
|
172
|
+
"error": {"message": payload.get("reason")} if event_type == "turn_aborted" else None,
|
|
173
|
+
"_derivedKey": "turn-end",
|
|
174
|
+
}
|
|
175
|
+
if event_type == "patch_apply_end":
|
|
176
|
+
changes = []
|
|
177
|
+
raw_changes = payload.get("changes")
|
|
178
|
+
if isinstance(raw_changes, dict):
|
|
179
|
+
changes = [
|
|
180
|
+
{"path": str(path), "action": str(change.get("type") or "change") if isinstance(change, dict) else "change"}
|
|
181
|
+
for path, change in raw_changes.items()
|
|
182
|
+
]
|
|
183
|
+
return {
|
|
184
|
+
"type": "fileChange",
|
|
185
|
+
"id": _string_value(payload.get("call_id")) or f"patch-{_short_event_key(payload)}",
|
|
186
|
+
"changes": changes,
|
|
187
|
+
"status": "completed" if payload.get("success") is True else "failed",
|
|
188
|
+
"_derivedKey": f"patch-{payload.get('call_id') or _short_event_key(payload)}",
|
|
189
|
+
}
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _short_event_key(payload: dict[str, Any]) -> str:
|
|
194
|
+
encoded = json.dumps(payload, sort_keys=True, default=str).encode("utf-8")
|
|
195
|
+
return hashlib.sha256(encoded).hexdigest()[:16]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _string_value(value: Any) -> str | None:
|
|
199
|
+
return value if isinstance(value, str) else None
|