@tyvm/knowhow 0.0.102 → 0.0.104
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 +2 -2
- package/src/agents/base/base.ts +3 -0
- package/src/chat/modules/AgentModule.ts +24 -10
- package/src/chat/modules/InternalChatModule.ts +6 -0
- package/src/chat/modules/RemoteSyncModule.ts +447 -0
- package/src/chat/types.ts +2 -0
- package/src/clients/pricing/catalog.ts +287 -0
- package/src/clients/pricing/index.ts +2 -0
- package/src/config.ts +2 -0
- package/src/fileSync.ts +15 -2
- package/src/hashes.ts +33 -0
- package/src/services/AgentSyncFs.ts +44 -14
- package/src/services/AgentSyncKnowhowWeb.ts +27 -5
- package/src/services/KnowhowClient.ts +61 -0
- package/src/services/SessionManager.ts +2 -0
- package/src/services/script-execution/ScriptPolicy.ts +0 -44
- package/src/types.ts +3 -0
- package/src/worker.ts +70 -4
- package/ts_build/package.json +2 -2
- package/ts_build/src/agents/base/base.js +1 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +2 -1
- package/ts_build/src/chat/modules/AgentModule.js +12 -7
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +6 -0
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +27 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js +282 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -0
- package/ts_build/src/chat/types.d.ts +2 -0
- package/ts_build/src/clients/pricing/catalog.d.ts +28 -0
- package/ts_build/src/clients/pricing/catalog.js +179 -0
- package/ts_build/src/clients/pricing/catalog.js.map +1 -0
- 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/config.js +1 -0
- package/ts_build/src/config.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 +6 -3
- package/ts_build/src/services/AgentSyncFs.js +30 -13
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +1 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +13 -2
- package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +21 -0
- package/ts_build/src/services/KnowhowClient.js +10 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/Mcp.d.ts +219 -406
- package/ts_build/src/services/SessionManager.js +2 -0
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -35
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
- package/ts_build/src/types.d.ts +2 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.js +51 -2
- package/ts_build/src/worker.js.map +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
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
|
+
|
|
16
|
+
export type ModelType =
|
|
17
|
+
| "completion"
|
|
18
|
+
| "embedding"
|
|
19
|
+
| "image"
|
|
20
|
+
| "audio"
|
|
21
|
+
| "video"
|
|
22
|
+
| "transaction";
|
|
23
|
+
|
|
24
|
+
export interface ModelPricing {
|
|
25
|
+
input: number;
|
|
26
|
+
output: number;
|
|
27
|
+
cached_input?: number;
|
|
28
|
+
cache_write?: number;
|
|
29
|
+
cache_hit?: number;
|
|
30
|
+
input_audio?: number;
|
|
31
|
+
output_audio?: number;
|
|
32
|
+
input_gt_200k?: number;
|
|
33
|
+
output_gt_200k?: number;
|
|
34
|
+
image_generation?: number;
|
|
35
|
+
video_generation?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ModelCatalogEntry {
|
|
39
|
+
id: string;
|
|
40
|
+
provider: string;
|
|
41
|
+
type: ModelType;
|
|
42
|
+
displayName: string;
|
|
43
|
+
pricing: ModelPricing;
|
|
44
|
+
/** Markup applied on top of base pricing (as a fraction, e.g. 0.025 = 2.5%) */
|
|
45
|
+
markupPercent: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Platform markup ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** 2.5% platform markup applied on top of all provider base rates */
|
|
51
|
+
export const USAGE_MARKUP_PERCENT = 2.5 / 100;
|
|
52
|
+
|
|
53
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function completion(
|
|
56
|
+
id: string,
|
|
57
|
+
provider: string,
|
|
58
|
+
displayName: string,
|
|
59
|
+
pricingOverride?: Partial<ModelPricing>
|
|
60
|
+
): ModelCatalogEntry {
|
|
61
|
+
const base =
|
|
62
|
+
(OpenAiTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
63
|
+
(AnthropicTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
64
|
+
(GeminiPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
65
|
+
(XaiTextPricing as Record<string, Partial<ModelPricing>>)[id] ||
|
|
66
|
+
{};
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
provider,
|
|
70
|
+
type: "completion",
|
|
71
|
+
displayName,
|
|
72
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
73
|
+
pricing: {
|
|
74
|
+
input: 0,
|
|
75
|
+
output: 0,
|
|
76
|
+
...base,
|
|
77
|
+
...pricingOverride,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function embedding(
|
|
83
|
+
id: string,
|
|
84
|
+
provider: string,
|
|
85
|
+
displayName: string,
|
|
86
|
+
input: number
|
|
87
|
+
): ModelCatalogEntry {
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
provider,
|
|
91
|
+
type: "embedding",
|
|
92
|
+
displayName,
|
|
93
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
94
|
+
pricing: { input, output: 0 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function image(
|
|
99
|
+
id: string,
|
|
100
|
+
provider: string,
|
|
101
|
+
displayName: string,
|
|
102
|
+
pricing: Partial<ModelPricing>
|
|
103
|
+
): ModelCatalogEntry {
|
|
104
|
+
return {
|
|
105
|
+
id,
|
|
106
|
+
provider,
|
|
107
|
+
type: "image",
|
|
108
|
+
displayName,
|
|
109
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
110
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function video(
|
|
115
|
+
id: string,
|
|
116
|
+
provider: string,
|
|
117
|
+
displayName: string,
|
|
118
|
+
video_generation: number
|
|
119
|
+
): ModelCatalogEntry {
|
|
120
|
+
return {
|
|
121
|
+
id,
|
|
122
|
+
provider,
|
|
123
|
+
type: "video",
|
|
124
|
+
displayName,
|
|
125
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
126
|
+
pricing: { input: 0, output: 0, video_generation },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function audio(
|
|
131
|
+
id: string,
|
|
132
|
+
provider: string,
|
|
133
|
+
displayName: string,
|
|
134
|
+
pricing: Partial<ModelPricing>
|
|
135
|
+
): ModelCatalogEntry {
|
|
136
|
+
return {
|
|
137
|
+
id,
|
|
138
|
+
provider,
|
|
139
|
+
type: "audio",
|
|
140
|
+
displayName,
|
|
141
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
142
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function transaction(
|
|
147
|
+
id: string,
|
|
148
|
+
provider: string,
|
|
149
|
+
displayName: string,
|
|
150
|
+
pricing: Partial<ModelPricing>
|
|
151
|
+
): ModelCatalogEntry {
|
|
152
|
+
return {
|
|
153
|
+
id,
|
|
154
|
+
provider,
|
|
155
|
+
type: "transaction",
|
|
156
|
+
displayName,
|
|
157
|
+
markupPercent: USAGE_MARKUP_PERCENT,
|
|
158
|
+
pricing: { input: 0, output: 0, ...pricing },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── OpenAI ───────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
export const OPENAI_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
165
|
+
// Completion
|
|
166
|
+
completion(Models.openai.GPT_4o, "openai", "GPT-4o"),
|
|
167
|
+
completion(Models.openai.GPT_4o_Mini, "openai", "GPT-4o Mini"),
|
|
168
|
+
completion(Models.openai.GPT_41, "openai", "GPT-4.1"),
|
|
169
|
+
completion(Models.openai.GPT_41_Mini, "openai", "GPT-4.1 Mini"),
|
|
170
|
+
completion(Models.openai.GPT_41_Nano, "openai", "GPT-4.1 Nano"),
|
|
171
|
+
completion(Models.openai.GPT_45, "openai", "GPT-4.5 Preview"),
|
|
172
|
+
completion(Models.openai.o1, "openai", "o1"),
|
|
173
|
+
completion(Models.openai.o1_Mini, "openai", "o1 Mini"),
|
|
174
|
+
completion(Models.openai.o3, "openai", "o3"),
|
|
175
|
+
completion(Models.openai.o3_Mini, "openai", "o3 Mini"),
|
|
176
|
+
completion(Models.openai.o4_Mini, "openai", "o4 Mini"),
|
|
177
|
+
// Embedding
|
|
178
|
+
embedding(EmbeddingModels.openai.EmbeddingAda2, "openai", "Embedding Ada 002", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingAda2]?.input ?? 0.1),
|
|
179
|
+
embedding(EmbeddingModels.openai.EmbeddingLarge3, "openai", "Embedding 3 Large", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingLarge3]?.input ?? 0.13),
|
|
180
|
+
embedding(EmbeddingModels.openai.EmbeddingSmall3, "openai", "Embedding 3 Small", OpenAiTextPricing[EmbeddingModels.openai.EmbeddingSmall3]?.input ?? 0.02),
|
|
181
|
+
// Image generation
|
|
182
|
+
image(Models.openai.DALL_E_3, "openai", "DALL-E 3", { image_generation: 0.04 }),
|
|
183
|
+
image(Models.openai.DALL_E_2, "openai", "DALL-E 2", { image_generation: 0.02 }),
|
|
184
|
+
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 }),
|
|
185
|
+
image(Models.openai.GPT_Image_1_Mini, "openai", "GPT Image 1 Mini", { input: OpenAiTextPricing[Models.openai.GPT_Image_1_Mini]?.input ?? 2.0 }),
|
|
186
|
+
// Video generation
|
|
187
|
+
video(Models.openai.Sora, "openai", "Sora", 0.012),
|
|
188
|
+
video(Models.openai.Sora_2, "openai", "Sora 2", 0.015),
|
|
189
|
+
// Audio
|
|
190
|
+
audio(Models.openai.TTS_1, "openai", "TTS-1", { input: 15.0 }),
|
|
191
|
+
audio(Models.openai.Whisper_1, "openai", "Whisper 1", { input: 0.006 }),
|
|
192
|
+
// Transaction / Search
|
|
193
|
+
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 }),
|
|
194
|
+
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 }),
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
// ─── Anthropic ────────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
export const ANTHROPIC_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
200
|
+
completion(Models.anthropic.Opus4_5, "anthropic", "Claude Opus 4.5"),
|
|
201
|
+
completion(Models.anthropic.Sonnet4_5, "anthropic", "Claude Sonnet 4.5"),
|
|
202
|
+
completion(Models.anthropic.Opus4, "anthropic", "Claude Opus 4"),
|
|
203
|
+
completion(Models.anthropic.Sonnet4, "anthropic", "Claude Sonnet 4"),
|
|
204
|
+
completion(Models.anthropic.Haiku4_5, "anthropic", "Claude Haiku 4.5"),
|
|
205
|
+
completion(Models.anthropic.Sonnet3_7, "anthropic", "Claude Sonnet 3.7"),
|
|
206
|
+
completion(Models.anthropic.Sonnet3_5, "anthropic", "Claude Sonnet 3.5"),
|
|
207
|
+
completion(Models.anthropic.Haiku3, "anthropic", "Claude Haiku 3"),
|
|
208
|
+
completion(Models.anthropic.Opus3, "anthropic", "Claude Opus 3"),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// ─── Google ───────────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
export const GOOGLE_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
214
|
+
// Completion
|
|
215
|
+
completion(Models.google.Gemini_25_Pro, "google", "Gemini 2.5 Pro"),
|
|
216
|
+
completion(Models.google.Gemini_25_Flash, "google", "Gemini 2.5 Flash"),
|
|
217
|
+
completion(Models.google.Gemini_25_Flash_Lite, "google", "Gemini 2.5 Flash Lite"),
|
|
218
|
+
completion(Models.google.Gemini_20_Flash, "google", "Gemini 2.0 Flash"),
|
|
219
|
+
completion(Models.google.Gemini_15_Pro, "google", "Gemini 1.5 Pro"),
|
|
220
|
+
completion(Models.google.Gemini_15_Flash, "google", "Gemini 1.5 Flash"),
|
|
221
|
+
completion(Models.google.Gemini_15_Flash_8B, "google", "Gemini 1.5 Flash 8B"),
|
|
222
|
+
// Embedding
|
|
223
|
+
embedding(EmbeddingModels.google.Gemini_Embedding, "google", "Gemini Embedding", GeminiPricing[EmbeddingModels.google.Gemini_Embedding]?.input ?? 0),
|
|
224
|
+
// Image generation
|
|
225
|
+
image(Models.google.Gemini_20_Flash_Preview_Image_Generation, "google", "Gemini 2.0 Flash Image", {
|
|
226
|
+
input: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.input ?? 0.1,
|
|
227
|
+
output: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.output ?? 0.4,
|
|
228
|
+
image_generation: GeminiPricing[Models.google.Gemini_20_Flash_Preview_Image_Generation]?.image_generation ?? 0.039,
|
|
229
|
+
}),
|
|
230
|
+
image(Models.google.Gemini_25_Flash_Image, "google", "Gemini 2.5 Flash Image", {
|
|
231
|
+
input: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.input ?? 0.3,
|
|
232
|
+
output: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.output ?? 0.039,
|
|
233
|
+
image_generation: GeminiPricing[Models.google.Gemini_25_Flash_Image]?.image_generation ?? 0.039,
|
|
234
|
+
}),
|
|
235
|
+
image(Models.google.Gemini_31_Flash_Image_Preview, "google", "Gemini 3.1 Flash Image", {
|
|
236
|
+
input: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.input ?? 0.5,
|
|
237
|
+
output: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.output ?? 3.0,
|
|
238
|
+
image_generation: GeminiPricing[Models.google.Gemini_31_Flash_Image_Preview]?.image_generation ?? 0.045,
|
|
239
|
+
}),
|
|
240
|
+
image(Models.google.Gemini_3_Pro_Image_Preview, "google", "Gemini 3 Pro Image", {
|
|
241
|
+
input: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.input ?? 2.0,
|
|
242
|
+
output: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.output ?? 12.0,
|
|
243
|
+
image_generation: GeminiPricing[Models.google.Gemini_3_Pro_Image_Preview]?.image_generation ?? 0.134,
|
|
244
|
+
}),
|
|
245
|
+
image(Models.google.Imagen_3, "google", "Imagen 4", { image_generation: GeminiPricing[Models.google.Imagen_3]?.image_generation ?? 0.04 }),
|
|
246
|
+
image(Models.google.Imagen_4_Fast, "google", "Imagen 4 Fast", { image_generation: GeminiPricing[Models.google.Imagen_4_Fast]?.image_generation ?? 0.02 }),
|
|
247
|
+
image(Models.google.Imagen_4_Ultra, "google", "Imagen 4 Ultra", { image_generation: GeminiPricing[Models.google.Imagen_4_Ultra]?.image_generation ?? 0.06 }),
|
|
248
|
+
// Video generation
|
|
249
|
+
video(Models.google.Veo_2, "google", "Veo 2", GeminiPricing[Models.google.Veo_2]?.video_generation ?? 0.35),
|
|
250
|
+
video(Models.google.Veo_3, "google", "Veo 3", GeminiPricing[Models.google.Veo_3]?.video_generation ?? 0.4),
|
|
251
|
+
video(Models.google.Veo_3_Fast, "google", "Veo 3 Fast", GeminiPricing[Models.google.Veo_3_Fast]?.video_generation ?? 0.1),
|
|
252
|
+
// Audio (TTS)
|
|
253
|
+
audio(Models.google.Gemini_25_Flash_TTS, "google", "Gemini 2.5 Flash TTS", {
|
|
254
|
+
input: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.input ?? 0.5,
|
|
255
|
+
output_audio: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.output_audio ?? 10.0,
|
|
256
|
+
output: GeminiPricing[Models.google.Gemini_25_Flash_TTS]?.output_audio ?? 10.0,
|
|
257
|
+
}),
|
|
258
|
+
audio(Models.google.Gemini_25_Pro_TTS, "google", "Gemini 2.5 Pro TTS", {
|
|
259
|
+
input: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.input ?? 1.0,
|
|
260
|
+
output_audio: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.output_audio ?? 20.0,
|
|
261
|
+
output: GeminiPricing[Models.google.Gemini_25_Pro_TTS]?.output_audio ?? 20.0,
|
|
262
|
+
}),
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
// ─── xAI ──────────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
export const XAI_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
268
|
+
completion(Models.xai.Grok4, "xai", "Grok 4"),
|
|
269
|
+
completion(Models.xai.Grok3Beta, "xai", "Grok 3 Beta"),
|
|
270
|
+
completion(Models.xai.Grok3MiniBeta, "xai", "Grok 3 Mini Beta"),
|
|
271
|
+
completion(Models.xai.Grok3FastBeta, "xai", "Grok 3 Fast Beta"),
|
|
272
|
+
completion(Models.xai.Grok21212, "xai", "Grok 2"),
|
|
273
|
+
// Image generation
|
|
274
|
+
image(Models.xai.GrokImagineImage, "xai", "Grok Imagine Image", { image_generation: XaiImagePricing["grok-imagine-image"] ?? 0.02 }),
|
|
275
|
+
image("grok-2-image-1212", "xai", "Grok 2 Image", { image_generation: XaiImagePricing["grok-2-image-1212"] ?? 0.07 }),
|
|
276
|
+
// Video generation
|
|
277
|
+
video(Models.xai.GrokImagineVideo, "xai", "Grok Imagine Video", XaiVideoPricing["grok-imagine-video"] ?? 0.05),
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
// ─── Combined catalog ─────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
export const ALL_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
283
|
+
...OPENAI_MODEL_CATALOG,
|
|
284
|
+
...ANTHROPIC_MODEL_CATALOG,
|
|
285
|
+
...GOOGLE_MODEL_CATALOG,
|
|
286
|
+
...XAI_MODEL_CATALOG,
|
|
287
|
+
];
|
|
@@ -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 "./catalog";
|
|
6
|
+
export type { ModelCatalogEntry, ModelType, ModelPricing as CatalogModelPricing } from "./catalog";
|
package/src/config.ts
CHANGED
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,21 +18,31 @@ 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;
|
|
31
35
|
private doneHandler: ((...args: any[]) => void) | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Tracks the most recent in-flight filesystem metadata update.
|
|
38
|
+
* The done handler awaits this before finalizing, preventing a race where
|
|
39
|
+
* the completion call writes before the last thread sync finishes.
|
|
40
|
+
*/
|
|
41
|
+
private pendingThreadUpdatePromise: Promise<void> | null = null;
|
|
32
42
|
|
|
33
43
|
constructor() {
|
|
34
44
|
// Start cleanup process when created
|
|
35
|
-
|
|
45
|
+
AgentSyncFs.startSharedCleanupProcess();
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
/**
|
|
@@ -294,8 +304,12 @@ export class AgentSyncFs {
|
|
|
294
304
|
if (!this.taskId) return;
|
|
295
305
|
|
|
296
306
|
try {
|
|
297
|
-
await
|
|
298
|
-
|
|
307
|
+
// Track the pending update so the done handler can await it.
|
|
308
|
+
this.pendingThreadUpdatePromise = (async () => {
|
|
309
|
+
await this.updateMetadata(agent, true);
|
|
310
|
+
await this.checkForChanges(agent);
|
|
311
|
+
})();
|
|
312
|
+
await this.pendingThreadUpdatePromise;
|
|
299
313
|
} catch (error) {
|
|
300
314
|
console.error(`❌ Error during threadUpdate sync:`, error);
|
|
301
315
|
}
|
|
@@ -314,6 +328,17 @@ export class AgentSyncFs {
|
|
|
314
328
|
// Store finalization promise so callers can await it (same pattern as AgentSyncKnowhowWeb)
|
|
315
329
|
this.finalizationPromise = (async () => {
|
|
316
330
|
try {
|
|
331
|
+
// Flush any in-flight thread update before finalizing.
|
|
332
|
+
// This prevents the race where a pending "inProgress: true" metadata write
|
|
333
|
+
// overwrites the finalization write.
|
|
334
|
+
if (this.pendingThreadUpdatePromise) {
|
|
335
|
+
console.log(`⏳ [AgentSyncFs] Awaiting pending thread update before finalizing...`);
|
|
336
|
+
await this.pendingThreadUpdatePromise.catch(() => {
|
|
337
|
+
// Ignore errors in pending update — we still want to finalize
|
|
338
|
+
});
|
|
339
|
+
this.pendingThreadUpdatePromise = null;
|
|
340
|
+
}
|
|
341
|
+
|
|
317
342
|
await this.updateMetadata(agent, false, result);
|
|
318
343
|
console.log(`✅ Completed filesystem sync for task: ${this.taskId}`);
|
|
319
344
|
await this.cleanup();
|
|
@@ -351,9 +376,9 @@ export class AgentSyncFs {
|
|
|
351
376
|
/**
|
|
352
377
|
* Clean up old task directories (older than 3 days)
|
|
353
378
|
*/
|
|
354
|
-
private async cleanupOldTasks(): Promise<void> {
|
|
379
|
+
private static async cleanupOldTasks(): Promise<void> {
|
|
355
380
|
try {
|
|
356
|
-
const agentsPath =
|
|
381
|
+
const agentsPath = AgentSyncFs.sharedBasePath;
|
|
357
382
|
|
|
358
383
|
// Check if directory exists
|
|
359
384
|
try {
|
|
@@ -393,23 +418,27 @@ export class AgentSyncFs {
|
|
|
393
418
|
/**
|
|
394
419
|
* Start periodic cleanup process
|
|
395
420
|
*/
|
|
396
|
-
private
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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();
|
|
400
428
|
}, 60 * 60 * 1000);
|
|
401
429
|
|
|
402
430
|
// Also run once on startup
|
|
403
|
-
|
|
431
|
+
AgentSyncFs.cleanupOldTasks();
|
|
404
432
|
}
|
|
405
433
|
|
|
406
434
|
/**
|
|
407
435
|
* Stop cleanup process
|
|
408
436
|
*/
|
|
409
437
|
stopCleanup(): void {
|
|
410
|
-
if (
|
|
411
|
-
clearInterval(
|
|
412
|
-
|
|
438
|
+
if (AgentSyncFs.sharedCleanupInterval) {
|
|
439
|
+
clearInterval(AgentSyncFs.sharedCleanupInterval);
|
|
440
|
+
AgentSyncFs.sharedCleanupInterval = null;
|
|
441
|
+
AgentSyncFs.cleanupStarted = false;
|
|
413
442
|
}
|
|
414
443
|
}
|
|
415
444
|
|
|
@@ -435,5 +464,6 @@ export class AgentSyncFs {
|
|
|
435
464
|
this.eventHandlersSetup = false;
|
|
436
465
|
this.lastInputContent = "";
|
|
437
466
|
this.finalizationPromise = null;
|
|
467
|
+
this.pendingThreadUpdatePromise = null;
|
|
438
468
|
}
|
|
439
469
|
}
|
|
@@ -36,6 +36,12 @@ export class AgentSyncKnowhowWeb {
|
|
|
36
36
|
private agent: BaseAgent | undefined;
|
|
37
37
|
private threadUpdateHandler: ((...args: any[]) => void) | undefined;
|
|
38
38
|
private doneHandler: ((...args: any[]) => void) | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Tracks the most recent in-flight thread update API call.
|
|
41
|
+
* The done handler awaits this before sending the finalization call,
|
|
42
|
+
* preventing a race where the completion overwrites a later in-progress update.
|
|
43
|
+
*/
|
|
44
|
+
private pendingThreadUpdatePromise: Promise<void> | null = null;
|
|
39
45
|
|
|
40
46
|
constructor(baseUrl: string = KNOWHOW_API_URL) {
|
|
41
47
|
this.baseUrl = baseUrl;
|
|
@@ -240,11 +246,15 @@ export class AgentSyncKnowhowWeb {
|
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
try {
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
// Track the pending thread update so the done handler can await it.
|
|
250
|
+
this.pendingThreadUpdatePromise = (async () => {
|
|
251
|
+
// Update task with current state
|
|
252
|
+
await this.updateChatTask(this.knowhowTaskId!, agent, true);
|
|
253
|
+
|
|
254
|
+
// Check for pending messages, pause, or kill status
|
|
255
|
+
await this.checkAndProcessPendingMessages(agent, this.knowhowTaskId!);
|
|
256
|
+
})();
|
|
257
|
+
await this.pendingThreadUpdatePromise;
|
|
248
258
|
} catch (error) {
|
|
249
259
|
console.error(`❌ Error during threadUpdate sync:`, error);
|
|
250
260
|
// Continue execution even if synchronization fails
|
|
@@ -264,6 +274,17 @@ export class AgentSyncKnowhowWeb {
|
|
|
264
274
|
// Create a promise that tracks finalization
|
|
265
275
|
this.finalizationPromise = (async () => {
|
|
266
276
|
try {
|
|
277
|
+
// Flush any in-flight thread update before sending the completion call.
|
|
278
|
+
// This prevents the race where a "inProgress: true" update overtakes
|
|
279
|
+
// the "inProgress: false" finalization call.
|
|
280
|
+
if (this.pendingThreadUpdatePromise) {
|
|
281
|
+
console.log(`⏳ [AgentSync] Awaiting pending thread update before finalizing...`);
|
|
282
|
+
await this.pendingThreadUpdatePromise.catch(() => {
|
|
283
|
+
// Ignore errors in the pending update — we still want to finalize
|
|
284
|
+
});
|
|
285
|
+
this.pendingThreadUpdatePromise = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
267
288
|
console.log(
|
|
268
289
|
`Updating Knowhow chat task on completion..., ${this.knowhowTaskId}`
|
|
269
290
|
);
|
|
@@ -306,6 +327,7 @@ export class AgentSyncKnowhowWeb {
|
|
|
306
327
|
this.knowhowTaskId = undefined;
|
|
307
328
|
this.eventHandlersSetup = false;
|
|
308
329
|
this.finalizationPromise = null;
|
|
330
|
+
this.pendingThreadUpdatePromise = null;
|
|
309
331
|
}
|
|
310
332
|
|
|
311
333
|
/**
|
|
@@ -24,6 +24,30 @@ import {
|
|
|
24
24
|
} from "../clients";
|
|
25
25
|
import { Config } from "../types";
|
|
26
26
|
|
|
27
|
+
// Remote sync placeholder interfaces
|
|
28
|
+
export interface CreateSessionPlaceholderRequest {
|
|
29
|
+
title?: string;
|
|
30
|
+
workerId?: string;
|
|
31
|
+
metadata?: Record<string, any>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CreateSessionPlaceholderResponse {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
orgId: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CreateMessagePlaceholderRequest {
|
|
40
|
+
content: string;
|
|
41
|
+
agentName?: string;
|
|
42
|
+
modelName?: string;
|
|
43
|
+
metadata?: Record<string, any>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CreateMessagePlaceholderResponse {
|
|
47
|
+
messageId: string;
|
|
48
|
+
taskId?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
// Chat Task interfaces
|
|
28
52
|
export interface CreateMessageTaskRequest {
|
|
29
53
|
messageId: string;
|
|
@@ -697,4 +721,41 @@ export class KnowhowSimpleClient {
|
|
|
697
721
|
{ headers: this.headers }
|
|
698
722
|
);
|
|
699
723
|
}
|
|
724
|
+
|
|
725
|
+
// ============================================
|
|
726
|
+
// Remote Sync Placeholder Methods
|
|
727
|
+
// ============================================
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Create a bare session stub without triggering AI inference.
|
|
731
|
+
* Used by the CLI remote sync feature to establish a remote session.
|
|
732
|
+
*/
|
|
733
|
+
async createSessionPlaceholder(
|
|
734
|
+
request: CreateSessionPlaceholderRequest = {}
|
|
735
|
+
): Promise<CreateSessionPlaceholderResponse> {
|
|
736
|
+
await this.checkJwt();
|
|
737
|
+
const response = await http.post<CreateSessionPlaceholderResponse>(
|
|
738
|
+
`${this.baseUrl}/api/chat/sessions/placeholder`,
|
|
739
|
+
request,
|
|
740
|
+
{ headers: this.headers }
|
|
741
|
+
);
|
|
742
|
+
return response.data;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Create a message placeholder in a session without triggering AI inference.
|
|
747
|
+
* Used by the CLI remote sync feature to register a message before syncing threads.
|
|
748
|
+
*/
|
|
749
|
+
async createMessagePlaceholder(
|
|
750
|
+
sessionId: string,
|
|
751
|
+
request: CreateMessagePlaceholderRequest
|
|
752
|
+
): Promise<CreateMessagePlaceholderResponse> {
|
|
753
|
+
await this.checkJwt();
|
|
754
|
+
const response = await http.post<CreateMessagePlaceholderResponse>(
|
|
755
|
+
`${this.baseUrl}/api/chat/sessions/${sessionId}/messages/placeholder`,
|
|
756
|
+
request,
|
|
757
|
+
{ headers: this.headers }
|
|
758
|
+
);
|
|
759
|
+
return response.data;
|
|
760
|
+
}
|
|
700
761
|
}
|
|
@@ -54,6 +54,7 @@ export class SessionManager {
|
|
|
54
54
|
sessionId: taskId,
|
|
55
55
|
knowhowMessageId: taskInfo.knowhowMessageId,
|
|
56
56
|
knowhowTaskId: taskInfo.knowhowTaskId,
|
|
57
|
+
chatSessionId: taskInfo.chatSessionId,
|
|
57
58
|
taskId,
|
|
58
59
|
agentName: taskInfo.agentName,
|
|
59
60
|
initialInput: taskInfo.initialInput,
|
|
@@ -99,6 +100,7 @@ export class SessionManager {
|
|
|
99
100
|
// Update Knowhow task fields if they exist in TaskInfo
|
|
100
101
|
session.knowhowMessageId = taskInfo.knowhowMessageId;
|
|
101
102
|
session.knowhowTaskId = taskInfo.knowhowTaskId;
|
|
103
|
+
session.chatSessionId = taskInfo.chatSessionId;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
|