@tyvm/knowhow 0.0.144 → 0.0.145
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/bin/knowhow.js +22 -20
- package/package.json +1 -1
- package/scripts/migrate-usage-json.js +147 -0
- package/src/agents/base/base.ts +65 -10
- package/src/agents/tools/patch.ts +84 -2
- package/src/agents/tools/textSearch.ts +98 -3
- package/src/chat/modules/AgentModule.ts +129 -6
- package/src/chat/modules/SessionsModule.ts +6 -0
- package/src/chat/types.ts +14 -0
- package/src/chat.ts +11 -0
- package/src/cli.ts +2 -0
- package/src/clients/anthropic.ts +56 -1
- package/src/clients/contextLimits.ts +40 -0
- package/src/clients/gemini.ts +59 -5
- package/src/clients/http.ts +85 -4
- package/src/clients/index.ts +4 -0
- package/src/clients/openai.ts +179 -31
- package/src/clients/pricing/fireworks.ts +50 -1
- package/src/clients/pricing/index.ts +1 -1
- package/src/clients/pricing/models.ts +3 -1
- package/src/clients/pricing/openai.ts +2 -2
- package/src/clients/pricing/qwen.ts +80 -0
- package/src/clients/qwen.ts +16 -0
- package/src/clients/types.ts +69 -1
- package/src/clients/xai.ts +73 -4
- package/src/commands/agent.ts +20 -1
- package/src/index.ts +9 -0
- package/src/processors/Base64ImageDetector.ts +46 -7
- package/src/processors/GemmaToolProcessor.ts +163 -0
- package/src/processors/index.ts +1 -0
- package/src/services/AgentSyncFs.ts +37 -0
- package/src/services/SessionManager.ts +14 -0
- package/src/services/Tools.ts +3 -0
- package/src/services/modules/computerUse.ts +216 -0
- package/src/services/modules/index.ts +47 -9
- package/src/services/modules/types.ts +23 -1
- package/src/types.ts +3 -0
- package/tests/clients/contextLimits.test.ts +4 -0
- package/tests/clients/openai.images.test.ts +105 -0
- package/tests/clients/pricing.test.ts +6 -0
- package/tests/compressor/usageBase64.json +159 -0
- package/tests/compressor/usageBase64.test.ts +175 -0
- package/tests/patching/fixtures/transcript-mangles/agent-sync-fs-nested-catch.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/automation-functions-crossed.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/fruit-ninja-confirmed-retirement.json +8 -0
- package/tests/patching/patchFileOutput.test.ts +112 -1
- package/tests/patching/transcriptMangleRegression.test.ts +125 -0
- package/tests/processors/Base64ImageDetector.test.ts +36 -1
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +8 -1
- package/ts_build/src/agents/base/base.js +37 -3
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/patch.d.ts +1 -0
- package/ts_build/src/agents/tools/patch.js +35 -2
- package/ts_build/src/agents/tools/patch.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.js +56 -2
- package/ts_build/src/agents/tools/textSearch.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +14 -1
- package/ts_build/src/chat/modules/AgentModule.js +73 -5
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +4 -0
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +11 -0
- package/ts_build/src/chat.js +7 -0
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.d.ts +2 -1
- package/ts_build/src/cli.js +1 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +28 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +25 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/gemini.js +48 -4
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.d.ts +2 -0
- package/ts_build/src/clients/http.js +66 -9
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +1 -0
- package/ts_build/src/clients/index.js +4 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +6 -1
- package/ts_build/src/clients/openai.js +131 -33
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +4 -1
- package/ts_build/src/clients/pricing/fireworks.js +33 -1
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +1 -1
- package/ts_build/src/clients/pricing/index.js +6 -2
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/models.d.ts +2 -1
- package/ts_build/src/clients/pricing/models.js +4 -1
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +2 -2
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/qwen.d.ts +5 -0
- package/ts_build/src/clients/pricing/qwen.js +30 -0
- package/ts_build/src/clients/pricing/qwen.js.map +1 -0
- package/ts_build/src/clients/qwen.d.ts +4 -0
- package/ts_build/src/clients/qwen.js +15 -0
- package/ts_build/src/clients/qwen.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +12 -0
- package/ts_build/src/clients/xai.d.ts +1 -0
- package/ts_build/src/clients/xai.js +56 -9
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +10 -1
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/index.d.ts +2 -0
- package/ts_build/src/index.js +1 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.js +22 -3
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/processors/GemmaToolProcessor.d.ts +14 -0
- package/ts_build/src/processors/GemmaToolProcessor.js +97 -0
- package/ts_build/src/processors/GemmaToolProcessor.js.map +1 -0
- package/ts_build/src/processors/index.d.ts +1 -0
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +17 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/SessionManager.js +12 -0
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +2 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/modules/computerUse.d.ts +163 -0
- package/ts_build/src/services/modules/computerUse.js +3 -0
- package/ts_build/src/services/modules/computerUse.js.map +1 -0
- package/ts_build/src/services/modules/index.d.ts +1 -0
- package/ts_build/src/services/modules/index.js +23 -6
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +4 -0
- package/ts_build/src/types.d.ts +3 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/workers/tools/index.d.ts +1 -1
- package/ts_build/tests/clients/contextLimits.test.js +2 -0
- package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
- package/ts_build/tests/clients/openai.images.test.d.ts +1 -0
- package/ts_build/tests/clients/openai.images.test.js +94 -0
- package/ts_build/tests/clients/openai.images.test.js.map +1 -0
- package/ts_build/tests/clients/pricing.test.js +4 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/compressor/usageBase64.test.d.ts +1 -0
- package/ts_build/tests/compressor/usageBase64.test.js +109 -0
- package/ts_build/tests/compressor/usageBase64.test.js.map +1 -0
- package/ts_build/tests/patching/patchFileOutput.test.js +82 -0
- package/ts_build/tests/patching/patchFileOutput.test.js.map +1 -1
- package/ts_build/tests/patching/transcriptMangleRegression.test.d.ts +1 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js +104 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js.map +1 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js +24 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/x-home.png +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, driver-agnostic interface for the computer-use service.
|
|
3
|
+
*
|
|
4
|
+
* The concrete implementation lives in `@tyvm/knowhow-module-computer-use`, but
|
|
5
|
+
* we declare the shape here so that `ToolContext` / `ModuleContext` can carry a
|
|
6
|
+
* `ComputerUse` service without the core knowhow package taking a runtime
|
|
7
|
+
* dependency on the module.
|
|
8
|
+
*
|
|
9
|
+
* An adapter module (e.g. `@tyvm/knowhow-module-computer-use-nutjs`) can
|
|
10
|
+
* register a `ComputerDriver` against the service in its `register` phase; the
|
|
11
|
+
* base module then `init()`s and picks the best available driver.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface Point {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Size {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Region extends Point, Size {}
|
|
25
|
+
|
|
26
|
+
export type MouseButton = "left" | "right" | "middle";
|
|
27
|
+
|
|
28
|
+
export interface OverlayPrimitive {
|
|
29
|
+
kind: "rect" | "circle" | "line" | "point";
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
x2?: number;
|
|
35
|
+
y2?: number;
|
|
36
|
+
color?: string;
|
|
37
|
+
lineWidth?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Display {
|
|
41
|
+
id: number;
|
|
42
|
+
bounds: Region;
|
|
43
|
+
scaleFactor: number;
|
|
44
|
+
primary: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ScreenshotOptions {
|
|
48
|
+
region?: Region;
|
|
49
|
+
displayId?: number;
|
|
50
|
+
format?: "png" | "jpeg";
|
|
51
|
+
scale?: number;
|
|
52
|
+
/** Native capture downscale. Unlike output encoding scale, drivers may apply
|
|
53
|
+
* this before raw pixels cross the process boundary. */
|
|
54
|
+
captureScale?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DriverCapabilities {
|
|
58
|
+
input: boolean;
|
|
59
|
+
capture: boolean;
|
|
60
|
+
windows: boolean;
|
|
61
|
+
reason?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AccessibilityElement {
|
|
65
|
+
id: string;
|
|
66
|
+
role: string;
|
|
67
|
+
subrole?: string;
|
|
68
|
+
title?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
value?: string;
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
focused?: boolean;
|
|
73
|
+
bounds?: Region;
|
|
74
|
+
actions: string[];
|
|
75
|
+
childCount: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AccessibilityOptions {
|
|
79
|
+
maxDepth?: number;
|
|
80
|
+
maxElements?: number;
|
|
81
|
+
interactiveOnly?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The engine seam. Concrete drivers (Rust core, CLI adapter, nut.js demo)
|
|
86
|
+
* implement this. Registered with the service via `registerDriver`.
|
|
87
|
+
*/
|
|
88
|
+
export interface ComputerDriver {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
/**
|
|
91
|
+
* Lower numbers win when multiple drivers are registered. The default Rust
|
|
92
|
+
* core registers at 100; a fallback CLI adapter at 500; an experimental demo
|
|
93
|
+
* adapter can register at e.g. 10 to force selection.
|
|
94
|
+
*/
|
|
95
|
+
readonly priority?: number;
|
|
96
|
+
|
|
97
|
+
capabilities(): Promise<DriverCapabilities>;
|
|
98
|
+
|
|
99
|
+
// screen
|
|
100
|
+
getDisplays(): Promise<Display[]>;
|
|
101
|
+
screenSize(): Promise<Size>;
|
|
102
|
+
screenshot(opts?: ScreenshotOptions): Promise<Buffer>;
|
|
103
|
+
pixelColor(p: Point): Promise<string>;
|
|
104
|
+
|
|
105
|
+
// mouse
|
|
106
|
+
mousePosition(): Promise<Point>;
|
|
107
|
+
moveMouse(p: Point, opts?: { duration?: number }): Promise<void>;
|
|
108
|
+
click(button?: MouseButton, opts?: { double?: boolean }): Promise<void>;
|
|
109
|
+
mouseDown(button?: MouseButton): Promise<void>;
|
|
110
|
+
mouseUp(button?: MouseButton): Promise<void>;
|
|
111
|
+
drag(
|
|
112
|
+
from: Point,
|
|
113
|
+
to: Point,
|
|
114
|
+
opts?: { button?: MouseButton; duration?: number }
|
|
115
|
+
): Promise<void>;
|
|
116
|
+
scroll(dx: number, dy: number): Promise<void>;
|
|
117
|
+
|
|
118
|
+
// keyboard
|
|
119
|
+
typeText(text: string, opts?: { delay?: number }): Promise<void>;
|
|
120
|
+
pressKey(key: string): Promise<void>;
|
|
121
|
+
hotkey(...keys: string[]): Promise<void>;
|
|
122
|
+
keyDown(key: string): Promise<void>;
|
|
123
|
+
keyUp(key: string): Promise<void>;
|
|
124
|
+
|
|
125
|
+
// windows (optional / capability-gated)
|
|
126
|
+
getActiveWindow?(): Promise<{ title: string; app?: string; bounds?: Region } | null>;
|
|
127
|
+
listWindows?(): Promise<Array<{ title: string; app?: string; bounds?: Region }>>;
|
|
128
|
+
focusWindow?(match: string): Promise<boolean>;
|
|
129
|
+
|
|
130
|
+
accessibilityTrusted?(): Promise<boolean>;
|
|
131
|
+
accessibilityElements?(options?: AccessibilityOptions): Promise<AccessibilityElement[]>;
|
|
132
|
+
setAccessibilityValue?(id: string, value: string): Promise<void>;
|
|
133
|
+
performAccessibilityAction?(id: string, action: string): Promise<void>;
|
|
134
|
+
|
|
135
|
+
// click-through visual debugging (optional / platform capability-gated)
|
|
136
|
+
showOverlay?(primitives: OverlayPrimitive[]): Promise<void>;
|
|
137
|
+
clearOverlay?(): Promise<void>;
|
|
138
|
+
|
|
139
|
+
dispose?(): Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The registered service (`ComputerUse`) surfaced to tools/CLI/MCP. It owns
|
|
144
|
+
* driver selection and provides the normalized, platform-agnostic verbs.
|
|
145
|
+
*
|
|
146
|
+
* We keep this as an interface (not a class) in the core package so the module
|
|
147
|
+
* owns the concrete implementation; anything reading `context.ComputerUse`
|
|
148
|
+
* codes against this contract.
|
|
149
|
+
*/
|
|
150
|
+
export interface ComputerUseService {
|
|
151
|
+
/** Register a driver (called by the base module and any adapter modules). */
|
|
152
|
+
registerDriver(driver: ComputerDriver): void;
|
|
153
|
+
/** List the names of all registered drivers. */
|
|
154
|
+
listDrivers(): string[];
|
|
155
|
+
/** Resolve/select the active driver (highest priority + capable). */
|
|
156
|
+
getDriver(): Promise<ComputerDriver>;
|
|
157
|
+
|
|
158
|
+
capabilities(): Promise<DriverCapabilities>;
|
|
159
|
+
getDisplays(): Promise<Display[]>;
|
|
160
|
+
screenSize(): Promise<Size>;
|
|
161
|
+
/** Returns encoded image bytes (PNG/JPEG) honoring service defaults. */
|
|
162
|
+
screenshot(opts?: ScreenshotOptions): Promise<Buffer>;
|
|
163
|
+
/** Convenience: base64 data (no data: prefix) for multimodal messages. */
|
|
164
|
+
screenshotBase64(opts?: ScreenshotOptions): Promise<string>;
|
|
165
|
+
pixelColor(p: Point): Promise<string>;
|
|
166
|
+
|
|
167
|
+
mousePosition(): Promise<Point>;
|
|
168
|
+
moveMouse(p: Point, opts?: { duration?: number }): Promise<void>;
|
|
169
|
+
click(button?: MouseButton, opts?: { double?: boolean }): Promise<void>;
|
|
170
|
+
drag(
|
|
171
|
+
from: Point,
|
|
172
|
+
to: Point,
|
|
173
|
+
opts?: { button?: MouseButton; duration?: number }
|
|
174
|
+
): Promise<void>;
|
|
175
|
+
scroll(dx: number, dy: number): Promise<void>;
|
|
176
|
+
/** Smooth stepped scrolling (repeat a small delta with a pause between). */
|
|
177
|
+
scrollBy?(
|
|
178
|
+
dx: number,
|
|
179
|
+
dy: number,
|
|
180
|
+
opts?: { repeat?: number; intervalMs?: number }
|
|
181
|
+
): Promise<void>;
|
|
182
|
+
|
|
183
|
+
typeText(text: string, opts?: { delay?: number }): Promise<void>;
|
|
184
|
+
pressKey(key: string): Promise<void>;
|
|
185
|
+
hotkey(...keys: string[]): Promise<void>;
|
|
186
|
+
|
|
187
|
+
/** Screenshot with an optional labeled grid and/or crosshair overlay. */
|
|
188
|
+
screenshotAnnotated?(
|
|
189
|
+
opts?: ScreenshotOptions & {
|
|
190
|
+
grid?: boolean;
|
|
191
|
+
gridStep?: number;
|
|
192
|
+
crosshair?: Point;
|
|
193
|
+
}
|
|
194
|
+
): Promise<Buffer>;
|
|
195
|
+
|
|
196
|
+
getActiveWindow(): Promise<{
|
|
197
|
+
title: string;
|
|
198
|
+
app?: string;
|
|
199
|
+
bounds?: Region;
|
|
200
|
+
} | null>;
|
|
201
|
+
listWindows(): Promise<
|
|
202
|
+
Array<{ title: string; app?: string; bounds?: Region }>
|
|
203
|
+
>;
|
|
204
|
+
focusWindow(match: string): Promise<boolean>;
|
|
205
|
+
|
|
206
|
+
accessibilityTrusted(): Promise<boolean>;
|
|
207
|
+
accessibilityElements(options?: AccessibilityOptions): Promise<AccessibilityElement[]>;
|
|
208
|
+
selectAccessibilityOption(id: string, option: string): Promise<void>;
|
|
209
|
+
setAccessibilityValue(id: string, value: string): Promise<void>;
|
|
210
|
+
performAccessibilityAction(id: string, action: string): Promise<void>;
|
|
211
|
+
|
|
212
|
+
showOverlay(primitives: OverlayPrimitive[]): Promise<void>;
|
|
213
|
+
clearOverlay(): Promise<void>;
|
|
214
|
+
|
|
215
|
+
dispose(): Promise<void>;
|
|
216
|
+
}
|
|
@@ -75,6 +75,21 @@ export class ModulesService {
|
|
|
75
75
|
// modules resolve against cwd/.knowhow/node_modules first. See localResolvePaths()
|
|
76
76
|
// and globalResolvePaths() above.
|
|
77
77
|
|
|
78
|
+
// Two-phase load:
|
|
79
|
+
// Phase 1 (register): resolve + require EVERY module and run its optional
|
|
80
|
+
// `register()`, which may (a) register CLI subcommands on Program and
|
|
81
|
+
// (b) inject shared services into `context` (e.g. `ComputerUse`) so that
|
|
82
|
+
// sibling/adapter modules loaded later can consume them.
|
|
83
|
+
// Phase 2 (init): run every loaded module's `init()`, now that all
|
|
84
|
+
// services registered during phase 1 are present. This lets, e.g., a
|
|
85
|
+
// `-nutjs` adapter module register a driver against the base
|
|
86
|
+
// computer-use service during phase 1, and the base module resolve the
|
|
87
|
+
// best driver during phase 2.
|
|
88
|
+
const loadedModules: {
|
|
89
|
+
modulePath: string;
|
|
90
|
+
module: KnowhowModule;
|
|
91
|
+
}[] = [];
|
|
92
|
+
|
|
78
93
|
for (const modulePath of allModulePaths) {
|
|
79
94
|
// Build an ordered list of candidate resolutions for this module.
|
|
80
95
|
// Relative paths resolve to a single candidate (relative to cwd), while
|
|
@@ -122,15 +137,16 @@ export class ModulesService {
|
|
|
122
137
|
"ModulesService",
|
|
123
138
|
`🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`
|
|
124
139
|
);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
// Phase 1: register (optional). Injects services into `context` and
|
|
141
|
+
// registers CLI commands. Must be idempotent (may run in the early
|
|
142
|
+
// Program-only CLI phase AND the full-services phase).
|
|
143
|
+
if (typeof importedModule.register === "function") {
|
|
144
|
+
await importedModule.register({
|
|
145
|
+
config,
|
|
146
|
+
cwd: process.cwd(),
|
|
147
|
+
context: context as ModuleContext,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
134
150
|
loaded = true;
|
|
135
151
|
break;
|
|
136
152
|
} catch (err: any) {
|
|
@@ -152,6 +168,28 @@ export class ModulesService {
|
|
|
152
168
|
);
|
|
153
169
|
continue;
|
|
154
170
|
}
|
|
171
|
+
loadedModules.push({ modulePath, module: importedModule });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Phase 2: init every successfully-loaded module now that all `register`
|
|
175
|
+
// phases have run and injected their services into `context`.
|
|
176
|
+
for (const { modulePath, module: importedModule } of loadedModules) {
|
|
177
|
+
try {
|
|
178
|
+
await importedModule.init({
|
|
179
|
+
config,
|
|
180
|
+
cwd: process.cwd(),
|
|
181
|
+
context: context as ModuleContext,
|
|
182
|
+
});
|
|
183
|
+
context.Events?.log(
|
|
184
|
+
"ModulesService",
|
|
185
|
+
`✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`
|
|
186
|
+
);
|
|
187
|
+
} catch (err: any) {
|
|
188
|
+
process.stderr.write(
|
|
189
|
+
`\n⚠️ Failed to init module "${modulePath}": ${err?.message || err}\n\n`
|
|
190
|
+
);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
155
193
|
// Only register tools/agents/plugins/clients if the relevant services
|
|
156
194
|
// are available in context (they may not be during early CLI command registration)
|
|
157
195
|
if (context.Agents) {
|
|
@@ -14,6 +14,7 @@ import { TunnelHandler } from "@tyvm/knowhow-tunnel";
|
|
|
14
14
|
import { EventService } from "../EventService";
|
|
15
15
|
import { ConversionService } from "../conversion/ConversionService";
|
|
16
16
|
import { BehaviorsService } from "../BehaviorsService";
|
|
17
|
+
import { ComputerUseService } from "./computerUse";
|
|
17
18
|
|
|
18
19
|
/*
|
|
19
20
|
*
|
|
@@ -62,10 +63,31 @@ export interface ModuleContext {
|
|
|
62
63
|
Conversion?: ConversionService;
|
|
63
64
|
Tunnel?: TunnelHandler;
|
|
64
65
|
Program?: Command;
|
|
65
|
-
Behaviors?: BehaviorsService
|
|
66
|
+
Behaviors?: BehaviorsService;
|
|
67
|
+
ComputerUse?: ComputerUseService;
|
|
68
|
+
[key: string]: any;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export interface KnowhowModule {
|
|
72
|
+
/**
|
|
73
|
+
* Phase 1 (optional): called first, potentially with ONLY `Program` in
|
|
74
|
+
* context (e.g. during early CLI command registration). Use this to:
|
|
75
|
+
* - register CLI subcommands on `context.Program`
|
|
76
|
+
* - inject services into the shared context (e.g.
|
|
77
|
+
* `context.Tools?.addContext("ComputerUse", svc)`) so that OTHER modules
|
|
78
|
+
* (or a sibling adapter module) can consume them during their own init.
|
|
79
|
+
*
|
|
80
|
+
* `register` must be side-effect-light and MUST NOT assume the full service
|
|
81
|
+
* graph is present — only `Program` is guaranteed during the early phase.
|
|
82
|
+
* It may be called more than once (early CLI phase + full-services phase);
|
|
83
|
+
* implementations should be idempotent.
|
|
84
|
+
*/
|
|
85
|
+
register?: (params: InitParams) => Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Phase 2: called with the full service graph available. Use this to
|
|
88
|
+
* actually connect/start things (open drivers, spin up watchers, read
|
|
89
|
+
* services registered by other modules' `register` phase, etc.).
|
|
90
|
+
*/
|
|
69
91
|
init: (params: InitParams) => Promise<void>;
|
|
70
92
|
commands: ModuleChatCommand[];
|
|
71
93
|
tools: ModuleTool[];
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
GeminiTextPricing,
|
|
10
10
|
AnthropicTextPricing,
|
|
11
11
|
XaiTextPricing,
|
|
12
|
+
FireworksTextPricing,
|
|
13
|
+
FireworksEmbeddingPricing,
|
|
12
14
|
} from "../../src/clients/pricing";
|
|
13
15
|
|
|
14
16
|
// Model ids explicitly marked deprecated across all pricing tables. Deprecated
|
|
@@ -21,6 +23,8 @@ const deprecatedModelIds = new Set<string>(
|
|
|
21
23
|
...GeminiTextPricing,
|
|
22
24
|
...AnthropicTextPricing,
|
|
23
25
|
...XaiTextPricing,
|
|
26
|
+
...FireworksTextPricing,
|
|
27
|
+
...FireworksEmbeddingPricing,
|
|
24
28
|
} as Record<string, { deprecated?: boolean }>)
|
|
25
29
|
.filter(([, p]) => p && p.deprecated === true)
|
|
26
30
|
.map(([id]) => id)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { GenericOpenAiClient } from "../../src/clients/openai";
|
|
2
|
+
import { Message } from "../../src/clients/types";
|
|
3
|
+
|
|
4
|
+
describe("GenericOpenAiClient image message conversion", () => {
|
|
5
|
+
const imageUrl = "data:image/jpeg;base64,/9j/4AAQSkZJRg==";
|
|
6
|
+
let client: GenericOpenAiClient;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
client = new GenericOpenAiClient("test-key");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("moves tool image output to a user message for Chat Completions", () => {
|
|
13
|
+
const messages: Message[] = [{
|
|
14
|
+
role: "tool",
|
|
15
|
+
tool_call_id: "call_1",
|
|
16
|
+
name: "loadImageAsBase64",
|
|
17
|
+
content: [{
|
|
18
|
+
type: "image_url",
|
|
19
|
+
image_url: { url: imageUrl, detail: "high" },
|
|
20
|
+
}],
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
const converted = client.toChatCompletionMessages(messages);
|
|
24
|
+
|
|
25
|
+
expect(converted).toEqual([
|
|
26
|
+
{
|
|
27
|
+
role: "tool",
|
|
28
|
+
tool_call_id: "call_1",
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: "Image output attached in the following user message.",
|
|
32
|
+
}],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
role: "user",
|
|
36
|
+
content: [{
|
|
37
|
+
type: "image_url",
|
|
38
|
+
image_url: { url: imageUrl, detail: "high" },
|
|
39
|
+
}],
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("uses input_image parts for the Responses API", () => {
|
|
45
|
+
const converted = client.toResponseContent([
|
|
46
|
+
{ type: "text", text: "Screenshot" },
|
|
47
|
+
{ type: "image_url", image_url: { url: imageUrl, detail: "low" } },
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(converted).toEqual([
|
|
51
|
+
{ type: "input_text", text: "Screenshot" },
|
|
52
|
+
{ type: "input_image", image_url: imageUrl, detail: "low" },
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it.each([
|
|
57
|
+
{ type: "audio_url", audio_url: { url: "data:audio/wav;base64,UklGRg==" } },
|
|
58
|
+
{ type: "video_url", video_url: { url: "data:video/mp4;base64,AAAAIGZ0eXA=" } },
|
|
59
|
+
] as const)("rejects unsupported $type Responses API content instead of dropping it", (part) => {
|
|
60
|
+
expect(() => client.toResponseContent([
|
|
61
|
+
{ type: "text", text: "Media" },
|
|
62
|
+
part,
|
|
63
|
+
])).toThrow(
|
|
64
|
+
`OpenAI Responses API conversion does not support ${part.type} content.`
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("attaches a tool image as input_image in a Responses API request", async () => {
|
|
69
|
+
const create = jest.fn().mockResolvedValue({
|
|
70
|
+
id: "resp_1",
|
|
71
|
+
output: [],
|
|
72
|
+
usage: undefined,
|
|
73
|
+
});
|
|
74
|
+
(client as any).client = { responses: { create } };
|
|
75
|
+
|
|
76
|
+
await client.createChatResponse({
|
|
77
|
+
model: "gpt-5.4",
|
|
78
|
+
reasoning_effort: "none",
|
|
79
|
+
messages: [{
|
|
80
|
+
role: "tool",
|
|
81
|
+
tool_call_id: "call_1",
|
|
82
|
+
content: [{
|
|
83
|
+
type: "image_url",
|
|
84
|
+
image_url: { url: imageUrl, detail: "auto" },
|
|
85
|
+
}],
|
|
86
|
+
}],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(create.mock.calls[0][0].input).toEqual([
|
|
90
|
+
{
|
|
91
|
+
type: "function_call_output",
|
|
92
|
+
call_id: "call_1",
|
|
93
|
+
output: "Image output attached in the following user message.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
role: "user",
|
|
97
|
+
content: [{
|
|
98
|
+
type: "input_image",
|
|
99
|
+
image_url: imageUrl,
|
|
100
|
+
detail: "auto",
|
|
101
|
+
}],
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
GeminiTextPricing,
|
|
31
31
|
AnthropicTextPricing,
|
|
32
32
|
XaiTextPricing,
|
|
33
|
+
FireworksTextPricing,
|
|
34
|
+
FireworksEmbeddingPricing,
|
|
33
35
|
ALL_MODEL_CATALOG,
|
|
34
36
|
XaiImagePricing,
|
|
35
37
|
XaiVideoPricing,
|
|
@@ -61,6 +63,8 @@ describe("Model Pricing Coverage", () => {
|
|
|
61
63
|
...GeminiTextPricing,
|
|
62
64
|
...AnthropicTextPricing,
|
|
63
65
|
...XaiTextPricing,
|
|
66
|
+
...FireworksTextPricing,
|
|
67
|
+
...FireworksEmbeddingPricing,
|
|
64
68
|
};
|
|
65
69
|
|
|
66
70
|
describe("Text completion models have pricing", () => {
|
|
@@ -166,6 +170,8 @@ describe("Model Catalog Coverage", () => {
|
|
|
166
170
|
...GeminiTextPricing,
|
|
167
171
|
...AnthropicTextPricing,
|
|
168
172
|
...XaiTextPricing,
|
|
173
|
+
...FireworksTextPricing,
|
|
174
|
+
...FireworksEmbeddingPricing,
|
|
169
175
|
} as Record<string, { deprecated?: boolean }>)
|
|
170
176
|
.filter(([, p]) => p && p.deprecated === true)
|
|
171
177
|
.map(([id]) => id)
|