574 lines
25 KiB
Python
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())
|