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.
Files changed (34) hide show
  1. {spawnllm-0.14.0 → spawnllm-0.16.0}/PKG-INFO +1 -1
  2. {spawnllm-0.14.0 → spawnllm-0.16.0}/pyproject.toml +1 -1
  3. {spawnllm-0.14.0 → spawnllm-0.16.0}/pyproject.toml.orig +1 -1
  4. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/_core/spawnllm_core.wasm +0 -0
  5. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/base.py +3 -3
  6. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/claude.py +16 -7
  7. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/codex.py +2 -2
  8. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/spec.py +12 -1
  9. {spawnllm-0.14.0 → spawnllm-0.16.0}/LICENSE +0 -0
  10. {spawnllm-0.14.0 → spawnllm-0.16.0}/README.md +0 -0
  11. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/__init__.py +0 -0
  12. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/__main__.py +0 -0
  13. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/_core/__init__.py +0 -0
  14. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/__init__.py +0 -0
  15. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/apple.py +0 -0
  16. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/claude_sdk.py +0 -0
  17. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/gemini.py +0 -0
  18. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/mlx.py +0 -0
  19. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/openai_endpoint.py +0 -0
  20. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/backends/registry.py +0 -0
  21. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/call.py +0 -0
  22. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/cli.py +0 -0
  23. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/extract.py +0 -0
  24. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/__init__.py +0 -0
  25. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/codec.py +0 -0
  26. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/engine.py +0 -0
  27. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/fuse.py +0 -0
  28. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/mlx/patches.py +0 -0
  29. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/proc.py +0 -0
  30. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/py.typed +0 -0
  31. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/response.py +0 -0
  32. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/run.py +0 -0
  33. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/structured.py +0 -0
  34. {spawnllm-0.14.0 → spawnllm-0.16.0}/spawnllm/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawnllm
3
- Version: 0.14.0
3
+ Version: 0.16.0
4
4
  Summary: Delete your subprocess wrappers around claude, codex, and gemini.
5
5
  Keywords:
6
6
  Author: Yasyf Mohamedali
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spawnllm"
3
- version = "0.14.0"
3
+ version = "0.16.0"
4
4
  description = "Delete your subprocess wrappers around claude, codex, and gemini."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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.14.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"
@@ -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 is None:
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
- self._isolated_config_dir = str(config_dir)
109
- return ClaudeIsolation(self._isolated_config_dir, seed["env"])
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-5.6-luna:low",
33
- "medium": "gpt-5.6-luna:medium",
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