Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/native_speech_mux.py
T

574 lines
25 KiB
Python

"""Owned native Codex stdio adapter; inert until explicitly launched by an owner.
Only a trusted in-process root adapter may install Binding objects. There is no
HTTP bind/RPC endpoint, tenant inference, or activated SSE subscription.
appendSpeech acceptance is not proof of audible speech or history persistence.
"""
from __future__ import annotations
import hmac
import hashlib
import json
import queue
import secrets
import subprocess
import sys
import threading
import time
from dataclasses import dataclass
from pathlib import Path
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import BinaryIO, TypedDict, Callable
@dataclass(frozen=True)
class WatchReceipt:
"""INTERNAL adapter contract, not a claim about backend wire field names.
Backend resolver authenticates an opaque handle using the member credential
against a trusted, configured Omni endpoint. Backend secret stays server-side.
The resolver must verify tenant, source request thread and history together.
No URL or identity supplied by a notice is trusted as subscription authority.
"""
opaque_handle: str
tenant_id: str
bekci_session_id: str
history_revision: str
thread_id: str
expires_at: float
@dataclass(frozen=True)
class RequestCorrelation:
"""Captured by the owned HTTP bridge from the actual incoming request.
Must be captured before metadata arrives, not reconstructed from its body.
generation is the mux epoch observed at request capture, never the latest tab.
"""
connection_id: str
thread_id: str
generation: int
class WatchReceiptAdapter:
"""One consumer; root chooses transport/decoder after backend agreement.
decode extracts only the opaque handle from bekci.watch SSE data or the
nonstream _bekci_watch value. resolve performs authenticated backend lookup.
Missing agreement is fail-closed, not fabricated ready metadata.
"""
def __init__(self, mux, *, decode: Callable[[object], str] | None = None,
resolve: Callable[[str], WatchReceipt] | None = None):
self.mux, self.decode, self.resolve = mux, decode, resolve
self._accepted = {}
def capture(self, headers: dict, body: dict) -> RequestCorrelation | None:
normalized = {k.lower(): v for k, v in headers.items()}
values = [normalized.get('session-id'), normalized.get('thread-id'), body.get('prompt_cache_key')]
if not all(isinstance(v, str) and v for v in values) or len(set(values)) != 1:
return None
snap = self.mux.snapshot()
state = snap['threads'].get(values[0], {})
if snap['closed'] or not state.get('active'):
return None
return RequestCorrelation(snap['connection_id'], values[0], state['generation'])
def accept(self, transport: str, payload: object, correlation: RequestCorrelation | None) -> str:
if transport not in ('bekci.watch', '_bekci_watch'):
return 'unsupported_metadata_transport'
if self.decode is None or self.resolve is None:
return 'backend_contract_not_established'
if correlation is None or correlation.connection_id != self.mux.connection_id:
return 'request_correlation_missing'
try:
handle = self.decode(payload)
if not isinstance(handle, str) or not 1 <= len(handle) <= 4096:
return 'invalid_receipt'
receipt = self.resolve(handle)
except Exception:
return 'receipt_verification_failed' # never expose credential-bearing exception text
if not isinstance(receipt, WatchReceipt) or receipt.opaque_handle != handle:
return 'invalid_receipt'
if (receipt.thread_id != correlation.thread_id or type(receipt.expires_at) not in (int, float)
or not time.time() < receipt.expires_at <= time.time() + 3600
or not all(isinstance(v, str) and 0 < len(v) <= 256 for v in
(receipt.tenant_id, receipt.bekci_session_id, receipt.history_revision, receipt.thread_id))):
return 'receipt_mismatch_or_expired'
binding = Binding(receipt.tenant_id, receipt.bekci_session_id, receipt.history_revision,
correlation.connection_id, receipt.thread_id, correlation.generation)
previous = self._accepted.get(handle)
if previous is not None and previous != (receipt, binding):
return 'receipt_replay_mismatch'
if previous is None and len(self._accepted) >= 4096:
return 'capacity_exhausted'
if not self.mux.bind(binding):
return 'stale_generation'
self._accepted[handle] = (receipt, binding)
return 'bound'
def notice(self, handle: str, notice: Notice) -> dict:
accepted = self._accepted.get(handle)
if accepted is None:
return {'status': 'binding_not_established'}
receipt, binding = accepted
if time.time() >= receipt.expires_at:
return {'status': 'receipt_expired'}
expected = dict(tenant_id=binding.tenant_id, bekci_session_id=binding.bekci_session_id,
history_revision=binding.history_revision, thread_id=binding.thread_id,
connection_id=binding.connection_id, expected_generation=binding.generation)
if not isinstance(notice, dict) or any(notice.get(k) != v for k, v in expected.items()):
return {'status': 'binding_mismatch'}
return self.mux.submit(notice)
def verified_original(path: str, expected_sha256: str) -> str:
"""Owner-pinned absolute original; no PATH/override discovery or extra engine."""
candidate = Path(path)
if not candidate.is_absolute() or not candidate.is_file() or candidate.resolve() == Path(__file__).resolve():
raise ValueError('original executable must be an explicitly pinned absolute file')
if len(expected_sha256) != 64:
raise ValueError('original executable hash required')
with candidate.open('rb') as stream:
actual = hashlib.file_digest(stream, 'sha256').hexdigest()
if not hmac.compare_digest(actual, expected_sha256.lower()):
raise ValueError('original executable hash mismatch')
return str(candidate.resolve())
def stdio_invocation(args: list[str]) -> bool:
"""Installed Desktop tc()/Ks() grammar, preserving every original argument."""
seen, i = False, 0
while i < len(args):
arg = args[i]
if arg in ('-c', '--config') and i + 1 < len(args):
i += 2
continue
if arg == 'app-server' and not seen:
seen = True
elif seen and arg in ('--stdio', '--analytics-default-enabled'):
pass
elif seen and arg == '--listen' and args[i+1:i+2] == ['stdio://']:
i += 1
else:
return False
i += 1
return seen
def launch_original(path: str, expected_sha256: str, args: list[str], *,
attach: Callable[[WatchReceiptAdapter], None] | None = None) -> int:
"""Source launcher entry for a future normal bridge module copy.
No activation/config mutation. Unknown invocation grammar passes native stdio
directly. Owner callback wires only this consumer after backend agreement.
No second engine, binary patch, token file or discovery of current UI tab.
"""
original = verified_original(path, expected_sha256)
observer = stdio_invocation(args)
mux = NativeSpeechMux([original, *args], observer=observer)
if observer and attach is not None:
try:
attach(WatchReceiptAdapter(mux))
except Exception:
# Optional adapter failure cannot take down native stdio. Invalidate
# any partial setup so no correction can escape a failed attachment.
mux._disabled = True
mux._bindings.clear()
return mux.run()
class Notice(TypedDict):
notice_id: str
tenant_id: str
bekci_session_id: str
history_revision: str
connection_id: str
thread_id: str
expected_generation: int
issued_at: float
expires_at: float
text: str
@dataclass(frozen=True)
class Binding:
"""Local scope labels (legacy field names), NOT literal server identity claims.
Trusted owner attests this opaque receipt scope belongs to this native epoch.
Install only from independently verified mapping, NEVER from notice fields.
Rebind explicitly for a new history revision or realtime generation.
"""
tenant_id: str
bekci_session_id: str
history_revision: str
connection_id: str
thread_id: str
generation: int
def _object(raw: bytes) -> dict:
try:
value = json.loads(raw)
return value if isinstance(value, dict) else {}
except (ValueError, UnicodeError):
return {}
def _id(value):
return (type(value), value) if type(value) in (str, int) else None
class NativeSpeechMux:
"""One original process, bounded FIFO, one writer in each direction.
argv is the original executable plus its original arguments. observer=True
must only be selected for a verified stdio app-server invocation. The CLI
deliberately recognizes only the exact ['app-server'] invocation; all other
invocations inherit the original stdio directly. No environment/cwd rewrite.
"""
def __init__(self, argv: list[str], *, observer: bool = False):
self.argv = list(argv)
self.observer = observer
self.connection_id = secrets.token_hex(24)
self._prefix = 'bekci-speech-' + secrets.token_hex(24) + ':'
self._token = secrets.token_urlsafe(32)
self._lock = threading.RLock()
self._queue = queue.Queue(maxsize=32)
self._ended = threading.Event()
self._eof = False
self._disabled = False
self._threads = {}
self._starts = set()
self._bindings = {}
self._receipts = {}
self._pending = {}
self._counter = 0
self._server = None
self._process = None
def snapshot(self) -> dict:
with self._lock:
return {'connection_id': self.connection_id,
'binding_status': 'established' if self._bindings else 'binding_not_established',
'threads': {k: dict(v) for k, v in self._threads.items()},
'closed': self._eof or self._ended.is_set(),
'injection_disabled': self._disabled}
def bind(self, binding: Binding) -> bool:
"""Trusted local API only. No untrusted endpoint can invoke this."""
with self._lock:
st = self._threads.get(binding.thread_id, {})
if (binding.connection_id != self.connection_id or not st.get('active')
or st.get('generation') != binding.generation or self._eof
or self._ended.is_set()):
return False
self._bindings[(binding.tenant_id, binding.bekci_session_id)] = binding
return True
def _invalidate(self, thread):
if not isinstance(thread, str):
return
if thread in self._threads:
self._threads[thread]['active'] = False
self._bindings = {k: b for k, b in self._bindings.items() if b.thread_id != thread}
def _observe_client(self, obj):
with self._lock:
rid = _id(obj.get('id'))
# Responses to server requests may legitimately reuse our ID.
if 'method' in obj and isinstance(obj.get('id'), str) and obj['id'].startswith(self._prefix):
self._disabled = True
self._pending.pop(obj['id'], None)
if obj.get('method') in ('thread/start', 'thread/resume', 'thread/fork') and rid:
if len(self._starts) < 4096:
self._starts.add(rid)
else:
self._disabled = True
if obj.get('method') in ('thread/realtime/stop', 'thread/realtime/start', 'thread/unsubscribe'):
params = obj.get('params')
if isinstance(params, dict):
self._invalidate(params.get('threadId'))
def _observe_server(self, obj) -> bool:
with self._lock:
rid = obj.get('id')
response = 'method' not in obj and (('result' in obj) != ('error' in obj))
if response and isinstance(rid, str) and rid in self._pending:
key = self._pending.pop(rid)
self._receipts[key]['status'] = 'rpc_error' if 'error' in obj else 'rpc_accepted'
return True
if response and _id(rid) in self._starts:
self._starts.discard(_id(rid))
result = obj.get('result')
thread = result.get('thread', {}) if isinstance(result, dict) else {}
tid = thread.get('id') if isinstance(thread, dict) else None
if isinstance(tid, str):
self._threads.setdefault(tid, {'generation': 0, 'active': False, 'realtime_session_id': None})
params = obj.get('params')
if not isinstance(params, dict) or 'id' in obj:
return False
tid = params.get('threadId')
if not isinstance(tid, str):
return False
method = obj.get('method')
if method == 'thread/realtime/started':
self._invalidate(tid)
previous = self._threads.get(tid, {}).get('generation', 0)
self._threads[tid] = {'generation': previous + 1, 'active': True,
'realtime_session_id': params.get('realtimeSessionId')}
elif method in ('thread/realtime/closed', 'thread/realtime/error'):
self._invalidate(tid)
return False
def _validate(self, notice):
if not isinstance(notice, dict) or set(notice) != set(Notice.__annotations__):
return 'invalid_schema'
for key in Notice.__annotations__:
if key not in ('expected_generation', 'issued_at', 'expires_at'):
if not isinstance(notice[key], str) or not notice[key].strip() or len(notice[key]) > (8000 if key == 'text' else 256):
return 'invalid_schema'
if type(notice['expected_generation']) is not int or notice['expected_generation'] < 1:
return 'invalid_schema'
if any(type(notice[k]) not in (int, float) for k in ('issued_at', 'expires_at')):
return 'invalid_schema'
now = time.time()
if not (now - 60 <= notice['issued_at'] <= now + 2 and now < notice['expires_at'] <= notice['issued_at'] + 60):
return 'expired_or_invalid_ttl'
if self._eof or self._ended.is_set():
return 'closed'
if self._disabled or not self.observer:
return 'injection_disabled'
binding = self._bindings.get((notice['tenant_id'], notice['bekci_session_id']))
if binding is None:
return 'binding_not_established'
if binding != Binding(notice['tenant_id'], notice['bekci_session_id'], notice['history_revision'],
notice['connection_id'], notice['thread_id'], notice['expected_generation']):
return 'binding_mismatch'
st = self._threads.get(binding.thread_id, {})
if not st.get('active') or st.get('generation') != binding.generation:
return 'stale_generation'
return None
def submit(self, notice: Notice) -> dict:
with self._lock:
error = self._validate(notice)
if error:
return {'status': error}
key = (notice['tenant_id'], notice['bekci_session_id'], notice['notice_id'])
if key in self._receipts:
return dict(self._receipts[key])
# Never evict dedupe during this process lifetime or retry uncertain sends.
if len(self._receipts) >= 4096:
return {'status': 'capacity_exhausted'}
receipt = {'status': 'queued'}
self._receipts[key] = receipt
try:
self._queue.put_nowait(('notice', (dict(notice), key)))
except queue.Full:
del self._receipts[key]
return {'status': 'busy'}
return dict(receipt)
def start_inlet(self) -> tuple[str, str]:
"""Return loopback URL and secret to trusted owner IN MEMORY; never log.
One bounded request at a time; 2s timeout applies only to inlet HTTP reads,
never the original engine. Owner must arrange private credential delivery.
"""
if not self.observer or self._server is not None:
raise ValueError('inlet requires an unused app-server observer')
mux = self
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def setup(self):
super().setup()
self.connection.settimeout(2)
def do_POST(self):
try:
auth = self.headers.get('Authorization', '')
if not hmac.compare_digest(auth.encode(), ('Bearer ' + mux._token).encode()):
self.send_error(401)
return
if self.headers.get('Origin') or self.headers.get('Transfer-Encoding'):
self.send_error(400)
return
lengths = self.headers.get_all('Content-Length', [])
if len(lengths) != 1 or not lengths[0].isdigit() or not 0 < int(lengths[0]) <= 65536:
self.send_error(400)
return
raw = self.rfile.read(int(lengths[0]))
if len(raw) != int(lengths[0]):
self.send_error(400)
return
if self.path == '/notice':
result = mux.submit(_object(raw))
elif self.path == '/status' and _object(raw) == {}:
result = mux.snapshot()
else:
self.send_error(404)
return
body = json.dumps(result).encode()
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
except (OSError, ValueError):
return
self._server = HTTPServer(('127.0.0.1', 0), Handler)
threading.Thread(target=self._server.serve_forever, daemon=True).start()
return f'http://127.0.0.1:{self._server.server_port}', self._token
def _put(self, item):
while not self._ended.is_set():
try:
self._queue.put(item, timeout=.1)
return True
except queue.Full:
pass
return False
def run(self, source: BinaryIO | None = None, target: BinaryIO | None = None) -> int:
if not self.observer:
return subprocess.call(self.argv) # exact inherited stdin/stdout/stderr/env/cwd
source = source if source is not None else sys.stdin.buffer
target = target if target is not None else sys.stdout.buffer
process = self._process = subprocess.Popen(self.argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def read_client():
fragment = False
while not self._ended.is_set():
raw = source.readline(65536)
if not raw:
with self._lock:
self._eof = True
self._put(('eof', None))
return
complete = raw.endswith(b'\n')
if not fragment and complete:
self._observe_client(_object(raw))
elif not complete:
# Oversized records still forward byte-for-byte, but cannot be
# safely observed. Fail closed for injection for this connection.
with self._lock:
self._disabled = True
fragment = not complete
if not self._put(('raw', raw)):
return
def send_notice(value):
notice, key = value
with self._lock:
error = self._validate(notice)
if error:
self._receipts[key]['status'] = error
return
self._counter += 1
rid = self._prefix + str(self._counter)
self._pending[rid] = key
self._receipts[key]['status'] = 'sent_unknown'
raw = json.dumps({'id': rid, 'method': 'thread/realtime/appendSpeech',
'params': {'threadId': notice['thread_id'], 'text': notice['text']}},
ensure_ascii=True).encode() + b'\n'
# No lock during blocking pipe I/O: observer must drain server output.
# Protocol has no server-side generation precondition; this race remains.
process.stdin.write(raw)
process.stdin.flush()
def write_server():
partial = False
try:
while not self._ended.is_set():
try:
kind, value = self._queue.get(timeout=.1)
except queue.Empty:
continue
if kind == 'eof':
return
if kind == 'notice':
if partial:
# An oversized frame has disabled injection; reject now.
with self._lock:
self._receipts[value[1]]['status'] = 'injection_disabled'
else:
send_notice(value)
else:
# Re-apply lifecycle invalidation at the actual write boundary
# too: a prior server start may have arrived while queued.
if not partial and value.endswith(b'\n'):
obj = _object(value)
if obj.get('method') in ('thread/realtime/start', 'thread/realtime/stop'):
params = obj.get('params')
if isinstance(params, dict):
with self._lock:
self._invalidate(params.get('threadId'))
process.stdin.write(value)
process.stdin.flush()
partial = not value.endswith(b'\n')
except (OSError, ValueError):
pass
finally:
try:
process.stdin.close()
except OSError:
pass
reader = threading.Thread(target=read_client, daemon=True)
writer = threading.Thread(target=write_server, daemon=True)
reader.start()
writer.start()
fragment = False
try:
while True:
raw = process.stdout.readline(65536)
if not raw:
break
complete = raw.endswith(b'\n')
consumed = not fragment and complete and self._observe_server(_object(raw))
if not complete:
with self._lock:
self._disabled = True
fragment = not complete
if not consumed:
target.write(raw)
target.flush()
finally:
self._ended.set()
process.stdout.close()
with self._lock:
self._bindings.clear()
for st in self._threads.values():
st['active'] = False
for receipt in self._receipts.values():
if receipt['status'] == 'queued':
receipt['status'] = 'closed'
if self._server:
self._server.shutdown()
self._server.server_close()
return process.wait() # no deadline on legitimate native work
def main() -> int:
# Future launcher must pin/verify the original executable, never resolve the
# override recursively. No CODEX_CLI_PATH configuration is performed here.
if len(sys.argv) >= 5 and sys.argv[1] == '--pinned-original' and sys.argv[4] == '--':
try:
return launch_original(sys.argv[2], sys.argv[3], sys.argv[5:])
except ValueError:
print('original executable verification failed', file=sys.stderr)
return 2
if len(sys.argv) < 3 or sys.argv[2] != '--':
print('usage: native_speech_mux.py --pinned-original ORIGINAL_EXECUTABLE SHA256 -- ORIGINAL_ARGS...', file=sys.stderr)
return 2
argv = [sys.argv[1], *sys.argv[3:]]
return NativeSpeechMux(argv, observer=argv[1:] == ['app-server']).run()
if __name__ == '__main__':
raise SystemExit(main())