spawnllm 0.14.0__tar.gz → 0.16.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spawnllm-0.14.0 → spawnllm-0.16.0}/PKG-INFO +1 -1
- {spawnllm-0.14.0 → spawnllm-0.16.0}/pyproject.toml +1 -1
- {spawnllm-0.14.0 → spawnllm-0.16.0}/pyproject.toml.orig +1 -1
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/_core/spawnllm_core.wasm +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/base.py +3 -3
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/claude.py +16 -7
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/codex.py +2 -2
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/spec.py +12 -1
- {spawnllm-0.14.0 → spawnllm-0.16.0}/LICENSE +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/README.md +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/__init__.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/__main__.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/_core/__init__.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/__init__.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/apple.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/claude_sdk.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/gemini.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/mlx.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/openai_endpoint.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/registry.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/call.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/cli.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/extract.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/__init__.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/codec.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/engine.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/fuse.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/patches.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/proc.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/py.typed +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/response.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/run.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/structured.py +0 -0
- {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/types.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spawnllm"
|
|
3
3
|
# Inert sentinel: the real version is set from the release tag (uv version --frozen).
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.16.0"
|
|
5
5
|
description = "Delete your subprocess wrappers around claude, codex, and gemini."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "MIT"
|
|
Binary file
|
|
@@ -396,7 +396,7 @@ class CliBackend(LlmBackend):
|
|
|
396
396
|
"""
|
|
397
397
|
return self.binary
|
|
398
398
|
|
|
399
|
-
def claude_isolation(self) -> ClaudeIsolation:
|
|
399
|
+
def claude_isolation(self, api_auth: bool) -> ClaudeIsolation:
|
|
400
400
|
"""Return the config home a claude run substitutes into `${isolated_config_dir}` and the env it adds."""
|
|
401
401
|
raise NotImplementedError
|
|
402
402
|
|
|
@@ -436,7 +436,7 @@ class CliBackend(LlmBackend):
|
|
|
436
436
|
argv = [tokens.get(arg, arg) for arg in plan["argv"]]
|
|
437
437
|
env = plan["env"]
|
|
438
438
|
if plan["needs_claude_isolation"]:
|
|
439
|
-
isolation = self.claude_isolation()
|
|
439
|
+
isolation = self.claude_isolation(spec.api_auth)
|
|
440
440
|
argv = [arg.replace("${isolated_config_dir}", isolation.config_dir) for arg in argv]
|
|
441
441
|
env = {
|
|
442
442
|
key: value.replace("${isolated_config_dir}", isolation.config_dir) for key, value in env.items()
|
|
@@ -468,7 +468,7 @@ class CliBackend(LlmBackend):
|
|
|
468
468
|
plan = self.core_plan(spec)
|
|
469
469
|
if not plan["needs_claude_isolation"]:
|
|
470
470
|
return plan["env"]
|
|
471
|
-
isolation = self.claude_isolation()
|
|
471
|
+
isolation = self.claude_isolation(spec.api_auth)
|
|
472
472
|
return {
|
|
473
473
|
key: value.replace("${isolated_config_dir}", isolation.config_dir) for key, value in plan["env"].items()
|
|
474
474
|
} | isolation.env
|
|
@@ -63,8 +63,9 @@ class ClaudeCliBackend(CliBackend):
|
|
|
63
63
|
schema_dialect: ClassVar[str | None] = "anthropic"
|
|
64
64
|
|
|
65
65
|
_isolated_config_dir: str | None = None
|
|
66
|
+
_api_config_dir: str | None = None
|
|
66
67
|
|
|
67
|
-
def claude_isolation(self) -> ClaudeIsolation:
|
|
68
|
+
def claude_isolation(self, api_auth: bool) -> ClaudeIsolation:
|
|
68
69
|
"""Return the isolation for one run: the process-lifetime config home and the env resolved now.
|
|
69
70
|
|
|
70
71
|
The core's `claude_isolation_sources` op resolves the account pointer,
|
|
@@ -76,7 +77,9 @@ class ClaudeCliBackend(CliBackend):
|
|
|
76
77
|
to set. The files land in a fresh temp dir removed at interpreter exit,
|
|
77
78
|
created on the first call and cached on the backend; the env is resolved
|
|
78
79
|
on every call so a renewed Keychain token reaches the next run, and it
|
|
79
|
-
only ever lives in memory.
|
|
80
|
+
only ever lives in memory. An `api_auth` run names no source at all, so it
|
|
81
|
+
reads no account, credentials file, or Keychain item and gets an empty home
|
|
82
|
+
of its own.
|
|
80
83
|
"""
|
|
81
84
|
sources = _core.dispatch(
|
|
82
85
|
"claude_isolation_sources",
|
|
@@ -88,22 +91,28 @@ class ClaudeCliBackend(CliBackend):
|
|
|
88
91
|
"claude_securestorage_config_dir_env": os.environ.get("CLAUDE_SECURESTORAGE_CONFIG_DIR"),
|
|
89
92
|
"claude_code_custom_oauth_url_env": os.environ.get("CLAUDE_CODE_CUSTOM_OAUTH_URL"),
|
|
90
93
|
"claude_code_oauth_token_env": os.environ.get("CLAUDE_CODE_OAUTH_TOKEN"),
|
|
91
|
-
}
|
|
94
|
+
},
|
|
95
|
+
"api_auth": api_auth,
|
|
92
96
|
},
|
|
93
97
|
)
|
|
94
|
-
account_json = read_file_opt(sources["account_path"])
|
|
98
|
+
account_json = read_file_opt(sources["account_path"]) if sources["account_path"] else None
|
|
95
99
|
credentials_json = read_file_opt(sources["credentials_path"]) if sources["credentials_path"] else None
|
|
96
100
|
if credentials_json is None and sources["keychain_service"] is not None:
|
|
97
101
|
credentials_json = keychain_credentials(sources["keychain_service"])
|
|
98
102
|
seed = _core.dispatch(
|
|
99
103
|
"claude_isolation_seed", {"account_json": account_json, "credentials_json": credentials_json}
|
|
100
104
|
)
|
|
101
|
-
if self._isolated_config_dir
|
|
105
|
+
cached = self._api_config_dir if api_auth else self._isolated_config_dir
|
|
106
|
+
if cached is None:
|
|
102
107
|
config_dir = Path(tempfile.mkdtemp(prefix="spawnllm-claude-config-"))
|
|
103
108
|
for file in seed["files"]:
|
|
104
109
|
fd = os.open(config_dir / file["name"], os.O_WRONLY | os.O_CREAT | os.O_EXCL, int(file["mode"], 8))
|
|
105
110
|
with os.fdopen(fd, "w") as handle:
|
|
106
111
|
handle.write(file["content"])
|
|
107
112
|
atexit.register(shutil.rmtree, config_dir, ignore_errors=True)
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
cached = str(config_dir)
|
|
114
|
+
if api_auth:
|
|
115
|
+
self._api_config_dir = cached
|
|
116
|
+
else:
|
|
117
|
+
self._isolated_config_dir = cached
|
|
118
|
+
return ClaudeIsolation(cached, seed["env"])
|
|
@@ -29,8 +29,8 @@ class CodexCliBackend(CliBackend):
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
models: ClassVar[dict[TModel, str]] = {
|
|
32
|
-
"small": "gpt-
|
|
33
|
-
"medium": "gpt-
|
|
32
|
+
"small": "gpt-6-luna:low",
|
|
33
|
+
"medium": "gpt-6-luna:medium",
|
|
34
34
|
"large": "gpt-5.5:medium",
|
|
35
35
|
}
|
|
36
36
|
provider: ClassVar[ProviderName] = "codex"
|
|
@@ -105,10 +105,16 @@ class CodexConfig:
|
|
|
105
105
|
user-level tier pin applies only with `isolated=False`. `developer_instructions` injects
|
|
106
106
|
the system-prompt layer via `-c developer_instructions=<value>`, serialized as
|
|
107
107
|
a TOML string so any text — multi-line, or TOML-ambiguous words like `true` —
|
|
108
|
-
arrives as a string.
|
|
108
|
+
arrives as a string. `bypass_approvals_and_sandbox` replaces `--sandbox` with
|
|
109
|
+
`--dangerously-bypass-approvals-and-sandbox` for a host that is already
|
|
110
|
+
externally sandboxed; it cannot be combined with `sandbox`.
|
|
109
111
|
|
|
110
112
|
Example:
|
|
111
113
|
>>> CodexConfig(sandbox="read-only", enable_mcp=True)
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ValueError: When `bypass_approvals_and_sandbox` is set together with a
|
|
117
|
+
`sandbox`, two policies the CLI cannot apply at once.
|
|
112
118
|
"""
|
|
113
119
|
|
|
114
120
|
sandbox: str | None = None
|
|
@@ -116,6 +122,11 @@ class CodexConfig:
|
|
|
116
122
|
enable_mcp: bool = False
|
|
117
123
|
service_tier: str | None = "fast"
|
|
118
124
|
developer_instructions: str | None = None
|
|
125
|
+
bypass_approvals_and_sandbox: bool = False
|
|
126
|
+
|
|
127
|
+
def __post_init__(self) -> None:
|
|
128
|
+
if self.bypass_approvals_and_sandbox and self.sandbox is not None:
|
|
129
|
+
raise ValueError("CodexConfig accepts either sandbox or bypass_approvals_and_sandbox, not both")
|
|
119
130
|
|
|
120
131
|
|
|
121
132
|
@dataclass(frozen=True, slots=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|