vibeoscore 1.0.2 → 1.0.9

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.
Files changed (45) hide show
  1. package/client.js +1 -0
  2. package/client.ts +2 -0
  3. package/lib/logger.js +27 -0
  4. package/mcp-server.js +5 -4
  5. package/mcp-server.ts +4 -3
  6. package/package.json +12 -10
  7. package/dashboard/dist/assets/index-BnPt1Fii.js +0 -1
  8. package/dashboard/dist/assets/index-CfH00tOL.css +0 -1
  9. package/dashboard/dist/index.html +0 -3
  10. package/lib/blackbox-rf.js +0 -1099
  11. package/lib/blackbox.js +0 -137
  12. package/lib/compression.js +0 -119
  13. package/lib/db.js +0 -106
  14. package/lib/db.ts +0 -113
  15. package/lib/delegation.js +0 -137
  16. package/lib/meta-controller.js +0 -418
  17. package/lib/meta-controller.mjs +0 -499
  18. package/lib/patterns.js +0 -150
  19. package/lib/resolution-tracker.js +0 -486
  20. package/lib/stress.js +0 -84
  21. package/lib/tdd.js +0 -218
  22. package/lib/tier-routing.js +0 -48
  23. package/middleware/auth.js +0 -75
  24. package/middleware/auth.ts +0 -87
  25. package/middleware/usage-logging.js +0 -29
  26. package/middleware/usage-logging.ts +0 -41
  27. package/nginx-vibetheog-api.conf +0 -64
  28. package/routes/admin.js +0 -93
  29. package/routes/admin.ts +0 -107
  30. package/routes/blackbox.js +0 -463
  31. package/routes/compression.js +0 -12
  32. package/routes/delegation.js +0 -30
  33. package/routes/patterns.js +0 -53
  34. package/routes/pricing.js +0 -62
  35. package/routes/stress.js +0 -30
  36. package/routes/tdd.js +0 -68
  37. package/routes/tier-routing.js +0 -31
  38. package/scripts/dashboard-server.mjs +0 -246
  39. package/scripts/deploy-zero-downtime.sh +0 -77
  40. package/scripts/deploy.sh +0 -68
  41. package/scripts/release.mjs +0 -30
  42. package/scripts/seed-master-token.js +0 -29
  43. package/scripts/start-all.mjs +0 -34
  44. package/server.js +0 -88
  45. package/vibeos-api.service +0 -19
