@genee/omp-opsx-addon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/index.ts +1327 -0
- package/lib/agent-defs.ts +291 -0
- package/lib/change-scanner.ts +72 -0
- package/lib/cursor-auth.ts +230 -0
- package/lib/direct-fetchers.ts +155 -0
- package/lib/edit-variant-pin.ts +156 -0
- package/lib/error-scan.ts +97 -0
- package/lib/family-filter.ts +174 -0
- package/lib/model-selector.ts +307 -0
- package/lib/model-tiers.ts +164 -0
- package/lib/notify.ts +4 -0
- package/lib/openspec-tasks.ts +28 -0
- package/lib/peak-detector.ts +44 -0
- package/lib/reachability-cache.ts +288 -0
- package/lib/reachability-probe.ts +459 -0
- package/lib/reachability-refresh.ts +84 -0
- package/lib/selection-filters.ts +100 -0
- package/lib/selection-lifecycle.ts +17 -0
- package/lib/session-title.ts +186 -0
- package/lib/session-usage.ts +252 -0
- package/lib/state.ts +95 -0
- package/lib/system-prompt.ts +160 -0
- package/lib/tiers-data.ts +136 -0
- package/lib/tiers-updater.ts +146 -0
- package/lib/unified-config.ts +463 -0
- package/lib/usage-estimator.ts +154 -0
- package/lib/usage-poller.ts +1200 -0
- package/lib/usage-poller.worker.ts +31 -0
- package/lib/usage-redis-client.ts +594 -0
- package/lib/usage-redis-multiproc.worker.ts +80 -0
- package/lib/usage-redis-probe.ts +389 -0
- package/lib/usage-render.ts +526 -0
- package/lib/usage-resolver.ts +477 -0
- package/lib/usage-sampler.ts +77 -0
- package/lib/usage-widget.ts +112 -0
- package/package.json +47 -0
- package/types/bun.d.ts +21 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-driven model tier data layer.
|
|
3
|
+
*
|
|
4
|
+
* Loads tier assignments from `~/.omp/cache/tiers-data.json`.
|
|
5
|
+
* Falls back silently to heuristic mode when the file is missing or corrupt.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import type { TierName } from './model-tiers.js';
|
|
12
|
+
|
|
13
|
+
// ── types ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface TierDataEntry {
|
|
16
|
+
/** Model selector: exact provider/id, bare id, or glob pattern. */
|
|
17
|
+
selector: string;
|
|
18
|
+
tier: TierName;
|
|
19
|
+
reputation: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TierDataFile {
|
|
23
|
+
version: number;
|
|
24
|
+
updated: string;
|
|
25
|
+
entries: TierDataEntry[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── state ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CACHE_PATH = join(homedir(), '.omp', 'cache', 'tiers-data.json');
|
|
31
|
+
|
|
32
|
+
let _data: TierDataFile | null | undefined; // undefined = not yet loaded, null = failed, object = ok
|
|
33
|
+
let _loadError: string | null = null;
|
|
34
|
+
|
|
35
|
+
// ── public API ───────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/** Path to the tiers data cache file. */
|
|
38
|
+
export const TIERS_DATA_PATH = CACHE_PATH;
|
|
39
|
+
|
|
40
|
+
/** Age of the loaded data (ms since 1970), or 0 if not loaded. */
|
|
41
|
+
export function tiersDataAge(): number {
|
|
42
|
+
if (_data?.updated) {
|
|
43
|
+
const ms = Date.parse(_data.updated);
|
|
44
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether the data was loaded successfully. Null if not yet loaded. */
|
|
50
|
+
export function isTiersDataLoaded(): boolean | null {
|
|
51
|
+
if (_data === undefined) return null;
|
|
52
|
+
return _data !== null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Last load error message, or null. */
|
|
56
|
+
export function tiersDataLoadError(): string | null {
|
|
57
|
+
return _loadError;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Match a model against the tier data.
|
|
62
|
+
* Returns `{ tier, reputation }` on match, or null when no entry matches.
|
|
63
|
+
*/
|
|
64
|
+
export function matchDataEntry(
|
|
65
|
+
modelId: string,
|
|
66
|
+
provider?: string,
|
|
67
|
+
): { tier: TierName; reputation: number; reason: string } | null {
|
|
68
|
+
const data = ensureLoaded();
|
|
69
|
+
if (!data) return null;
|
|
70
|
+
|
|
71
|
+
// 1. Provider-scoped exact match
|
|
72
|
+
if (provider) {
|
|
73
|
+
const scoped = `${provider}/${modelId}`;
|
|
74
|
+
const hit = data.entries.find((e) => e.selector === scoped);
|
|
75
|
+
if (hit) return { tier: hit.tier, reputation: hit.reputation, reason: `data: ${hit.selector}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2. Bare id exact match
|
|
79
|
+
const bare = data.entries.find((e) => e.selector === modelId);
|
|
80
|
+
if (bare) return { tier: bare.tier, reputation: bare.reputation, reason: `data: ${bare.selector}` };
|
|
81
|
+
|
|
82
|
+
// 3. Glob patterns (in order)
|
|
83
|
+
const globs = data.entries.filter((e) => hasGlob(e.selector));
|
|
84
|
+
for (const g of globs) {
|
|
85
|
+
if (globMatch(g.selector, modelId)) {
|
|
86
|
+
return { tier: g.tier, reputation: g.reputation, reason: `data: ${g.selector}` };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Force-reload data (used after pick-model update). */
|
|
94
|
+
export function reloadTierData(): void {
|
|
95
|
+
_data = undefined;
|
|
96
|
+
_loadError = null;
|
|
97
|
+
ensureLoaded();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Reset internal state (for tests only). */
|
|
101
|
+
export function _resetTierDataForTest(): void {
|
|
102
|
+
_data = undefined;
|
|
103
|
+
_loadError = null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── internal ─────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function ensureLoaded(): TierDataFile | null {
|
|
109
|
+
if (_data !== undefined) return _data;
|
|
110
|
+
try {
|
|
111
|
+
const raw = readFileSync(CACHE_PATH, 'utf-8');
|
|
112
|
+
const parsed = JSON.parse(raw) as TierDataFile;
|
|
113
|
+
if (!Array.isArray(parsed.entries)) throw new Error('missing entries array');
|
|
114
|
+
_data = parsed;
|
|
115
|
+
_loadError = null;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
_data = null;
|
|
118
|
+
_loadError = err instanceof Error ? err.message : String(err);
|
|
119
|
+
}
|
|
120
|
+
return _data;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hasGlob(selector: string): boolean {
|
|
124
|
+
return selector.includes('*') || selector.includes('?');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function globMatch(pattern: string, target: string): boolean {
|
|
128
|
+
return globToRegex(pattern).test(target);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function globToRegex(pattern: string): RegExp {
|
|
132
|
+
// Escape regex specials, then replace glob wildcards
|
|
133
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
134
|
+
const withStars = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
135
|
+
return new RegExp(`^${withStars}$`, 'i');
|
|
136
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier data updater — seed data + cache file management.
|
|
3
|
+
*
|
|
4
|
+
* Called by `/pick-model update` and auto-refresh on startup
|
|
5
|
+
* when the cache is missing or stale (> 7 days).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join, dirname } from 'node:path';
|
|
11
|
+
import type { TierDataEntry } from './tiers-data.js';
|
|
12
|
+
import { reloadTierData, TIERS_DATA_PATH, tiersDataAge, isTiersDataLoaded } from './tiers-data.js';
|
|
13
|
+
|
|
14
|
+
// ── constants ────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/** Auto-refresh when cache is older than this (ms). */
|
|
17
|
+
const STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
18
|
+
|
|
19
|
+
// ── seed data ────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Curated tier data for known models.
|
|
23
|
+
* Provider-scoped entries take precedence over bare-id entries.
|
|
24
|
+
* This list should be updated when new flagship models are released.
|
|
25
|
+
*/
|
|
26
|
+
const SEED_ENTRIES: TierDataEntry[] = [
|
|
27
|
+
// ── anthropic ──────────────────────────────────
|
|
28
|
+
{ selector: 'claude-fable-5', tier: 'top', reputation: 85 },
|
|
29
|
+
{ selector: 'claude-mythos-5', tier: 'top', reputation: 90 },
|
|
30
|
+
{ selector: 'claude-opus-4-8', tier: 'top', reputation: 80 },
|
|
31
|
+
{ selector: 'claude-opus-*', tier: 'top', reputation: 78 },
|
|
32
|
+
{ selector: 'claude-sonnet-5', tier: 'high', reputation: 65 },
|
|
33
|
+
{ selector: 'claude-sonnet-4-*', tier: 'high', reputation: 60 },
|
|
34
|
+
{ selector: 'claude-haiku-4-*', tier: 'low', reputation: 35 },
|
|
35
|
+
|
|
36
|
+
// ── deepseek ───────────────────────────────────
|
|
37
|
+
{ selector: 'deepseek/deepseek-v4-pro', tier: 'top', reputation: 70 },
|
|
38
|
+
{ selector: 'deepseek/deepseek-v4-flash', tier: 'mid', reputation: 45 },
|
|
39
|
+
{ selector: 'deepseek/deepseek-v4-pro', tier: 'top', reputation: 70 },
|
|
40
|
+
|
|
41
|
+
// ── minimax ────────────────────────────────────
|
|
42
|
+
{ selector: 'MiniMax-M3', tier: 'top', reputation: 60 },
|
|
43
|
+
{ selector: 'minimax-code-cn/MiniMax-M3', tier: 'top', reputation: 60 },
|
|
44
|
+
|
|
45
|
+
// ── kimi / moonshot ────────────────────────────
|
|
46
|
+
{ selector: 'k3', tier: 'top', reputation: 75 },
|
|
47
|
+
{ selector: 'kimi-k2.7', tier: 'top', reputation: 72 },
|
|
48
|
+
{ selector: 'kimi-k2.5', tier: 'top', reputation: 65 },
|
|
49
|
+
{ selector: 'kimi-k2.*', tier: 'top', reputation: 65 },
|
|
50
|
+
{ selector: 'kimi-k3', tier: 'top', reputation: 75 },
|
|
51
|
+
{ selector: 'kimi-k*', tier: 'top', reputation: 60 },
|
|
52
|
+
|
|
53
|
+
// ── zhipu / glm ────────────────────────────────
|
|
54
|
+
{ selector: 'glm-5.2', tier: 'top', reputation: 70 },
|
|
55
|
+
{ selector: 'glm-5.*', tier: 'top', reputation: 68 },
|
|
56
|
+
{ selector: 'glm-4.7', tier: 'mid', reputation: 50 },
|
|
57
|
+
|
|
58
|
+
// ── openai ─────────────────────────────────────
|
|
59
|
+
{ selector: 'gpt-5.6-luna-pro', tier: 'top', reputation: 82 },
|
|
60
|
+
{ selector: 'gpt-5.6-luna', tier: 'high', reputation: 70 },
|
|
61
|
+
{ selector: 'gpt-5.6', tier: 'high', reputation: 65 },
|
|
62
|
+
{ selector: 'gpt-5.*-pro', tier: 'top', reputation: 80 },
|
|
63
|
+
{ selector: 'gpt-5.*', tier: 'high', reputation: 65 },
|
|
64
|
+
{ selector: 'gpt-5.4-mini', tier: 'tiny', reputation: 35 },
|
|
65
|
+
|
|
66
|
+
// ── google ─────────────────────────────────────
|
|
67
|
+
{ selector: 'gemini-3.1-pro*', tier: 'top', reputation: 75 },
|
|
68
|
+
{ selector: 'gemini-3.5-flash', tier: 'mid', reputation: 40 },
|
|
69
|
+
{ selector: 'gemini-3.1-flash-lite', tier: 'tiny', reputation: 30 },
|
|
70
|
+
|
|
71
|
+
// ── xai ────────────────────────────────────────
|
|
72
|
+
{ selector: 'grok-4.20*', tier: 'mid', reputation: 50 },
|
|
73
|
+
{ selector: 'grok-3-mini', tier: 'tiny', reputation: 30 },
|
|
74
|
+
|
|
75
|
+
// ── opencode-go ────────────────────────────────
|
|
76
|
+
{ selector: 'opencode-go/glm-5.2', tier: 'top', reputation: 70 },
|
|
77
|
+
{ selector: 'opencode-go/grok-4.5', tier: 'mid', reputation: 50 },
|
|
78
|
+
|
|
79
|
+
// ── opencode ───────────────────────────────────
|
|
80
|
+
{ selector: 'opencode/gpt-5.4-pro', tier: 'top', reputation: 70 },
|
|
81
|
+
{ selector: 'opencode/gpt-5.4', tier: 'high', reputation: 60 },
|
|
82
|
+
|
|
83
|
+
// ── openai-codex ───────────────────────────────
|
|
84
|
+
{ selector: 'openai-codex/gpt-5.6-luna', tier: 'high', reputation: 70 },
|
|
85
|
+
{ selector: 'openai-codex/gpt-5.4-mini', tier: 'tiny', reputation: 35 },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// ── public API ───────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/** Check whether cached tier data is stale (> 7 days). */
|
|
91
|
+
export function isTiersDataStale(): boolean {
|
|
92
|
+
const loaded = isTiersDataLoaded();
|
|
93
|
+
if (loaded === null) return true; // not yet loaded → treat as stale
|
|
94
|
+
if (!loaded) return true; // failed to load → stale
|
|
95
|
+
const age = tiersDataAge();
|
|
96
|
+
if (age === 0) return true;
|
|
97
|
+
return Date.now() - age > STALE_THRESHOLD_MS;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Auto-refresh tier data if needed.
|
|
102
|
+
* Returns true if a refresh was performed.
|
|
103
|
+
*/
|
|
104
|
+
export function autoRefreshTiersIfStale(): boolean {
|
|
105
|
+
if (isTiersDataStale()) {
|
|
106
|
+
updateTiers({ silent: true });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Result of an update operation. */
|
|
113
|
+
export interface UpdateResult {
|
|
114
|
+
ok: boolean;
|
|
115
|
+
path: string;
|
|
116
|
+
count: number;
|
|
117
|
+
error?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Write seed tier data to the cache file.
|
|
122
|
+
* Creates parent directories as needed.
|
|
123
|
+
*/
|
|
124
|
+
export function updateTiers(opts?: { silent?: boolean }): UpdateResult {
|
|
125
|
+
const path = TIERS_DATA_PATH;
|
|
126
|
+
try {
|
|
127
|
+
const dir = dirname(path);
|
|
128
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
129
|
+
|
|
130
|
+
const data = {
|
|
131
|
+
version: 1,
|
|
132
|
+
updated: new Date().toISOString(),
|
|
133
|
+
entries: SEED_ENTRIES,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
137
|
+
|
|
138
|
+
// Force runtime to reload on next access
|
|
139
|
+
reloadTierData();
|
|
140
|
+
|
|
141
|
+
return { ok: true, path, count: SEED_ENTRIES.length };
|
|
142
|
+
} catch (err) {
|
|
143
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
144
|
+
return { ok: false, path, count: 0, error: msg };
|
|
145
|
+
}
|
|
146
|
+
}
|