@tyvm/knowhow 0.0.104 → 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 -2
- package/src/clients/pricing/{catalog.ts → models.ts} +5 -40
- 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/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 -2
- package/ts_build/src/clients/pricing/index.js +7 -7
- 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/{catalog.js → models.js} +4 -10
- package/ts_build/src/clients/pricing/models.js.map +1 -0
- package/ts_build/src/clients/pricing/{catalog.d.ts → types.d.ts} +2 -9
- 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/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/ts_build/src/clients/pricing/catalog.js.map +0 -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,5 +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 "./
|
|
6
|
-
export type { ModelCatalogEntry, ModelType, ModelPricing as CatalogModelPricing } from "./
|
|
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";
|
|
@@ -12,38 +12,9 @@ import { OpenAiTextPricing } from "./openai";
|
|
|
12
12
|
import { AnthropicTextPricing } from "./anthropic";
|
|
13
13
|
import { GeminiPricing } from "./google";
|
|
14
14
|
import { XaiTextPricing, XaiImagePricing, XaiVideoPricing } from "./xai";
|
|
15
|
+
import { ModelPricing, ModelType, ModelCatalogEntry } from "./types";
|
|
15
16
|
|
|
16
|
-
export
|
|
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
|
-
}
|
|
17
|
+
export { ModelPricing, ModelType, ModelCatalogEntry };
|
|
47
18
|
|
|
48
19
|
// ─── Platform markup ──────────────────────────────────────────────────────────
|
|
49
20
|
|
|
@@ -69,7 +40,6 @@ function completion(
|
|
|
69
40
|
provider,
|
|
70
41
|
type: "completion",
|
|
71
42
|
displayName,
|
|
72
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
73
43
|
pricing: {
|
|
74
44
|
input: 0,
|
|
75
45
|
output: 0,
|
|
@@ -90,7 +60,6 @@ function embedding(
|
|
|
90
60
|
provider,
|
|
91
61
|
type: "embedding",
|
|
92
62
|
displayName,
|
|
93
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
94
63
|
pricing: { input, output: 0 },
|
|
95
64
|
};
|
|
96
65
|
}
|
|
@@ -106,7 +75,6 @@ function image(
|
|
|
106
75
|
provider,
|
|
107
76
|
type: "image",
|
|
108
77
|
displayName,
|
|
109
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
110
78
|
pricing: { input: 0, output: 0, ...pricing },
|
|
111
79
|
};
|
|
112
80
|
}
|
|
@@ -122,7 +90,6 @@ function video(
|
|
|
122
90
|
provider,
|
|
123
91
|
type: "video",
|
|
124
92
|
displayName,
|
|
125
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
126
93
|
pricing: { input: 0, output: 0, video_generation },
|
|
127
94
|
};
|
|
128
95
|
}
|
|
@@ -138,7 +105,6 @@ function audio(
|
|
|
138
105
|
provider,
|
|
139
106
|
type: "audio",
|
|
140
107
|
displayName,
|
|
141
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
142
108
|
pricing: { input: 0, output: 0, ...pricing },
|
|
143
109
|
};
|
|
144
110
|
}
|
|
@@ -154,7 +120,6 @@ function transaction(
|
|
|
154
120
|
provider,
|
|
155
121
|
type: "transaction",
|
|
156
122
|
displayName,
|
|
157
|
-
markupPercent: USAGE_MARKUP_PERCENT,
|
|
158
123
|
pricing: { input: 0, output: 0, ...pricing },
|
|
159
124
|
};
|
|
160
125
|
}
|
|
@@ -271,10 +236,10 @@ export const XAI_MODEL_CATALOG: ModelCatalogEntry[] = [
|
|
|
271
236
|
completion(Models.xai.Grok3FastBeta, "xai", "Grok 3 Fast Beta"),
|
|
272
237
|
completion(Models.xai.Grok21212, "xai", "Grok 2"),
|
|
273
238
|
// 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 }),
|
|
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 }),
|
|
276
241
|
// Video generation
|
|
277
|
-
video(Models.xai.GrokImagineVideo, "xai", "Grok Imagine Video", XaiVideoPricing["grok-imagine-video"] ?? 0.05),
|
|
242
|
+
video(Models.xai.GrokImagineVideo, "xai", "Grok Imagine Video", XaiVideoPricing["grok-imagine-video"]?.video_generation ?? 0.05),
|
|
278
243
|
];
|
|
279
244
|
|
|
280
245
|
// ─── Combined catalog ─────────────────────────────────────────────────────────
|
|
@@ -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 {
|
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { Message } from "../clients/types";
|
|
7
7
|
import { EventService } from "./EventService";
|
|
8
|
-
import * as fs from "fs";
|
|
9
|
-
import * as fsPromises from "fs/promises";
|
|
10
|
-
import * as path from "path";
|
|
11
|
-
import { messagesToRenderEvents } from "../chat/renderer/messagesToRenderEvents";
|
|
12
|
-
import { KnowhowSimpleClient } from "./KnowhowClient";
|
|
13
8
|
|
|
14
9
|
export interface SyncedAgentWatcher {
|
|
15
10
|
/** Start watching for changes, emitting agent events */
|
|
@@ -27,7 +22,13 @@ export interface SyncedAgentWatcher {
|
|
|
27
22
|
/** EventService that emits agent lifecycle events (toolCall, toolUsed, agentSay, threadUpdate, done) */
|
|
28
23
|
agentEvents: EventService;
|
|
29
24
|
/** Event type constants mirroring BaseAgent.eventTypes */
|
|
30
|
-
eventTypes: {
|
|
25
|
+
eventTypes: {
|
|
26
|
+
done: string;
|
|
27
|
+
toolCall: string;
|
|
28
|
+
toolUsed: string;
|
|
29
|
+
agentSay: string;
|
|
30
|
+
threadUpdate: string;
|
|
31
|
+
};
|
|
31
32
|
/** Pause the remote agent */
|
|
32
33
|
pause(): Promise<void>;
|
|
33
34
|
/** Unpause/resume the remote agent */
|
|
@@ -44,7 +45,12 @@ export interface SyncedAgentWatcher {
|
|
|
44
45
|
export interface AttachableAgent {
|
|
45
46
|
name: string;
|
|
46
47
|
agentEvents: EventService;
|
|
47
|
-
eventTypes: {
|
|
48
|
+
eventTypes: {
|
|
49
|
+
done: string;
|
|
50
|
+
toolCall?: string;
|
|
51
|
+
toolUsed?: string;
|
|
52
|
+
agentSay?: string;
|
|
53
|
+
};
|
|
48
54
|
getTotalCostUsd(): number;
|
|
49
55
|
pause(): void | Promise<void>;
|
|
50
56
|
unpause(): void | Promise<void>;
|
|
@@ -104,294 +110,3 @@ export class WatcherBackedAgent implements AttachableAgent {
|
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Watches an agent running in another process via the filesystem.
|
|
110
|
-
* Reads .knowhow/processes/agents/<taskId>/metadata.json for changes.
|
|
111
|
-
* Sends messages by writing to .knowhow/processes/agents/<taskId>/input.txt
|
|
112
|
-
*/
|
|
113
|
-
export class FsSyncedAgentWatcher implements SyncedAgentWatcher {
|
|
114
|
-
public taskId: string = "";
|
|
115
|
-
private taskPath: string = "";
|
|
116
|
-
private watcher: fs.FSWatcher | null = null;
|
|
117
|
-
private lastThreadLength: number = 0;
|
|
118
|
-
public agentName: string = "unknown";
|
|
119
|
-
private debounceTimer: NodeJS.Timeout | null = null;
|
|
120
|
-
public agentEvents = new EventService();
|
|
121
|
-
public eventTypes = {
|
|
122
|
-
done: "done",
|
|
123
|
-
toolCall: "tool:pre_call",
|
|
124
|
-
toolUsed: "tool:post_call",
|
|
125
|
-
agentSay: "agent:say",
|
|
126
|
-
threadUpdate: "thread_update",
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
async startWatching(taskId: string): Promise<void> {
|
|
130
|
-
this.taskId = taskId;
|
|
131
|
-
this.taskPath = path.join(".knowhow/processes/agents", taskId);
|
|
132
|
-
|
|
133
|
-
// Load initial state to track current thread length (for delta rendering)
|
|
134
|
-
const metadata = await this.readMetadata();
|
|
135
|
-
if (metadata) {
|
|
136
|
-
const threads: any[][] = metadata.threads || [];
|
|
137
|
-
const lastThread = threads[threads.length - 1] || [];
|
|
138
|
-
this.agentName = metadata.agentName || taskId;
|
|
139
|
-
this.lastThreadLength = lastThread.length;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Watch the directory for metadata.json changes
|
|
143
|
-
try {
|
|
144
|
-
this.watcher = fs.watch(this.taskPath, (event, filename) => {
|
|
145
|
-
if (filename === "metadata.json" || filename === null) {
|
|
146
|
-
// Debounce rapid file writes
|
|
147
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
148
|
-
this.debounceTimer = setTimeout(() => {
|
|
149
|
-
this.onMetadataChanged().catch(() => {});
|
|
150
|
-
}, 200);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
} catch (err: any) {
|
|
154
|
-
console.warn(`⚠️ Could not watch ${this.taskPath}: ${err.message}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
console.log(`👁️ Watching fs-synced agent: ${taskId} (${this.agentName})`);
|
|
158
|
-
console.log(
|
|
159
|
-
` Type /logs 20 to see recent messages, or type to send a message`
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private async onMetadataChanged(): Promise<void> {
|
|
164
|
-
const metadata = await this.readMetadata();
|
|
165
|
-
if (!metadata?.threads) return;
|
|
166
|
-
|
|
167
|
-
const threads: any[][] = metadata.threads;
|
|
168
|
-
const lastThread = threads[threads.length - 1] || [];
|
|
169
|
-
|
|
170
|
-
// Only render NEW messages since last check
|
|
171
|
-
const newMessages = lastThread.slice(this.lastThreadLength);
|
|
172
|
-
if (newMessages.length > 0) {
|
|
173
|
-
const renderEvents = messagesToRenderEvents(
|
|
174
|
-
newMessages,
|
|
175
|
-
this.taskId,
|
|
176
|
-
this.agentName
|
|
177
|
-
);
|
|
178
|
-
for (const event of renderEvents) {
|
|
179
|
-
if (event.type === "toolCall") {
|
|
180
|
-
this.agentEvents.emit(this.eventTypes.toolCall, {
|
|
181
|
-
toolCall: (event as any).toolCall,
|
|
182
|
-
});
|
|
183
|
-
} else if (event.type === "toolResult") {
|
|
184
|
-
this.agentEvents.emit(this.eventTypes.toolUsed, {
|
|
185
|
-
toolCall: (event as any).toolCall,
|
|
186
|
-
functionResp: (event as any).result,
|
|
187
|
-
});
|
|
188
|
-
} else if (event.type === "agentMessage") {
|
|
189
|
-
this.agentEvents.emit(this.eventTypes.agentSay, {
|
|
190
|
-
message: (event as any).message,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
|
|
195
|
-
this.lastThreadLength = lastThread.length;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Emit done if the agent has completed and has a result
|
|
199
|
-
const status = metadata.status;
|
|
200
|
-
const result = metadata.result;
|
|
201
|
-
if ((status === "completed" || status === "killed") && result != null) {
|
|
202
|
-
this.stopWatching();
|
|
203
|
-
this.agentEvents.emit(this.eventTypes.done, result);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async sendMessage(message: string): Promise<void> {
|
|
208
|
-
const inputPath = path.join(this.taskPath, "input.txt");
|
|
209
|
-
await fsPromises.writeFile(inputPath, message, "utf8");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async getThreads(): Promise<any[][]> {
|
|
213
|
-
const metadata = await this.readMetadata();
|
|
214
|
-
return metadata?.threads || [];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
stopWatching(): void {
|
|
218
|
-
if (this.debounceTimer) {
|
|
219
|
-
clearTimeout(this.debounceTimer);
|
|
220
|
-
this.debounceTimer = null;
|
|
221
|
-
}
|
|
222
|
-
this.watcher?.close();
|
|
223
|
-
this.watcher = null;
|
|
224
|
-
console.log(`🔌 Stopped watching agent: ${this.taskId}`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async pause(): Promise<void> {
|
|
228
|
-
const statusPath = path.join(this.taskPath, "status.txt");
|
|
229
|
-
await fsPromises.writeFile(statusPath, "paused", "utf8");
|
|
230
|
-
console.log(`⏸️ Paused remote agent: ${this.taskId}`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async unpause(): Promise<void> {
|
|
234
|
-
const statusPath = path.join(this.taskPath, "status.txt");
|
|
235
|
-
await fsPromises.writeFile(statusPath, "running", "utf8");
|
|
236
|
-
console.log(`▶️ Unpaused remote agent: ${this.taskId}`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async kill(): Promise<void> {
|
|
240
|
-
const statusPath = path.join(this.taskPath, "status.txt");
|
|
241
|
-
await fsPromises.writeFile(statusPath, "killed", "utf8");
|
|
242
|
-
console.log(`🛑 Killed remote agent: ${this.taskId}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private async readMetadata(): Promise<any> {
|
|
246
|
-
try {
|
|
247
|
-
const metaPath = path.join(this.taskPath, "metadata.json");
|
|
248
|
-
const content = await fsPromises.readFile(metaPath, "utf8");
|
|
249
|
-
return JSON.parse(content);
|
|
250
|
-
} catch {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Watches an agent running on Knowhow Web via polling the API.
|
|
258
|
-
* Polls GET /tasks/<taskId> every 3 seconds for thread updates.
|
|
259
|
-
* Sends messages via the client's sendMessageToAgent method.
|
|
260
|
-
*/
|
|
261
|
-
export class WebSyncedAgentWatcher implements SyncedAgentWatcher {
|
|
262
|
-
public taskId: string = "";
|
|
263
|
-
private client: KnowhowSimpleClient;
|
|
264
|
-
private pollInterval: NodeJS.Timeout | null = null;
|
|
265
|
-
private lastThreadLength: number = 0;
|
|
266
|
-
public agentName: string = "remote-agent";
|
|
267
|
-
private stopped: boolean = false;
|
|
268
|
-
public agentEvents = new EventService();
|
|
269
|
-
public eventTypes = {
|
|
270
|
-
done: "done",
|
|
271
|
-
toolCall: "tool:pre_call",
|
|
272
|
-
toolUsed: "tool:post_call",
|
|
273
|
-
agentSay: "agent:say",
|
|
274
|
-
threadUpdate: "thread_update",
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
constructor(client?: KnowhowSimpleClient) {
|
|
278
|
-
this.client = client || new KnowhowSimpleClient();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async startWatching(taskId: string): Promise<void> {
|
|
282
|
-
this.taskId = taskId;
|
|
283
|
-
this.stopped = false;
|
|
284
|
-
|
|
285
|
-
// Load initial state to track current thread length
|
|
286
|
-
try {
|
|
287
|
-
const details = await this.client.getTaskDetails(taskId);
|
|
288
|
-
const threads: any[][] = details?.data?.threads || [];
|
|
289
|
-
const lastThread = threads[threads.length - 1] || [];
|
|
290
|
-
this.agentName = "remote-agent";
|
|
291
|
-
this.lastThreadLength = lastThread.length;
|
|
292
|
-
} catch (err: any) {
|
|
293
|
-
console.warn(
|
|
294
|
-
`⚠️ Could not load initial state for task ${taskId}: ${err.message}`
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Poll every 3 seconds for updates
|
|
299
|
-
this.pollInterval = setInterval(async () => {
|
|
300
|
-
if (!this.stopped) {
|
|
301
|
-
await this.onPoll().catch(() => {});
|
|
302
|
-
}
|
|
303
|
-
}, 3000);
|
|
304
|
-
|
|
305
|
-
console.log(`🌐 Watching web-synced agent: ${taskId} (${this.agentName})`);
|
|
306
|
-
console.log(
|
|
307
|
-
` Type /logs 20 to see recent messages, or type to send a message`
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private async onPoll(): Promise<void> {
|
|
312
|
-
if (this.stopped) return;
|
|
313
|
-
try {
|
|
314
|
-
const details = await this.client.getTaskDetails(this.taskId);
|
|
315
|
-
const threads: any[][] = details?.data?.threads || [];
|
|
316
|
-
const lastThread = threads[threads.length - 1] || [];
|
|
317
|
-
|
|
318
|
-
const newMessages = lastThread.slice(this.lastThreadLength);
|
|
319
|
-
if (newMessages.length > 0) {
|
|
320
|
-
const renderEvents = messagesToRenderEvents(
|
|
321
|
-
newMessages,
|
|
322
|
-
this.taskId,
|
|
323
|
-
this.agentName
|
|
324
|
-
);
|
|
325
|
-
for (const event of renderEvents) {
|
|
326
|
-
if (event.type === "toolCall") {
|
|
327
|
-
this.agentEvents.emit(this.eventTypes.toolCall, {
|
|
328
|
-
toolCall: (event as any).toolCall,
|
|
329
|
-
});
|
|
330
|
-
} else if (event.type === "toolResult") {
|
|
331
|
-
this.agentEvents.emit(this.eventTypes.toolUsed, {
|
|
332
|
-
toolCall: (event as any).toolCall,
|
|
333
|
-
functionResp: (event as any).result,
|
|
334
|
-
});
|
|
335
|
-
} else if (event.type === "agentMessage") {
|
|
336
|
-
this.agentEvents.emit(this.eventTypes.agentSay, {
|
|
337
|
-
message: (event as any).message,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
|
|
342
|
-
this.lastThreadLength = lastThread.length;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Stop polling and emit done if task is complete with a result
|
|
346
|
-
const status = details?.data?.status;
|
|
347
|
-
const result = details?.data?.result;
|
|
348
|
-
if (status === "completed" || status === "killed") {
|
|
349
|
-
this.stopWatching();
|
|
350
|
-
if (result != null) {
|
|
351
|
-
this.agentEvents.emit(this.eventTypes.done, result);
|
|
352
|
-
} else {
|
|
353
|
-
console.log(`\n✅ Remote agent ${this.taskId} status: ${status} (no result)`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
// Silently continue on poll errors
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async sendMessage(message: string): Promise<void> {
|
|
362
|
-
await this.client.sendMessageToAgent(this.taskId, message);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async getThreads(): Promise<any[][]> {
|
|
366
|
-
try {
|
|
367
|
-
const details = await this.client.getTaskDetails(this.taskId);
|
|
368
|
-
return details?.data?.threads || [];
|
|
369
|
-
} catch {
|
|
370
|
-
return [];
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
stopWatching(): void {
|
|
375
|
-
this.stopped = true;
|
|
376
|
-
if (this.pollInterval) {
|
|
377
|
-
clearInterval(this.pollInterval);
|
|
378
|
-
this.pollInterval = null;
|
|
379
|
-
}
|
|
380
|
-
console.log(`🔌 Stopped watching web agent: ${this.taskId}`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async pause(): Promise<void> {
|
|
384
|
-
await this.client.pauseAgent(this.taskId);
|
|
385
|
-
console.log(`⏸️ Paused remote web agent: ${this.taskId}`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
async unpause(): Promise<void> {
|
|
389
|
-
await this.client.resumeAgent(this.taskId);
|
|
390
|
-
console.log(`▶️ Unpaused remote web agent: ${this.taskId}`);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async kill(): Promise<void> {
|
|
394
|
-
await this.client.killAgent(this.taskId);
|
|
395
|
-
console.log(`🛑 Killed remote web agent: ${this.taskId}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
package/src/services/index.ts
CHANGED