mirror of
https://github.com/usestrix/strix.git
synced 2026-10-15 04:18:10 +00:00
Normalize source line endings
This commit is contained in:
parent
c428874108
commit
02ff1d0a28
4 changed files with 595 additions and 470 deletions
|
|
@ -1,161 +1,60 @@
|
|||
r‡^Ñf¥–ئ{O,yÊ'vî¶›"""Strix application settings — pydantic-settings powered."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import AliasChoices, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]
|
||||
|
||||
DEFAULT_MAX_TURNS = 500
|
||||
|
||||
_BASE_CONFIG = SettingsConfigDict(
|
||||
case_sensitive=False,
|
||||
populate_by_name=True,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
|
||||
class LlmSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
model: str | None = Field(default=None, alias="STRIX_LLM")
|
||||
api_key: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("LLM_API_KEY", "OPENAI_API_KEY"),
|
||||
repr=False,
|
||||
)
|
||||
api_base: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices(
|
||||
"LLM_API_BASE",
|
||||
"OPENAI_API_BASE",
|
||||
"OPENAI_BASE_URL",
|
||||
"LITELLM_BASE_URL",
|
||||
"OLLAMA_API_BASE",
|
||||
),
|
||||
)
|
||||
extra_headers: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
alias="LLM_EXTRA_HEADERS",
|
||||
repr=False,
|
||||
)
|
||||
reasoning_effort: ReasoningEffort = Field(default="high", alias="STRIX_REASONING_EFFORT")
|
||||
force_required_tool_choice: bool = Field(
|
||||
default=False,
|
||||
alias="STRIX_FORCE_REQUIRED_TOOL_CHOICE",
|
||||
)
|
||||
prompt_cache: bool = Field(
|
||||
default=True,
|
||||
alias="STRIX_PROMPT_CACHE",
|
||||
)
|
||||
disable_streaming: bool = Field(
|
||||
default=False,
|
||||
alias="LLM_DISABLE_STREAMING",
|
||||
)
|
||||
timeout: int = Field(default=300, alias="LLM_TIMEOUT")
|
||||
stream_idle_timeout: int = Field(default=300, ge=0, alias="LLM_STREAM_IDLE_TIMEOUT")
|
||||
max_tool_calls_per_turn: int = Field(
|
||||
default=32,
|
||||
ge=0,
|
||||
alias="LLM_MAX_TOOL_CALLS_PER_TURN",
|
||||
)
|
||||
|
||||
|
||||
class DedupeSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
model: str | None = Field(default=None, alias="STRIX_DEDUPE_MODEL")
|
||||
reasoning_effort: ReasoningEffort | None = Field(
|
||||
default=None,
|
||||
alias="STRIX_DEDUPE_REASONING_EFFORT",
|
||||
)
|
||||
api_key: str | None = Field(default=None, alias="DEDUPE_LLM_API_KEY", repr=False)
|
||||
api_base: str | None = Field(default=None, alias="DEDUPE_LLM_API_BASE")
|
||||
extra_headers: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
alias="DEDUPE_LLM_EXTRA_HEADERS",
|
||||
repr=False,
|
||||
)
|
||||
|
||||
|
||||
class ContextSettings(BaseSettings):
|
||||
"""Context-window management: per-tool-output caps and history compaction."""
|
||||
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
auto_compact: bool = Field(default=True, alias="STRIX_CONTEXT_AUTO_COMPACT")
|
||||
compact_buffer_tokens: int = Field(default=20_000, gt=0, alias="STRIX_CONTEXT_BUFFER_TOKENS")
|
||||
keep_tokens: int = Field(default=8_000, gt=0, alias="STRIX_CONTEXT_KEEP_TOKENS")
|
||||
fallback_context_tokens: int = Field(
|
||||
default=200_000, gt=0, alias="STRIX_CONTEXT_FALLBACK_TOKENS"
|
||||
)
|
||||
summary_max_tokens: int = Field(default=4_096, gt=0, alias="STRIX_CONTEXT_SUMMARY_TOKENS")
|
||||
tool_output_max_tokens: int = Field(default=8_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_TOKENS")
|
||||
tool_output_max_lines: int = Field(default=2_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_LINES")
|
||||
# Floor above the truncation-notice size so a preview always fits.
|
||||
tool_output_max_bytes: int = Field(
|
||||
default=50 * 1024, ge=1024, alias="STRIX_TOOL_OUTPUT_MAX_BYTES"
|
||||
)
|
||||
|
||||
|
||||
class RuntimeSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
image: str = Field(
|
||||
default="ghcr.io/usestrix/strix-sandbox:1.3.0",
|
||||
alias="STRIX_IMAGE",
|
||||
)
|
||||
image_pull_policy: Literal["auto", "never"] = Field(
|
||||
default="auto",
|
||||
alias="STRIX_IMAGE_PULL_POLICY",
|
||||
)
|
||||
backend: str = Field(default="docker", alias="STRIX_RUNTIME_BACKEND")
|
||||
caido_boot_wait_s: int = Field(default=300, gt=0, alias="STRIX_CAIDO_BOOT_WAIT_S")
|
||||
# Max screenshot/image tool outputs kept live per agent context (0 = none).
|
||||
max_context_images: int = Field(default=3, ge=0, alias="STRIX_MAX_CONTEXT_IMAGES")
|
||||
|
||||
|
||||
class TelemetrySettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
enabled: bool = Field(default=True, alias="STRIX_TELEMETRY")
|
||||
|
||||
|
||||
class IntegrationSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
perplexity_api_key: str | None = Field(
|
||||
default=None,
|
||||
alias="PERPLEXITY_API_KEY",
|
||||
repr=False,
|
||||
)
|
||||
postman_api_key: str | None = Field(
|
||||
default=None,
|
||||
alias="POSTMAN_API_KEY",
|
||||
repr=False,
|
||||
)
|
||||
|
||||
|
||||
class ViewerSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
# Base URL of the Strix relay the local viewer proxies to for email
|
||||
# verification and encrypted report delivery. The browser never talks to
|
||||
# the relay directly; the local server is the only caller.
|
||||
app_url: str = Field(default="https://app.strix.ai", alias="STRIX_APP_URL")
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
llm: LlmSettings = Field(default_factory=LlmSettings)
|
||||
dedupe: DedupeSettings = Field(default_factory=DedupeSettings)
|
||||
runtime: RuntimeSettings = Field(default_factory=RuntimeSettings)
|
||||
context: ContextSettings = Field(default_factory=ContextSettings)
|
||||
telemetry: TelemetrySettings = Field(default_factory=TelemetrySettings)
|
||||
integrations: IntegrationSettings = Field(default_factory=IntegrationSettings)
|
||||
viewer: ViewerSettings = Field(default_factory=ViewerSettings)
|
||||
r‡^Ñf¥–ئ{[r‰Ý°ë¦ëHˆˆ”Ýš^\XØ][ÛˆÙ][™ÜÈ8 %Y[<EFBFBD>XË\Ù][™ÜÈÝÙ\™Yˆˆˆ‚‚™œ›ÛH×Ù<EFBFBD>]\™W×È[\Ü<EFBFBD>[››Ý][ۜ‚™œ›ÛH\[™È[\Ü<EFBFBD>]\˜[‚™œ›ÛHY[<EFBFBD>XÈ[\Ü<EFBFBD>[X\ÐÚÚXÙ\ËšY[™œ›ÛHY[<EFBFBD>X×ÜÙ][™ÜÈ[\Ü<EFBFBD>˜\ÙTÙ][™ÜËÙ][™ÜÐÛÛ™šYÑXÝ‚‚”™X\ÛÛš[™ÑY™›Ü<EFBFBD>H]\˜[È››Û™H‹›Z[š[X[‹›ÝÈ‹›YY][H‹šYÚ‹žYÚ‹›X^—B‚‘Q<EFBFBD>USÓPVÕT“”ÈH
L‚—Ð<EFBFBD>TÑWÐÓÓ‘’QÈHÙ][™ÜÐÛÛ™šYÑXÝ
|
||||
ˆØ\ÙWÜÙ[œÚ]]™OQ˜[ÙKˆÜ[]WØžWÛ˜[YOU<EFBFBD>YKˆ^˜OHšYۛܙH‹ŠB‚‚˜Û\ÜÈTÙ][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ[Ù[ˆÝˆ›Û™HHšY[
|
||||
Y˜][S›Û™K[X\ÏH”Õ’VÓHŠBˆ\WÚÙ^NˆÝˆ›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ˜[Y][Û—Ø[X\ÏP[X\ÐÚÚXÙ\Ê“WÐTWÒÑVH‹“ÔS<EFBFBD>RWÐTWÒÑVHŠKˆ™\<EFBFBD>Q˜[ÙKˆ
|
||||
Bˆ\Wؘ\ÙNˆÝˆ›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ˜[Y][Û—Ø[X\ÏP[X\ÐÚÚXÙ\ʈ“WÐTWÐ<EFBFBD>TÑH‹ˆ“ÔS<EFBFBD>RWÐTWÐ<EFBFBD>TÑH‹ˆ“ÔS<EFBFBD>RWÐ<EFBFBD>TÑWÕT“‹ˆ“USWÐ<EFBFBD>TÑWÕT“‹ˆ“ÓSPWÐTWÐ<EFBFBD>TÑH‹ˆ
|
||||
Kˆ
|
||||
Bˆ^˜WÚXY\œÎˆXÝÜ݋ݗH›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ[X\ÏH“WÑV<EFBFBD>WÒPQT”È‹ˆ™\<EFBFBD>Q˜[ÙKˆ
|
||||
Bˆ™X\ÛÛš[™×ÙY™›Ü<EFBFBD>ˆ™X\ÛÛš[™ÑY™›Ü<EFBFBD>HšY[
|
||||
Y˜][HšYÚ‹[X\ÏH”Õ’VÔ‘PTÓÓ’S‘×ÑQ‘“Ô•ŠBˆ›Ü˜ÙWÜ™\]Z\™YÝÛÛØÚÚXÙNˆ›ÛÛHšY[
|
||||
ˆY˜][Q˜[ÙKˆ[X\ÏH”Õ’VÑ“Ô<EFBFBD>ÑWÔ‘TURT‘QÕÓÓÐÒÒPÑH‹ˆ
|
||||
Bˆ›Û\ØØXÚNˆ›ÛÛHšY[
|
||||
ˆY˜][U<EFBFBD>YKˆ[X\ÏH”Õ’VÔ“ÓTÐÐPÒH‹ˆ
|
||||
Bˆ\ØX›WÜÝ™X[Z[™Îˆ›ÛÛHšY[
|
||||
ˆY˜][Q˜[ÙKˆ[X\ÏH“WÑTÐP“WÔÕ‘PSRS‘È‹ˆ
|
||||
Bˆ[Y[Ý]ˆ[<EFBFBD>HšY[
|
||||
Y˜][LÌ[X\ÏH“WÕSQSÕUŠBˆÝ™X[WÚYWÝ[Y[Ý]ˆ[<EFBFBD>HšY[
|
||||
Y˜][LÌÙOL[X\ÏH“WÔÕ‘PSWÒQWÕSQSÕUŠBˆX^ÝÛÛØØ[×Ü\—Ý\›Žˆ[<EFBFBD>HšY[
|
||||
ˆY˜][LÌ‹ˆÙOLˆ[X\ÏH“WÓPVÕÓÓÐÐS×ÔT—ÕT“ˆ‹ˆ
|
||||
B‚‚˜Û\ÜÈY\TÙ][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ[Ù[ˆÝˆ›Û™HHšY[
|
||||
Y˜][S›Û™K[X\ÏH”Õ’VÑQTWÓSÑSŠBˆ™X\ÛÛš[™×ÙY™›Ü<EFBFBD>ˆ™X\ÛÛš[™ÑY™›Ü<EFBFBD>›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ[X\ÏH”Õ’VÑQTWÔ‘PTÓÓ’S‘×ÑQ‘“Ô•‹ˆ
|
||||
Bˆ\WÚÙ^NˆÝˆ›Û™HHšY[
|
||||
Y˜][S›Û™K[X\ÏH‘QTWÓWÐTWÒÑVH‹™\<EFBFBD>Q˜[ÙJBˆ\Wؘ\ÙNˆÝˆ›Û™HHšY[
|
||||
Y˜][S›Û™K[X\ÏH‘QTWÓWÐTWÐ<EFBFBD>TÑHŠBˆ^˜WÚXY\œÎˆXÝÜ݋ݗH›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ[X\ÏH‘QTWÓWÑV<EFBFBD>WÒPQT”È‹ˆ™\<EFBFBD>Q˜[ÙKˆ
|
||||
B‚‚˜Û\ÜÈÛÛ<EFBFBD>^Ù][™Üʘ\ÙTÙ][™ÜÊN‚ˆˆˆ<EFBFBD>ÛÛ<EFBFBD>^]Ú[™ÝÈX[˜YÙ[Y[<EFBFBD>ˆ\‹]ÛÛ[Ý]]Ø\È[™\ÝÜžHÛÛ\XÝ[Û‹ˆˆˆ‚‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ]]רÛÛ\X݈›ÛÛHšY[
|
||||
Y˜][U<EFBFBD>YK[X\ÏH”Õ’VÐÓÓ•VÐUU×ÐÓÓTPÕŠBˆÛÛ\XÝØ<EFBFBD>Y™™\—ÝÚÙ[œÎˆ[<EFBFBD>HšY[
|
||||
Y˜][LŒÌÝL[X\ÏH”Õ’VÐÓÓ•VЕQ‘‘T—ÕÒÑS”ÈŠBˆÙY\ÝÚÙ[œÎˆ[<EFBFBD>HšY[
|
||||
Y˜][NÌÝL[X\ÏH”Õ’VÐÓÓ•VÒÑQTÕÒÑS”ÈŠBˆ˜[˜XÚרÛÛ<EFBFBD>^ÝÚÙ[œÎˆ[<EFBFBD>HšY[
|
||||
ˆY˜][LŒÌÝL[X\ÏH”Õ’VÐÓÓ•VÑ<EFBFBD>S<EFBFBD>PÒ×ÕÒÑS”È‚ˆ
|
||||
BˆÝ[[X\žWÛX^ÝÚÙ[œÎˆ[<EFBFBD>HšY[
|
||||
Y˜][MÌM‹ÝL[X\ÏH”Õ’VÐÓÓ•VÔÕSSPT–WÕÒÑS”ÈŠBˆÛÛÛÝ]]ÛX^ÝÚÙ[œÎˆ[<EFBFBD>HšY[
|
||||
Y˜][NÌÝL[X\ÏH”Õ’VÕÓÓÓÕUUÓPVÕÒÑS”ÈŠBˆÛÛÛÝ]]ÛX^Û[™\Έ[<EFBFBD>HšY[
|
||||
Y˜][L—ÌÝL[X\ÏH”Õ’VÕÓÓÓÕUUÓPVÓS‘TÈŠBˆÈ›Û܈X›Ý™HH<EFBFBD>[˜Ø][Û‹[›ÝXÙHÚ^™HÛÈH™]šY]È[Ø^\Èš]Ë‚ˆÛÛÛÝ]]ÛX^Øž]\Έ[<EFBFBD>HšY[
|
||||
ˆY˜][ML
|
||||
ˆL<EFBFBD>ÙOLL<EFBFBD>[X\ÏH”Õ’VÕÓÓÓÕUUÓPVЖUTÈ‚ˆ
|
||||
B‚‚˜Û\ÜÈ<EFBFBD>[<EFBFBD>[YTÙ][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ[XYÙNˆÝˆHšY[
|
||||
ˆY˜][H™ÚÜ‹š[ËÝ\Ù\Ýš^ÜÝš^\Ø[™›ÞŒKŒËŒ‹ˆ[X\ÏH”Õ’VÒSPQÑH‹ˆ
|
||||
Bˆ[XYÙWÜ[ÜÛXÞNˆ]\˜[Ș]]È‹›™]™\ˆ—HHšY[
|
||||
ˆY˜][H˜]]È‹ˆ[X\ÏH”Õ’VÒSPQÑWÔSÔÓPÖH‹ˆ
|
||||
Bˆ˜XÚÙ[™ˆÝˆHšY[
|
||||
Y˜][H™ØÚÙ\ˆ‹[X\ÏH”Õ’VÔ•S•SQWÐ<EFBFBD>PÒÑS‘ŠBˆØZYר›ÛÝÝØZ]ÜΈ[<EFBFBD>HšY[
|
||||
Y˜][LÌÝL[X\ÏH”Õ’VÐÐRQ×ГÓÕÕÐRUÔÈŠBˆÈX^ØÜ™Y[œÚÝÚ[XYÙHÛÛÝ]]ÈÙ\]™H\ˆYÙ[<EFBFBD>ÛÛ<EFBFBD>^
|
||||
H›Û™JK‚ˆX^ØÛÛ<EFBFBD>^Ú[XYÙ\Έ[<EFBFBD>HšY[
|
||||
Y˜][LËÙOL[X\ÏH”Õ’VÓPVÐÓÓ•VÒSPQÑTÈŠB‚‚˜Û\ÜÈ[[Y]žTÙ][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ[˜X›Yˆ›ÛÛHšY[
|
||||
Y˜][U<EFBFBD>YK[X\ÏH”Õ’VÕSSQU–HŠB‚‚˜Û\ÜÈ[<EFBFBD>Yܘ][Û”Ù][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆ\œ^]WØ\WÚÙ^NˆÝˆ›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ[X\ÏH”T”VUWÐTWÒÑVH‹ˆ™\<EFBFBD>Q˜[ÙKˆ
|
||||
BˆÜÝX[—Ø\WÚÙ^NˆÝˆ›Û™HHšY[
|
||||
ˆY˜][S›Û™Kˆ[X\ÏH”ÔÕPS—ÐTWÒÑVH‹ˆ™\<EFBFBD>Q˜[ÙKˆ
|
||||
B‚‚˜Û\ÜÈšY]Ù\”Ù][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆÈ˜\ÙHT“ÙˆHÝš^™[^HHØØ[šY]Ù\ˆ›ÞY\Èț܈[XZ[ˆÈ™\šYšXØ][Ûˆ[™[˜Üž\Y™\Ü<EFBFBD>[]™\žKˆHœ›ÝÜÙ\ˆ™]™\ˆ[ÜȈÈH™[^H\™XÝNÈHØØ[Ù\<EFBFBD>™\ˆ\ÈHÛ›HØ[\‹‚ˆ\Ý\›ˆÝˆHšY[
|
||||
Y˜][HšÎ‹ËØ\œÝš^˜ZH‹[X\ÏH”Õ’VÐTÕT“ŠB‚‚˜Û\ÜÈÙ][™Üʘ\ÙTÙ][™ÜÊN‚ˆ[Ù[ØÛÛ™šYÈHÐ<EFBFBD>TÑWÐÓÓ‘’Q‚ˆNˆTÙ][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOSTÙ][™ÜÊBˆY\NˆY\TÙ][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOQY\TÙ][™ÜÊBˆ<EFBFBD>[<EFBFBD>[YNˆ<EFBFBD>[<EFBFBD>[YTÙ][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOT<EFBFBD>[<EFBFBD>[YTÙ][™ÜÊBˆÛÛ<EFBFBD>^ˆÛÛ<EFBFBD>^Ù][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOPÛÛ<EFBFBD>^Ù][™ÜÊBˆ[[Y]žNˆ[[Y]žTÙ][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOU[[Y]žTÙ][™ÜÊBˆ[<EFBFBD>Yܘ][ۜΈ[<EFBFBD>Yܘ][Û”Ù][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOR[<EFBFBD>Yܘ][Û”Ù][™ÜÊBˆšY]Ù\ŽˆšY]Ù\”Ù][™ÜÈHšY[
|
||||
Y˜][Ù˜XÝÜžOUšY]Ù\”Ù][™ÜÊB
|
||||
|
|
@ -1,115 +1,224 @@
|
|||
r‡^Ñf¥–ئ{[r‰Ý°ë¦ëHˆˆ”Ý\<EFBFBD>\[<EFBFBD>š\›Û›Y[<EFBFBD>˜[Y][Ûˆ[™ØÚÙ\ˆ[XYÙHX[˜YÙ[Y[<EFBFBD>ˆˆˆ‚‚š[\Ü<EFBFBD>ÙÙÚ[™Âš[\Ü<EFBFBD>Ú][š[\Ü<EFBFBD>Þ\‚™œ›ÛHšXÚ˜ÛÛœÛÛH[\Ü<EFBFBD>ÛÛœÛÛB™œ›ÛHšXÚœ[™[[\Ü<EFBFBD>[™[™œ›ÛHšXÚ<EFBFBD>^[\Ü<EFBFBD>^‚™œ›ÛHÝš^˜ÛÛ™šYÈ[\Ü<EFBFBD>ÛÙ^ØYÜÙ][™Ü™œ›ÛHÝš^š[<EFBFBD>\™˜XÙK<EFBFBD>][È[\Ü<EFBFBD>
|
||||
ˆÚXÚ×ÙØÚÙ\—ØÛÛ›™XÝ[Û‹ˆ[XYÙWÙ^\Ýˈ›ØÙ\Ü×Ü[Û[™KŠB‚‚›ÙÙÙ\ˆHÙÙÚ[™Ë™Ù]ÙÙÙ\Š×Û˜[YW×ÊB‚‚™Yˆ˜[Y]WÙ[<EFBFBD>š\›Û›Y[<EFBFBD>
|
||||
r‡^Ñf¥–ئ{O,yÊ'vî¶›"""Startup environment validation and Docker image management."""
|
||||
|
||||
HOˆ›Û™N‚ˆÙÙÙ\‹š[™›Ê•˜[Y][™È[<EFBFBD>š\›Û›Y[<EFBFBD>ŠBˆÛÛœÛÛHHÛÛœÛÛJ
|
||||
BˆZ\ÜÚ[™×Ü™\]Z\™Yݘ\œÈH×BˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÈH×B‚ˆÙ][™ÜÈHØYÜÙ][™ÜÊ
|
||||
B‚ˆYˆÛÙ^œÝXœØÜš\[Û—Û[Ù[
|
||||
Ù][™ÜË›K›[Ù[
|
||||
N‚ˆYˆ›ÝÛÙ^š\ר]][<EFBFBD>XØ]Y
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
N‚ˆÛÛœÛÛKœš[<EFBFBD>
|
||||
ˆˆ–Ü™YTÕ’VÓO^ÜÙ][™ÜË›K›[Ù[H\Ù\È[Ý\ˆÚ]ÔÝXœØÜš\[Û‹‚ˆ˜<EFBFBD>][ÝIÜ™H›ÝÚYÛ™Y[‹–Ë×H<EFBFBD>[ˆØÞX[—\Ýš^]]ÙÚ[ˆÚ]ÜË×Hš\œÝˆ‚ˆ
|
||||
BˆÞ\Ë™^]
|
||||
JBˆÙÙÙ\‹š[™›Ê‘[<EFBFBD>š\›Û›Y[<EFBFBD>ÒÈ
|
||||
Ú]ÔÝXœØÜš\[ÛŠHŠBˆ™]\›‚‚ˆYˆ›ÝÙ][™ÜË›K›[Ù[‚ˆZ\ÜÚ[™×Ü™\]Z\™Yݘ\œË˜\[™
|
||||
”Õ’VÓHŠB‚ˆYˆ›ÝÙ][™ÜË›K˜\WÚÙ^N‚ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œË˜\[™
|
||||
“WÐTWÒÑVHŠB‚ˆYˆ›ÝÙ][™ÜË›K˜\Wؘ\ÙN‚ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œË˜\[™
|
||||
“WÐTWÐ<EFBFBD>TÑHŠB‚ˆYˆ›ÝÙ][™ÜËš[<EFBFBD>Yܘ][ۜ˜\œ^]WØ\WÚÙ^N‚ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œË˜\[™
|
||||
”T”VUWÐTWÒÑVHŠB‚ˆYˆZ\ÜÚ[™×Ü™\]Z\™Yݘ\œÎ‚ˆ\œ›Ü—Ý^H^
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
Bˆ\œ›Ü—Ý^˜\[™
|
||||
“RTÔÒS‘È‘TURT‘QS•’T“Ó“QS•<EFBFBD>T’PP“TÈ‹Ý[OH˜›Û™YŠBˆ\œ›Ü—Ý^˜\[™
|
||||
——ˆ‹Ý[OH<EFBFBD>Ú]HŠB‚ˆ›Üˆ˜\ˆ[ˆZ\ÜÚ[™×Ü™\]Z\™Yݘ\œÎ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ¸ (ˆÝ˜\ŸH‹Ý[OH˜›ÛY[ÝÈŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ\È›ÝÙ]ˆ‹Ý[OH<EFBFBD>Ú]HŠB‚ˆYˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
—“Ü[Û˜[[<EFBFBD>š\›Û›Y[<EFBFBD>˜\šXX›\Ηˆ‹Ý[OH™[HÚ]HŠBˆ›Üˆ˜\ˆ[ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ¸ (ˆÝ˜\ŸH‹Ý[OH™[HY[ÝÈŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ\È›ÝÙ]ˆ‹Ý[OH™[HÚ]HŠB‚ˆ\œ›Ü—Ý^˜\[™
|
||||
—”™\]Z\™Y[<EFBFBD>š\›Û›Y[<EFBFBD>˜\šXX›\Ηˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ›Üˆ˜\ˆ[ˆZ\ÜÚ[™×Ü™\]Z\™Yݘ\œÎ‚ˆYˆ˜\ˆOH”Õ’VÓHŽ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
¸ (ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
”Õ’VÓH‹Ý[OH˜›ÛÞX[ˆŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆˆH[Ù[˜[YHÈ\ÙH
|
||||
K™Ë‹ ÛÜ[˜ZKÙÜMK<EFBFBD> È܈‚ˆ‰Ø[<EFBFBD>›ÜXËØÛ]YK[Ü\ËMMÉÊWˆ‹ˆÝ[OH<EFBFBD>Ú]H‹ˆ
|
||||
B‚ˆYˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
—“Ü[Û˜[[<EFBFBD>š\›Û›Y[<EFBFBD>˜\šXX›\Ηˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ›Üˆ˜\ˆ[ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆYˆ˜\ˆOH“WÐTWÐ<EFBFBD>TÑHŽ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
¸ (ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
“WÐTWÐ<EFBFBD>TÑH‹Ý[OH˜›ÛÞX[ˆŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆˆHÝ\ÝÛHTH˜\ÙHT“Yˆ\Ú[™ÈØØ[[Ù[È
|
||||
K™Ë‹Û[XKTÝY[ÊWˆ‹ˆÝ[OH<EFBFBD>Ú]H‹ˆ
|
||||
Bˆ[Yˆ˜\ˆOH”T”VUWÐTWÒÑVHŽ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
¸ (ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
”T”VUWÐTWÒÑVH‹Ý[OH˜›ÛÞX[ˆŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆˆHTHÙ^H›Üˆ\œ^]HRHÙXˆÙX\˜Ú
|
||||
[˜X›\È™X[][YH™\ÙX\˜Ú
|
||||
Wˆ‹ˆÝ[OH<EFBFBD>Ú]H‹ˆ
|
||||
Bˆ[Yˆ˜\ˆOH”Õ’VÔ‘PTÓÓ’S‘×ÑQ‘“Ô•Ž‚ˆ\œ›Ü—Ý^˜\[™
|
||||
¸ (ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
”Õ’VÔ‘PTÓÓ’S‘×ÑQ‘“Ô•‹Ý[OH˜›ÛÞX[ˆŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆˆH™X\ÛÛš[™ÈY™›Ü<EFBFBD>]™[ˆ›Û™KZ[š[X[ÝËYY][KYÚYÚ‚ˆ›X^
|
||||
Y˜][ˆYÚ
|
||||
Wˆ‹ˆÝ[OH<EFBFBD>Ú]H‹ˆ
|
||||
B‚ˆ\œ›Ü—Ý^˜\[™
|
||||
—‘^[\HÙ]\—ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
™^Ü<EFBFBD>Õ’VÓOIÛÜ[˜ZKÙÜMK<EFBFBD> ׈‹Ý[OH™[HÚ]HŠB‚ˆYˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆ›Üˆ˜\ˆ[ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÎ‚ˆYˆ˜\ˆOH“WÐTWÐ<EFBFBD>TÑHŽ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ™^Ü<EFBFBD>WÐTWÐ<EFBFBD>TÑOIÚ‹ËÛØØ[ÜÝŒLMÍ È‚ˆˆÈ™YYY›ÜˆØØ[[Ù[ÈÛ›Wˆ‹ˆÝ[OH™[HÚ]H‹ˆ
|
||||
Bˆ[Yˆ˜\ˆOH”T”VUWÐTWÒÑVHŽ‚ˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ™^Ü<EFBFBD>T”VUWÐTWÒÑVOIÞ[Ý\‹\\œ^]KZÙ^KZ\™I׈‹Ý[OH™[HÚ]H‚ˆ
|
||||
Bˆ[Yˆ˜\ˆOH”Õ’VÔ‘PTÓÓ’S‘×ÑQ‘“Ô•Ž‚ˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ™^Ü<EFBFBD>Õ’VÔ‘PTÓÓ’S‘×ÑQ‘“Ô•IÚYÚ ×ˆ‹ˆÝ[OH™[HÚ]H‹ˆ
|
||||
B‚ˆ[™[H[™[
|
||||
ˆ\œ›Ü—Ý^ˆ]OH–Ø›ÛÚ]WTÕ’V‹ˆ]WØ[YÛ<EFBFBD>H›Y<EFBFBD>‹ˆ›Ü™\—ÜÝ[OHœ™Y‹ˆY[™ÏJKŠKˆ
|
||||
B‚ˆÙÙÙ\‹™X<EFBFBD>YÊ“Z\ÜÚ[™È™\]Z\™Y[<EFBFBD>ˆ˜\œÎˆ \È‹Z\ÜÚ[™×Ü™\]Z\™Yݘ\œÊBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
—ˆŠBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
[™[
|
||||
BˆÛÛœÛÛKœš[<EFBFBD>
|
||||
from strix.config import codex, load_settings
|
||||
from strix.interface.utils import (
|
||||
check_docker_connection,
|
||||
image_exists,
|
||||
process_pull_line,
|
||||
)
|
||||
|
||||
BˆÞ\Ë™^]
|
||||
JBˆÙÙÙ\‹š[™›Êˆ‘[<EFBFBD>š\›Û›Y[<EFBFBD>ÒÈ
|
||||
Ü[Û˜[Z\ÜÚ[™Îˆ \ÊH‹ˆZ\ÜÚ[™×ÛÜ[Û˜[ݘ\œÈ܈››Û™H‹ˆ
|
||||
B‚‚™YˆÚXÚ×ÙØÚÙ\—Ú[œÝ[Y
|
||||
|
||||
HOˆ›Û™N‚ˆYˆÚ][<EFBFBD>ÚXÚ
|
||||
™ØÚÙ\ˆŠH\țۙN‚ˆÙÙÙ\‹™X<EFBFBD>YÊ‘ØÚÙ\ˆÓH›Ý›Ý[™[ˆUŠBˆÛÛœÛÛHHÛÛœÛÛJ
|
||||
Bˆ\œ›Ü—Ý^H^
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Bˆ\œ›Ü—Ý^˜\[™
|
||||
‘ÐÒÑTˆ“ÕS”ÕSQ‹Ý[OH˜›Û™YŠBˆ\œ›Ü—Ý^˜\[™
|
||||
——ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
•H ÙØÚÙ\‰ÈÓHØ\țݛÝ[™[ˆ[Ý\ˆU—ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ”X\ÙH[œÝ[ØÚÙ\ˆ[™[œÝ\™HH ÙØÚÙ\‰ÈÛÛ[X[™\È]˜Z[X›K——ˆ‹Ý[OH<EFBFBD>Ú]H‚ˆ
|
||||
B‚ˆ[™[H[™[
|
||||
ˆ\œ›Ü—Ý^ˆ]OH–Ø›ÛÚ]WTÕ’V‹ˆ]WØ[YÛ<EFBFBD>H›Y<EFBFBD>‹ˆ›Ü™\—ÜÝ[OHœ™Y‹ˆY[™ÏJKŠKˆ
|
||||
BˆÛÛœÛÛKœš[<EFBFBD>
|
||||
—ˆ‹[™[—ˆŠBˆÞ\Ë™^]
|
||||
JBˆÙÙÙ\‹™X<EFBFBD>YÊ‘ØÚÙ\ˆÓH™\Ù[<EFBFBD>ŠB‚‚™Yˆ[ÙØÚÙ\—Ú[XYÙJ
|
||||
HOˆ›Û™N‚ˆœ›ÛHØÚÙ\‹™\œ›ÜœÈ[\Ü<EFBFBD>ØÚÙ\‘^Ù\[Û‚‚ˆÛÛœÛÛHHÛÛœÛÛJ
|
||||
BˆÛY[<EFBFBD>HÚXÚ×ÙØÚÙ\—ØÛÛ›™XÝ[ÛŠ
|
||||
B‚ˆ<EFBFBD>[<EFBFBD>[YHHØYÜÙ][™ÜÊ
|
||||
Kœ<EFBFBD>[<EFBFBD>[YBˆ[XYÙHH<EFBFBD>[<EFBFBD>[YKš[XYÙB‚ˆYˆ[XYÙWÙ^\ÝÊÛY[<EFBFBD>[XYÙJN‚ˆÙÙÙ\‹™X<EFBFBD>YÊ‘ØÚÙ\ˆ[XYÙH[™XYH™\Ù[<EFBFBD>ØØ[Nˆ \È‹[XYÙJBˆ™]\›‚‚ˆYˆ<EFBFBD>[<EFBFBD>[YKš[XYÙWÜ[ÜÛXÞHOH›™]™\ˆŽ‚ˆ˜Z\ÙH<EFBFBD>[<EFBFBD>[YQ\œ›ÜŠˆˆ‘ØÚÙ\ˆ[XYÙHÚ[XYÙH\ŸH\È›Ý]˜Z[X›HØØ[H[™‚ˆ”Õ’VÒSPQÑWÔSÔÓPÖO[™]™\ˆ›Ü˜šYÈ[[™È]‚ˆ
|
||||
B‚ˆÙÙÙ\‹š[™›Ê”[[™ÈØÚÙ\ˆ[XYÙNˆ \È‹[XYÙJBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
|
||||
BˆÛÛœÛÛKœš[<EFBFBD>
|
||||
ˆ–Ù[WT[[™È[XYÙVË×HÚ[XYÙ_HŠBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
–Ù[HY[Ý×U\ÈÛ›H\[œÈÛˆš\œÝ<EFBFBD>[ˆ[™X^HZÙHH™]ÈZ[<EFBFBD>]\Ë‹‹–Ë×HŠBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
def validate_environment() -> None:
|
||||
logger.info("Validating environment")
|
||||
console = Console()
|
||||
missing_required_vars = []
|
||||
missing_optional_vars = []
|
||||
|
||||
B‚ˆÚ]ÛÛœÛÛKœÝ]\Ê–Ø›ÛÞX[—QÝÛ›ØY[™È[XYÙH^Y\œË‹‹ˆ‹Ü[›™\<EFBFBD>H™ÝÈŠH\ÈÝ]\΂ˆžN‚ˆ^Y\œ×Ú[™›ÎˆXÝÜ݋ݗHHßBˆ\ÝÝ\]HHˆ‚‚ˆ›Üˆ[™H[ˆÛY[<EFBFBD>˜\Kœ[
|
||||
[XYÙKÝ™X[OU<EFBFBD>YKXÛÙOU<EFBFBD>YJN‚ˆ\ÝÝ\]HH›ØÙ\Ü×Ü[Û[™J[™K^Y\œ×Ú[™›ËÝ]\Ë\ÝÝ\]JB‚ˆ^Ù\ØÚÙ\‘^Ù\[Ûˆ\ÈN‚ˆÙÙÙ\‹™X<EFBFBD>YÊ‘˜Z[YÈ[ØÚÙ\ˆ[XYÙH \È‹[XYÙK^×Ú[™›ÏU<EFBFBD>YJBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
settings = load_settings()
|
||||
|
||||
Bˆ\œ›Ü—Ý^H^
|
||||
if codex.subscription_model(settings.llm.model):
|
||||
if not codex.is_authenticated():
|
||||
console.print(
|
||||
f"[red]STRIX_LLM={settings.llm.model} uses your ChatGPT subscription, "
|
||||
"but you're not signed in.[/] Run [cyan]strix auth login chatgpt[/] first."
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Environment OK (ChatGPT subscription)")
|
||||
return
|
||||
|
||||
Bˆ\œ›Ü—Ý^˜\[™
|
||||
‘<EFBFBD>RSQÈSSPQÑH‹Ý[OH˜›Û™YŠBˆ\œ›Ü—Ý^˜\[™
|
||||
——ˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ˆ<EFBFBD>ÛÝ[›ÝÝÛ›ØYˆÚ[XYÙ_Wˆ‹Ý[OH<EFBFBD>Ú]HŠBˆ\œ›Ü—Ý^˜\[™
|
||||
ÝŠJKÝ[OH™[H™YŠB‚ˆ[™[H[™[
|
||||
ˆ\œ›Ü—Ý^ˆ]OH–Ø›ÛÚ]WTÕ’V‹ˆ]WØ[YÛ<EFBFBD>H›Y<EFBFBD>‹ˆ›Ü™\—ÜÝ[OHœ™Y‹ˆY[™ÏJKŠKˆ
|
||||
BˆÛÛœÛÛKœš[<EFBFBD>
|
||||
[™[—ˆŠBˆÞ\Ë™^]
|
||||
JB‚ˆÙÙÙ\‹š[™›Ê‘ØÚÙ\ˆ[XYÙH \È™XYH‹[XYÙJBˆÝXØÙ\Ü×Ý^H^
|
||||
if not settings.llm.model:
|
||||
missing_required_vars.append("STRIX_LLM")
|
||||
|
||||
BˆÝXØÙ\Ü×Ý^˜\[™
|
||||
‘ØÚÙ\ˆ[XYÙH™XYH‹Ý[OHˆÌŒ˜ÍMYHŠBˆÛÛœÛÛKœš[<EFBFBD>
|
||||
ÝXØÙ\Ü×Ý^
|
||||
BˆÛÛœÛÛKœš[<EFBFBD>
|
||||
if not settings.llm.api_key:
|
||||
missing_optional_vars.append("LLM_API_KEY")
|
||||
|
||||
B
|
||||
if not settings.llm.api_base:
|
||||
missing_optional_vars.append("LLM_API_BASE")
|
||||
|
||||
if not settings.integrations.perplexity_api_key:
|
||||
missing_optional_vars.append("PERPLEXITY_API_KEY")
|
||||
|
||||
if missing_required_vars:
|
||||
error_text = Text()
|
||||
error_text.append("MISSING REQUIRED ENVIRONMENT VARIABLES", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
|
||||
for var in missing_required_vars:
|
||||
error_text.append(f"• {var}", style="bold yellow")
|
||||
error_text.append(" is not set\n", style="white")
|
||||
|
||||
if missing_optional_vars:
|
||||
error_text.append("\nOptional environment variables:\n", style="dim white")
|
||||
for var in missing_optional_vars:
|
||||
error_text.append(f"• {var}", style="dim yellow")
|
||||
error_text.append(" is not set\n", style="dim white")
|
||||
|
||||
error_text.append("\nRequired environment variables:\n", style="white")
|
||||
for var in missing_required_vars:
|
||||
if var == "STRIX_LLM":
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("STRIX_LLM", style="bold cyan")
|
||||
error_text.append(
|
||||
" - Model name to use (e.g., 'openai/gpt-5.4' or "
|
||||
"'anthropic/claude-opus-4-7')\n",
|
||||
style="white",
|
||||
)
|
||||
|
||||
if missing_optional_vars:
|
||||
error_text.append("\nOptional environment variables:\n", style="white")
|
||||
for var in missing_optional_vars:
|
||||
if var == "LLM_API_BASE":
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("LLM_API_BASE", style="bold cyan")
|
||||
error_text.append(
|
||||
" - Custom API base URL if using local models (e.g., Ollama, LMStudio)\n",
|
||||
style="white",
|
||||
)
|
||||
elif var == "PERPLEXITY_API_KEY":
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("PERPLEXITY_API_KEY", style="bold cyan")
|
||||
error_text.append(
|
||||
" - API key for Perplexity AI web search (enables real-time research)\n",
|
||||
style="white",
|
||||
)
|
||||
elif var == "STRIX_REASONING_EFFORT":
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("STRIX_REASONING_EFFORT", style="bold cyan")
|
||||
error_text.append(
|
||||
" - Reasoning effort level: none, minimal, low, medium, high, xhigh, "
|
||||
"max (default: high)\n",
|
||||
style="white",
|
||||
)
|
||||
|
||||
error_text.append("\nExample setup:\n", style="white")
|
||||
error_text.append("export STRIX_LLM='openai/gpt-5.4'\n", style="dim white")
|
||||
|
||||
if missing_optional_vars:
|
||||
for var in missing_optional_vars:
|
||||
if var == "LLM_API_BASE":
|
||||
error_text.append(
|
||||
"export LLM_API_BASE='http://localhost:11434' "
|
||||
"# needed for local models only\n",
|
||||
style="dim white",
|
||||
)
|
||||
elif var == "PERPLEXITY_API_KEY":
|
||||
error_text.append(
|
||||
"export PERPLEXITY_API_KEY='your-perplexity-key-here'\n", style="dim white"
|
||||
)
|
||||
elif var == "STRIX_REASONING_EFFORT":
|
||||
error_text.append(
|
||||
"export STRIX_REASONING_EFFORT='high'\n",
|
||||
style="dim white",
|
||||
)
|
||||
|
||||
panel = Panel(
|
||||
error_text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
|
||||
logger.debug("Missing required env vars: %s", missing_required_vars)
|
||||
console.print("\n")
|
||||
console.print(panel)
|
||||
console.print()
|
||||
sys.exit(1)
|
||||
logger.info(
|
||||
"Environment OK (optional missing: %s)",
|
||||
missing_optional_vars or "none",
|
||||
)
|
||||
|
||||
|
||||
def check_docker_installed() -> None:
|
||||
if shutil.which("docker") is None:
|
||||
logger.debug("Docker CLI not found in PATH")
|
||||
console = Console()
|
||||
error_text = Text()
|
||||
error_text.append("DOCKER NOT INSTALLED", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append("The 'docker' CLI was not found in your PATH.\n", style="white")
|
||||
error_text.append(
|
||||
"Please install Docker and ensure the 'docker' command is available.\n\n", style="white"
|
||||
)
|
||||
|
||||
panel = Panel(
|
||||
error_text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print("\n", panel, "\n")
|
||||
sys.exit(1)
|
||||
logger.debug("Docker CLI present")
|
||||
|
||||
|
||||
def pull_docker_image() -> None:
|
||||
from docker.errors import DockerException
|
||||
|
||||
console = Console()
|
||||
client = check_docker_connection()
|
||||
|
||||
runtime = load_settings().runtime
|
||||
image = runtime.image
|
||||
|
||||
if image_exists(client, image):
|
||||
logger.debug("Docker image already present locally: %s", image)
|
||||
return
|
||||
|
||||
if runtime.image_pull_policy == "never":
|
||||
raise RuntimeError(
|
||||
f"Docker image {image!r} is not available locally and "
|
||||
"STRIX_IMAGE_PULL_POLICY=never forbids pulling it"
|
||||
)
|
||||
|
||||
logger.info("Pulling docker image: %s", image)
|
||||
console.print()
|
||||
console.print(f"[dim]Pulling image[/] {image}")
|
||||
console.print("[dim yellow]This only happens on first run and may take a few minutes...[/]")
|
||||
console.print()
|
||||
|
||||
with console.status("[bold cyan]Downloading image layers...", spinner="dots") as status:
|
||||
try:
|
||||
layers_info: dict[str, str] = {}
|
||||
last_update = ""
|
||||
|
||||
for line in client.api.pull(image, stream=True, decode=True):
|
||||
last_update = process_pull_line(line, layers_info, status, last_update)
|
||||
|
||||
except DockerException as e:
|
||||
logger.debug("Failed to pull docker image %s", image, exc_info=True)
|
||||
console.print()
|
||||
error_text = Text()
|
||||
error_text.append("FAILED TO PULL IMAGE", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append(f"Could not download: {image}\n", style="white")
|
||||
error_text.append(str(e), style="dim red")
|
||||
|
||||
panel = Panel(
|
||||
error_text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(panel, "\n")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("Docker image %s ready", image)
|
||||
success_text = Text()
|
||||
success_text.append("Docker image ready", style="#22c55e")
|
||||
console.print(success_text)
|
||||
console.print()
|
||||
|
|
|
|||
|
|
@ -1,107 +1,31 @@
|
|||
r‡^Ñf¥–ئ{O,yÊ'vî¶›"""Sandbox backend registry — selected via STRIX_RUNTIME_BACKEND (default: docker)."""
|
||||
r‡^Ñf¥–ئ{[r‰Ý°ë¦ëHˆˆ”Ø[™›Þ˜XÚÙ[™™YÚ\ÝžH8 %Ù[XÝYšXHÕ’VÔ•S•SQWÐ<EFBFBD>PÒÑS‘
|
||||
Y˜][ˆØÚÙ\ŠKˆˆˆ‚‚™œ›ÛH×Ù<EFBFBD>]\™W×È[\Ü<EFBFBD>[››Ý][ۜ‚š[\Ü<EFBFBD>ÙÙÚ[™Â™œ›ÛHÛÛXÝ[ۜ˘X˜È[\Ü<EFBFBD>]ØZ]X›KØ[X›B™œ›ÛH\[™È[\Ü<EFBFBD>TWÐÒPÒÒS‘Ë[žB‚‚šYˆTWÐÒPÒÒS‘΂ˆœ›ÛHYÙ[<EFBFBD>ËœØ[™›Þ›X[šY™\Ý[\Ü<EFBFBD>X[šY™\Ý‚‚›ÙÙÙ\ˆHÙÙÚ[™Ë™Ù]ÙÙÙ\Š×Û˜[YW×ÊB‚‚”Ø[™›Þ˜XÚÙ[™HØ[X›VË‹‹‹]ØZ]X›VÝ\VÐ[žK[žWWWB‚‚˜\Þ[˜ÈYˆÙØÚÙ\—ؘXÚÙ[™
|
||||
ˆ
|
||||
‹ˆ[XYÙNˆÝ‹ˆX[šY™\݈X[šY™\݈^ÜÙYÜÜ<EFBFBD>Έ\VÚ[<EFBFBD>‹‹—Kˆš[™Û[Ý[<EFBFBD>Έ\ÝÙXÝÜÝ‹[žWWH›Û™HH›Û™KŠHOˆ\VÐ[žK[žWN‚ˆˆˆ<EFBFBD>œš[™È\HÙ\ÜÚ[Ûˆ˜XÚÙYžHHØØ[ØÚÙ\ˆY[[Û‹‚‚ˆ\Ù\ȘÛ\ÜΘݚ^ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>È[š™XÝ‘UÐQRSˆÂˆ‘UÔ<EFBFBD>UÈØ\È
|
||||
ÈÜÝ™ØÚÙ\‹š[<EFBFBD>\›˜[ÜÝYØ]]Ø^Kˆ[\Ü<EFBFBD>ˆØÚÙ\˜^š[HÛÈ\Þ[Y[<EFBFBD>È]\™Ù]H›Û‹QØÚÙ\‚ˆ˜XÚÙ[™Û‰Ý™YYHØÚÙ\‹\HXœ˜\žH[œÝ[Y‚‚ˆÙ\ÜÚ[Û‹œÝ\<EFBFBD>
|
||||
|
||||
from __future__ import annotations
|
||||
X\ÈÚ]X]\šX[^™\ÈHX[šY™\Ý[<EFBFBD>ÈH<EFBFBD>[›š[™ÂˆÛÛ<EFBFBD>Z[™\ˆ8 %HÑÉÜÈÛY[<EFBFBD>˜Ü™X]J
|
||||
XÛ›H<EFBFBD>Z[ÈH[›™\ˆÙ\ÜÚ[Û‚ˆØš™XÝÚ]Ý]\Z[™È]ˆ\Þ[˜ÈÚ]Ù\ÜÚ[ÛŽ˜ÛÝ[Ø[]ÛË<EFBFBD>]ˆÝš^X[˜YÙ\ÈÙ\ÜÚ[ÛˆY™][YH^XÚ]HšXHÛY[<EFBFBD>™[]J
|
||||
XÛÈÙBˆšYÙÙ\ˆÝ\<EFBFBD>
|
||||
|
||||
import logging
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
XÝ\œÙ[™\Ë‚ˆˆˆ‚ˆ[\Ü<EFBFBD>ØÚÙ\‚ˆœ›ÛHYÙ[<EFBFBD>ËœØ[™›ÞœØ[™›Þ\Ë™ØÚÙ\ˆ[\Ü<EFBFBD>ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>Ü[ۜ‚ˆœ›ÛHÝš^˜ÛÛ™šYË›ØY\ˆ[\Ü<EFBFBD>ØYÜÙ][™Üˆœ›ÛHÝš^œ<EFBFBD>[<EFBFBD>[YK™ØÚÙ\—ØÛY[<EFBFBD>[\Ü<EFBFBD>Ýš^ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>‚ˆÛY[<EFBFBD>HÝš^ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>
|
||||
ØÚÙ\‹™œ›ÛWÙ[<EFBFBD>Š
|
||||
JBˆÛY[<EFBFBD>œÝš^Øš[™Û[Ý[<EFBFBD>ÈHš[™Û[Ý[<EFBFBD>È܈×BˆÛY[<EFBFBD>š[XYÙWÜ[ÜÛXÞHHØYÜÙ][™ÜÊ
|
||||
Kœ<EFBFBD>[<EFBFBD>[YKš[XYÙWÜ[ÜÛXÞBˆÜ[ÛœÈHØÚÙ\”Ø[™›ÞÛY[<EFBFBD>Ü[ÛœÊ[XYÙOZ[XYÙK^ÜÙYÜÜ<EFBFBD>ÏY^ÜÙYÜÜ<EFBFBD>ÊBˆÙ\ÜÚ[ÛˆH]ØZ]ÛY[<EFBFBD>˜Ü™X]JÜ[ÛœÏ[Ü[ÛœËX[šY™\Ý[X[šY™\Ý
|
||||
Bˆ]ØZ]Ù\ÜÚ[Û‹œÝ\<EFBFBD>
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agents.sandbox.manifest import Manifest
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SandboxBackend = Callable[..., Awaitable[tuple[Any, Any]]]
|
||||
|
||||
|
||||
async def _docker_backend(
|
||||
*,
|
||||
image: str,
|
||||
manifest: Manifest,
|
||||
exposed_ports: tuple[int, ...],
|
||||
bind_mounts: list[dict[str, Any]] | None = None,
|
||||
) -> tuple[Any, Any]:
|
||||
"""Bring up a session backed by the local Docker daemon.
|
||||
|
||||
Uses :class:`StrixDockerSandboxClient` to inject NET_ADMIN /
|
||||
NET_RAW caps + ``host.docker.internal`` host-gateway. Imports
|
||||
``docker`` lazily so deployments that target a non-Docker
|
||||
backend don't need the docker-py library installed.
|
||||
|
||||
``session.start()`` is what materializes the manifest into the running
|
||||
container — the SDK's ``client.create()`` only builds the inner session
|
||||
object without applying it. ``async with session:`` would call it too, but
|
||||
Strix manages session lifetime explicitly via ``client.delete()`` so we
|
||||
trigger ``start()`` ourselves.
|
||||
"""
|
||||
import docker
|
||||
from agents.sandbox.sandboxes.docker import DockerSandboxClientOptions
|
||||
|
||||
from strix.config.loader import load_settings
|
||||
from strix.runtime.docker_client import StrixDockerSandboxClient
|
||||
|
||||
client = StrixDockerSandboxClient(docker.from_env())
|
||||
client.strix_bind_mounts = bind_mounts or []
|
||||
client.image_pull_policy = load_settings().runtime.image_pull_policy
|
||||
options = DockerSandboxClientOptions(image=image, exposed_ports=exposed_ports)
|
||||
session = await client.create(options=options, manifest=manifest)
|
||||
await session.start()
|
||||
return client, session
|
||||
|
||||
|
||||
_BACKENDS: dict[str, SandboxBackend] = {
|
||||
"docker": _docker_backend,
|
||||
}
|
||||
|
||||
_BIND_MOUNT_BACKENDS: set[str] = {"docker"}
|
||||
|
||||
|
||||
def get_backend(name: str) -> SandboxBackend:
|
||||
"""Return the backend factory for ``name`` or raise.
|
||||
|
||||
Args:
|
||||
name: Backend identifier (e.g. ``"docker"``). Match is exact;
|
||||
no fallback. Unknown values raise so config typos surface
|
||||
immediately instead of silently picking a default.
|
||||
"""
|
||||
backend = _BACKENDS.get(name)
|
||||
if backend is None:
|
||||
supported = ", ".join(sorted(_BACKENDS))
|
||||
raise ValueError(
|
||||
f"Unknown STRIX_RUNTIME_BACKEND: {name!r} (supported: {supported})",
|
||||
)
|
||||
logger.debug("Selected sandbox backend: %s", name)
|
||||
return backend
|
||||
|
||||
|
||||
def register_backend(
|
||||
name: str,
|
||||
backend: SandboxBackend,
|
||||
*,
|
||||
supports_bind_mounts: bool = False,
|
||||
) -> None:
|
||||
"""Register a custom backend under ``name``.
|
||||
|
||||
Intended for downstream users who ship their own runtime — register
|
||||
before any ``session_manager.create_or_reuse`` call. Re-registering
|
||||
an existing name overwrites the prior entry. ``supports_bind_mounts``
|
||||
defaults to False: a remote runtime cannot see the caller's filesystem, so
|
||||
it is handed local sources as manifest entries to upload instead.
|
||||
"""
|
||||
_BACKENDS[name] = backend
|
||||
if supports_bind_mounts:
|
||||
_BIND_MOUNT_BACKENDS.add(name)
|
||||
else:
|
||||
_BIND_MOUNT_BACKENDS.discard(name)
|
||||
logger.info("Registered sandbox backend: %s (bind mounts: %s)", name, supports_bind_mounts)
|
||||
|
||||
|
||||
def backend_supports_bind_mounts(name: str) -> bool:
|
||||
return name in _BIND_MOUNT_BACKENDS
|
||||
|
||||
|
||||
def supported_backends() -> list[str]:
|
||||
return sorted(_BACKENDS)
|
||||
Bˆ™]\›ˆÛY[<EFBFBD>Ù\ÜÚ[Û‚‚‚—Ð<EFBFBD>PÒÑS‘ΈXÝÜÝ‹Ø[™›Þ˜XÚÙ[™HHˆ™ØÚÙ\ˆŽˆÙØÚÙ\—ؘXÚÙ[™ŸB‚—Ð’S‘ÓSÕS•Ð<EFBFBD>PÒÑS‘ΈÙ]ÜÝ—HHÈ™ØÚÙ\ˆŸB‚‚™YˆÙ]ؘXÚÙ[™
|
||||
˜[YNˆÝŠHOˆØ[™›Þ˜XÚÙ[™‚ˆˆˆ”™]\›ˆH˜XÚÙ[™˜XÝÜžH›Üˆ˜[YX܈˜Z\ÙK‚‚ˆ\™Ü΂ˆ˜[YNˆ˜XÚÙ[™Y[<EFBFBD>YšY\ˆ
|
||||
K™Ëˆ™ØÚÙ\ˆ˜
|
||||
KˆX]Ú\È^X݈›È˜[˜XÚˈ[šÛ›ÝÛˆ˜[Y\ȘZ\ÙHÛÈÛÛ™šYÈ\ÜÈÝ\™˜XÙBˆ[[YYX][H[œÝXYÙˆÚ[[<EFBFBD>HXÚÚ[™ÈHY˜][‚ˆˆˆ‚ˆ˜XÚÙ[™HÐ<EFBFBD>PÒÑS‘Ë™Ù]
|
||||
˜[YJBˆYˆ˜XÚÙ[™\țۙN‚ˆÝ\Ü<EFBFBD>YH‹‹š›Ú[ŠÛÜ<EFBFBD>Y
|
||||
Ð<EFBFBD>PÒÑS‘ÊJBˆ˜Z\ÙH˜[YQ\œ›ÜŠˆˆ•[šÛ›ÝÛˆÕ’VÔ•S•SQWÐ<EFBFBD>PÒÑS‘ˆÛ˜[YH\ŸH
|
||||
Ý\Ü<EFBFBD>YˆÜÝ\Ü<EFBFBD>YJH‹ˆ
|
||||
BˆÙÙÙ\‹™X<EFBFBD>YÊ”Ù[XÝYØ[™›Þ˜XÚÙ[™ˆ \È‹˜[YJBˆ™]\›ˆ˜XÚÙ[™‚‚™Yˆ™YÚ\Ý\—ؘXÚÙ[™
|
||||
ˆ˜[YNˆÝ‹ˆ˜XÚÙ[™ˆØ[™›Þ˜XÚÙ[™ˆ
|
||||
‹ˆÝ\Ü<EFBFBD>רš[™Û[Ý[<EFBFBD>Έ›ÛÛH˜[ÙKŠHOˆ›Û™N‚ˆˆˆ”™YÚ\Ý\ˆHÝ\ÝÛH˜XÚÙ[™[™\ˆ˜[YX‚‚ˆ[<EFBFBD>[™Y›ÜˆÝۜݙX[H\Ù\œÈÚÈÚ\Z\ˆÝÛˆ<EFBFBD>[<EFBFBD>[YH8 %™YÚ\Ý\‚ˆ™Y›Ü™H[žHÙ\ÜÚ[Û—ÛX[˜YÙ\‹˜Ü™X]WÛÜ—Ü™]\ÙXØ[ˆ™K\™YÚ\Ý\š[™Âˆ[ˆ^\Ý[™È˜[YHÝ™\<EFBFBD>Üš]\ÈHš[܈[<EFBFBD>žKˆÝ\Ü<EFBFBD>רš[™Û[Ý[<EFBFBD>؈Y˜][ÈȘ[ÙNˆH™[[ÝH<EFBFBD>[<EFBFBD>[YHØ[››ÝÙYHHØ[\‰ÜÈš[\Þ\Ý[KÛˆ]\È[™YØØ[ÛÝ\˜Ù\È\ÈX[šY™\Ý[<EFBFBD>šY\ÈÈ\ØY[œÝXY‚ˆˆˆ‚ˆÐ<EFBFBD>PÒÑS‘ÖÛ˜[YWHH˜XÚÙ[™ˆYˆÝ\Ü<EFBFBD>רš[™Û[Ý[<EFBFBD>΂ˆÐ’S‘ÓSÕS•Ð<EFBFBD>PÒÑS‘˘Y
|
||||
˜[YJBˆ[ÙN‚ˆÐ’S‘ÓSÕS•Ð<EFBFBD>PÒÑS‘Ë™\ØØ\™
|
||||
˜[YJBˆÙÙÙ\‹š[™›Ê”™YÚ\Ý\™YØ[™›Þ˜XÚÙ[™ˆ \È
|
||||
š[™[Ý[<EFBFBD>Έ \ÊH‹˜[YKÝ\Ü<EFBFBD>רš[™Û[Ý[<EFBFBD>ÊB‚‚™Yˆ˜XÚÙ[™ÜÝ\Ü<EFBFBD>רš[™Û[Ý[<EFBFBD>ʘ[YNˆÝŠHOˆ›ÛÛ‚ˆ™]\›ˆ˜[YH[ˆÐ’S‘ÓSÕS•Ð<EFBFBD>PÒÑS‘‚‚™YˆÝ\Ü<EFBFBD>YؘXÚÙ[™Ê
|
||||
HOˆ\ÝÜÝ—N‚ˆ™]\›ˆÛÜ<EFBFBD>Y
|
||||
Ð<EFBFBD>PÒÑS‘ÊB
|
||||
|
|
@ -1,114 +1,307 @@
|
|||
r‡^Ñf¥–ئ{[r‰Ý°ë¦ëHˆˆ”Ýš^ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>8 %™\Ù\<EFBFBD>™\ÈH[XYÙIÜÈS•–TÒS•[™Y“‘UÐQRS‹Ó‘UÔ<EFBFBD>UÈØ\Xš[]Y\È
|
||||
ÈÜÝYØ]]Ø^K‚‚•HÑÉÜÈØÚÙ\”Ø[™›ÞÛY[<EFBFBD>—ØÜ™X]WØÛÛ<EFBFBD>Z[™\˜Ù\È›Ý^ÜÙHHÛÚț܂™^[™[™ÈÜ™X]WÚÝØ\™ÜØ™Y›Ü™HÛÛ<EFBFBD>Z[™\œË˜Ü™X]X\ÈØ[YˆÙHÝX˜Û\ܘ[™™Z[\[Y[<EFBFBD>HY]Ù›ÙH™\˜˜][Hœ›ÛHHÑÈÛÝ\˜ÙKÚ]™YB™[\΂‚ŒKˆ›ÜHÑÉÜÈ[<EFBFBD>ž\Ú[<EFBFBD>VÈ<EFBFBD>Z[—XÝ™\œšYNÈÝ\HÈ<EFBFBD>Z[‹‹Yˆ‹ˆ‹Ù]‹Û<EFBFBD>[—X\ÈÛÛ[X[™[œÝXYˆ\È]ÈÝ\ˆ[XYÙI܈ØÚÙ\‹Y[<EFBFBD>ž\Ú[<EFBFBD>œÚXÝX[H<EFBFBD>[ˆ8 %Ú]Ý]]ØZYËXÛX™]™\‚ˆÝ\<EFBFBD>È[œÚYHHÛÛ<EFBFBD>Z[™\ˆ[™›ÛÝݘ\ØØZYØ™]šY\ÈYØZ[œÝBˆXYÜ<EFBFBD>‚Œ‹ˆ\[™‘UÐQRS‹Ó‘UÔ<EFBFBD>UÈÈØ\ØY
|
||||
™\]Z\™YžH›X\\ÔØ[™ˆÝ\ˆ˜]Ë\ÛØÚÙ]ÛÛÊK‚ŒËˆYÜÝ™ØÚÙ\‹š[<EFBFBD>\›˜[8¡¤ˆÜÝYØ]]Ø^HÈ^˜WÚÜÝØÛÈBˆYÙ[<EFBFBD>Ø[ˆ™XXÚÜÝ\Ù\<EFBFBD>™Y\Ë‚‚”[›™YÈÜ[˜ZKXYÙ[<EFBFBD>ÏOLŒM<EFBFBD>˜ˆ<EFBFBD>[\[™ÈHÑÈ™\]Z\™\œ™K[Y\™Ú[™ÈH\™[<EFBFBD>›ÙKˆ˜XÚÈ\Ý™X[H›Üˆ[ˆ[š™XÝ[ÛˆÛÚË‚ˆˆˆ‚‚™œ›ÛH×Ù<EFBFBD>]\™W×È[\Ü<EFBFBD>[››Ý][ۜ‚š[\Ü<EFBFBD>ÛÛ<EFBFBD>^X‚š[\Ü<EFBFBD>ÙÙÚ[™Âš[\Ü<EFBFBD>Üš[\Ü<EFBFBD>]ZY™œ›ÛH\[™È[\Ü<EFBFBD>[žK]\˜[Ø\Ý‚™œ›ÛHYÙ[<EFBFBD>ËœØ[™›Þ™\œ›ÜœÈ[\Ü<EFBFBD>^ÜÙYÜ<EFBFBD>[˜]˜Z[X›Q\œ›Ü‚™œ›ÛHYÙ[<EFBFBD>ËœØ[™›Þ›X[šY™\Ý[\Ü<EFBFBD>X[šY™\Ý™œ›ÛHYÙ[<EFBFBD>ËœØ[™›ÞœØ[™›Þ\Ë™ØÚÙ\ˆ[\Ü<EFBFBD>
|
||||
ˆØÚÙ\”Ø[™›ÞÛY[<EFBFBD>ˆØÚÙ\”Ø[™›ÞÙ\ÜÚ[Û‹ˆØ<EFBFBD>Z[ÙØÚÙ\—Ý›Û[YWÛ[Ý[<EFBFBD>ËˆÙØÚÙ\—ÜÜ<EFBFBD>ÚÙ^KˆÛX[šY™\ÝÜ™\]Z\™\×Ù<EFBFBD>\ÙKˆÛX[šY™\ÝÜ™\]Z\™\×ÜÞ\רYZ[‹ŠB™œ›ÛHYÙ[<EFBFBD>ËœØ[™›ÞœÙ\ÜÚ[Û‹œØ[™›ÞÜÙ\ÜÚ[Ûˆ[\Ü<EFBFBD>Ø[™›ÞÙ\ÜÚ[Û‚™œ›ÛHYÙ[<EFBFBD>ËœØ[™›Þ<EFBFBD>\\È[\Ü<EFBFBD>^ÜÙYÜ<EFBFBD>[™Ú[<EFBFBD>™œ›ÛHØÚÙ\ˆ[\Ü<EFBFBD>\œ›ÜœÈ\ÈØÚÙ\—Ù\œ›ÜœÈÈ\NˆYۛܙVÚ[\Ü<EFBFBD>][<EFBFBD>\Y[<EFBFBD>\ÙYZYۛܙWB™œ›ÛHØÚÙ\‹›[Ù[˘ÛÛ<EFBFBD>Z[™\œÈ[\Ü<EFBFBD>ÛÛ<EFBFBD>Z[™\ˆÈ\NˆYۛܙVÚ[\Ü<EFBFBD>][<EFBFBD>\Y[<EFBFBD>\ÙYZYۛܙWB™œ›ÛHØÚÙ\‹<EFBFBD>\\È[\Ü<EFBFBD>ÙÐÛÛ™šYÈÈ\NˆYۛܙVÚ[\Ü<EFBFBD>][<EFBFBD>\Y[<EFBFBD>\ÙYZYۛܙWB™œ›ÛHØÚÙ\‹<EFBFBD>\\È[\Ü<EFBFBD>[Ý[<EFBFBD>\ÈØÚÙ\”ÑÓ[Ý[<EFBFBD>È\NˆYۛܙVÚ[\Ü<EFBFBD>][<EFBFBD>\Y[<EFBFBD>\ÙYZYۛܙWB™œ›ÛHØÚÙ\‹<EFBFBD>][È[\Ü<EFBFBD>\œÙWÜ™\ÜÚ]ÜžWÝYÈÈ\NˆYۛܙVÚ[\Ü<EFBFBD>][<EFBFBD>\Y[<EFBFBD>\ÙYZYۛܙWB™œ›ÛH™\]Y\ÝË™^Ù\[ÛœÈ[\Ü<EFBFBD>™\]Y\Ý^Ù\[Û‚‚‚›ÙÙÙ\ˆHÙÙÚ[™Ë™Ù]ÙÙÙ\Š×Û˜[YW×ÊB‚‚—ÔÐS‘“ÖÓ‘UÓÔ’×ÑS•ˆH”Õ’VÑÐÒÑT—ÔÐS‘“ÖÓ‘UÓÔ’È‚‚‚™YˆÙ[œÝ\™WÚ[XYÙWØ]˜Z[X›JˆÛY[<EFBFBD>ˆ[žKˆ[XYÙNˆÝ‹ˆ[ÜÛXÞNˆ]\˜[Ș]]È‹›™]™\ˆ—KŠHOˆ›Û™N‚ˆYˆÛY[<EFBFBD>š[XYÙWÙ^\ÝÊ[XYÙJN‚ˆ™]\›‚ˆYˆ[ÜÛXÞHOH›™]™\ˆŽ‚ˆ˜Z\ÙH<EFBFBD>[<EFBFBD>[YQ\œ›ÜŠˆˆ‘ØÚÙ\ˆ[XYÙHÚ[XYÙH\ŸH\È›Ý]˜Z[X›HØØ[H[™‚ˆ”Õ’VÒSPQÑWÔSÔÓPÖO[™]™\ˆ›Ü˜šYÈ[[™È]‚ˆ
|
||||
Bˆ™\ËYÈH\œÙWÜ™\ÜÚ]ÜžWÝYÊ[XYÙJBˆÛY[<EFBFBD>™ØÚÙ\—ØÛY[<EFBFBD>š[XYÙ\Ëœ[
|
||||
™\ËYÏ]YÈ܈›Û™K[ÝYÜÏQ˜[ÙJB‚‚™YˆÜØ[™›ÞÛ™]ÛÜšÊ
|
||||
HOˆÝˆ›Û™N‚ˆ˜[YHHÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
ÔÐS‘“ÖÓ‘UÓÔ’×ÑS•‹ˆŠKœÝš\
|
||||
r‡^Ñf¥–ئ{O,yÊ'vî¶›"""StrixDockerSandboxClient — preserves the image's ENTRYPOINT and adds
|
||||
NET_ADMIN/NET_RAW capabilities + host-gateway.
|
||||
|
||||
Bˆ™]\›ˆ˜[YH܈›Û™B‚‚™YˆØ\WÜØ[™›ÞÛ™]ÛÜšÊÜ™X]WÚÝØ\™ÜΈXÝÜÝ‹[žWJHOˆ›Û™N‚ˆ™]ÛÜšÈHÜØ[™›ÞÛ™]ÛÜšÊ
|
||||
BˆYˆ™]Ûܚ΂ˆÜ™X]WÚÝØ\™ÜÖÈ›™]ÛܚȗHH™]ÛܚˆܙX]WÚÝØ\™ÜËœÜ
|
||||
œÜ<EFBFBD>È‹›Û™JB‚‚™YˆØ\WÜ™\ÛÝ\˜ÙWÛ[Z]ÊÜ™X]WÚÝØ\™ÜΈXÝÜÝ‹[žWJHOˆ›Û™N‚ˆˆˆ<EFBFBD>\HÜ[Û˜[ÙÜ›Ý\™\ÛÝ\˜ÙHØ\Èœ›ÛHH[<EFBFBD>š\›Û›Y[<EFBFBD>ˆ[œÙ]Ø›[šÂˆ˜[Y\ÈX]™HØÚÙ\‰ÜÈY˜][
|
||||
[˜›Ý[™Y
|
||||
KÛÈ\È\ÈÜZ[ˆ\ˆÜ݈ˆˆ‚ˆY[WÛ[Z]HÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÓQSWÓSRU‹ˆŠKœÝš\
|
||||
The SDK's ``DockerSandboxClient._create_container`` does not expose a hook for
|
||||
extending ``create_kwargs`` before ``containers.create`` is called. We subclass
|
||||
and reimplement the method body verbatim from the SDK source, with three
|
||||
deltas:
|
||||
|
||||
BˆYˆY[WÛ[Z]‚ˆÜ™X]WÚÝØ\™ÜÖÈ›Y[WÛ[Z]—HHY[WÛ[Z]‚ˆÚWÜÚ^™HHÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÔÒWÔÒV‘H‹ˆŠKœÝš\
|
||||
1. Drop the SDK's ``entrypoint=["tail"]`` override; supply ``["tail", "-f",
|
||||
"/dev/null"]`` as ``command`` instead. This lets our image's
|
||||
``docker-entrypoint.sh`` actually run — without it, ``caido-cli`` never
|
||||
starts inside the container and ``bootstrap_caido`` retries against a
|
||||
dead port.
|
||||
2. Append NET_ADMIN/NET_RAW to ``cap_add`` (required by ``nmap -sS`` and
|
||||
other raw-socket tools).
|
||||
3. Add ``host.docker.internal`` → host-gateway to ``extra_hosts`` so the
|
||||
agent can reach host-served apps.
|
||||
|
||||
BˆYˆÚWÜÚ^™N‚ˆÜ™X]WÚÝØ\™ÜÖÈœÚWÜÚ^™H—HHÚWÜÚ^™B‚ˆÜ\ÈHÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÐÔTÈ‹ˆŠKœÝš\
|
||||
Pinned to ``openai-agents==0.14.6``. Bumping the SDK requires
|
||||
re-merging the parent body. Track upstream for an injection hook.
|
||||
"""
|
||||
|
||||
BˆYˆÜ\΂ˆÚ]ÛÛ<EFBFBD>^X‹œÝ\™\Üʘ[YQ\œ›Ü‹Ý™\™›ÝÑ\œ›ÜŠN‚ˆ˜[›×ØÜ\ÈH[<EFBFBD>
|
||||
ݯ]
|
||||
Ü\ÊH
|
||||
ˆWÌÌÌ
|
||||
BˆYˆ˜[›×ØÜ\ÈHŠŠ<EFBFBD>ŒÈHN‚ˆÜ™X]WÚÝØ\™ÜÖÈ›˜[›×ØÜ\È—HH˜[›×ØÜ\‚ˆY×Û[Z]HÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÔQ×ÓSRU‹ˆŠKœÝš\
|
||||
from __future__ import annotations
|
||||
|
||||
BˆYˆY×Û[Z]‚ˆÚ]ÛÛ<EFBFBD>^X‹œÝ\™\Üʘ[YQ\œ›ÜŠN‚ˆÜ™X]WÚÝØ\™ÜÖÈœY×Û[Z]—HH[<EFBFBD>
|
||||
Y×Û[Z]
|
||||
B‚‚™YˆØ\WÛÙ×Û[Z]ÊÜ™X]WÚÝØ\™ÜΈXÝÜÝ‹[žWJHOˆ›Û™N‚ˆˆˆ<EFBFBD>›Ý[™HÛÛ<EFBFBD>Z[™\‰ÜÈœÛÛ‹Yš[HÙÈÛÈH<EFBFBD>[˜]Ø^H›ØÙ\ÜÈ[ˆHØ[™›Þˆ
|
||||
K™ËˆHÛÛ]<EFBFBD>\ÞK[ÛÜÈÜš][™ÈÈÝÝ]
|
||||
HØ[››Ýš[HÜÝ\Úˆ[™ZÙHHØÚÙ\ˆY[[ÛˆÝÛˆÚ]]‚‚ˆ[›ZÙHHÙÜ›Ý\Ø\ÈX›Ý™K\ÈY˜][È
|
||||
Š›ÛŠŠˆ8 %ØÚÙ\‰ÜÈÝÛˆY˜][ˆ\È[ˆ[˜›Ý[™YœÛÛ‹Yš[KÚXÚ\È[œØY™H›Üˆ[ˆ]]Û›Û[Ý\ÈYÙ[<EFBFBD>]ˆ^XÝ]\È\˜š]˜\žHÛÛ[X[™ËˆX^Yš[X›Ý][ÛˆYX[œÈHÛ‹Y\ÚÈØ\\ˆX^\Ú^™H
|
||||
ˆX^Yš[XˆÙ]Õ’VÔÐS‘“ÖÓÑ×ÓPVÔÒV‘XÈØÙ™˜ˆÈܘXÚÈÝ]ÈØÚÙ\‰ÜÈY˜][ˆˆˆ‚ˆX^ÜÚ^™HHÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÓÑ×ÓPVÔÒV‘H‹<EFBFBD>LHŠKœÝš\
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
BˆYˆX^ÜÚ^™K›ÝÙ\Š
|
||||
H[ˆ
|
||||
Œ‹›Ù™ˆ‹››Û™H‹<EFBFBD>[›[Z]YŠN‚ˆ™]\›‚ˆX^Ùš[HHÜË™[<EFBFBD>š\›Û‹™Ù]
|
||||
”Õ’VÔÐS‘“ÖÓÑ×ÓPVÑ’SH‹ŒÈŠKœÝš\
|
||||
from agents.sandbox.errors import ExposedPortUnavailableError
|
||||
from agents.sandbox.manifest import Manifest
|
||||
from agents.sandbox.sandboxes.docker import (
|
||||
DockerSandboxClient,
|
||||
DockerSandboxSession,
|
||||
_build_docker_volume_mounts,
|
||||
_docker_port_key,
|
||||
_manifest_requires_fuse,
|
||||
_manifest_requires_sys_admin,
|
||||
)
|
||||
from agents.sandbox.session.sandbox_session import SandboxSession
|
||||
from agents.sandbox.types import ExposedPortEndpoint
|
||||
from docker import errors as docker_errors # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.types import LogConfig # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.types import Mount as DockerSDKMount # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.utils import parse_repository_tag # type: ignore[import-untyped, unused-ignore]
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
H܈ŒÈ‚ˆÜ™X]WÚÝØ\™ÜÖÈ›ÙרÛÛ™šYÈ—HHÙÐÛÛ™šYʈ\OSÙÐÛÛ™šYË<EFBFBD>\\Ë’”ÓÓ‹ˆÛÛ™šYÏ^È›X^\Ú^™HŽˆX^ÜÚ^™K›X^Yš[HŽˆX^Ùš[_Kˆ
|
||||
B‚‚™YˆØ\WÜ<EFBFBD>[—ÛX™[ÊÜ™X]WÚÝØ\™ÜΈXÝÜÝ‹[žWJHOˆ›Û™N‚ˆ<EFBFBD>[—ÚYHÜË™Ù][<EFBFBD>ДՒVÔ•S—ÒQŠBˆYˆ›Ý<EFBFBD>[—ÚY‚ˆ™]\›‚ˆX™[ÈHÜ™X]WÚÝØ\™ÜËœÙ]Y˜][
|
||||
›X™[È‹ßJBˆYˆ›Ý\Ú[œÝ[˜ÙJX™[ËXÝ
|
||||
N‚ˆ™]\›‚ˆX™[ÖȜݚ^\<EFBFBD>[‹ZY—HH<EFBFBD>[—ÚYˆ<EFBFBD>[—Ý\HHÜË™Ù][<EFBFBD>ДՒVÔ•S—ÕTHŠBˆYˆ<EFBFBD>[—Ý\N‚ˆX™[ÖȜݚ^\<EFBFBD>[‹]\H—HH<EFBFBD>[—Ý\B‚‚˜Û\ÜÈÝš^ØÚÙ\”Ø[™›ÞÙ\ÜÚ[ÛŠØÚÙ\”Ø[™›ÞÙ\ÜÚ[ÛŠN‚ˆØ[™›ÞÛ™]ÛܚΈ݈Hˆ‚‚ˆ\Þ[˜ÈYˆÜ™\ÛÛ™WÙ^ÜÙYÜÜ<EFBFBD>
|
||||
Ù[‹Ü<EFBFBD>ˆ[<EFBFBD>
|
||||
HOˆ^ÜÙYÜ<EFBFBD>[™Ú[<EFBFBD>‚ˆžN‚ˆÙ[‹—ØÛÛ<EFBFBD>Z[™\‹œ™[ØY
|
||||
|
||||
Bˆ^Ù\ØÚÙ\—Ù\œ›ÜœË<EFBFBD>TQ\œ›Üˆ\ÈN‚ˆ˜Z\ÙH^ÜÙYÜ<EFBFBD>[˜]˜Z[X›Q\œ›ÜŠˆÜ<EFBFBD>\Ü<EFBFBD>ˆ^ÜÙYÜÜ<EFBFBD>Ï\Ù[‹œÝ]K™^ÜÙYÜÜ<EFBFBD>ˈ™X\ÛÛ<EFBFBD>H˜˜XÚÙ[™Ý[˜]˜Z[X›H‹ˆÛÛ<EFBFBD>^^ˆ˜˜XÚÙ[™Žˆ™ØÚÙ\ˆ‹ˆ™]Z[Žˆ˜ÛÛ<EFBFBD>Z[™\—Ü™[ØYÙ˜Z[Y‹ˆ›™]ÛܚȎˆÙ[‹œØ[™›ÞÛ™]ÛܚˈKˆØ]\ÙOYKˆ
|
||||
Hœ›ÛHB‚ˆ]œÈHÙ]]ŠÙ[‹—ØÛÛ<EFBFBD>Z[™\‹˜]œÈ‹ßJH܈ßBˆ™]ÛÜšÜÈH]œË™Ù]
|
||||
“™]ÛÜšÔÙ][™ÜÈ‹ßJK™Ù]
|
||||
“™]ÛÜšÜÈ‹ßJBˆ[™Ú[<EFBFBD>H™]ÛÜšÜË™Ù]
|
||||
Ù[‹œØ[™›ÞÛ™]ÛÜšÊH܈ßBˆ\H[™Ú[<EFBFBD>™Ù]
|
||||
’TY™\ÜÈŠH܈[™Ú[<EFBFBD>™Ù]
|
||||
‘ÛØ˜[T<EFBFBD><EFBFBD>Y™\ÜÈŠBˆYˆ›Ý\Ú[œÝ[˜ÙJ\ÝŠH܈›Ý\‚ˆ˜Z\ÙH^ÜÙYÜ<EFBFBD>[˜]˜Z[X›Q\œ›ÜŠˆÜ<EFBFBD>\Ü<EFBFBD>ˆ^ÜÙYÜÜ<EFBFBD>Ï\Ù[‹œÝ]K™^ÜÙYÜÜ<EFBFBD>ˈ™X\ÛÛ<EFBFBD>H˜˜XÚÙ[™Ý[˜]˜Z[X›H‹ˆÛÛ<EFBFBD>^^ˆ˜˜XÚÙ[™Žˆ™ØÚÙ\ˆ‹ˆ™]Z[Žˆ˜ÛÛ<EFBFBD>Z[™\—Û›ÝÛÛ—Û™]Ûܚȋˆ›™]ÛܚȎˆÙ[‹œØ[™›ÞÛ™]ÛܚˈKˆ
|
||||
BˆÜÝHˆ–ÞÚ\WHˆYˆŽˆˆ[ˆ\[ÙH\ˆ™]\›ˆ^ÜÙYÜ<EFBFBD>[™Ú[<EFBFBD>
|
||||
ÜÝZÜÝÜ<EFBFBD>\Ü<EFBFBD>ÏQ˜[ÙJB‚‚˜Û\ÜÈÝš^ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>
|
||||
ØÚÙ\”Ø[™›ÞÛY[<EFBFBD>
|
||||
N‚ˆÈÜÝ\™XÝÜšY\ÈÈš[™[[Ý[<EFBFBD>[<EFBFBD>ÈHÛÛ<EFBFBD>Z[™\‹Ù]žHHØÚÙ\‚ˆÈ˜XÚÙ[™™Y›Ü™HÜ™X]J
|
||||
XˆXXÚ][H\ÈÜÛÝ\˜ÙK\™Ù]™XYÛÛ›_X‚ˆÝš^Øš[™Û[Ý[<EFBFBD>Έ\ÝÙXÝÜÝ‹[žWWH›Û™HH›Û™Bˆ[XYÙWÜ[ÜÛXÞNˆ]\˜[Ș]]È‹›™]™\ˆ—HH˜]]È‚‚ˆ\Þ[˜ÈYˆØÜ™X]WØÛÛ<EFBFBD>Z[™\ŠˆÙ[‹ˆ[XYÙNˆÝ‹ˆ
|
||||
‹ˆX[šY™\݈X[šY™\ݛۙHH›Û™Kˆ^ÜÙYÜÜ<EFBFBD>Έ\VÚ[<EFBFBD>‹‹—HH
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
KˆÙ\ÜÚ[Û—ÚYˆ]ZY•URQ›Û™HH›Û™Kˆ
|
||||
HOˆÛÛ<EFBFBD>Z[™\Ž‚ˆÈKKKKH‘QÒSˆ‘T<EFBFBD><EFBFBD>USHÓÔHÙˆØÚÙ\”Ø[™›ÞÛY[<EFBFBD>—ØÜ™X]WØÛÛ<EFBFBD>Z[™\ˆKKKKBˆÈÑÈ™YŽˆÜ˜ËØYÙ[<EFBFBD>ËÜØ[™›ÞÜØ[™›Þ\ËÙØÚÙ\‹œNŒMÍLM
ÍÈ
|
||||
ŒŒM<EFBFBD>ŠK‚ˆÙ[œÝ\™WÚ[XYÙWØ]˜Z[X›JÙ[‹[XYÙKÙ[‹š[XYÙWÜ[ÜÛXÞJB‚ˆ\ÜÙ\<EFBFBD>Ù[‹š[XYÙWÙ^\ÝÊ[XYÙJBˆ[<EFBFBD>š\›Û›Y[<EFBFBD>ˆXÝÜ݋ݗH›Û™HH›Û™BˆYˆX[šY™\Ý‚ˆ[<EFBFBD>š\›Û›Y[<EFBFBD>H]ØZ]X[šY™\Ý™[<EFBFBD>š\›Û›Y[<EFBFBD>œ™\ÛÛ™J
|
||||
BˆÈÝš^[Hœ›ÛHHÑÈ›ÙNˆ›Ü[<EFBFBD>ž\Ú[<EFBFBD>Ý™\œšYH[™ˆÈÝ\HZ[YˆÙ]‹Û<EFBFBD>[\ÈÛÛ[X[™ÛÈH[XYÙI܈ÈS•–TÒS•
|
||||
ØÚÙ\‹Y[<EFBFBD>ž\Ú[<EFBFBD>œÚ
|
||||
H<EFBFBD>[œÈÙ]\[ˆ^Xˆȉ˜™XÛÛY\È^XÈZ[YˆÙ]‹Û<EFBFBD>[›ÜˆHÙY\X[]™K‚ˆÈÚ]Ý]\ËØZYËXÛH
|
||||
ÈH[‹XÛÛ<EFBFBD>Z[™\ˆÐH<EFBFBD>\Ý™]™\ˆÙ]ˆÈ[š]X[^™Y‚ˆÜ™X]WÚÝØ\™ÜΈXÝÜÝ‹[žWHHˆš[XYÙHŽˆ[XYÙKˆ™]XÚŽˆ<EFBFBD>YKˆ˜ÛÛ[X[™ŽˆÈ<EFBFBD>Z[‹‹Yˆ‹‹Ù]‹Û<EFBFBD>[—Kˆ™[<EFBFBD>š\›Û›Y[<EFBFBD>Žˆ[<EFBFBD>š\›Û›Y[<EFBFBD>ˆBˆYˆX[šY™\Ý\țݛۙN‚ˆØÚÙ\—Û[Ý[<EFBFBD>ÈHØ<EFBFBD>Z[ÙØÚÙ\—Ý›Û[YWÛ[Ý[<EFBFBD>ʈX[šY™\݈Ù\ÜÚ[Û—ÚY\Ù\ÜÚ[Û—ÚYˆ
|
||||
BˆYˆØÚÙ\—Û[Ý[<EFBFBD>΂ˆÜ™X]WÚÝØ\™ÜÖÈ›[Ý[<EFBFBD>È—HHØÚÙ\—Û[Ý[<EFBFBD>ˆYˆÛX[šY™\ÝÜ™\]Z\™\×Ù<EFBFBD>\ÙJX[šY™\Ý
|
||||
N‚ˆÜ™X]WÚÝØ\™ÜË<EFBFBD>\]Jˆ]šXÙ\ÏVÈ‹Ù]‹Ù<EFBFBD>\ÙH—KˆØ\ØYVÈ”ÖT×ÐQRSˆ—KˆÙXÝ\š]WÛÜVȘ\\›[ÜŽ<EFBFBD>[˜ÛÛ™š[™Y—Kˆ
|
||||
Bˆ[YˆÛX[šY™\ÝÜ™\]Z\™\×ÜÞ\רYZ[ŠX[šY™\Ý
|
||||
N‚ˆÜ™X]WÚÝØ\™ÜË<EFBFBD>\]JˆØ\ØYVÈ”ÖT×ÐQRSˆ—KˆÙXÝ\š]WÛÜVȘ\\›[ÜŽ<EFBFBD>[˜ÛÛ™š[™Y—Kˆ
|
||||
BˆYˆ^ÜÙYÜÜ<EFBFBD>΂ˆÜ™X]WÚÝØ\™ÜÖÈœÜ<EFBFBD>È—HHÂˆÙØÚÙ\—ÜÜ<EFBFBD>ÚÙ^JÜ<EFBFBD>
|
||||
Nˆ
|
||||
ŒL<EFBFBD>ËŒŒŒH‹›Û™JH›ÜˆÜ<EFBFBD>[ˆ^ÜÙYÜÜ<EFBFBD>ˆBˆÈKKKKHS‘‘T<EFBFBD><EFBFBD>USHÓÔHKKKKB‚ˆÈÝš^[š™XÝ[ÛœÈ8 %\[™Û‰ÝÝ™\<EFBFBD>Üš]KÛÈ•TÑKÔÖT×ÐQRSˆÝ\<EFBFBD>š]™\Ë‚ˆØ\ØYHÜ™X]WÚÝØ\™ÜËœÙ]Y˜][
|
||||
˜Ø\ØY‹×JBˆYˆ›Ý\Ú[œÝ[˜ÙJØ\ØY\Ý
|
||||
N‚ˆØ\ØYH\Ý
|
||||
Ø\ØY
|
||||
BˆÜ™X]WÚÝØ\™ÜÖȘØ\ØY—HHØ\ØYˆ›ÜˆØ\[ˆ
|
||||
“‘UÐQRSˆ‹“‘UÔ<EFBFBD>UÈŠN‚ˆYˆØ\›Ý[ˆØ\ØY‚ˆØ\ØY˜\[™
|
||||
Ø\
|
||||
B‚ˆ^˜WÚÜÝÈHÜ™X]WÚÝØ\™ÜËœÙ]Y˜][
|
||||
™^˜WÚÜÝÈ‹ßJBˆ^˜WÚÜÝÖÈšÜÝ™ØÚÙ\‹š[<EFBFBD>\›˜[—HHšÜÝYØ]]Ø^H‚‚ˆØ\WÜØ[™›ÞÛ™]ÛÜšÊÜ™X]WÚÝØ\™ÜÊBˆØ\WÜ™\ÛÝ\˜ÙWÛ[Z]ÊÜ™X]WÚÝØ\™ÜÊBˆØ\WÛÙ×Û[Z]ÊÜ™X]WÚÝØ\™ÜÊBˆØ\WÜ<EFBFBD>[—ÛX™[ÊÜ™X]WÚÝØ\™ÜÊB‚ˆÈÝš^[š™XÝ[ÛŽˆØØ[ÛÝ\˜ÙH™Y\ËÛÜ<EFBFBD>YÚ[ÝÙ\ÝYš\œÝÛÈBˆÈ™\ÝYÜXÈ[™ÈÛˆÜÙˆH™YH]ÛÝ™\œË‚ˆš[™Û[Ý[<EFBFBD>ÈHÙ[‹œÝš^Øš[™Û[Ý[<EFBFBD>È܈
|
||||
|
||||
BˆYˆš[™Û[Ý[<EFBFBD>΂ˆ[Ý[<EFBFBD>ÈHÜ™X]WÚÝØ\™ÜËœÙ]Y˜][
|
||||
›[Ý[<EFBFBD>È‹×JBˆ›ÜˆÜXÈ[ˆÛÜ<EFBFBD>Y
|
||||
š[™Û[Ý[<EFBFBD>ËÙ^O[[X™HΈ݊ÖÈ<EFBFBD>\™Ù]—JK˜ÛÝ[<EFBFBD>
|
||||
‹ÈŠJN‚ˆ[Ý[<EFBFBD>˘\[™
|
||||
ˆØÚÙ\”ÑÓ[Ý[<EFBFBD>
|
||||
ˆ\™Ù]\ÜXÖÈ<EFBFBD>\™Ù]—KˆÛÝ\˜ÙO\ÜXÖÈœÛÝ\˜ÙH—Kˆ\OH˜š[™‹ˆ™XYÛÛ›O\ÜXË™Ù]
|
||||
œ™XYÛÛ›H‹˜[ÙJKˆ
|
||||
Bˆ
|
||||
B‚ˆÙÙÙ\‹™X<EFBFBD>Yʈ<EFBFBD>Ü™X][™ÈØ[™›ÞÛÛ<EFBFBD>Z[™\Žˆ[XYÙOI\ÈØ\ÏI\È^ÜÙYÜÜ<EFBFBD>ÏI\È‹ˆ[XYÙKˆØ\ØYˆ\Ý
|
||||
^ÜÙYÜÜ<EFBFBD>ÊKˆ
|
||||
BˆÛÛ<EFBFBD>Z[™\ˆHÙ[‹™ØÚÙ\—ØÛY[<EFBFBD>˜ÛÛ<EFBFBD>Z[™\œË˜Ü™X]J
|
||||
Š˜Ü™X]WÚÝØ\™ÜÊBˆÙÙÙ\‹š[™›Êˆ”Ø[™›ÞÛÛ<EFBFBD>Z[™\ˆÜ™X]YˆYI\È[XYÙOI\È‹ˆÛÛ<EFBFBD>Z[™\‹œÚÜ<EFBFBD>ÚYYˆ\Ø]ŠÛÛ<EFBFBD>Z[™\‹œÚÜ<EFBFBD>ÚYŠH[ÙH<EFBFBD>È‹ˆ[XYÙKˆ
|
||||
Bˆ™]\›ˆÛÛ<EFBFBD>Z[™\‚‚ˆ\Þ[˜ÈYˆÜ™X]JÙ[‹
|
||||
ŠšÝØ\™ÜΈ[žJHOˆØ[™›ÞÙ\ÜÚ[ÛŽ‚ˆÙ\ÜÚ[ÛˆH]ØZ]Ý\\Š
|
||||
K˜Ü™X]J
|
||||
ŠšÝØ\™ÜÊBˆ™]ÛÜšÈHÜØ[™›ÞÛ™]ÛÜšÊ
|
||||
Bˆ[›™\ˆHÙ\ÜÚ[Û‹—Ú[›™\‚ˆYˆ™]ÛÜšÈ[™\Ú[œÝ[˜ÙJ[›™\‹ØÚÙ\”Ø[™›ÞÙ\ÜÚ[ÛŠN‚ˆ[›™\‹—רÛ\Ü××ÈHÝš^ØÚÙ\”Ø[™›ÞÙ\ÜÚ[Û‚ˆØ\Ý
|
||||
”Ýš^ØÚÙ\”Ø[™›ÞÙ\ÜÚ[Ûˆ‹[›™\ŠKœØ[™›ÞÛ™]ÛÜšÈH™]Ûܚˆ™]\›ˆÙ\ÜÚ[Û‚‚ˆ\Þ[˜ÈYˆ[]JÙ[‹Ù\ÜÚ[ÛŽˆØ[™›ÞÙ\ÜÚ[ÛŠHOˆØ[™›ÞÙ\ÜÚ[ÛŽ‚ˆÛÛ<EFBFBD>Z[™\—ÚYHÙ]]ŠÙ]]ŠÙ\ÜÚ[Û‹—Ú[›™\‹œÝ]H‹›Û™JK˜ÛÛ<EFBFBD>Z[™\—ÚY‹›Û™JBˆYˆÛÛ<EFBFBD>Z[™\—ÚY‚ˆÈ™\ÝYY™›Ü<EFBFBD>Ú[ˆ›Ý›Ý[™ÐTQ\œ›ÜˆÛÝ™\ˆHÛÛ™H܈[š\BˆÈÛÛ<EFBFBD>Z[™\‹ˆ™\]Y\Ý^Ù\[ÛˆÛÝ™\œÈHÜ›‹YÝÛˆY[[ÛˆÛØÚÙ]8 %ˆÈÛÛ<EFBFBD>Z[™\œË™Ù]
|
||||
_SANDBOX_NETWORK_ENV = "STRIX_DOCKER_SANDBOX_NETWORK"
|
||||
|
||||
HOˆ[œÜXÝØÛÛ<EFBFBD>Z[™\ˆ˜Z\Ù\È™\]Y\ÝɈÈÛÛ›™XÝ[Û‘\œ›Ü‹ÚXÚ\ÈHÚX›[™ÈÙˆØÚÙ\‹™\œ›ÜœË<EFBFBD>TQ\œ›Ü‚ˆÈ[™\ˆ™\]Y\ÝË”™\]Y\Ý^Ù\[Ûˆ
|
||||
›ÝHÝX˜Û\ÜÊKÛÈ]\ØØ\\ˆÈ[ˆTQ\œ›Ü‹[Û›HÝ\™\ÜÈ[™Ý\™˜XÙ\ÈH<EFBFBD>[˜XÙX˜XÚÈ]™[‚ˆÈÝYÚ\ÈX\™ÝÛˆ\ÈYX[<EFBFBD>È™H™\ÝYY™›Ü<EFBFBD>‚ˆÚ]ÛÛ<EFBFBD>^X‹œÝ\™\ÜʈØÚÙ\—Ù\œ›ÜœË“›Ý›Ý[™ØÚÙ\—Ù\œ›ÜœË<EFBFBD>TQ\œ›Ü‹™\]Y\Ý^Ù\[Û‚ˆ
|
||||
N‚ˆÙ[‹™ØÚÙ\—ØÛY[<EFBFBD>˜ÛÛ<EFBFBD>Z[™\œË™Ù]
|
||||
ÛÛ<EFBFBD>Z[™\—ÚY
|
||||
KšÚ[
|
||||
|
||||
Bˆ™]\›ˆ]ØZ]Ý\\Š
|
||||
K™[]JÙ\ÜÚ[ÛŠB
|
||||
def _ensure_image_available(
|
||||
client: Any,
|
||||
image: str,
|
||||
pull_policy: Literal["auto", "never"],
|
||||
) -> None:
|
||||
if client.image_exists(image):
|
||||
return
|
||||
if pull_policy == "never":
|
||||
raise RuntimeError(
|
||||
f"Docker image {image!r} is not available locally and "
|
||||
"STRIX_IMAGE_PULL_POLICY=never forbids pulling it"
|
||||
)
|
||||
repo, tag = parse_repository_tag(image)
|
||||
client.docker_client.images.pull(repo, tag=tag or None, all_tags=False)
|
||||
|
||||
|
||||
def _sandbox_network() -> str | None:
|
||||
value = os.environ.get(_SANDBOX_NETWORK_ENV, "").strip()
|
||||
return value or None
|
||||
|
||||
|
||||
def _apply_sandbox_network(create_kwargs: dict[str, Any]) -> None:
|
||||
network = _sandbox_network()
|
||||
if network:
|
||||
create_kwargs["network"] = network
|
||||
create_kwargs.pop("ports", None)
|
||||
|
||||
|
||||
def _apply_resource_limits(create_kwargs: dict[str, Any]) -> None:
|
||||
"""Apply optional cgroup resource caps from the environment. Unset/blank
|
||||
values leave docker's default (unbounded), so this is opt-in per host."""
|
||||
mem_limit = os.environ.get("STRIX_SANDBOX_MEM_LIMIT", "").strip()
|
||||
if mem_limit:
|
||||
create_kwargs["mem_limit"] = mem_limit
|
||||
|
||||
shm_size = os.environ.get("STRIX_SANDBOX_SHM_SIZE", "").strip()
|
||||
if shm_size:
|
||||
create_kwargs["shm_size"] = shm_size
|
||||
|
||||
cpus = os.environ.get("STRIX_SANDBOX_CPUS", "").strip()
|
||||
if cpus:
|
||||
with contextlib.suppress(ValueError, OverflowError):
|
||||
nano_cpus = int(float(cpus) * 1_000_000_000)
|
||||
if 0 < nano_cpus <= 2**63 - 1:
|
||||
create_kwargs["nano_cpus"] = nano_cpus
|
||||
|
||||
pids_limit = os.environ.get("STRIX_SANDBOX_PIDS_LIMIT", "").strip()
|
||||
if pids_limit:
|
||||
with contextlib.suppress(ValueError):
|
||||
create_kwargs["pids_limit"] = int(pids_limit)
|
||||
|
||||
|
||||
def _apply_log_limits(create_kwargs: dict[str, Any]) -> None:
|
||||
"""Bound the container's json-file log so a runaway process in the sandbox
|
||||
(e.g. a tool that busy-loops writing to stdout) cannot fill the host disk
|
||||
and take the Docker daemon down with it.
|
||||
|
||||
Unlike the cgroup caps above, this defaults **on** — docker's own default
|
||||
is an unbounded json-file, which is unsafe for an autonomous agent that
|
||||
executes arbitrary commands. ``max-file`` rotation means the on-disk cap is
|
||||
``max-size * max-file``. Set ``STRIX_SANDBOX_LOG_MAX_SIZE`` to ``0``/``off``
|
||||
to opt back out to docker's default."""
|
||||
max_size = os.environ.get("STRIX_SANDBOX_LOG_MAX_SIZE", "50m").strip()
|
||||
if max_size.lower() in ("0", "off", "none", "unlimited"):
|
||||
return
|
||||
max_file = os.environ.get("STRIX_SANDBOX_LOG_MAX_FILE", "3").strip() or "3"
|
||||
create_kwargs["log_config"] = LogConfig(
|
||||
type=LogConfig.types.JSON,
|
||||
config={"max-size": max_size, "max-file": max_file},
|
||||
)
|
||||
|
||||
|
||||
def _apply_run_labels(create_kwargs: dict[str, Any]) -> None:
|
||||
run_id = os.getenv("STRIX_RUN_ID")
|
||||
if not run_id:
|
||||
return
|
||||
labels = create_kwargs.setdefault("labels", {})
|
||||
if not isinstance(labels, dict):
|
||||
return
|
||||
labels["strix-run-id"] = run_id
|
||||
run_type = os.getenv("STRIX_RUN_TYPE")
|
||||
if run_type:
|
||||
labels["strix-run-type"] = run_type
|
||||
|
||||
|
||||
class StrixDockerSandboxSession(DockerSandboxSession):
|
||||
sandbox_network: str = ""
|
||||
|
||||
async def _resolve_exposed_port(self, port: int) -> ExposedPortEndpoint:
|
||||
try:
|
||||
self._container.reload()
|
||||
except docker_errors.APIError as e:
|
||||
raise ExposedPortUnavailableError(
|
||||
port=port,
|
||||
exposed_ports=self.state.exposed_ports,
|
||||
reason="backend_unavailable",
|
||||
context={
|
||||
"backend": "docker",
|
||||
"detail": "container_reload_failed",
|
||||
"network": self.sandbox_network,
|
||||
},
|
||||
cause=e,
|
||||
) from e
|
||||
|
||||
attrs = getattr(self._container, "attrs", {}) or {}
|
||||
networks = attrs.get("NetworkSettings", {}).get("Networks", {})
|
||||
endpoint = networks.get(self.sandbox_network) or {}
|
||||
ip = endpoint.get("IPAddress") or endpoint.get("GlobalIPv6Address")
|
||||
if not isinstance(ip, str) or not ip:
|
||||
raise ExposedPortUnavailableError(
|
||||
port=port,
|
||||
exposed_ports=self.state.exposed_ports,
|
||||
reason="backend_unavailable",
|
||||
context={
|
||||
"backend": "docker",
|
||||
"detail": "container_not_on_network",
|
||||
"network": self.sandbox_network,
|
||||
},
|
||||
)
|
||||
host = f"[{ip}]" if ":" in ip else ip
|
||||
return ExposedPortEndpoint(host=host, port=port, tls=False)
|
||||
|
||||
|
||||
class StrixDockerSandboxClient(DockerSandboxClient):
|
||||
# Host directories to bind-mount into the container, set by the docker
|
||||
# backend before ``create()``. Each item is ``{source, target, read_only}``.
|
||||
strix_bind_mounts: list[dict[str, Any]] | None = None
|
||||
image_pull_policy: Literal["auto", "never"] = "auto"
|
||||
|
||||
async def _create_container(
|
||||
self,
|
||||
image: str,
|
||||
*,
|
||||
manifest: Manifest | None = None,
|
||||
exposed_ports: tuple[int, ...] = (),
|
||||
session_id: uuid.UUID | None = None,
|
||||
) -> Container:
|
||||
# ----- BEGIN VERBATIM COPY of DockerSandboxClient._create_container -----
|
||||
# SDK ref: src/agents/sandbox/sandboxes/docker.py:1434-1477 (v0.14.6).
|
||||
_ensure_image_available(self, image, self.image_pull_policy)
|
||||
|
||||
assert self.image_exists(image)
|
||||
environment: dict[str, str] | None = None
|
||||
if manifest:
|
||||
environment = await manifest.environment.resolve()
|
||||
# Strix delta from the SDK body: drop ``entrypoint`` override and
|
||||
# supply ``tail -f /dev/null`` as ``command`` so the image's
|
||||
# ENTRYPOINT (``docker-entrypoint.sh``) runs setup, then ``exec
|
||||
# "$@"`` becomes ``exec tail -f /dev/null`` for the keep-alive.
|
||||
# Without this, caido-cli + the in-container CA trust never get
|
||||
# initialized.
|
||||
create_kwargs: dict[str, Any] = {
|
||||
"image": image,
|
||||
"detach": True,
|
||||
"command": ["tail", "-f", "/dev/null"],
|
||||
"environment": environment,
|
||||
}
|
||||
if manifest is not None:
|
||||
docker_mounts = _build_docker_volume_mounts(
|
||||
manifest,
|
||||
session_id=session_id,
|
||||
)
|
||||
if docker_mounts:
|
||||
create_kwargs["mounts"] = docker_mounts
|
||||
if _manifest_requires_fuse(manifest):
|
||||
create_kwargs.update(
|
||||
devices=["/dev/fuse"],
|
||||
cap_add=["SYS_ADMIN"],
|
||||
security_opt=["apparmor:unconfined"],
|
||||
)
|
||||
elif _manifest_requires_sys_admin(manifest):
|
||||
create_kwargs.update(
|
||||
cap_add=["SYS_ADMIN"],
|
||||
security_opt=["apparmor:unconfined"],
|
||||
)
|
||||
if exposed_ports:
|
||||
create_kwargs["ports"] = {
|
||||
_docker_port_key(port): ("127.0.0.1", None) for port in exposed_ports
|
||||
}
|
||||
# ----- END VERBATIM COPY -----
|
||||
|
||||
# Strix injections — append, don't overwrite, so FUSE/SYS_ADMIN survives.
|
||||
cap_add = create_kwargs.setdefault("cap_add", [])
|
||||
if not isinstance(cap_add, list):
|
||||
cap_add = list(cap_add)
|
||||
create_kwargs["cap_add"] = cap_add
|
||||
for cap in ("NET_ADMIN", "NET_RAW"):
|
||||
if cap not in cap_add:
|
||||
cap_add.append(cap)
|
||||
|
||||
extra_hosts = create_kwargs.setdefault("extra_hosts", {})
|
||||
extra_hosts["host.docker.internal"] = "host-gateway"
|
||||
|
||||
_apply_sandbox_network(create_kwargs)
|
||||
_apply_resource_limits(create_kwargs)
|
||||
_apply_log_limits(create_kwargs)
|
||||
_apply_run_labels(create_kwargs)
|
||||
|
||||
# Strix injection: local source trees, sorted shallowest-first so a
|
||||
# nested spec lands on top of the tree it covers.
|
||||
bind_mounts = self.strix_bind_mounts or ()
|
||||
if bind_mounts:
|
||||
mounts = create_kwargs.setdefault("mounts", [])
|
||||
for spec in sorted(bind_mounts, key=lambda s: str(s["target"]).count("/")):
|
||||
mounts.append(
|
||||
DockerSDKMount(
|
||||
target=spec["target"],
|
||||
source=spec["source"],
|
||||
type="bind",
|
||||
read_only=spec.get("read_only", False),
|
||||
)
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Creating sandbox container: image=%s caps=%s exposed_ports=%s",
|
||||
image,
|
||||
cap_add,
|
||||
list(exposed_ports),
|
||||
)
|
||||
container = self.docker_client.containers.create(**create_kwargs)
|
||||
logger.info(
|
||||
"Sandbox container created: id=%s image=%s",
|
||||
container.short_id if hasattr(container, "short_id") else "?",
|
||||
image,
|
||||
)
|
||||
return container
|
||||
|
||||
async def create(self, **kwargs: Any) -> SandboxSession:
|
||||
session = await super().create(**kwargs)
|
||||
network = _sandbox_network()
|
||||
inner = session._inner
|
||||
if network and isinstance(inner, DockerSandboxSession):
|
||||
inner.__class__ = StrixDockerSandboxSession
|
||||
cast("StrixDockerSandboxSession", inner).sandbox_network = network
|
||||
return session
|
||||
|
||||
async def delete(self, session: SandboxSession) -> SandboxSession:
|
||||
container_id = getattr(getattr(session._inner, "state", None), "container_id", None)
|
||||
if container_id:
|
||||
# Best-effort kill: NotFound/APIError cover a gone or unhappy
|
||||
# container. RequestException covers a torn-down daemon socket —
|
||||
# containers.get() -> inspect_container raises requests'
|
||||
# ConnectionError, which is a sibling of docker.errors.APIError
|
||||
# under requests.RequestException (not a subclass), so it escapes
|
||||
# an APIError-only suppress and surfaces a full traceback even
|
||||
# though this teardown is meant to be best-effort.
|
||||
with contextlib.suppress(
|
||||
docker_errors.NotFound, docker_errors.APIError, RequestException
|
||||
):
|
||||
self.docker_client.containers.get(container_id).kill()
|
||||
return await super().delete(session)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue