opencode-llmstack 0.7.1__tar.gz → 0.7.3__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 (42) hide show
  1. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/PKG-INFO +10 -5
  2. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/README.md +9 -4
  3. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/activate.py +42 -6
  4. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/setup.py +15 -6
  5. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/models.ini +140 -78
  6. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/shell_env.py +114 -33
  7. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/PKG-INFO +10 -5
  8. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/pyproject.toml +1 -1
  9. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/AGENTS.md +0 -0
  10. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/__init__.py +0 -0
  11. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/__main__.py +0 -0
  12. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/_platform.py +0 -0
  13. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/app.py +0 -0
  14. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/backends/__init__.py +0 -0
  15. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/backends/bedrock.py +0 -0
  16. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/check_models.py +0 -0
  17. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/cli.py +0 -0
  18. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/__init__.py +0 -0
  19. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/_helpers.py +0 -0
  20. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/check.py +0 -0
  21. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/download.py +0 -0
  22. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/install.py +0 -0
  23. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/install_llama_swap.py +0 -0
  24. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/reload.py +0 -0
  25. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/restart.py +0 -0
  26. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/start.py +0 -0
  27. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/status.py +0 -0
  28. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/commands/stop.py +0 -0
  29. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/download/__init__.py +0 -0
  30. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/download/binary.py +0 -0
  31. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/download/ggufs.py +0 -0
  32. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/generators/__init__.py +0 -0
  33. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/generators/llama_swap.py +0 -0
  34. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/generators/opencode.py +0 -0
  35. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/paths.py +0 -0
  36. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/llmstack/tiers.py +0 -0
  37. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/SOURCES.txt +0 -0
  38. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/dependency_links.txt +0 -0
  39. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/entry_points.txt +0 -0
  40. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/requires.txt +0 -0
  41. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/opencode_llmstack.egg-info/top_level.txt +0 -0
  42. {opencode_llmstack-0.7.1 → opencode_llmstack-0.7.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-llmstack
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: Multi-tier local LLM stack: llama-swap + FastAPI auto-router + opencode wiring.
5
5
  Author: llmstack
6
6
  License: MIT
@@ -313,10 +313,15 @@ py -3 -m venv .venv
313
313
  # `start` falls back to spawning a PowerShell subshell.
314
314
  .venv\Scripts\llmstack start
315
315
 
316
- # 4. Auto-activate per project from any new PowerShell window:
317
- Invoke-Expression (& llmstack activate powershell | Out-String)
318
- # or persist (writes ~/.powershell_llmstack_hook + sources it on every shell):
319
- "Invoke-Expression (& llmstack activate powershell | Out-String)" | Add-Content $PROFILE
316
+ # 4. Auto-activate per project from any new PowerShell window. The hook
317
+ # file is a .ps1 (PowerShell won't dot-source it without that
318
+ # extension) and dot-sourcing it requires script execution to be
319
+ # allowed -- if you see "running scripts is disabled on this
320
+ # system", run once:
321
+ # Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
322
+ llmstack activate powershell | Out-String | Invoke-Expression
323
+ # or persist (writes ~/.powershell_llmstack_hook.ps1 + sources it on every shell):
324
+ "llmstack activate powershell | Out-String | Invoke-Expression" | Add-Content $PROFILE
320
325
  ```
321
326
 
322
327
  Notes:
@@ -278,10 +278,15 @@ py -3 -m venv .venv
278
278
  # `start` falls back to spawning a PowerShell subshell.
279
279
  .venv\Scripts\llmstack start
280
280
 
281
- # 4. Auto-activate per project from any new PowerShell window:
282
- Invoke-Expression (& llmstack activate powershell | Out-String)
283
- # or persist (writes ~/.powershell_llmstack_hook + sources it on every shell):
284
- "Invoke-Expression (& llmstack activate powershell | Out-String)" | Add-Content $PROFILE
281
+ # 4. Auto-activate per project from any new PowerShell window. The hook
282
+ # file is a .ps1 (PowerShell won't dot-source it without that
283
+ # extension) and dot-sourcing it requires script execution to be
284
+ # allowed -- if you see "running scripts is disabled on this
285
+ # system", run once:
286
+ # Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
287
+ llmstack activate powershell | Out-String | Invoke-Expression
288
+ # or persist (writes ~/.powershell_llmstack_hook.ps1 + sources it on every shell):
289
+ "llmstack activate powershell | Out-String | Invoke-Expression" | Add-Content $PROFILE
285
290
  ```
286
291
 
287
292
  Notes:
@@ -28,20 +28,42 @@ def _print_help() -> None:
28
28
  print("usage: llmstack activate <zsh|bash|powershell>", file=sys.stderr)
29
29
 
30
30
 
31
+ def _is_powershell(shell: str) -> bool:
32
+ return shell in ("powershell", "pwsh")
33
+
34
+
31
35
  def _hook_path(shell: str) -> Path:
32
36
  """``~/.<shell>_llmstack_hook`` -- ``pwsh`` is normalised to ``powershell``
33
- so the user doesn't end up with two redundant files."""
34
- name = "powershell" if shell in ("powershell", "pwsh") else shell
35
- return Path.home() / f".{name}_llmstack_hook"
37
+ so the user doesn't end up with two redundant files.
38
+
39
+ PowerShell additionally needs a ``.ps1`` suffix or the host won't
40
+ dot-source it -- without the extension Windows hands the file to
41
+ the OS shell file-association (Notepad, etc.) instead of running
42
+ it as a script.
43
+ """
44
+ if _is_powershell(shell):
45
+ return Path.home() / ".powershell_llmstack_hook.ps1"
46
+ return Path.home() / f".{shell}_llmstack_hook"
36
47
 
37
48
 
38
49
  def _source_line(shell: str, path: Path) -> str:
39
50
  """Shell-specific incantation to load the hook file."""
40
- if shell in ("powershell", "pwsh"):
51
+ if _is_powershell(shell):
41
52
  return f". '{path}'"
42
53
  return f'source "{path}"'
43
54
 
44
55
 
56
+ def eval_line(shell: str) -> str:
57
+ """The one-shot the user pastes / adds to their rc to install the hook.
58
+
59
+ POSIX shells use ``eval "$(...)"``; PowerShell has no ``eval`` and
60
+ needs ``Invoke-Expression`` over the captured stdout.
61
+ """
62
+ if _is_powershell(shell):
63
+ return f"llmstack activate {shell} | Out-String | Invoke-Expression"
64
+ return f'eval "$(llmstack activate {shell})"'
65
+
66
+
45
67
  def write_hook(shell: str) -> tuple[Path, str]:
46
68
  """Render the hook for ``shell``, write it to disk, return ``(path, source_line)``.
47
69
 
@@ -62,10 +84,24 @@ def run(args: list[str]) -> int:
62
84
 
63
85
  path, src = write_hook(shell)
64
86
 
65
- eval_line = f'eval "$(llmstack activate {shell})"'
87
+ line = eval_line(shell)
66
88
  print(f"[OK] hook written: {path}", file=sys.stderr)
67
89
  print( " activate in this shell now (and for every new shell:", file=sys.stderr)
68
- print(f" paste into your rc): {eval_line}", file=sys.stderr)
90
+ print(f" paste into your rc): {line}", file=sys.stderr)
91
+ if _is_powershell(shell):
92
+ # PowerShell's default `Restricted` policy on Windows blocks
93
+ # dot-sourcing any .ps1; surface the one-time fix so the
94
+ # `Invoke-Expression` line above doesn't fail with "running
95
+ # scripts is disabled on this system".
96
+ print(
97
+ " PowerShell execution policy must allow local scripts; "
98
+ "if dot-sourcing fails, run once:",
99
+ file=sys.stderr,
100
+ )
101
+ print(
102
+ " Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned",
103
+ file=sys.stderr,
104
+ )
69
105
 
70
106
  print(src)
71
107
  return 0
@@ -18,7 +18,7 @@ import shutil
18
18
  import subprocess
19
19
 
20
20
  from llmstack._platform import IS_WINDOWS, shell_family
21
- from llmstack.commands.activate import write_hook
21
+ from llmstack.commands.activate import eval_line, write_hook
22
22
  from llmstack.download.binary import install_llama_swap
23
23
  from llmstack.download.ggufs import download_all, wait_for_downloads
24
24
  from llmstack.paths import is_remote, remote_url
@@ -86,16 +86,25 @@ def run(args: list[str]) -> int:
86
86
  rc_hint = "your shell rc file"
87
87
  hook_arg = None
88
88
 
89
- eval_line: str | None = None
89
+ activate_line: str | None = None
90
90
  if hook_arg is not None:
91
91
  path, _src = write_hook(hook_arg)
92
- eval_line = f'eval "$(llmstack activate {hook_arg})"'
92
+ activate_line = eval_line(hook_arg)
93
93
  print(f"[OK] hook installed: {path}")
94
94
  print()
95
95
  print("To turn it on in this shell now (and persist across new shells, paste")
96
96
  print(f"the same line into {rc_hint}):")
97
97
  print()
98
- print(f" {eval_line}")
98
+ print(f" {activate_line}")
99
+ if family == "powershell":
100
+ # PowerShell needs script execution allowed before
101
+ # dot-sourcing the .ps1; flag the one-time fix here so the
102
+ # Invoke-Expression line above doesn't silently fail.
103
+ print()
104
+ print(" PowerShell execution policy must allow local scripts;")
105
+ print(" if dot-sourcing fails with \"running scripts is disabled\", run once:")
106
+ print()
107
+ print(" Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned")
99
108
 
100
109
  print()
101
110
  print("[5/5] checking opencode...")
@@ -134,8 +143,8 @@ def run(args: list[str]) -> int:
134
143
  print("[OK] setup complete.")
135
144
  print()
136
145
  print("Next steps:")
137
- if eval_line is not None:
138
- print(f" 1. Run (and paste into {rc_hint} for persistence): {eval_line}")
146
+ if activate_line is not None:
147
+ print(f" 1. Run (and paste into {rc_hint} for persistence): {activate_line}")
139
148
  else:
140
149
  print(" 1. Source the generated hook in your shell rc (see above)")
141
150
  print(" 2. llmstack install # generate .llmstack/ configs for this project")
@@ -51,7 +51,6 @@
51
51
  [DEFAULT]
52
52
  host = 127.0.0.1
53
53
  router_port = 10101 ; FastAPI auto-router (what opencode hits)
54
- swap_port = 10102 ; llama-swap manager UI + raw model endpoints
55
54
  n_gpu_layers = 999 ; offload everything to Metal on Apple Silicon
56
55
  flash_attn = on
57
56
  jinja = true
@@ -72,11 +71,26 @@ ctx_size = 131072 ; native 32k extended via YaRN (factor 4)
72
71
  rope_scaling = yarn (scale=4, orig_ctx=32768)
73
72
  size_gb = 2.5
74
73
  quant = Q5_K_M
75
- status = downloading ; queued by `llmstack.sh download`
76
- opencode_use = small_model + auto-fast tier
77
74
  sampler = temp=0.2, top_p=0.95, top_k=40, min_p=0.05 ; deterministic
78
75
  description = Qwen2.5-Coder 3B - autocomplete / FIM / quick Q&A
79
76
 
77
+ ; Bedrock alternative for code-fast -- comment out the [code-fast] block above
78
+ ; and uncomment the block below to swap to a hosted fast tier (Claude Haiku
79
+ ; 4.5: cheapest + fastest Anthropic model with tool calling, sub-second TTFT).
80
+ ; See "BEDROCK NOTES" at the bottom of this file for profile / sampler /
81
+ ; access-form details.
82
+ ;
83
+ ; [code-fast]
84
+ ; tier = code
85
+ ; role = fast
86
+ ; backend = bedrock
87
+ ; aws_model_id = eu.anthropic.claude-haiku-4-5-20251001-v1:0
88
+ ; aws_region = eu-central-1
89
+ ; aws_profile = bedrock-prod
90
+ ; ctx_size = 200000
91
+ ; sampler = temp=0.2 ; deterministic; Haiku 4.5 accepts ONE of temp / top_p
92
+ ; description = Claude Haiku 4.5 on Bedrock - hosted fast tier for autocomplete / FIM / quick Q&A
93
+
80
94
  [code-smart]
81
95
  tier = code
82
96
  role = agent
@@ -88,11 +102,25 @@ size_gb = 45
88
102
  size_gb_next = 50
89
103
  quant = Q4_K_M
90
104
  quant_next = UD-Q4_K_XL
91
- status = ready (Q4_K_M); UD-Q4_K_XL queued
92
- opencode_use = agent.build + auto-agent tier
93
105
  sampler = temp=0.5, top_p=0.85, top_k=20, min_p=0.05, rep_pen=1.05 ; balanced agent
94
106
  description = Qwen3-Coder-Next 80B-A3B MoE - heavy coder for agent loops
95
107
 
108
+ ; Bedrock alternative for code-smart -- comment out the [code-smart] block
109
+ ; above and uncomment the block below to swap to a hosted heavy coder
110
+ ; (Claude Sonnet 4.6: agent-loop workhorse, heavy tool calling, multi-file
111
+ ; edits). See "BEDROCK NOTES" at the bottom of this file.
112
+ ;
113
+ ; [code-smart]
114
+ ; tier = code
115
+ ; role = agent
116
+ ; backend = bedrock
117
+ ; aws_model_id = eu.anthropic.claude-sonnet-4-6
118
+ ; aws_region = eu-central-1
119
+ ; aws_profile = bedrock-prod
120
+ ; ctx_size = 200000
121
+ ; sampler = temp=0.5 ; Sonnet 4.6 accepts ONE of temp / top_p; pick `temp` for agent work
122
+ ; description = Claude Sonnet 4.6 on Bedrock - heavy coder for agent loops
123
+
96
124
  ; Top-tier hosted coder. Shipped disabled because it requires boto3 +
97
125
  ; AWS Bedrock access. `llmstack install` auto-uncomments the block
98
126
  ; below (by stripping the leading "; " from each line and dropping
@@ -107,10 +135,9 @@ description = Qwen3-Coder-Next 80B-A3B MoE - heavy coder for agent loops
107
135
  ; role = ultra
108
136
  ; backend = bedrock
109
137
  ; aws_model_id = global.anthropic.claude-opus-4-7 ; global.* cross-region inference profile
110
- ; aws_region = us-east-1 ; API anchor region; global.* auto-routes inference cross-region
111
- ; aws_profile = bedrock-prod ; uncomment + set your own profile name; falls back to default cred chain otherwise
138
+ ; aws_region = eu-central-1 ; API anchor region; global.* auto-routes inference cross-region (set EU as the anchor for residency)
139
+ ; aws_profile = bedrock-prod ; conventional profile name; configure once with `aws configure --profile bedrock-prod` (or change to your own and run `llmstack install`)
112
140
  ; ctx_size = 200000
113
- ; opencode_use = on-demand top-tier coder for hard agent tasks
114
141
  ; ; NB: no `sampler =` line. Claude Opus 4.7 explicitly rejects all
115
142
  ; ; sampler params (temperature, top_p, top_k) -- per the Bedrock
116
143
  ; ; model card, "the recommended migration path is to omit these
@@ -134,11 +161,26 @@ size_gb = 9.2
134
161
  size_gb_next = 12.1
135
162
  quant = Q4_K_M
136
163
  quant_next = Q6_K
137
- status = ready (Q4_K_M); Q6_K queued
138
- opencode_use = agent.plan + auto-plan tier
139
164
  sampler = temp=0.7, top_p=0.9, top_k=40, min_p=0.05 ; creative thinking
140
165
  description = Qwopus GLM 18B - planning, design discussions, architecture
141
166
 
167
+ ; Bedrock alternative for plan -- comment out the [plan] block above and
168
+ ; uncomment the block below to swap to a hosted planner (Claude Opus 4.6:
169
+ ; deep reasoning for design discussions and architecture). Opus 4.6 still
170
+ ; accepts both temperature and top_p (unlike 4.7), so the local sampler
171
+ ; maps over cleanly. See "BEDROCK NOTES" at the bottom of this file.
172
+ ;
173
+ ; [plan]
174
+ ; tier = chat
175
+ ; role = plan
176
+ ; backend = bedrock
177
+ ; aws_model_id = eu.anthropic.claude-opus-4-6-v1
178
+ ; aws_region = eu-central-1
179
+ ; aws_profile = bedrock-prod
180
+ ; ctx_size = 200000
181
+ ; sampler = temp=0.7, top_p=0.9 ; creative; Opus 4.6 accepts both
182
+ ; description = Claude Opus 4.6 on Bedrock - planning, design discussions, architecture
183
+
142
184
  [plan-uncensored]
143
185
  tier = chat
144
186
  role = plan-uncensored
@@ -150,11 +192,51 @@ size_gb = 13
150
192
  size_gb_next = 20
151
193
  quant = i1-Q4_K_M
152
194
  quant_next = i1-Q6_K
153
- status = ready (i1-Q4_K_M); i1-Q6_K queued
154
- opencode_use = agent.plan-nofilter + auto via [nofilter] trigger
155
195
  sampler = temp=0.85, top_p=0.95, top_k=50, min_p=0.05 ; max exploration
156
196
  description = Mistral-Small 3.2 24B Heretic - no-filter planning
157
197
 
198
+ ; Bedrock alternative for plan-uncensored -- comment out the [plan-uncensored]
199
+ ; block above and uncomment ONE of the blocks below. Anthropic models on
200
+ ; Bedrock are filtered, so for the uncensored slot we pick the largest
201
+ ; open-weights model on Bedrock: Llama 3.1 405B has minimal safety post-
202
+ ; training and matches the spirit of the local Heretic tier. NOTE: Meta
203
+ ; models do NOT require the AWS use-case form, so this swap unblocks
204
+ ; plan-uncensored on a fresh AWS account.
205
+ ;
206
+ ; REGION CAVEAT: unlike the other tiers above, Llama 3.1 405B has NO
207
+ ; cross-region inference profile (no eu.* / global.*) and is only
208
+ ; deployed in US regions. Pin to us-west-2 even when the rest of the
209
+ ; stack is anchored in eu-central-1. If EU residency is mandatory for
210
+ ; this tier, switch to one of the eu.anthropic.* IDs at the cost of
211
+ ; losing the "uncensored" property. See "BEDROCK NOTES" at the bottom
212
+ ; of this file.
213
+ ;
214
+ ; [plan-uncensored]
215
+ ; tier = chat
216
+ ; role = plan-uncensored
217
+ ; backend = bedrock
218
+ ; aws_model_id = meta.llama3-1-405b-instruct-v1:0
219
+ ; aws_region = us-west-2 ; Llama 405B has no EU deployment; keep on US
220
+ ; aws_profile = bedrock-prod
221
+ ; ctx_size = 128000
222
+ ; sampler = temp=0.85, top_p=0.95 ; max exploration
223
+ ; description = Llama 3.1 405B on Bedrock - no-filter planning
224
+ ;
225
+ ; ...or, if your org locks Bedrock access to a VPC endpoint, use this
226
+ ; variant instead (same model + sampler, with aws_endpoint_url set):
227
+ ;
228
+ ; [plan-uncensored]
229
+ ; tier = chat
230
+ ; role = plan-uncensored
231
+ ; backend = bedrock
232
+ ; aws_model_id = meta.llama3-1-405b-instruct-v1:0
233
+ ; aws_region = us-west-2 ; Llama 405B has no EU deployment
234
+ ; aws_profile = bedrock-prod
235
+ ; aws_endpoint_url = https://bedrock-runtime.us-west-2.vpce.amazonaws.com
236
+ ; ctx_size = 128000
237
+ ; sampler = temp=0.85, top_p=0.95
238
+ ; description = Llama 3.1 405B on Bedrock (VPC) - no-filter planning
239
+
158
240
  ;------------------------------------------------------------------------------
159
241
  [ROUTING]
160
242
  ; STEP-DOWN ladder: start at the top of the fidelity ladder for short
@@ -203,30 +285,46 @@ uncensored_triggers = [nofilter], [uncensored], [heretic], "uncensored:", "nof
203
285
  ultra_triggers = [ultra], [opus], "ultra:", "opus:" (line start)
204
286
 
205
287
  ;------------------------------------------------------------------------------
206
- ; BEDROCK EXAMPLES (commented out -- copy / uncomment to adopt)
288
+ ; BEDROCK NOTES (referenced by the commented-out alternatives above)
207
289
  ;------------------------------------------------------------------------------
208
- ; To swap one of the local GGUF tiers above for an AWS Bedrock model, COMMENT
209
- ; OUT the existing tier of the same name and uncomment one of these. The router
210
- ; auto-detects backend=bedrock from the presence of `aws_model_id` -- no other
211
- ; flag needed. llama-swap won't load it; the router calls Bedrock directly via
212
- ; boto3 (`pip install 'llmstack[bedrock]'`).
290
+ ; Each tier section above carries a "Bedrock alternative for <tier>" block
291
+ ; directly underneath it (commented out by default). To swap a tier:
292
+ ;
293
+ ; 1. comment out the active local section (GGUF by default);
294
+ ; 2. uncomment the Bedrock-alternative block beneath it;
295
+ ; 3. run `llmstack install` (and `llmstack restart` if the tier was
296
+ ; already loaded -- bedrock creds aren't picked up live).
213
297
  ;
214
- ; Credentials: this file ONLY names a profile. The actual keys / SSO /
215
- ; role chaining live in the standard AWS config files. One-time setup:
298
+ ; The router auto-detects backend=bedrock from `aws_model_id`, but every
299
+ ; alternative block also sets `backend = bedrock` explicitly so the intent
300
+ ; is obvious. llama-swap won't load bedrock tiers; the router calls
301
+ ; Bedrock directly via boto3 (`pip install 'llmstack[bedrock]'`).
302
+ ;
303
+ ; PROFILE: every alternative uses `aws_profile = bedrock-prod`, the
304
+ ; conventional profile name for this stack. The actual keys / SSO /
305
+ ; role chaining live in the standard AWS config files (this file ONLY
306
+ ; names a profile -- never put credentials here). One-time setup:
216
307
  ;
217
308
  ; aws configure --profile bedrock-prod
218
- ; # for SSO: aws configure sso --profile bedrock-prod
219
- ; # for role chaining, edit ~/.aws/config and add a profile with:
309
+ ; # SSO: aws configure sso --profile bedrock-prod
310
+ ; # role chaining: edit ~/.aws/config and add:
311
+ ; # [profile bedrock-prod]
220
312
  ; # role_arn = arn:aws:iam::123456789012:role/llmstack-bedrock
221
- ; # source_profile = bedrock-prod
313
+ ; # source_profile = bedrock-prod-base
314
+ ;
315
+ ; To use a different profile name, edit the `aws_profile` line. To fall
316
+ ; back on boto3's default chain (env vars, default profile, instance
317
+ ; role), remove the line entirely.
222
318
  ;
223
- ; Then reference the profile name from your tier with `aws_profile = ...`.
224
- ; If you omit `aws_profile`, boto3's default chain applies (env vars,
225
- ; default profile, instance role -- whatever boto3 normally finds).
319
+ ; UPGRADE PRE-STAGING: optional `aws_model_id_next` (+ `aws_region_next`)
320
+ ; is the queued upgrade target -- mirrors gguf `hf_file_next`. The router
321
+ ; uses it only when `llmstack start --next` is in effect; permanent
322
+ ; promotion is the same as gguf: edit `aws_model_id` and re-run
323
+ ; `llmstack install`.
226
324
  ;
227
- ; SAMPLER NOTE: the `sampler = temp=..., top_p=..., top_k=..., ...`
228
- ; line on each tier is the SINGLE SOURCE OF TRUTH for sampling, but how
229
- ; it gets applied depends on the backend:
325
+ ; SAMPLER: the `sampler = temp=..., top_p=..., top_k=..., ...` line on
326
+ ; each tier is the SINGLE SOURCE OF TRUTH for sampling, but how it gets
327
+ ; applied depends on the backend:
230
328
  ;
231
329
  ; * gguf tiers -- the llama-swap generator bakes the sampler keys
232
330
  ; into the llama-server startup command line as `--temp`,
@@ -248,57 +346,21 @@ ultra_triggers = [ultra], [opus], "ultra:", "opus:" (line start)
248
346
  ; opencode.json is sampler-free in both cases by design (the
249
347
  ; opencode.json generator never emits sampler params on agents).
250
348
  ;
251
- ; Per-Bedrock-family rules (as of 2026):
349
+ ; Per-Bedrock-family sampler rules (as of 2026):
252
350
  ;
253
351
  ; * Claude Opus 4.7+ -- rejects all sampler params; OMIT `sampler =`
254
352
  ; entirely (the router will then pass requests through untouched).
255
- ; * Claude Sonnet 4.5 / Haiku 4.5 -- accept `temp` OR `top_p`, never
256
- ; both; pick one.
353
+ ; * Claude Sonnet 4.5 / 4.6 / Haiku 4.5 -- accept `temp` OR `top_p`,
354
+ ; never both; pick one.
257
355
  ; * Claude Opus 4.x (4.1, 4.5, 4.6) -- accept `temp` and `top_p`.
258
- ; * Llama / Titan / Cohere / etc. -- accept `temp` + `top_p`; check
259
- ; the model card if in doubt.
356
+ ; * Llama / Titan / Mistral / Cohere / Nova / etc. -- accept `temp`
357
+ ; + `top_p`; check the model card if in doubt.
260
358
  ;
261
- ; Example A: top-tier coder on Bedrock (us-west-2), default cred chain.
262
- ; Optional `aws_model_id_next` (and optional `aws_region_next`) is the
263
- ; queued upgrade target -- mirrors gguf `hf_file_next`. The router uses
264
- ; it only when `--next` is in effect; permanent promotion is the same
265
- ; as gguf: edit `aws_model_id` and re-run `llmstack install`.
266
- ;
267
- ; [code-smart]
268
- ; tier = code
269
- ; role = agent
270
- ; backend = bedrock
271
- ; aws_model_id = anthropic.claude-sonnet-4-5-20250929-v1:0
272
- ; aws_region = us-west-2
273
- ; aws_model_id_next = anthropic.claude-sonnet-5-20260201-v1:0 ; queued
274
- ; aws_region_next = us-east-1 ; (optional) different region for the new model
275
- ; ctx_size = 200000
276
- ; sampler = temp=0.5 ; Sonnet 4.5 accepts ONE of temp / top_p; pick `temp` for agent work
277
- ; description = Claude Sonnet 4.5 on Bedrock - heavy coder for agent loops
278
- ;
279
- ; Example B: planner in a different AWS account, accessed via a named
280
- ; profile that itself uses role-chaining + SSO under ~/.aws/config.
281
- ; (Different tier => different profile name; different account/region.)
282
- ;
283
- ; [plan]
284
- ; tier = chat
285
- ; role = plan
286
- ; aws_model_id = us.anthropic.claude-opus-4-1-20250805-v1:0
287
- ; aws_region = us-east-1
288
- ; aws_profile = bedrock-planning
289
- ; ctx_size = 200000
290
- ; sampler = temp=0.7, top_p=0.9
291
- ; description = Claude Opus 4.1 on Bedrock - planning, design discussions
292
- ;
293
- ; Example C: large model behind a VPC endpoint.
294
- ;
295
- ; [plan-uncensored]
296
- ; tier = chat
297
- ; role = plan-uncensored
298
- ; aws_model_id = meta.llama3-1-405b-instruct-v1:0
299
- ; aws_region = us-west-2
300
- ; aws_profile = bedrock-prod
301
- ; aws_endpoint_url = https://bedrock-runtime.us-west-2.vpce.amazonaws.com
302
- ; ctx_size = 128000
303
- ; sampler = temp=0.85, top_p=0.95
304
- ; description = Llama 3.1 405B on Bedrock - max-exploration planning
359
+ ; ACCESS: Anthropic Claude on Bedrock requires a one-time use-case-form
360
+ ; approval per AWS account (Bedrock console -> Model catalog -> pick the
361
+ ; model -> fill the form). Approval is account-level and persists; once
362
+ ; granted, every Claude variant works (bare ID, us./eu./global. cross-
363
+ ; region profile, application inference profile ARN). To skip the form
364
+ ; entirely, use the Llama 3.1 405B variant under [plan-uncensored] (Meta
365
+ ; models don't require the form) or pick another non-Anthropic family
366
+ ; (Amazon Nova, Mistral, Cohere, Titan).
@@ -261,14 +261,15 @@ _ZSH_HOOK = r"""# --- llmstack auto-activation hook (zsh) ----------------------
261
261
  #
262
262
  # Tool-availability gate: before activating, we verify the tools needed
263
263
  # for this channel are present:
264
- # - `llmstack` (always required)
265
- # - `llama-swap` (only for local channels: current / next)
266
- # - `llama-server` or `llama-cli` (likewise local-only)
267
- # external-mode projects skip the local-tool checks because llama-swap
268
- # and llama-server live on the remote. If any required tool is missing
269
- # we print "folder detected but tool not available" + install hints and
270
- # DON'T activate -- the env stays clean so opencode keeps using the
271
- # user's global config until they install the missing piece.
264
+ # - `llmstack` (always required -- blocker)
265
+ # - `llama-swap` (only for local channels: current / next -- blocker)
266
+ # - `llama-server` / `llama-cli` (local-only, *warning* not blocker --
267
+ # a Bedrock-only models.ini activates fine without llama-server;
268
+ # local GGUF rows would fail to start, hence the heads-up)
269
+ # external-mode projects skip all local-tool checks because llama-swap
270
+ # and llama-server live on the remote. Blockers print "folder detected
271
+ # but tool not available" + install hints and skip activation; warnings
272
+ # print a one-shot hint and activate anyway.
272
273
  #
273
274
  # Marker file format (one line):
274
275
  # <channel>[ <url>]
@@ -322,13 +323,18 @@ _llmstack_find_swap() {
322
323
  }
323
324
 
324
325
  _llmstack_check_tools() {
325
- # Populates _llmstack_missing array. Returns 0 iff nothing is missing.
326
+ # Populates _llmstack_missing (blockers) and _llmstack_warnings
327
+ # (non-blockers). Returns 0 iff there are no blockers; warnings
328
+ # never block activation.
326
329
  _llmstack_missing=()
330
+ _llmstack_warnings=()
327
331
  command -v llmstack >/dev/null 2>&1 || _llmstack_missing+=("llmstack")
328
332
  if [[ "${1:-current}" != "external" ]]; then
329
333
  _llmstack_find_swap || _llmstack_missing+=("llama-swap")
334
+ # llama-server is a soft requirement: bedrock-only models.ini
335
+ # files don't need it, so missing == warn-and-continue.
330
336
  if ! command -v llama-server >/dev/null 2>&1 && ! command -v llama-cli >/dev/null 2>&1; then
331
- _llmstack_missing+=("llama-server")
337
+ _llmstack_warnings+=("llama-server")
332
338
  fi
333
339
  fi
334
340
  (( ${#_llmstack_missing[@]} == 0 ))
@@ -343,7 +349,7 @@ _llmstack_install_hint() {
343
349
  }
344
350
 
345
351
  _llmstack_warn_missing() {
346
- # $1 = project root; uses _llmstack_missing.
352
+ # $1 = project root; uses _llmstack_missing (blockers only).
347
353
  print -r -- ""
348
354
  print -P -- "%F{220}[llmstack]%f detected $1/.llmstack but missing local tool(s):"
349
355
  local t
@@ -354,6 +360,19 @@ _llmstack_warn_missing() {
354
360
  print -r -- ""
355
361
  }
356
362
 
363
+ _llmstack_warn_optional() {
364
+ # $1 = project root; uses _llmstack_warnings. One-shot per activation
365
+ # (the LLMSTACK_WORK_DIR idempotency guard suppresses repeats).
366
+ print -r -- ""
367
+ print -P -- "%F{220}[llmstack]%f $1: activating without optional local tool(s):"
368
+ local t
369
+ for t in "${_llmstack_warnings[@]}"; do
370
+ _llmstack_install_hint "$t"
371
+ done
372
+ print -r -- " bedrock-only models.ini works fine; local GGUF rows will fail to start."
373
+ print -r -- ""
374
+ }
375
+
357
376
  _llmstack_deactivate() {
358
377
  if [[ -n "${LLMSTACK_WORK_DIR:-}" ]]; then
359
378
  unset OPENCODE_CONFIG LLMSTACK_WORK_DIR LLMSTACK_ACTIVE LLMSTACK_CHANNEL LLMSTACK_REMOTE_URL
@@ -393,12 +412,17 @@ _llmstack_activate() {
393
412
  fi
394
413
  : "${_ch:=current}"
395
414
 
396
- # Tool gate -- bail before exporting anything if requirements aren't met.
415
+ # Tool gate -- bail before exporting anything if a *blocker* is
416
+ # missing. Non-blocking warnings (e.g. llama-server for a
417
+ # bedrock-only setup) print a hint but proceed.
397
418
  if ! _llmstack_check_tools "$_ch"; then
398
419
  _llmstack_warn_missing "$found"
399
420
  export _LLMSTACK_WARNED_FOR="$found"
400
421
  return 0
401
422
  fi
423
+ if (( ${#_llmstack_warnings[@]} > 0 )); then
424
+ _llmstack_warn_optional "$found"
425
+ fi
402
426
 
403
427
  export OPENCODE_CONFIG="$found/.llmstack/opencode.json"
404
428
  export LLMSTACK_WORK_DIR="$found"
@@ -441,14 +465,15 @@ _BASH_HOOK = r"""# --- llmstack auto-activation hook (bash) --------------------
441
465
  #
442
466
  # Tool-availability gate: before activating we verify the tools needed
443
467
  # for this channel are present:
444
- # - `llmstack` (always required)
445
- # - `llama-swap` (only for local channels: current / next)
446
- # - `llama-server` or `llama-cli` (likewise local-only)
447
- # If any required tool is missing we print a one-shot "folder detected
448
- # but tool not available" warning + install hints and DON'T activate
449
- # (env stays clean). The warning is suppressed on subsequent prompts in
450
- # the same project via the _LLMSTACK_WARNED_FOR guard so we don't spam
451
- # every PROMPT_COMMAND tick.
468
+ # - `llmstack` (always required -- blocker)
469
+ # - `llama-swap` (only for local channels: current / next -- blocker)
470
+ # - `llama-server` / `llama-cli` (local-only, *warning* not blocker --
471
+ # a Bedrock-only models.ini activates fine without llama-server;
472
+ # local GGUF rows would fail to start, hence the heads-up)
473
+ # Blockers print a one-shot "folder detected but tool not available"
474
+ # warning + install hints and DON'T activate (env stays clean). Warnings
475
+ # print a hint and activate anyway. The _LLMSTACK_WARNED_FOR guard
476
+ # suppresses repeat warnings on subsequent PROMPT_COMMAND ticks.
452
477
  #
453
478
  # Marker file format (one line):
454
479
  # <channel>[ <url>]
@@ -501,12 +526,17 @@ _llmstack_find_swap() {
501
526
  }
502
527
 
503
528
  _llmstack_check_tools() {
529
+ # Populates _llmstack_missing (blockers) and _llmstack_warnings
530
+ # (non-blockers). Returns 0 iff there are no blockers.
504
531
  _llmstack_missing=()
532
+ _llmstack_warnings=()
505
533
  command -v llmstack >/dev/null 2>&1 || _llmstack_missing+=("llmstack")
506
534
  if [[ "${1:-current}" != "external" ]]; then
507
535
  _llmstack_find_swap || _llmstack_missing+=("llama-swap")
536
+ # llama-server is a soft requirement: bedrock-only models.ini
537
+ # files don't need it, so missing == warn-and-continue.
508
538
  if ! command -v llama-server >/dev/null 2>&1 && ! command -v llama-cli >/dev/null 2>&1; then
509
- _llmstack_missing+=("llama-server")
539
+ _llmstack_warnings+=("llama-server")
510
540
  fi
511
541
  fi
512
542
  [[ ${#_llmstack_missing[@]} -eq 0 ]]
@@ -530,6 +560,18 @@ _llmstack_warn_missing() {
530
560
  printf ' not activating. install the missing tool(s) and `cd` back in to retry.\n\n'
531
561
  }
532
562
 
563
+ _llmstack_warn_optional() {
564
+ # $1 = project root; uses _llmstack_warnings. One-shot per
565
+ # activation thanks to LLMSTACK_WORK_DIR idempotency.
566
+ printf '\n'
567
+ printf '\033[38;5;220m[llmstack]\033[0m %s: activating without optional local tool(s):\n' "$1"
568
+ local t
569
+ for t in "${_llmstack_warnings[@]}"; do
570
+ _llmstack_install_hint "$t"
571
+ done
572
+ printf ' bedrock-only models.ini works fine; local GGUF rows will fail to start.\n\n'
573
+ }
574
+
533
575
  _llmstack_deactivate() {
534
576
  if [[ -n "${LLMSTACK_WORK_DIR:-}" ]]; then
535
577
  unset OPENCODE_CONFIG LLMSTACK_WORK_DIR LLMSTACK_ACTIVE LLMSTACK_CHANNEL LLMSTACK_REMOTE_URL
@@ -565,11 +607,16 @@ _llmstack_activate() {
565
607
  fi
566
608
  : "${_ch:=current}"
567
609
 
610
+ # Blockers only -- non-blocking warnings (e.g. llama-server for a
611
+ # bedrock-only setup) print a hint but don't skip activation.
568
612
  if ! _llmstack_check_tools "$_ch"; then
569
613
  _llmstack_warn_missing "$found"
570
614
  export _LLMSTACK_WARNED_FOR="$found"
571
615
  return 0
572
616
  fi
617
+ if [[ ${#_llmstack_warnings[@]} -gt 0 ]]; then
618
+ _llmstack_warn_optional "$found"
619
+ fi
573
620
 
574
621
  export OPENCODE_CONFIG="$found/.llmstack/opencode.json"
575
622
  export LLMSTACK_WORK_DIR="$found"
@@ -618,12 +665,25 @@ _POWERSHELL_HOOK = r"""# --- llmstack auto-activation hook (PowerShell) --------
618
665
  #
619
666
  # Tool-availability gate: before activating we verify the tools needed
620
667
  # for this channel are present:
621
- # - llmstack (always required)
622
- # - llama-swap (only for local channels: current / next)
623
- # - llama-server or llama-cli (likewise local-only)
624
- # If any required tool is missing we print a one-shot warning + install
625
- # hints and DON'T activate. The _LLMSTACK_WARNED_FOR guard suppresses
626
- # the warning on subsequent prompts in the same project.
668
+ # - llmstack (always required -- blocker)
669
+ # - llama-swap (only for local channels: current / next -- blocker)
670
+ # - llama-server / llama-cli (local-only, *warning* not blocker --
671
+ # a Bedrock-only models.ini activates fine without llama-server;
672
+ # local GGUF rows would fail to start, hence the heads-up)
673
+ # Blockers print a one-shot warning + install hints and DON'T activate.
674
+ # Warnings print a hint and activate anyway. The _LLMSTACK_WARNED_FOR
675
+ # guard suppresses repeats on subsequent prompts in the same project.
676
+ #
677
+ # Note: this hook file MUST be saved with a `.ps1` extension or
678
+ # PowerShell won't dot-source it (it'll try to open the file via the
679
+ # Windows shell file-association instead). `llmstack activate
680
+ # powershell` writes ~/.powershell_llmstack_hook.ps1 for that reason.
681
+ #
682
+ # Note: PowerShell's default execution policy on Windows (`Restricted`)
683
+ # blocks loading any .ps1 from disk. If you see "running scripts is
684
+ # disabled on this system", allow signed local scripts once with:
685
+ # Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
686
+ # (or `-ExecutionPolicy Bypass` for an even looser policy).
627
687
  #
628
688
  # Add to your $PROFILE (one time):
629
689
  # llmstack activate powershell | Out-String | Invoke-Expression
@@ -691,16 +751,20 @@ function global:_LlmstackFindSwap {
691
751
 
692
752
  function global:_LlmstackCheckTools {
693
753
  param([string]$Channel)
694
- $missing = @()
754
+ # Returns @{ missing = <blockers>; warnings = <non-blockers> }.
755
+ $missing = @()
756
+ $warnings = @()
695
757
  if (-not (Get-Command llmstack -ErrorAction SilentlyContinue)) { $missing += "llmstack" }
696
758
  if ($Channel -ne "external") {
697
759
  if (-not (_LlmstackFindSwap)) { $missing += "llama-swap" }
760
+ # llama-server is a soft requirement: bedrock-only models.ini
761
+ # files don't need it, so missing == warn-and-continue.
698
762
  if (-not (Get-Command llama-server -ErrorAction SilentlyContinue) -and `
699
763
  -not (Get-Command llama-cli -ErrorAction SilentlyContinue)) {
700
- $missing += "llama-server"
764
+ $warnings += "llama-server"
701
765
  }
702
766
  }
703
- return ,$missing
767
+ return @{ missing = ,$missing; warnings = ,$warnings }
704
768
  }
705
769
 
706
770
  function global:_LlmstackInstallHint {
@@ -723,6 +787,20 @@ function global:_LlmstackWarnMissing {
723
787
  Write-Host ""
724
788
  }
725
789
 
790
+ function global:_LlmstackWarnOptional {
791
+ # Non-blocking: tool isn't on PATH but a bedrock-only models.ini
792
+ # would still work. One-shot per activation thanks to the
793
+ # LLMSTACK_WORK_DIR idempotency guard.
794
+ param([string]$Found, [string[]]$Warnings)
795
+ Write-Host ""
796
+ $esc = [char]27
797
+ Write-Host -NoNewline "${esc}[38;5;220m[llmstack]${esc}[0m "
798
+ Write-Host "${Found}: activating without optional local tool(s):"
799
+ foreach ($t in $Warnings) { Write-Host (_LlmstackInstallHint $t) }
800
+ Write-Host " bedrock-only models.ini works fine; local GGUF rows will fail to start."
801
+ Write-Host ""
802
+ }
803
+
726
804
  function global:_LlmstackDeactivate {
727
805
  if ($env:LLMSTACK_WORK_DIR) {
728
806
  Remove-Item Env:OPENCODE_CONFIG -ErrorAction SilentlyContinue
@@ -751,12 +829,15 @@ function global:_LlmstackActivate {
751
829
  $marker = if (Test-Path -LiteralPath $live) { _LlmstackReadMarker $live } else { _LlmstackReadMarker $intent }
752
830
  $channel = if ($marker.channel) { $marker.channel } else { "current" }
753
831
 
754
- $missing = _LlmstackCheckTools $channel
755
- if ($missing.Count -gt 0) {
756
- _LlmstackWarnMissing $found $missing
832
+ $tools = _LlmstackCheckTools $channel
833
+ if ($tools.missing.Count -gt 0) {
834
+ _LlmstackWarnMissing $found $tools.missing
757
835
  $env:_LLMSTACK_WARNED_FOR = $found
758
836
  return
759
837
  }
838
+ if ($tools.warnings.Count -gt 0) {
839
+ _LlmstackWarnOptional $found $tools.warnings
840
+ }
760
841
 
761
842
  $env:OPENCODE_CONFIG = Join-Path $found ".llmstack/opencode.json"
762
843
  $env:LLMSTACK_WORK_DIR = $found
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-llmstack
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: Multi-tier local LLM stack: llama-swap + FastAPI auto-router + opencode wiring.
5
5
  Author: llmstack
6
6
  License: MIT
@@ -313,10 +313,15 @@ py -3 -m venv .venv
313
313
  # `start` falls back to spawning a PowerShell subshell.
314
314
  .venv\Scripts\llmstack start
315
315
 
316
- # 4. Auto-activate per project from any new PowerShell window:
317
- Invoke-Expression (& llmstack activate powershell | Out-String)
318
- # or persist (writes ~/.powershell_llmstack_hook + sources it on every shell):
319
- "Invoke-Expression (& llmstack activate powershell | Out-String)" | Add-Content $PROFILE
316
+ # 4. Auto-activate per project from any new PowerShell window. The hook
317
+ # file is a .ps1 (PowerShell won't dot-source it without that
318
+ # extension) and dot-sourcing it requires script execution to be
319
+ # allowed -- if you see "running scripts is disabled on this
320
+ # system", run once:
321
+ # Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
322
+ llmstack activate powershell | Out-String | Invoke-Expression
323
+ # or persist (writes ~/.powershell_llmstack_hook.ps1 + sources it on every shell):
324
+ "llmstack activate powershell | Out-String | Invoke-Expression" | Add-Content $PROFILE
320
325
  ```
321
326
 
322
327
  Notes:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opencode-llmstack"
7
- version = "0.7.1"
7
+ version = "0.7.3"
8
8
  description = "Multi-tier local LLM stack: llama-swap + FastAPI auto-router + opencode wiring."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"