lynkr 9.14.14 → 9.14.16
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/config/difficulty-anchors.json +12 -0
- package/package.json +2 -2
- package/scripts/harvest-followup-eval.js +87 -0
- package/src/api/router.js +344 -39
- package/src/cache/embeddings.js +30 -3
- package/src/clients/cursor-utils.js +791 -0
- package/src/clients/databricks.js +136 -4
- package/src/clients/health-probe.js +10 -0
- package/src/config/index.js +10 -0
- package/src/orchestrator/index.js +8 -0
- package/src/orchestrator/sse-transformer.js +37 -3
- package/src/routing/affinity-store.js +136 -1
- package/src/routing/capabilities.js +6 -0
- package/src/routing/complexity-analyzer.js +8 -4
- package/src/routing/harness-envelope.js +71 -0
- package/src/routing/index.js +98 -13
- package/src/routing/intent-score.js +108 -75
- package/src/routing/jev-router.js +131 -25
- package/src/routing/model-registry.js +6 -3
- package/src/routing/model-tiers.js +2 -0
- package/src/routing/passthrough-route.js +20 -1
- package/src/routing/risk-analyzer.js +8 -1
- package/src/routing/shortfall.js +12 -6
- package/src/routing/task-ledger.js +443 -0
- package/src/routing/telemetry.js +44 -2
- package/src/routing/vision.js +1 -1
|
@@ -28,5 +28,17 @@
|
|
|
28
28
|
"design a horizontally scalable architecture for this service with failure-mode analysis",
|
|
29
29
|
"Compare how databricks.js and openai-format.js each handle non-2xx upstream responses, then propose a unified error-normalization helper both could share",
|
|
30
30
|
"Code review the PR #84 routing hardening changes"
|
|
31
|
+
],
|
|
32
|
+
"frontier": [
|
|
33
|
+
"Prove the correctness of this lock-free queue implementation and identify any ABA hazards",
|
|
34
|
+
"Design a novel conflict-free replicated data type for collaborative rich-text editing and argue its convergence from first principles",
|
|
35
|
+
"Derive the optimal cache eviction policy for this access distribution and prove its competitive ratio",
|
|
36
|
+
"Diagnose this heisenbug: a race that only reproduces under load across three services, given these interleaved logs",
|
|
37
|
+
"Design a Byzantine-fault-tolerant consensus protocol variant that tolerates f faults with 2f+1 replicas by weakening liveness, and analyze exactly which guarantees are lost",
|
|
38
|
+
"Formally verify that this state machine can never deadlock, or produce a counterexample trace",
|
|
39
|
+
"Given these profiler traces, determine whether the tail latency is queueing-theoretic or GC-driven, and prove which by constructing a discriminating experiment",
|
|
40
|
+
"Reason through the security of this key-rotation scheme under an adversary who can observe but not modify traffic, and find the weakest assumption it relies on",
|
|
41
|
+
"Work out the exact memory ordering constraints this concurrent hashmap needs on ARM, and justify each barrier from the C++ memory model",
|
|
42
|
+
"Given this failing distributed transaction trace, determine whether the anomaly is write skew or lost update, and design the minimal isolation-level change that eliminates it"
|
|
31
43
|
]
|
|
32
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lynkr",
|
|
3
|
-
"version": "9.14.
|
|
3
|
+
"version": "9.14.16",
|
|
4
4
|
"files": [
|
|
5
5
|
"index.js",
|
|
6
6
|
"install.sh",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"start:supervised": "while true; do node index.js 2>&1 | npx pino-pretty --sync; code=$?; echo \"[supervisor] lynkr exited ($code) — restarting in 3s\"; sleep 3; done",
|
|
39
39
|
"lint": "eslint src index.js",
|
|
40
40
|
"test": "npm run test:unit && npm run test:performance",
|
|
41
|
-
"test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js test/jev-router.test.js test/jev-routing.test.js test/force-patterns.test.js test/upstream-fidelity.test.js test/tool-schema-compression.test.js test/passthrough-route.test.js test/quota-ledger.test.js",
|
|
41
|
+
"test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js test/harness-envelope.test.js test/task-ledger.test.js test/jev-router.test.js test/jev-routing.test.js test/force-patterns.test.js test/upstream-fidelity.test.js test/tool-schema-compression.test.js test/passthrough-route.test.js test/quota-ledger.test.js",
|
|
42
42
|
"test:memory": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js",
|
|
43
43
|
"test:new-features": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
|
|
44
44
|
"test:performance": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Harvest short follow-up turns from routing telemetry for the TaskBand
|
|
4
|
+
* continuation-detector eval set.
|
|
5
|
+
*
|
|
6
|
+
* Reads <cwd>/.lynkr/telemetry.db (read-only — never mutates production
|
|
7
|
+
* telemetry), groups rows by session ordered by timestamp, and emits every
|
|
8
|
+
* turn whose request_text is ≤200 chars: short asks are where "do the same
|
|
9
|
+
* here" follow-ups live, and where single-message scoring is blindest.
|
|
10
|
+
* Each line carries up to 3 prior asks from the same session so a labeler
|
|
11
|
+
* can judge continuation vs. new task without replaying the session.
|
|
12
|
+
*
|
|
13
|
+
* Output: data/followup-eval-unlabeled.jsonl (gitignored), one JSON object
|
|
14
|
+
* per line: {session_id, ts, request_text, prior_asks, chosen_tier,
|
|
15
|
+
* complexity_score, label: null} — label is filled in by hand.
|
|
16
|
+
*
|
|
17
|
+
* Usage: node scripts/harvest-followup-eval.js
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
|
|
23
|
+
const MAX_ASK_CHARS = 200;
|
|
24
|
+
const MAX_PRIOR_ASKS = 3;
|
|
25
|
+
const OUT_FILE = path.join(__dirname, "../data/followup-eval-unlabeled.jsonl");
|
|
26
|
+
|
|
27
|
+
let Database;
|
|
28
|
+
try {
|
|
29
|
+
Database = require("better-sqlite3");
|
|
30
|
+
} catch {
|
|
31
|
+
console.error("better-sqlite3 is not installed (it is an optionalDependency) — cannot read telemetry.db.");
|
|
32
|
+
console.error("Install it with: npm install better-sqlite3");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function main() {
|
|
37
|
+
const dbPath = path.join(process.cwd(), ".lynkr", "telemetry.db");
|
|
38
|
+
if (!fs.existsSync(dbPath)) {
|
|
39
|
+
console.error(`No telemetry database at ${dbPath} — run the proxy from this directory first to collect telemetry.`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
44
|
+
|
|
45
|
+
const rows = db
|
|
46
|
+
.prepare(
|
|
47
|
+
`SELECT session_id, timestamp, request_text, tier, complexity_score
|
|
48
|
+
FROM routing_telemetry
|
|
49
|
+
WHERE session_id IS NOT NULL AND request_text IS NOT NULL AND request_text != ''
|
|
50
|
+
ORDER BY session_id, timestamp ASC`
|
|
51
|
+
)
|
|
52
|
+
.all();
|
|
53
|
+
db.close();
|
|
54
|
+
|
|
55
|
+
const out = [];
|
|
56
|
+
let sessions = 0;
|
|
57
|
+
let prevSession = null;
|
|
58
|
+
let priorAsks = [];
|
|
59
|
+
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
if (row.session_id !== prevSession) {
|
|
62
|
+
prevSession = row.session_id;
|
|
63
|
+
priorAsks = [];
|
|
64
|
+
sessions++;
|
|
65
|
+
}
|
|
66
|
+
const text = String(row.request_text);
|
|
67
|
+
if (text.length <= MAX_ASK_CHARS) {
|
|
68
|
+
out.push(JSON.stringify({
|
|
69
|
+
session_id: row.session_id,
|
|
70
|
+
ts: row.timestamp,
|
|
71
|
+
request_text: text,
|
|
72
|
+
prior_asks: priorAsks.slice(-MAX_PRIOR_ASKS),
|
|
73
|
+
chosen_tier: row.tier ?? null,
|
|
74
|
+
complexity_score: row.complexity_score ?? null,
|
|
75
|
+
label: null,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
priorAsks.push(text);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
|
|
82
|
+
fs.writeFileSync(OUT_FILE, out.length ? out.join("\n") + "\n" : "");
|
|
83
|
+
console.log(`Scanned ${rows.length} rows across ${sessions} sessions.`);
|
|
84
|
+
console.log(`Wrote ${OUT_FILE}: ${out.length} candidate follow-up turns (label: null — awaiting hand labels).`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main();
|
package/src/api/router.js
CHANGED
|
@@ -10,6 +10,7 @@ const providersRouter = require("./providers-handler");
|
|
|
10
10
|
const claudeDesktopGatewayRouter = require("./claude-desktop-gateway");
|
|
11
11
|
const { getRoutingHeaders, getRoutingStats, analyzeComplexity, getModelTierSelector, analyzeRisk, checkSessionPin, writeSessionPin, checkPinScoreDrift } = require("../routing");
|
|
12
12
|
const { resolveTierForModelId } = require("../routing/model-slots");
|
|
13
|
+
const { stripHarnessEnvelope } = require("../routing/harness-envelope");
|
|
13
14
|
|
|
14
15
|
// Upstream streams can die without a clean end (reader.read() never
|
|
15
16
|
// resolves on a dropped socket), hanging the client forever. Every
|
|
@@ -44,6 +45,24 @@ const router = express.Router();
|
|
|
44
45
|
|
|
45
46
|
const rateLimiter = createRateLimiter();
|
|
46
47
|
|
|
48
|
+
// TaskBand holdout: a deterministic slice of sessions never receives the
|
|
49
|
+
// continuation floor, so telemetry keeps a running baseline cohort proving
|
|
50
|
+
// floored sessions aren't quietly doing worse. FNV-1a bucketing mirrors
|
|
51
|
+
// deescalator.isHeldOut; the percentage is task-ledger's hardcoded
|
|
52
|
+
// HOLDOUT_PCT (no env knob by design).
|
|
53
|
+
function _taskbandHoldout(sessionKey, pct) {
|
|
54
|
+
if (!sessionKey) return false;
|
|
55
|
+
const cut = Math.round(Math.max(0, Math.min(1, Number(pct) || 0)) * 100);
|
|
56
|
+
if (cut === 0) return false;
|
|
57
|
+
let hash = 0x811c9dc5;
|
|
58
|
+
const s = String(sessionKey);
|
|
59
|
+
for (let i = 0; i < s.length; i++) {
|
|
60
|
+
hash ^= s.charCodeAt(i);
|
|
61
|
+
hash = Math.imul(hash, 0x01000193);
|
|
62
|
+
}
|
|
63
|
+
return ((hash >>> 0) % 100) < cut;
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
/**
|
|
48
67
|
* Decide which tier/provider/model handles an OAuth-subscription request.
|
|
49
68
|
*
|
|
@@ -170,45 +189,25 @@ async function pickTierByIntent(body) {
|
|
|
170
189
|
};
|
|
171
190
|
}
|
|
172
191
|
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
let text = '';
|
|
178
|
-
if (typeof msg.content === 'string') text = msg.content;
|
|
179
|
-
else if (Array.isArray(msg.content)) {
|
|
180
|
-
text = msg.content
|
|
181
|
-
.filter((b) => b?.type === 'text' && typeof b.text === 'string')
|
|
182
|
-
.map((b) => b.text)
|
|
183
|
-
.join(' ');
|
|
184
|
-
}
|
|
185
|
-
return stripReminders(text).replace(/\s+/g, ' ').trim().slice(0, max);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// Conversation context for the difficulty classifier: the nearest prior
|
|
189
|
-
// assistant reply and the user turn before it. A bare follow-up ("Who
|
|
190
|
-
// kills him ?") is unclassifiable in isolation — the single-message
|
|
192
|
+
// Task-ledger context for the difficulty judge: a slim stable string
|
|
193
|
+
// (task anchor + last ask) folded from the PREFIX of the thread — the ask
|
|
194
|
+
// being scored never feeds its own context. A bare follow-up ("Who kills
|
|
195
|
+
// him ?") is unclassifiable in isolation — the single-message
|
|
191
196
|
// intentPayload below is context-blind BY DESIGN (envelope invariance),
|
|
192
197
|
// so the context travels as a separate condensed string, never as extra
|
|
193
|
-
// messages that would pollute the anchor score.
|
|
194
|
-
|
|
198
|
+
// messages that would pollute the anchor score. The ledger's prefix memo
|
|
199
|
+
// keeps the per-index derivation O(1) per new ask.
|
|
200
|
+
let _taskLedgerMod = null;
|
|
201
|
+
try { _taskLedgerMod = require("../routing/task-ledger"); } catch { /* context degrades to null */ }
|
|
202
|
+
const _ledgerBefore = (msg) => {
|
|
203
|
+
if (!_taskLedgerMod) return null;
|
|
195
204
|
const idx = messages.indexOf(msg);
|
|
196
205
|
if (idx <= 0) return null;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!assistantText && m?.role === 'assistant') assistantText = _condenseText(m);
|
|
202
|
-
else if (assistantText && m?.role === 'user') {
|
|
203
|
-
priorUserText = _condenseText(m);
|
|
204
|
-
if (priorUserText) break;
|
|
205
|
-
}
|
|
206
|
+
try {
|
|
207
|
+
return _taskLedgerMod.deriveTaskLedger(messages.slice(0, idx));
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
206
210
|
}
|
|
207
|
-
if (!assistantText && !priorUserText) return null;
|
|
208
|
-
const parts = [];
|
|
209
|
-
if (priorUserText) parts.push(`user asked: """${priorUserText}"""`);
|
|
210
|
-
if (assistantText) parts.push(`assistant replied about: """${assistantText}"""`);
|
|
211
|
-
return parts.join(' → ');
|
|
212
211
|
};
|
|
213
212
|
|
|
214
213
|
// Per-message scoring intentionally omits _sessionId so session affinity
|
|
@@ -223,6 +222,11 @@ async function pickTierByIntent(body) {
|
|
|
223
222
|
for (let i = 0; i < windowUserMsgs.length; i++) {
|
|
224
223
|
const age = windowUserMsgs.length - 1 - i; // 0 = latest, length-1 = oldest in window
|
|
225
224
|
const cleaned = cleanMsg(windowUserMsgs[i]);
|
|
225
|
+
const _msgLedger = _ledgerBefore(windowUserMsgs[i]);
|
|
226
|
+
let _msgContext = null;
|
|
227
|
+
try {
|
|
228
|
+
_msgContext = _msgLedger ? _taskLedgerMod.buildTaskContext(_msgLedger) : null;
|
|
229
|
+
} catch { /* context degrades to null */ }
|
|
226
230
|
const intentPayload = {
|
|
227
231
|
messages: cleaned ? [cleaned] : [],
|
|
228
232
|
tools: intentTools,
|
|
@@ -230,10 +234,17 @@ async function pickTierByIntent(body) {
|
|
|
230
234
|
// agentic detector inside determineProviderSmart can subtract the
|
|
231
235
|
// harness's baseline tools during intent scoring too.
|
|
232
236
|
_clientProfile: body?._clientProfile || null,
|
|
233
|
-
// Classifier context (see
|
|
234
|
-
// the outbound chokepoint like every internal field.
|
|
235
|
-
_conversationContext:
|
|
237
|
+
// Classifier context + memo identity (see _ledgerBefore). Underscored
|
|
238
|
+
// — stripped at the outbound chokepoint like every internal field.
|
|
239
|
+
_conversationContext: _msgContext,
|
|
240
|
+
_taskAnchorHash: _msgLedger?.anchorHash || null,
|
|
236
241
|
};
|
|
242
|
+
if (age === 0) {
|
|
243
|
+
// Continuation detection ran ONCE in the handler, on the latest typed
|
|
244
|
+
// ask only — windowed history messages must never carry these.
|
|
245
|
+
if (body?._isContinuation) intentPayload._isContinuation = true;
|
|
246
|
+
if (body?._inheritedFloorTier) intentPayload._inheritedFloorTier = body._inheritedFloorTier;
|
|
247
|
+
}
|
|
237
248
|
try {
|
|
238
249
|
const decision = await determineProviderSmart(intentPayload, {
|
|
239
250
|
workspace: body?._workspace || null,
|
|
@@ -359,7 +370,9 @@ async function handleOauthPassthrough(req, res, opts = {}) {
|
|
|
359
370
|
// Strip Lynkr's internal underscore-prefixed fields (_sessionId,
|
|
360
371
|
// _forceProvider, _tierModel, _tierName, _forcedMethod, _baseTier,
|
|
361
372
|
// _escalationSource, _pinnedRoute, _switchReason, _clientProfile,
|
|
362
|
-
// _workspace, _tenantPolicy, _deadlineMs, _suggestionModeModel
|
|
373
|
+
// _workspace, _tenantPolicy, _deadlineMs, _suggestionModeModel,
|
|
374
|
+
// _conversationContext, _taskAnchorHash, _isContinuation,
|
|
375
|
+
// _inheritedFloorTier, _taskband). Anthropic
|
|
363
376
|
// rejects unknown top-level keys with "Extra inputs are not permitted".
|
|
364
377
|
// The orchestrator's downstream paths whitelist their outbound bodies,
|
|
365
378
|
// but the passthrough sends this body VERBATIM — so we must strip here.
|
|
@@ -664,6 +677,7 @@ async function handleOauthPassthrough(req, res, opts = {}) {
|
|
|
664
677
|
message_count: req.body?.messages?.length || null,
|
|
665
678
|
tool_count: Array.isArray(req.body?.tools) ? req.body.tools.length : 0,
|
|
666
679
|
...tlm.jevFields(tier),
|
|
680
|
+
...tlm.taskbandFields(tier),
|
|
667
681
|
// Inner-decision attribution: the tier label alone can't explain a
|
|
668
682
|
// serve (a COMPLEX label once served Haiku with no trace because the
|
|
669
683
|
// tier object carried a null model). Record the decision's own
|
|
@@ -1180,6 +1194,96 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1180
1194
|
req.body._sessionId = req.sessionId;
|
|
1181
1195
|
}
|
|
1182
1196
|
|
|
1197
|
+
// TaskBand — fold the thread (minus the current ask) into a task ledger
|
|
1198
|
+
// and run continuation detection ONCE, on the latest typed ask only.
|
|
1199
|
+
// Stamped on the body so pin-drift checks, the per-message intent loop
|
|
1200
|
+
// and the difficulty judge all score from identical inputs. Best-effort
|
|
1201
|
+
// throughout: any failure leaves routing exactly as before TaskBand.
|
|
1202
|
+
let _taskLedger = null;
|
|
1203
|
+
let _taskCont = null;
|
|
1204
|
+
let _taskFloorTier = null; // would-be continuation floor (tier name)
|
|
1205
|
+
let _taskFloorHeldOut = false; // holdout cohort: floor logged, not applied
|
|
1206
|
+
let _taskPreFloorTier = null; // fresh tier before the floor raised it
|
|
1207
|
+
let _continuationFloored = false;
|
|
1208
|
+
let _taskSideRequest = false;
|
|
1209
|
+
let _jevStatsBefore = null;
|
|
1210
|
+
try { _jevStatsBefore = require("../routing/jev-router").getJevCacheStats(); } catch { /* stamp stays null */ }
|
|
1211
|
+
try {
|
|
1212
|
+
const taskLedgerMod = require("../routing/task-ledger");
|
|
1213
|
+
const { cleanUserText } = require("../routing/jev-router");
|
|
1214
|
+
const _msgs = Array.isArray(req.body?.messages) ? req.body.messages : [];
|
|
1215
|
+
let _askIdx = -1;
|
|
1216
|
+
let _askText = null;
|
|
1217
|
+
for (let i = _msgs.length - 1; i >= 0; i--) {
|
|
1218
|
+
if (_msgs[i]?.role !== 'user') continue;
|
|
1219
|
+
const t = cleanUserText(_msgs[i]);
|
|
1220
|
+
if (t) { _askIdx = i; _askText = t; break; }
|
|
1221
|
+
}
|
|
1222
|
+
if (_askIdx > 0) _taskLedger = taskLedgerMod.deriveTaskLedger(_msgs.slice(0, _askIdx));
|
|
1223
|
+
if (_taskLedger) {
|
|
1224
|
+
const _ctx = taskLedgerMod.buildTaskContext(_taskLedger);
|
|
1225
|
+
if (_ctx) req.body._conversationContext = _ctx;
|
|
1226
|
+
if (_taskLedger.anchorHash) req.body._taskAnchorHash = _taskLedger.anchorHash;
|
|
1227
|
+
const _embedFn = async (t) => {
|
|
1228
|
+
try {
|
|
1229
|
+
return await require("../routing/knn-router").getKnnRouter().embed(t);
|
|
1230
|
+
} catch { return null; }
|
|
1231
|
+
};
|
|
1232
|
+
_taskCont = await taskLedgerMod.detectContinuation(_askText, _taskLedger, _embedFn);
|
|
1233
|
+
if (_taskCont?.isContinuation) req.body._isContinuation = true;
|
|
1234
|
+
}
|
|
1235
|
+
const {
|
|
1236
|
+
FLOOR_ENABLED = true,
|
|
1237
|
+
DECAY_TURNS = 2,
|
|
1238
|
+
HOLDOUT_PCT = 0.10,
|
|
1239
|
+
} = taskLedgerMod;
|
|
1240
|
+
if (FLOOR_ENABLED && _taskCont?.isContinuation && _taskLedger?.anchorHash) {
|
|
1241
|
+
const TIER_ORDER = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
|
|
1242
|
+
let effIdx = null;
|
|
1243
|
+
let coldFallback = false;
|
|
1244
|
+
const _taskState = req.body._sessionId
|
|
1245
|
+
? require("../routing/affinity-store").getTaskState(req.body._sessionId)
|
|
1246
|
+
: null;
|
|
1247
|
+
if (_taskState && _taskState.anchorHash === _taskLedger.anchorHash
|
|
1248
|
+
&& Number.isInteger(_taskState.effectiveBand)) {
|
|
1249
|
+
effIdx = Math.max(0, Math.min(3, _taskState.effectiveBand));
|
|
1250
|
+
} else if (_taskLedger.anchorText) {
|
|
1251
|
+
// COLD fallback: the task predates its state row (restart, TTL,
|
|
1252
|
+
// anchor change). Anchor-only score of the task's first ask,
|
|
1253
|
+
// capped at MEDIUM — an unproven inheritance must not re-derive
|
|
1254
|
+
// an expensive band from a single string.
|
|
1255
|
+
const { scoreIntent } = require("../routing/intent-score");
|
|
1256
|
+
const anchorIntent = await scoreIntent(
|
|
1257
|
+
{ messages: [{ role: 'user', content: _taskLedger.anchorText }] },
|
|
1258
|
+
{ skipClassifier: true },
|
|
1259
|
+
);
|
|
1260
|
+
if (anchorIntent && Number.isFinite(anchorIntent.score)) {
|
|
1261
|
+
const t = getModelTierSelector().getTier(anchorIntent.score);
|
|
1262
|
+
effIdx = Math.min(TIER_ORDER.indexOf(t), TIER_ORDER.indexOf('MEDIUM'));
|
|
1263
|
+
coldFallback = true;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
let floorIdx = null;
|
|
1267
|
+
if (effIdx != null && effIdx >= 0) {
|
|
1268
|
+
if (_taskLedger.open) floorIdx = effIdx;
|
|
1269
|
+
else if ((_taskLedger.turnsSinceClose ?? Infinity) <= DECAY_TURNS) floorIdx = effIdx - 1;
|
|
1270
|
+
}
|
|
1271
|
+
if (floorIdx != null && coldFallback) {
|
|
1272
|
+
floorIdx = Math.min(floorIdx, TIER_ORDER.indexOf('COMPLEX'));
|
|
1273
|
+
}
|
|
1274
|
+
if (floorIdx != null && floorIdx >= 0) {
|
|
1275
|
+
_taskFloorTier = TIER_ORDER[floorIdx];
|
|
1276
|
+
_taskFloorHeldOut = _taskbandHoldout(req.body._sessionId, HOLDOUT_PCT);
|
|
1277
|
+
// Held-out sessions must stay floor-free end to end — the judge's
|
|
1278
|
+
// cap widening counts as an application, so the body stamp is
|
|
1279
|
+
// withheld too, keeping the baseline cohort clean.
|
|
1280
|
+
if (!_taskFloorHeldOut) req.body._inheritedFloorTier = _taskFloorTier;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
logger.debug({ err: err.message }, '[TaskBand] ledger/continuation derivation failed');
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1183
1287
|
// WS1 — sticky-session reuse. If this session already has a valid pin
|
|
1184
1288
|
// (guards pass, no compaction), skip pickTierByIntent entirely and reuse
|
|
1185
1289
|
// the pinned decision. This is the biggest cost win of WS1: repeat turns
|
|
@@ -1306,7 +1410,11 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1306
1410
|
}
|
|
1307
1411
|
return '';
|
|
1308
1412
|
})();
|
|
1413
|
+
// Suggestion-mode detection reads the RAW text (the marker is itself
|
|
1414
|
+
// wrapper text); the force/risk probes below get envelope-stripped text
|
|
1415
|
+
// so Cursor's <user_info>/<rules> blocks can't fire triggers on "Hi".
|
|
1309
1416
|
const isSuggestionMode = _lastUserText.includes('[SUGGESTION MODE:');
|
|
1417
|
+
const _lastUserAskClean = stripHarnessEnvelope(_lastUserText);
|
|
1310
1418
|
// Tool-lessness alone is NOT harness evidence: generic API clients
|
|
1311
1419
|
// (curl, benchmarks, SDKs) legitimately send bare messages and must get
|
|
1312
1420
|
// full routing — live regression 2026-07-08: a benchmark's security-
|
|
@@ -1358,6 +1466,9 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1358
1466
|
|| isBareRequest
|
|
1359
1467
|
|| isFingerprintDrift
|
|
1360
1468
|
|| ((!Array.isArray(req.body?.tools) || req.body.tools.length === 0) && isKnownHarness);
|
|
1469
|
+
// Task state must never see side traffic (hoisted for the upkeep block
|
|
1470
|
+
// at the final-decision site — the model-id-pin branch skips this scope).
|
|
1471
|
+
_taskSideRequest = isSideRequest;
|
|
1361
1472
|
// Side requests short-circuit to the static SIMPLE tier: no pin read
|
|
1362
1473
|
// (a COMPLEX-pinned conversation would burn expensive tokens on
|
|
1363
1474
|
// autocomplete), no pin write, no intent scoring of wrapper text.
|
|
@@ -1429,7 +1540,7 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1429
1540
|
let _pinForceBypass = null;
|
|
1430
1541
|
if (!sideTier && pinCheck.serve && pinCheck.reason === 'guards_passed' && !isSideRequest) {
|
|
1431
1542
|
try {
|
|
1432
|
-
const _probe = { messages: [{ role: 'user', content:
|
|
1543
|
+
const _probe = { messages: [{ role: 'user', content: _lastUserAskClean || '' }] };
|
|
1433
1544
|
const ca = require("../routing/complexity-analyzer");
|
|
1434
1545
|
if (_lastUserText && ca.shouldForceReasoning(_probe)) _pinForceBypass = 'force_reasoning';
|
|
1435
1546
|
else if (_lastUserText && ca.shouldForceCloud(_probe)) _pinForceBypass = 'force_cloud';
|
|
@@ -1521,6 +1632,63 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1521
1632
|
};
|
|
1522
1633
|
}
|
|
1523
1634
|
}
|
|
1635
|
+
// TaskBand continuation floor — fresh decisions only, and only to
|
|
1636
|
+
// RAISE. Placement is deliberate: after the compaction floor so the
|
|
1637
|
+
// two floors compose (max wins), before the pin write so a floored
|
|
1638
|
+
// tier can never fossilize into a session pin (writeSessionPin also
|
|
1639
|
+
// refuses '+continuation_inherit', mirroring the risk skip).
|
|
1640
|
+
if (!isSideRequest && _taskFloorTier && tier?.tier) {
|
|
1641
|
+
try {
|
|
1642
|
+
const { TIER_DEFINITIONS } = require("../routing/model-tiers");
|
|
1643
|
+
const pri = (t) => TIER_DEFINITIONS[t]?.priority ?? -1;
|
|
1644
|
+
const wouldRaise = pri(tier.tier) < pri(_taskFloorTier);
|
|
1645
|
+
if (_taskFloorHeldOut && wouldRaise) {
|
|
1646
|
+
tier.method = (tier.method || 'tier_config') + '+continuation_holdout';
|
|
1647
|
+
logger.debug({
|
|
1648
|
+
sessionId: pinCheck.sessionId,
|
|
1649
|
+
freshTier: tier.tier,
|
|
1650
|
+
wouldFloorTo: _taskFloorTier,
|
|
1651
|
+
evidence: _taskCont?.evidence || [],
|
|
1652
|
+
}, "OAuth intent — continuation floor held out (baseline cohort)");
|
|
1653
|
+
} else if (wouldRaise) {
|
|
1654
|
+
const flooredSel = getModelTierSelector().selectModel(_taskFloorTier, null);
|
|
1655
|
+
if (flooredSel?.provider) {
|
|
1656
|
+
const _fromTier = tier.tier;
|
|
1657
|
+
const _fromModel = tier.model || null;
|
|
1658
|
+
_taskPreFloorTier = _fromTier;
|
|
1659
|
+
logger.info({
|
|
1660
|
+
sessionId: pinCheck.sessionId,
|
|
1661
|
+
freshTier: _fromTier,
|
|
1662
|
+
flooredTo: _taskFloorTier,
|
|
1663
|
+
evidence: _taskCont?.evidence || [],
|
|
1664
|
+
}, "OAuth intent — continuation inherits task band");
|
|
1665
|
+
tier = {
|
|
1666
|
+
...tier,
|
|
1667
|
+
tier: _taskFloorTier,
|
|
1668
|
+
provider: flooredSel.provider,
|
|
1669
|
+
model: flooredSel.model || null,
|
|
1670
|
+
method: (tier.method || 'tier_config') + '+continuation_inherit',
|
|
1671
|
+
switch_reason: tier.switch_reason || 'continuation_inherit',
|
|
1672
|
+
base_tier: tier.base_tier ?? _fromTier,
|
|
1673
|
+
escalation_source: tier.escalation_source ?? 'continuation_inherit',
|
|
1674
|
+
escalations: [
|
|
1675
|
+
...(Array.isArray(tier.escalations) ? tier.escalations : []),
|
|
1676
|
+
{
|
|
1677
|
+
source: 'continuation_inherit',
|
|
1678
|
+
fromTier: _fromTier,
|
|
1679
|
+
toTier: _taskFloorTier,
|
|
1680
|
+
fromModel: _fromModel,
|
|
1681
|
+
toModel: flooredSel.model || null,
|
|
1682
|
+
},
|
|
1683
|
+
],
|
|
1684
|
+
};
|
|
1685
|
+
_continuationFloored = true;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
logger.debug({ err: err.message }, '[TaskBand] continuation floor failed — fresh tier stands');
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1524
1692
|
// Persist the fresh decision so the next turn on this session can
|
|
1525
1693
|
// reuse it. checkSessionPin returned serve=false (or WS1.5 drift fired),
|
|
1526
1694
|
// so either there was no pin, the pin lost a guard (context/vision/
|
|
@@ -1546,6 +1714,104 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1546
1714
|
tier.switch_reason = 'model_pin_drift';
|
|
1547
1715
|
}
|
|
1548
1716
|
|
|
1717
|
+
// TaskBand telemetry stamp — travels on the decision AND (underscored)
|
|
1718
|
+
// on the body, so the forced-provider path can reconstitute it onto the
|
|
1719
|
+
// routingResult that feeds routing_telemetry.
|
|
1720
|
+
try {
|
|
1721
|
+
let _jevCacheHit = null;
|
|
1722
|
+
if (_jevStatsBefore) {
|
|
1723
|
+
const _jevStatsAfter = require("../routing/jev-router").getJevCacheStats();
|
|
1724
|
+
const _missDelta = _jevStatsAfter.misses - _jevStatsBefore.misses;
|
|
1725
|
+
const _hitDelta = _jevStatsAfter.hits - _jevStatsBefore.hits;
|
|
1726
|
+
// Null means the judge never consulted its cache this request
|
|
1727
|
+
// (pin serve, side frame, classifier skip) — "not measured".
|
|
1728
|
+
_jevCacheHit = _missDelta > 0 ? 0 : (_hitDelta > 0 ? 1 : null);
|
|
1729
|
+
}
|
|
1730
|
+
tier.taskband = {
|
|
1731
|
+
isContinuation: _taskCont?.isContinuation ? 1 : 0,
|
|
1732
|
+
inheritedFloor: _taskFloorTier || null,
|
|
1733
|
+
anchorHash: _taskLedger?.anchorHash || null,
|
|
1734
|
+
jevContext: typeof req.body._conversationContext === 'string'
|
|
1735
|
+
? req.body._conversationContext : null,
|
|
1736
|
+
jevCacheHit: _jevCacheHit,
|
|
1737
|
+
};
|
|
1738
|
+
req.body._taskband = tier.taskband;
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
logger.debug({ err: err.message }, '[TaskBand] telemetry stamp failed');
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// TaskBand task-state upkeep — once per request, at the final-decision
|
|
1744
|
+
// site, never inside the per-message scoring loop. Side requests never
|
|
1745
|
+
// touch task state. Pin replays never write: a pinned tier keeps no
|
|
1746
|
+
// provenance of how it was chosen (a force-triggered tier pins like any
|
|
1747
|
+
// scored one), so replaying it must not launder into earned band — the
|
|
1748
|
+
// fresh serve that created the pin already merged. Model-id pins are
|
|
1749
|
+
// explicit client choices, not earned difficulty — no writes either.
|
|
1750
|
+
try {
|
|
1751
|
+
const _sessForTask = req.body._sessionId || null;
|
|
1752
|
+
if (_sessForTask && _taskLedger?.anchorHash && !_taskSideRequest) {
|
|
1753
|
+
const store = require("../routing/affinity-store");
|
|
1754
|
+
let AUTO_CLOSE_LOW_TURNS = 3;
|
|
1755
|
+
try {
|
|
1756
|
+
const _tl = require("../routing/task-ledger");
|
|
1757
|
+
if (Number.isInteger(_tl.AUTO_CLOSE_LOW_TURNS)) AUTO_CLOSE_LOW_TURNS = _tl.AUTO_CLOSE_LOW_TURNS;
|
|
1758
|
+
} catch { /* default stands */ }
|
|
1759
|
+
const TIER_ORDER = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
|
|
1760
|
+
const _method = tier?.method || '';
|
|
1761
|
+
const _openNow = _taskLedger.open ? 1 : 0;
|
|
1762
|
+
const _row = store.getTaskState(_sessForTask);
|
|
1763
|
+
const _anchorMatches = !!(_row && _row.anchorHash === _taskLedger.anchorHash);
|
|
1764
|
+
if (_method !== 'session_pin' && _method !== 'model_id_pin' && _method !== 'fallback') {
|
|
1765
|
+
// Low-streak decay reads the FRESH-scored tier, not the floored
|
|
1766
|
+
// one — otherwise an applied floor could never auto-close.
|
|
1767
|
+
let _freshTier = _taskPreFloorTier || tier?.tier || null;
|
|
1768
|
+
// A jev-override lift rides a cap the inherited floor may have
|
|
1769
|
+
// widened; only the scored base tier is earned band. Merging the
|
|
1770
|
+
// lifted tier would raise the next turn's floor, which widens the
|
|
1771
|
+
// next cap — the ratchet the one-band-up cap exists to break.
|
|
1772
|
+
if (/(^|\+)jev_override(\+|$)/.test(_method)) {
|
|
1773
|
+
const _scoredIdx = TIER_ORDER.indexOf(tier?.base_tier);
|
|
1774
|
+
if (_scoredIdx >= 0 && _scoredIdx < TIER_ORDER.indexOf(_freshTier)) {
|
|
1775
|
+
_freshTier = tier.base_tier;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const _freshIdx = TIER_ORDER.indexOf(_freshTier);
|
|
1779
|
+
const _forceOrRisk = /(^|\+)(force|risk)(\+|$)/.test(_method)
|
|
1780
|
+
|| tier?.escalation_source === 'risk';
|
|
1781
|
+
if (_freshIdx >= 0) {
|
|
1782
|
+
let _lowStreak = 0;
|
|
1783
|
+
if (_anchorMatches && Number.isInteger(_row.effectiveBand)
|
|
1784
|
+
&& _freshIdx <= _row.effectiveBand - 2) {
|
|
1785
|
+
_lowStreak = (_row.lowStreak ?? 0) + 1;
|
|
1786
|
+
}
|
|
1787
|
+
if (_lowStreak >= AUTO_CLOSE_LOW_TURNS) {
|
|
1788
|
+
store.clearTaskState(_sessForTask);
|
|
1789
|
+
} else if (!_continuationFloored && !_forceOrRisk) {
|
|
1790
|
+
// MAX-merge on matching anchor, whole-row overwrite on an
|
|
1791
|
+
// anchor change — both live inside setTaskState.
|
|
1792
|
+
store.setTaskState(_sessForTask, {
|
|
1793
|
+
anchorHash: _taskLedger.anchorHash,
|
|
1794
|
+
effectiveBand: _freshIdx,
|
|
1795
|
+
open: _openNow,
|
|
1796
|
+
lowStreak: _lowStreak,
|
|
1797
|
+
});
|
|
1798
|
+
} else if (_anchorMatches) {
|
|
1799
|
+
// Floored/force/risk serves never merge their band, but the
|
|
1800
|
+
// marker-close and the low streak still have to land.
|
|
1801
|
+
store.setTaskState(_sessForTask, {
|
|
1802
|
+
anchorHash: _row.anchorHash,
|
|
1803
|
+
effectiveBand: Number.isInteger(_row.effectiveBand) ? _row.effectiveBand : _freshIdx,
|
|
1804
|
+
open: _row.open && _openNow ? 1 : 0,
|
|
1805
|
+
lowStreak: _lowStreak,
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
logger.debug({ err: err.message }, '[TaskBand] task-state upkeep failed');
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1549
1815
|
// Subscription-only fork: byte-for-byte passthrough to api.anthropic.com
|
|
1550
1816
|
// when the picked tier resolves to azure-anthropic — FIRST-PARTY
|
|
1551
1817
|
// Anthropic clients only (Claude Code / Claude Desktop wrapping their
|
|
@@ -1622,6 +1888,15 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1622
1888
|
tierMethod: tier?.method || null,
|
|
1623
1889
|
tierPinned: tier?.pinned ?? null,
|
|
1624
1890
|
cacheState: _pinCacheState,
|
|
1891
|
+
// Mid tool-exchange frames must keep serving the in-flight model
|
|
1892
|
+
// (same invariant as the orchestrator's tool_history pin serves).
|
|
1893
|
+
hasToolHistory: (() => {
|
|
1894
|
+
try { return require("../routing/session-affinity").payloadHasToolHistory(req.body); }
|
|
1895
|
+
catch { return false; }
|
|
1896
|
+
})(),
|
|
1897
|
+
// This branch IS the flat-fee subscription: the gate's dollar
|
|
1898
|
+
// break-even leg has no premium to amortize here.
|
|
1899
|
+
flatRate: true,
|
|
1625
1900
|
// Quota pressure shortens the hold horizon inside the gate (high
|
|
1626
1901
|
// sustained burn → descents clear sooner). Zero when the ledger has
|
|
1627
1902
|
// no history — the gate behaves exactly as before.
|
|
@@ -1658,6 +1933,36 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
|
|
|
1658
1933
|
logger.debug({ err: err.message }, '[Routing] Pin-hold restore failed (non-fatal)');
|
|
1659
1934
|
}
|
|
1660
1935
|
}
|
|
1936
|
+
// Upgrades must persist the SERVED model into the pin, mirroring the
|
|
1937
|
+
// pin_hold restore above — the write at the fresh-decision site stored
|
|
1938
|
+
// the pre-rewrite tier model, and a pin that forgets the upgrade lets
|
|
1939
|
+
// later frames descend below what actually served. Unlike pin_hold,
|
|
1940
|
+
// an upgrade usually happens with no prior pin, so the payload comes
|
|
1941
|
+
// from the fresh tier object. Floored (+continuation_inherit) and
|
|
1942
|
+
// risk-lifted tiers stay unpinned (writeSessionPin re-checks, but the
|
|
1943
|
+
// method is overridden to 'session_pin' here, so guard at the source).
|
|
1944
|
+
if (
|
|
1945
|
+
_passthroughRoute.action === 'upgrade'
|
|
1946
|
+
&& _passthroughRoute.model
|
|
1947
|
+
&& pinCheck?.sessionId
|
|
1948
|
+
&& tier?.provider
|
|
1949
|
+
&& !String(tier?.method || '').includes('+continuation_inherit')
|
|
1950
|
+
&& tier?.escalation_source !== 'risk'
|
|
1951
|
+
) {
|
|
1952
|
+
try {
|
|
1953
|
+
const { writeSessionPin } = require("../routing/index");
|
|
1954
|
+
writeSessionPin(pinCheck.sessionId, {
|
|
1955
|
+
provider: tier.provider,
|
|
1956
|
+
model: _passthroughRoute.model,
|
|
1957
|
+
tier: tier.tier,
|
|
1958
|
+
score: tier.score ?? null,
|
|
1959
|
+
method: 'session_pin',
|
|
1960
|
+
reason: 'passthrough_upgrade',
|
|
1961
|
+
}, req.body);
|
|
1962
|
+
} catch (err) {
|
|
1963
|
+
logger.debug({ err: err.message }, '[Routing] Upgrade pin persist failed (non-fatal)');
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1661
1966
|
if (_passthroughRoute.action !== 'verbatim' && _passthroughRoute.model) {
|
|
1662
1967
|
logger.info({
|
|
1663
1968
|
from: _passthroughClientModel,
|