package/lib/blackbox.js DELETED
@@ -1,137 +0,0 @@
1
- import { ResolutionTracker as SharedResolutionTracker } from "./resolution-tracker.js"
2
-
3
- const SUB_REGIMES = ["INIT", "DIVERGENT", "EXPLORING", "REFINING", "CONVERGING", "CLOSED", "LOOPING"]
4
-
5
- class ResolutionTracker {
6
- constructor(sessionId, projectId, maxHistory = 50) {
7
- this._inner = new SharedResolutionTracker(sessionId, maxHistory)
8
- this.projectId = projectId || null
9
- }
10
-
11
- update(entry) {
12
- const userText = entry.userText || ""
13
- const features = entry.features && typeof entry.features === "object" && !Array.isArray(entry.features)
14
- ? entry.features
15
- : {}
16
- const action = (entry.actions && entry.actions.length > 0) ? entry.actions[0] : (entry.action || "explore")
17
- const entropy = entry.entropy ?? 1.0
18
- const uncertainty = entry.uncertainty ?? 50
19
- const embedding = entry.embedding || null
20
-
21
- const state = this._inner.update(userText, features, action, entropy, uncertainty, embedding)
22
- if (entry.telemetry && typeof entry.telemetry === "object" && !Array.isArray(entry.telemetry)) {
23
- const lastEntry = this._inner.history?.[this._inner.history.length - 1]
24
- if (lastEntry) {
25
- lastEntry.telemetry = {
26
- ...(lastEntry.telemetry || {}),
27
- ...entry.telemetry,
28
- }
29
- }
30
- }
31
- return state
32
- }
33
-
34
- getState() {
35
- const snap = this._inner.snapshot()
36
- return {
37
- sub_regime: snap.sub_regime || "INIT",
38
- resolution: snap.resolution || "unresolved",
39
- momentum: snap.momentum || 0,
40
- signals: snap.signals || {},
41
- intent_state: snap.intent_state || { volatility_score: 0, drift_rate: 0, core_goal_embedding: null },
42
- continuity_state: snap.continuity_state || null,
43
- is_looping: snap.is_looping || false,
44
- loop_consecutive: snap.loop_consecutive || 0,
45
- loop_intervention_level: snap.loop_intervention_level || "none",
46
- pivot_detected: snap.pivot_detected || false,
47
- pivot_score: snap.pivot_score || 0,
48
- outcome: snap.outcome || null,
49
- n_interactions: snap.n_interactions || 0,
50
- loop_count: snap.is_looping ? snap.loop_consecutive || 1 : 0,
51
- turn_count: snap.n_interactions || 0,
52
- history_length: this._inner.getHistory().length,
53
- telemetry: this._inner.getHistory().slice(-1)[0]?.telemetry || null,
54
- }
55
- }
56
-
57
- annotateLastTurn(telemetry) {
58
- if (!telemetry || typeof telemetry !== "object" || Array.isArray(telemetry)) {
59
- return
60
- }
61
- const lastEntry = this._inner.history?.[this._inner.history.length - 1]
62
- if (lastEntry) {
63
- lastEntry.telemetry = {
64
- ...(lastEntry.telemetry || {}),
65
- ...telemetry,
66
- }
67
- }
68
- }
69
-
70
- recordOutcome(outcome) {
71
- this._inner.recordOutcome(outcome)
72
- }
73
-
74
- getLoopIntervention() {
75
- return this._inner.getLoopIntervention()
76
- }
77
-
78
- getPivotDirective() {
79
- return this._inner.getPivotDirective()
80
- }
81
-
82
- setCalibratedWeights(weights) {
83
- this._inner.setCalibratedWeights(weights)
84
- }
85
-
86
- getOutcomeHistory() {
87
- return this._inner.getOutcomeHistory()
88
- }
89
-
90
- reset() {
91
- this._inner.reset()
92
- }
93
-
94
- serialize() {
95
- return this._inner.serialize()
96
- }
97
-
98
- static deserialize(data) {
99
- const tracker = new ResolutionTracker(data.sessionId)
100
- tracker._inner = SharedResolutionTracker.deserialize(data)
101
- return tracker
102
- }
103
- }
104
-
105
- function extractFeatures(text) {
106
- if (!text || typeof text !== "string") return {}
107
- const len = text.length
108
- const words = text.split(/\s+/).filter(w => w.length > 0)
109
- const wordCount = words.length
110
- const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0)
111
- const sentenceCount = sentences.length
112
- const avgWordLen = wordCount > 0 ? words.reduce((s, w) => s + w.length, 0) / wordCount : 0
113
- const questions = (text.match(/\?/g) || []).length
114
- const questionRatio = sentenceCount > 0 ? questions / sentenceCount : 0
115
- const codeBlocks = (text.match(/```/g) || []).length / 2
116
- const urgency = /urgent|asap|immediately|critical|broken|failing|crash|error|bug/i.test(text) ? 1.0 : 0.0
117
- const repetition = wordCount > 5
118
- ? (text.toLowerCase().match(/(\b\w+\b).*?\1/g) || []).length / wordCount
119
- : 0
120
- const sentimentIndicators = /thanks|great|perfect|awesome/i.test(text) ? 0.2
121
- : /frustrat|annoy|not working|doesn't work|stupid|useless/i.test(text) ? 0.8
122
- : 0.5
123
-
124
- return {
125
- length: Math.min(1.0, len / 5000),
126
- word_count: Math.min(1.0, wordCount / 500),
127
- sentence_count: Math.min(1.0, sentenceCount / 50),
128
- avg_word_length: Math.min(1.0, avgWordLen / 10),
129
- question_ratio: Math.min(1.0, questionRatio),
130
- code_blocks: Math.min(1.0, codeBlocks / 5),
131
- urgency,
132
- repetition: Math.min(1.0, repetition * 10),
133
- sentiment: sentimentIndicators,
134
- }
135
- }
136
-
137
- export { ResolutionTracker, SUB_REGIMES, extractFeatures }
@@ -1,119 +0,0 @@
1
- const VERBOSE_LINE_RE = [
2
- /^\s*(Sure|Certainly|Absolutely|Of course|Great question)[!.,]?\s*$/i,
3
- /^\s*(Hope this helps|Let me know if|Feel free to|Happy to|Please let me know).*$/i,
4
- /^\s*(I'd be happy|I can help|I'm here|Is there anything|Do you need).*$/i,
5
- ]
6
-
7
- const BULLET_PATTERNS = [
8
- /^\s*\w[^:]{0,80}:/,
9
- /^\s*[-*\u2022]\s/,
10
- /^\s*\d+\.\s/,
11
- /^\s*(NOTE|TIP|IMPORTANT|WARNING|FIX|TODO|HACK)\b/i,
12
- /^\s*[A-Z][A-Z\s_-]{4,}:\s/,
13
- /^\s*>\s/,
14
- /^\s*```\w*$/,
15
- /^\s*#{1,6}\s/,
16
- ]
17
-
18
- const COMPRESS_RATIO = 0.30
19
- const COMPRESS_THRESHOLD = 2000
20
- const MIN_KEPT_LINES_RATIO = 0.40
21
-
22
- function compressText(text) {
23
- if (!text || typeof text !== "string" || text.length <= COMPRESS_THRESHOLD) {
24
- return text
25
- }
26
-
27
- let lines = text.split("\n")
28
-
29
- lines = lines.filter(line => {
30
- return !VERBOSE_LINE_RE.some(re => re.test(line))
31
- })
32
-
33
- let result = lines.join("\n")
34
- result = result.replace(/\n{3,}/g, "\n\n")
35
-
36
- lines = result.split("\n")
37
- const originalCharCount = result.length
38
- const targetChars = Math.ceil(originalCharCount * COMPRESS_RATIO)
39
- const minLines = Math.ceil(lines.length * MIN_KEPT_LINES_RATIO)
40
-
41
- if (originalCharCount > targetChars && lines.length > minLines) {
42
- lines = extractBulletLines(lines, targetChars, minLines)
43
- }
44
-
45
- result = lines.join("\n")
46
-
47
- if (result.length > originalCharCount * 0.6) {
48
- result = safetyTruncate(result, Math.ceil(originalCharCount * 0.5))
49
- }
50
-
51
- if (!result.trim() && text.trim()) {
52
- return text.split("\n").slice(0, Math.max(5, Math.ceil(text.split("\n").length * 0.2))).join("\n")
53
- }
54
-
55
- return result
56
- }
57
-
58
- function extractBulletLines(lines, targetChars, minLines) {
59
- const keyLines = []
60
- const otherLines = []
61
-
62
- for (const line of lines) {
63
- if (BULLET_PATTERNS.some(re => re.test(line))) {
64
- keyLines.push(line)
65
- } else {
66
- otherLines.push(line)
67
- }
68
- }
69
-
70
- const selected = [...keyLines]
71
-
72
- for (const line of otherLines) {
73
- if (selected.length >= minLines && selected.join("\n").length >= targetChars) {
74
- break
75
- }
76
- selected.push(line)
77
- }
78
-
79
- while (selected.length > minLines && selected.join("\n").length > targetChars * 2) {
80
- selected.pop()
81
- }
82
-
83
- return selected
84
- }
85
-
86
- function safetyTruncate(text, maxChars) {
87
- if (text.length <= maxChars) return text
88
-
89
- const lines = text.split("\n")
90
- const selected = []
91
- let charCount = 0
92
-
93
- for (const line of lines) {
94
- if (charCount + line.length > maxChars && selected.length >= 3) {
95
- break
96
- }
97
- selected.push(line)
98
- charCount += line.length + 1
99
- }
100
-
101
- return selected.join("\n")
102
- }
103
-
104
- function compressToolOutput(output, threshold = COMPRESS_THRESHOLD) {
105
- if (!output || typeof output !== "string" || output.length <= threshold) {
106
- return { compressed: false, content: output, original_length: output?.length || 0 }
107
- }
108
-
109
- const compressed = compressText(output)
110
- return {
111
- compressed: true,
112
- content: compressed,
113
- original_length: output.length,
114
- compressed_length: compressed.length,
115
- savings_percent: Math.round((1 - compressed.length / output.length) * 100),
116
- }
117
- }
118
-
119
- export { compressText, compressToolOutput, extractBulletLines, COMPRESS_THRESHOLD, COMPRESS_RATIO }
package/lib/db.js DELETED
@@ -1,106 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import { existsSync, mkdirSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const DB_PATH = process.env.VIBEOS_API_DB_PATH || join(__dirname, "..", "data", "vibeos-api.db");
7
- let db;
8
- export function getDb() {
9
- if (db)
10
- return db;
11
- const dbDir = dirname(DB_PATH);
12
- if (!existsSync(dbDir)) {
13
- mkdirSync(dbDir, { recursive: true });
14
- }
15
- db = new Database(DB_PATH);
16
- db.pragma("journal_mode = WAL");
17
- db.pragma("foreign_keys = ON");
18
- db.exec(`
19
- CREATE TABLE IF NOT EXISTS seats (
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- name TEXT NOT NULL,
22
- email TEXT,
23
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'cancelled')),
24
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
25
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
26
- );
27
-
28
- CREATE TABLE IF NOT EXISTS api_tokens (
29
- id INTEGER PRIMARY KEY AUTOINCREMENT,
30
- token TEXT NOT NULL UNIQUE,
31
- seat_id INTEGER NOT NULL,
32
- label TEXT,
33
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'revoked', 'expired')),
34
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
35
- revoked_at TEXT,
36
- expires_at TEXT,
37
- last_used_at TEXT,
38
- FOREIGN KEY (seat_id) REFERENCES seats(id) ON DELETE CASCADE
39
- );
40
-
41
- CREATE TRIGGER IF NOT EXISTS prevent_api_token_delete
42
- BEFORE DELETE ON api_tokens
43
- BEGIN
44
- SELECT RAISE(ABORT, 'api_tokens rows cannot be deleted; update status instead');
45
- END;
46
-
47
- CREATE TABLE IF NOT EXISTS blackbox_sessions (
48
- id INTEGER PRIMARY KEY AUTOINCREMENT,
49
- session_id TEXT NOT NULL UNIQUE,
50
- project_id TEXT,
51
- state_json TEXT NOT NULL DEFAULT '{}',
52
- outcome TEXT,
53
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
54
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
55
- );
56
-
57
- CREATE TABLE IF NOT EXISTS blackbox_calibration (
58
- id INTEGER PRIMARY KEY AUTOINCREMENT,
59
- project_id TEXT NOT NULL DEFAULT 'global',
60
- weights_json TEXT NOT NULL DEFAULT '{}',
61
- samples_used INTEGER NOT NULL DEFAULT 0,
62
- precision REAL,
63
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
64
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
65
- UNIQUE(project_id)
66
- );
67
-
68
- CREATE TABLE IF NOT EXISTS usage_log (
69
- id INTEGER PRIMARY KEY AUTOINCREMENT,
70
- token_id INTEGER NOT NULL,
71
- endpoint TEXT NOT NULL,
72
- request_body TEXT,
73
- response_size INTEGER,
74
- latency_ms INTEGER,
75
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
76
- FOREIGN KEY (token_id) REFERENCES api_tokens(id)
77
- );
78
-
79
- CREATE TABLE IF NOT EXISTS admin_audit_log (
80
- id INTEGER PRIMARY KEY AUTOINCREMENT,
81
- method TEXT NOT NULL,
82
- endpoint TEXT NOT NULL,
83
- request_body TEXT,
84
- response_status INTEGER NOT NULL,
85
- response_size INTEGER,
86
- latency_ms INTEGER,
87
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
88
- );
89
-
90
- CREATE INDEX IF NOT EXISTS idx_tokens_token ON api_tokens(token);
91
- CREATE INDEX IF NOT EXISTS idx_tokens_seat ON api_tokens(seat_id);
92
- CREATE INDEX IF NOT EXISTS idx_tokens_status ON api_tokens(status);
93
- CREATE INDEX IF NOT EXISTS idx_seats_status ON seats(status);
94
- CREATE INDEX IF NOT EXISTS idx_blackbox_project ON blackbox_sessions(project_id);
95
- CREATE INDEX IF NOT EXISTS idx_blackbox_updated ON blackbox_sessions(updated_at);
96
- CREATE INDEX IF NOT EXISTS idx_usage_token ON usage_log(token_id);
97
- CREATE INDEX IF NOT EXISTS idx_usage_created ON usage_log(created_at);
98
- CREATE INDEX IF NOT EXISTS idx_admin_audit_created ON admin_audit_log(created_at);
99
- CREATE INDEX IF NOT EXISTS idx_admin_audit_endpoint ON admin_audit_log(endpoint);
100
- `);
101
- return db;
102
- }
103
- export function initDb() {
104
- getDb();
105
- return db;
106
- }
package/lib/db.ts DELETED
@@ -1,113 +0,0 @@
1
- import Database from "better-sqlite3"
2
- import { existsSync, mkdirSync } from "node:fs"
3
- import { dirname, join } from "node:path"
4
- import { fileURLToPath } from "node:url"
5
-
6
- const __dirname = dirname(fileURLToPath(import.meta.url))
7
- const DB_PATH = process.env.VIBEOS_API_DB_PATH || join(__dirname, "..", "data", "vibeos-api.db")
8
-
9
- let db: any
10
-
11
- export function getDb() {
12
- if (db) return db
13
-
14
- const dbDir = dirname(DB_PATH)
15
- if (!existsSync(dbDir)) {
16
- mkdirSync(dbDir, { recursive: true })
17
- }
18
-
19
- db = new Database(DB_PATH)
20
- db.pragma("journal_mode = WAL")
21
- db.pragma("foreign_keys = ON")
22
-
23
- db.exec(`
24
- CREATE TABLE IF NOT EXISTS seats (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- name TEXT NOT NULL,
27
- email TEXT,
28
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'cancelled')),
29
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
30
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
31
- );
32
-
33
- CREATE TABLE IF NOT EXISTS api_tokens (
34
- id INTEGER PRIMARY KEY AUTOINCREMENT,
35
- token TEXT NOT NULL UNIQUE,
36
- seat_id INTEGER NOT NULL,
37
- label TEXT,
38
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'revoked', 'expired')),
39
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
40
- revoked_at TEXT,
41
- expires_at TEXT,
42
- last_used_at TEXT,
43
- FOREIGN KEY (seat_id) REFERENCES seats(id) ON DELETE CASCADE
44
- );
45
-
46
- CREATE TRIGGER IF NOT EXISTS prevent_api_token_delete
47
- BEFORE DELETE ON api_tokens
48
- BEGIN
49
- SELECT RAISE(ABORT, 'api_tokens rows cannot be deleted; update status instead');
50
- END;
51
-
52
- CREATE TABLE IF NOT EXISTS blackbox_sessions (
53
- id INTEGER PRIMARY KEY AUTOINCREMENT,
54
- session_id TEXT NOT NULL UNIQUE,
55
- project_id TEXT,
56
- state_json TEXT NOT NULL DEFAULT '{}',
57
- outcome TEXT,
58
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
59
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
60
- );
61
-
62
- CREATE TABLE IF NOT EXISTS blackbox_calibration (
63
- id INTEGER PRIMARY KEY AUTOINCREMENT,
64
- project_id TEXT NOT NULL DEFAULT 'global',
65
- weights_json TEXT NOT NULL DEFAULT '{}',
66
- samples_used INTEGER NOT NULL DEFAULT 0,
67
- precision REAL,
68
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
69
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
70
- UNIQUE(project_id)
71
- );
72
-
73
- CREATE TABLE IF NOT EXISTS usage_log (
74
- id INTEGER PRIMARY KEY AUTOINCREMENT,
75
- token_id INTEGER NOT NULL,
76
- endpoint TEXT NOT NULL,
77
- request_body TEXT,
78
- response_size INTEGER,
79
- latency_ms INTEGER,
80
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
81
- FOREIGN KEY (token_id) REFERENCES api_tokens(id)
82
- );
83
-
84
- CREATE TABLE IF NOT EXISTS admin_audit_log (
85
- id INTEGER PRIMARY KEY AUTOINCREMENT,
86
- method TEXT NOT NULL,
87
- endpoint TEXT NOT NULL,
88
- request_body TEXT,
89
- response_status INTEGER NOT NULL,
90
- response_size INTEGER,
91
- latency_ms INTEGER,
92
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
93
- );
94
-
95
- CREATE INDEX IF NOT EXISTS idx_tokens_token ON api_tokens(token);
96
- CREATE INDEX IF NOT EXISTS idx_tokens_seat ON api_tokens(seat_id);
97
- CREATE INDEX IF NOT EXISTS idx_tokens_status ON api_tokens(status);
98
- CREATE INDEX IF NOT EXISTS idx_seats_status ON seats(status);
99
- CREATE INDEX IF NOT EXISTS idx_blackbox_project ON blackbox_sessions(project_id);
100
- CREATE INDEX IF NOT EXISTS idx_blackbox_updated ON blackbox_sessions(updated_at);
101
- CREATE INDEX IF NOT EXISTS idx_usage_token ON usage_log(token_id);
102
- CREATE INDEX IF NOT EXISTS idx_usage_created ON usage_log(created_at);
103
- CREATE INDEX IF NOT EXISTS idx_admin_audit_created ON admin_audit_log(created_at);
104
- CREATE INDEX IF NOT EXISTS idx_admin_audit_endpoint ON admin_audit_log(endpoint);
105
- `)
106
-
107
- return db
108
- }
109
-
110
- export function initDb() {
111
- getDb()
112
- return db
113
- }
package/lib/delegation.js DELETED
@@ -1,137 +0,0 @@
1
- const WARN_ON_DIRECT = new Set(["write", "edit", "notebookedit"])
2
- const SOFT_QUOTA = new Set(["bash", "webfetch", "websearch"])
3
-
4
- const SAVE_EST = {
5
- WRITE_EDIT: 0.0006,
6
- SOFT_QUOTA: 0.0003,
7
- CONTEXT7: 0.0006,
8
- OPUS_DISABLE: 0.03,
9
- }
10
-
11
- const MODEL_USD_PER_TURN = {
12
- "anthropic/claude-opus-4-7": 0.033,
13
- "anthropic/claude-opus-4-5": 0.033,
14
- "anthropic/claude-sonnet-4-6": 0.0066,
15
- "anthropic/claude-sonnet-4-5": 0.0066,
16
- "anthropic/claude-3-5-sonnet": 0.0066,
17
- "anthropic/claude-3-5-sonnet-20241022": 0.0066,
18
- "anthropic/claude-3-5-sonnet-20240620": 0.0066,
19
- "anthropic/claude-3-7-sonnet": 0.0066,
20
- "anthropic/claude-3-7-sonnet-20250219": 0.0066,
21
- "anthropic/claude-3-opus": 0.03,
22
- "anthropic/claude-3-opus-20240229": 0.03,
23
- "anthropic/claude-3-haiku": 0.0006,
24
- "anthropic/claude-3-5-haiku": 0.0016,
25
- "google/gemini-2.5-pro": 0.0035,
26
- "google/gemini-2.5-flash": 0.00075,
27
- "google/gemini-2.0-flash": 0.0002,
28
- "google/gemini-pro-1.5": 0.0035,
29
- "deepseek/deepseek-chat": 0,
30
- "deepseek/deepseek-v3": 0,
31
- "deepseek/deepseek-v4-pro": 0.002,
32
- "deepseek/deepseek-v4-flash": 0,
33
- "openai/gpt-5": 0.02,
34
- "openai/gpt-4o": 0.005,
35
- "openai/gpt-4o-mini": 0.0003,
36
- "openai/o1": 0.03,
37
- "openai/o3": 0.02,
38
- "openai/o4-mini": 0.0022,
39
- "mistral/mistral-large-2": 0.004,
40
- "mistral/codestral-2501": 0.001,
41
- "x-ai/grok-3": 0.006,
42
- "x-ai/grok-3-mini": 0.0006,
43
- "qwen/qwen3-235b-a22b": 0.0004,
44
- "qwen/qwen3-30b-a3b": 0.0001,
45
- }
46
-
47
- const TURN_BLEND_INPUT_TOKENS = 0.000003
48
- const TURN_BLEND_OUTPUT_TOKENS = 0.000012
49
-
50
- function normalizeModelId(model) {
51
- if (!model) return ""
52
- const s = String(model).toLowerCase().trim()
53
- return s.replace(/^openrouter\//, "").replace(/^anthropic\//, "anthropic/").replace(/^google\//, "google/").replace(/^deepseek\//, "deepseek/").replace(/^openai\//, "openai/").replace(/^mistral\//, "mistral/").replace(/^x-ai\//, "x-ai/").replace(/^qwen\//, "qwen/")
54
- }
55
-
56
- function modelCostPerTurn(model, dynamicCache = {}) {
57
- if (!model) return 0
58
- const dyn = dynamicCostFor(model, dynamicCache)
59
- if (dyn != null) return dyn
60
- const key = normalizeModelId(model)
61
- if (Object.prototype.hasOwnProperty.call(MODEL_USD_PER_TURN, key)) return MODEL_USD_PER_TURN[key]
62
- for (const [k, v] of Object.entries(MODEL_USD_PER_TURN)) {
63
- if (key.startsWith(k) && /-\d+$/.test(key) && key.charAt(k.length) === "-") return v
64
- }
65
- return null
66
- }
67
-
68
- function dynamicCostFor(model, dynamicCache = {}) {
69
- const key = normalizeModelId(model)
70
- if (Object.prototype.hasOwnProperty.call(dynamicCache, key)) return dynamicCache[key]
71
- for (const [k, v] of Object.entries(dynamicCache)) {
72
- if (key === k) return v
73
- if (key.startsWith(k) && /-\d+$/.test(key) && key.charAt(k.length) === "-") return v
74
- }
75
- return null
76
- }
77
-
78
- function parseOpenRouterTurnCost(modelRow) {
79
- const p = modelRow?.pricing || {}
80
- const inTok = Number(p.prompt ?? p.input ?? p.request)
81
- const outTok = Number(p.completion ?? p.output ?? p.response)
82
- if (Number.isFinite(inTok) && Number.isFinite(outTok)) {
83
- return inTok * TURN_BLEND_INPUT_TOKENS + outTok * TURN_BLEND_OUTPUT_TOKENS
84
- }
85
- const oneTok = Number(p.price ?? p.total ?? p.input ?? p.output)
86
- if (Number.isFinite(oneTok)) return oneTok * 1000
87
- return null
88
- }
89
-
90
- function checkDelegation(tool, tier, model, prompt, dynamicCache = {}) {
91
- const toolLower = String(tool || "").toLowerCase()
92
-
93
- if (!WARN_ON_DIRECT.has(toolLower)) {
94
- return { blocked: false, reason: null, savings: 0 }
95
- }
96
-
97
- if (tier !== "high") {
98
- return { blocked: false, reason: null, savings: 0 }
99
- }
100
-
101
- const cost = modelCostPerTurn(model, dynamicCache) ?? SAVE_EST.WRITE_EDIT
102
- const savings = cost > 0 ? cost : SAVE_EST.WRITE_EDIT
103
-
104
- return {
105
- blocked: true,
106
- reason: `Direct ${tool} blocked on Brain tier. Delegate via Task or switch tier.`,
107
- savings: savings,
108
- redirect_path: toolLower === "write" ? `/tmp/vibeos-enforcement-blocked-${Date.now()}` : null,
109
- old_string_replacement: (toolLower === "edit" || toolLower === "notebookedit") ? `__THE_SAVER_ENFORCEMENT_BLOCK_${Date.now()}__` : null,
110
- }
111
- }
112
-
113
- function checkSoftQuota(tool, currentCount, limit = 5) {
114
- const toolLower = String(tool || "").toLowerCase()
115
- if (!SOFT_QUOTA.has(toolLower)) return { warned: false }
116
- if (currentCount >= limit) {
117
- return {
118
- warned: true,
119
- message: `Soft quota reached for ${tool} (${currentCount}/${limit}). Consider delegating.`,
120
- savings: SAVE_EST.SOFT_QUOTA,
121
- }
122
- }
123
- return { warned: false }
124
- }
125
-
126
- export {
127
- WARN_ON_DIRECT,
128
- SOFT_QUOTA,
129
- SAVE_EST,
130
- MODEL_USD_PER_TURN,
131
- normalizeModelId,
132
- modelCostPerTurn,
133
- dynamicCostFor,
134
- parseOpenRouterTurnCost,
135
- checkDelegation,
136
- checkSoftQuota,
137
- }