@tyvm/knowhow 0.0.105 ā 0.0.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +8 -5
- package/package.json +3 -2
- package/scripts/check-model-pricing.ts +509 -0
- package/scripts/compare-openrouter-coverage.ts +576 -0
- package/src/agents/base/base.ts +127 -2
- package/src/agents/tools/execCommand.ts +4 -0
- package/src/agents/tools/executeScript/definition.ts +1 -1
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +3 -43
- package/src/agents/tools/writeFile.ts +1 -1
- package/src/auth/browserLogin.ts +9 -4
- package/src/chat/modules/RemoteSyncModule.ts +3 -0
- package/src/cli.ts +31 -1
- package/src/clients/cerebras.ts +10 -0
- package/src/clients/contextLimits.ts +7 -2
- package/src/clients/copilot.ts +23 -0
- package/src/clients/deepseek.ts +16 -0
- package/src/clients/fireworks.ts +15 -0
- package/src/clients/gemini.ts +45 -2
- package/src/clients/github.ts +16 -0
- package/src/clients/groq.ts +15 -0
- package/src/clients/http.ts +190 -6
- package/src/clients/index.ts +116 -4
- package/src/clients/llama.ts +16 -0
- package/src/clients/mistral.ts +16 -0
- package/src/clients/nvidia.ts +16 -0
- package/src/clients/openai.ts +41 -11
- package/src/clients/openrouter.ts +17 -0
- package/src/clients/pricing/anthropic.ts +105 -78
- package/src/clients/pricing/cerebras.ts +11 -0
- package/src/clients/pricing/copilot.ts +60 -0
- package/src/clients/pricing/deepseek.ts +15 -0
- package/src/clients/pricing/fireworks.ts +32 -0
- package/src/clients/pricing/github.ts +69 -0
- package/src/clients/pricing/google.ts +245 -206
- package/src/clients/pricing/groq.ts +56 -0
- package/src/clients/pricing/index.ts +42 -5
- package/src/clients/pricing/llama.ts +18 -0
- package/src/clients/pricing/mistral.ts +34 -0
- package/src/clients/pricing/models.ts +7 -236
- package/src/clients/pricing/nvidia.ts +102 -0
- package/src/clients/pricing/openai.ts +347 -171
- package/src/clients/pricing/openrouter.ts +36 -0
- package/src/clients/pricing/types.ts +83 -2
- package/src/clients/pricing/xai.ts +121 -65
- package/src/clients/types.ts +4 -0
- package/src/clients/xai.ts +150 -0
- package/src/fileSync.ts +8 -2
- package/src/login.ts +11 -3
- package/src/services/AgentSyncFs.ts +36 -12
- package/src/services/KnowhowClient.ts +11 -0
- package/src/services/LazyToolsService.ts +6 -0
- package/src/services/S3.ts +0 -7
- package/src/services/modules/index.ts +11 -2
- package/src/types.ts +56 -279
- package/src/worker.ts +174 -0
- package/tests/clients/pricing.test.ts +37 -0
- package/tests/manual/clients/completions.json +838 -226
- package/tests/manual/clients/completions.test.ts +46 -31
- package/ts_build/package.json +3 -2
- package/ts_build/src/agents/base/base.d.ts +17 -1
- package/ts_build/src/agents/base/base.js +82 -1
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.js +3 -0
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +3 -38
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
- package/ts_build/src/agents/tools/writeFile.js +1 -1
- package/ts_build/src/agents/tools/writeFile.js.map +1 -1
- package/ts_build/src/ai.d.ts +1 -1
- package/ts_build/src/auth/browserLogin.d.ts +2 -1
- package/ts_build/src/auth/browserLogin.js +10 -3
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/cli.js +19 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -82
- package/ts_build/src/clients/cerebras.d.ts +4 -0
- package/ts_build/src/clients/cerebras.js +14 -0
- package/ts_build/src/clients/cerebras.js.map +1 -0
- package/ts_build/src/clients/contextLimits.js +7 -2
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/copilot.d.ts +4 -0
- package/ts_build/src/clients/copilot.js +15 -0
- package/ts_build/src/clients/copilot.js.map +1 -0
- package/ts_build/src/clients/deepseek.d.ts +4 -0
- package/ts_build/src/clients/deepseek.js +15 -0
- package/ts_build/src/clients/deepseek.js.map +1 -0
- package/ts_build/src/clients/fireworks.d.ts +4 -0
- package/ts_build/src/clients/fireworks.js +15 -0
- package/ts_build/src/clients/fireworks.js.map +1 -0
- package/ts_build/src/clients/gemini.d.ts +1 -0
- package/ts_build/src/clients/gemini.js +28 -1
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/github.d.ts +4 -0
- package/ts_build/src/clients/github.js +15 -0
- package/ts_build/src/clients/github.js.map +1 -0
- package/ts_build/src/clients/groq.d.ts +4 -0
- package/ts_build/src/clients/groq.js +15 -0
- package/ts_build/src/clients/groq.js.map +1 -0
- package/ts_build/src/clients/http.d.ts +22 -1
- package/ts_build/src/clients/http.js +132 -7
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +14 -0
- package/ts_build/src/clients/index.js +94 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/llama.d.ts +4 -0
- package/ts_build/src/clients/llama.js +15 -0
- package/ts_build/src/clients/llama.js.map +1 -0
- package/ts_build/src/clients/mistral.d.ts +4 -0
- package/ts_build/src/clients/mistral.js +15 -0
- package/ts_build/src/clients/mistral.js.map +1 -0
- package/ts_build/src/clients/nvidia.d.ts +4 -0
- package/ts_build/src/clients/nvidia.js +15 -0
- package/ts_build/src/clients/nvidia.js.map +1 -0
- package/ts_build/src/clients/openai.d.ts +4 -206
- package/ts_build/src/clients/openai.js +27 -9
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +4 -0
- package/ts_build/src/clients/openrouter.js +15 -0
- package/ts_build/src/clients/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
- package/ts_build/src/clients/pricing/anthropic.js +75 -78
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
- package/ts_build/src/clients/pricing/cerebras.js +11 -0
- package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
- package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
- package/ts_build/src/clients/pricing/copilot.js +35 -0
- package/ts_build/src/clients/pricing/copilot.js.map +1 -0
- package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
- package/ts_build/src/clients/pricing/deepseek.js +10 -0
- package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
- package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
- package/ts_build/src/clients/pricing/fireworks.js +21 -0
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
- package/ts_build/src/clients/pricing/github.d.ts +4 -0
- package/ts_build/src/clients/pricing/github.js +58 -0
- package/ts_build/src/clients/pricing/github.js.map +1 -0
- package/ts_build/src/clients/pricing/google.d.ts +59 -6
- package/ts_build/src/clients/pricing/google.js +214 -167
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/groq.d.ts +5 -0
- package/ts_build/src/clients/pricing/groq.js +41 -0
- package/ts_build/src/clients/pricing/groq.js.map +1 -0
- package/ts_build/src/clients/pricing/index.d.ts +16 -5
- package/ts_build/src/clients/pricing/index.js +62 -7
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/llama.d.ts +4 -0
- package/ts_build/src/clients/pricing/llama.js +14 -0
- package/ts_build/src/clients/pricing/llama.js.map +1 -0
- package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
- package/ts_build/src/clients/pricing/mistral.js +23 -0
- package/ts_build/src/clients/pricing/mistral.js.map +1 -0
- package/ts_build/src/clients/pricing/models.d.ts +5 -4
- package/ts_build/src/clients/pricing/models.js +8 -162
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
- package/ts_build/src/clients/pricing/nvidia.js +96 -0
- package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +86 -197
- package/ts_build/src/clients/pricing/openai.js +294 -168
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
- package/ts_build/src/clients/pricing/openrouter.js +29 -0
- package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/types.d.ts +27 -2
- package/ts_build/src/clients/pricing/types.js +46 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +37 -57
- package/ts_build/src/clients/pricing/xai.js +92 -59
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +1 -0
- package/ts_build/src/clients/xai.d.ts +2 -62
- package/ts_build/src/clients/xai.js +121 -0
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/fileSync.js +7 -2
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/login.js +8 -2
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +1 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js +7 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +1 -0
- package/ts_build/src/services/LazyToolsService.js +3 -0
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/S3.js +0 -7
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/modules/index.js +41 -1
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +163 -124
- package/ts_build/src/types.js +33 -213
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +4 -0
- package/ts_build/src/worker.js +140 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/clients/pricing.test.js +21 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/manual/clients/completions.test.js +27 -24
- package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
package/CONFIG.md
CHANGED
|
@@ -296,13 +296,16 @@ There are many commands you can call from a chat session, try TAB to see a list
|
|
|
296
296
|
|
|
297
297
|
### knowhow chat: custom agents
|
|
298
298
|
```json
|
|
299
|
-
"
|
|
299
|
+
"agents": [
|
|
300
300
|
{
|
|
301
|
-
"name": "
|
|
302
|
-
"description": "
|
|
303
|
-
"instructions": "
|
|
301
|
+
"name": "Example agent",
|
|
302
|
+
"description": "You can define agents in the config. They will have access to all tools.",
|
|
303
|
+
"instructions": "Reply to the user saying 'Hello, world!'",
|
|
304
|
+
"model": "gpt-4o-2024-08-06",
|
|
305
|
+
"provider": "openai"
|
|
304
306
|
}
|
|
305
|
-
]
|
|
307
|
+
],
|
|
308
|
+
|
|
306
309
|
```
|
|
307
310
|
You can configure new agents via the config above. This would create a new option when you use `agents` in a chat session.
|
|
308
311
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.106",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"check:isolated": "bash scripts/check-isolated-deps.sh",
|
|
21
21
|
"lint": "tslint ./src/**/*.ts",
|
|
22
22
|
"lint:deps": "depcheck --config=.depcheckrc",
|
|
23
|
-
"lint:all": "npm run lint && npm run lint:deps"
|
|
23
|
+
"lint:all": "npm run lint && npm run lint:deps",
|
|
24
|
+
"check:model-pricing": "ts-node scripts/check-model-pricing.ts"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [],
|
|
26
27
|
"author": "Micah Riggan",
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env npx ts-node
|
|
2
|
+
/**
|
|
3
|
+
* check-model-pricing.ts
|
|
4
|
+
*
|
|
5
|
+
* Compares models.dev live data against knowhow's registered providers & pricing.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx ts-node scripts/check-model-pricing.ts
|
|
9
|
+
* npx ts-node scripts/check-model-pricing.ts --provider groq
|
|
10
|
+
* npx ts-node scripts/check-model-pricing.ts --provider openai --show-all
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --provider <name> Only check a specific provider
|
|
14
|
+
* --show-all Show all models, not just mismatches
|
|
15
|
+
* --free-only Only show models with $0 cost on models.dev
|
|
16
|
+
* --output <file> Write results to a markdown file (e.g. analysis.md)
|
|
17
|
+
* --include-deprecated Include deprecated/retired models in gap analysis (default: excluded)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import https from "https";
|
|
21
|
+
import { ALL_MODEL_CATALOG } from "../src/clients/pricing/models";
|
|
22
|
+
import { ModelCatalogEntry } from "../src/clients/pricing/types";
|
|
23
|
+
import { GroqTextPricing } from "../src/clients/pricing/groq";
|
|
24
|
+
import { DeepSeekTextPricing } from "../src/clients/pricing/deepseek";
|
|
25
|
+
import { MistralTextPricing } from "../src/clients/pricing/mistral";
|
|
26
|
+
import { NvidiaTextPricing, NvidiaImagePricing } from "../src/clients/pricing/nvidia";
|
|
27
|
+
import { GitHubModelsTextPricing } from "../src/clients/pricing/github";
|
|
28
|
+
import { OpenRouterTextPricing } from "../src/clients/pricing/openrouter";
|
|
29
|
+
import { LlamaTextPricing } from "../src/clients/pricing/llama";
|
|
30
|
+
import { CopilotTextPricing } from "../src/clients/pricing/copilot";
|
|
31
|
+
import { CerebrasTextPricing } from "../src/clients/pricing/cerebras";
|
|
32
|
+
|
|
33
|
+
// Build per-provider pricing maps for providers not yet in ALL_MODEL_CATALOG
|
|
34
|
+
const EXTRA_PROVIDER_PRICING: Record<string, Record<string, { input: number; output: number }>> = {
|
|
35
|
+
groq: GroqTextPricing,
|
|
36
|
+
deepseek: DeepSeekTextPricing,
|
|
37
|
+
mistral: MistralTextPricing,
|
|
38
|
+
nvidia: { ...NvidiaTextPricing, ...NvidiaImagePricing },
|
|
39
|
+
github: GitHubModelsTextPricing,
|
|
40
|
+
openrouter: OpenRouterTextPricing,
|
|
41
|
+
llama: LlamaTextPricing,
|
|
42
|
+
"github-copilot": CopilotTextPricing,
|
|
43
|
+
cerebras: CerebrasTextPricing,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// CLI args
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
const getArg = (flag: string) => {
|
|
51
|
+
const idx = args.indexOf(flag);
|
|
52
|
+
return idx !== -1 ? args[idx + 1] : null;
|
|
53
|
+
};
|
|
54
|
+
const hasFlag = (flag: string) => args.includes(flag);
|
|
55
|
+
|
|
56
|
+
const filterProvider = getArg("--provider");
|
|
57
|
+
const showAll = hasFlag("--show-all");
|
|
58
|
+
const freeOnly = hasFlag("--free-only");
|
|
59
|
+
const outputFile = getArg("--output");
|
|
60
|
+
const includeDeprecated = hasFlag("--include-deprecated");
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// models.dev API types
|
|
64
|
+
// The API endpoint is https://models.dev/api.json
|
|
65
|
+
// It returns an object keyed by provider ID, each value has a `models` sub-object
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
interface ModelDevModel {
|
|
68
|
+
id: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
cost?: {
|
|
71
|
+
input?: number | null;
|
|
72
|
+
output?: number | null;
|
|
73
|
+
input_cached?: number | null;
|
|
74
|
+
};
|
|
75
|
+
limit?: {
|
|
76
|
+
context?: number;
|
|
77
|
+
output?: number;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ModelDevProvider {
|
|
82
|
+
id: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
models: Record<string, ModelDevModel>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ModelDevApiResponse {
|
|
88
|
+
[providerId: string]: ModelDevProvider;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Fetch helpers
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
function fetchJson<T>(url: string): Promise<T> {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
https
|
|
97
|
+
.get(url, { headers: { "User-Agent": "knowhow-pricing-check/1.0" } }, (res) => {
|
|
98
|
+
let data = "";
|
|
99
|
+
res.on("data", (chunk) => (data += chunk));
|
|
100
|
+
res.on("end", () => {
|
|
101
|
+
try {
|
|
102
|
+
resolve(JSON.parse(data) as T);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
reject(new Error(`Failed to parse response from ${url}: ${e}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
})
|
|
108
|
+
.on("error", reject);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// models.dev provider ID ā knowhow provider name mapping
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
const PROVIDER_MAP: Record<string, string> = {
|
|
116
|
+
openai: "openai",
|
|
117
|
+
anthropic: "anthropic",
|
|
118
|
+
google: "google",
|
|
119
|
+
"x-ai": "xai",
|
|
120
|
+
xai: "xai",
|
|
121
|
+
groq: "groq",
|
|
122
|
+
mistral: "mistral",
|
|
123
|
+
deepseek: "deepseek",
|
|
124
|
+
nvidia: "nvidia",
|
|
125
|
+
"github-models": "github",
|
|
126
|
+
github: "github",
|
|
127
|
+
openrouter: "openrouter",
|
|
128
|
+
"github-copilot": "github-copilot",
|
|
129
|
+
"meta-llama": "llama",
|
|
130
|
+
llama: "llama",
|
|
131
|
+
cerebras: "cerebras",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Providers we have knowhow clients for
|
|
135
|
+
const SUPPORTED_PROVIDERS = new Set(Object.values(PROVIDER_MAP));
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Result types
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
interface ModelComparison {
|
|
141
|
+
modelId: string;
|
|
142
|
+
provider: string; // knowhow provider name
|
|
143
|
+
devProvider: string; // models.dev provider id
|
|
144
|
+
devInputPrice: number | null;
|
|
145
|
+
devOutputPrice: number | null;
|
|
146
|
+
khInputPrice: number | null;
|
|
147
|
+
khOutputPrice: number | null;
|
|
148
|
+
status:
|
|
149
|
+
| "ok"
|
|
150
|
+
| "price-mismatch"
|
|
151
|
+
| "missing-in-knowhow"
|
|
152
|
+
| "missing-pricing"
|
|
153
|
+
| "extra-in-knowhow";
|
|
154
|
+
details?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Main
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
async function main() {
|
|
161
|
+
console.log("š Fetching model data from models.dev...\n");
|
|
162
|
+
|
|
163
|
+
// Flat list of all dev models with provider attached
|
|
164
|
+
interface FlatDevModel extends ModelDevModel {
|
|
165
|
+
provider: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let allDevModels: FlatDevModel[] = [];
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetchJson<ModelDevApiResponse>("https://models.dev/api.json");
|
|
172
|
+
for (const [providerId, providerData] of Object.entries(response)) {
|
|
173
|
+
if (!providerData || typeof providerData.models !== "object") continue;
|
|
174
|
+
for (const [modelId, model] of Object.entries(providerData.models)) {
|
|
175
|
+
allDevModels.push({
|
|
176
|
+
...model,
|
|
177
|
+
id: model.id || modelId,
|
|
178
|
+
provider: providerId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (e: any) {
|
|
183
|
+
console.error(`ā Failed to fetch from models.dev: ${e.message}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(`ā
Fetched ${allDevModels.length} models from models.dev\n`);
|
|
188
|
+
|
|
189
|
+
// Build static knowhow model catalog from ALL_MODEL_CATALOG + extra provider pricing maps
|
|
190
|
+
// This does NOT require API keys ā it uses the static pricing files directly.
|
|
191
|
+
const khCatalogEntries: ModelCatalogEntry[] = [...ALL_MODEL_CATALOG];
|
|
192
|
+
|
|
193
|
+
// Add entries for providers that have pricing maps but aren't yet in ALL_MODEL_CATALOG
|
|
194
|
+
for (const [provider, pricingMap] of Object.entries(EXTRA_PROVIDER_PRICING)) {
|
|
195
|
+
for (const [modelId, pricing] of Object.entries(pricingMap)) {
|
|
196
|
+
khCatalogEntries.push({
|
|
197
|
+
id: modelId,
|
|
198
|
+
provider,
|
|
199
|
+
type: "completion",
|
|
200
|
+
pricing: { input: pricing.input, output: pricing.output },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Build lookup maps
|
|
206
|
+
const khPriceMap = new Map<string, ModelCatalogEntry>();
|
|
207
|
+
const khModelsByProvider = new Map<string, Set<string>>();
|
|
208
|
+
for (const entry of khCatalogEntries) {
|
|
209
|
+
khPriceMap.set(`${entry.provider}:${entry.id}`, entry);
|
|
210
|
+
if (!khModelsByProvider.has(entry.provider)) {
|
|
211
|
+
khModelsByProvider.set(entry.provider, new Set());
|
|
212
|
+
}
|
|
213
|
+
khModelsByProvider.get(entry.provider)!.add(entry.id);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Fuzzy model matching helpers
|
|
218
|
+
// models.dev often uses generic aliases (e.g. "grok-4", "grok-3")
|
|
219
|
+
// while knowhow uses versioned IDs (e.g. "grok-4-0709", "grok-3-beta").
|
|
220
|
+
// Also, anthropic uses base IDs in knowhow (e.g. "claude-opus-4") while
|
|
221
|
+
// models.dev uses dated versions (e.g. "claude-opus-4-20250514").
|
|
222
|
+
//
|
|
223
|
+
// To avoid false matches like kh "gpt-5" matching dev "gpt-5-pro",
|
|
224
|
+
// we only consider a suffix to be a "version suffix" if it looks like:
|
|
225
|
+
// - A date: all digits, e.g. "20250514", "1212", "0709"
|
|
226
|
+
// - A known version word: "beta", "latest", "preview"
|
|
227
|
+
// - A short alphanumeric version tag that starts with a digit, e.g. "001"
|
|
228
|
+
// NOT accepted as version suffix: "pro", "mini", "nano", "fast", "turbo", etc.
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
const VERSION_SUFFIX_RE = /^(\d+|beta|latest|preview|exp|rc\d*|v\d+)$/i;
|
|
231
|
+
|
|
232
|
+
function isVersionSuffix(part: string): boolean {
|
|
233
|
+
return VERSION_SUFFIX_RE.test(part);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// We match if:
|
|
237
|
+
// 1. Exact match, OR
|
|
238
|
+
// 2. Any knowhow model ID starts with the dev model ID + "-" or "/"
|
|
239
|
+
// (e.g. dev "grok-4" matches kh "grok-4-0709")
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
function findKhEntry(khProvider: string, devModelId: string): ModelCatalogEntry | undefined {
|
|
242
|
+
// 1. Exact match
|
|
243
|
+
const exact = khPriceMap.get(`${khProvider}:${devModelId}`);
|
|
244
|
+
if (exact) return exact;
|
|
245
|
+
|
|
246
|
+
const khModels = khModelsByProvider.get(khProvider);
|
|
247
|
+
if (!khModels) return undefined;
|
|
248
|
+
|
|
249
|
+
// Collect all candidates and pick the longest match (most specific)
|
|
250
|
+
let bestMatch: ModelCatalogEntry | undefined;
|
|
251
|
+
let bestMatchLen = 0;
|
|
252
|
+
|
|
253
|
+
for (const khId of khModels) {
|
|
254
|
+
// 2. kh is more specific: kh ID starts with dev ID + "-" or "/"
|
|
255
|
+
// e.g. dev "grok-4" matches kh "grok-4-0709"
|
|
256
|
+
if (khId.startsWith(devModelId + "-") || khId.startsWith(devModelId + "/")) {
|
|
257
|
+
const suffix = khId.slice(devModelId.length + 1);
|
|
258
|
+
// Only match if the extra suffix looks like a version (not a different model variant)
|
|
259
|
+
if (suffix.split("-").every(isVersionSuffix)) {
|
|
260
|
+
if (khId.length > bestMatchLen) {
|
|
261
|
+
bestMatch = khPriceMap.get(`${khProvider}:${khId}`);
|
|
262
|
+
bestMatchLen = khId.length;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// 3. dev is more specific: dev ID starts with kh ID + "-" or "/"
|
|
267
|
+
// e.g. dev "claude-opus-4-20250514" matches kh "claude-opus-4"
|
|
268
|
+
// e.g. dev "claude-3-7-sonnet-20250219" matches kh "claude-3-7-sonnet"
|
|
269
|
+
if (devModelId.startsWith(khId + "-") || devModelId.startsWith(khId + "/")) {
|
|
270
|
+
const suffix = devModelId.slice(khId.length + 1);
|
|
271
|
+
// Only match if the extra suffix looks like a version (not a different model variant)
|
|
272
|
+
if (suffix.split("-").every(isVersionSuffix)) {
|
|
273
|
+
if (khId.length > bestMatchLen) {
|
|
274
|
+
bestMatch = khPriceMap.get(`${khProvider}:${khId}`);
|
|
275
|
+
bestMatchLen = khId.length;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return bestMatch;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Compare
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
const results: ModelComparison[] = [];
|
|
288
|
+
const checkedProviders = new Set<string>();
|
|
289
|
+
|
|
290
|
+
for (const devModel of allDevModels) {
|
|
291
|
+
const rawProvider = devModel.provider?.toLowerCase() || "";
|
|
292
|
+
const khProvider = PROVIDER_MAP[rawProvider] || rawProvider;
|
|
293
|
+
|
|
294
|
+
if (!SUPPORTED_PROVIDERS.has(khProvider)) continue;
|
|
295
|
+
if (filterProvider && khProvider !== filterProvider && rawProvider !== filterProvider) continue;
|
|
296
|
+
if (freeOnly && devModel.cost?.input !== 0) continue;
|
|
297
|
+
|
|
298
|
+
checkedProviders.add(khProvider);
|
|
299
|
+
|
|
300
|
+
const modelId = devModel.id;
|
|
301
|
+
const devInputPrice = devModel.cost?.input ?? null;
|
|
302
|
+
const devOutputPrice = devModel.cost?.output ?? null;
|
|
303
|
+
|
|
304
|
+
// Check if model is registered in knowhow (exact or prefix match)
|
|
305
|
+
const khEntry = findKhEntry(khProvider, modelId);
|
|
306
|
+
const isRegistered = !!khEntry;
|
|
307
|
+
|
|
308
|
+
// Skip deprecated/retired models ā they shouldn't appear in coverage gaps
|
|
309
|
+
if (!includeDeprecated && (khEntry?.deprecated || khEntry?.limitedAvailability || khEntry?.type === "live")) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const khInputPrice = khEntry?.pricing?.input ?? null;
|
|
314
|
+
// For image models, prefer image_generation_per_1m_tokens for output comparison
|
|
315
|
+
// since models.dev reports image output as per-1M-tokens rate
|
|
316
|
+
const khOutputPrice =
|
|
317
|
+
(khEntry?.pricing as any)?.image_generation_per_1m_tokens ??
|
|
318
|
+
khEntry?.pricing?.output ??
|
|
319
|
+
null;
|
|
320
|
+
|
|
321
|
+
let status: ModelComparison["status"] = "ok";
|
|
322
|
+
let details: string | undefined;
|
|
323
|
+
|
|
324
|
+
if (!isRegistered) {
|
|
325
|
+
status = "missing-in-knowhow";
|
|
326
|
+
details = `Model not registered for provider "${khProvider}" in knowhow`;
|
|
327
|
+
} else if (!khEntry) {
|
|
328
|
+
status = "missing-pricing";
|
|
329
|
+
details = `Model registered but no pricing data in knowhow`;
|
|
330
|
+
} else {
|
|
331
|
+
// If models.dev says FREE but we have a :free variant registered, it's not a mismatch ā
|
|
332
|
+
// models.dev is tracking the free tier while we also track the paid tier separately.
|
|
333
|
+
const devIsFree = devInputPrice === 0 && devOutputPrice === 0;
|
|
334
|
+
const hasFreeVariant = khModelsByProvider.get(khProvider)?.has(`${modelId}:free`);
|
|
335
|
+
if (devIsFree && hasFreeVariant) {
|
|
336
|
+
// models.dev tracks the free tier; we have both free and paid ā skip comparison
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Check for price mismatches (allow 5% floating point tolerance)
|
|
340
|
+
const inputMismatch =
|
|
341
|
+
!(devIsFree && hasFreeVariant) &&
|
|
342
|
+
devInputPrice !== null &&
|
|
343
|
+
khInputPrice !== null &&
|
|
344
|
+
Math.abs((devInputPrice - khInputPrice) / Math.max(devInputPrice, 0.0001)) > 0.05;
|
|
345
|
+
const outputMismatch =
|
|
346
|
+
!(devIsFree && hasFreeVariant) &&
|
|
347
|
+
devOutputPrice !== null &&
|
|
348
|
+
khOutputPrice !== null &&
|
|
349
|
+
Math.abs((devOutputPrice - khOutputPrice) / Math.max(devOutputPrice, 0.0001)) > 0.05;
|
|
350
|
+
|
|
351
|
+
if (inputMismatch || outputMismatch) {
|
|
352
|
+
status = "price-mismatch";
|
|
353
|
+
const parts: string[] = [];
|
|
354
|
+
if (inputMismatch) {
|
|
355
|
+
parts.push(
|
|
356
|
+
`input: models.dev=$${devInputPrice}/1M, knowhow=$${khInputPrice}/1M`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (outputMismatch) {
|
|
360
|
+
parts.push(
|
|
361
|
+
`output: models.dev=$${devOutputPrice}/1M, knowhow=$${khOutputPrice}/1M`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
details = parts.join("; ");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (showAll || status !== "ok") {
|
|
369
|
+
results.push({
|
|
370
|
+
modelId,
|
|
371
|
+
provider: khProvider,
|
|
372
|
+
devProvider: rawProvider,
|
|
373
|
+
devInputPrice,
|
|
374
|
+
devOutputPrice,
|
|
375
|
+
khInputPrice,
|
|
376
|
+
khOutputPrice,
|
|
377
|
+
status,
|
|
378
|
+
details,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Also check for models in knowhow that aren't on models.dev
|
|
384
|
+
const devModelIds = new Set(allDevModels.map((m) => m.id));
|
|
385
|
+
for (const [khProvider, models] of khModelsByProvider.entries()) {
|
|
386
|
+
if (filterProvider && khProvider !== filterProvider) continue;
|
|
387
|
+
if (!SUPPORTED_PROVIDERS.has(khProvider)) continue;
|
|
388
|
+
for (const modelId of models) {
|
|
389
|
+
// A kh model is "extra" only if no dev model is a prefix of it
|
|
390
|
+
// (i.e. no dev model "grok-4" that would prefix-match kh "grok-4-0709")
|
|
391
|
+
const hasDevAlias = [...devModelIds].some(
|
|
392
|
+
(devId) =>
|
|
393
|
+
modelId === devId ||
|
|
394
|
+
modelId.startsWith(devId + "-") ||
|
|
395
|
+
modelId.startsWith(devId + "/")
|
|
396
|
+
);
|
|
397
|
+
if (!hasDevAlias) {
|
|
398
|
+
if (showAll) {
|
|
399
|
+
results.push({
|
|
400
|
+
modelId,
|
|
401
|
+
provider: khProvider,
|
|
402
|
+
devProvider: "",
|
|
403
|
+
devInputPrice: null,
|
|
404
|
+
devOutputPrice: null,
|
|
405
|
+
khInputPrice: null,
|
|
406
|
+
khOutputPrice: null,
|
|
407
|
+
status: "extra-in-knowhow",
|
|
408
|
+
details: "Model in knowhow but not found on models.dev",
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
// Output
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
const summary = generateReport(results, checkedProviders, allDevModels.length);
|
|
419
|
+
console.log(summary);
|
|
420
|
+
|
|
421
|
+
if (outputFile) {
|
|
422
|
+
const fs = await import("fs");
|
|
423
|
+
fs.writeFileSync(outputFile, summary, "utf-8");
|
|
424
|
+
console.log(`\nš Report written to ${outputFile}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
function generateReport(
|
|
430
|
+
results: ModelComparison[],
|
|
431
|
+
checkedProviders: Set<string>,
|
|
432
|
+
totalDevModels: number
|
|
433
|
+
): string {
|
|
434
|
+
const lines: string[] = [];
|
|
435
|
+
lines.push("# models.dev Pricing Comparison Report");
|
|
436
|
+
lines.push(`\n> Generated: ${new Date().toISOString()}`);
|
|
437
|
+
lines.push(`> models.dev total models fetched: ${totalDevModels}`);
|
|
438
|
+
lines.push(`> Checked providers: ${[...checkedProviders].sort().join(", ")}`);
|
|
439
|
+
lines.push("");
|
|
440
|
+
|
|
441
|
+
// Summary counts
|
|
442
|
+
const byStatus = new Map<string, ModelComparison[]>();
|
|
443
|
+
for (const r of results) {
|
|
444
|
+
const list = byStatus.get(r.status) || [];
|
|
445
|
+
list.push(r);
|
|
446
|
+
byStatus.set(r.status, list);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
lines.push("## Summary");
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push("| Status | Count |");
|
|
452
|
+
lines.push("|--------|-------|");
|
|
453
|
+
const statusLabels: Record<string, string> = {
|
|
454
|
+
ok: "ā
OK (registered & priced correctly)",
|
|
455
|
+
"missing-in-knowhow": "ā Missing in knowhow (not registered)",
|
|
456
|
+
"missing-pricing": "ā ļø Missing pricing data",
|
|
457
|
+
"price-mismatch": "š“ Price mismatch",
|
|
458
|
+
"extra-in-knowhow": "ā¹ļø In knowhow but not on models.dev",
|
|
459
|
+
};
|
|
460
|
+
for (const [status, label] of Object.entries(statusLabels)) {
|
|
461
|
+
const count = byStatus.get(status)?.length || 0;
|
|
462
|
+
if (count > 0) {
|
|
463
|
+
lines.push(`| ${label} | ${count} |`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
lines.push("");
|
|
467
|
+
|
|
468
|
+
// Detailed sections
|
|
469
|
+
const orderedStatuses = [
|
|
470
|
+
"price-mismatch",
|
|
471
|
+
"missing-pricing",
|
|
472
|
+
"missing-in-knowhow",
|
|
473
|
+
"extra-in-knowhow",
|
|
474
|
+
"ok",
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
for (const status of orderedStatuses) {
|
|
478
|
+
const items = byStatus.get(status);
|
|
479
|
+
if (!items || items.length === 0) continue;
|
|
480
|
+
|
|
481
|
+
const label = statusLabels[status] || status;
|
|
482
|
+
lines.push(`## ${label}`);
|
|
483
|
+
lines.push("");
|
|
484
|
+
lines.push(
|
|
485
|
+
"| Provider | Model ID | models.dev Input | models.dev Output | knowhow Input | knowhow Output | Details |"
|
|
486
|
+
);
|
|
487
|
+
lines.push(
|
|
488
|
+
"|----------|----------|-----------------|------------------|--------------|---------------|---------|"
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
for (const r of items.sort((a, b) =>
|
|
492
|
+
`${a.provider}/${a.modelId}`.localeCompare(`${b.provider}/${b.modelId}`)
|
|
493
|
+
)) {
|
|
494
|
+
const fmt = (v: number | null) =>
|
|
495
|
+
v === null ? "ā" : v === 0 ? "**FREE**" : `$${v.toFixed(3)}`;
|
|
496
|
+
lines.push(
|
|
497
|
+
`| ${r.provider} | \`${r.modelId}\` | ${fmt(r.devInputPrice)} | ${fmt(r.devOutputPrice)} | ${fmt(r.khInputPrice)} | ${fmt(r.khOutputPrice)} | ${r.details || ""} |`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
lines.push("");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return lines.join("\n");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
main().catch((e) => {
|
|
507
|
+
console.error("Fatal error:", e);
|
|
508
|
+
process.exit(1);
|
|
509
|
+
});
|