opencode-codeops 1.10.3 → 2.0.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 +21 -0
- package/README.md +36 -20
- package/_shared/reasoning-effort.md +25 -13
- package/_shared/workspace-hygiene.md +10 -6
- package/bin/index.mjs +7 -1
- package/bin/lib/opencode-plugin.mjs +122 -24
- package/bin/lib/reasoning-effort.d.mts +11 -0
- package/bin/lib/reasoning-effort.mjs +118 -11
- package/package.json +7 -6
- package/plugin/index.ts +236 -211
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
- package/skills/setup-routing/routing.md +9 -4
- package/standards/coding-standards-full.md +2 -2
|
@@ -303,19 +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.
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
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.
|
|
319
389
|
*
|
|
320
390
|
* @param options - Current provider options
|
|
321
391
|
* @param level - Candidate level from {@link resolveEffort}
|
|
@@ -327,16 +397,53 @@ export function applyEffort(options, level, model) {
|
|
|
327
397
|
|
|
328
398
|
const variants = extractModelVariants(model)
|
|
329
399
|
if (variants !== undefined) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return deepMergePlain(options, variantOptions)
|
|
400
|
+
const variantKey = selectEffortVariant(level, model)
|
|
401
|
+
if (variantKey === undefined) return options
|
|
402
|
+
return deepMergePlain(options, variants[variantKey])
|
|
334
403
|
}
|
|
335
404
|
|
|
336
405
|
if (!modelSupportsReasoning(model)) return options
|
|
337
406
|
return { ...options, reasoningEffort: level }
|
|
338
407
|
}
|
|
339
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Normalize a host model's variant collection into a keyed record.
|
|
411
|
+
*
|
|
412
|
+
* OpenCode 2 exposes model variants as an array of entries shaped
|
|
413
|
+
* `{ id, settings }`, while {@link applyEffort} consumes a record keyed by
|
|
414
|
+
* variant id. This adapter bridges the two without changing the core logic.
|
|
415
|
+
* A record input is passed through unchanged, so v1-style callers and tests
|
|
416
|
+
* keep working. Malformed entries — non-objects, missing ids, non-object
|
|
417
|
+
* settings — are skipped; anything that is not an array or plain object yields
|
|
418
|
+
* an empty record.
|
|
419
|
+
*
|
|
420
|
+
* @param variants - Variant collection from the host (any shape)
|
|
421
|
+
* @returns A record mapping variant id to its settings object
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* normalizeModelVariants([{ id: "high", settings: { reasoningEffort: "high" } }])
|
|
425
|
+
* // { high: { reasoningEffort: "high" } }
|
|
426
|
+
*/
|
|
427
|
+
export function normalizeModelVariants(variants) {
|
|
428
|
+
if (isPlainObject(variants)) return { ...variants }
|
|
429
|
+
if (!Array.isArray(variants)) return {}
|
|
430
|
+
|
|
431
|
+
const record = {}
|
|
432
|
+
for (const entry of variants) {
|
|
433
|
+
if (!isPlainObject(entry)) continue
|
|
434
|
+
const id = entry.id
|
|
435
|
+
if (typeof id !== "string" || id.length === 0) continue
|
|
436
|
+
const settings = isPlainObject(entry.settings) ? entry.settings : {}
|
|
437
|
+
Object.defineProperty(record, id, {
|
|
438
|
+
value: settings,
|
|
439
|
+
writable: true,
|
|
440
|
+
enumerable: true,
|
|
441
|
+
configurable: true,
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
return record
|
|
445
|
+
}
|
|
446
|
+
|
|
340
447
|
/**
|
|
341
448
|
* Recursively merge plain objects into a new object.
|
|
342
449
|
*
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codeops",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Specification-first engineering for complex systems — CodeOps plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=18"
|
|
7
|
+
"node": ">=18",
|
|
8
|
+
"opencode": ">=2 <3"
|
|
8
9
|
},
|
|
9
10
|
"main": "./plugin/index.ts",
|
|
10
11
|
"exports": {
|
|
11
|
-
".": "./plugin/index.ts"
|
|
12
|
+
".": "./plugin/index.ts",
|
|
13
|
+
"./server": "./plugin/index.ts"
|
|
12
14
|
},
|
|
13
15
|
"bin": {
|
|
14
16
|
"opencode-codeops": "bin/index.mjs"
|
|
@@ -62,11 +64,10 @@
|
|
|
62
64
|
"bugs": {
|
|
63
65
|
"url": "https://github.com/blendsdk/opencode-codeops/issues"
|
|
64
66
|
},
|
|
65
|
-
"
|
|
66
|
-
"@opencode
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@opencode/plugin": "^2.0.24"
|
|
67
69
|
},
|
|
68
70
|
"devDependencies": {
|
|
69
|
-
"@opencode-ai/plugin": "1.18.30",
|
|
70
71
|
"@types/node": "^22.20.2",
|
|
71
72
|
"typescript": "^5.0.0"
|
|
72
73
|
}
|