@pfoundation/ocadvisor 26.9.0 → 26.9.2
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 +420 -43
- 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 +19 -0
- package/dist/benchmarkConfig.d.ts.map +1 -0
- package/dist/benchmarkConfig.js +58 -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 +57 -0
- package/dist/benchmarkMatch.d.ts.map +1 -0
- package/dist/benchmarkMatch.js +236 -0
- package/dist/benchmarkMatch.js.map +1 -0
- package/dist/benchmarkStore.d.ts +44 -0
- package/dist/benchmarkStore.d.ts.map +1 -0
- package/dist/benchmarkStore.js +308 -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 +377 -0
- package/dist/cli.js.map +1 -0
- package/dist/data/artificialAnalysis.mappings.json +197 -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 +152 -7
- package/dist/ocAdvisor.d.ts.map +1 -1
- package/dist/ocAdvisor.js +681 -54
- package/dist/ocAdvisor.js.map +1 -1
- package/dist/typesafeGate.d.ts +89 -0
- package/dist/typesafeGate.d.ts.map +1 -0
- package/dist/typesafeGate.js +463 -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 +9 -3
package/dist/ocAdvisor.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { appendFile } from "fs/promises";
|
|
3
3
|
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
4
|
+
import { isAbsolute, join } from "path";
|
|
5
|
+
import { resolveBenchmarkPaths } from "./benchmarkConfig.js";
|
|
6
|
+
import { buildBenchmarkEvidence, } from "./benchmarkEvidence.js";
|
|
7
|
+
import { createBenchmarkStore, } from "./benchmarkStore.js";
|
|
8
|
+
import { resolveAdvisorProfile, resolveRequesterProfile, } from "./modelProfiles.js";
|
|
9
|
+
import { runTypeSafeGate, } from "./typesafeGate.js";
|
|
10
|
+
import { TYPESAFE_DEFAULTS, buildDecisionState, normalizeTypeSafeOptions, resolveTypeSafeConfig, } from "./typesafeState.js";
|
|
5
11
|
const DB_PATH = join(homedir(), ".local/share/opencode/opencode.db");
|
|
6
12
|
const METRICS_PATH = join(homedir(), ".local/share/opencode/ocAdvisor-metrics.jsonl");
|
|
7
13
|
const ADVISOR_PROVIDER = "anthropic";
|
|
8
14
|
const ADVISOR_MODEL = "claude-fable-5-1";
|
|
9
|
-
const ADVISOR_VARIANT = "
|
|
15
|
+
const ADVISOR_VARIANT = "xhigh";
|
|
10
16
|
const ADVISOR_SESSION_TITLE = "advisor";
|
|
11
17
|
// Sessions created before the ocAdvisor → advisor rename keep working: title
|
|
12
18
|
// discovery accepts both, and the storage key below is unchanged.
|
|
13
19
|
const LEGACY_ADVISOR_SESSION_TITLE = "ocAdvisor";
|
|
14
20
|
const ADVISOR_STORAGE_KEY = "advisorSessionID";
|
|
15
21
|
const ADVISOR_TIMEOUT_MS = 300_000;
|
|
22
|
+
// Effort levels the agent may request per call when `agentEffort: true`
|
|
23
|
+
// (subset of the model's catalog variants; validated per consultation).
|
|
24
|
+
const AGENT_EFFORT_DEFAULTS = ["high", "xhigh", "max"];
|
|
16
25
|
const FABLE_DISABLED = "advisor is disabled for anthropic/claude-fable-* sessions — the current model is already Fable.";
|
|
17
26
|
const DEFAULT_ADVISOR_CONFIG = {
|
|
18
27
|
provider: ADVISOR_PROVIDER,
|
|
@@ -20,6 +29,12 @@ const DEFAULT_ADVISOR_CONFIG = {
|
|
|
20
29
|
variant: ADVISOR_VARIANT,
|
|
21
30
|
timeoutMs: ADVISOR_TIMEOUT_MS,
|
|
22
31
|
maxTranscriptChars: 0,
|
|
32
|
+
agentEffort: null,
|
|
33
|
+
disabledForModels: [],
|
|
34
|
+
benchmarks: {},
|
|
35
|
+
typesafeSource: { disabled: false, overrides: {} },
|
|
36
|
+
typesafe: { enabled: false, settings: null, keyPresent: false },
|
|
37
|
+
typesafeSettings: null,
|
|
23
38
|
};
|
|
24
39
|
function normalizeVariant(value) {
|
|
25
40
|
if (value === null || value === undefined)
|
|
@@ -29,6 +44,98 @@ function normalizeVariant(value) {
|
|
|
29
44
|
return undefined;
|
|
30
45
|
return text;
|
|
31
46
|
}
|
|
47
|
+
function dedupeEfforts(items) {
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const text = item.trim();
|
|
51
|
+
if (text)
|
|
52
|
+
seen.add(text);
|
|
53
|
+
}
|
|
54
|
+
return seen.size > 0 ? [...seen] : null;
|
|
55
|
+
}
|
|
56
|
+
// `true` enables the default levels, `false`/`null` disables the feature,
|
|
57
|
+
// and an array or comma-separated string sets an explicit allow-list.
|
|
58
|
+
// Returns undefined for unrecognized types so the caller keeps the base.
|
|
59
|
+
function normalizeAgentEffort(value) {
|
|
60
|
+
if (value === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (value === null)
|
|
63
|
+
return null;
|
|
64
|
+
if (value === true)
|
|
65
|
+
return [...AGENT_EFFORT_DEFAULTS];
|
|
66
|
+
if (value === false)
|
|
67
|
+
return null;
|
|
68
|
+
if (typeof value === "string") {
|
|
69
|
+
const text = value.trim();
|
|
70
|
+
if (!text)
|
|
71
|
+
return null;
|
|
72
|
+
const lowered = text.toLowerCase();
|
|
73
|
+
if (lowered === "true")
|
|
74
|
+
return [...AGENT_EFFORT_DEFAULTS];
|
|
75
|
+
if (lowered === "false" || lowered === "none")
|
|
76
|
+
return null;
|
|
77
|
+
return dedupeEfforts(text.split(","));
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
return dedupeEfforts(value
|
|
81
|
+
.filter((entry) => typeof entry === "string")
|
|
82
|
+
.flatMap((entry) => entry.split(",")));
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
// Exact `provider/model` IDs. A string is split on commas; blanks are
|
|
87
|
+
// dropped. `undefined` means "not set" so a later source can keep the
|
|
88
|
+
// previous list. An empty array or whitespace-only string clears it.
|
|
89
|
+
function normalizeDisabledForModels(value) {
|
|
90
|
+
if (value === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
const entries = typeof value === "string" ? value.split(",") : value;
|
|
93
|
+
if (!Array.isArray(entries) ||
|
|
94
|
+
entries.some((entry) => typeof entry !== "string")) {
|
|
95
|
+
throw new Error("disabledForModels must be a string array or comma-separated string.");
|
|
96
|
+
}
|
|
97
|
+
const refs = entries.map((entry) => entry.trim()).filter(Boolean);
|
|
98
|
+
for (const ref of refs) {
|
|
99
|
+
const slash = ref.indexOf("/");
|
|
100
|
+
if (slash <= 0 ||
|
|
101
|
+
slash === ref.length - 1 ||
|
|
102
|
+
ref.endsWith("/") ||
|
|
103
|
+
ref.includes("//") ||
|
|
104
|
+
/[\s#*?]/u.test(ref)) {
|
|
105
|
+
throw new Error("disabledForModels requires exact provider/model IDs without variants or wildcards.");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...new Set(refs)];
|
|
109
|
+
}
|
|
110
|
+
// `{ path, mappingsPath }` with absolute locations. `undefined`/`null`
|
|
111
|
+
// means "not set"; blank entries are dropped; unknown keys are ignored.
|
|
112
|
+
// Invalid explicit values throw so misconfiguration fails fast at setup
|
|
113
|
+
// instead of silently degrading to default benchmark data.
|
|
114
|
+
function normalizeBenchmarks(value) {
|
|
115
|
+
if (value === undefined || value === null)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
118
|
+
throw new Error("benchmarks must be an object with path options.");
|
|
119
|
+
}
|
|
120
|
+
const raw = value;
|
|
121
|
+
const out = {};
|
|
122
|
+
for (const key of ["path", "mappingsPath"]) {
|
|
123
|
+
const entry = raw[key];
|
|
124
|
+
if (entry === undefined || entry === null)
|
|
125
|
+
continue;
|
|
126
|
+
if (typeof entry !== "string") {
|
|
127
|
+
throw new Error(`benchmarks.${key} must be an absolute path.`);
|
|
128
|
+
}
|
|
129
|
+
const trimmed = entry.trim();
|
|
130
|
+
if (!trimmed)
|
|
131
|
+
continue;
|
|
132
|
+
if (!isAbsolute(trimmed)) {
|
|
133
|
+
throw new Error(`benchmarks.${key} must be an absolute path.`);
|
|
134
|
+
}
|
|
135
|
+
out[key] = trimmed;
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
32
139
|
function toBoundedInt(value, min) {
|
|
33
140
|
const raw = typeof value === "number"
|
|
34
141
|
? value
|
|
@@ -65,7 +172,7 @@ function parseModelRef(ref) {
|
|
|
65
172
|
}
|
|
66
173
|
return out;
|
|
67
174
|
}
|
|
68
|
-
function applyAdvisorConfigSource(base, src) {
|
|
175
|
+
function applyAdvisorConfigSource(base, src, env = process.env) {
|
|
69
176
|
if (!src || typeof src !== "object")
|
|
70
177
|
return base;
|
|
71
178
|
const next = { ...base };
|
|
@@ -86,12 +193,37 @@ function applyAdvisorConfigSource(base, src) {
|
|
|
86
193
|
if (src.variant !== undefined) {
|
|
87
194
|
next.variant = normalizeVariant(src.variant);
|
|
88
195
|
}
|
|
196
|
+
const agentEffort = src.agentEffort ?? src.agent_effort;
|
|
197
|
+
if (agentEffort !== undefined) {
|
|
198
|
+
const normalized = normalizeAgentEffort(agentEffort);
|
|
199
|
+
if (normalized !== undefined)
|
|
200
|
+
next.agentEffort = normalized;
|
|
201
|
+
}
|
|
202
|
+
const disabledForModels = normalizeDisabledForModels(src.disabledForModels);
|
|
203
|
+
if (disabledForModels !== undefined) {
|
|
204
|
+
next.disabledForModels = disabledForModels;
|
|
205
|
+
}
|
|
206
|
+
// Benchmark paths merge per field so a plugin option can override one
|
|
207
|
+
// location while the other still falls back to the environment default.
|
|
208
|
+
const benchmarks = normalizeBenchmarks(src.benchmarks);
|
|
209
|
+
if (benchmarks !== undefined) {
|
|
210
|
+
next.benchmarks = { ...next.benchmarks, ...benchmarks };
|
|
211
|
+
}
|
|
89
212
|
const timeout = toBoundedInt(src.timeoutMs ?? src.timeout_ms, 1);
|
|
90
213
|
if (timeout !== undefined)
|
|
91
214
|
next.timeoutMs = timeout;
|
|
92
215
|
const cap = toBoundedInt(src.maxTranscriptChars ?? src.max_transcript_chars, 0);
|
|
93
216
|
if (cap !== undefined)
|
|
94
217
|
next.maxTranscriptChars = cap;
|
|
218
|
+
if (src.typesafe !== undefined) {
|
|
219
|
+
const normalized = normalizeTypeSafeOptions(src.typesafe);
|
|
220
|
+
if ("error" in normalized) {
|
|
221
|
+
throw new Error(normalized.error);
|
|
222
|
+
}
|
|
223
|
+
next.typesafeSource = normalized;
|
|
224
|
+
}
|
|
225
|
+
next.typesafe = resolveTypeSafeConfig(next.typesafeSource, env);
|
|
226
|
+
next.typesafeSettings = next.typesafe.settings;
|
|
95
227
|
return next;
|
|
96
228
|
}
|
|
97
229
|
function envAdvisorConfigSource(env = process.env) {
|
|
@@ -101,13 +233,22 @@ function envAdvisorConfigSource(env = process.env) {
|
|
|
101
233
|
variant: env.OCADVISOR_VARIANT,
|
|
102
234
|
timeoutMs: env.OCADVISOR_TIMEOUT_MS,
|
|
103
235
|
maxTranscriptChars: env.OCADVISOR_MAX_TRANSCRIPT_CHARS,
|
|
236
|
+
agentEffort: env.OCADVISOR_AGENT_EFFORT,
|
|
237
|
+
disabledForModels: env.OCADVISOR_DISABLED_FOR_MODELS,
|
|
238
|
+
benchmarks: env.OCADVISOR_BENCHMARKS_PATH === undefined &&
|
|
239
|
+
env.OCADVISOR_BENCHMARK_MAPPINGS_PATH === undefined
|
|
240
|
+
? undefined
|
|
241
|
+
: {
|
|
242
|
+
path: env.OCADVISOR_BENCHMARKS_PATH,
|
|
243
|
+
mappingsPath: env.OCADVISOR_BENCHMARK_MAPPINGS_PATH,
|
|
244
|
+
},
|
|
104
245
|
};
|
|
105
246
|
}
|
|
106
247
|
// Precedence (low to high): built-in defaults, environment variables,
|
|
107
248
|
// plugin options from opencode.json.
|
|
108
249
|
function resolveAdvisorConfig(options, env = process.env) {
|
|
109
|
-
let config = applyAdvisorConfigSource(DEFAULT_ADVISOR_CONFIG, envAdvisorConfigSource(env));
|
|
110
|
-
config = applyAdvisorConfigSource(config, options);
|
|
250
|
+
let config = applyAdvisorConfigSource(DEFAULT_ADVISOR_CONFIG, envAdvisorConfigSource(env), env);
|
|
251
|
+
config = applyAdvisorConfigSource(config, options, env);
|
|
111
252
|
return config;
|
|
112
253
|
}
|
|
113
254
|
const SYSTEM_BASE = `You are a senior advisor reviewing a coding agent's work. You have the full session transcript.
|
|
@@ -124,19 +265,30 @@ You are a debugger. Analyze error patterns, stack traces, and failed attempts in
|
|
|
124
265
|
};
|
|
125
266
|
const TOOL_DESCRIPTION = `Consult a senior advisor model with your full session transcript — including parent sessions for subagents — for high-quality analysis.
|
|
126
267
|
|
|
127
|
-
Use advisor
|
|
268
|
+
Use advisor when an independent perspective could improve the approach, help resolve a problem, or strengthen an implementation review.
|
|
269
|
+
|
|
270
|
+
On substantial work, consider consulting before committing to an approach, when progress stalls, or before completing meaningful changes. Additional consultations are welcome as the work evolves — particularly when new evidence appears, the approach changes, or another concern needs review.
|
|
128
271
|
|
|
129
|
-
|
|
130
|
-
- mode "general": a second opinion that does not fit the above.
|
|
272
|
+
Ask a concrete, focused question. Avoid repeating settled questions without new context. Straightforward tasks usually need no consultation.
|
|
131
273
|
|
|
132
274
|
Rules:
|
|
133
275
|
- Always pass a concrete "question" naming the decision or artifact under review.
|
|
134
|
-
-
|
|
276
|
+
- Reconcile an advisor conflict with primary-source evidence via one "followup" call stating both sides.
|
|
135
277
|
- Give the advice serious weight. A passing self-test alone is not counter-evidence; primary-source evidence (the file says X) is. Clear factual corrections do not need another confirmation call.
|
|
278
|
+
- When TypeSafe screening is enabled, a clearly unnecessary consultation returns a skip notice instead of advice, and an omitted effort may be chosen for you.
|
|
136
279
|
|
|
137
280
|
Args: "mode" (general, review, plan, debug), "trigger" (before_approach, stuck, pre_complete, followup, other), "question" (concrete question focusing the advisor).
|
|
138
281
|
`;
|
|
139
|
-
|
|
282
|
+
// When `agentEffort` is enabled the tool advertises an optional `effort`
|
|
283
|
+
// argument; otherwise the description is exactly TOOL_DESCRIPTION.
|
|
284
|
+
function buildToolDescription(config = DEFAULT_ADVISOR_CONFIG) {
|
|
285
|
+
if (!config.agentEffort || config.agentEffort.length === 0) {
|
|
286
|
+
return TOOL_DESCRIPTION;
|
|
287
|
+
}
|
|
288
|
+
return (TOOL_DESCRIPTION +
|
|
289
|
+
`Optional "effort" (one of: ${config.agentEffort.join(", ")}): reasoning effort for this consultation; omit to use the configured variant.\n`);
|
|
290
|
+
}
|
|
291
|
+
const CHECKPOINT_INSTRUCTION = `[advisor] Consult advisor when an independent perspective would improve the approach, help resolve a problem, or strengthen review. Use useful checkpoints during substantial work; additional consultations are welcome when evidence, approach, or concerns change. Ask a concrete question and avoid repeating settled questions without new context.`;
|
|
140
292
|
const ADVISOR_TRIGGERS = [
|
|
141
293
|
"before_approach",
|
|
142
294
|
"stuck",
|
|
@@ -163,8 +315,28 @@ const ADVISOR_INPUT_SCHEMA = {
|
|
|
163
315
|
},
|
|
164
316
|
},
|
|
165
317
|
};
|
|
166
|
-
|
|
167
|
-
|
|
318
|
+
// When `agentEffort` is enabled the schema gains an optional `effort`
|
|
319
|
+
// argument restricted to the allowed levels.
|
|
320
|
+
function buildAdvisorInputSchema(config = DEFAULT_ADVISOR_CONFIG) {
|
|
321
|
+
if (!config.agentEffort || config.agentEffort.length === 0) {
|
|
322
|
+
return ADVISOR_INPUT_SCHEMA;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
...ADVISOR_INPUT_SCHEMA,
|
|
326
|
+
properties: {
|
|
327
|
+
...ADVISOR_INPUT_SCHEMA.properties,
|
|
328
|
+
effort: {
|
|
329
|
+
type: "string",
|
|
330
|
+
enum: [...config.agentEffort],
|
|
331
|
+
description: "Reasoning effort for this consultation (omit to use the configured variant)",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function openDb(runtime) {
|
|
337
|
+
return new Database(runtime?.__advisorTest?.dbPath ?? DB_PATH, {
|
|
338
|
+
readonly: true,
|
|
339
|
+
});
|
|
168
340
|
}
|
|
169
341
|
function tableExists(db, name) {
|
|
170
342
|
const row = db
|
|
@@ -190,6 +362,22 @@ function isFableModel(model) {
|
|
|
190
362
|
const id = String(model.id || model.modelID || "").toLowerCase();
|
|
191
363
|
return provider.includes("anthropic") && id.includes("fable");
|
|
192
364
|
}
|
|
365
|
+
function advisorDisabledReason(model, config) {
|
|
366
|
+
if (isFableModel(model)) {
|
|
367
|
+
return { outcome: "skipped_fable", message: FABLE_DISABLED };
|
|
368
|
+
}
|
|
369
|
+
const provider = model?.providerID || model?.provider;
|
|
370
|
+
const id = model?.id || model?.modelID;
|
|
371
|
+
if (!provider || !id)
|
|
372
|
+
return null;
|
|
373
|
+
const ref = `${provider}/${id}`;
|
|
374
|
+
if (!config.disabledForModels.includes(ref))
|
|
375
|
+
return null;
|
|
376
|
+
return {
|
|
377
|
+
outcome: "skipped_model",
|
|
378
|
+
message: `advisor is disabled (model opt-out): ${ref} is listed in disabledForModels.`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
193
381
|
function inferTrigger(mode, trigger) {
|
|
194
382
|
if (trigger && ADVISOR_TRIGGERS.includes(trigger)) {
|
|
195
383
|
return trigger;
|
|
@@ -205,6 +393,23 @@ function inferTrigger(mode, trigger) {
|
|
|
205
393
|
return "other";
|
|
206
394
|
}
|
|
207
395
|
}
|
|
396
|
+
// Validates a per-call effort request against the plugin's allow-list.
|
|
397
|
+
// Returns undefined when the agent omitted it or the feature is disabled
|
|
398
|
+
// (a stray value is ignored then, since the schema never advertised it).
|
|
399
|
+
function resolveRequestedEffort(config, value) {
|
|
400
|
+
if (value === undefined || value === null)
|
|
401
|
+
return undefined;
|
|
402
|
+
const text = String(value).trim();
|
|
403
|
+
if (!text)
|
|
404
|
+
return undefined;
|
|
405
|
+
const allowed = config.agentEffort;
|
|
406
|
+
if (!allowed || allowed.length === 0)
|
|
407
|
+
return undefined;
|
|
408
|
+
if (!allowed.includes(text)) {
|
|
409
|
+
throw new Error(`Effort "${text}" is not allowed (allowed: ${allowed.join(", ")}).`);
|
|
410
|
+
}
|
|
411
|
+
return text;
|
|
412
|
+
}
|
|
208
413
|
function classifyAdvisorError(message) {
|
|
209
414
|
const text = message.toLowerCase();
|
|
210
415
|
if (text.includes("credit balance is too low") ||
|
|
@@ -214,6 +419,9 @@ function classifyAdvisorError(message) {
|
|
|
214
419
|
if (text.includes("rate_limit") || text.includes(" 429")) {
|
|
215
420
|
return "rate_limit";
|
|
216
421
|
}
|
|
422
|
+
if (text.includes("not a variant of") || text.includes("is not allowed")) {
|
|
423
|
+
return "invalid_effort";
|
|
424
|
+
}
|
|
217
425
|
if (text.includes("failed to parse json") ||
|
|
218
426
|
text.includes("unexpected token")) {
|
|
219
427
|
return "json_parse";
|
|
@@ -308,6 +516,13 @@ function isAdvisorToolName(name) {
|
|
|
308
516
|
function countPriorAdvisorCalls(db, sessionId) {
|
|
309
517
|
const callIds = new Set();
|
|
310
518
|
const modes = [];
|
|
519
|
+
const questions = [];
|
|
520
|
+
const record = (input) => {
|
|
521
|
+
modes.push(String(input?.mode || "general"));
|
|
522
|
+
if (typeof input?.question === "string" && input.question.trim()) {
|
|
523
|
+
questions.push(input.question);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
311
526
|
try {
|
|
312
527
|
for (const sid of collectSessionChain(db, sessionId)) {
|
|
313
528
|
if (tableExists(db, "session_message")) {
|
|
@@ -336,7 +551,7 @@ function countPriorAdvisorCalls(db, sessionId) {
|
|
|
336
551
|
const cid = String(block.id || block.callID || row.id);
|
|
337
552
|
if (!callIds.has(cid)) {
|
|
338
553
|
callIds.add(cid);
|
|
339
|
-
|
|
554
|
+
record(state.input);
|
|
340
555
|
}
|
|
341
556
|
}
|
|
342
557
|
nested.forEach((call, index) => {
|
|
@@ -344,7 +559,7 @@ function countPriorAdvisorCalls(db, sessionId) {
|
|
|
344
559
|
const cid = `${block.id || row.id}#${index}`;
|
|
345
560
|
if (!callIds.has(cid)) {
|
|
346
561
|
callIds.add(cid);
|
|
347
|
-
|
|
562
|
+
record(call.input);
|
|
348
563
|
}
|
|
349
564
|
}
|
|
350
565
|
});
|
|
@@ -372,22 +587,88 @@ function countPriorAdvisorCalls(db, sessionId) {
|
|
|
372
587
|
if (!cid || callIds.has(cid))
|
|
373
588
|
continue;
|
|
374
589
|
callIds.add(cid);
|
|
375
|
-
|
|
590
|
+
record(block.state?.input);
|
|
376
591
|
}
|
|
377
592
|
}
|
|
378
593
|
}
|
|
379
594
|
}
|
|
380
595
|
catch { }
|
|
381
|
-
return { count: callIds.size, modes };
|
|
596
|
+
return { count: callIds.size, modes, questions };
|
|
382
597
|
}
|
|
383
|
-
async function logAdvisorMetrics(metrics) {
|
|
598
|
+
async function logAdvisorMetrics(runtime, metrics) {
|
|
384
599
|
try {
|
|
385
|
-
|
|
600
|
+
const path = runtime?.__advisorTest?.metricsPath ?? METRICS_PATH;
|
|
601
|
+
await appendFile(path, JSON.stringify(metrics) + "\n", "utf-8");
|
|
386
602
|
}
|
|
387
603
|
catch {
|
|
388
604
|
// Metrics must never break the advisor call.
|
|
389
605
|
}
|
|
390
606
|
}
|
|
607
|
+
// Flattens a gate decision into the additive metrics record. A bypassed gate
|
|
608
|
+
// reports `bypass` so reports can separate bypass, skip, fallback, and
|
|
609
|
+
// allowed proceed without inferring from other fields.
|
|
610
|
+
function gateRecordFrom(decision) {
|
|
611
|
+
if (decision.status === "disabled") {
|
|
612
|
+
return {
|
|
613
|
+
status: "bypass",
|
|
614
|
+
reason: "disabled",
|
|
615
|
+
model: null,
|
|
616
|
+
neededProbability: null,
|
|
617
|
+
suggestedEffort: null,
|
|
618
|
+
effortConfidence: null,
|
|
619
|
+
effectiveEffort: null,
|
|
620
|
+
effortSource: null,
|
|
621
|
+
latencyMs: 0,
|
|
622
|
+
stateBytes: 0,
|
|
623
|
+
truncated: false,
|
|
624
|
+
inputTokens: null,
|
|
625
|
+
outputTokens: null,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const metrics = decision.metrics;
|
|
629
|
+
return {
|
|
630
|
+
status: decision.status,
|
|
631
|
+
reason: decision.reason,
|
|
632
|
+
model: metrics.model,
|
|
633
|
+
neededProbability: metrics.neededProbability,
|
|
634
|
+
suggestedEffort: metrics.suggestedEffort,
|
|
635
|
+
effortConfidence: metrics.effortConfidence,
|
|
636
|
+
effectiveEffort: decision.status === "proceed" ? decision.effectiveEffort : null,
|
|
637
|
+
effortSource: decision.status === "proceed" ? decision.effortSource : null,
|
|
638
|
+
latencyMs: metrics.latencyMs,
|
|
639
|
+
stateBytes: metrics.stateBytes,
|
|
640
|
+
truncated: metrics.truncated,
|
|
641
|
+
inputTokens: metrics.inputTokens,
|
|
642
|
+
outputTokens: metrics.outputTokens,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// Human-readable gate summary for the tool output. A bypassed gate contributes
|
|
646
|
+
// nothing, so keyless or disabled installs keep the plain footer.
|
|
647
|
+
export function gateSummary(gate) {
|
|
648
|
+
if (!gate || gate.status === "bypass")
|
|
649
|
+
return null;
|
|
650
|
+
const parts = [];
|
|
651
|
+
if (gate.neededProbability !== null) {
|
|
652
|
+
parts.push(`need=${gate.neededProbability.toFixed(2)}`);
|
|
653
|
+
}
|
|
654
|
+
if (gate.effectiveEffort) {
|
|
655
|
+
parts.push(gate.effortSource === "typesafe"
|
|
656
|
+
? `effort=${gate.effectiveEffort} (gate)`
|
|
657
|
+
: `effort=${gate.effectiveEffort}`);
|
|
658
|
+
}
|
|
659
|
+
switch (gate.status) {
|
|
660
|
+
case "skip":
|
|
661
|
+
parts.push("decision=skip", gate.reason);
|
|
662
|
+
break;
|
|
663
|
+
case "fallback":
|
|
664
|
+
parts.push("decision=fallback", gate.reason);
|
|
665
|
+
break;
|
|
666
|
+
default:
|
|
667
|
+
parts.push("decision=proceed");
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
return `gate: ${parts.join(", ")}`;
|
|
671
|
+
}
|
|
391
672
|
function getSession(db, sessionId) {
|
|
392
673
|
return db
|
|
393
674
|
.query("SELECT id, parent_id, title, directory FROM session WHERE id = ?")
|
|
@@ -642,7 +923,19 @@ function findAdvisorModel(models, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
642
923
|
}
|
|
643
924
|
return null;
|
|
644
925
|
}
|
|
645
|
-
function resolveAdvisorVariant(model, config = DEFAULT_ADVISOR_CONFIG) {
|
|
926
|
+
function resolveAdvisorVariant(model, config = DEFAULT_ADVISOR_CONFIG, requestedVariant) {
|
|
927
|
+
// An agent-requested effort must be a real variant of the model; unlike
|
|
928
|
+
// the configured default it never silently falls back.
|
|
929
|
+
if (requestedVariant !== undefined) {
|
|
930
|
+
if (!model || !Array.isArray(model.variants))
|
|
931
|
+
return requestedVariant;
|
|
932
|
+
const ids = model.variants.map((variant) => variant?.id);
|
|
933
|
+
if (!ids.includes(requestedVariant)) {
|
|
934
|
+
const available = ids.filter((id) => !!id).join(", ") || "none";
|
|
935
|
+
throw new Error(`Effort "${requestedVariant}" is not a variant of ${config.provider}/${config.model} (available: ${available}).`);
|
|
936
|
+
}
|
|
937
|
+
return requestedVariant;
|
|
938
|
+
}
|
|
646
939
|
if (config.variant === undefined)
|
|
647
940
|
return undefined;
|
|
648
941
|
if (!model || !Array.isArray(model.variants))
|
|
@@ -650,6 +943,20 @@ function resolveAdvisorVariant(model, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
650
943
|
const ids = model.variants.map((variant) => variant?.id);
|
|
651
944
|
return ids.includes(config.variant) ? config.variant : undefined;
|
|
652
945
|
}
|
|
946
|
+
// Effort levels the gate may choose: plugin-allowed candidates intersected
|
|
947
|
+
// with the model's live variants. Unknown discovery yields no candidates,
|
|
948
|
+
// which keeps the configured effort and omits automatic effort selection.
|
|
949
|
+
function resolveSupportedEfforts(model, config) {
|
|
950
|
+
const allowed = config.agentEffort && config.agentEffort.length > 0
|
|
951
|
+
? config.agentEffort
|
|
952
|
+
: null;
|
|
953
|
+
if (!allowed || !model || !Array.isArray(model.variants))
|
|
954
|
+
return [];
|
|
955
|
+
const variants = new Set(model.variants
|
|
956
|
+
.map((variant) => variant?.id)
|
|
957
|
+
.filter((id) => !!id));
|
|
958
|
+
return allowed.filter((effort) => variants.has(effort));
|
|
959
|
+
}
|
|
653
960
|
function hasAdvisorConnection(connection) {
|
|
654
961
|
if (connection === null || connection === undefined)
|
|
655
962
|
return false;
|
|
@@ -663,11 +970,50 @@ function hasAdvisorConnection(connection) {
|
|
|
663
970
|
}
|
|
664
971
|
return true;
|
|
665
972
|
}
|
|
666
|
-
|
|
667
|
-
|
|
973
|
+
// Discovery namespaces: the documented V2 `model`/`provider` domains win,
|
|
974
|
+
// the legacy `catalog` namespace stays for older servers and fixtures.
|
|
975
|
+
// Calls stay in method form so any receiver-bound implementation keeps
|
|
976
|
+
// working.
|
|
977
|
+
function discoveryModelApi(runtime) {
|
|
978
|
+
if (runtime.model && typeof runtime.model.list === "function") {
|
|
979
|
+
return runtime.model;
|
|
980
|
+
}
|
|
981
|
+
if (runtime.catalog?.model &&
|
|
982
|
+
typeof runtime.catalog.model.list === "function") {
|
|
983
|
+
return runtime.catalog.model;
|
|
984
|
+
}
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
function discoveryProviderApi(runtime) {
|
|
988
|
+
if (runtime.provider && typeof runtime.provider.get === "function") {
|
|
989
|
+
return runtime.provider;
|
|
990
|
+
}
|
|
991
|
+
if (runtime.catalog?.provider &&
|
|
992
|
+
typeof runtime.catalog.provider.get === "function") {
|
|
993
|
+
return runtime.catalog.provider;
|
|
994
|
+
}
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
// Shared by support checks and the effort gate; null means discovery is
|
|
998
|
+
// unavailable or the configured model was not found.
|
|
999
|
+
async function findAdvisorCatalogModel(runtime, config) {
|
|
1000
|
+
const api = discoveryModelApi(runtime);
|
|
1001
|
+
if (!api)
|
|
1002
|
+
return null;
|
|
1003
|
+
try {
|
|
1004
|
+
const models = unwrapData((await api.list()));
|
|
1005
|
+
return findAdvisorModel(models, config);
|
|
1006
|
+
}
|
|
1007
|
+
catch {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
async function checkAdvisorSupport(runtime, config = DEFAULT_ADVISOR_CONFIG, requestedVariant) {
|
|
1012
|
+
const providerApi = discoveryProviderApi(runtime);
|
|
1013
|
+
if (providerApi) {
|
|
668
1014
|
let provider = null;
|
|
669
1015
|
try {
|
|
670
|
-
provider = unwrapData((await
|
|
1016
|
+
provider = unwrapData((await providerApi.get({
|
|
671
1017
|
providerID: config.provider,
|
|
672
1018
|
})));
|
|
673
1019
|
}
|
|
@@ -684,11 +1030,12 @@ async function checkAdvisorSupport(runtime, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
684
1030
|
};
|
|
685
1031
|
}
|
|
686
1032
|
}
|
|
687
|
-
let variant = config.variant;
|
|
688
|
-
|
|
1033
|
+
let variant = requestedVariant ?? config.variant;
|
|
1034
|
+
const modelApi = discoveryModelApi(runtime);
|
|
1035
|
+
if (modelApi) {
|
|
689
1036
|
let models = null;
|
|
690
1037
|
try {
|
|
691
|
-
models = unwrapData((await
|
|
1038
|
+
models = unwrapData((await modelApi.list()));
|
|
692
1039
|
}
|
|
693
1040
|
catch (err) {
|
|
694
1041
|
return {
|
|
@@ -703,7 +1050,15 @@ async function checkAdvisorSupport(runtime, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
703
1050
|
reason: `Model unavailable: ${config.provider}/${config.model}`,
|
|
704
1051
|
};
|
|
705
1052
|
}
|
|
706
|
-
|
|
1053
|
+
try {
|
|
1054
|
+
variant = resolveAdvisorVariant(model, config, requestedVariant);
|
|
1055
|
+
}
|
|
1056
|
+
catch (err) {
|
|
1057
|
+
return {
|
|
1058
|
+
supported: false,
|
|
1059
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
707
1062
|
}
|
|
708
1063
|
if (typeof runtime.integration?.connection?.active === "function") {
|
|
709
1064
|
let connection = null;
|
|
@@ -726,9 +1081,15 @@ async function checkAdvisorSupport(runtime, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
726
1081
|
return { supported: true, variant };
|
|
727
1082
|
}
|
|
728
1083
|
let cachedAdvisorSessionId = null;
|
|
1084
|
+
// Last variant pinned by this process (tri-state: unknown until a session
|
|
1085
|
+
// is pinned or reports its variant).
|
|
1086
|
+
let cachedAdvisorVariant;
|
|
1087
|
+
let cachedAdvisorVariantKnown = false;
|
|
729
1088
|
let advisorQueue = Promise.resolve();
|
|
730
1089
|
function resetAdvisorSessionCache() {
|
|
731
1090
|
cachedAdvisorSessionId = null;
|
|
1091
|
+
cachedAdvisorVariant = undefined;
|
|
1092
|
+
cachedAdvisorVariantKnown = false;
|
|
732
1093
|
}
|
|
733
1094
|
function enqueueAdvisor(task) {
|
|
734
1095
|
const next = advisorQueue.then(task, task);
|
|
@@ -777,7 +1138,7 @@ async function getAdvisorSession(runtime, sessionId) {
|
|
|
777
1138
|
return null;
|
|
778
1139
|
}
|
|
779
1140
|
}
|
|
780
|
-
function advisorSessionNeedsModel(session, config = DEFAULT_ADVISOR_CONFIG) {
|
|
1141
|
+
function advisorSessionNeedsModel(session, variant, allowCacheFallback, config = DEFAULT_ADVISOR_CONFIG) {
|
|
781
1142
|
if (!session)
|
|
782
1143
|
return true;
|
|
783
1144
|
const model = session.model;
|
|
@@ -785,8 +1146,32 @@ function advisorSessionNeedsModel(session, config = DEFAULT_ADVISOR_CONFIG) {
|
|
|
785
1146
|
return true;
|
|
786
1147
|
const provider = String(model.providerID || "").toLowerCase();
|
|
787
1148
|
const id = String(model.id || model.modelID || "").toLowerCase();
|
|
788
|
-
|
|
789
|
-
id !== config.model.toLowerCase())
|
|
1149
|
+
if (provider !== config.provider.toLowerCase() ||
|
|
1150
|
+
id !== config.model.toLowerCase()) {
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
// Re-pin when the requested effort differs from the session's variant. The
|
|
1154
|
+
// session payload may not report a variant; then fall back to the last
|
|
1155
|
+
// pinned value — but only for the session it was pinned on.
|
|
1156
|
+
if (typeof model.variant === "string" && model.variant) {
|
|
1157
|
+
return model.variant !== variant;
|
|
1158
|
+
}
|
|
1159
|
+
if (allowCacheFallback && cachedAdvisorVariantKnown) {
|
|
1160
|
+
return cachedAdvisorVariant !== variant;
|
|
1161
|
+
}
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
// Records the variant a reused session reports so later requests for a
|
|
1165
|
+
// different effort re-pin even when a future payload omits it.
|
|
1166
|
+
function syncCachedAdvisorVariant(session) {
|
|
1167
|
+
const model = session?.model;
|
|
1168
|
+
if (model &&
|
|
1169
|
+
typeof model === "object" &&
|
|
1170
|
+
typeof model.variant === "string" &&
|
|
1171
|
+
model.variant) {
|
|
1172
|
+
cachedAdvisorVariant = model.variant;
|
|
1173
|
+
cachedAdvisorVariantKnown = true;
|
|
1174
|
+
}
|
|
790
1175
|
}
|
|
791
1176
|
async function switchAdvisorSessionModel(runtime, sessionId, variant, config = DEFAULT_ADVISOR_CONFIG) {
|
|
792
1177
|
if (typeof runtime.session?.switchModel !== "function") {
|
|
@@ -811,8 +1196,13 @@ async function ensureAdvisorSession(runtime, variant, config = DEFAULT_ADVISOR_C
|
|
|
811
1196
|
const session = await getAdvisorSession(runtime, candidate);
|
|
812
1197
|
if (!session)
|
|
813
1198
|
continue;
|
|
814
|
-
if (advisorSessionNeedsModel(session, config)) {
|
|
1199
|
+
if (advisorSessionNeedsModel(session, variant, candidate === cachedAdvisorSessionId, config)) {
|
|
815
1200
|
await switchAdvisorSessionModel(runtime, candidate, variant, config);
|
|
1201
|
+
cachedAdvisorVariant = variant;
|
|
1202
|
+
cachedAdvisorVariantKnown = true;
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
syncCachedAdvisorVariant(session);
|
|
816
1206
|
}
|
|
817
1207
|
cachedAdvisorSessionId = candidate;
|
|
818
1208
|
return candidate;
|
|
@@ -824,8 +1214,13 @@ async function ensureAdvisorSession(runtime, variant, config = DEFAULT_ADVISOR_C
|
|
|
824
1214
|
session.title === LEGACY_ADVISOR_SESSION_TITLE);
|
|
825
1215
|
const existingId = typeof existing?.id === "string" ? existing.id : null;
|
|
826
1216
|
if (existing && existingId) {
|
|
827
|
-
if (advisorSessionNeedsModel(existing, config)) {
|
|
1217
|
+
if (advisorSessionNeedsModel(existing, variant, existingId === cachedAdvisorSessionId, config)) {
|
|
828
1218
|
await switchAdvisorSessionModel(runtime, existingId, variant, config);
|
|
1219
|
+
cachedAdvisorVariant = variant;
|
|
1220
|
+
cachedAdvisorVariantKnown = true;
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
syncCachedAdvisorVariant(existing);
|
|
829
1224
|
}
|
|
830
1225
|
cachedAdvisorSessionId = existingId;
|
|
831
1226
|
await storeAdvisorSessionId(runtime, existingId);
|
|
@@ -846,6 +1241,8 @@ async function ensureAdvisorSession(runtime, variant, config = DEFAULT_ADVISOR_C
|
|
|
846
1241
|
}
|
|
847
1242
|
await switchAdvisorSessionModel(runtime, sessionId, variant, config);
|
|
848
1243
|
cachedAdvisorSessionId = sessionId;
|
|
1244
|
+
cachedAdvisorVariant = variant;
|
|
1245
|
+
cachedAdvisorVariantKnown = true;
|
|
849
1246
|
await storeAdvisorSessionId(runtime, sessionId);
|
|
850
1247
|
return sessionId;
|
|
851
1248
|
}
|
|
@@ -855,7 +1252,7 @@ async function callAdvisor(opts) {
|
|
|
855
1252
|
if (typeof runtime?.session?.generate !== "function") {
|
|
856
1253
|
throw new Error("advisor requires the OpenCode V2 plugin runtime (session.generate unavailable).");
|
|
857
1254
|
}
|
|
858
|
-
const support = await checkAdvisorSupport(runtime, config);
|
|
1255
|
+
const support = await checkAdvisorSupport(runtime, config, opts.effort);
|
|
859
1256
|
if (!support.supported) {
|
|
860
1257
|
throw new Error(support.reason);
|
|
861
1258
|
}
|
|
@@ -866,10 +1263,25 @@ async function callAdvisor(opts) {
|
|
|
866
1263
|
const text = await enqueueAdvisor(() => withTimeout((async () => {
|
|
867
1264
|
const sessionId = await ensureAdvisorSession(runtime, support.variant, config);
|
|
868
1265
|
const request = { sessionID: sessionId, prompt };
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1266
|
+
const startedAt = Date.now();
|
|
1267
|
+
const run = async () => {
|
|
1268
|
+
const result = opts.signal
|
|
1269
|
+
? await generate(request, { signal: opts.signal })
|
|
1270
|
+
: await generate(request);
|
|
1271
|
+
return extractGeneratedText(result);
|
|
1272
|
+
};
|
|
1273
|
+
// A generation can come back without text (transient provider
|
|
1274
|
+
// behavior, e.g. a reasoning-only response with adaptive thinking).
|
|
1275
|
+
// Retry once while most of the timeout budget remains; never retry
|
|
1276
|
+
// after a caller cancellation.
|
|
1277
|
+
let output = await run();
|
|
1278
|
+
if (!output?.trim() && !opts.signal?.aborted) {
|
|
1279
|
+
const elapsed = Date.now() - startedAt;
|
|
1280
|
+
if (elapsed < config.timeoutMs / 2) {
|
|
1281
|
+
console.log(`[advisor] empty generation; retrying once (session=${sessionId} elapsedMs=${elapsed})`);
|
|
1282
|
+
output = await run();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
873
1285
|
if (!output?.trim()) {
|
|
874
1286
|
throw new Error("Advisor returned an empty response.");
|
|
875
1287
|
}
|
|
@@ -882,6 +1294,86 @@ async function callAdvisor(opts) {
|
|
|
882
1294
|
text: `${text}\n\n---\n_advisor via OpenCode: ${modelLabel} (token usage unavailable via session generation)_`,
|
|
883
1295
|
inputTokens: null,
|
|
884
1296
|
outputTokens: null,
|
|
1297
|
+
variant: support.variant,
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
// Benchmark stores live for the process, keyed by resolved user paths (and
|
|
1301
|
+
// test seed overrides), so every consultation observes CLI refreshes
|
|
1302
|
+
// without re-reading unchanged files. Tests reset between cases.
|
|
1303
|
+
const benchmarkStores = new Map();
|
|
1304
|
+
function resetBenchmarkStores() {
|
|
1305
|
+
benchmarkStores.clear();
|
|
1306
|
+
}
|
|
1307
|
+
function benchmarkStoreKey(snapshotPath, mappingsPath, seedSnapshotPath, seedMappingsPath) {
|
|
1308
|
+
return [
|
|
1309
|
+
snapshotPath,
|
|
1310
|
+
mappingsPath,
|
|
1311
|
+
seedSnapshotPath ?? "",
|
|
1312
|
+
seedMappingsPath ?? "",
|
|
1313
|
+
].join("\n");
|
|
1314
|
+
}
|
|
1315
|
+
// Loads one consistent benchmark view and assembles gate evidence from it.
|
|
1316
|
+
// Any failure (bad paths, unreadable files, unexpected errors) yields null
|
|
1317
|
+
// and the consultation proceeds without benchmark enrichment.
|
|
1318
|
+
async function loadBenchmarkEvidence(runtime, config, requester, advisor) {
|
|
1319
|
+
try {
|
|
1320
|
+
const testOpts = runtime?.__advisorTest?.benchmarkStoreOptions;
|
|
1321
|
+
const paths = resolveBenchmarkPaths(config.benchmarks, testOpts?.env ?? process.env);
|
|
1322
|
+
if ("error" in paths)
|
|
1323
|
+
return null;
|
|
1324
|
+
const key = benchmarkStoreKey(paths.snapshotPath, paths.mappingsPath, testOpts?.seedSnapshotPath, testOpts?.seedMappingsPath);
|
|
1325
|
+
let store = benchmarkStores.get(key);
|
|
1326
|
+
if (!store) {
|
|
1327
|
+
store = await createBenchmarkStore({
|
|
1328
|
+
snapshotPath: paths.snapshotPath,
|
|
1329
|
+
mappingsPath: paths.mappingsPath,
|
|
1330
|
+
seedSnapshotPath: testOpts?.seedSnapshotPath,
|
|
1331
|
+
seedMappingsPath: testOpts?.seedMappingsPath,
|
|
1332
|
+
fs: testOpts?.fs,
|
|
1333
|
+
maxBytes: testOpts?.maxBytes,
|
|
1334
|
+
});
|
|
1335
|
+
benchmarkStores.set(key, store);
|
|
1336
|
+
}
|
|
1337
|
+
const view = await store.view();
|
|
1338
|
+
return {
|
|
1339
|
+
view,
|
|
1340
|
+
evidence: buildBenchmarkEvidence({ requester, advisor, view }),
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
catch {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function summarizeBenchmarkEvidence(loaded, finalEffort) {
|
|
1348
|
+
if (!loaded)
|
|
1349
|
+
return undefined;
|
|
1350
|
+
const { view, evidence } = loaded;
|
|
1351
|
+
const { requester, advisor } = evidence.models;
|
|
1352
|
+
const policy = advisor.policy;
|
|
1353
|
+
const finalMatch = finalEffort === null
|
|
1354
|
+
? null
|
|
1355
|
+
: view.matcher.match({
|
|
1356
|
+
providerID: advisor.providerID,
|
|
1357
|
+
modelID: advisor.modelID,
|
|
1358
|
+
variant: finalEffort,
|
|
1359
|
+
}).status;
|
|
1360
|
+
return {
|
|
1361
|
+
source: evidence.benchmarks.source,
|
|
1362
|
+
contentHash: evidence.benchmarks.contentHash,
|
|
1363
|
+
fetchedAt: evidence.benchmarks.fetchedAt,
|
|
1364
|
+
hashVerified: evidence.benchmarks.hashVerified,
|
|
1365
|
+
requester: requester.providerID && requester.modelID
|
|
1366
|
+
? `${requester.providerID}/${requester.modelID}${requester.variant ? `#${requester.variant}` : ""} (${requester.provenance})`
|
|
1367
|
+
: null,
|
|
1368
|
+
requesterMatch: evidence.benchmarks.requesterMatch.status,
|
|
1369
|
+
advisorPolicy: policy.kind === "pinned"
|
|
1370
|
+
? `pinned:${policy.effort}`
|
|
1371
|
+
: policy.kind === "candidates"
|
|
1372
|
+
? `candidates:${policy.candidates.join(",")}>${policy.fallback ?? "none"}`
|
|
1373
|
+
: `fixed:${policy.effort ?? "none"}`,
|
|
1374
|
+
advisorMatch: evidence.benchmarks.advisorDefaultMatch.status,
|
|
1375
|
+
finalEffort,
|
|
1376
|
+
finalMatch,
|
|
885
1377
|
};
|
|
886
1378
|
}
|
|
887
1379
|
async function runAdvisor(opts) {
|
|
@@ -892,7 +1384,7 @@ async function runAdvisor(opts) {
|
|
|
892
1384
|
const sessionId = opts.sessionId;
|
|
893
1385
|
const questionChars = opts.question?.length ?? 0;
|
|
894
1386
|
if (!sessionId) {
|
|
895
|
-
await logAdvisorMetrics({
|
|
1387
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
896
1388
|
ts: new Date().toISOString(),
|
|
897
1389
|
sessionId: null,
|
|
898
1390
|
callerModel: null,
|
|
@@ -901,6 +1393,7 @@ async function runAdvisor(opts) {
|
|
|
901
1393
|
mode,
|
|
902
1394
|
trigger,
|
|
903
1395
|
questionChars,
|
|
1396
|
+
effort: null,
|
|
904
1397
|
outcome: "no_session",
|
|
905
1398
|
errorType: "no_session",
|
|
906
1399
|
latencyMs: Date.now() - started,
|
|
@@ -914,13 +1407,14 @@ async function runAdvisor(opts) {
|
|
|
914
1407
|
}
|
|
915
1408
|
let db = null;
|
|
916
1409
|
try {
|
|
917
|
-
db = openDb();
|
|
1410
|
+
db = openDb(opts.runtime);
|
|
918
1411
|
const info = getSessionInfo(db, sessionId);
|
|
919
1412
|
const callerModel = callerLabel(info?.model ?? null);
|
|
920
1413
|
const callerAgent = opts.callerAgent || info?.agent || null;
|
|
921
1414
|
const directory = opts.callerDirectory || info?.directory || null;
|
|
922
|
-
|
|
923
|
-
|
|
1415
|
+
const disabled = advisorDisabledReason(info?.model, config);
|
|
1416
|
+
if (disabled) {
|
|
1417
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
924
1418
|
ts: new Date().toISOString(),
|
|
925
1419
|
sessionId,
|
|
926
1420
|
callerModel,
|
|
@@ -929,7 +1423,8 @@ async function runAdvisor(opts) {
|
|
|
929
1423
|
mode,
|
|
930
1424
|
trigger,
|
|
931
1425
|
questionChars,
|
|
932
|
-
|
|
1426
|
+
effort: null,
|
|
1427
|
+
outcome: disabled.outcome,
|
|
933
1428
|
errorType: null,
|
|
934
1429
|
latencyMs: Date.now() - started,
|
|
935
1430
|
inputTokens: null,
|
|
@@ -938,12 +1433,12 @@ async function runAdvisor(opts) {
|
|
|
938
1433
|
priorConsultations: 0,
|
|
939
1434
|
via: "opencode-session",
|
|
940
1435
|
});
|
|
941
|
-
console.log(`[advisor] session=${sessionId} mode=${mode} outcome
|
|
942
|
-
return
|
|
1436
|
+
console.log(`[advisor] session=${sessionId} mode=${mode} outcome=${disabled.outcome}`);
|
|
1437
|
+
return disabled.message;
|
|
943
1438
|
}
|
|
944
1439
|
let transcript = buildTranscript(db, sessionId);
|
|
945
1440
|
if (!transcript?.trim()) {
|
|
946
|
-
await logAdvisorMetrics({
|
|
1441
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
947
1442
|
ts: new Date().toISOString(),
|
|
948
1443
|
sessionId,
|
|
949
1444
|
callerModel,
|
|
@@ -952,6 +1447,7 @@ async function runAdvisor(opts) {
|
|
|
952
1447
|
mode,
|
|
953
1448
|
trigger,
|
|
954
1449
|
questionChars,
|
|
1450
|
+
effort: null,
|
|
955
1451
|
outcome: "no_transcript",
|
|
956
1452
|
errorType: "no_transcript",
|
|
957
1453
|
latencyMs: Date.now() - started,
|
|
@@ -971,23 +1467,141 @@ async function runAdvisor(opts) {
|
|
|
971
1467
|
"... (older transcript trimmed to fit maxTranscriptChars) ...\n\n" +
|
|
972
1468
|
transcript.slice(-config.maxTranscriptChars);
|
|
973
1469
|
}
|
|
1470
|
+
// Invocation-correct requester identity for benchmark matching: the
|
|
1471
|
+
// model that produced this tool call, resolved after the early guards
|
|
1472
|
+
// so skipped calls pay for no extra reads.
|
|
1473
|
+
const requesterProfile = resolveRequesterProfile(db, {
|
|
1474
|
+
sessionId,
|
|
1475
|
+
messageID: opts.callerMessageID,
|
|
1476
|
+
callID: opts.callerCallID,
|
|
1477
|
+
sessionModel: info?.model ?? null,
|
|
1478
|
+
});
|
|
974
1479
|
const prior = countPriorAdvisorCalls(db, sessionId);
|
|
975
1480
|
const priorNote = prior.count > 0
|
|
976
1481
|
? `Note: this session chain already has ${prior.count} recorded advisor consultation(s) (modes: ${prior.modes.join(", ") || "unknown"}). Focus on what is new since then; do not repeat settled advice unless new evidence changes it.`
|
|
977
1482
|
: null;
|
|
978
1483
|
const systemPrompt = SYSTEM_PROMPTS[mode] || SYSTEM_PROMPTS.general;
|
|
1484
|
+
let requestedEffort;
|
|
1485
|
+
let effectiveEffort;
|
|
1486
|
+
let gateRecord;
|
|
1487
|
+
let benchmarkLoaded = null;
|
|
979
1488
|
try {
|
|
1489
|
+
requestedEffort = resolveRequestedEffort(config, opts.effort);
|
|
1490
|
+
// Optional TypeSafe preflight: one bounded request decides whether this
|
|
1491
|
+
// consultation is worth the expensive generation and, when the caller
|
|
1492
|
+
// did not pin an effort, which supported effort fits. Gate failures
|
|
1493
|
+
// fall back to the ordinary behavior and never fail the call.
|
|
1494
|
+
let gateDecision = null;
|
|
1495
|
+
let advisorProfile = null;
|
|
1496
|
+
if (config.typesafe.keyPresent && !config.typesafeSource.disabled) {
|
|
1497
|
+
const catalogModel = await findAdvisorCatalogModel(opts.runtime, config);
|
|
1498
|
+
const supportedEfforts = resolveSupportedEfforts(catalogModel, config);
|
|
1499
|
+
advisorProfile = resolveAdvisorProfile({
|
|
1500
|
+
providerID: config.provider,
|
|
1501
|
+
modelID: config.model,
|
|
1502
|
+
requestedEffort,
|
|
1503
|
+
supportedEfforts,
|
|
1504
|
+
defaultEffort: requestedEffort ??
|
|
1505
|
+
resolveAdvisorVariant(catalogModel, config) ??
|
|
1506
|
+
null,
|
|
1507
|
+
});
|
|
1508
|
+
const gateSettings = config.typesafe.settings ?? TYPESAFE_DEFAULTS;
|
|
1509
|
+
benchmarkLoaded = await loadBenchmarkEvidence(opts.runtime, config, requesterProfile, advisorProfile);
|
|
1510
|
+
gateDecision = await runTypeSafeGate({
|
|
1511
|
+
state: buildDecisionState(db, sessionId, {
|
|
1512
|
+
question: opts.question ?? null,
|
|
1513
|
+
mode,
|
|
1514
|
+
trigger,
|
|
1515
|
+
explicitEffort: requestedEffort ?? null,
|
|
1516
|
+
caller: callerModel,
|
|
1517
|
+
directory,
|
|
1518
|
+
priorConsultations: prior.count,
|
|
1519
|
+
priorModes: prior.modes,
|
|
1520
|
+
priorQuestions: prior.questions,
|
|
1521
|
+
supportedEfforts,
|
|
1522
|
+
defaultEffort: requestedEffort ?? config.variant ?? null,
|
|
1523
|
+
maxStateBytes: gateSettings.maxStateBytes,
|
|
1524
|
+
models: benchmarkLoaded?.evidence.models ?? null,
|
|
1525
|
+
benchmarks: benchmarkLoaded?.evidence.benchmarks ?? null,
|
|
1526
|
+
formatMessage: formatV2Message,
|
|
1527
|
+
}),
|
|
1528
|
+
input: {
|
|
1529
|
+
mode,
|
|
1530
|
+
trigger,
|
|
1531
|
+
question: opts.question ?? null,
|
|
1532
|
+
explicitEffort: requestedEffort ?? null,
|
|
1533
|
+
supportedEfforts,
|
|
1534
|
+
defaultEffort: requestedEffort ?? config.variant ?? null,
|
|
1535
|
+
},
|
|
1536
|
+
settings: gateSettings,
|
|
1537
|
+
keyPresent: config.typesafe.keyPresent,
|
|
1538
|
+
client: opts.runtime?.__advisorTest?.gateClient,
|
|
1539
|
+
env: opts.runtime?.__advisorTest?.gateEnv,
|
|
1540
|
+
fetchImpl: opts.runtime?.__advisorTest?.gateFetch,
|
|
1541
|
+
signal: opts.signal,
|
|
1542
|
+
});
|
|
1543
|
+
gateRecord = gateRecordFrom(gateDecision);
|
|
1544
|
+
}
|
|
1545
|
+
// Profiles resolve for every consultation: with discovery the advisor
|
|
1546
|
+
// policy reflects live variants, otherwise it fixes to the configured
|
|
1547
|
+
// effort (or the caller's pin).
|
|
1548
|
+
advisorProfile ??= resolveAdvisorProfile({
|
|
1549
|
+
providerID: config.provider,
|
|
1550
|
+
modelID: config.model,
|
|
1551
|
+
requestedEffort,
|
|
1552
|
+
supportedEfforts: [],
|
|
1553
|
+
defaultEffort: config.variant ?? null,
|
|
1554
|
+
});
|
|
1555
|
+
opts.runtime?.__advisorTest?.profileSink?.({
|
|
1556
|
+
requester: requesterProfile,
|
|
1557
|
+
advisor: advisorProfile,
|
|
1558
|
+
});
|
|
1559
|
+
if (gateDecision?.status === "skip") {
|
|
1560
|
+
const latencyMs = Date.now() - started;
|
|
1561
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
1562
|
+
ts: new Date().toISOString(),
|
|
1563
|
+
sessionId,
|
|
1564
|
+
callerModel,
|
|
1565
|
+
callerAgent,
|
|
1566
|
+
directory,
|
|
1567
|
+
mode,
|
|
1568
|
+
trigger,
|
|
1569
|
+
questionChars,
|
|
1570
|
+
effort: null,
|
|
1571
|
+
outcome: "skipped_typesafe",
|
|
1572
|
+
errorType: null,
|
|
1573
|
+
latencyMs,
|
|
1574
|
+
inputTokens: null,
|
|
1575
|
+
outputTokens: null,
|
|
1576
|
+
transcriptChars: transcript.length,
|
|
1577
|
+
priorConsultations: prior.count,
|
|
1578
|
+
via: "opencode-session",
|
|
1579
|
+
gate: gateRecord,
|
|
1580
|
+
benchmarks: summarizeBenchmarkEvidence(benchmarkLoaded, null),
|
|
1581
|
+
});
|
|
1582
|
+
console.log(`[advisor] session=${sessionId} mode=${mode} trigger=${trigger} outcome=skipped_typesafe needed=${gateDecision.metrics.neededProbability ?? "n/a"} latencyMs=${latencyMs}`);
|
|
1583
|
+
const skipNote = gateSummary(gateRecord);
|
|
1584
|
+
return (`advisor consultation skipped (typesafe): the request did not need an advisor at this point` +
|
|
1585
|
+
`${gateDecision.metrics.neededProbability !== null ? ` (need probability ${gateDecision.metrics.neededProbability.toFixed(2)})` : ""}. ` +
|
|
1586
|
+
`Ask again with a concrete unresolved question if the situation changes.` +
|
|
1587
|
+
(skipNote ? `\n_${skipNote}_` : ""));
|
|
1588
|
+
}
|
|
1589
|
+
effectiveEffort =
|
|
1590
|
+
gateDecision?.status === "proceed" && gateDecision.effectiveEffort
|
|
1591
|
+
? gateDecision.effectiveEffort
|
|
1592
|
+
: requestedEffort;
|
|
980
1593
|
const result = await callAdvisor({
|
|
981
1594
|
runtime: opts.runtime,
|
|
982
1595
|
systemPrompt,
|
|
983
1596
|
transcript,
|
|
984
1597
|
question: opts.question,
|
|
985
1598
|
priorNote,
|
|
1599
|
+
effort: effectiveEffort,
|
|
986
1600
|
signal: opts.signal,
|
|
987
1601
|
config,
|
|
988
1602
|
});
|
|
989
1603
|
const latencyMs = Date.now() - started;
|
|
990
|
-
await logAdvisorMetrics({
|
|
1604
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
991
1605
|
ts: new Date().toISOString(),
|
|
992
1606
|
sessionId,
|
|
993
1607
|
callerModel,
|
|
@@ -996,6 +1610,7 @@ async function runAdvisor(opts) {
|
|
|
996
1610
|
mode,
|
|
997
1611
|
trigger,
|
|
998
1612
|
questionChars,
|
|
1613
|
+
effort: result.variant ?? null,
|
|
999
1614
|
outcome: "advisor_response",
|
|
1000
1615
|
errorType: null,
|
|
1001
1616
|
latencyMs,
|
|
@@ -1004,16 +1619,20 @@ async function runAdvisor(opts) {
|
|
|
1004
1619
|
transcriptChars: transcript.length,
|
|
1005
1620
|
priorConsultations: prior.count,
|
|
1006
1621
|
via: "opencode-session",
|
|
1622
|
+
gate: gateRecord,
|
|
1623
|
+
benchmarks: summarizeBenchmarkEvidence(benchmarkLoaded, result.variant ?? effectiveEffort ?? requestedEffort ?? null),
|
|
1007
1624
|
});
|
|
1008
1625
|
console.log(`[advisor] session=${sessionId} mode=${mode} trigger=${trigger} outcome=advisor_response latencyMs=${latencyMs}`);
|
|
1626
|
+
const gateNote = gateSummary(gateRecord);
|
|
1009
1627
|
return (result.text +
|
|
1010
|
-
`\n\n_advisor consultation #${prior.count + 1} in this session chain (trigger=${trigger})_`
|
|
1628
|
+
`\n\n_advisor consultation #${prior.count + 1} in this session chain (trigger=${trigger})_` +
|
|
1629
|
+
(gateNote ? `\n_${gateNote}_` : ""));
|
|
1011
1630
|
}
|
|
1012
1631
|
catch (err) {
|
|
1013
1632
|
const message = err instanceof Error ? err.message : String(err);
|
|
1014
1633
|
const errorType = classifyAdvisorError(message);
|
|
1015
1634
|
const latencyMs = Date.now() - started;
|
|
1016
|
-
await logAdvisorMetrics({
|
|
1635
|
+
await logAdvisorMetrics(opts.runtime, {
|
|
1017
1636
|
ts: new Date().toISOString(),
|
|
1018
1637
|
sessionId,
|
|
1019
1638
|
callerModel,
|
|
@@ -1022,6 +1641,7 @@ async function runAdvisor(opts) {
|
|
|
1022
1641
|
mode,
|
|
1023
1642
|
trigger,
|
|
1024
1643
|
questionChars,
|
|
1644
|
+
effort: effectiveEffort ?? requestedEffort ?? null,
|
|
1025
1645
|
outcome: "error",
|
|
1026
1646
|
errorType,
|
|
1027
1647
|
latencyMs,
|
|
@@ -1030,6 +1650,8 @@ async function runAdvisor(opts) {
|
|
|
1030
1650
|
transcriptChars: transcript.length,
|
|
1031
1651
|
priorConsultations: prior.count,
|
|
1032
1652
|
via: "opencode-session",
|
|
1653
|
+
gate: gateRecord,
|
|
1654
|
+
benchmarks: summarizeBenchmarkEvidence(benchmarkLoaded, effectiveEffort ?? requestedEffort ?? null),
|
|
1033
1655
|
});
|
|
1034
1656
|
console.log(`[advisor] session=${sessionId} mode=${mode} trigger=${trigger} outcome=error errorType=${errorType} latencyMs=${latencyMs}`);
|
|
1035
1657
|
throw new Error(`advisor failed (${errorType}): ${message}`);
|
|
@@ -1046,8 +1668,8 @@ export async function setupOcAdvisorV2(ctx) {
|
|
|
1046
1668
|
const reg = await ctx.tool.transform((draft) => {
|
|
1047
1669
|
draft.add({
|
|
1048
1670
|
name: "advisor",
|
|
1049
|
-
description:
|
|
1050
|
-
input:
|
|
1671
|
+
description: buildToolDescription(advisorConfig),
|
|
1672
|
+
input: buildAdvisorInputSchema(advisorConfig),
|
|
1051
1673
|
// Register as a direct tool, not a Code Mode tool. OpenCode 2 only
|
|
1052
1674
|
// exposes tools with `codemode: false` to the model directly; every
|
|
1053
1675
|
// other tool is reachable solely through `execute`, whose tool log
|
|
@@ -1064,9 +1686,12 @@ export async function setupOcAdvisorV2(ctx) {
|
|
|
1064
1686
|
mode: input?.mode,
|
|
1065
1687
|
trigger: input?.trigger,
|
|
1066
1688
|
question: input?.question,
|
|
1689
|
+
effort: input?.effort,
|
|
1067
1690
|
signal: context.abort,
|
|
1068
1691
|
callerAgent: context.agent,
|
|
1069
1692
|
callerDirectory: context.directory,
|
|
1693
|
+
callerMessageID: context.messageID,
|
|
1694
|
+
callerCallID: context.id,
|
|
1070
1695
|
config: advisorConfig,
|
|
1071
1696
|
});
|
|
1072
1697
|
return { content: text };
|
|
@@ -1082,15 +1707,17 @@ export async function setupOcAdvisorV2(ctx) {
|
|
|
1082
1707
|
return;
|
|
1083
1708
|
// `event.tools` lists the direct tools available to this request.
|
|
1084
1709
|
// OpenCode drops entries a hook adds for tools it did not register,
|
|
1085
|
-
// so the hook can only hide the tool (Fable sessions),
|
|
1086
|
-
// The checkpoint instruction is injected only when
|
|
1087
|
-
// actually available, e.g. not when a permission rule
|
|
1710
|
+
// so the hook can only hide the tool (Fable or opted-out sessions),
|
|
1711
|
+
// never add it. The checkpoint instruction is injected only when
|
|
1712
|
+
// the tool is actually available, e.g. not when a permission rule
|
|
1713
|
+
// removed it.
|
|
1714
|
+
const disabled = advisorDisabledReason(event.model, advisorConfig);
|
|
1088
1715
|
let available = false;
|
|
1089
1716
|
for (const key of Object.keys(event.tools)) {
|
|
1090
1717
|
if (!isAdvisorToolName(key)) {
|
|
1091
1718
|
continue;
|
|
1092
1719
|
}
|
|
1093
|
-
if (
|
|
1720
|
+
if (disabled) {
|
|
1094
1721
|
delete event.tools[key];
|
|
1095
1722
|
}
|
|
1096
1723
|
else {
|
|
@@ -1123,5 +1750,5 @@ export const OcAdvisorPluginV2 = plugin;
|
|
|
1123
1750
|
export default plugin;
|
|
1124
1751
|
// Named exports for unit tests (bun test). The plugin entrypoint is the
|
|
1125
1752
|
// default export above.
|
|
1126
|
-
export { ADVISOR_TRIGGERS, CHECKPOINT_INSTRUCTION, DEFAULT_ADVISOR_CONFIG, TOOL_DESCRIPTION, buildAdvisorPrompt, checkAdvisorSupport, classifyAdvisorError, ensureAdvisorSession, extractGeneratedText, findAdvisorModel, hasAdvisorConnection, inferTrigger, isAdvisorToolName, isFableModel, isProviderUsable, parseModelRef, resolveAdvisorConfig, resolveAdvisorVariant, resetAdvisorSessionCache, unwrapData, withTimeout, };
|
|
1753
|
+
export { ADVISOR_TRIGGERS, CHECKPOINT_INSTRUCTION, DEFAULT_ADVISOR_CONFIG, TOOL_DESCRIPTION, buildAdvisorInputSchema, buildAdvisorPrompt, buildToolDescription, checkAdvisorSupport, classifyAdvisorError, ensureAdvisorSession, extractGeneratedText, findAdvisorModel, hasAdvisorConnection, advisorDisabledReason, inferTrigger, isAdvisorToolName, isFableModel, isProviderUsable, parseModelRef, resolveAdvisorConfig, resolveAdvisorVariant, resolveRequestedEffort, resolveSupportedEfforts, resetAdvisorSessionCache, resetBenchmarkStores, unwrapData, withTimeout, formatV2Message, runAdvisor, };
|
|
1127
1754
|
//# sourceMappingURL=ocAdvisor.js.map
|