thumbgate 1.27.18 → 1.27.20
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/.claude/commands/dashboard.md +15 -0
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-dashboard.md +15 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +4 -3
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +33 -12
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +246 -30
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/gemini/function-declarations.json +1 -0
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +263 -11
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +1456 -122
- package/bin/dashboard-cli.js +7 -0
- package/bin/postinstall.js +11 -27
- package/commands/dashboard.md +15 -0
- package/commands/thumbgate-dashboard.md +15 -0
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +216 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +31 -2
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +23 -13
- package/config/merge-quality-checks.json +0 -1
- package/config/model-candidates.json +121 -6
- package/config/post-deploy-marketing-pages.json +80 -0
- package/config/tessl-tiles.json +1 -3
- package/openapi/openapi.yaml +12 -0
- package/package.json +225 -100
- package/public/about.html +162 -0
- package/public/agent-manager.html +179 -0
- package/public/agents-cost-savings.html +153 -0
- package/public/ai-malpractice-prevention.html +818 -0
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +28 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +21 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/blog.html +4 -4
- package/public/brand/thumbgate-mark.svg +19 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-enterprise.html +123 -0
- package/public/codex-plugin.html +72 -20
- package/public/compare.html +31 -8
- package/public/dashboard.html +930 -166
- package/public/diagnostic.html +345 -0
- package/public/federal.html +2 -2
- package/public/guide.html +33 -13
- package/public/index.html +469 -111
- package/public/install.html +193 -0
- package/public/js/buyer-intent.js +672 -0
- package/public/learn.html +183 -18
- package/public/lessons.html +168 -10
- package/public/numbers.html +7 -7
- package/public/pricing.html +399 -0
- package/public/pro.html +34 -11
- package/scripts/action-receipts.js +324 -0
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-readiness.js +20 -3
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/audit.js +65 -0
- package/scripts/auto-promote-gates.js +82 -10
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/aws-blocks-guardrails.js +272 -0
- package/scripts/billing.js +93 -1
- package/scripts/bot-detection.js +61 -3
- package/scripts/build-metadata.js +50 -10
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-feedback.js +4 -2
- package/scripts/cli-schema.js +97 -0
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/commercial-offer.js +82 -2
- package/scripts/context-manager.js +74 -6
- package/scripts/dashboard-chat.js +332 -0
- package/scripts/dashboard.js +68 -2
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-loop.js +123 -1
- package/scripts/feedback-quality.js +87 -0
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +1176 -85
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-runtime.js +20 -14
- package/scripts/hook-stop-anti-claim.js +301 -0
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +142 -7
- package/scripts/install-shim.js +87 -0
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/license.js +10 -10
- package/scripts/llm-client.js +169 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/mcp-config.js +7 -1
- package/scripts/mcp-oauth.js +293 -0
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/meta-agent-loop.js +36 -0
- package/scripts/noop-detect.js +285 -0
- package/scripts/operational-integrity.js +39 -5
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plan-gate.js +243 -0
- package/scripts/plausible-domain-config.js +99 -0
- package/scripts/plausible-server-events.js +9 -6
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/rate-limiter.js +64 -13
- package/scripts/repeat-metric.js +137 -0
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-redaction.js +166 -0
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +260 -10
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/self-healing-check.js +193 -0
- package/scripts/self-protection.js +90 -0
- package/scripts/seo-gsd.js +916 -7
- package/scripts/silent-failure-cluster.js +531 -0
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +28 -2
- package/scripts/statusline.sh +20 -4
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/telemetry-analytics.js +357 -0
- package/scripts/thompson-sampling.js +31 -10
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/tool-registry.js +169 -1
- package/scripts/trajectory-scorer.js +63 -0
- package/scripts/vector-store.js +45 -0
- package/scripts/verify-marketing-pages-deployed.js +212 -0
- package/scripts/visitor-journey.js +172 -0
- package/scripts/workflow-sentinel.js +286 -53
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +2683 -319
- package/.claude-plugin/marketplace.json +0 -85
- package/adapters/chatgpt/openapi.yaml +0 -1695
- package/scripts/bot-detector.js +0 -50
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* install-shim.js — Install a stable shim at ~/.thumbgate/bin/thumbgate-hook
|
|
5
|
+
*
|
|
6
|
+
* The shim is a tiny shell script that resolves the cached ThumbGate runtime
|
|
7
|
+
* first, so hook commands in settings.local.json stay stable across projects
|
|
8
|
+
* and agent restarts.
|
|
9
|
+
*
|
|
10
|
+
* The shim checks for a cached runtime binary first (fast path), and falls
|
|
11
|
+
* back to `npx --yes thumbgate@latest` (slow path, first-time self-install).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const SHIM_DIR = path.join(os.homedir(), '.thumbgate', 'bin');
|
|
19
|
+
const SHIM_PATH = path.join(SHIM_DIR, 'thumbgate-hook');
|
|
20
|
+
const RUNTIME_BIN = path.join(os.homedir(), '.thumbgate', 'runtime', 'node_modules', '.bin', 'thumbgate');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The shim script. Key design choices:
|
|
24
|
+
* - Uses `exec` to replace the shell process (no zombie processes)
|
|
25
|
+
* - Fast path: if cached runtime binary exists, exec it directly
|
|
26
|
+
* - Slow path: npx --yes thumbgate@latest (first-time auto-installs)
|
|
27
|
+
* - No default self-mutation: background upgrades are opt-in via
|
|
28
|
+
* THUMBGATE_SHIM_AUTO_UPDATE=1 so source checkouts, enterprise pins, and
|
|
29
|
+
* dogfood runtimes cannot be overwritten by a hook side effect.
|
|
30
|
+
*/
|
|
31
|
+
function shimContent() {
|
|
32
|
+
const escapedRuntimeBin = JSON.stringify(RUNTIME_BIN);
|
|
33
|
+
const escapedRuntimeDir = JSON.stringify(path.join(os.homedir(), '.thumbgate', 'runtime'));
|
|
34
|
+
|
|
35
|
+
return `#!/usr/bin/env bash
|
|
36
|
+
# ThumbGate hook shim — DO NOT EDIT
|
|
37
|
+
# Installed by: thumbgate init
|
|
38
|
+
# Purpose: stable hook entry point that runs the cached ThumbGate runtime
|
|
39
|
+
# Pattern: Volta-style stable shim (see https://volta.sh)
|
|
40
|
+
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
|
|
43
|
+
RUNTIME_BIN=${escapedRuntimeBin}
|
|
44
|
+
RUNTIME_DIR=${escapedRuntimeDir}
|
|
45
|
+
|
|
46
|
+
# Fast path: cached runtime binary exists and is executable
|
|
47
|
+
if [ -x "$RUNTIME_BIN" ]; then
|
|
48
|
+
# Optional background upgrade. Disabled by default so hooks never mutate a
|
|
49
|
+
# source checkout, enterprise pin, or dogfood runtime behind the operator's back.
|
|
50
|
+
if [ "\${THUMBGATE_SHIM_AUTO_UPDATE:-0}" = "1" ]; then
|
|
51
|
+
( nohup npm install --prefix "$RUNTIME_DIR" --no-save --omit=dev thumbgate@latest >/dev/null 2>&1 & ) 2>/dev/null || true
|
|
52
|
+
fi
|
|
53
|
+
exec "$RUNTIME_BIN" "$@"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Slow path: no cached binary — install + exec via npx
|
|
57
|
+
mkdir -p "$RUNTIME_DIR"
|
|
58
|
+
exec npx --yes --package thumbgate@latest -- thumbgate "$@"
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function installShim() {
|
|
63
|
+
fs.mkdirSync(SHIM_DIR, { recursive: true });
|
|
64
|
+
fs.writeFileSync(SHIM_PATH, shimContent(), { mode: 0o755 });
|
|
65
|
+
return SHIM_PATH;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shimInstalled() {
|
|
69
|
+
try {
|
|
70
|
+
return fs.existsSync(SHIM_PATH) && (fs.statSync(SHIM_PATH).mode & 0o111) !== 0;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function shimPath() {
|
|
77
|
+
return SHIM_PATH;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
installShim,
|
|
82
|
+
shimInstalled,
|
|
83
|
+
shimPath,
|
|
84
|
+
shimContent,
|
|
85
|
+
SHIM_DIR,
|
|
86
|
+
SHIM_PATH,
|
|
87
|
+
};
|
|
@@ -18,6 +18,7 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
20
20
|
const { ensureParentDir, readJsonl } = require('./fs-utils');
|
|
21
|
+
const { redactSecretsDeep } = require('./secret-redaction');
|
|
21
22
|
const {
|
|
22
23
|
buildStableId,
|
|
23
24
|
extractFilePaths,
|
|
@@ -137,15 +138,19 @@ function createLesson({ feedbackId, signal, inferredLesson, triggerMessage, prio
|
|
|
137
138
|
// Stable link: dashboard deep-link to this lesson
|
|
138
139
|
lesson.link = `${getLessonBaseUrl()}/lessons#${lesson.id}`;
|
|
139
140
|
|
|
141
|
+
// Redact secrets before persisting — lesson text is derived from conversation content that may
|
|
142
|
+
// have captured a pasted credential. See scripts/secret-redaction.js.
|
|
143
|
+
const redactedLesson = redactSecretsDeep(lesson);
|
|
144
|
+
|
|
140
145
|
const lessonsPath = getLessonsPath();
|
|
141
146
|
ensureParentDir(lessonsPath);
|
|
142
|
-
fs.appendFileSync(lessonsPath, JSON.stringify(
|
|
147
|
+
fs.appendFileSync(lessonsPath, JSON.stringify(redactedLesson) + '\n');
|
|
143
148
|
|
|
144
149
|
// Update recent lesson for statusbar
|
|
145
150
|
const recentPath = getRecentLessonPath();
|
|
146
|
-
fs.writeFileSync(recentPath, JSON.stringify(
|
|
151
|
+
fs.writeFileSync(recentPath, JSON.stringify(redactedLesson, null, 2) + '\n');
|
|
147
152
|
|
|
148
|
-
return
|
|
153
|
+
return redactedLesson;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
/**
|
package/scripts/lesson-search.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { readJSONL, getFeedbackPaths } = require('./feedback-loop');
|
|
5
|
+
const { buildMemoryLifecycleView, scoreHybridMemoryMatch } = require('./agent-memory-lifecycle');
|
|
5
6
|
const { loadOptionalModule } = require('./private-core-boundary');
|
|
6
7
|
|
|
7
8
|
const HIGH_RISK_TAGS = new Set([
|
|
@@ -411,7 +412,8 @@ function scoreLesson(queryText, memory, parsed, sourceFeedback) {
|
|
|
411
412
|
const score = jaccardSimilarity(queryTokens, lessonTokens)
|
|
412
413
|
+ substringBoost(queryText, lessonText)
|
|
413
414
|
+ recencyScore(memory.timestamp)
|
|
414
|
-
+ (memory.category === 'error' ? 0.05 : 0)
|
|
415
|
+
+ (memory.category === 'error' ? 0.05 : 0)
|
|
416
|
+
+ Math.min(0.2, scoreHybridMemoryMatch(queryText, memory).score * 0.1);
|
|
415
417
|
|
|
416
418
|
return {
|
|
417
419
|
score,
|
|
@@ -427,6 +429,7 @@ function buildLessonResult(memory, sourceFeedback, options = {}) {
|
|
|
427
429
|
const { score, matchedTokens } = scoreLesson(options.query || '', memory, parsed, sourceFeedback);
|
|
428
430
|
const harnessRecommendations = buildHarnessRecommendations(memory, parsed, sourceFeedback, ruleMatches, gateMatches);
|
|
429
431
|
const lifecycle = buildLifecycle(memory, parsed, sourceFeedback, ruleMatches, gateMatches, harnessRecommendations);
|
|
432
|
+
const memoryLifecycle = buildMemoryLifecycleView(memory, { query: options.query || '' });
|
|
430
433
|
|
|
431
434
|
return {
|
|
432
435
|
id: memory.id,
|
|
@@ -452,6 +455,7 @@ function buildLessonResult(memory, sourceFeedback, options = {}) {
|
|
|
452
455
|
systemResponse: {
|
|
453
456
|
promotedToMemory: true,
|
|
454
457
|
lifecycle,
|
|
458
|
+
memoryLifecycle,
|
|
455
459
|
diagnosis: memory.diagnosis || null,
|
|
456
460
|
sourceFeedback: sourceFeedback
|
|
457
461
|
? {
|
|
@@ -589,6 +593,14 @@ function tryFts5Search(query, options) {
|
|
|
589
593
|
tags: row.tags,
|
|
590
594
|
importance: row.importance,
|
|
591
595
|
timestamp: row.timestamp,
|
|
596
|
+
memoryLifecycle: buildMemoryLifecycleView({
|
|
597
|
+
title: row.context,
|
|
598
|
+
content: [row.whatWentWrong, row.whatToChange, row.whatWorked].filter(Boolean).join('\n'),
|
|
599
|
+
domain: row.domain,
|
|
600
|
+
tags: row.tags,
|
|
601
|
+
importance: row.importance,
|
|
602
|
+
timestamp: row.timestamp,
|
|
603
|
+
}, { query: query || '' }),
|
|
592
604
|
})),
|
|
593
605
|
backend: 'sqlite-fts5',
|
|
594
606
|
};
|
|
@@ -618,6 +630,10 @@ function formatLessonSearchResults(payload) {
|
|
|
618
630
|
payload.results.forEach((result, index) => {
|
|
619
631
|
lines.push(`${index + 1}. ${result.title}`);
|
|
620
632
|
lines.push(` Category: ${result.category} | Tags: ${result.tags.join(', ') || 'none'} | Score: ${result.score}`);
|
|
633
|
+
if (result.systemResponse.memoryLifecycle) {
|
|
634
|
+
const memoryLifecycle = result.systemResponse.memoryLifecycle;
|
|
635
|
+
lines.push(` Memory: scope=${memoryLifecycle.scope} | decay=${memoryLifecycle.decay.state} | hybrid=${memoryLifecycle.retrievalHints.hybridScore}`);
|
|
636
|
+
}
|
|
621
637
|
if (result.lesson.summary) {
|
|
622
638
|
lines.push(` Lesson: ${result.lesson.summary}`);
|
|
623
639
|
}
|
package/scripts/license.js
CHANGED
|
@@ -23,15 +23,17 @@ function isValidKey(key) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function verifyLicense(options = {}) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// Only ThumbGate's own env vars are license candidates — scanning foreign
|
|
27
|
+
// *_API_KEY / *_PRO_KEY vars would treat another vendor's secret as a
|
|
28
|
+
// license key. The result object never carries the raw key value.
|
|
29
|
+
const envEntry = [
|
|
30
|
+
['THUMBGATE_API_KEY', process.env.THUMBGATE_API_KEY],
|
|
31
|
+
['THUMBGATE_PRO_KEY', process.env.THUMBGATE_PRO_KEY],
|
|
29
32
|
...Object.entries(process.env)
|
|
30
|
-
.filter(([name]) => /(?:_API_KEY|_PRO_KEY)$/.test(name))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return { valid: true, source: 'env', key: envKey };
|
|
33
|
+
.filter(([name]) => name.startsWith('THUMBGATE_') && /(?:_API_KEY|_PRO_KEY)$/.test(name)),
|
|
34
|
+
].find(([, value]) => isValidKey(value));
|
|
35
|
+
if (envEntry) {
|
|
36
|
+
return { valid: true, source: 'env', envVar: envEntry[0] };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const licensePath = getLicensePath(options.homeDir);
|
|
@@ -42,7 +44,6 @@ function verifyLicense(options = {}) {
|
|
|
42
44
|
return {
|
|
43
45
|
valid: true,
|
|
44
46
|
source: 'file',
|
|
45
|
-
key: data.key,
|
|
46
47
|
activatedAt: data.activatedAt,
|
|
47
48
|
path: licensePath,
|
|
48
49
|
};
|
|
@@ -80,7 +81,6 @@ module.exports = {
|
|
|
80
81
|
verifyLicense,
|
|
81
82
|
isProLicensed,
|
|
82
83
|
activateLicense,
|
|
83
|
-
generateLicenseKey,
|
|
84
84
|
isValidKey,
|
|
85
85
|
VALID_PREFIXES,
|
|
86
86
|
LICENSE_PATH,
|
package/scripts/llm-client.js
CHANGED
|
@@ -11,20 +11,23 @@ const MODELS = {
|
|
|
11
11
|
const DEFAULT_MODEL = MODELS.FAST;
|
|
12
12
|
const DEFAULT_MAX_TOKENS = 1024;
|
|
13
13
|
const DEFAULT_CACHE_TTL = '5m';
|
|
14
|
+
const DEFAULT_ZAI_BASE_URL = 'https://api.z.ai/api/paas/v4';
|
|
15
|
+
const DEFAULT_ZAI_MODEL = 'glm-5.2-flash';
|
|
14
16
|
|
|
15
|
-
let
|
|
17
|
+
let _anthropicClient = null;
|
|
18
|
+
let _geminiClient = null;
|
|
16
19
|
|
|
17
20
|
function isAvailable() {
|
|
18
21
|
return Boolean(process.env.ANTHROPIC_API_KEY);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function getClient() {
|
|
22
|
-
if (
|
|
25
|
+
if (_anthropicClient) return _anthropicClient;
|
|
23
26
|
if (!isAvailable()) return null;
|
|
24
27
|
try {
|
|
25
28
|
const Anthropic = require('@anthropic-ai/sdk');
|
|
26
|
-
|
|
27
|
-
return
|
|
29
|
+
_anthropicClient = new Anthropic();
|
|
30
|
+
return _anthropicClient;
|
|
28
31
|
} catch {
|
|
29
32
|
return null;
|
|
30
33
|
}
|
|
@@ -138,7 +141,144 @@ function parseClaudeJson(text) {
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
function getZaiApiKey(env = process.env) {
|
|
145
|
+
return env.ZAI_API_KEY || env.THUMBGATE_ZAI_API_KEY || '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getZaiBaseUrl(env = process.env) {
|
|
149
|
+
return env.ZAI_BASE_URL || env.THUMBGATE_ZAI_BASE_URL || DEFAULT_ZAI_BASE_URL;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getZaiModel(env = process.env) {
|
|
153
|
+
return env.ZAI_API_MODEL || env.THUMBGATE_ZAI_MODEL || DEFAULT_ZAI_MODEL;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function callZaiInternal(options = {}, env = process.env) {
|
|
157
|
+
const apiKey = getZaiApiKey(env);
|
|
158
|
+
if (!apiKey || typeof fetch !== 'function') return null;
|
|
159
|
+
|
|
160
|
+
const messages = Array.isArray(options.messages) && options.messages.length > 0
|
|
161
|
+
? options.messages
|
|
162
|
+
: [
|
|
163
|
+
...(options.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
|
|
164
|
+
{ role: 'user', content: options.userPrompt || '' },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const response = await fetch(`${getZaiBaseUrl(env).replace(/\/$/, '')}/chat/completions`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${apiKey}`,
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
model: options.model || getZaiModel(env),
|
|
176
|
+
messages,
|
|
177
|
+
max_tokens: options.maxTokens || DEFAULT_MAX_TOKENS,
|
|
178
|
+
temperature: Number.isFinite(options.temperature) ? options.temperature : 0,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!response.ok) return null;
|
|
183
|
+
const json = await response.json();
|
|
184
|
+
return {
|
|
185
|
+
text: stripCodeFences(json?.choices?.[0]?.message?.content || ''),
|
|
186
|
+
usage: json?.usage || null,
|
|
187
|
+
stopReason: json?.choices?.[0]?.finish_reason || null,
|
|
188
|
+
id: json?.id || null,
|
|
189
|
+
model: json?.model || options.model || getZaiModel(env),
|
|
190
|
+
};
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function callGeminiInternal(options = {}) {
|
|
197
|
+
const env = process.env;
|
|
198
|
+
const { detectInferenceBackend } = require('./local-model-profile');
|
|
199
|
+
const providerMode = detectInferenceBackend(env).providerMode;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
203
|
+
if (!_geminiClient) {
|
|
204
|
+
if (providerMode === 'vertex') {
|
|
205
|
+
_geminiClient = new GoogleGenAI({
|
|
206
|
+
enterprise: true,
|
|
207
|
+
project: env.VERTEX_PROJECT_ID || 'ai-revenue28-webhook',
|
|
208
|
+
location: env.VERTEX_LOCATION || 'us-central1',
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
_geminiClient = new GoogleGenAI({
|
|
212
|
+
apiKey: env.GEMINI_API_KEY,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const contents = convertMessagesToGemini(options.messages, options.userPrompt);
|
|
218
|
+
const config = {};
|
|
219
|
+
if (options.systemPrompt) {
|
|
220
|
+
config.systemInstruction = options.systemPrompt;
|
|
221
|
+
}
|
|
222
|
+
if (Number.isFinite(options.temperature)) {
|
|
223
|
+
config.temperature = options.temperature;
|
|
224
|
+
}
|
|
225
|
+
if (options.maxTokens) {
|
|
226
|
+
config.maxOutputTokens = options.maxTokens;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const response = await runStep('llm.callGemini', {
|
|
230
|
+
retries: 2,
|
|
231
|
+
logger: (msg) => console.warn(msg),
|
|
232
|
+
}, async () => _geminiClient.models.generateContent({
|
|
233
|
+
model: options.model,
|
|
234
|
+
contents,
|
|
235
|
+
config,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
text: response.text || '',
|
|
240
|
+
usage: response.usageMetadata ? {
|
|
241
|
+
input_tokens: response.usageMetadata.promptTokenCount,
|
|
242
|
+
output_tokens: response.usageMetadata.candidatesTokenCount,
|
|
243
|
+
} : null,
|
|
244
|
+
stopReason: response.candidates?.[0]?.finishReason || null,
|
|
245
|
+
id: null,
|
|
246
|
+
model: options.model,
|
|
247
|
+
};
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error('Gemini/Vertex AI execution error:', err);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function convertMessagesToGemini(messages, userPrompt) {
|
|
255
|
+
const list = Array.isArray(messages) && messages.length > 0
|
|
256
|
+
? messages
|
|
257
|
+
: [{ role: 'user', content: userPrompt }];
|
|
258
|
+
|
|
259
|
+
return list.map((msg) => {
|
|
260
|
+
const role = msg.role === 'assistant' ? 'model' : 'user';
|
|
261
|
+
let text = '';
|
|
262
|
+
if (typeof msg.content === 'string') {
|
|
263
|
+
text = msg.content;
|
|
264
|
+
} else if (Array.isArray(msg.content)) {
|
|
265
|
+
text = msg.content.map((c) => c.text || '').join('');
|
|
266
|
+
} else if (msg.content && typeof msg.content === 'object') {
|
|
267
|
+
text = msg.content.text || JSON.stringify(msg.content);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
role,
|
|
271
|
+
parts: [{ text }],
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
141
276
|
async function callClaudeInternal(options = {}) {
|
|
277
|
+
const modelName = options.model || '';
|
|
278
|
+
if (modelName.startsWith('gemini') || modelName.startsWith('vertex')) {
|
|
279
|
+
return callGeminiInternal(options);
|
|
280
|
+
}
|
|
281
|
+
|
|
142
282
|
const client = getClient();
|
|
143
283
|
if (!client) return null;
|
|
144
284
|
|
|
@@ -193,10 +333,35 @@ async function callClaudeJson(options = {}) {
|
|
|
193
333
|
return parsed;
|
|
194
334
|
}
|
|
195
335
|
|
|
336
|
+
async function callZaiJson(options = {}) {
|
|
337
|
+
const result = await callZaiInternal(options);
|
|
338
|
+
if (!result) return null;
|
|
339
|
+
|
|
340
|
+
const parsed = parseClaudeJson(result.text);
|
|
341
|
+
if (parsed === null) return null;
|
|
342
|
+
|
|
343
|
+
if (options.returnMetadata) {
|
|
344
|
+
return {
|
|
345
|
+
parsed,
|
|
346
|
+
text: result.text,
|
|
347
|
+
usage: result.usage,
|
|
348
|
+
stopReason: result.stopReason,
|
|
349
|
+
id: result.id,
|
|
350
|
+
model: result.model,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return parsed;
|
|
355
|
+
}
|
|
356
|
+
|
|
196
357
|
module.exports = {
|
|
197
358
|
isAvailable,
|
|
198
359
|
callClaude,
|
|
199
360
|
callClaudeJson,
|
|
361
|
+
callZaiJson,
|
|
362
|
+
getZaiApiKey,
|
|
363
|
+
getZaiBaseUrl,
|
|
364
|
+
getZaiModel,
|
|
200
365
|
stripCodeFences,
|
|
201
366
|
parseClaudeJson,
|
|
202
367
|
normalizeCacheOptions,
|
|
@@ -111,7 +111,8 @@ function isSparseAttentionFamily(modelFamily) {
|
|
|
111
111
|
|
|
112
112
|
function resolveProviderMode(env = process.env) {
|
|
113
113
|
const explicit = normalizeSlug(env.THUMBGATE_PROVIDER_MODE || env.THUMBGATE_MODEL_PROVIDER_MODE);
|
|
114
|
-
if (explicit === 'local' || explicit === 'managed') return explicit;
|
|
114
|
+
if (explicit === 'local' || explicit === 'managed' || explicit === 'vertex') return explicit;
|
|
115
|
+
if (env.VERTEX_PROJECT_ID || env.VERTEX_API_ENDPOINT) return 'vertex';
|
|
115
116
|
if (env.THUMBGATE_LOCAL_MODEL_FAMILY || env.THUMBGATE_LOCAL_MODEL_SERVER) return 'local';
|
|
116
117
|
return 'managed';
|
|
117
118
|
}
|
|
@@ -133,6 +134,7 @@ function resolveModelFamily(env = process.env) {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
function buildBackendLabel(providerMode, modelFamily) {
|
|
137
|
+
if (providerMode === 'vertex') return 'Vertex AI secure cloud backend';
|
|
136
138
|
if (providerMode === 'managed') return 'Managed API backend';
|
|
137
139
|
if (modelFamily.startsWith('deepseek')) return 'Local DeepSeek sparse backend';
|
|
138
140
|
if (modelFamily.startsWith('glm')) return 'Local GLM sparse backend';
|
|
@@ -148,14 +150,18 @@ function detectInferenceBackend(env = process.env) {
|
|
|
148
150
|
&& supportsSparseAttention
|
|
149
151
|
&& INDEXCACHE_SERVER_ENGINES.has(serverEngine);
|
|
150
152
|
const indexCacheEnabled = indexCacheEligible && parseBoolean(env.THUMBGATE_INDEXCACHE_ENABLED, false);
|
|
151
|
-
const id = providerMode === '
|
|
152
|
-
? '
|
|
153
|
-
:
|
|
154
|
-
?
|
|
155
|
-
:
|
|
153
|
+
const id = providerMode === 'vertex'
|
|
154
|
+
? 'vertex-api'
|
|
155
|
+
: providerMode === 'managed'
|
|
156
|
+
? 'managed-api'
|
|
157
|
+
: supportsSparseAttention
|
|
158
|
+
? `local-${modelFamily}-sparse`
|
|
159
|
+
: 'local-dense';
|
|
156
160
|
|
|
157
161
|
let rationale = 'Baseline backend with no sparse-attention acceleration.';
|
|
158
|
-
if (providerMode === '
|
|
162
|
+
if (providerMode === 'vertex') {
|
|
163
|
+
rationale = 'Vertex AI secure cloud backend providing compliant enterprise Gemini models inside VPC boundary.';
|
|
164
|
+
} else if (providerMode === 'managed') {
|
|
159
165
|
rationale = 'Managed API path does not expose sparse-attention kernel controls like IndexCache.';
|
|
160
166
|
} else if (indexCacheEnabled) {
|
|
161
167
|
rationale = `Local ${modelFamily} backend is sparse-attention capable and IndexCache-ready on ${serverEngine}.`;
|
|
@@ -336,7 +342,8 @@ function resolveModelRole(role, env) {
|
|
|
336
342
|
const envKey = `THUMBGATE_MODEL_ROLE_${normalized.toUpperCase()}`;
|
|
337
343
|
const modelFamily = resolveModelFamily(e);
|
|
338
344
|
const isLocalGlm = modelFamily.startsWith('glm');
|
|
339
|
-
const
|
|
345
|
+
const providerMode = resolveProviderMode(e);
|
|
346
|
+
const provider = isLocalGlm ? 'local' : (providerMode === 'vertex' ? 'vertex' : 'gemini');
|
|
340
347
|
const defaultModel = isLocalGlm ? (GLM_MODEL_ROLES[normalized] || MODEL_ROLES[normalized]) : MODEL_ROLES[normalized];
|
|
341
348
|
const model = (e[envKey] && String(e[envKey]).trim()) || defaultModel;
|
|
342
349
|
return { role: normalized, model, provider, envKey };
|
package/scripts/mcp-config.js
CHANGED
|
@@ -107,9 +107,15 @@ function portableMcpEntry(pkgVersion) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function codexAutoUpdateCliEntry(commandArgs = []) {
|
|
110
|
+
// Fast-start from the installed runtime binary; only resolve @latest via npx
|
|
111
|
+
// when the runtime is absent (e.g. first launch). This matches the hook
|
|
112
|
+
// commands and must never block MCP server startup on a per-launch reinstall
|
|
113
|
+
// (a blocking `npm install thumbgate@latest` on every launch caused
|
|
114
|
+
// capture/serve timeouts on slow or offline networks — a failed install left
|
|
115
|
+
// the chained `exec` unreached, so the server never started).
|
|
110
116
|
return {
|
|
111
117
|
command: 'sh',
|
|
112
|
-
args: ['-lc', publishedCliShellCommand('latest', commandArgs
|
|
118
|
+
args: ['-lc', publishedCliShellCommand('latest', commandArgs)],
|
|
113
119
|
};
|
|
114
120
|
}
|
|
115
121
|
|