opencode-codeops 1.10.2 → 1.11.0
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.
- package/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/_shared/reasoning-effort.md +18 -10
- package/bin/lib/reasoning-effort.d.mts +6 -0
- package/bin/lib/reasoning-effort.mjs +81 -8
- package/package.json +1 -1
- package/plugin/index.ts +6 -0
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
- package/skills/setup-routing/routing.md +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CodeOps are recorded here.
|
|
4
4
|
|
|
5
|
+
## 1.11.0 — 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- effort: map missing levels to the nearest exposed variant
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
- effort: note the exposed model variant set
|
|
14
|
+
- trace: record live verification and close T-03
|
|
15
|
+
|
|
16
|
+
## 1.10.3 — 2026-10-04
|
|
17
|
+
|
|
18
|
+
### Fixes
|
|
19
|
+
|
|
20
|
+
- effort: prefer the model variant over the reasoning-support flag
|
|
21
|
+
|
|
5
22
|
## 1.10.2 — 2026-10-04
|
|
6
23
|
|
|
7
24
|
### Fixes
|
package/README.md
CHANGED
|
@@ -166,8 +166,10 @@ Reasoning-heavy skills also accept `--auto-effort` (use the skill's recommended
|
|
|
166
166
|
`--auto-effort=high` (explicit level), announce it, and clear it before the final run summary.
|
|
167
167
|
Resolution order: dispatch marker, then session `--auto-effort`, then
|
|
168
168
|
`routing.roles.<agent>.reasoning` in `codeops/codeops.json`, then the inherited parent variant.
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
A level the active model does not expose resolves to the nearest exposed variant (`medium`
|
|
170
|
+
becomes `high` on a `low`/`high`/`max` model). The levels are suggestions: no permission,
|
|
171
|
+
verification step, or review gate ever reads them. The full contract is in
|
|
172
|
+
`_shared/reasoning-effort.md`.
|
|
171
173
|
|
|
172
174
|
## Project specialists
|
|
173
175
|
|
|
@@ -16,13 +16,18 @@ contract consumed by the plugin and the skills. It is advisory by design and nev
|
|
|
16
16
|
| `high` | Correctness- or security-sensitive, cross-cutting, or planning/review work | Requirements, planning, correctness/security review, ambiguous implementation |
|
|
17
17
|
| `max` | Adversarial or high-risk analysis where a missed detail is costly | Thorough preflight, complex or sensitive phases |
|
|
18
18
|
|
|
19
|
-
The four levels are the complete suggestion vocabulary. A level is applied
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
The four levels are the complete suggestion vocabulary. A level is applied through the model's
|
|
20
|
+
own variant record: the exact variant when it exists, otherwise the nearest exposed level on the
|
|
21
|
+
provider scale, with ties resolving to the higher level. `none` stays exact-match only, because
|
|
22
|
+
mapping it to a reasoning level would enable reasoning the caller disabled. When no variant can
|
|
23
|
+
be chosen — or when the model exposes no variants and does not report reasoning support — the
|
|
24
|
+
request is left unchanged, and it never raises a provider error. Routing policy is project
|
|
25
|
+
configuration, not a suggestion: `routing.roles.<agent>.reasoning` may name any value from the
|
|
26
|
+
provider enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`).
|
|
27
|
+
|
|
28
|
+
The deepseek flash model in this environment exposes `low`, `high`, and `max`, so a `medium`
|
|
29
|
+
suggestion resolves to `high` there. Confirm the active model's set by enabling
|
|
30
|
+
`CODEOPS_EFFORT_TRACE=1` and reading `variantLevels` on an apply line.
|
|
26
31
|
|
|
27
32
|
## Marker grammar
|
|
28
33
|
|
|
@@ -99,6 +104,9 @@ already records:
|
|
|
99
104
|
| Docs/config/rename-only phase with deterministic verification | `low` |
|
|
100
105
|
| Any other non-trivial phase | `medium` |
|
|
101
106
|
|
|
107
|
+
The runtime maps a missing level to the nearest exposed variant, so the four levels remain valid
|
|
108
|
+
suggestions on every reasoning-capable model.
|
|
109
|
+
|
|
102
110
|
The line is written as `> **Reasoning**: <level> — <one-line reason>`. It is a suggestion: the
|
|
103
111
|
user may edit or delete it, and `exec-plan` never blocks on it. A plan without the line keeps
|
|
104
112
|
the inherited behavior.
|
|
@@ -135,9 +143,9 @@ forces that level for the whole run, including every dispatch marker composed du
|
|
|
135
143
|
Set `CODEOPS_EFFORT_TRACE=1` (or `true`) in the environment that starts OpenCode to record what
|
|
136
144
|
the plugin does. One content-free JSON line is appended per marker capture and per request to
|
|
137
145
|
`reasoning-effort-trace.jsonl` inside the session's scratch directory. Lines carry only a
|
|
138
|
-
timestamp, event name, message id, agent name, level, source, and applied flag —
|
|
139
|
-
text or file content. The trace lives with the rest of the session scratch and is
|
|
140
|
-
it. Tracing is off by default and never affects a request.
|
|
146
|
+
timestamp, event name, message id, agent name, level, source, chosen variant, and applied flag —
|
|
147
|
+
never prompt text or file content. The trace lives with the rest of the session scratch and is
|
|
148
|
+
removed with it. Tracing is off by default and never affects a request.
|
|
141
149
|
|
|
142
150
|
## Suggestion-only guarantee
|
|
143
151
|
|
|
@@ -84,6 +84,12 @@ export declare function extractModelVariants(
|
|
|
84
84
|
/** Check whether a model advertises reasoning support. */
|
|
85
85
|
export declare function modelSupportsReasoning(model: unknown): boolean
|
|
86
86
|
|
|
87
|
+
/** Choose the model variant key to apply for a requested level. */
|
|
88
|
+
export declare function selectEffortVariant(
|
|
89
|
+
level: unknown,
|
|
90
|
+
model: unknown
|
|
91
|
+
): RoutingReasoning | undefined
|
|
92
|
+
|
|
87
93
|
/** Merge the model's variant options for a level into the request options. */
|
|
88
94
|
export declare function applyEffort(
|
|
89
95
|
options: Record<string, unknown>,
|
|
@@ -303,15 +303,89 @@ export function modelSupportsReasoning(model) {
|
|
|
303
303
|
return capabilities.reasoning === true
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Order of provider reasoning levels, weakest to strongest.
|
|
308
|
+
*
|
|
309
|
+
* The scale is used only to pick the nearest exposed variant when a model
|
|
310
|
+
* does not offer the exact requested level.
|
|
311
|
+
*/
|
|
312
|
+
const EFFORT_RANK = Object.freeze({
|
|
313
|
+
none: 0,
|
|
314
|
+
minimal: 1,
|
|
315
|
+
low: 2,
|
|
316
|
+
medium: 3,
|
|
317
|
+
high: 4,
|
|
318
|
+
xhigh: 5,
|
|
319
|
+
max: 6,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Choose the model variant key to apply for a requested level.
|
|
324
|
+
*
|
|
325
|
+
* The exact level wins when the model exposes it. Otherwise the nearest
|
|
326
|
+
* exposed level on the provider scale is chosen, and ties resolve to the
|
|
327
|
+
* higher level so ordinary work is never under-powered. `none` is
|
|
328
|
+
* exact-match only: mapping it to a reasoning level would enable reasoning
|
|
329
|
+
* the caller explicitly disabled. Keys whose value is not a plain object are
|
|
330
|
+
* ignored, so a malformed variant can never reach the request options.
|
|
331
|
+
*
|
|
332
|
+
* @param level - Requested level (any routing enum value)
|
|
333
|
+
* @param model - Model object from the hook input
|
|
334
|
+
* @returns The variant key to apply, or `undefined` when none applies
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* selectEffortVariant("medium", { variants: { low: {}, high: {} } }) // "high"
|
|
338
|
+
*/
|
|
339
|
+
export function selectEffortVariant(level, model) {
|
|
340
|
+
if (!isRoutingReasoning(level)) return undefined
|
|
341
|
+
const variants = extractModelVariants(model)
|
|
342
|
+
if (variants === undefined) return undefined
|
|
343
|
+
|
|
344
|
+
if (level === "none") {
|
|
345
|
+
return isPlainObject(variants.none) ? "none" : undefined
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const candidates = Object.keys(variants).filter(
|
|
349
|
+
(key) =>
|
|
350
|
+
key !== "none" &&
|
|
351
|
+
Object.prototype.hasOwnProperty.call(EFFORT_RANK, key) &&
|
|
352
|
+
isPlainObject(variants[key])
|
|
353
|
+
)
|
|
354
|
+
if (candidates.includes(level)) return level
|
|
355
|
+
|
|
356
|
+
let best
|
|
357
|
+
for (const key of candidates) {
|
|
358
|
+
if (best === undefined) {
|
|
359
|
+
best = key
|
|
360
|
+
continue
|
|
361
|
+
}
|
|
362
|
+
const distance = Math.abs(EFFORT_RANK[key] - EFFORT_RANK[level])
|
|
363
|
+
const bestDistance = Math.abs(EFFORT_RANK[best] - EFFORT_RANK[level])
|
|
364
|
+
if (
|
|
365
|
+
distance < bestDistance ||
|
|
366
|
+
(distance === bestDistance && EFFORT_RANK[key] > EFFORT_RANK[best])
|
|
367
|
+
) {
|
|
368
|
+
best = key
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return best
|
|
372
|
+
}
|
|
373
|
+
|
|
306
374
|
/**
|
|
307
375
|
* Merge the model's variant options for a level into the request options.
|
|
308
376
|
*
|
|
309
377
|
* The runtime model carries a `variants` record whose entries are the exact
|
|
310
378
|
* provider options for each level (for example `reasoningEffort`, or a nested
|
|
311
379
|
* `reasoning.effort`). This function is the only place that mapping is
|
|
312
|
-
* consumed, so the plugin never hardcodes a provider key. The
|
|
313
|
-
*
|
|
314
|
-
*
|
|
380
|
+
* consumed, so the plugin never hardcodes a provider key. The applied key is
|
|
381
|
+
* chosen by {@link selectEffortVariant}: exact match first, then the nearest
|
|
382
|
+
* exposed level. A matching variant applies even when the model's reasoning
|
|
383
|
+
* capability flag is absent or false, because the variant itself is the
|
|
384
|
+
* provider-known option set and the host applies inherited variants the same
|
|
385
|
+
* way. Only when no variants record exists does the documented
|
|
386
|
+
* `reasoningEffort` passthrough require an explicit reasoning capability. The
|
|
387
|
+
* function is pure: it returns a new object when a change applies and the
|
|
388
|
+
* original object reference otherwise.
|
|
315
389
|
*
|
|
316
390
|
* @param options - Current provider options
|
|
317
391
|
* @param level - Candidate level from {@link resolveEffort}
|
|
@@ -320,16 +394,15 @@ export function modelSupportsReasoning(model) {
|
|
|
320
394
|
*/
|
|
321
395
|
export function applyEffort(options, level, model) {
|
|
322
396
|
if (!isRoutingReasoning(level)) return options
|
|
323
|
-
if (!modelSupportsReasoning(model)) return options
|
|
324
397
|
|
|
325
398
|
const variants = extractModelVariants(model)
|
|
326
399
|
if (variants !== undefined) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return deepMergePlain(options, variantOptions)
|
|
400
|
+
const variantKey = selectEffortVariant(level, model)
|
|
401
|
+
if (variantKey === undefined) return options
|
|
402
|
+
return deepMergePlain(options, variants[variantKey])
|
|
331
403
|
}
|
|
332
404
|
|
|
405
|
+
if (!modelSupportsReasoning(model)) return options
|
|
333
406
|
return { ...options, reasoningEffort: level }
|
|
334
407
|
}
|
|
335
408
|
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -12,13 +12,16 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
appendEffortTrace,
|
|
14
14
|
applyEffort,
|
|
15
|
+
extractModelVariants,
|
|
15
16
|
findEffortMarker,
|
|
16
17
|
isEffortLevel,
|
|
17
18
|
isEffortTraceEnabled,
|
|
18
19
|
isRoutingReasoning,
|
|
20
|
+
modelSupportsReasoning,
|
|
19
21
|
readRoutingReasoning,
|
|
20
22
|
readSessionEffort,
|
|
21
23
|
resolveEffort,
|
|
24
|
+
selectEffortVariant,
|
|
22
25
|
} from "../bin/lib/reasoning-effort.mjs"
|
|
23
26
|
import type { EffortLevel } from "../bin/lib/reasoning-effort.mjs"
|
|
24
27
|
|
|
@@ -348,6 +351,9 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
348
351
|
level,
|
|
349
352
|
source,
|
|
350
353
|
applied: changed,
|
|
354
|
+
variant: selectEffortVariant(level, input.model) ?? null,
|
|
355
|
+
reasoningSupported: modelSupportsReasoning(input.model),
|
|
356
|
+
variantLevels: Object.keys(extractModelVariants(input.model) ?? {}),
|
|
351
357
|
})
|
|
352
358
|
if (!changed) {
|
|
353
359
|
if (marker !== undefined) {
|
|
Binary file
|
|
@@ -45,10 +45,15 @@ comes from an execution plan's per-phase suggestion; a session level comes from
|
|
|
45
45
|
when a role entry exists — with no entry the child inherits the parent variant.
|
|
46
46
|
|
|
47
47
|
The suggestion vocabulary is `low`, `medium`, `high`, and `max`, while the routing field accepts
|
|
48
|
-
the wider provider enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`).
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
the wider provider enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). A level the
|
|
49
|
+
model does not expose resolves to the nearest exposed variant (ties choose the higher level);
|
|
50
|
+
`none` is exact-match only. On the deepseek flash model in this environment
|
|
51
|
+
(`low`/`high`/`max`), `medium` therefore resolves to `high`. Confirm the active set with
|
|
52
|
+
`CODEOPS_EFFORT_TRACE=1` and `variantLevels` on an apply line (see
|
|
53
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md)). Prefer `low` for
|
|
54
|
+
mechanical work, `medium` (or the nearest exposed level) for bounded implementation, `high` for
|
|
55
|
+
planning and review, and `max` only for adversarial analysis. A value that cannot be mapped is
|
|
56
|
+
skipped, so a routing entry never produces a provider error.
|
|
52
57
|
|
|
53
58
|
Model pins are optional per role. When omitted, OpenCode resolves the model from the explicit spawn, project defaults, and parent session. A missing pin must never block the workflow.
|
|
54
59
|
|