thumbgate 1.35.0 → 1.37.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/.agents/skills/cobble-hot-store-compare-not-clone/SKILL.md +80 -0
- package/.agents/skills/colab-compute-honesty-not-clone/SKILL.md +70 -0
- package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/token-shunt-honesty-not-clone/SKILL.md +67 -0
- package/.agents/skills/typesafe-typed-questions-not-clone/SKILL.md +82 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +465 -3
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +629 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +9 -3
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +6 -0
- package/config/model-candidates.json +312 -29
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +408 -150
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-schema.js +203 -1
- package/scripts/cobble-hot-store-split.js +600 -0
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/colab-compute-honesty.js +281 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard.js +251 -32
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gates-engine.js +826 -68
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/token-shunt-honesty.js +502 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/typesafe-typed-questions.js +813 -0
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +724 -153
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- package/src/security-questionnaire.js +195 -0
|
@@ -32,11 +32,18 @@ function loadConfig() {
|
|
|
32
32
|
const TIERS = {
|
|
33
33
|
nano: { label: 'nano', costMultiplier: 0.1, maxContext: 32000 },
|
|
34
34
|
mini: { label: 'mini', costMultiplier: 0.4, maxContext: 200000 },
|
|
35
|
+
// Cheap OpenAI-compatible cloud flagship (Qwen3.8-Max class) for steady
|
|
36
|
+
// high-output workloads. Inert until its endpoint env vars are configured.
|
|
37
|
+
bulkCloud: { label: 'bulk-cloud', costMultiplier: 0.25, maxContext: 1000000 },
|
|
35
38
|
frontier: { label: 'frontier', costMultiplier: 1.0, maxContext: 1000000 },
|
|
36
39
|
// Self-hosted open-source frontier (e.g. GLM 5.1). Zero marginal cost.
|
|
37
40
|
localFrontier: { label: 'local-frontier', costMultiplier: 0.0, maxContext: 1000000 },
|
|
38
41
|
};
|
|
39
42
|
|
|
43
|
+
// Routing priority for task-type matching. Config-declared tiers missing from
|
|
44
|
+
// this list are appended after, before the frontier fallback semantics apply.
|
|
45
|
+
const TIER_ROUTING_ORDER = ['nano', 'mini', 'bulkCloud', 'frontier'];
|
|
46
|
+
|
|
40
47
|
// ---------------------------------------------------------------------------
|
|
41
48
|
// Task classification → tier mapping
|
|
42
49
|
// ---------------------------------------------------------------------------
|
|
@@ -91,8 +98,10 @@ function classifyTask(task = {}) {
|
|
|
91
98
|
// --- Normal tier routing by task type ---
|
|
92
99
|
|
|
93
100
|
const tiers = config.tiers;
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
const declaredOrder = TIER_ROUTING_ORDER.filter((name) => tiers[name])
|
|
102
|
+
.concat(Object.keys(tiers).filter((name) => !TIER_ROUTING_ORDER.includes(name)));
|
|
103
|
+
for (const tierName of declaredOrder) {
|
|
104
|
+
if (Array.isArray(tiers[tierName].taskTypes) && tiers[tierName].taskTypes.includes(type)) {
|
|
96
105
|
return {
|
|
97
106
|
tier: tierName,
|
|
98
107
|
reason: `task type "${type}" mapped to ${tierName}`,
|
|
@@ -269,6 +278,19 @@ class FrontierBudget {
|
|
|
269
278
|
}
|
|
270
279
|
}
|
|
271
280
|
|
|
281
|
+
function isSwitchyardEnabled(task = {}, env = process.env) {
|
|
282
|
+
const flag = String(env.THUMBGATE_SWITCHYARD || '').trim().toLowerCase();
|
|
283
|
+
if (flag === '1' || flag === 'true' || flag === 'yes' || flag === 'on') return true;
|
|
284
|
+
const tags = Array.isArray(task.tags) ? task.tags.map((t) => String(t).toLowerCase()) : [];
|
|
285
|
+
return Boolean(
|
|
286
|
+
Array.isArray(task.steps) && task.steps.length > 0
|
|
287
|
+
|| tags.includes('switchyard')
|
|
288
|
+
|| tags.includes('multi-model')
|
|
289
|
+
|| tags.includes('nemotron')
|
|
290
|
+
|| tags.includes('always-on'),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
272
294
|
function recommendExecutionPlan(task = {}, env = process.env) {
|
|
273
295
|
const classification = classifyTask(task);
|
|
274
296
|
const inference = recommendInferenceBackend(task, env);
|
|
@@ -280,7 +302,7 @@ function recommendExecutionPlan(task = {}, env = process.env) {
|
|
|
280
302
|
? 'localFrontier'
|
|
281
303
|
: classification.tier;
|
|
282
304
|
|
|
283
|
-
|
|
305
|
+
const plan = {
|
|
284
306
|
architecture: 'risk-aware-model-routing',
|
|
285
307
|
mixtureOfExperts: false,
|
|
286
308
|
tier: effectiveTier,
|
|
@@ -295,6 +317,52 @@ function recommendExecutionPlan(task = {}, env = process.env) {
|
|
|
295
317
|
indexCacheEnabled: inference.backend.indexCacheEnabled,
|
|
296
318
|
reason: `${classification.reason}; ${inference.reason}`,
|
|
297
319
|
};
|
|
320
|
+
|
|
321
|
+
// Optional NeMo Switchyard–style multi-model step plan (opt-in).
|
|
322
|
+
if (isSwitchyardEnabled(task, env)) {
|
|
323
|
+
try {
|
|
324
|
+
const {
|
|
325
|
+
routeAgentSteps,
|
|
326
|
+
buildAlwaysOnAgentPlan,
|
|
327
|
+
} = require('./switchyard-router');
|
|
328
|
+
const steps = Array.isArray(task.steps) && task.steps.length
|
|
329
|
+
? task.steps
|
|
330
|
+
: buildAlwaysOnAgentPlan({
|
|
331
|
+
sensitive: task.privacyRoute === 'local' || task.sensitive,
|
|
332
|
+
highVolume: task.highVolume || task.costPriority === 'primary',
|
|
333
|
+
riskLevel: task.riskLevel,
|
|
334
|
+
actType: task.type,
|
|
335
|
+
});
|
|
336
|
+
const switchyard = routeAgentSteps(steps);
|
|
337
|
+
plan.switchyard = switchyard;
|
|
338
|
+
plan.multiModel = switchyard.multiModel;
|
|
339
|
+
plan.reason = `${plan.reason}; switchyard models=${switchyard.distinctModels.join(',')}`;
|
|
340
|
+
} catch {
|
|
341
|
+
// Switchyard optional; never break baseline routing.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Optional dual-stack cost lane (Qwen volume / Claude quality). Opt-in via
|
|
346
|
+
// THUMBGATE_COST_ROUTE_QWEN=1 or task tags cost-sensitive|high-volume|bulk.
|
|
347
|
+
if (isCostRouteQwenEnabled(task, env)) {
|
|
348
|
+
try {
|
|
349
|
+
const { recommendCostQualitySplit } = require('./qwen38-max-cost-optimizer');
|
|
350
|
+
const split = recommendCostQualitySplit(task, {
|
|
351
|
+
useTokenPlanPromo: String(env.THUMBGATE_QWEN_TOKEN_PLAN_PROMO || '').toLowerCase() === '1'
|
|
352
|
+
|| String(env.THUMBGATE_QWEN_TOKEN_PLAN_PROMO || '').toLowerCase() === 'true',
|
|
353
|
+
localModel: env.THUMBGATE_LOCAL_MODEL || null,
|
|
354
|
+
});
|
|
355
|
+
plan.costQualitySplit = split;
|
|
356
|
+
plan.costLane = split.lane;
|
|
357
|
+
plan.preferredProvider = split.primaryProvider;
|
|
358
|
+
plan.preferredModel = split.primaryModel;
|
|
359
|
+
plan.reason = `${plan.reason}; cost-lane=${split.lane}→${split.primaryProvider}/${split.primaryModel}`;
|
|
360
|
+
} catch {
|
|
361
|
+
// Cost module optional at runtime; never fail routing.
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return plan;
|
|
298
366
|
}
|
|
299
367
|
|
|
300
368
|
// ---------------------------------------------------------------------------
|
|
@@ -371,23 +439,46 @@ function resolveGenerationTarget(plan, task, tierConfig, options, env) {
|
|
|
371
439
|
|
|
372
440
|
const model = modelOverride || tierConfig.modelId;
|
|
373
441
|
if (!model) throw new Error(`missing modelId for tier "${plan.tier}"`);
|
|
374
|
-
|
|
442
|
+
const target = {
|
|
375
443
|
model,
|
|
376
444
|
provider: providerOverride || tierConfig.provider || inferProvider(model, plan.tier),
|
|
377
445
|
};
|
|
446
|
+
if (tierConfig.endpoint && typeof tierConfig.endpoint === 'object') {
|
|
447
|
+
const { baseUrlEnv, apiKeyEnv } = tierConfig.endpoint;
|
|
448
|
+
const baseUrl = baseUrlEnv ? env[baseUrlEnv] : undefined;
|
|
449
|
+
const apiKey = apiKeyEnv ? env[apiKeyEnv] : undefined;
|
|
450
|
+
if (!baseUrl || !apiKey) {
|
|
451
|
+
const missing = [!baseUrl && baseUrlEnv, !apiKey && apiKeyEnv].filter(Boolean).join(', ');
|
|
452
|
+
throw new Error(`tier "${plan.tier}" requires endpoint configuration; set ${missing}`);
|
|
453
|
+
}
|
|
454
|
+
target.endpoint = { baseUrl, apiKey };
|
|
455
|
+
}
|
|
456
|
+
return target;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Estimate the USD cost of a generation for a tier that declares
|
|
461
|
+
* pricingUsdPerMTok in config. Returns null when pricing is not declared.
|
|
462
|
+
*/
|
|
463
|
+
function estimateTierCostUsd(tierName, inputTokens, outputTokens, config = loadConfig()) {
|
|
464
|
+
const pricing = config?.tiers?.[tierName]?.pricingUsdPerMTok;
|
|
465
|
+
if (!pricing || !Number.isFinite(pricing.input) || !Number.isFinite(pricing.output)) return null;
|
|
466
|
+
const inTok = Number.isFinite(inputTokens) ? Math.max(0, inputTokens) : 0;
|
|
467
|
+
const outTok = Number.isFinite(outputTokens) ? Math.max(0, outputTokens) : 0;
|
|
468
|
+
return (inTok * pricing.input + outTok * pricing.output) / 1_000_000;
|
|
378
469
|
}
|
|
379
470
|
|
|
380
471
|
function createOpenAiCompatibleAdapter(options = {}) {
|
|
381
472
|
const env = options.env || process.env;
|
|
382
473
|
const fetchImpl = options.fetchImpl || global.fetch;
|
|
383
474
|
|
|
384
|
-
return async function openAiCompatibleAdapter({ request, model, provider }) {
|
|
475
|
+
return async function openAiCompatibleAdapter({ request, model, provider, endpoint }) {
|
|
385
476
|
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable');
|
|
386
477
|
const local = provider === 'openai-compatible';
|
|
387
|
-
const baseUrl =
|
|
388
|
-
? env.THUMBGATE_LOCAL_MODEL_BASE_URL
|
|
389
|
-
|
|
390
|
-
|
|
478
|
+
const baseUrl = endpoint?.baseUrl
|
|
479
|
+
|| (local ? env.THUMBGATE_LOCAL_MODEL_BASE_URL : (env.OPENAI_BASE_URL || 'https://api.openai.com/v1'));
|
|
480
|
+
const apiKey = endpoint?.apiKey
|
|
481
|
+
|| (local ? env.THUMBGATE_LOCAL_MODEL_API_KEY : env.OPENAI_API_KEY);
|
|
391
482
|
if (!baseUrl) throw new Error('THUMBGATE_LOCAL_MODEL_BASE_URL is required for local routing');
|
|
392
483
|
if (!local && !apiKey) throw new Error('OPENAI_API_KEY is required for OpenAI routing');
|
|
393
484
|
|
|
@@ -443,38 +534,56 @@ async function executeRoutedGeneration(task = {}, request = {}, options = {}) {
|
|
|
443
534
|
const plan = recommendExecutionPlan(task, env);
|
|
444
535
|
const tierConfig = config.tiers[plan.tier];
|
|
445
536
|
if (!tierConfig) throw new Error(`missing configuration for tier "${plan.tier}"`);
|
|
446
|
-
const { model, provider } = resolveGenerationTarget(plan, task, tierConfig, options, env);
|
|
447
|
-
const adapters = options.adapters || createDefaultGenerationAdapters({ env, fetchImpl: options.fetchImpl });
|
|
448
|
-
const adapter = adapters[plan.tier] || adapters[provider];
|
|
449
|
-
if (typeof adapter !== 'function') throw new Error(`no generation adapter registered for provider "${provider}"`);
|
|
450
|
-
|
|
451
537
|
const now = options.now || (() => Date.now());
|
|
452
538
|
const startedAt = now();
|
|
539
|
+
// Resolution can fail (e.g. an unconfigured tier endpoint), so the base event
|
|
540
|
+
// starts from tier config defaults and target resolution happens inside the
|
|
541
|
+
// telemetry try block — misconfiguration emits an error event, not silence.
|
|
453
542
|
const baseEvent = {
|
|
454
543
|
timestamp: new Date().toISOString(),
|
|
455
544
|
architecture: plan.architecture,
|
|
456
545
|
taskType: task.type || 'unknown',
|
|
457
546
|
riskLevel: task.riskLevel || 'unspecified',
|
|
458
547
|
tier: plan.tier,
|
|
459
|
-
provider,
|
|
460
|
-
model,
|
|
548
|
+
provider: tierConfig.provider || null,
|
|
549
|
+
model: tierConfig.modelId || null,
|
|
461
550
|
escalated: plan.escalated,
|
|
462
551
|
routeReason: plan.reason,
|
|
463
552
|
};
|
|
464
553
|
|
|
465
554
|
try {
|
|
466
|
-
const
|
|
555
|
+
const { model, provider, endpoint } = resolveGenerationTarget(plan, task, tierConfig, options, env);
|
|
556
|
+
baseEvent.provider = provider;
|
|
557
|
+
baseEvent.model = model;
|
|
558
|
+
const adapters = options.adapters || createDefaultGenerationAdapters({ env, fetchImpl: options.fetchImpl });
|
|
559
|
+
const adapter = adapters[plan.tier] || adapters[provider];
|
|
560
|
+
if (typeof adapter !== 'function') throw new Error(`no generation adapter registered for provider "${provider}"`);
|
|
561
|
+
const raw = await adapter({ task, request, plan, model, provider, endpoint });
|
|
467
562
|
const result = normalizeGenerationResult(raw, { model, provider });
|
|
563
|
+
const inputTokens = Number(result.usage?.input_tokens || result.usage?.prompt_tokens || 0) || null;
|
|
564
|
+
const outputTokens = Number(result.usage?.output_tokens || result.usage?.completion_tokens || 0) || null;
|
|
565
|
+
// When the adapter reports no cost but the tier declares per-token pricing,
|
|
566
|
+
// derive real cost from usage so telemetry and holdout cost math stay live.
|
|
567
|
+
// Never derive when usage is entirely unmeasured (an unmeasured request is
|
|
568
|
+
// unknown-cost, not free) or when execution actually ran on a local backend
|
|
569
|
+
// (tier pricing describes the cloud endpoint, not local inference).
|
|
570
|
+
let costCents = result.costCents;
|
|
571
|
+
const usageMeasured = inputTokens !== null || outputTokens !== null;
|
|
572
|
+
if ((costCents === null || costCents === undefined) && usageMeasured && plan.providerMode !== 'local') {
|
|
573
|
+
const estimatedUsd = estimateTierCostUsd(plan.tier, inputTokens || 0, outputTokens || 0, config);
|
|
574
|
+
costCents = estimatedUsd === null ? null : Number((estimatedUsd * 100).toFixed(4));
|
|
575
|
+
}
|
|
576
|
+
if (costCents === undefined) costCents = null;
|
|
468
577
|
const event = {
|
|
469
578
|
...baseEvent,
|
|
470
579
|
latencyMs: Math.max(0, now() - startedAt),
|
|
471
|
-
inputTokens
|
|
472
|
-
outputTokens
|
|
473
|
-
costCents
|
|
580
|
+
inputTokens,
|
|
581
|
+
outputTokens,
|
|
582
|
+
costCents,
|
|
474
583
|
outcome: 'success',
|
|
475
584
|
};
|
|
476
585
|
if (options.telemetrySink) await options.telemetrySink(event);
|
|
477
|
-
return { ...result, route: plan, telemetry: event };
|
|
586
|
+
return { ...result, costCents, route: plan, telemetry: event };
|
|
478
587
|
} catch (error) {
|
|
479
588
|
const event = {
|
|
480
589
|
...baseEvent,
|
|
@@ -564,12 +673,30 @@ async function evaluateRoutingHoldout(cases, options = {}) {
|
|
|
564
673
|
// Exports
|
|
565
674
|
// ---------------------------------------------------------------------------
|
|
566
675
|
|
|
676
|
+
function isCostRouteQwenEnabled(task = {}, env = process.env) {
|
|
677
|
+
const flag = String(env.THUMBGATE_COST_ROUTE_QWEN || '').trim().toLowerCase();
|
|
678
|
+
if (flag === '1' || flag === 'true' || flag === 'yes' || flag === 'on') return true;
|
|
679
|
+
const tags = Array.isArray(task.tags) ? task.tags.map((t) => String(t).toLowerCase()) : [];
|
|
680
|
+
return Boolean(
|
|
681
|
+
task.costPriority === 'primary'
|
|
682
|
+
|| task.highVolume
|
|
683
|
+
|| tags.includes('cost-sensitive')
|
|
684
|
+
|| tags.includes('high-volume')
|
|
685
|
+
|| tags.includes('bulk')
|
|
686
|
+
|| tags.includes('qwen-volume'),
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
567
690
|
module.exports = {
|
|
691
|
+
isCostRouteQwenEnabled,
|
|
568
692
|
TIERS,
|
|
693
|
+
TIER_ROUTING_ORDER,
|
|
569
694
|
classifyTask,
|
|
695
|
+
estimateTierCostUsd,
|
|
570
696
|
shouldEscalate,
|
|
571
697
|
FrontierBudget,
|
|
572
698
|
recommendExecutionPlan,
|
|
699
|
+
isSwitchyardEnabled,
|
|
573
700
|
inferProvider,
|
|
574
701
|
normalizeGenerationResult,
|
|
575
702
|
resolveGenerationTarget,
|