opencac 0.2.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.
- opencac/__init__.py +12 -0
- opencac/agents.py +18 -0
- opencac/audit.py +90 -0
- opencac/cli.py +166 -0
- opencac/cli_runtime.py +421 -0
- opencac/pipeline.py +124 -0
- opencac/roles.py +569 -0
- opencac/runtime.py +545 -0
- opencac/schemas.py +30 -0
- opencac/service.py +339 -0
- opencac/sidecar.py +108 -0
- opencac-0.2.0.dist-info/METADATA +189 -0
- opencac-0.2.0.dist-info/RECORD +17 -0
- opencac-0.2.0.dist-info/WHEEL +5 -0
- opencac-0.2.0.dist-info/entry_points.txt +2 -0
- opencac-0.2.0.dist-info/licenses/LICENSE +21 -0
- opencac-0.2.0.dist-info/top_level.txt +1 -0
opencac/__init__.py
ADDED
opencac/agents.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .pipeline import resume_pipeline, run_pipeline
|
|
4
|
+
from .roles import Antigravity, ClaudeCodePlanner, CodexExecutor
|
|
5
|
+
from .runtime import InferenceConfig, RoutingConfig, Sidecar, ensure_private_runtime, make_envelope
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Antigravity",
|
|
9
|
+
"ClaudeCodePlanner",
|
|
10
|
+
"CodexExecutor",
|
|
11
|
+
"InferenceConfig",
|
|
12
|
+
"RoutingConfig",
|
|
13
|
+
"Sidecar",
|
|
14
|
+
"ensure_private_runtime",
|
|
15
|
+
"make_envelope",
|
|
16
|
+
"run_pipeline",
|
|
17
|
+
"resume_pipeline",
|
|
18
|
+
]
|
opencac/audit.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def utc_now() -> str:
|
|
12
|
+
return datetime.now(timezone.utc).isoformat()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AuditLog:
|
|
17
|
+
path: Path
|
|
18
|
+
|
|
19
|
+
def __post_init__(self) -> None:
|
|
20
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
self._io_lock = threading.Lock()
|
|
22
|
+
self._session_offsets: Dict[str, List[int]] = {}
|
|
23
|
+
self._all_offsets: List[int] = []
|
|
24
|
+
self._indexed_size = 0
|
|
25
|
+
|
|
26
|
+
def append(self, event: Dict[str, Any]) -> Dict[str, Any]:
|
|
27
|
+
enriched = {"ts": utc_now(), **event}
|
|
28
|
+
encoded = (json.dumps(enriched, ensure_ascii=False) + "\n").encode("utf-8")
|
|
29
|
+
with self._io_lock:
|
|
30
|
+
offset = self.path.stat().st_size if self.path.exists() else 0
|
|
31
|
+
with self.path.open("ab") as handle:
|
|
32
|
+
handle.write(encoded)
|
|
33
|
+
self._all_offsets.append(offset)
|
|
34
|
+
session_id = enriched.get("session_id")
|
|
35
|
+
if isinstance(session_id, str):
|
|
36
|
+
self._session_offsets.setdefault(session_id, []).append(offset)
|
|
37
|
+
self._indexed_size = offset + len(encoded)
|
|
38
|
+
return enriched
|
|
39
|
+
|
|
40
|
+
def read(self, session_id: Optional[str] = None, last: int = 20) -> List[Dict[str, Any]]:
|
|
41
|
+
if not self.path.exists():
|
|
42
|
+
return []
|
|
43
|
+
with self._io_lock:
|
|
44
|
+
self._ensure_index_locked()
|
|
45
|
+
if session_id is None:
|
|
46
|
+
offsets = self._all_offsets[-last:]
|
|
47
|
+
else:
|
|
48
|
+
offsets = self._session_offsets.get(session_id, [])[-last:]
|
|
49
|
+
return self._read_offsets_locked(offsets)
|
|
50
|
+
|
|
51
|
+
def _ensure_index_locked(self) -> None:
|
|
52
|
+
if not self.path.exists():
|
|
53
|
+
self._session_offsets = {}
|
|
54
|
+
self._all_offsets = []
|
|
55
|
+
self._indexed_size = 0
|
|
56
|
+
return
|
|
57
|
+
current_size = self.path.stat().st_size
|
|
58
|
+
if current_size < self._indexed_size:
|
|
59
|
+
self._session_offsets = {}
|
|
60
|
+
self._all_offsets = []
|
|
61
|
+
self._indexed_size = 0
|
|
62
|
+
if current_size == self._indexed_size:
|
|
63
|
+
return
|
|
64
|
+
with self.path.open("rb") as handle:
|
|
65
|
+
handle.seek(self._indexed_size)
|
|
66
|
+
while True:
|
|
67
|
+
offset = handle.tell()
|
|
68
|
+
line = handle.readline()
|
|
69
|
+
if not line:
|
|
70
|
+
break
|
|
71
|
+
try:
|
|
72
|
+
item = json.loads(line.decode("utf-8"))
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
continue
|
|
75
|
+
self._all_offsets.append(offset)
|
|
76
|
+
session_id = item.get("session_id")
|
|
77
|
+
if isinstance(session_id, str):
|
|
78
|
+
self._session_offsets.setdefault(session_id, []).append(offset)
|
|
79
|
+
self._indexed_size = handle.tell()
|
|
80
|
+
|
|
81
|
+
def _read_offsets_locked(self, offsets: List[int]) -> List[Dict[str, Any]]:
|
|
82
|
+
entries: List[Dict[str, Any]] = []
|
|
83
|
+
with self.path.open("rb") as handle:
|
|
84
|
+
for offset in offsets:
|
|
85
|
+
handle.seek(offset)
|
|
86
|
+
line = handle.readline()
|
|
87
|
+
if not line:
|
|
88
|
+
continue
|
|
89
|
+
entries.append(json.loads(line.decode("utf-8")))
|
|
90
|
+
return entries
|
opencac/cli.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from .agents import InferenceConfig, Sidecar, resume_pipeline
|
|
10
|
+
from .audit import AuditLog
|
|
11
|
+
from .service import serve
|
|
12
|
+
|
|
13
|
+
from .cli_runtime import InteractiveState, _http_get, _http_post, _question_needs_research, _run_task_once, run_interactive
|
|
14
|
+
|
|
15
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="opencac", description="OpenCAC CLI")
|
|
17
|
+
sub = parser.add_subparsers(dest="command", required=False)
|
|
18
|
+
|
|
19
|
+
run_parser = sub.add_parser("run", help="dispatch a natural-language task into the OpenCAC pipeline")
|
|
20
|
+
run_parser.add_argument("prompt", help="natural language instruction")
|
|
21
|
+
run_parser.add_argument("--mode", choices=["cloud", "private"], default="cloud")
|
|
22
|
+
run_parser.add_argument("--workspace", default=".", help="artifact root")
|
|
23
|
+
run_parser.add_argument("--audit", default=".opencac/audit.jsonl", help="audit JSONL path")
|
|
24
|
+
run_parser.add_argument("--distributed", action="store_true", help="route run through the local OpenCAC HTTP service")
|
|
25
|
+
run_parser.add_argument("--async-run", action="store_true", help="return immediately and continue distributed processing in the background")
|
|
26
|
+
run_parser.add_argument("--base-url", default="http://127.0.0.1:8000", help="OpenCAC service base URL for distributed mode")
|
|
27
|
+
run_parser.add_argument("--callback-url", help="reverse POST endpoint for rejection or execution result callbacks")
|
|
28
|
+
run_parser.add_argument("--model", default="gpt-oss:20b")
|
|
29
|
+
run_parser.add_argument("--speculative-mode", choices=["auto", "draft-model", "self-speculative"], default="auto")
|
|
30
|
+
run_parser.add_argument("--draft-model")
|
|
31
|
+
run_parser.add_argument("--spec-type", choices=["none", "ngram-cache", "ngram-simple", "ngram-map-k", "ngram-map-k4v", "ngram-mod"], default="ngram-simple")
|
|
32
|
+
run_parser.add_argument("--draft-max", type=int, default=64)
|
|
33
|
+
run_parser.add_argument("--draft-min", type=int, default=16)
|
|
34
|
+
run_parser.add_argument("--spec-ngram-size-n", type=int, default=12)
|
|
35
|
+
run_parser.add_argument("--spec-ngram-size-m", type=int, default=48)
|
|
36
|
+
run_parser.add_argument("--spec-ngram-min-hits", type=int, default=1)
|
|
37
|
+
|
|
38
|
+
audit_parser = sub.add_parser("audit", help="show recent audit entries")
|
|
39
|
+
audit_parser.add_argument("--audit", default=".opencac/audit.jsonl", help="audit JSONL path")
|
|
40
|
+
audit_parser.add_argument("--session-id")
|
|
41
|
+
audit_parser.add_argument("--last", type=int, default=20)
|
|
42
|
+
|
|
43
|
+
resume_parser = sub.add_parser("resume", help="resume a session from JSONL audit")
|
|
44
|
+
resume_parser.add_argument("session_id")
|
|
45
|
+
resume_parser.add_argument("--workspace", default=".", help="artifact root")
|
|
46
|
+
resume_parser.add_argument("--audit", default=".opencac/audit.jsonl", help="audit JSONL path")
|
|
47
|
+
|
|
48
|
+
sidecar_parser = sub.add_parser("sidecar-check", help="validate a JSON message through the sidecar")
|
|
49
|
+
sidecar_parser.add_argument("message", help="raw JSON string")
|
|
50
|
+
sidecar_parser.add_argument("--audit", default=".opencac/audit.jsonl", help="audit JSONL path")
|
|
51
|
+
|
|
52
|
+
discover_parser = sub.add_parser("discover", help="fetch the local agent card from an OpenCAC service")
|
|
53
|
+
discover_parser.add_argument("--base-url", default="http://127.0.0.1:8000")
|
|
54
|
+
|
|
55
|
+
task_get_parser = sub.add_parser("task-get", help="fetch task status from an OpenCAC service")
|
|
56
|
+
task_get_parser.add_argument("session_id")
|
|
57
|
+
task_get_parser.add_argument("--base-url", default="http://127.0.0.1:8000")
|
|
58
|
+
|
|
59
|
+
send_parser = sub.add_parser("send", help="send a protocol message to an agent endpoint")
|
|
60
|
+
send_parser.add_argument("agent_id", choices=["antigravity", "claude-code", "codex"])
|
|
61
|
+
send_parser.add_argument("message", help="raw JSON message envelope")
|
|
62
|
+
send_parser.add_argument("--base-url", default="http://127.0.0.1:8000")
|
|
63
|
+
send_parser.add_argument("--execute", action="store_true")
|
|
64
|
+
|
|
65
|
+
serve_parser = sub.add_parser("serve", help="start the OpenCAC HTTP service")
|
|
66
|
+
serve_parser.add_argument("--host", default="127.0.0.1")
|
|
67
|
+
serve_parser.add_argument("--port", type=int, default=8000)
|
|
68
|
+
serve_parser.add_argument("--workspace", default=".", help="artifact root")
|
|
69
|
+
serve_parser.add_argument("--audit", default=".opencac/audit.jsonl", help="audit JSONL path")
|
|
70
|
+
|
|
71
|
+
sub.add_parser("interactive", help="start interactive CLI mode")
|
|
72
|
+
return parser
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
76
|
+
parser = build_parser()
|
|
77
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
78
|
+
|
|
79
|
+
if args.command in {None, "interactive"}:
|
|
80
|
+
raise SystemExit(run_interactive(stdin=sys.stdin, stdout=sys.stdout))
|
|
81
|
+
|
|
82
|
+
if args.command == "run":
|
|
83
|
+
inference = InferenceConfig(
|
|
84
|
+
engine="llama.cpp",
|
|
85
|
+
model=args.model,
|
|
86
|
+
speculative_mode=args.speculative_mode,
|
|
87
|
+
draft_model=args.draft_model,
|
|
88
|
+
spec_type=args.spec_type,
|
|
89
|
+
draft_max=args.draft_max,
|
|
90
|
+
draft_min=args.draft_min,
|
|
91
|
+
spec_ngram_size_n=args.spec_ngram_size_n,
|
|
92
|
+
spec_ngram_size_m=args.spec_ngram_size_m,
|
|
93
|
+
spec_ngram_min_hits=args.spec_ngram_min_hits,
|
|
94
|
+
)
|
|
95
|
+
result = _run_task_once(
|
|
96
|
+
args.prompt,
|
|
97
|
+
mode=args.mode,
|
|
98
|
+
workspace_arg=args.workspace,
|
|
99
|
+
audit_arg=args.audit,
|
|
100
|
+
inference=inference,
|
|
101
|
+
distributed=args.distributed,
|
|
102
|
+
async_run=args.async_run,
|
|
103
|
+
base_url=args.base_url,
|
|
104
|
+
callback_url=args.callback_url,
|
|
105
|
+
)
|
|
106
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
if args.command == "audit":
|
|
110
|
+
audit = AuditLog(Path(args.audit).resolve())
|
|
111
|
+
print(json.dumps(audit.read(session_id=args.session_id, last=args.last), ensure_ascii=False, indent=2))
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if args.command == "resume":
|
|
115
|
+
workspace = Path(args.workspace).resolve()
|
|
116
|
+
audit = AuditLog((workspace / args.audit).resolve())
|
|
117
|
+
result = resume_pipeline(session_id=args.session_id, workspace=workspace, audit=audit)
|
|
118
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
if args.command == "sidecar-check":
|
|
122
|
+
audit = AuditLog(Path(args.audit).resolve())
|
|
123
|
+
sidecar = Sidecar(audit)
|
|
124
|
+
try:
|
|
125
|
+
message = json.loads(args.message)
|
|
126
|
+
print(json.dumps(sidecar.forward(message), ensure_ascii=False, indent=2))
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
session_id = "sidecar-check"
|
|
129
|
+
record = sidecar.reject(
|
|
130
|
+
args.message,
|
|
131
|
+
from_agent="dispatcher",
|
|
132
|
+
to_agent="sidecar",
|
|
133
|
+
session_id=session_id,
|
|
134
|
+
reason=str(exc),
|
|
135
|
+
)
|
|
136
|
+
print(json.dumps(record, ensure_ascii=False, indent=2))
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
if args.command == "discover":
|
|
140
|
+
print(json.dumps(_http_get(f"{args.base_url.rstrip('/')}/.well-known/agent.json"), ensure_ascii=False, indent=2))
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if args.command == "task-get":
|
|
144
|
+
print(json.dumps(_http_get(f"{args.base_url.rstrip('/')}/tasks/{args.session_id}"), ensure_ascii=False, indent=2))
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if args.command == "send":
|
|
148
|
+
message = json.loads(args.message)
|
|
149
|
+
suffix = "?execute=1" if args.execute else ""
|
|
150
|
+
print(
|
|
151
|
+
json.dumps(
|
|
152
|
+
_http_post(f"{args.base_url.rstrip('/')}/agents/{args.agent_id}/message/send{suffix}", {"message": message}),
|
|
153
|
+
ensure_ascii=False,
|
|
154
|
+
indent=2,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if args.command == "serve":
|
|
160
|
+
workspace = Path(args.workspace).resolve()
|
|
161
|
+
audit = AuditLog((workspace / args.audit).resolve())
|
|
162
|
+
serve(host=args.host, port=args.port, workspace=workspace, audit=audit)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|