@pfoundation/ocadvisor 26.9.1 → 26.9.3
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/README.md +444 -46
- package/dist/artificialAnalysis.d.ts +4 -0
- package/dist/artificialAnalysis.d.ts.map +1 -0
- package/dist/artificialAnalysis.js +77 -0
- package/dist/artificialAnalysis.js.map +1 -0
- package/dist/benchmarkConfig.d.ts +21 -0
- package/dist/benchmarkConfig.d.ts.map +1 -0
- package/dist/benchmarkConfig.js +59 -0
- package/dist/benchmarkConfig.js.map +1 -0
- package/dist/benchmarkEvidence.d.ts +37 -0
- package/dist/benchmarkEvidence.d.ts.map +1 -0
- package/dist/benchmarkEvidence.js +81 -0
- package/dist/benchmarkEvidence.js.map +1 -0
- package/dist/benchmarkMatch.d.ts +58 -0
- package/dist/benchmarkMatch.d.ts.map +1 -0
- package/dist/benchmarkMatch.js +259 -0
- package/dist/benchmarkMatch.js.map +1 -0
- package/dist/benchmarkStore.d.ts +45 -0
- package/dist/benchmarkStore.d.ts.map +1 -0
- package/dist/benchmarkStore.js +309 -0
- package/dist/benchmarkStore.js.map +1 -0
- package/dist/benchmarkTypes.d.ts +48 -0
- package/dist/benchmarkTypes.d.ts.map +1 -0
- package/dist/benchmarkTypes.js +298 -0
- package/dist/benchmarkTypes.js.map +1 -0
- package/dist/benchmarkUpdate.d.ts +33 -0
- package/dist/benchmarkUpdate.d.ts.map +1 -0
- package/dist/benchmarkUpdate.js +327 -0
- package/dist/benchmarkUpdate.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +397 -0
- package/dist/cli.js.map +1 -0
- package/dist/data/artificialAnalysis.mappings.json +229 -0
- package/dist/data/artificialAnalysis.snapshot.json +17684 -0
- package/dist/modelProfiles.d.ts +45 -0
- package/dist/modelProfiles.d.ts.map +1 -0
- package/dist/modelProfiles.js +141 -0
- package/dist/modelProfiles.js.map +1 -0
- package/dist/ocAdvisor.d.ts +105 -5
- package/dist/ocAdvisor.d.ts.map +1 -1
- package/dist/ocAdvisor.js +534 -45
- package/dist/ocAdvisor.js.map +1 -1
- package/dist/typesafeGate.d.ts +96 -0
- package/dist/typesafeGate.d.ts.map +1 -0
- package/dist/typesafeGate.js +475 -0
- package/dist/typesafeGate.js.map +1 -0
- package/dist/typesafeState.d.ts +94 -0
- package/dist/typesafeState.d.ts.map +1 -0
- package/dist/typesafeState.js +301 -0
- package/dist/typesafeState.js.map +1 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -40,18 +40,25 @@ and the usage report accept both `advisor` and the pre-rename `ocAdvisor`.
|
|
|
40
40
|
|
|
41
41
|
## Configuration
|
|
42
42
|
|
|
43
|
-
The advisor model and limits are configurable. Defaults
|
|
44
|
-
`anthropic/claude-fable-5-1#
|
|
45
|
-
transcript cap
|
|
43
|
+
The advisor model and limits are configurable. Defaults:
|
|
44
|
+
`anthropic/claude-fable-5-1#xhigh`, a 300 s generation timeout, no
|
|
45
|
+
transcript cap, TypeSafe screening on when a key is present, and
|
|
46
|
+
model-capability evidence from a local Artificial Analysis snapshot when
|
|
47
|
+
one is available.
|
|
46
48
|
|
|
47
49
|
| Option | Default | Meaning |
|
|
48
50
|
|---|---|---|
|
|
49
51
|
| `model` | `claude-fable-5-1` | Model id, or a full `provider/model#variant` reference |
|
|
50
52
|
| `provider` | `anthropic` | Provider id (overrides the provider in `model`) |
|
|
51
|
-
| `variant` | `
|
|
53
|
+
| `variant` | `xhigh` | Reasoning-effort variant; `null` or `"none"` pins no variant |
|
|
52
54
|
| `timeoutMs` | `300000` | Per-consultation generation timeout, in milliseconds |
|
|
53
55
|
| `maxTranscriptChars` | `0` | Cap on transcript size (`0` = unlimited); the most recent tail is kept |
|
|
54
56
|
| `agentEffort` | `false` | Let the agent pick effort per call: `true` allows `high`, `xhigh`, `max`; an array or comma string sets an explicit allow-list |
|
|
57
|
+
| `disabledForModels` | `[]` | Exact caller `provider/model` IDs that must not see or invoke advisor. Applies to every effort variant of that ID. A string array is the documented form; a comma-separated string is also accepted |
|
|
58
|
+
| `typesafe` | enabled with a key | `false` disables screening; `true` or an object enables it (see below) |
|
|
59
|
+
| `benchmarks.path` | data-directory snapshot | Absolute path to the local Artificial Analysis snapshot file |
|
|
60
|
+
| `benchmarks.mappingsPath` | beside the snapshot | Absolute path to the local model-mapping overrides file |
|
|
61
|
+
| `benchmarks.matchAnyProvider` | `false` | Resolve a model on another provider route when no exact provider binding exists (see below) |
|
|
55
62
|
|
|
56
63
|
Set them as plugin options in `opencode.json`. Because a plugin loaded from
|
|
57
64
|
the auto-discovered `plugin/` directory cannot receive options, list it
|
|
@@ -87,51 +94,344 @@ With `agentEffort` enabled the tool accepts an optional `effort` argument
|
|
|
87
94
|
model fails the call with an `invalid_effort` error instead of silently
|
|
88
95
|
falling back.
|
|
89
96
|
|
|
97
|
+
To hide advisor from selected *caller* models (for example Astra), list
|
|
98
|
+
their exact `provider/model` IDs. This is independent of `model` /
|
|
99
|
+
`provider`, which configure the advisor itself:
|
|
100
|
+
|
|
101
|
+
```jsonc
|
|
102
|
+
{
|
|
103
|
+
"$schema": "https://opencode.ai/config.json",
|
|
104
|
+
"plugins": [
|
|
105
|
+
{
|
|
106
|
+
"package": "@pfoundation/ocadvisor",
|
|
107
|
+
"options": {
|
|
108
|
+
"disabledForModels": ["openai/gpt-6-astra"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Matching is exact and case-sensitive on `provider/model`. Effort variants
|
|
116
|
+
are ignored, so `openai/gpt-6-astra#xhigh` is still excluded by
|
|
117
|
+
`openai/gpt-6-astra`. Nearby names (`openai/gpt-6-astra-preview`) and other
|
|
118
|
+
providers do not match. Gateway IDs keep the serving provider: list
|
|
119
|
+
`openrouter/openai/gpt-6-astra` to exclude that route, not
|
|
120
|
+
`openai/gpt-6-astra`. Bare names, `#variant` suffixes, and wildcards are
|
|
121
|
+
rejected at config load.
|
|
122
|
+
|
|
123
|
+
Plugin options replace the environment list rather than merging it. An
|
|
124
|
+
explicit `[]` (or a blank string) clears configurable exclusions even when
|
|
125
|
+
`OCADVISOR_DISABLED_FOR_MODELS` is set:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
OCADVISOR_DISABLED_FOR_MODELS=openai/gpt-6-astra,anthropic/claude-opus-5
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
After changing plugin options, restart the background service
|
|
132
|
+
(`opencode service restart`) so the server reloads configuration.
|
|
133
|
+
|
|
90
134
|
Environment variables work for any install and take
|
|
91
135
|
lower precedence than plugin options: `OCADVISOR_MODEL` (accepts
|
|
92
136
|
`provider/model#variant`), `OCADVISOR_PROVIDER`, `OCADVISOR_VARIANT`,
|
|
93
137
|
`OCADVISOR_TIMEOUT_MS`, `OCADVISOR_MAX_TRANSCRIPT_CHARS`,
|
|
94
|
-
`OCADVISOR_AGENT_EFFORT` (`true`, `false`, or a comma-separated allow-list)
|
|
138
|
+
`OCADVISOR_AGENT_EFFORT` (`true`, `false`, or a comma-separated allow-list),
|
|
139
|
+
`OCADVISOR_DISABLED_FOR_MODELS` (comma-separated exact `provider/model` IDs),
|
|
140
|
+
`OCADVISOR_BENCHMARKS_PATH`, `OCADVISOR_BENCHMARK_MAPPINGS_PATH`, and
|
|
141
|
+
`OCADVISOR_BENCHMARKS_MATCH_ANY_PROVIDER` (`true`/`false`).
|
|
95
142
|
|
|
96
143
|
The "already the advisor model" skip is still keyed to Fable
|
|
97
144
|
(`anthropic/claude-fable-*`); if you point the advisor at a different model,
|
|
98
|
-
that self-consultation guard no longer matches it.
|
|
145
|
+
that self-consultation guard no longer matches it. Fable sessions stay
|
|
146
|
+
blocked even when they also appear in `disabledForModels`.
|
|
99
147
|
|
|
100
148
|
## Usage policy (what agents are told)
|
|
101
149
|
|
|
102
|
-
Use `advisor`
|
|
103
|
-
|
|
150
|
+
Use `advisor` when an independent perspective could improve the approach, help
|
|
151
|
+
resolve a problem, or strengthen an implementation review. On substantial
|
|
152
|
+
work, consider consulting before committing to an approach, when progress
|
|
153
|
+
stalls, or before completing meaningful changes. Additional consultations are
|
|
154
|
+
welcome as the work evolves — particularly when new evidence appears, the
|
|
155
|
+
approach changes, or another concern needs review. Straightforward tasks
|
|
156
|
+
usually need no consultation.
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
second opinion has the most value — pick one stage, not all three:
|
|
158
|
+
Typical checkpoints:
|
|
107
159
|
|
|
108
160
|
| Situation | Mode / trigger |
|
|
109
161
|
|---|---|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
162
|
+
| Deciding an approach with real tradeoffs | `plan` / `before_approach` |
|
|
163
|
+
| Stalled progress or contradictory evidence | `debug` / `stuck` |
|
|
164
|
+
| Reviewing meaningful changes before declaring done | `review` / `pre_complete` |
|
|
113
165
|
|
|
114
166
|
Rules enforced by the tool description and an injected session instruction:
|
|
115
167
|
|
|
116
168
|
- Always pass a concrete `question` naming the decision or artifact.
|
|
117
|
-
-
|
|
118
|
-
unresolved issue, or an explicit user request (`followup` trigger to
|
|
169
|
+
- Avoid repeating settled questions without new context (`followup` trigger to
|
|
119
170
|
reconcile conflicts with primary-source evidence).
|
|
120
171
|
- Give the advice serious weight; a passing self-test alone is not
|
|
121
172
|
counter-evidence. Clear factual corrections do not need another
|
|
122
173
|
confirmation call.
|
|
123
174
|
- The tool is hidden in `anthropic/claude-fable-*` sessions (the current
|
|
124
|
-
model is already Fable)
|
|
175
|
+
model is already Fable) and in sessions whose caller model is listed in
|
|
176
|
+
`disabledForModels`. Direct calls there return a disabled notice and do
|
|
177
|
+
not run TypeSafe screening or advisor generation. Eligibility is
|
|
178
|
+
reevaluated per request, so switching models mid-session takes effect
|
|
179
|
+
immediately; a child session uses its own model, not the parent's.
|
|
125
180
|
- When the `agentEffort` plugin option is enabled, an optional `effort`
|
|
126
181
|
argument selects the reasoning effort for that consultation.
|
|
182
|
+
- When TypeSafe screening is active, a clearly unnecessary consultation
|
|
183
|
+
returns a skip notice instead of advice, and an omitted effort may be
|
|
184
|
+
chosen automatically. When model-capability evidence is available, it is
|
|
185
|
+
weighed as context, not as a rule: a strong requester can still benefit
|
|
186
|
+
from independent review and a weak requester still needs no advice for
|
|
187
|
+
trivial work.
|
|
188
|
+
|
|
189
|
+
## TypeSafe screening (optional)
|
|
190
|
+
|
|
191
|
+
When `TYPESAFE_API_KEY` is available to the OpenCode server process, the
|
|
192
|
+
plugin can screen each consultation before paying for advisor generation.
|
|
193
|
+
One [TypeSafe](https://docs.typesafe.ai) System One request asks whether an
|
|
194
|
+
independent advisor would materially help at this point and — when the caller
|
|
195
|
+
did not pin an effort — which allowed effort fits. Only a clearly low need
|
|
196
|
+
probability skips generation; uncertain judgments preserve the consultation,
|
|
197
|
+
and any gate failure (timeout, transport, malformed answer) falls back to the
|
|
198
|
+
ordinary advisor call.
|
|
199
|
+
|
|
200
|
+
The need question asks what material value advice would add beyond the agent's
|
|
201
|
+
next direct action. Its explicit yes/no criteria distinguish unresolved design,
|
|
202
|
+
diagnosis, and correctness concerns from direct lookups, deterministic operations,
|
|
203
|
+
mechanical edits, and unchanged already-answered questions. Independent review
|
|
204
|
+
does not require the agent to be stuck. The criteria favor proceeding when the
|
|
205
|
+
user explicitly asks for the advisor or task context is missing; model identities
|
|
206
|
+
and benchmark advantages alone do not make a routine task worth consulting on.
|
|
207
|
+
|
|
208
|
+
Screening is enabled automatically when the key is present. Configure it with
|
|
209
|
+
the `typesafe` plugin option:
|
|
210
|
+
|
|
211
|
+
```jsonc
|
|
212
|
+
{
|
|
213
|
+
"plugins": [
|
|
214
|
+
{
|
|
215
|
+
"package": "@pfoundation/ocadvisor",
|
|
216
|
+
"options": {
|
|
217
|
+
"typesafe": {
|
|
218
|
+
"model": "jev-1.13.0",
|
|
219
|
+
"timeoutMs": 3000,
|
|
220
|
+
"skipBelow": 0.2,
|
|
221
|
+
"minEffortConfidence": 0.6,
|
|
222
|
+
"maxStateBytes": 16384,
|
|
223
|
+
"efforts": ["high", "xhigh", "max"]
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| Option | Default | Meaning |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| `typesafe` | `true` when the key is set | `false` disables the gate completely; an object enables it with overrides |
|
|
234
|
+
| `model` | SDK default (`TYPESAFE_DEFAULT_MODEL`, else `jev-latest`) | Gate model; pin a version for reproducible decisions |
|
|
235
|
+
| `timeoutMs` | `3000` | Total gate budget, no retries |
|
|
236
|
+
| `skipBelow` | `0.20` | Skip only below this need probability |
|
|
237
|
+
| `minEffortConfidence` | `0.60` | Below this, keep the configured effort |
|
|
238
|
+
| `maxStateBytes` | `16384` | Byte budget for the compact decision state |
|
|
239
|
+
| `efforts` | `["high", "xhigh", "max"]` | Allowed automatic effort levels, intersected with the model's live variants |
|
|
240
|
+
|
|
241
|
+
Behavior notes:
|
|
242
|
+
|
|
243
|
+
- The gate runs only when an agent invokes `advisor`; ordinary model turns
|
|
244
|
+
never trigger TypeSafe calls.
|
|
245
|
+
- A missing or blank key, or `typesafe: false`, means no client is created and
|
|
246
|
+
no request is sent.
|
|
247
|
+
- Effort precedence when generation proceeds: an explicit tool `effort`, then
|
|
248
|
+
a confident gate selection, then the configured `variant`.
|
|
249
|
+
- A skip returns a visible `advisor consultation skipped (typesafe)` notice
|
|
250
|
+
and does not create, switch, or generate on the advisor session. On a
|
|
251
|
+
proceed or fallback the tool output gains one short line, for example
|
|
252
|
+
`gate: need=0.31, effort=xhigh (gate), decision=proceed` or
|
|
253
|
+
`gate: decision=fallback, timeout`; bypassed screening adds nothing.
|
|
254
|
+
- Gate metrics (decision, need probability, selected effort, latency, tokens)
|
|
255
|
+
are recorded alongside the consultation in `ocAdvisor-metrics.jsonl`.
|
|
256
|
+
|
|
257
|
+
## Model-capability benchmarks (Artificial Analysis)
|
|
258
|
+
|
|
259
|
+
The gate judges how much an independent advisor could help *this* requesting
|
|
260
|
+
model. To do that it needs two things: the actual model that made the call —
|
|
261
|
+
including its reasoning effort — and measured capability data for both that
|
|
262
|
+
model and the advisor. The plugin reads a local snapshot of
|
|
263
|
+
[Artificial Analysis](https://artificialanalysis.ai/) results; ordinary
|
|
264
|
+
consultations never touch the network for benchmark data.
|
|
265
|
+
|
|
266
|
+
What the gate receives, in the same single TypeSafe request:
|
|
267
|
+
|
|
268
|
+
- `models.requester` — `provider`, `model`, effort variant, and where the
|
|
269
|
+
identity came from (`invocation_message`, `latest_message`,
|
|
270
|
+
`session_fallback`, or `unknown`). The requester is the model behind the
|
|
271
|
+
outgoing tool call, not the parent session or whatever the session row
|
|
272
|
+
points at after a later switch.
|
|
273
|
+
- `models.advisor` — `provider`, `model`, and the effort policy in force:
|
|
274
|
+
a pinned caller effort, gate-selectable candidates with their fallback, or
|
|
275
|
+
a fixed default.
|
|
276
|
+
- `benchmarks` — the snapshot source and age, one match per profile, and
|
|
277
|
+
comparisons for the shared metrics: Artificial Analysis Coding Index,
|
|
278
|
+
Intelligence Index, and up to two reasoning/math results (HLE, GPQA, Math
|
|
279
|
+
Index) when present. Code computes oriented differences
|
|
280
|
+
(`advisor_minus_requester`); the model never does arithmetic. Scores that
|
|
281
|
+
cannot be strictly compared carry an explicit `reason` (`effort_mismatch`,
|
|
282
|
+
`effort_unknown`, `missing_requester`, …) and no delta.
|
|
283
|
+
|
|
284
|
+
Policy rule baked into the gate instructions: scores estimate comparative
|
|
285
|
+
capability, not certainty about the task. A strong requester can still benefit
|
|
286
|
+
from independent review or a fresh perspective when stuck, and a weaker
|
|
287
|
+
requester still needs no advice for trivial work. Unknown, mismatched, or
|
|
288
|
+
stale data is uncertainty — never evidence against consultation. No
|
|
289
|
+
automatic skip is ever derived from scores alone.
|
|
290
|
+
|
|
291
|
+
### Refreshing data between releases
|
|
292
|
+
|
|
293
|
+
The data lives in a local file the plugin reads at consultation time:
|
|
294
|
+
|
|
295
|
+
```text
|
|
296
|
+
$XDG_DATA_HOME/opencode/ocadvisor/artificial-analysis.json # or ~/.local/share/...
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The `ocadvisor` CLI refreshes it independently of plugin releases:
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
# Installed (npm bin):
|
|
303
|
+
ARTIFICIAL_ANALYSIS_API_KEY=... ocadvisor benchmarks update
|
|
304
|
+
|
|
305
|
+
# One-shot without a global install:
|
|
306
|
+
ARTIFICIAL_ANALYSIS_API_KEY=... bunx --package @pfoundation/ocadvisor ocadvisor benchmarks update
|
|
307
|
+
|
|
308
|
+
# Custom locations (absolute paths):
|
|
309
|
+
ocadvisor benchmarks update --path /srv/ocadvisor/artificial-analysis.json
|
|
310
|
+
ocadvisor benchmarks status --path /srv/ocadvisor/artificial-analysis.json --model openai/gpt-6-astra#xhigh
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
- `benchmarks update` fetches the official models endpoint once, validates it,
|
|
314
|
+
and atomically replaces the snapshot. The old file is preserved byte-for-byte
|
|
315
|
+
if fetch, validation, or write fails; concurrent updaters coordinate through
|
|
316
|
+
a per-target lock. It prints the path, fetch time, model count, metric
|
|
317
|
+
coverage, and content hash.
|
|
318
|
+
- `benchmarks status` is fully offline. It reports the active source
|
|
319
|
+
(user file, bundled baseline, or unavailable), schema version, age, model
|
|
320
|
+
count, metric coverage, mapping counts, and — with `--model` — the exact
|
|
321
|
+
match or the reason a model is unresolved. Status describes what is on
|
|
322
|
+
disk; a running plugin keeps serving its last good in-memory copy until a
|
|
323
|
+
valid replacement is observed.
|
|
324
|
+
- Exit codes: `0` success; `1` update failed, no usable snapshot, or model
|
|
325
|
+
unresolved; `2` usage error.
|
|
326
|
+
- The plugin picks up a refreshed snapshot on the next consultation without a
|
|
327
|
+
restart. A consultation already in flight keeps the snapshot it started
|
|
328
|
+
with, so both models are always resolved against one consistent dataset.
|
|
329
|
+
|
|
330
|
+
The API key is read from the environment, trimmed, sent only as the
|
|
331
|
+
`x-api-key` header, and never stored or logged. The free Artificial Analysis
|
|
332
|
+
API is rate-limited, so refresh manually (for example between releases)
|
|
333
|
+
rather than in a loop. Benchmark data is provided by Artificial Analysis
|
|
334
|
+
(https://artificialanalysis.ai/).
|
|
335
|
+
|
|
336
|
+
A validated snapshot from the package release date ships inside the package
|
|
337
|
+
as the bundled baseline, so a fresh install has real coverage before the
|
|
338
|
+
first refresh. Regenerate the shipped data with
|
|
339
|
+
`ARTIFICIAL_ANALYSIS_API_KEY=... bun scripts/buildBenchmarkData.ts
|
|
340
|
+
--refresh-snapshot` (mappings only without the flag; the docs recommend
|
|
341
|
+
refreshing the user snapshot instead of rerunning this).
|
|
342
|
+
|
|
343
|
+
### Model mappings
|
|
344
|
+
|
|
345
|
+
Published model IDs do not always line up with serving providers (gateways
|
|
346
|
+
add prefixes) or with the effort used in an evaluation. The plugin resolves
|
|
347
|
+
matches only through explicit bindings to stable Artificial Analysis IDs —
|
|
348
|
+
never fuzzy names, prefix stripping, or sibling substitution:
|
|
349
|
+
|
|
350
|
+
- `src/data/artificialAnalysis.mappings.json` ships baseline bindings
|
|
351
|
+
(curated in `scripts/buildBenchmarkData.ts`, every entry pointing at a
|
|
352
|
+
stable Artificial Analysis ID with its published evaluated effort).
|
|
353
|
+
- `model-mappings.json` beside the snapshot holds user overrides; these
|
|
354
|
+
survive refreshes and plugin upgrades.
|
|
355
|
+
- Binding keys are exact `(providerID, modelID, variant)` tuples. A null
|
|
356
|
+
variant covers unset variants only — it is never a wildcard. Variants are
|
|
357
|
+
independent: a `high` binding does not match an `xhigh` call.
|
|
358
|
+
- Local bindings override bundled ones per tuple and can cover a newly
|
|
359
|
+
released model with no plugin release:
|
|
360
|
+
|
|
361
|
+
```jsonc
|
|
362
|
+
{
|
|
363
|
+
"schemaVersion": 1,
|
|
364
|
+
"bindings": [
|
|
365
|
+
{
|
|
366
|
+
"providerID": "openai",
|
|
367
|
+
"modelID": "gpt-6-astra",
|
|
368
|
+
"variant": "xhigh",
|
|
369
|
+
"aaModelID": "the-stable-artificial-analysis-uuid",
|
|
370
|
+
"evaluatedEffort": "max",
|
|
371
|
+
"evidenceURL": "https://artificialanalysis.ai/models/gpt-6-astra"
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Set `evaluatedEffort` to the effort the evaluation actually ran at when the
|
|
378
|
+
source documents it, or `null` when it is unreported. Bindings with a null
|
|
379
|
+
evaluated effort still show scores but are labeled `effort_unknown`, and
|
|
380
|
+
their deltas are withheld from strict comparison. Use `benchmarks status
|
|
381
|
+
--model` to check exactly how a model resolves.
|
|
382
|
+
|
|
383
|
+
Unknown or unmapped models are not an error: consultations proceed with no
|
|
384
|
+
benchmark evidence for them, and the gate treats the gap as uncertainty.
|
|
385
|
+
|
|
386
|
+
### Matching across provider routes
|
|
387
|
+
|
|
388
|
+
Bindings are provider-exact by default, which is the safest behavior: the
|
|
389
|
+
same model can differ by serving route. When the same model is served
|
|
390
|
+
through several routes (an aggregator, a gateway, or the host's own
|
|
391
|
+
provider namespace), enable the cross-provider fallback instead of
|
|
392
|
+
maintaining a binding per route:
|
|
393
|
+
|
|
394
|
+
```jsonc
|
|
395
|
+
{
|
|
396
|
+
"plugins": [
|
|
397
|
+
{
|
|
398
|
+
"package": "@pfoundation/ocadvisor",
|
|
399
|
+
"options": {
|
|
400
|
+
"benchmarks": { "matchAnyProvider": true }
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
With it on, a request for `opencode/deepseek-v4.1-flash#max` resolves through
|
|
408
|
+
the official `deepseek/deepseek-v4.1-flash#max` binding: exact provider
|
|
409
|
+
bindings still win first, variants stay independent (`high` never borrows
|
|
410
|
+
`max`), and the stable AA ID plus its published evaluated effort are
|
|
411
|
+
preserved. If several providers define the same model and variant, local
|
|
412
|
+
bindings win over bundled ones. Check the outcome before relying on it:
|
|
413
|
+
|
|
414
|
+
```sh
|
|
415
|
+
ocadvisor benchmarks status --match-any-provider true --model opencode/deepseek-v4.1-flash#max
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
The equivalent environment variable is
|
|
419
|
+
`OCADVISOR_BENCHMARKS_MATCH_ANY_PROVIDER=true`.
|
|
127
420
|
|
|
128
421
|
## How it works
|
|
129
422
|
|
|
130
423
|
- `src/index.ts` → `dist/index.js` is the published entrypoint (default
|
|
131
|
-
export)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
424
|
+
export); `index.js` at the repository root forwards to it so OpenCode's
|
|
425
|
+
local-directory loader can resolve a configured checkout.
|
|
426
|
+
`src/ocAdvisor.ts` holds the plugin implementation: it registers the
|
|
427
|
+
`advisor` tool, injects a short selective-use instruction into eligible
|
|
428
|
+
sessions via the `context` hook, and builds the transcript from the OpenCode
|
|
429
|
+
SQLite database. `src/typesafeGate.ts` and `src/typesafeState.ts` hold the
|
|
430
|
+
optional TypeSafe preflight. `src/benchmarkTypes.ts`, `src/artificialAnalysis.ts`,
|
|
431
|
+
`src/benchmarkUpdate.ts`, `src/benchmarkStore.ts`, `src/benchmarkMatch.ts`,
|
|
432
|
+
and `src/benchmarkEvidence.ts` hold the local Artificial Analysis snapshot,
|
|
433
|
+
its refresh transaction, the hot-reloading reader, and exact model mapping;
|
|
434
|
+
`src/cli.ts` exposes the maintenance commands.
|
|
135
435
|
- The tool is registered as a direct tool (`options.codemode: false`).
|
|
136
436
|
OpenCode 2 otherwise exposes plugin tools only through the `execute` Code
|
|
137
437
|
Mode tool, whose tool log records each nested call's input but hides the
|
|
@@ -139,16 +439,23 @@ Rules enforced by the tool description and an injected session instruction:
|
|
|
139
439
|
TUI. As a direct tool, the TUI's tool log shows the call's `mode`,
|
|
140
440
|
`trigger`, and `question` fields followed by `output:` with the answer.
|
|
141
441
|
Direct calls also avoid Code Mode's output-size truncation. The `context`
|
|
142
|
-
hook can only hide the tool (Fable sessions
|
|
143
|
-
|
|
144
|
-
the request.
|
|
442
|
+
hook can only hide the tool (Fable sessions and `disabledForModels`
|
|
443
|
+
callers), never add one, and the selective-use instruction is injected
|
|
444
|
+
only when the tool is available to the request.
|
|
145
445
|
- Before each consultation it checks OpenCode for support of the configured
|
|
146
|
-
advisor model: the provider is enabled (`
|
|
147
|
-
|
|
148
|
-
|
|
446
|
+
advisor model: the provider is enabled (`provider.get`), the model is
|
|
447
|
+
available (`model.list`, configured variant when listed), and a connection
|
|
448
|
+
exists (`integration.connection.active`). The older `catalog.*` discovery
|
|
449
|
+
namespace remains as a compatibility fallback.
|
|
450
|
+
- Benchmark snapshots and mapping overrides are read through one cached
|
|
451
|
+
store per process. Before each gate-enabled consultation the store
|
|
452
|
+
re-stats both files and reloads only when their identity changed, so CLI
|
|
453
|
+
refreshes land on the next call without a restart; invalid replacements
|
|
454
|
+
keep the last good data and unmatched scenarios report explicit coverage
|
|
455
|
+
instead of guessing.
|
|
149
456
|
- Consultations run as transient generations on a dedicated, reusable
|
|
150
457
|
`advisor` session pinned to the configured model (default
|
|
151
|
-
`anthropic/claude-fable-5-1#
|
|
458
|
+
`anthropic/claude-fable-5-1#xhigh`) via `session.create` +
|
|
152
459
|
`session.switchModel` once, then `session.generate` per call. Transient
|
|
153
460
|
generations do not mutate session history, so the advisor session stays
|
|
154
461
|
empty while its stats attribute advisor spend. Title discovery also
|
|
@@ -170,25 +477,55 @@ Rules enforced by the tool description and an injected session instruction:
|
|
|
170
477
|
what is new since then.
|
|
171
478
|
- Real failures (provider/model/connection issues, missing
|
|
172
479
|
transcript/session) throw so OpenCode records them as errors instead of
|
|
173
|
-
silent `completed` results. Fable skips still return
|
|
480
|
+
silent `completed` results. Fable and configured-model skips still return
|
|
481
|
+
a disabled notice without TypeSafe or advisor requests.
|
|
174
482
|
|
|
175
483
|
## Metrics
|
|
176
484
|
|
|
177
485
|
Every invocation appends one JSON line to
|
|
178
486
|
`~/.local/share/opencode/ocAdvisor-metrics.jsonl` with timestamp, session,
|
|
179
487
|
caller model/agent, mode, trigger, effective effort, outcome (`advisor_response`,
|
|
180
|
-
`skipped_fable`, `
|
|
488
|
+
`skipped_fable`, `skipped_model`, `skipped_typesafe`, `error`, `no_transcript`,
|
|
489
|
+
`no_session`), error type
|
|
181
490
|
(`provider_unavailable`, `model_unavailable`, `invalid_effort`, `auth`, …), latency,
|
|
182
491
|
transcript size, prior-consultation count, and transport (`via`).
|
|
183
492
|
Token usage is `null`: OpenCode generation returns text only.
|
|
184
493
|
Logging is best-effort and never breaks a call.
|
|
494
|
+
When the tool is hidden for the request, there is no invocation and no
|
|
495
|
+
metrics row. A direct call that still reaches the executor for an excluded
|
|
496
|
+
caller records one `skipped_model` line and no TypeSafe gate field.
|
|
497
|
+
|
|
498
|
+
Gate-enabled calls also carry a compact `benchmarks` object: snapshot source
|
|
499
|
+
(`user`/`seed`/`unavailable`), content hash, fetch time, hash-verified flag,
|
|
500
|
+
requester identity plus match status, advisor effort policy, the default
|
|
501
|
+
match, and — once generation runs — the final effort and its match. Rows
|
|
502
|
+
from before this feature simply lack the field; the report treats it as
|
|
503
|
+
optional and summarizes benchmark coverage when present.
|
|
185
504
|
|
|
186
505
|
## Activation
|
|
187
506
|
|
|
188
507
|
The server loads plugin files once per process, so after installing or
|
|
189
|
-
updating the plugin
|
|
190
|
-
|
|
191
|
-
|
|
508
|
+
updating the plugin, or after changing plugin options such as
|
|
509
|
+
`disabledForModels`, restart the background service
|
|
510
|
+
(`opencode service restart`) or the old code and config keep running.
|
|
511
|
+
Location eviction does not reload plugin files.
|
|
512
|
+
|
|
513
|
+
## Local development install
|
|
514
|
+
|
|
515
|
+
A configured local checkout (the global `plugins` entry pointing at the
|
|
516
|
+
repository directory) is resolved through the root `index.js` shim, which
|
|
517
|
+
forwards the compiled `dist/index.js`. After changing `src/`, run
|
|
518
|
+
`bun run build` and restart the service:
|
|
519
|
+
|
|
520
|
+
```sh
|
|
521
|
+
opencode plugin list
|
|
522
|
+
opencode api get '/api/plugin?location%5Bdirectory%5D=%2Fhome%2Fubuntu%2Fdev%2FocAdvisor'
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
The first command lists CLI-managed package plugins; the second reports the
|
|
526
|
+
server plugins for an explicit location, where `oc-advisor` should appear
|
|
527
|
+
active. The config-directory watcher only tracks the `plugin/` and `plugins/`
|
|
528
|
+
directories, so changes to this checkout need an explicit service restart.
|
|
192
529
|
|
|
193
530
|
## Development
|
|
194
531
|
|
|
@@ -196,31 +533,92 @@ eviction does not reload plugin files.
|
|
|
196
533
|
bun install # install dependencies (frozen lockfile in CI)
|
|
197
534
|
bun test # unit tests
|
|
198
535
|
bun run typecheck # typecheck (tsc --noEmit)
|
|
199
|
-
bun run build # compile dist/ (runs
|
|
536
|
+
bun run build # compile dist/ + copy benchmark data (runs on npm pack/publish)
|
|
200
537
|
bun run report # advisor usage over the last 30 days
|
|
201
538
|
bun src/usageReport.ts --days 7
|
|
539
|
+
bun src/cli.ts benchmarks status # the shipped CLI, from source
|
|
202
540
|
```
|
|
203
541
|
|
|
204
542
|
## Evaluation
|
|
205
543
|
|
|
206
544
|
The report combines the metrics log with the session database and shows
|
|
207
|
-
|
|
208
|
-
(sessions with ≥10 non-Fable tool calls vs. sessions that
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
545
|
+
generated advice, skips, gate decisions, and caller results plus eligibility
|
|
546
|
+
coverage (sessions with ≥10 non-Fable tool calls vs. sessions that received
|
|
547
|
+
generated advice). That coverage figure is a non-Fable activity proxy, not a
|
|
548
|
+
historical reconstruction of per-location `disabledForModels` settings.
|
|
549
|
+
Database and metrics views are independent and must not be
|
|
550
|
+
summed: a caller entry can fail while the plugin still recorded a generated
|
|
551
|
+
response, and a completed entry without visible output is reported as
|
|
552
|
+
unknown rather than success. Re-run it after a few weeks of the new
|
|
553
|
+
checkpoints to judge coverage and whether advice is changing outcomes. The
|
|
554
|
+
usage report is a maintainer tool run from a source checkout; it is not
|
|
555
|
+
shipped in the npm package.
|
|
556
|
+
|
|
557
|
+
## Gate evaluation
|
|
558
|
+
|
|
559
|
+
`bun src/typesafeGate.eval.ts [--model jev-1.13.0]` runs labeled cases from
|
|
560
|
+
`src/fixtures/typesafeGate.cases.json` against the gate and prints per-case
|
|
561
|
+
decisions plus false-skip, unnecessary-proceed, fallback, latency, token, and
|
|
562
|
+
cost figures. Cases may carry synthetic benchmark profiles (fake model IDs
|
|
563
|
+
and scores) that exercise the full evidence wire format without touching real
|
|
564
|
+
snapshot files; the fixture file says so explicitly. It requires
|
|
565
|
+
`TYPESAFE_API_KEY` in the process environment and calls TypeSafe only — never
|
|
566
|
+
the advisor model. Neither the script nor its fixtures are published.
|
|
567
|
+
|
|
568
|
+
Initial run (2026-09-19, `jev-1.13.0`, six cases, 4,240 input tokens,
|
|
569
|
+
≈$0.00018): no false skips on must-consult cases, no fallbacks, latency
|
|
570
|
+
p50/p95 266/626 ms.
|
|
571
|
+
|
|
572
|
+
Benchmark-aware run (same model and day, ten cases including four synthetic
|
|
573
|
+
benchmark profiles — stronger requester, weaker requester, unknown scores,
|
|
574
|
+
stale snapshot — 11,164 input tokens, ≈$0.00047): **0 false skips** on
|
|
575
|
+
must-consult cases, including the stronger-requester review and the
|
|
576
|
+
unknown-scores debug; the stale-snapshot review proceeded as well. Two
|
|
577
|
+
unnecessary proceeds on routine questions landed at need 0.23–0.33, above the
|
|
578
|
+
conservative default `skipBelow` (0.20): uncertainty preserves consultation
|
|
579
|
+
by design. No fallbacks; latency p50/p95 280/607 ms. Adjust the threshold
|
|
580
|
+
only from observed cases and re-run the evaluation after changing it.
|
|
581
|
+
|
|
582
|
+
The [2026-09-20 gate-policy report](docs/reports/2026-09-20-gate-policy.md)
|
|
583
|
+
compares threshold changes, identity-neutral wording, explicit material-value
|
|
584
|
+
criteria, task-only screening, and a separate routine classifier over 24 synthetic
|
|
585
|
+
scenarios. The selected material-value question keeps `skipBelow: 0.20` and model
|
|
586
|
+
evidence. In 72 final confirmation calls it skipped all 30 routine consultations
|
|
587
|
+
and preserved all 42 useful ones. The report records the held-out check, full
|
|
588
|
+
criteria, tradeoffs, and limitations; these are scenario results, not a measured
|
|
589
|
+
production error rate. The opt-in evaluation now includes mechanical edits,
|
|
590
|
+
unchanged repeat questions, explicit advisor requests, and consequential small
|
|
591
|
+
changes. Recalibrate after changing either question wording or threshold.
|
|
592
|
+
|
|
593
|
+
### End-to-end benchmark data flow
|
|
594
|
+
|
|
595
|
+
1. Fresh install: the plugin reads the bundled snapshot shipped with the
|
|
596
|
+
package, so real coverage exists before any refresh. User files, when
|
|
597
|
+
present, take precedence over it.
|
|
598
|
+
2. An operator with an Artificial Analysis key runs
|
|
599
|
+
`ocadvisor benchmarks update`. The validated snapshot lands in the data
|
|
600
|
+
directory and the next consultation logs its content hash.
|
|
601
|
+
3. `ocadvisor benchmarks status --model ...` confirms which models now
|
|
602
|
+
resolve, and local `model-mappings.json` entries cover anything missing.
|
|
603
|
+
4. A later plugin upgrade leaves the user snapshot and mappings untouched —
|
|
604
|
+
they live outside the package — so coverage persists across releases.
|
|
212
605
|
|
|
213
606
|
## Versioning
|
|
214
607
|
|
|
215
|
-
Releases use calendar versioning (`YY.M.patch`, e.g. `26.9.0`). The
|
|
216
|
-
(`v26.9.0`)
|
|
608
|
+
Releases use calendar versioning (`YY.M.patch`, e.g. `26.9.0`). The GitHub
|
|
609
|
+
Release tag (`v26.9.0`) is the source of truth: the publish workflow overwrites
|
|
610
|
+
`package.json` `version` from the tag. Do not bump `version` in `package.json`
|
|
611
|
+
for a release; leave it at the last published version.
|
|
217
612
|
|
|
218
613
|
## Releasing (maintainers)
|
|
219
614
|
|
|
220
|
-
1.
|
|
221
|
-
`
|
|
222
|
-
2.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
615
|
+
1. Land the change on `master` and wait for CI to pass. Leave `package.json`
|
|
616
|
+
`version` as the last published release.
|
|
617
|
+
2. Cut a GitHub Release on tag `vYY.M.patch`. Pre-releases (the release's
|
|
618
|
+
pre-release flag, or a `-suffix` version) publish under dist-tag `next`;
|
|
619
|
+
stable releases publish `--tag latest`. Pushing a tag alone publishes
|
|
620
|
+
nothing.
|
|
621
|
+
3. The `publish` workflow sets the package version from the tag, re-runs
|
|
622
|
+
every gate, and publishes via OIDC trusted publishing (no npm token).
|
|
623
|
+
`v*` tag creation is restricted to maintainers by a ruleset.
|
|
226
624
|
4. Verify: `npm view @pfoundation/ocadvisor@YY.M.patch`.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type BenchmarkSnapshot, type BenchmarkValidationError } from "./benchmarkTypes.js";
|
|
2
|
+
export declare const AA_LLM_MODELS_ENDPOINT = "https://artificialanalysis.ai/api/v2/data/llms/models";
|
|
3
|
+
export declare function normalizeAAResponse(body: unknown, fetchedAt: string): BenchmarkSnapshot | BenchmarkValidationError;
|
|
4
|
+
//# sourceMappingURL=artificialAnalysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artificialAnalysis.d.ts","sourceRoot":"","sources":["../src/artificialAnalysis.ts"],"names":[],"mappings":"AAIA,OAAO,EAQL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAE7B,eAAO,MAAM,sBAAsB,0DACsB,CAAC;AAM1D,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,GAChB,iBAAiB,GAAG,wBAAwB,CA0E9C"}
|