Commit 05affb9
authored
feat: auth-aware source model resolution (#348)
* docs(plans): add auth-aware source model resolution plan
Plan for expanding SOURCE_CATEGORY_MODEL_DEFAULTS from a single
provider/model string per category to an ordered array, resolved at
plugin config(cfg) time by reading OpenCode's auth.json and selecting
the first array entry whose provider is authenticated.
4 implementation units:
1. Expand source-default shape and update validators
2. Add auth-file reader and auth-aware resolver
3. Wire the auth-aware resolver into the config hook
4. Document the new behavior
Origin: docs/brainstorms/2026-05-09-auth-aware-source-model-resolution-requirements.md
(gitignored, local-only per project convention).
Document review: 3 reviewers (coherence, feasibility, security-lens),
10 findings, all addressed before commit. Confidence check passed
without deepening — local patterns abundant, brainstorm research
exhaustive.
* feat(agent-overlays): expand source category defaults to ordered arrays
Change SOURCE_CATEGORY_MODEL_DEFAULTS shape from Record<CategoryId, string>
to Record<CategoryId, readonly string[]>. Each array is an ordered
preference list, most preferred first.
This commit is shape-only — getSourceCategoryModel(category) still
returns the first array entry, preserving current zero-config behavior.
The auth-aware resolution that picks based on which provider is
authenticated lands in a follow-up commit.
validateSourceCategoryModelDefaults now rejects non-arrays and empty
arrays explicitly, and iterates each entry with indexed key paths
(source category model defaults.${category}[${index}]) for clearer
error messages.
* feat(agent-overlays): add auth-aware source model resolver
Add getAuthenticatedProviders() that reads OpenCode's auth.json
synchronously using the XDG_DATA_HOME path convention. Returns the
top-level keys as a ReadonlySet<string>. Treats missing files as
'no providers authenticated' silently; emits a single stderr
diagnostic for unreadable or malformed files without leaking
contents. Reads only Object.keys; nested values (API keys, OAuth
tokens) are never inspected, logged, persisted, or transmitted.
Extend getSourceCategoryModel(category, authedProviders?) with an
optional second parameter. When provided, the resolver returns the
first array entry whose provider ID (substring before the first /)
is in the authenticated set. Falls back to array[0] when no entry
matches or when no auth set is supplied (backward compat).
Tests: 15 new scenarios including a real-token leak fixture that
captures stderr/stdout and asserts no secret-shaped strings appear,
and a file-not-modified contract verified via mtime + sha256.
* feat(config-handler): wire auth-aware resolver into config hook
Read OpenCode's authenticated providers once per config(cfg) hook
invocation in createConfigHandler, thread the ReadonlySet through
collectAgents and applyAgentOverlays, and pass it to
getSourceCategoryModel(category, authedProviders) so source
defaults pick the first array entry whose provider is authenticated.
Overlay precedence is unchanged: source default -> category overlay
-> exact overlay. Auth-aware resolution only affects the source
default; user/category/exact overlays still win as today.
Add ConfigHandlerDeps.getAuthenticatedProviders for test injection.
Tests:
- 6 new unit tests in tests/unit/config-handler.test.ts covering
no-auth zero-config, single-provider match, multi-provider
first-match-wins, category-overlay-wins, exact-overlay-wins, and
the read-once contract via injected counting spy.
- 2 new integration scenarios in tests/integration/opencode.test.ts
using the existing homeDir fixture: single-provider auth and
multi-provider auth, asserting emitted models match per-category
expectations.
* docs: explain auth-aware source model resolution
Document that SOURCE_CATEGORY_MODEL_DEFAULTS is now an ordered
preference array per category and that the resolver reads OpenCode's
auth.json at config(cfg) time to pick the first array entry whose
provider is authenticated. Falls back to the first array entry when
no match is found.
Make the resolution precedence explicit: user agents.<key>.model >
user categories.<id>.model > source-default-resolver > bundled
markdown > OpenCode inheritance. User overlays remain scalar
provider/model strings; arrays are not accepted in user config in
this iteration.
Document two known limitations: (1) autoload-true providers like
AWS Bedrock that load from environment variables may not appear
in auth.json and may be skipped by the resolver — pin via category
or exact overlay; (2) a tiny race window exists with 'opencode
auth login' that resolves on next OpenCode restart.
Preserve the existing 'Systematic does not support fallback_models'
sentence — the arrays are a preference list, not a runtime fallback
chain.
* Address PR review feedback (#348)
- Fix CodeQL js/file-system-race in tests/unit/agent-overlays.test.ts
by removing the redundant second fs.readFileSync; mtime + size
comparison is sufficient to prove the file wasn't touched.
- Narrow isSystemError type assertion from Record<string, unknown>
to { code: unknown } per Fro Bot NBC.
- Replace the nested-form provider-extraction test with a focused
public-API assertion of the slashIndex logic, naming the false-match
guarantee in the test description.
- Add 3 XDG_DATA_HOME branch tests covering absolute path, empty
fallback, and non-absolute fallback.
- Mark plan units 1-4 as completed and flip plan status to completed.
Net: +3 tests, 562 unit tests pass.1 parent 6e14d5d commit 05affb9
8 files changed
Lines changed: 1058 additions & 20 deletions
File tree
- docs
- plans
- src/content/docs/getting-started
- src/lib
- tests
- integration
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
338 | 338 | | |
339 | 339 | | |
340 | 340 | | |
| 341 | + | |
| 342 | + | |
341 | 343 | | |
342 | 344 | | |
343 | 345 | | |
| |||
Lines changed: 343 additions & 0 deletions
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
122 | 156 | | |
123 | 157 | | |
124 | 158 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
52 | 56 | | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
60 | 64 | | |
61 | 65 | | |
62 | 66 | | |
| |||
178 | 182 | | |
179 | 183 | | |
180 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
181 | 243 | | |
182 | 244 | | |
| 245 | + | |
183 | 246 | | |
184 | 247 | | |
185 | | - | |
186 | | - | |
187 | | - | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
188 | 267 | | |
189 | 268 | | |
190 | 269 | | |
| |||
204 | 283 | | |
205 | 284 | | |
206 | 285 | | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
211 | | - | |
212 | | - | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
213 | 304 | | |
214 | 305 | | |
215 | 306 | | |
| |||
637 | 728 | | |
638 | 729 | | |
639 | 730 | | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| 27 | + | |
| 28 | + | |
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| |||
157 | 160 | | |
158 | 161 | | |
159 | 162 | | |
| 163 | + | |
160 | 164 | | |
161 | 165 | | |
162 | 166 | | |
| |||
174 | 178 | | |
175 | 179 | | |
176 | 180 | | |
177 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
178 | 187 | | |
179 | 188 | | |
180 | 189 | | |
| |||
185 | 194 | | |
186 | 195 | | |
187 | 196 | | |
| 197 | + | |
188 | 198 | | |
189 | 199 | | |
190 | 200 | | |
| |||
213 | 223 | | |
214 | 224 | | |
215 | 225 | | |
216 | | - | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
217 | 230 | | |
218 | 231 | | |
219 | 232 | | |
| |||
421 | 434 | | |
422 | 435 | | |
423 | 436 | | |
| 437 | + | |
| 438 | + | |
424 | 439 | | |
425 | 440 | | |
426 | 441 | | |
| |||
449 | 464 | | |
450 | 465 | | |
451 | 466 | | |
| 467 | + | |
452 | 468 | | |
453 | 469 | | |
454 | 470 | | |
455 | 471 | | |
456 | 472 | | |
| 473 | + | |
457 | 474 | | |
458 | 475 | | |
459 | 476 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
| 131 | + | |
131 | 132 | | |
132 | 133 | | |
133 | 134 | | |
| |||
427 | 428 | | |
428 | 429 | | |
429 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
430 | 474 | | |
431 | 475 | | |
432 | 476 | | |
| |||
0 commit comments