sneakoscope 10.2.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +3 -1
- package/dist/cli/command-registry.js +9 -1
- package/dist/cli/install-helpers-menubar.js +57 -0
- package/dist/cli/install-helpers.js +10 -1
- package/dist/commands/decision.js +7 -0
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/native-worker-backend-router.js +7 -2
- package/dist/core/codex-app/menubar/constants.js +2 -0
- package/dist/core/decisions/cli.js +324 -0
- package/dist/core/decisions/config.js +109 -0
- package/dist/core/decisions/evaluation.js +62 -0
- package/dist/core/decisions/integration.js +262 -0
- package/dist/core/decisions/openrouter.js +270 -0
- package/dist/core/decisions/policy.js +393 -0
- package/dist/core/decisions/questions.js +215 -0
- package/dist/core/decisions/receipt.js +41 -0
- package/dist/core/decisions/recovery.js +39 -0
- package/dist/core/decisions/routing.js +58 -0
- package/dist/core/decisions/state.js +173 -0
- package/dist/core/decisions/types.js +69 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/feature-registry.js +1 -0
- package/dist/core/hooks-runtime/subagent-context.js +5 -3
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +9 -3
- package/dist/core/provider/model-router.js +3 -3
- package/dist/core/routes.js +2 -1
- package/dist/core/subagents/model-policy.js +15 -0
- package/dist/core/subagents/official-subagent-lock.js +15 -0
- package/dist/core/subagents/official-subagent-preparation.js +265 -24
- package/dist/core/subagents/official-subagent-prompt.js +56 -13
- package/dist/core/update/update-migration-state/retired-local-decision.js +203 -0
- package/dist/core/update/update-migration-state.js +6 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +6 -3
- package/dist/native/sks-menubar/Sources/LocalDecisionModels.swift +166 -0
- package/dist/native/sks-menubar/Sources/LocalDecisionViewController.swift +200 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +4 -0
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
- package/dist/scripts/current-surface-update-e2e-check.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function percentile(values, p) {
|
|
2
|
+
const sorted = values.filter((value) => Number.isFinite(value)).sort((left, right) => left - right);
|
|
3
|
+
if (!sorted.length)
|
|
4
|
+
return null;
|
|
5
|
+
const rank = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
|
|
6
|
+
return sorted[rank] ?? null;
|
|
7
|
+
}
|
|
8
|
+
export const EVALUATION_SCHEMA = 'sks.jev-evaluation.v1';
|
|
9
|
+
export function buildEvaluationReport(input) {
|
|
10
|
+
const unavailable = [];
|
|
11
|
+
const rows = [...input.rows];
|
|
12
|
+
if (rows.length === 0)
|
|
13
|
+
unavailable.push('no_labeled_tasks');
|
|
14
|
+
const success = rows.map((row) => row.success).filter((value) => value !== null);
|
|
15
|
+
const escaped = rows.map((row) => row.escapedFailure).filter((value) => value !== null);
|
|
16
|
+
const wall = rows.map((row) => row.wallClockMs).filter((value) => Number.isFinite(value));
|
|
17
|
+
const overhead = rows.map((row) => row.jevOverheadMs).filter((value) => Number.isFinite(value));
|
|
18
|
+
const tokenKnown = rows.every((row) => row.tokenEvidence === 'provider_response');
|
|
19
|
+
if (!tokenKnown)
|
|
20
|
+
unavailable.push('token_savings_unavailable');
|
|
21
|
+
const inputTokens = tokenKnown
|
|
22
|
+
? sum(rows.map((row) => row.inputTokens))
|
|
23
|
+
: null;
|
|
24
|
+
const outputTokens = tokenKnown
|
|
25
|
+
? sum(rows.map((row) => row.outputTokens))
|
|
26
|
+
: null;
|
|
27
|
+
const cost = rows.some((row) => row.reportedCost === null)
|
|
28
|
+
? null
|
|
29
|
+
: sum(rows.map((row) => row.reportedCost));
|
|
30
|
+
if (cost === null)
|
|
31
|
+
unavailable.push('cost_unavailable');
|
|
32
|
+
const successes = rows.filter((row) => row.success === true).length;
|
|
33
|
+
const costPerSuccess = cost === null || successes === 0 ? null : cost / successes;
|
|
34
|
+
const coverage = rows.filter((row) => row.coveragePreserved).length;
|
|
35
|
+
return {
|
|
36
|
+
schema: EVALUATION_SCHEMA,
|
|
37
|
+
evidenceLevel: input.evidenceLevel || 'synthetic',
|
|
38
|
+
live: input.live === true,
|
|
39
|
+
arms: [...new Set(rows.map((row) => row.arm))],
|
|
40
|
+
taskCount: rows.length,
|
|
41
|
+
metrics: {
|
|
42
|
+
taskSuccessRate: success.length ? success.filter(Boolean).length / success.length : null,
|
|
43
|
+
escapedFailureRate: escaped.length ? escaped.filter(Boolean).length / escaped.length : null,
|
|
44
|
+
wallClockP50Ms: percentile(wall, 50),
|
|
45
|
+
wallClockP95Ms: percentile(wall, 95),
|
|
46
|
+
jevOverheadP50Ms: percentile(overhead, 50),
|
|
47
|
+
llmRejudgeCalls: rows.reduce((sumValue, row) => sumValue + row.llmRejudgeCalls, 0),
|
|
48
|
+
inputTokens,
|
|
49
|
+
outputTokens,
|
|
50
|
+
reportedCost: cost,
|
|
51
|
+
costPerSuccessfulTask: costPerSuccess,
|
|
52
|
+
coveragePreservedRate: rows.length ? coverage / rows.length : null
|
|
53
|
+
},
|
|
54
|
+
unavailableReasons: [...unavailable],
|
|
55
|
+
receipts: [...(input.receipts || [])]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function sum(values) {
|
|
59
|
+
if (values.some((value) => value === null || !Number.isFinite(value)))
|
|
60
|
+
return null;
|
|
61
|
+
return values.reduce((total, value) => total + (value || 0), 0);
|
|
62
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { sha256 } from '../fsx.js';
|
|
2
|
+
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
3
|
+
import { jevEnabled, readDecisionConfig } from './config.js';
|
|
4
|
+
import { requestOpenRouterDecision } from './openrouter.js';
|
|
5
|
+
import { compileDecision } from './policy.js';
|
|
6
|
+
import { buildDecisionBundle, validatePlanCoverage } from './questions.js';
|
|
7
|
+
import { applyRecoveryEffect, listProductionRecoveryCandidates } from './recovery.js';
|
|
8
|
+
import { assembleRoutingSelection, buildRoutingCandidates } from './routing.js';
|
|
9
|
+
import { buildDecisionReceipt } from './receipt.js';
|
|
10
|
+
import { applyOptionalContextSelection, graphFileDigest, hydrateContextCandidates, sourceSnapshotDigest } from './state.js';
|
|
11
|
+
import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE } from './types.js';
|
|
12
|
+
let testOverrides = null;
|
|
13
|
+
const appliedEffects = new Set();
|
|
14
|
+
export function setDecisionTestOverrides(overrides) {
|
|
15
|
+
testOverrides = overrides;
|
|
16
|
+
}
|
|
17
|
+
function activeOverrides() {
|
|
18
|
+
return process.env.SKS_JEV_DECISION_TEST_OVERRIDES === '1' ? testOverrides : null;
|
|
19
|
+
}
|
|
20
|
+
export async function decideOfficialSubagentPreparation(input) {
|
|
21
|
+
const started = Date.now();
|
|
22
|
+
try {
|
|
23
|
+
return await decideOfficialSubagentPreparationInner(input, started);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof Error && error.name === 'AssertionError')
|
|
27
|
+
throw error;
|
|
28
|
+
return keep(input, 'transport_error', started);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function decideOfficialSubagentPreparationInner(input, started) {
|
|
32
|
+
const overrides = activeOverrides();
|
|
33
|
+
const env = input.env || process.env;
|
|
34
|
+
const config = overrides?.config ?? await readDecisionConfig(env);
|
|
35
|
+
const baseline = keep(input, 'off', started);
|
|
36
|
+
if (!jevEnabled(config)) {
|
|
37
|
+
overrides?.observe?.({ mode: config.mode, eligible: false, compiled: baseline.compiled, receipt: baseline.receipt });
|
|
38
|
+
return baseline;
|
|
39
|
+
}
|
|
40
|
+
const resolved = await resolveOpenRouterApiKey({ env });
|
|
41
|
+
if (!resolved.key) {
|
|
42
|
+
const missing = keep(input, 'missing_key', started);
|
|
43
|
+
overrides?.observe?.({ mode: config.mode, eligible: false, compiled: missing.compiled, receipt: missing.receipt });
|
|
44
|
+
return missing;
|
|
45
|
+
}
|
|
46
|
+
const contextCandidates = config.capabilities.context.ready
|
|
47
|
+
? await hydrateContextCandidates(input.root, input.attention, input.changedPaths || [])
|
|
48
|
+
: [];
|
|
49
|
+
const planCandidates = config.capabilities.plan.ready && input.requestedSource === 'automatic'
|
|
50
|
+
? buildAutomaticPlanCandidates(input)
|
|
51
|
+
: [];
|
|
52
|
+
const routingCandidates = input.requestedSource === 'automatic'
|
|
53
|
+
? buildRoutingCandidates({ roles: input.routingRoles || [] })
|
|
54
|
+
: [];
|
|
55
|
+
const recoveryCandidates = config.capabilities.recovery.ready
|
|
56
|
+
? [...listProductionRecoveryCandidates()]
|
|
57
|
+
: [];
|
|
58
|
+
const optionalContext = contextCandidates.filter((row) => !row.pinned && row.excerpt);
|
|
59
|
+
const eligible = planCandidates.length > 1 || routingCandidates.length > 0 || optionalContext.length > 0 || recoveryCandidates.length > 0;
|
|
60
|
+
if (!eligible) {
|
|
61
|
+
const compiled = keep(input, 'no_alternative', started);
|
|
62
|
+
overrides?.observe?.({ mode: config.mode, eligible: false, compiled: compiled.compiled, receipt: compiled.receipt });
|
|
63
|
+
return compiled;
|
|
64
|
+
}
|
|
65
|
+
const sourceDigest = await sourceSnapshotDigest(input.root);
|
|
66
|
+
const graphDigest = input.attention?.snapshot_hash || await graphFileDigest(input.root);
|
|
67
|
+
const bundle = buildDecisionBundle({
|
|
68
|
+
projectId: sha256(input.root).slice(0, 32),
|
|
69
|
+
workflowRunId: input.workflowRunId,
|
|
70
|
+
workflowRevision: input.workflowRevision,
|
|
71
|
+
sourceDigest,
|
|
72
|
+
graphDigest,
|
|
73
|
+
goal: input.goal,
|
|
74
|
+
planCandidates,
|
|
75
|
+
contextCandidates,
|
|
76
|
+
recoveryCandidates,
|
|
77
|
+
routingCandidates,
|
|
78
|
+
baselinePlanId: planCandidates[0]?.id ?? null,
|
|
79
|
+
requiredSliceIds: planCandidates[0]?.sliceIds ?? input.slices.map((slice) => slice.id),
|
|
80
|
+
requiredVerificationIds: ['review_coverage']
|
|
81
|
+
});
|
|
82
|
+
const fetchImpl = input.fetchImpl || overrides?.fetchImpl;
|
|
83
|
+
const transport = await requestOpenRouterDecision(bundle, {
|
|
84
|
+
env,
|
|
85
|
+
deadlineMs: input.deadlineMs ?? DESIGN_DEFAULTS.deadlineMs,
|
|
86
|
+
...(fetchImpl ? { fetchImpl } : {}),
|
|
87
|
+
...(input.signal ? { signal: input.signal } : {})
|
|
88
|
+
});
|
|
89
|
+
const compiled = transport.ok
|
|
90
|
+
? compileDecision(bundle, transport.response)
|
|
91
|
+
: {
|
|
92
|
+
kind: 'keep_baseline',
|
|
93
|
+
binding: bundle.binding,
|
|
94
|
+
reason: transport.reason,
|
|
95
|
+
usage: transport.usage
|
|
96
|
+
};
|
|
97
|
+
const applied = compiled.kind === 'apply'
|
|
98
|
+
? applyEffects(input.attention, contextCandidates, planCandidates, compiled)
|
|
99
|
+
: {
|
|
100
|
+
attention: input.attention,
|
|
101
|
+
selectedPlan: null,
|
|
102
|
+
selectedRouting: null,
|
|
103
|
+
selectedContextIds: null,
|
|
104
|
+
consumptionEvidence: null
|
|
105
|
+
};
|
|
106
|
+
const receipt = buildDecisionReceipt({
|
|
107
|
+
binding: bundle.binding,
|
|
108
|
+
compiled,
|
|
109
|
+
result: compiled.kind === 'apply' ? 'applied' : 'kept_baseline',
|
|
110
|
+
reason: compiled.kind === 'apply' ? compiled.effects.map((effect) => effect.kind).join(',') : compiled.reason,
|
|
111
|
+
elapsedMs: Date.now() - started,
|
|
112
|
+
cacheHit: transport.ok && transport.cacheHit,
|
|
113
|
+
resolvedModel: transport.ok ? transport.resolvedModel : null,
|
|
114
|
+
responseId: transport.ok ? transport.responseId : null,
|
|
115
|
+
consumptionEvidence: applied.consumptionEvidence,
|
|
116
|
+
env
|
|
117
|
+
});
|
|
118
|
+
const result = {
|
|
119
|
+
compiled,
|
|
120
|
+
receipt,
|
|
121
|
+
attention: applied.attention,
|
|
122
|
+
selectedPlan: applied.selectedPlan,
|
|
123
|
+
selectedRouting: applied.selectedRouting,
|
|
124
|
+
selectedContextIds: applied.selectedContextIds,
|
|
125
|
+
llmRejudgeCalls: 0,
|
|
126
|
+
bundle
|
|
127
|
+
};
|
|
128
|
+
overrides?.observe?.({ mode: config.mode, eligible: true, compiled, receipt });
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
export function effectAlreadyConsumed(identity) {
|
|
132
|
+
return appliedEffects.has(identity);
|
|
133
|
+
}
|
|
134
|
+
export function markEffectConsumed(identity) {
|
|
135
|
+
if (appliedEffects.has(identity))
|
|
136
|
+
return false;
|
|
137
|
+
if (appliedEffects.size >= 256) {
|
|
138
|
+
const oldest = appliedEffects.values().next().value;
|
|
139
|
+
if (oldest !== undefined)
|
|
140
|
+
appliedEffects.delete(oldest);
|
|
141
|
+
}
|
|
142
|
+
appliedEffects.add(identity);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
export async function dispatchBoundRecovery(input) {
|
|
146
|
+
if (!markEffectConsumed(input.consumptionIdentity)) {
|
|
147
|
+
return { invoked: false, reason: 'duplicate_delivery', consumptionEvidence: null };
|
|
148
|
+
}
|
|
149
|
+
const result = await applyRecoveryEffect(input);
|
|
150
|
+
if (!result.ok)
|
|
151
|
+
return { invoked: false, reason: result.reason, consumptionEvidence: null };
|
|
152
|
+
return { invoked: true, reason: 'applied', consumptionEvidence: result.consumptionEvidence };
|
|
153
|
+
}
|
|
154
|
+
function applyEffects(attention, contextCandidates, planCandidates, compiled) {
|
|
155
|
+
let nextAttention = attention;
|
|
156
|
+
let selectedPlan = null;
|
|
157
|
+
let selectedContextIds = null;
|
|
158
|
+
const evidence = [];
|
|
159
|
+
const routingEffects = [];
|
|
160
|
+
for (const effect of compiled.effects) {
|
|
161
|
+
if (effect.kind === 'select_optional_context') {
|
|
162
|
+
nextAttention = applyOptionalContextSelection(attention, contextCandidates, effect.keepIds);
|
|
163
|
+
selectedContextIds = effect.keepIds;
|
|
164
|
+
evidence.push(`context:${effect.keepIds.join(',')}`);
|
|
165
|
+
}
|
|
166
|
+
if (effect.kind === 'select_plan') {
|
|
167
|
+
selectedPlan = planCandidates.find((row) => row.id === effect.planId) || null;
|
|
168
|
+
if (selectedPlan)
|
|
169
|
+
evidence.push(`plan:${selectedPlan.id}`);
|
|
170
|
+
}
|
|
171
|
+
if (effect.kind === 'select_routing') {
|
|
172
|
+
routingEffects.push({ roleId: effect.roleId, model: effect.model });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const selectedRouting = assembleRoutingSelection(routingEffects);
|
|
176
|
+
if (selectedRouting) {
|
|
177
|
+
evidence.push(...Object.entries(selectedRouting.models).map(([roleId, model]) => `routing:${roleId}=${model}`));
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
attention: nextAttention,
|
|
181
|
+
selectedPlan,
|
|
182
|
+
selectedRouting,
|
|
183
|
+
selectedContextIds,
|
|
184
|
+
consumptionEvidence: evidence.length ? evidence.join('|') : null
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function buildAutomaticPlanCandidates(input) {
|
|
188
|
+
const slices = input.slices.filter((slice) => slice.id);
|
|
189
|
+
const sliceIds = slices.map((slice) => slice.id);
|
|
190
|
+
const workerCount = Math.max(1, input.requestedSubagents);
|
|
191
|
+
if (workerCount < 2)
|
|
192
|
+
return [];
|
|
193
|
+
const verification = ['review_coverage'];
|
|
194
|
+
const covered = sliceIds.length > 0 ? sliceIds : ['parent_owned_decomposition'];
|
|
195
|
+
const baselineGroups = slices.length > 0
|
|
196
|
+
? slices.map((slice, index) => ({
|
|
197
|
+
id: `G${index + 1}`,
|
|
198
|
+
sliceIds: [slice.id],
|
|
199
|
+
role: slice.agent || (slice.kind === 'expert' ? 'expert' : 'worker'),
|
|
200
|
+
effort: slice.kind === 'expert' ? 'max' : 'low'
|
|
201
|
+
}))
|
|
202
|
+
: [{ id: 'G1', sliceIds: covered, role: 'expert', effort: 'max' }];
|
|
203
|
+
const baseline = {
|
|
204
|
+
id: 'baseline',
|
|
205
|
+
summary: sliceIds.length > 0
|
|
206
|
+
? `Execute every required slice with ${workerCount} permitted workers.`
|
|
207
|
+
: `Execute the automatic parent-owned work with ${workerCount} permitted workers. The parent still decomposes slices.`,
|
|
208
|
+
sliceIds: covered,
|
|
209
|
+
groups: baselineGroups,
|
|
210
|
+
workerCount,
|
|
211
|
+
requiredVerificationIds: verification
|
|
212
|
+
};
|
|
213
|
+
const grouped = {
|
|
214
|
+
id: 'grouped',
|
|
215
|
+
summary: sliceIds.length > 0
|
|
216
|
+
? 'Execute every required slice in one permitted worker group, preserving full coverage.'
|
|
217
|
+
: 'Execute the automatic parent-owned work in one permitted worker group. The parent still decomposes slices.',
|
|
218
|
+
sliceIds: covered,
|
|
219
|
+
groups: [{ id: 'G1', sliceIds: covered, role: 'expert', effort: 'max' }],
|
|
220
|
+
workerCount: 1,
|
|
221
|
+
requiredVerificationIds: verification
|
|
222
|
+
};
|
|
223
|
+
const candidates = [baseline, grouped].filter((candidate) => validatePlanCoverage(covered, candidate).length === 0);
|
|
224
|
+
const workerCounts = new Set(candidates.map((candidate) => candidate.workerCount));
|
|
225
|
+
if (candidates.length < 2 || workerCounts.size < 2)
|
|
226
|
+
return [];
|
|
227
|
+
return candidates;
|
|
228
|
+
}
|
|
229
|
+
function keep(input, reason, started) {
|
|
230
|
+
const compiled = {
|
|
231
|
+
kind: 'keep_baseline',
|
|
232
|
+
binding: {
|
|
233
|
+
projectId: sha256(input.root).slice(0, 32),
|
|
234
|
+
workflowRunId: input.workflowRunId,
|
|
235
|
+
workflowRevision: input.workflowRevision,
|
|
236
|
+
sourceDigest: 'uncomputed',
|
|
237
|
+
graphDigest: input.attention?.snapshot_hash ?? null,
|
|
238
|
+
candidateDigest: 'none',
|
|
239
|
+
questionDigest: 'none',
|
|
240
|
+
policyRevision: POLICY_REVISION,
|
|
241
|
+
requestedModel: DESIGN_DEFAULTS.model
|
|
242
|
+
},
|
|
243
|
+
reason,
|
|
244
|
+
usage: { ...UNKNOWN_USAGE }
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
compiled,
|
|
248
|
+
receipt: buildDecisionReceipt({
|
|
249
|
+
binding: compiled.binding,
|
|
250
|
+
compiled,
|
|
251
|
+
result: 'kept_baseline',
|
|
252
|
+
reason,
|
|
253
|
+
elapsedMs: Date.now() - started
|
|
254
|
+
}),
|
|
255
|
+
attention: input.attention,
|
|
256
|
+
selectedPlan: null,
|
|
257
|
+
selectedRouting: null,
|
|
258
|
+
selectedContextIds: null,
|
|
259
|
+
llmRejudgeCalls: 0,
|
|
260
|
+
bundle: null
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { officialSubagentLifecycleLockHeld } from '../subagents/official-subagent-lock.js';
|
|
2
|
+
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
3
|
+
import { redactOpenRouterSecrets, redactOpenRouterString } from '../security/redact-secrets.js';
|
|
4
|
+
import { decodeUsage, decodeWireResponse } from './policy.js';
|
|
5
|
+
import { DESIGN_DEFAULTS, UNKNOWN_USAGE } from './types.js';
|
|
6
|
+
export const OPENROUTER_DECISIONS_ENDPOINT = DESIGN_DEFAULTS.endpoint;
|
|
7
|
+
export const OPENROUTER_DECISIONS_MODEL = DESIGN_DEFAULTS.model;
|
|
8
|
+
const inflight = { count: 0 };
|
|
9
|
+
const circuit = {
|
|
10
|
+
failures: 0,
|
|
11
|
+
openedAt: 0,
|
|
12
|
+
halfOpen: false
|
|
13
|
+
};
|
|
14
|
+
const memo = new Map();
|
|
15
|
+
export function resetDecisionTransportState() {
|
|
16
|
+
inflight.count = 0;
|
|
17
|
+
circuit.failures = 0;
|
|
18
|
+
circuit.openedAt = 0;
|
|
19
|
+
circuit.halfOpen = false;
|
|
20
|
+
memo.clear();
|
|
21
|
+
}
|
|
22
|
+
export async function requestOpenRouterDecision(bundle, options = {}) {
|
|
23
|
+
if (officialSubagentLifecycleLockHeld()) {
|
|
24
|
+
return fail('transport_error', 'fetch_while_lifecycle_lock_held', null);
|
|
25
|
+
}
|
|
26
|
+
const request = bundle.request;
|
|
27
|
+
const encoded = encodeRequest(request);
|
|
28
|
+
if (!encoded.ok)
|
|
29
|
+
return fail(encoded.reason, encoded.detail, null);
|
|
30
|
+
if (inflight.count >= DESIGN_DEFAULTS.maxInFlight) {
|
|
31
|
+
return fail('busy', 'max_in_flight', null);
|
|
32
|
+
}
|
|
33
|
+
const now = options.now || Date.now;
|
|
34
|
+
const circuitState = inspectCircuit(now());
|
|
35
|
+
if (circuitState === 'open')
|
|
36
|
+
return fail('circuit_open', 'circuit_open', null);
|
|
37
|
+
const memoKey = encoded.body;
|
|
38
|
+
const cached = memo.get(memoKey);
|
|
39
|
+
if (cached) {
|
|
40
|
+
const decoded = decodeWireResponse(bundle, cached.response);
|
|
41
|
+
if (decoded.ok) {
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
response: decoded.response,
|
|
45
|
+
usage: {
|
|
46
|
+
inputTokens: decoded.response.usage.input_tokens,
|
|
47
|
+
outputTokens: decoded.response.usage.output_tokens,
|
|
48
|
+
reportedCost: decoded.response.usage.cost === undefined ? null : decoded.response.usage.cost,
|
|
49
|
+
evidence: 'provider_response'
|
|
50
|
+
},
|
|
51
|
+
resolvedModel: decoded.response.model,
|
|
52
|
+
responseId: decoded.response.id || null,
|
|
53
|
+
cacheHit: true,
|
|
54
|
+
status: 200
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const resolved = await resolveOpenRouterApiKey({ env: options.env || process.env });
|
|
59
|
+
if (!resolved.key)
|
|
60
|
+
return fail('missing_key', resolved.blockers.join(',') || 'missing_openrouter_key', null);
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const parent = options.signal;
|
|
63
|
+
const onAbort = () => controller.abort(parent?.reason);
|
|
64
|
+
if (parent) {
|
|
65
|
+
if (parent.aborted)
|
|
66
|
+
controller.abort(parent.reason);
|
|
67
|
+
else
|
|
68
|
+
parent.addEventListener('abort', onAbort, { once: true });
|
|
69
|
+
}
|
|
70
|
+
const deadlineMs = options.deadlineMs ?? DESIGN_DEFAULTS.deadlineMs;
|
|
71
|
+
const timer = setTimeout(() => controller.abort(Object.assign(new Error('deadline'), { name: 'TimeoutError' })), deadlineMs);
|
|
72
|
+
inflight.count += 1;
|
|
73
|
+
try {
|
|
74
|
+
const fetchImpl = options.fetchImpl || globalThis.fetch;
|
|
75
|
+
const response = await fetchImpl(OPENROUTER_DECISIONS_ENDPOINT, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
redirect: 'error',
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${resolved.key}`,
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
Accept: 'application/json'
|
|
82
|
+
},
|
|
83
|
+
body: encoded.body,
|
|
84
|
+
signal: controller.signal
|
|
85
|
+
});
|
|
86
|
+
const status = response.status;
|
|
87
|
+
const raw = await readBoundedBody(response, DESIGN_DEFAULTS.maxResponseBytes, controller.signal);
|
|
88
|
+
if (!raw.ok)
|
|
89
|
+
return recordFailure(status, raw.reason, raw.detail, now());
|
|
90
|
+
if (status === 401)
|
|
91
|
+
return recordAuthFailure('unauthorized', status, raw.text);
|
|
92
|
+
if (status === 402)
|
|
93
|
+
return recordAuthFailure('payment_required', status, raw.text);
|
|
94
|
+
if (status === 403)
|
|
95
|
+
return recordAuthFailure(privacyUnavailable(raw.text) ? 'privacy_unavailable' : 'unauthorized', status, raw.text);
|
|
96
|
+
if (status === 404)
|
|
97
|
+
return recordAuthFailure('capability_unavailable', status, raw.text);
|
|
98
|
+
if (status === 413)
|
|
99
|
+
return recordAuthFailure('budget_exceeded', status, raw.text);
|
|
100
|
+
if (status === 429)
|
|
101
|
+
return recordFailure(status, 'rate_limited', raw.text, now());
|
|
102
|
+
if (status >= 500)
|
|
103
|
+
return recordFailure(status, 'transport_error', raw.text, now());
|
|
104
|
+
if (status !== 200)
|
|
105
|
+
return recordFailure(status, 'transport_error', raw.text, now());
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(raw.text);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return recordFailure(status, 'invalid_response', 'malformed_json', now());
|
|
112
|
+
}
|
|
113
|
+
const decoded = decodeWireResponse(bundle, parsed);
|
|
114
|
+
if (!decoded.ok)
|
|
115
|
+
return recordFailure(status, decoded.reason, 'schema_rejected', now());
|
|
116
|
+
remember(memoKey, decoded.response);
|
|
117
|
+
circuit.failures = 0;
|
|
118
|
+
circuit.halfOpen = false;
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
response: decoded.response,
|
|
122
|
+
usage: {
|
|
123
|
+
inputTokens: decoded.response.usage.input_tokens,
|
|
124
|
+
outputTokens: decoded.response.usage.output_tokens,
|
|
125
|
+
reportedCost: decoded.response.usage.cost === undefined ? null : decoded.response.usage.cost,
|
|
126
|
+
evidence: 'provider_response'
|
|
127
|
+
},
|
|
128
|
+
resolvedModel: decoded.response.model,
|
|
129
|
+
responseId: decoded.response.id || null,
|
|
130
|
+
cacheHit: false,
|
|
131
|
+
status: 200
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const aborted = controller.signal.aborted || (error instanceof Error && error.name === 'AbortError');
|
|
136
|
+
const timeout = error instanceof Error && (error.name === 'TimeoutError' || /deadline/i.test(error.message));
|
|
137
|
+
if (timeout)
|
|
138
|
+
return recordFailure(null, 'deadline', 'deadline', now(), UNKNOWN_USAGE);
|
|
139
|
+
if (aborted)
|
|
140
|
+
return fail('cancelled', 'cancelled', null, UNKNOWN_USAGE);
|
|
141
|
+
if (isRedirectError(error))
|
|
142
|
+
return fail('transport_error', 'redirect_refused', null);
|
|
143
|
+
return recordFailure(null, 'transport_error', error instanceof Error ? error.message : String(error), now());
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
if (parent)
|
|
148
|
+
parent.removeEventListener('abort', onAbort);
|
|
149
|
+
inflight.count = Math.max(0, inflight.count - 1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function encodeRequest(request) {
|
|
153
|
+
if (request.model !== DESIGN_DEFAULTS.model) {
|
|
154
|
+
return { ok: false, reason: 'unknown_model', detail: request.model };
|
|
155
|
+
}
|
|
156
|
+
const questions = Object.keys(request.questions);
|
|
157
|
+
if (questions.length === 0)
|
|
158
|
+
return { ok: false, reason: 'invalid_response', detail: 'empty_questions' };
|
|
159
|
+
if (questions.length > DESIGN_DEFAULTS.maxQuestions) {
|
|
160
|
+
return { ok: false, reason: 'budget_exceeded', detail: 'max_questions' };
|
|
161
|
+
}
|
|
162
|
+
if (request.provider.zdr !== true
|
|
163
|
+
|| request.provider.data_collection !== 'deny'
|
|
164
|
+
|| request.provider.allow_fallbacks !== false) {
|
|
165
|
+
return { ok: false, reason: 'privacy_unavailable', detail: 'provider_policy' };
|
|
166
|
+
}
|
|
167
|
+
const body = JSON.stringify({
|
|
168
|
+
model: request.model,
|
|
169
|
+
state: request.state,
|
|
170
|
+
questions: request.questions,
|
|
171
|
+
provider: {
|
|
172
|
+
zdr: true,
|
|
173
|
+
data_collection: 'deny',
|
|
174
|
+
allow_fallbacks: false
|
|
175
|
+
},
|
|
176
|
+
...(request.session_id ? { session_id: request.session_id } : {})
|
|
177
|
+
});
|
|
178
|
+
if (Buffer.byteLength(body, 'utf8') > DESIGN_DEFAULTS.maxRequestBytes) {
|
|
179
|
+
return { ok: false, reason: 'budget_exceeded', detail: 'max_request_bytes' };
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, body };
|
|
182
|
+
}
|
|
183
|
+
export function redactDecisionValue(value, env = process.env) {
|
|
184
|
+
return redactOpenRouterSecrets(value, env);
|
|
185
|
+
}
|
|
186
|
+
export function redactDecisionText(value, env = process.env) {
|
|
187
|
+
return redactOpenRouterString(value, env);
|
|
188
|
+
}
|
|
189
|
+
async function readBoundedBody(response, maxBytes, signal) {
|
|
190
|
+
if (!response.body) {
|
|
191
|
+
const text = await response.text();
|
|
192
|
+
if (Buffer.byteLength(text, 'utf8') > maxBytes) {
|
|
193
|
+
return { ok: false, reason: 'budget_exceeded', detail: 'max_response_bytes' };
|
|
194
|
+
}
|
|
195
|
+
return { ok: true, text };
|
|
196
|
+
}
|
|
197
|
+
const reader = response.body.getReader();
|
|
198
|
+
const chunks = [];
|
|
199
|
+
let size = 0;
|
|
200
|
+
try {
|
|
201
|
+
while (true) {
|
|
202
|
+
if (signal.aborted)
|
|
203
|
+
return { ok: false, reason: 'cancelled', detail: 'cancelled' };
|
|
204
|
+
const { done, value } = await reader.read();
|
|
205
|
+
if (done)
|
|
206
|
+
break;
|
|
207
|
+
if (value) {
|
|
208
|
+
size += value.byteLength;
|
|
209
|
+
if (size > maxBytes) {
|
|
210
|
+
await reader.cancel();
|
|
211
|
+
return { ok: false, reason: 'budget_exceeded', detail: 'max_response_bytes' };
|
|
212
|
+
}
|
|
213
|
+
chunks.push(value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (signal.aborted || (error instanceof Error && error.name === 'AbortError')) {
|
|
219
|
+
return { ok: false, reason: signal.reason instanceof Error && signal.reason.name === 'TimeoutError' ? 'deadline' : 'cancelled', detail: 'body_aborted' };
|
|
220
|
+
}
|
|
221
|
+
return { ok: false, reason: 'transport_error', detail: error instanceof Error ? error.message : String(error) };
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
reader.releaseLock();
|
|
225
|
+
}
|
|
226
|
+
return { ok: true, text: Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString('utf8') };
|
|
227
|
+
}
|
|
228
|
+
function inspectCircuit(now) {
|
|
229
|
+
if (circuit.failures < DESIGN_DEFAULTS.circuitFailures)
|
|
230
|
+
return 'closed';
|
|
231
|
+
if (now - circuit.openedAt < DESIGN_DEFAULTS.circuitOpenMs)
|
|
232
|
+
return 'open';
|
|
233
|
+
if (circuit.halfOpen)
|
|
234
|
+
return 'open';
|
|
235
|
+
circuit.halfOpen = true;
|
|
236
|
+
return 'half_open';
|
|
237
|
+
}
|
|
238
|
+
function recordFailure(status, reason, detail, now, usage = UNKNOWN_USAGE) {
|
|
239
|
+
circuit.failures += 1;
|
|
240
|
+
if (circuit.failures >= DESIGN_DEFAULTS.circuitFailures && circuit.openedAt === 0) {
|
|
241
|
+
circuit.openedAt = now;
|
|
242
|
+
}
|
|
243
|
+
if (circuit.halfOpen && reason !== 'circuit_open') {
|
|
244
|
+
circuit.openedAt = now;
|
|
245
|
+
circuit.halfOpen = false;
|
|
246
|
+
}
|
|
247
|
+
return fail(reason, detail, status, usage);
|
|
248
|
+
}
|
|
249
|
+
function recordAuthFailure(reason, status, detail) {
|
|
250
|
+
return fail(reason, detail, status);
|
|
251
|
+
}
|
|
252
|
+
function fail(reason, detail, status, usage = UNKNOWN_USAGE) {
|
|
253
|
+
return { ok: false, reason, usage, status, detail, cacheHit: false };
|
|
254
|
+
}
|
|
255
|
+
function remember(key, response) {
|
|
256
|
+
if (memo.size >= DESIGN_DEFAULTS.memoEntries) {
|
|
257
|
+
const oldest = memo.keys().next().value;
|
|
258
|
+
if (oldest !== undefined)
|
|
259
|
+
memo.delete(oldest);
|
|
260
|
+
}
|
|
261
|
+
memo.set(key, { response, storedAt: Date.now() });
|
|
262
|
+
}
|
|
263
|
+
function privacyUnavailable(text) {
|
|
264
|
+
return /zdr|data_collection|allow_fallbacks|privacy|data collection/i.test(text);
|
|
265
|
+
}
|
|
266
|
+
function isRedirectError(error) {
|
|
267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
+
return /redirect/i.test(message);
|
|
269
|
+
}
|
|
270
|
+
export { decodeUsage };
|