@tyvm/knowhow 0.0.103 → 0.0.105
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/package.json +1 -1
- package/src/clients/index.ts +100 -6
- package/src/clients/pricing/index.ts +2 -0
- package/src/clients/pricing/models.ts +252 -0
- package/src/clients/pricing/types.ts +29 -0
- package/src/clients/pricing/xai.ts +7 -6
- package/src/clients/xai.ts +2 -2
- package/src/fileSync.ts +15 -2
- package/src/hashes.ts +33 -0
- package/src/services/AgentSyncFs.ts +20 -12
- package/src/services/SyncedAgentWatcher.ts +13 -298
- package/src/services/index.ts +1 -0
- package/src/services/watchers/FsSyncer.ts +155 -0
- package/src/services/watchers/RemoteSyncer.ts +153 -0
- package/src/services/watchers/index.ts +2 -0
- package/src/types.ts +2 -2
- package/ts_build/package.json +1 -1
- package/ts_build/src/clients/index.d.ts +8 -0
- package/ts_build/src/clients/index.js +57 -2
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +2 -0
- package/ts_build/src/clients/pricing/index.js +8 -1
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/models.d.ts +8 -0
- package/ts_build/src/clients/pricing/models.js +173 -0
- package/ts_build/src/clients/pricing/models.js.map +1 -0
- package/ts_build/src/clients/pricing/types.d.ts +21 -0
- package/ts_build/src/clients/pricing/types.js +3 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -0
- package/ts_build/src/clients/pricing/xai.d.ts +3 -8
- package/ts_build/src/clients/pricing/xai.js +4 -4
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/xai.d.ts +9 -5
- package/ts_build/src/clients/xai.js +2 -2
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/fileSync.js +8 -1
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +2 -0
- package/ts_build/src/hashes.js +23 -0
- package/ts_build/src/hashes.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +5 -3
- package/ts_build/src/services/AgentSyncFs.js +17 -11
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +0 -51
- package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
- package/ts_build/src/services/index.d.ts +1 -0
- package/ts_build/src/services/index.js +1 -0
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/watchers/FsSyncer.d.ts +27 -0
- package/ts_build/src/services/watchers/FsSyncer.js +135 -0
- package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/index.d.ts +2 -0
- package/ts_build/src/services/watchers/index.js +19 -0
- package/ts_build/src/services/watchers/index.js.map +1 -0
- package/ts_build/src/types.d.ts +2 -2
- package/ts_build/src/types.js +2 -2
- package/ts_build/src/types.js.map +1 -1
package/package.json
CHANGED
package/src/clients/index.ts
CHANGED
|
@@ -30,6 +30,32 @@ import { ModelProvider } from "../types";
|
|
|
30
30
|
import { getConfig } from "../config";
|
|
31
31
|
import { loadKnowhowJwt, KNOWHOW_API_URL } from "../services/KnowhowClient";
|
|
32
32
|
import { ContextLimits } from "./contextLimits";
|
|
33
|
+
import { OpenAiTextPricing } from "./pricing/openai";
|
|
34
|
+
import { AnthropicTextPricing } from "./pricing/anthropic";
|
|
35
|
+
import { GeminiPricing } from "./pricing/google";
|
|
36
|
+
import {
|
|
37
|
+
XaiTextPricing,
|
|
38
|
+
XaiImagePricing,
|
|
39
|
+
XaiVideoPricing,
|
|
40
|
+
} from "./pricing/xai";
|
|
41
|
+
import type {
|
|
42
|
+
ModelPricing,
|
|
43
|
+
ModelType,
|
|
44
|
+
ModelCatalogEntry,
|
|
45
|
+
} from "./pricing/types";
|
|
46
|
+
export {
|
|
47
|
+
OpenAiTextPricing,
|
|
48
|
+
AnthropicTextPricing,
|
|
49
|
+
GeminiPricing,
|
|
50
|
+
XaiTextPricing,
|
|
51
|
+
XaiImagePricing,
|
|
52
|
+
XaiVideoPricing,
|
|
53
|
+
};
|
|
54
|
+
export type {
|
|
55
|
+
ModelPricing,
|
|
56
|
+
ModelType,
|
|
57
|
+
ModelCatalogEntry,
|
|
58
|
+
} from "./pricing/types";
|
|
33
59
|
|
|
34
60
|
// ---------------------------------------------------------------------------
|
|
35
61
|
// Built-in provider registry
|
|
@@ -219,7 +245,9 @@ export class AIClient {
|
|
|
219
245
|
|
|
220
246
|
if (!client) {
|
|
221
247
|
if (entry.provider === "knowhow") {
|
|
222
|
-
console.warn(
|
|
248
|
+
console.warn(
|
|
249
|
+
`⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`
|
|
250
|
+
);
|
|
223
251
|
}
|
|
224
252
|
continue;
|
|
225
253
|
}
|
|
@@ -499,17 +527,22 @@ export class AIClient {
|
|
|
499
527
|
}
|
|
500
528
|
|
|
501
529
|
const allModels = this.listAllModels();
|
|
502
|
-
const hasKnowhowModels =
|
|
503
|
-
allModels["knowhow"] && allModels["knowhow"].length > 0;
|
|
530
|
+
const hasKnowhowModels = allModels.knowhow && allModels.knowhow.length > 0;
|
|
504
531
|
const knowhowIsConfigured = Object.keys(allModels).includes("knowhow");
|
|
505
532
|
|
|
506
|
-
console.warn(
|
|
507
|
-
|
|
533
|
+
console.warn(
|
|
534
|
+
`⚠️ Unable to find model '${model}' for provider '${provider}'.`
|
|
535
|
+
);
|
|
536
|
+
console.warn(
|
|
537
|
+
` Available providers: ${Object.keys(allModels).join(", ") || "(none)"}`
|
|
538
|
+
);
|
|
508
539
|
|
|
509
540
|
if (!hasKnowhowModels && !knowhowIsConfigured) {
|
|
510
541
|
console.warn(` Tip: Run 'knowhow login' to enable Knowhow models.`);
|
|
511
542
|
} else if (!hasKnowhowModels) {
|
|
512
|
-
console.warn(
|
|
543
|
+
console.warn(
|
|
544
|
+
` Tip: The Knowhow provider returned no models. Try running 'knowhow login' to re-authenticate.`
|
|
545
|
+
);
|
|
513
546
|
}
|
|
514
547
|
|
|
515
548
|
return { provider, model };
|
|
@@ -763,6 +796,67 @@ export class AIClient {
|
|
|
763
796
|
if (contextLimit === undefined) return undefined;
|
|
764
797
|
return { contextLimit, threshold: contextLimit };
|
|
765
798
|
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Returns pricing information for all known models, derived from the
|
|
802
|
+
* provider pricing maps.
|
|
803
|
+
*
|
|
804
|
+
* @param modelId Optional model id filter (without provider prefix).
|
|
805
|
+
* If omitted, all models across all providers are returned.
|
|
806
|
+
*/
|
|
807
|
+
getPrices(modelId?: string): ModelCatalogEntry[] {
|
|
808
|
+
const results: ModelCatalogEntry[] = [];
|
|
809
|
+
|
|
810
|
+
const addModels = (
|
|
811
|
+
models: Record<string, string[]>,
|
|
812
|
+
type: ModelType,
|
|
813
|
+
pricingMap: Record<string, ModelPricing>
|
|
814
|
+
) => {
|
|
815
|
+
for (const [provider, ids] of Object.entries(models)) {
|
|
816
|
+
for (const id of ids) {
|
|
817
|
+
if (modelId && id !== modelId) continue;
|
|
818
|
+
if (!pricingMap[id]) continue;
|
|
819
|
+
|
|
820
|
+
const p = pricingMap[id];
|
|
821
|
+
results.push({
|
|
822
|
+
id,
|
|
823
|
+
provider,
|
|
824
|
+
type,
|
|
825
|
+
displayName: id,
|
|
826
|
+
pricing: p,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// Build a combined pricing map across all providers
|
|
833
|
+
const allTextPricing: Record<string, ModelPricing> = {
|
|
834
|
+
...OpenAiTextPricing,
|
|
835
|
+
...AnthropicTextPricing,
|
|
836
|
+
...GeminiPricing,
|
|
837
|
+
...XaiTextPricing,
|
|
838
|
+
};
|
|
839
|
+
const allImagePricing: Record<string, ModelPricing> = {
|
|
840
|
+
...XaiImagePricing,
|
|
841
|
+
};
|
|
842
|
+
const allVideoPricing: Record<string, ModelPricing> = {
|
|
843
|
+
...XaiVideoPricing,
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
addModels(this.completionModels, "completion", allTextPricing);
|
|
847
|
+
addModels(this.embeddingModels, "embedding", allTextPricing);
|
|
848
|
+
addModels(this.imageModels, "image", {
|
|
849
|
+
...allTextPricing,
|
|
850
|
+
...allImagePricing,
|
|
851
|
+
});
|
|
852
|
+
addModels(this.audioModels, "audio", allTextPricing);
|
|
853
|
+
addModels(this.videoModels, "video", {
|
|
854
|
+
...allTextPricing,
|
|
855
|
+
...allVideoPricing,
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
return results;
|
|
859
|
+
}
|
|
766
860
|
}
|
|
767
861
|
|
|
768
862
|
export const Clients = new AIClient();
|
|
@@ -2,3 +2,5 @@ export { OpenAiTextPricing } from "./openai";
|
|
|
2
2
|
export { GeminiTextPricing } from "./google";
|
|
3
3
|
export { AnthropicTextPricing } from "./anthropic";
|
|
4
4
|
export { XaiTextPricing, XaiImagePricing, XaiVideoPricing } from "./xai";
|
|
5
|
+
export { ALL_MODEL_CATALOG, OPENAI_MODEL_CATALOG, ANTHROPIC_MODEL_CATALOG, GOOGLE_MODEL_CATALOG, XAI_MODEL_CATALOG, USAGE_MARKUP_PERCENT } from "./models";
|
|
6
|
+
export type { ModelCatalogEntry, ModelType, ModelPricing as CatalogModelPricing } from "./types";
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model catalog: a single source of truth for all supported AI models,
|
|
3
|
+
* their providers, types, display names, and pricing.
|
|
4
|
+
*
|
|
5
|
+
* Pricing is in USD per 1M tokens (or per-image / per-second for media models).
|
|
6
|
+
* This is exported so the Knowhow backend (and other consumers) can import
|
|
7
|
+
* model/pricing data without duplicating it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Models, EmbeddingModels } from "../../types";
|
|
11
|
+
import { OpenAiTextPricing } from "./openai";
|
|
12
|
+
import { AnthropicTextPricing } from "./anthropic";
|
|
13
|
+
import { GeminiPricing } from "./google";
|
|
14
|
+
import { XaiTextPricing, XaiImagePricing, XaiVideoPricing } from "./xai";
|
|
15
|
+
import { ModelPricing, ModelType, ModelCatalogEntry } from "./types";
|
|
16
|
+
|
|
17
|
+
export { ModelPricing, ModelType, ModelCatalogEntry };
|
|
18
|
+
|
|
19
|
+
// ─── Platform markup ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** 2.5% platform markup applied on top of all provider base rates */
|
|
22
|
+
export const USAGE_MARKUP_PERCENT = 2.5 / 100;
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function completion(
|
|
27
|
+
id: string,
|
|
28
|
+
provider: string,
|
|
29
|
+
displayName: string,
|
|
30
|
+
pricingOverride?: Partial<ModelPricing>
|
|
31
|
+
): ModelCatalogEntry {
|
|
32
|
+
const base =
|
|
33
|
+
(OpenAiTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
34
|
+
(AnthropicTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
35
|
+
(GeminiPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
36
|
+
(XaiTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
37
|
+
{};
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
provider,
|
|
41
|
+
type: "completion",
|
|
42
|
+
displayName,
|
|
43
|
+
pricing: {
|
|
44
|
+
input: 0,
|
|
45
|
+
output: 0,
|
|
46
|
+
...base,
|
|
47
|
+
...pricingOverride,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function embedding(
|
|
53
|
+
id: string,
|
|
54
|
+
provider: string,
|
|
55
|
+
displayName: string,
|
|
56
|
+
input: number
|
|
57
|
+
): ModelCatalogEntry {
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
provider,
|
|
61
|
+
type: "embedding",
|
|
62
|
+
displayName,
|
|
63
|
+
pricing: { input, output: 0 },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function image(
|
|
68
|
+
id: string,
|
|
69
|
+
provider: string,
|
|
70
|
+
displayName: string,
|
|
71
|
+
pricing: Partial<ModelPricing>
|
|
72
|
+
): ModelCatalogEntry {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
provider,
|
|
76
|
+
type: "image",
|
|
77
|
+
displayName,
|
|
78
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function video(
|
|
83
|
+
id: string,
|
|
84
|
+
provider: string,
|
|
85
|
+
displayName: string,
|
|
86
|
+
video_generation: number
|
|
87
|
+
): ModelCatalogEntry {
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
provider,
|
|
91
|
+
type: "video",
|
|
92
|
+
displayName,
|
|
93
|
+
pricing: { input: 0, output: 0, video_generation },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function audio(
|
|
98
|
+
id: string,
|
|
99
|
+
provider: string,
|
|
100
|
+
displayName: string,
|
|
101
|
+
pricing: Partial<ModelPricing>
|
|
102
|
+
): ModelCatalogEntry {
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
provider,
|
|
106
|
+
type: "audio",
|
|
107
|
+
displayName,
|
|
108
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function transaction(
|
|
113
|
+
id: string,
|
|
114
|
+
provider: string,
|
|
115
|
+
displayName: string,
|
|
116
|
+
pricing: Partial<ModelPricing>
|
|
117
|
+
): ModelCatalogEntry {
|
|
118
|
+
return {
|
|
119
|
+
id,
|
|
120
|
+
provider,
|
|
121
|
+
type: "transaction",
|
|
122
|
+
displayName,
|
|
123
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── OpenAI ───────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export const OPENAI_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
130
|
+
// Completion
|
|
131
|
+
completion(Models.openai.GPT_4o, "openai", "GPT-4o"),
|
|
132
|
+
completion(Models.openai.GPT_4o_Mini, "openai", "GPT-4o Mini"),
|
|
133
|
+
completion(Models.openai.GPT_41, "openai", "GPT-4.1"),
|
|
134
|
+
completion(Models.openai.GPT_41_Mini, "openai", "GPT-4.1 Mini"),
|
|
135
|
+
completion(Models.openai.GPT_41_Nano, "openai", "GPT-4.1 Nano"),
|
|
136
|
+
completion(Models.openai.GPT_45, "openai", "GPT-4.5 Preview"),
|
|
137
|
+
completion(Models.openai.o1, "openai", "o1"),
|
|
138
|
+
completion(Models.openai.o1_Mini, "openai", "o1 Mini"),
|
|
139
|
+
completion(Models.openai.o3, "openai", "o3"),
|
|
140
|
+
completion(Models.openai.o3_Mini, "openai", "o3 Mini"),
|
|
141
|
+
completion(Models.openai.o4_Mini, "openai", "o4 Mini"),
|
|
142
|
+
// Embedding
|
|
143
|
+
embedding(EmbeddingModels.openai.EmbeddingAda2, "openai", "Embedding Ada 002", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingAda2]?.input ?? 0.1),
|
|
144
|
+
embedding(EmbeddingModels.openai.EmbeddingLarge3, "openai", "Embedding 3 Large", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingLarge3]?.input ?? 0.13),
|
|
145
|
+
embedding(EmbeddingModels.openai.EmbeddingSmall3, "openai", "Embedding 3 Small", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingSmall3]?.input ?? 0.02),
|
|
146
|
+
// Image generation
|
|
147
|
+
image(Models.openai.DALL_E_3, "openai", "DALL-E 3", { image_generation: 0.04 }),
|
|
148
|
+
image(Models.openai.DALL_E_2, "openai", "DALL-E 2", { image_generation: 0.02 }),
|
|
149
|
+
image(Models.openai.GPT_Image_15, "openai", "GPT Image 1.5", { input: OpenAiTextPricing[Models.openai.GPT_Image_15]?.input ?? 5.0, output: OpenAiTextPricing[Models.openai.GPT_Image_15]?.output ?? 10.0 }),
|
|
150
|
+
image(Models.openai.GPT_Image_1_Mini, "openai", "GPT Image 1 Mini", { input: OpenAiTextPricing[Models.openai.GPT_Image_1_Mini]?.input ?? 2.0 }),
|
|
151
|
+
// Video generation
|
|
152
|
+
video(Models.openai.Sora, "openai", "Sora", 0.012),
|
|
153
|
+
video(Models.openai.Sora_2, "openai", "Sora 2", 0.015),
|
|
154
|
+
// Audio
|
|
155
|
+
audio(Models.openai.TTS_1, "openai", "TTS-1", { input: 15.0 }),
|
|
156
|
+
audio(Models.openai.Whisper_1, "openai", "Whisper 1", { input: 0.006 }),
|
|
157
|
+
// Transaction / Search
|
|
158
|
+
transaction(Models.openai.GPT_4o_Mini_Search, "openai", "GPT-4o Mini Search", { input: OpenAiTextPricing[Models.openai.GPT_4o_Mini_Search]?.input ?? 0.15, output: OpenAiTextPricing[Models.openai.GPT_4o_Mini_Search]?.output ?? 0.6 }),
|
|
159
|
+
transaction(Models.openai.GPT_4o_Search, "openai", "GPT-4o Search", { input: OpenAiTextPricing[Models.openai.GPT_4o_Search]?.input ?? 2.5, output: OpenAiTextPricing[Models.openai.GPT_4o_Search]?.output ?? 10.0 }),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
// ─── Anthropic ────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
export const ANTHROPIC_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
165
|
+
completion(Models.anthropic.Opus4_5, "anthropic", "Claude Opus 4.5"),
|
|
166
|
+
completion(Models.anthropic.Sonnet4_5, "anthropic", "Claude Sonnet 4.5"),
|
|
167
|
+
completion(Models.anthropic.Opus4, "anthropic", "Claude Opus 4"),
|
|
168
|
+
completion(Models.anthropic.Sonnet4, "anthropic", "Claude Sonnet 4"),
|
|
169
|
+
completion(Models.anthropic.Haiku4_5, "anthropic", "Claude Haiku 4.5"),
|
|
170
|
+
completion(Models.anthropic.Sonnet3_7, "anthropic", "Claude Sonnet 3.7"),
|
|
171
|
+
completion(Models.anthropic.Sonnet3_5, "anthropic", "Claude Sonnet 3.5"),
|
|
172
|
+
completion(Models.anthropic.Haiku3, "anthropic", "Claude Haiku 3"),
|
|
173
|
+
completion(Models.anthropic.Opus3, "anthropic", "Claude Opus 3"),
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
// ─── Google ───────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
export const GOOGLE_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
179
|
+
// Completion
|
|
180
|
+
completion(Models.google.Gemini_25_Pro, "google", "Gemini 2.5 Pro"),
|
|
181
|
+
completion(Models.google.Gemini_25_Flash, "google", "Gemini 2.5 Flash"),
|
|
182
|
+
completion(Models.google.Gemini_25_Flash_Lite, "google", "Gemini 2.5 Flash Lite"),
|
|
183
|
+
completion(Models.google.Gemini_20_Flash, "google", "Gemini 2.0 Flash"),
|
|
184
|
+
completion(Models.google.Gemini_15_Pro, "google", "Gemini 1.5 Pro"),
|
|
185
|
+
completion(Models.google.Gemini_15_Flash, "google", "Gemini 1.5 Flash"),
|
|
186
|
+
completion(Models.google.Gemini_15_Flash_8B, "google", "Gemini 1.5 Flash 8B"),
|
|
187
|
+
// Embedding
|
|
188
|
+
embedding(EmbeddingModels.google.Gemini_Embedding, "google", "Gemini Embedding", GeminiPricing[EmbeddingModels.google.Gemini_Embedding]?.input ?? 0),
|
|
189
|
+
// Image generation
|
|
190
|
+
image(Models.google.Gemini_20_Flash_Preview_Image_Generation, "google", "Gemini 2.0 Flash Image", {
|
|
191
|
+
input: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.input ?? 0.1,
|
|
192
|
+
output: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.output ?? 0.4,
|
|
193
|
+
image_generation: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.image_generation ?? 0.039,
|
|
194
|
+
}),
|
|
195
|
+
image(Models.google.Gemini_25_Flash_Image, "google", "Gemini 2.5 Flash Image", {
|
|
196
|
+
input: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.input ?? 0.3,
|
|
197
|
+
output: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.output ?? 0.039,
|
|
198
|
+
image_generation: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.image_generation ?? 0.039,
|
|
199
|
+
}),
|
|
200
|
+
image(Models.google.Gemini_31_Flash_Image_Preview, "google", "Gemini 3.1 Flash Image", {
|
|
201
|
+
input: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.input ?? 0.5,
|
|
202
|
+
output: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.output ?? 3.0,
|
|
203
|
+
image_generation: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.image_generation ?? 0.045,
|
|
204
|
+
}),
|
|
205
|
+
image(Models.google.Gemini_3_Pro_Image_Preview, "google", "Gemini 3 Pro Image", {
|
|
206
|
+
input: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.input ?? 2.0,
|
|
207
|
+
output: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.output ?? 12.0,
|
|
208
|
+
image_generation: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.image_generation ?? 0.134,
|
|
209
|
+
}),
|
|
210
|
+
image(Models.google.Imagen_3, "google", "Imagen 4", { image_generation: GeminiPricing[Models.google.Imagen_3]?.image_generation ?? 0.04 }),
|
|
211
|
+
image(Models.google.Imagen_4_Fast, "google", "Imagen 4 Fast", { image_generation: GeminiPricing[Models.google.Imagen_4_Fast]?.image_generation ?? 0.02 }),
|
|
212
|
+
image(Models.google.Imagen_4_Ultra, "google", "Imagen 4 Ultra", { image_generation: GeminiPricing[Models.google.Imagen_4_Ultra]?.image_generation ?? 0.06 }),
|
|
213
|
+
// Video generation
|
|
214
|
+
video(Models.google.Veo_2, "google", "Veo 2", GeminiPricing[Models.google.Veo_2]?.video_generation ?? 0.35),
|
|
215
|
+
video(Models.google.Veo_3, "google", "Veo 3", GeminiPricing[Models.google.Veo_3]?.video_generation ?? 0.4),
|
|
216
|
+
video(Models.google.Veo_3_Fast, "google", "Veo 3 Fast", GeminiPricing[Models.google.Veo_3_Fast]?.video_generation ?? 0.1),
|
|
217
|
+
// Audio (TTS)
|
|
218
|
+
audio(Models.google.Gemini_25_Flash_TTS, "google", "Gemini 2.5 Flash TTS", {
|
|
219
|
+
input: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.input ?? 0.5,
|
|
220
|
+
output_audio: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.output_audio ?? 10.0,
|
|
221
|
+
output: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.output_audio ?? 10.0,
|
|
222
|
+
}),
|
|
223
|
+
audio(Models.google.Gemini_25_Pro_TTS, "google", "Gemini 2.5 Pro TTS", {
|
|
224
|
+
input: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.input ?? 1.0,
|
|
225
|
+
output_audio: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.output_audio ?? 20.0,
|
|
226
|
+
output: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.output_audio ?? 20.0,
|
|
227
|
+
}),
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// ─── xAI ──────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
export const XAI_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
233
|
+
completion(Models.xai.Grok4, "xai", "Grok 4"),
|
|
234
|
+
completion(Models.xai.Grok3Beta, "xai", "Grok 3 Beta"),
|
|
235
|
+
completion(Models.xai.Grok3MiniBeta, "xai", "Grok 3 Mini Beta"),
|
|
236
|
+
completion(Models.xai.Grok3FastBeta, "xai", "Grok 3 Fast Beta"),
|
|
237
|
+
completion(Models.xai.Grok21212, "xai", "Grok 2"),
|
|
238
|
+
// Image generation
|
|
239
|
+
image(Models.xai.GrokImagineImage, "xai", "Grok Imagine Image", { image_generation: XaiImagePricing["grok-imagine-image"]?.image_generation ?? 0.02 }),
|
|
240
|
+
image("grok-2-image-1212", "xai", "Grok 2 Image", { image_generation: XaiImagePricing["grok-2-image-1212"]?.image_generation ?? 0.07 }),
|
|
241
|
+
// Video generation
|
|
242
|
+
video(Models.xai.GrokImagineVideo, "xai", "Grok Imagine Video", XaiVideoPricing["grok-imagine-video"]?.video_generation ?? 0.05),
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// ─── Combined catalog ─────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
export const ALL_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
248
|
+
...OPENAI_MODEL_CATALOG,
|
|
249
|
+
...ANTHROPIC_MODEL_CATALOG,
|
|
250
|
+
...GOOGLE_MODEL_CATALOG,
|
|
251
|
+
...XAI_MODEL_CATALOG,
|
|
252
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ModelType =
|
|
2
|
+
| "completion"
|
|
3
|
+
| "embedding"
|
|
4
|
+
| "image"
|
|
5
|
+
| "audio"
|
|
6
|
+
| "video"
|
|
7
|
+
| "transaction";
|
|
8
|
+
|
|
9
|
+
export interface ModelPricing {
|
|
10
|
+
input?: number;
|
|
11
|
+
output?: number;
|
|
12
|
+
cached_input?: number;
|
|
13
|
+
cache_write?: number;
|
|
14
|
+
cache_hit?: number;
|
|
15
|
+
input_audio?: number;
|
|
16
|
+
output_audio?: number;
|
|
17
|
+
input_gt_200k?: number;
|
|
18
|
+
output_gt_200k?: number;
|
|
19
|
+
image_generation?: number;
|
|
20
|
+
video_generation?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ModelCatalogEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
provider: string;
|
|
26
|
+
type: ModelType;
|
|
27
|
+
displayName: string;
|
|
28
|
+
pricing: ModelPricing;
|
|
29
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Models } from "../../types";
|
|
2
|
+
import { ModelPricing } from "./types";
|
|
2
3
|
|
|
3
4
|
export const XaiTextPricing = {
|
|
4
5
|
|
|
@@ -60,14 +61,14 @@ export const XaiTextPricing = {
|
|
|
60
61
|
|
|
61
62
|
// Image generation pricing: per image
|
|
62
63
|
// Based on https://docs.x.ai/developers/models
|
|
63
|
-
export const XaiImagePricing = {
|
|
64
|
-
"grok-imagine-image-pro": 0.07,
|
|
65
|
-
"grok-imagine-image": 0.02,
|
|
66
|
-
"grok-2-image-1212": 0.07,
|
|
64
|
+
export const XaiImagePricing: Record<string, ModelPricing> = {
|
|
65
|
+
"grok-imagine-image-pro": { image_generation: 0.07 },
|
|
66
|
+
"grok-imagine-image": { image_generation: 0.02 },
|
|
67
|
+
"grok-2-image-1212": { image_generation: 0.07 },
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
// Video generation pricing: $0.05 per second
|
|
70
71
|
// Based on https://docs.x.ai/developers/models
|
|
71
|
-
export const XaiVideoPricing = {
|
|
72
|
-
"grok-imagine-video": 0.05, // per second
|
|
72
|
+
export const XaiVideoPricing: Record<string, ModelPricing> = {
|
|
73
|
+
"grok-imagine-video": { video_generation: 0.05 }, // per second
|
|
73
74
|
};
|
package/src/clients/xai.ts
CHANGED
|
@@ -175,7 +175,7 @@ export class GenericXAIClient implements GenericClient {
|
|
|
175
175
|
// Calculate cost based on model name
|
|
176
176
|
const imageModel = options.model || "grok-imagine-image";
|
|
177
177
|
const costPerImage =
|
|
178
|
-
XaiImagePricing[imageModel as keyof typeof XaiImagePricing] || 0.02;
|
|
178
|
+
XaiImagePricing[imageModel as keyof typeof XaiImagePricing]?.image_generation || 0.02;
|
|
179
179
|
const usdCost = (options.n || 1) * costPerImage;
|
|
180
180
|
|
|
181
181
|
return {
|
|
@@ -250,7 +250,7 @@ export class GenericXAIClient implements GenericClient {
|
|
|
250
250
|
// Return immediately with the jobId – do NOT poll here.
|
|
251
251
|
// Use getVideoStatus() to poll and downloadVideo() to fetch the result.
|
|
252
252
|
const duration = options.duration || 5;
|
|
253
|
-
const pricePerSecond = XaiVideoPricing[model] || 0.07;
|
|
253
|
+
const pricePerSecond = XaiVideoPricing[model]?.video_generation || 0.07;
|
|
254
254
|
const usdCost = duration * pricePerSecond;
|
|
255
255
|
|
|
256
256
|
return {
|
package/src/fileSync.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { loadJwt } from "./login";
|
|
|
5
5
|
import { getConfig } from "./config";
|
|
6
6
|
import { services } from "./services";
|
|
7
7
|
import { S3Service } from "./services/S3";
|
|
8
|
-
import { getHashes, hasFileChangedSinceUpload, saveUploadHash, isLocalFileMatchingRemote } from "./hashes";
|
|
8
|
+
import { getHashes, hasFileChangedSinceUpload, saveUploadHash, isLocalFileMatchingRemote, isLocalFileMatchingDownloadHash, saveDownloadHash } from "./hashes";
|
|
9
9
|
|
|
10
10
|
export interface FileSyncOptions {
|
|
11
11
|
upload?: boolean;
|
|
@@ -145,12 +145,21 @@ async function downloadFile(
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
try {
|
|
148
|
+
// Fast-path: check stored download hash before hitting the API
|
|
149
|
+
const hashes = await getHashes();
|
|
150
|
+
if (await isLocalFileMatchingDownloadHash(localPath, hashes)) {
|
|
151
|
+
console.log(` ✓ Skipping ${localPath} (matches stored download hash)`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
148
155
|
// Get presigned download URL + remote checksum
|
|
149
156
|
const { downloadUrl, checksumSHA256 } = await client.getOrgFilePresignedDownloadUrl(remotePath);
|
|
150
157
|
|
|
151
158
|
// Skip if local file matches remote checksum
|
|
152
159
|
if (isLocalFileMatchingRemote(localPath, checksumSHA256)) {
|
|
153
160
|
console.log(` ✓ Skipping ${localPath} (matches remote checksum)`);
|
|
161
|
+
// Store the hash so future syncs can skip without hitting the API
|
|
162
|
+
await saveDownloadHash(localPath);
|
|
154
163
|
return;
|
|
155
164
|
}
|
|
156
165
|
|
|
@@ -163,6 +172,9 @@ async function downloadFile(
|
|
|
163
172
|
// Download file using presigned URL
|
|
164
173
|
await s3Service.downloadFromPresignedUrl(downloadUrl, localPath);
|
|
165
174
|
|
|
175
|
+
// Save download hash so we can skip unchanged files next time
|
|
176
|
+
await saveDownloadHash(localPath);
|
|
177
|
+
|
|
166
178
|
// Get file size for logging
|
|
167
179
|
const stats = fs.statSync(localPath);
|
|
168
180
|
console.log(` ✓ Downloaded ${stats.size} bytes`);
|
|
@@ -284,7 +296,8 @@ async function downloadDirectory(
|
|
|
284
296
|
const fullPath = f.folderPath.endsWith("/")
|
|
285
297
|
? f.folderPath + f.fileName
|
|
286
298
|
: f.folderPath + "/" + f.fileName;
|
|
287
|
-
|
|
299
|
+
// Exclude directory placeholder entries (empty fileName) and only include real files
|
|
300
|
+
return f.fileName !== "" && fullPath.startsWith(remoteDir);
|
|
288
301
|
});
|
|
289
302
|
|
|
290
303
|
if (matchingFiles.length === 0) {
|
package/src/hashes.ts
CHANGED
|
@@ -73,6 +73,8 @@ export async function saveAllFileHashes(files: string[], promptHash: string) {
|
|
|
73
73
|
|
|
74
74
|
const UPLOAD_KEY = "upload";
|
|
75
75
|
|
|
76
|
+
const DOWNLOAD_KEY = "download";
|
|
77
|
+
|
|
76
78
|
/**
|
|
77
79
|
* Returns true if the file has changed since the last successful upload
|
|
78
80
|
* (or if it has never been uploaded before)
|
|
@@ -101,6 +103,37 @@ export async function saveUploadHash(localPath: string) {
|
|
|
101
103
|
await saveHashes(hashes);
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Returns true if the local file already matches the hash stored from
|
|
108
|
+
* the last successful download, meaning we can skip the download.
|
|
109
|
+
*/
|
|
110
|
+
export async function isLocalFileMatchingDownloadHash(
|
|
111
|
+
localPath: string,
|
|
112
|
+
hashes: any
|
|
113
|
+
): Promise<boolean> {
|
|
114
|
+
if (!fs.existsSync(localPath)) return false;
|
|
115
|
+
const storedHash = hashes[localPath]?.[DOWNLOAD_KEY];
|
|
116
|
+
if (!storedHash) return false;
|
|
117
|
+
const content = fs.readFileSync(localPath);
|
|
118
|
+
const currentHash = crypto.createHash("sha256").update(content).digest("base64");
|
|
119
|
+
return storedHash === currentHash;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Saves the SHA-256 hash of the file after a successful download so we can
|
|
124
|
+
* skip unchanged files on the next sync.
|
|
125
|
+
*/
|
|
126
|
+
export async function saveDownloadHash(localPath: string) {
|
|
127
|
+
const hashes = await getHashes();
|
|
128
|
+
const content = fs.readFileSync(localPath);
|
|
129
|
+
const currentHash = crypto.createHash("sha256").update(content).digest("base64");
|
|
130
|
+
if (!hashes[localPath]) {
|
|
131
|
+
hashes[localPath] = { fileHash: currentHash, promptHash: "" };
|
|
132
|
+
}
|
|
133
|
+
hashes[localPath][DOWNLOAD_KEY] = currentHash;
|
|
134
|
+
await saveHashes(hashes);
|
|
135
|
+
}
|
|
136
|
+
|
|
104
137
|
/**
|
|
105
138
|
* Compute SHA-256 of a local file, returned as base64 (matches S3 encoding)
|
|
106
139
|
*/
|
|
@@ -18,13 +18,17 @@ export interface FsSyncOptions {
|
|
|
18
18
|
* Creates files in .knowhow/processes/agents/{taskId}/ for status and input
|
|
19
19
|
*/
|
|
20
20
|
export class AgentSyncFs {
|
|
21
|
+
/** Shared cleanup interval across all instances to avoid duplicate cleanup runs */
|
|
22
|
+
private static sharedCleanupInterval: NodeJS.Timeout | null = null;
|
|
23
|
+
private static sharedBasePath: string = ".knowhow/processes/agents";
|
|
24
|
+
private static cleanupStarted: boolean = false;
|
|
25
|
+
|
|
21
26
|
private taskId: string | undefined;
|
|
22
27
|
private basePath: string = ".knowhow/processes/agents";
|
|
23
28
|
private taskPath: string | undefined;
|
|
24
29
|
private eventHandlersSetup: boolean = false;
|
|
25
30
|
private watcher: ReturnType<typeof watch> | null = null;
|
|
26
31
|
private lastInputContent: string = "";
|
|
27
|
-
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
28
32
|
private finalizationPromise: Promise<void> | null = null;
|
|
29
33
|
private agent: BaseAgent | undefined;
|
|
30
34
|
private threadUpdateHandler: ((...args: any[]) => void) | undefined;
|
|
@@ -38,7 +42,7 @@ export class AgentSyncFs {
|
|
|
38
42
|
|
|
39
43
|
constructor() {
|
|
40
44
|
// Start cleanup process when created
|
|
41
|
-
|
|
45
|
+
AgentSyncFs.startSharedCleanupProcess();
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/**
|
|
@@ -372,9 +376,9 @@ export class AgentSyncFs {
|
|
|
372
376
|
/**
|
|
373
377
|
* Clean up old task directories (older than 3 days)
|
|
374
378
|
*/
|
|
375
|
-
private async cleanupOldTasks(): Promise<void> {
|
|
379
|
+
private static async cleanupOldTasks(): Promise<void> {
|
|
376
380
|
try {
|
|
377
|
-
const agentsPath =
|
|
381
|
+
const agentsPath = AgentSyncFs.sharedBasePath;
|
|
378
382
|
|
|
379
383
|
// Check if directory exists
|
|
380
384
|
try {
|
|
@@ -414,23 +418,27 @@ export class AgentSyncFs {
|
|
|
414
418
|
/**
|
|
415
419
|
* Start periodic cleanup process
|
|
416
420
|
*/
|
|
417
|
-
private
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
+
private static startSharedCleanupProcess(): void {
|
|
422
|
+
if (AgentSyncFs.cleanupStarted) return;
|
|
423
|
+
AgentSyncFs.cleanupStarted = true;
|
|
424
|
+
|
|
425
|
+
// Run cleanup every hour (shared across all instances)
|
|
426
|
+
AgentSyncFs.sharedCleanupInterval = setInterval(() => {
|
|
427
|
+
AgentSyncFs.cleanupOldTasks();
|
|
421
428
|
}, 60 * 60 * 1000);
|
|
422
429
|
|
|
423
430
|
// Also run once on startup
|
|
424
|
-
|
|
431
|
+
AgentSyncFs.cleanupOldTasks();
|
|
425
432
|
}
|
|
426
433
|
|
|
427
434
|
/**
|
|
428
435
|
* Stop cleanup process
|
|
429
436
|
*/
|
|
430
437
|
stopCleanup(): void {
|
|
431
|
-
if (
|
|
432
|
-
clearInterval(
|
|
433
|
-
|
|
438
|
+
if (AgentSyncFs.sharedCleanupInterval) {
|
|
439
|
+
clearInterval(AgentSyncFs.sharedCleanupInterval);
|
|
440
|
+
AgentSyncFs.sharedCleanupInterval = null;
|
|
441
|
+
AgentSyncFs.cleanupStarted = false;
|
|
434
442
|
}
|
|
435
443
|
}
|
|
436
444
|
|