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 ADDED
@@ -0,0 +1,12 @@
1
+ __all__ = [
2
+ "agents",
3
+ "audit",
4
+ "cli",
5
+ "cli_runtime",
6
+ "pipeline",
7
+ "roles",
8
+ "runtime",
9
+ "schemas",
10
+ "service",
11
+ "sidecar",
12
+ ]
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()