unsloth/studio/backend/core/inference/codex_spoof.py
2026-05-27 13:31:21 +00:00

271 lines
9.3 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Local-only Codex SDK spoof for credit-free dev / CI runs.
Activated by ``UNSLOTH_CODEX_SPOOF=1`` -- the gate in ``codex_provider``
swaps in this module's symbols for ``openai_codex`` so the rest of the
provider can run end-to-end (thread_start, turn().stream(), run_streaming,
run(), AppServerConfig, ApprovalMode.deny_all, SandboxMode.read_only)
without ever touching the real CLI or upstream API.
The fake stream emits one ``message.delta`` per visible token plus a
trailing ``ItemCompletedNotification(item=agentMessage)`` so both the
delta path and the completion-only fallback in
``_stream_thread_run`` exercise their real branches.
The replies are deterministic and tagged with the model + tab index so
the parallel-calls fan-out shows visibly distinct text per tab, which
is the point of the tab UI demo. The spoof intentionally does NOT
emit command / file / tool deltas -- those would be denylisted by
``_coerce_text`` and never reach the user, and we want the demo to
show the same shape Codex normally streams: pure agent text.
This file is import-safe: it has no side effects on import. It MUST
never be selected unless the env flag is set explicitly.
"""
from __future__ import annotations
import asyncio
import os
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, AsyncIterator, Optional
SPOOF_ENV_VAR = "UNSLOTH_CODEX_SPOOF"
def is_spoof_enabled() -> bool:
"""Return True when the env flag is set to an explicit truthy value."""
return os.environ.get(SPOOF_ENV_VAR, "").strip().lower() in (
"1",
"true",
"yes",
"on",
)
class ApprovalMode(str, Enum):
deny_all = "deny_all"
auto_review = "auto_review"
class SandboxMode(str, Enum):
read_only = "read_only"
workspace_write = "workspace_write"
@dataclass
class AppServerConfig:
env: Optional[dict[str, str]] = None
codex_bin: Optional[str] = None
extra: dict[str, Any] = field(default_factory = dict)
@dataclass
class _AgentMessage:
type: str = "agentMessage"
text: str = ""
@dataclass
class _ItemRoot:
root: _AgentMessage
@dataclass
class _ItemCompletedNotification:
"""Mirrors openai_codex.api.ItemCompletedNotification shape.
The class name is matched verbatim by ``_completed_agent_message_text``
so the completion-only fallback in ``_stream_thread_run`` recognises
these payloads.
"""
item: _ItemRoot
type: str = "ItemCompletedNotification"
def _tab_id_from_system(system: Optional[str]) -> int:
"""Pull the synthetic ``[tab N]`` marker the provider prepends to
each parallel worker's system prompt (when present), else 0."""
if not system:
return 0
for line in system.splitlines():
line = line.strip()
if line.startswith("[tab ") and line.endswith("]"):
try:
return int(line[len("[tab ") : -1].split("/")[0])
except ValueError:
pass
return 0
def _spoof_response_text(model: str, prompt: str, tab_id: int) -> str:
"""Deterministic but visibly per-tab response.
Format keeps each parallel worker's reply distinct so when the user
clicks between tabs they see different text -- the whole point of
the tab UI demo.
"""
prompt_clean = (prompt or "").strip().replace("\n", " ")
if len(prompt_clean) > 120:
prompt_clean = prompt_clean[:117] + "..."
tab_suffix = f" (worker {tab_id})" if tab_id else ""
return (
f"[spoof reply from {model}{tab_suffix}] "
f"You said: {prompt_clean!r}. "
f"This response is generated by the local Codex spoof "
f"(UNSLOTH_CODEX_SPOOF=1) -- no upstream tokens were used."
)
class _TurnStream:
"""Async iterator returned by ``Turn.stream()``.
Emits a sequence of dict-shaped ``message.delta`` events (one word at
a time, so the chat-adapter's streaming surface gets exercised) and
closes with an ``ItemCompletedNotification`` carrying the same final
text. Matches the dual delta + completion shape the real upstream
SDK emits.
"""
def __init__(self, text: str, delay_s: float = 0.01) -> None:
self._text = text
self._delay_s = delay_s
self._iter: Optional[AsyncIterator[Any]] = None
def __aiter__(self) -> "_TurnStream":
return self
async def _generate(self) -> AsyncIterator[Any]:
# One word at a time gives a visible streaming effect in the UI
# without flooding the SSE channel.
words = self._text.split(" ")
for i, word in enumerate(words):
chunk = (" " + word) if i > 0 else word
yield {"type": "message.delta", "delta": chunk}
if self._delay_s > 0:
await asyncio.sleep(self._delay_s)
# Final completion event -- the canonical SDK always emits this,
# and ``_stream_thread_run`` uses it as its fallback when no
# deltas arrived (so worth keeping even when deltas did stream).
yield _ItemCompletedNotification(
item = _ItemRoot(root = _AgentMessage(text = self._text))
)
async def __anext__(self) -> Any:
if self._iter is None:
self._iter = self._generate()
return await self._iter.__anext__()
# Some SDK revs let callers ``async with stream:``. Treat as a no-op.
async def __aenter__(self) -> "_TurnStream":
return self
async def __aexit__(self, *_exc: Any) -> None:
return None
class _Turn:
def __init__(self, text: str) -> None:
self._text = text
def stream(self) -> _TurnStream:
return _TurnStream(self._text)
class _Thread:
def __init__(self, model: str, system: Optional[str]) -> None:
self._model = model
self._system = system
self._tab_id = _tab_id_from_system(system)
# Canonical path: ``thread.turn(prompt).stream()``.
def turn(self, prompt: str) -> _Turn:
text = _spoof_response_text(self._model, prompt, self._tab_id)
return _Turn(text)
# Legacy path: ``async for event in thread.run_streaming(prompt)``.
def run_streaming(self, prompt: str) -> _TurnStream:
text = _spoof_response_text(self._model, prompt, self._tab_id)
return _TurnStream(text)
# Buffered fallback: ``await thread.run(prompt)`` returning a result
# whose ``.text`` (or ``.final_response``) is the answer.
async def run(self, prompt: str) -> Any:
from types import SimpleNamespace
text = _spoof_response_text(self._model, prompt, self._tab_id)
await asyncio.sleep(0)
return SimpleNamespace(text = text, final_response = text)
class AsyncCodex:
"""Spoof drop-in for ``openai_codex.AsyncCodex``.
Accepts the same ``config=AppServerConfig(...)`` constructor signature
Studio passes through. ``thread_start`` returns a ``_Thread`` whose
turn / run / run_streaming methods emit deterministic streams.
"""
def __init__(self, config: Optional[AppServerConfig] = None, **_kw: Any) -> None:
self._config = config or AppServerConfig()
self._started_at = time.time()
async def thread_start(
self,
*,
model: str,
base_instructions: Optional[str] = None,
system: Optional[str] = None,
approval_mode: Optional[ApprovalMode] = None,
sandbox: Optional[SandboxMode] = None,
**_extra: Any,
) -> _Thread:
await asyncio.sleep(0)
# Either kwarg path is accepted -- the real provider tries
# ``base_instructions`` first then falls back to ``system``.
sys_text = base_instructions if base_instructions is not None else system
return _Thread(model = model, system = sys_text)
def install_as_openai_codex() -> None:
"""Insert this module into ``sys.modules`` under the names the real
SDK would use, so ``importlib.util.find_spec`` succeeds and the
provider's existing import path picks it up unchanged.
Idempotent: a second call is a no-op. Called from ``codex_provider``
inside ``_import_codex`` when the env flag is set.
"""
import sys
for name in ("openai_codex", "codex_app_server"):
if name in sys.modules:
continue
sys.modules[name] = _build_module_alias(name)
def _build_module_alias(name: str) -> Any:
"""Build a module-like object exposing the same public symbols as
this file, under the requested import name. Using a fresh module
object (rather than aliasing ``codex_spoof`` directly) means the
SDK's ``__name__`` lookups (e.g. for ``ImportError`` messages) get
the real upstream-style name.
"""
import types
import importlib.machinery
mod = types.ModuleType(name)
# ``importlib.util.find_spec(name)`` walks ``sys.modules[name].__spec__``
# first, so an empty spec is required for the provider's existing
# availability probe to recognise the spoof.
mod.__spec__ = importlib.machinery.ModuleSpec(name, loader = None)
mod.AsyncCodex = AsyncCodex # type: ignore[attr-defined]
mod.AppServerConfig = AppServerConfig # type: ignore[attr-defined]
mod.ApprovalMode = ApprovalMode # type: ignore[attr-defined]
mod.SandboxMode = SandboxMode # type: ignore[attr-defined]
mod.__spoof__ = True # marker -- tests can assert this
return mod