@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.
Files changed (151) hide show
  1. package/bin/knowhow.js +22 -20
  2. package/package.json +1 -1
  3. package/scripts/migrate-usage-json.js +147 -0
  4. package/src/agents/base/base.ts +65 -10
  5. package/src/agents/tools/patch.ts +84 -2
  6. package/src/agents/tools/textSearch.ts +98 -3
  7. package/src/chat/modules/AgentModule.ts +129 -6
  8. package/src/chat/modules/SessionsModule.ts +6 -0
  9. package/src/chat/types.ts +14 -0
  10. package/src/chat.ts +11 -0
  11. package/src/cli.ts +2 -0
  12. package/src/clients/anthropic.ts +56 -1
  13. package/src/clients/contextLimits.ts +40 -0
  14. package/src/clients/gemini.ts +59 -5
  15. package/src/clients/http.ts +85 -4
  16. package/src/clients/index.ts +4 -0
  17. package/src/clients/openai.ts +179 -31
  18. package/src/clients/pricing/fireworks.ts +50 -1
  19. package/src/clients/pricing/index.ts +1 -1
  20. package/src/clients/pricing/models.ts +3 -1
  21. package/src/clients/pricing/openai.ts +2 -2
  22. package/src/clients/pricing/qwen.ts +80 -0
  23. package/src/clients/qwen.ts +16 -0
  24. package/src/clients/types.ts +69 -1
  25. package/src/clients/xai.ts +73 -4
  26. package/src/commands/agent.ts +20 -1
  27. package/src/index.ts +9 -0
  28. package/src/processors/Base64ImageDetector.ts +46 -7
  29. package/src/processors/GemmaToolProcessor.ts +163 -0
  30. package/src/processors/index.ts +1 -0
  31. package/src/services/AgentSyncFs.ts +37 -0
  32. package/src/services/SessionManager.ts +14 -0
  33. package/src/services/Tools.ts +3 -0
  34. package/src/services/modules/computerUse.ts +216 -0
  35. package/src/services/modules/index.ts +47 -9
  36. package/src/services/modules/types.ts +23 -1
  37. package/src/types.ts +3 -0
  38. package/tests/clients/contextLimits.test.ts +4 -0
  39. package/tests/clients/openai.images.test.ts +105 -0
  40. package/tests/clients/pricing.test.ts +6 -0
  41. package/tests/compressor/usageBase64.json +159 -0
  42. package/tests/compressor/usageBase64.test.ts +175 -0
  43. package/tests/patching/fixtures/transcript-mangles/agent-sync-fs-nested-catch.json +9 -0
  44. package/tests/patching/fixtures/transcript-mangles/automation-functions-crossed.json +9 -0
  45. package/tests/patching/fixtures/transcript-mangles/fruit-ninja-confirmed-retirement.json +8 -0
  46. package/tests/patching/patchFileOutput.test.ts +112 -1
  47. package/tests/patching/transcriptMangleRegression.test.ts +125 -0
  48. package/tests/processors/Base64ImageDetector.test.ts +36 -1
  49. package/ts_build/package.json +1 -1
  50. package/ts_build/src/agents/base/base.d.ts +8 -1
  51. package/ts_build/src/agents/base/base.js +37 -3
  52. package/ts_build/src/agents/base/base.js.map +1 -1
  53. package/ts_build/src/agents/tools/patch.d.ts +1 -0
  54. package/ts_build/src/agents/tools/patch.js +35 -2
  55. package/ts_build/src/agents/tools/patch.js.map +1 -1
  56. package/ts_build/src/agents/tools/textSearch.js +56 -2
  57. package/ts_build/src/agents/tools/textSearch.js.map +1 -1
  58. package/ts_build/src/chat/modules/AgentModule.d.ts +14 -1
  59. package/ts_build/src/chat/modules/AgentModule.js +73 -5
  60. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  61. package/ts_build/src/chat/modules/SessionsModule.js +4 -0
  62. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  63. package/ts_build/src/chat/types.d.ts +11 -0
  64. package/ts_build/src/chat.js +7 -0
  65. package/ts_build/src/chat.js.map +1 -1
  66. package/ts_build/src/cli.d.ts +2 -1
  67. package/ts_build/src/cli.js +1 -0
  68. package/ts_build/src/cli.js.map +1 -1
  69. package/ts_build/src/clients/anthropic.js +28 -1
  70. package/ts_build/src/clients/anthropic.js.map +1 -1
  71. package/ts_build/src/clients/contextLimits.js +25 -0
  72. package/ts_build/src/clients/contextLimits.js.map +1 -1
  73. package/ts_build/src/clients/gemini.js +48 -4
  74. package/ts_build/src/clients/gemini.js.map +1 -1
  75. package/ts_build/src/clients/http.d.ts +2 -0
  76. package/ts_build/src/clients/http.js +66 -9
  77. package/ts_build/src/clients/http.js.map +1 -1
  78. package/ts_build/src/clients/index.d.ts +1 -0
  79. package/ts_build/src/clients/index.js +4 -0
  80. package/ts_build/src/clients/index.js.map +1 -1
  81. package/ts_build/src/clients/openai.d.ts +6 -1
  82. package/ts_build/src/clients/openai.js +131 -33
  83. package/ts_build/src/clients/openai.js.map +1 -1
  84. package/ts_build/src/clients/pricing/fireworks.d.ts +4 -1
  85. package/ts_build/src/clients/pricing/fireworks.js +33 -1
  86. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  87. package/ts_build/src/clients/pricing/index.d.ts +1 -1
  88. package/ts_build/src/clients/pricing/index.js +6 -2
  89. package/ts_build/src/clients/pricing/index.js.map +1 -1
  90. package/ts_build/src/clients/pricing/models.d.ts +2 -1
  91. package/ts_build/src/clients/pricing/models.js +4 -1
  92. package/ts_build/src/clients/pricing/models.js.map +1 -1
  93. package/ts_build/src/clients/pricing/openai.js +2 -2
  94. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  95. package/ts_build/src/clients/pricing/qwen.d.ts +5 -0
  96. package/ts_build/src/clients/pricing/qwen.js +30 -0
  97. package/ts_build/src/clients/pricing/qwen.js.map +1 -0
  98. package/ts_build/src/clients/qwen.d.ts +4 -0
  99. package/ts_build/src/clients/qwen.js +15 -0
  100. package/ts_build/src/clients/qwen.js.map +1 -0
  101. package/ts_build/src/clients/types.d.ts +12 -0
  102. package/ts_build/src/clients/xai.d.ts +1 -0
  103. package/ts_build/src/clients/xai.js +56 -9
  104. package/ts_build/src/clients/xai.js.map +1 -1
  105. package/ts_build/src/commands/agent.js +10 -1
  106. package/ts_build/src/commands/agent.js.map +1 -1
  107. package/ts_build/src/index.d.ts +2 -0
  108. package/ts_build/src/index.js +1 -0
  109. package/ts_build/src/index.js.map +1 -1
  110. package/ts_build/src/processors/Base64ImageDetector.js +22 -3
  111. package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
  112. package/ts_build/src/processors/GemmaToolProcessor.d.ts +14 -0
  113. package/ts_build/src/processors/GemmaToolProcessor.js +97 -0
  114. package/ts_build/src/processors/GemmaToolProcessor.js.map +1 -0
  115. package/ts_build/src/processors/index.d.ts +1 -0
  116. package/ts_build/src/processors/index.js +3 -1
  117. package/ts_build/src/processors/index.js.map +1 -1
  118. package/ts_build/src/services/AgentSyncFs.js +17 -0
  119. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  120. package/ts_build/src/services/SessionManager.js +12 -0
  121. package/ts_build/src/services/SessionManager.js.map +1 -1
  122. package/ts_build/src/services/Tools.d.ts +2 -0
  123. package/ts_build/src/services/Tools.js.map +1 -1
  124. package/ts_build/src/services/modules/computerUse.d.ts +163 -0
  125. package/ts_build/src/services/modules/computerUse.js +3 -0
  126. package/ts_build/src/services/modules/computerUse.js.map +1 -0
  127. package/ts_build/src/services/modules/index.d.ts +1 -0
  128. package/ts_build/src/services/modules/index.js +23 -6
  129. package/ts_build/src/services/modules/index.js.map +1 -1
  130. package/ts_build/src/services/modules/types.d.ts +4 -0
  131. package/ts_build/src/types.d.ts +3 -0
  132. package/ts_build/src/types.js.map +1 -1
  133. package/ts_build/src/workers/tools/index.d.ts +1 -1
  134. package/ts_build/tests/clients/contextLimits.test.js +2 -0
  135. package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
  136. package/ts_build/tests/clients/openai.images.test.d.ts +1 -0
  137. package/ts_build/tests/clients/openai.images.test.js +94 -0
  138. package/ts_build/tests/clients/openai.images.test.js.map +1 -0
  139. package/ts_build/tests/clients/pricing.test.js +4 -0
  140. package/ts_build/tests/clients/pricing.test.js.map +1 -1
  141. package/ts_build/tests/compressor/usageBase64.test.d.ts +1 -0
  142. package/ts_build/tests/compressor/usageBase64.test.js +109 -0
  143. package/ts_build/tests/compressor/usageBase64.test.js.map +1 -0
  144. package/ts_build/tests/patching/patchFileOutput.test.js +82 -0
  145. package/ts_build/tests/patching/patchFileOutput.test.js.map +1 -1
  146. package/ts_build/tests/patching/transcriptMangleRegression.test.d.ts +1 -0
  147. package/ts_build/tests/patching/transcriptMangleRegression.test.js +104 -0
  148. package/ts_build/tests/patching/transcriptMangleRegression.test.js.map +1 -0
  149. package/ts_build/tests/processors/Base64ImageDetector.test.js +24 -0
  150. package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
  151. 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
- await importedModule.init({
126
- config,
127
- cwd: process.cwd(),
128
- context: context as ModuleContext,
129
- });
130
- context.Events?.log(
131
- "ModulesService",
132
- `✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`
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
@@ -86,6 +86,9 @@ export type Config = {
86
86
  chat?: {
87
87
  rootModule?: string;
88
88
  renderer?: string;
89
+ defaultAgent?: string;
90
+ defaultModel?: string;
91
+ defaultProvider?: string;
89
92
  modules?: string[];
90
93
  };
91
94
  modules: string[];
@@ -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)