@tyvm/knowhow 0.0.69 → 0.0.71

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 (214) hide show
  1. package/docs/shell-commands.md +174 -0
  2. package/package.json +1 -1
  3. package/src/agents/base/base.ts +4 -5
  4. package/src/agents/developer/developer.ts +21 -13
  5. package/src/agents/tools/agentCall.ts +4 -2
  6. package/src/agents/tools/fileSearch.ts +5 -1
  7. package/src/agents/tools/startAgentTask.ts +131 -22
  8. package/src/agents/tools/stringReplace.ts +42 -12
  9. package/src/chat/CliChatService.ts +57 -11
  10. package/src/chat/modules/AgentModule.ts +72 -12
  11. package/src/chat/modules/CustomCommandsModule.ts +79 -0
  12. package/src/chat/modules/InternalChatModule.ts +11 -1
  13. package/src/chat/modules/ShellCommandModule.ts +96 -0
  14. package/src/chat/modules/index.ts +1 -0
  15. package/src/chat/types.ts +14 -2
  16. package/src/chat.ts +16 -13
  17. package/src/cli.ts +16 -6
  18. package/src/clients/anthropic.ts +82 -112
  19. package/src/clients/gemini.ts +445 -87
  20. package/src/clients/index.ts +125 -0
  21. package/src/clients/knowhow.ts +81 -0
  22. package/src/clients/openai.ts +256 -145
  23. package/src/clients/pricing/anthropic.ts +90 -0
  24. package/src/clients/pricing/google.ts +65 -0
  25. package/src/clients/pricing/index.ts +4 -0
  26. package/src/clients/pricing/openai.ts +134 -0
  27. package/src/clients/pricing/xai.ts +62 -0
  28. package/src/clients/types.ts +170 -1
  29. package/src/clients/xai.ts +275 -46
  30. package/src/config.ts +61 -15
  31. package/src/embeddings.ts +9 -1
  32. package/src/microphone.ts +15 -16
  33. package/src/migrations.ts +151 -0
  34. package/src/plugins/AgentsMdPlugin.ts +118 -0
  35. package/src/plugins/PluginBase.ts +8 -0
  36. package/src/plugins/downloader/downloader.ts +5 -6
  37. package/src/plugins/embedding.ts +10 -8
  38. package/src/plugins/exec.ts +70 -0
  39. package/src/plugins/github.ts +120 -74
  40. package/src/plugins/language.ts +11 -13
  41. package/src/plugins/plugins.ts +25 -4
  42. package/src/plugins/tmux.ts +132 -0
  43. package/src/plugins/types.ts +1 -0
  44. package/src/plugins/vim.ts +14 -1
  45. package/src/services/AgentSyncFs.ts +417 -0
  46. package/src/services/{AgentSynchronization.ts → AgentSyncKnowhowWeb.ts} +2 -2
  47. package/src/services/EventService.ts +0 -1
  48. package/src/services/KnowhowClient.ts +106 -0
  49. package/src/services/index.ts +4 -2
  50. package/src/types.ts +57 -4
  51. package/src/worker.ts +11 -6
  52. package/tests/manual/modalities/README.md +157 -0
  53. package/tests/manual/modalities/google.modalities.test.ts +335 -0
  54. package/tests/manual/modalities/openai.modalities.test.ts +329 -0
  55. package/tests/manual/modalities/streaming.test.ts +260 -0
  56. package/tests/manual/modalities/xai.modalities.test.ts +307 -0
  57. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -5
  58. package/tests/plugins/language/languagePlugin-integration.test.ts +1 -1
  59. package/tests/plugins/language/languagePlugin.test.ts +17 -8
  60. package/ts_build/package.json +1 -1
  61. package/ts_build/src/agents/base/base.d.ts +3 -3
  62. package/ts_build/src/agents/base/base.js +1 -1
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/developer/developer.js +21 -12
  65. package/ts_build/src/agents/developer/developer.js.map +1 -1
  66. package/ts_build/src/agents/tools/agentCall.js +4 -2
  67. package/ts_build/src/agents/tools/agentCall.js.map +1 -1
  68. package/ts_build/src/agents/tools/executeScript/index.d.ts +1 -1
  69. package/ts_build/src/agents/tools/fileSearch.js +2 -1
  70. package/ts_build/src/agents/tools/fileSearch.js.map +1 -1
  71. package/ts_build/src/agents/tools/github/index.d.ts +1 -1
  72. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -1
  73. package/ts_build/src/agents/tools/startAgentTask.js +118 -17
  74. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  75. package/ts_build/src/agents/tools/stringReplace.js +29 -12
  76. package/ts_build/src/agents/tools/stringReplace.js.map +1 -1
  77. package/ts_build/src/chat/CliChatService.d.ts +4 -0
  78. package/ts_build/src/chat/CliChatService.js +39 -5
  79. package/ts_build/src/chat/CliChatService.js.map +1 -1
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +4 -1
  81. package/ts_build/src/chat/modules/AgentModule.js +49 -11
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +9 -0
  84. package/ts_build/src/chat/modules/CustomCommandsModule.js +58 -0
  85. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -0
  86. package/ts_build/src/chat/modules/InternalChatModule.d.ts +2 -0
  87. package/ts_build/src/chat/modules/InternalChatModule.js +10 -0
  88. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/ShellCommandModule.d.ts +8 -0
  90. package/ts_build/src/chat/modules/ShellCommandModule.js +83 -0
  91. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -0
  92. package/ts_build/src/chat/modules/index.d.ts +1 -0
  93. package/ts_build/src/chat/modules/index.js +3 -1
  94. package/ts_build/src/chat/modules/index.js.map +1 -1
  95. package/ts_build/src/chat/types.d.ts +11 -1
  96. package/ts_build/src/chat.js +16 -13
  97. package/ts_build/src/chat.js.map +1 -1
  98. package/ts_build/src/cli.js +10 -3
  99. package/ts_build/src/cli.js.map +1 -1
  100. package/ts_build/src/clients/anthropic.d.ts +5 -1
  101. package/ts_build/src/clients/anthropic.js +61 -112
  102. package/ts_build/src/clients/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/gemini.d.ts +80 -2
  104. package/ts_build/src/clients/gemini.js +336 -74
  105. package/ts_build/src/clients/gemini.js.map +1 -1
  106. package/ts_build/src/clients/index.d.ts +9 -1
  107. package/ts_build/src/clients/index.js +65 -0
  108. package/ts_build/src/clients/index.js.map +1 -1
  109. package/ts_build/src/clients/knowhow.d.ts +9 -1
  110. package/ts_build/src/clients/knowhow.js +43 -0
  111. package/ts_build/src/clients/knowhow.js.map +1 -1
  112. package/ts_build/src/clients/openai.d.ts +9 -1
  113. package/ts_build/src/clients/openai.js +201 -133
  114. package/ts_build/src/clients/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/anthropic.d.ts +17 -0
  116. package/ts_build/src/clients/pricing/anthropic.js +93 -0
  117. package/ts_build/src/clients/pricing/anthropic.js.map +1 -0
  118. package/ts_build/src/clients/pricing/google.d.ts +73 -0
  119. package/ts_build/src/clients/pricing/google.js +68 -0
  120. package/ts_build/src/clients/pricing/google.js.map +1 -0
  121. package/ts_build/src/clients/pricing/index.d.ts +4 -0
  122. package/ts_build/src/clients/pricing/index.js +14 -0
  123. package/ts_build/src/clients/pricing/index.js.map +1 -0
  124. package/ts_build/src/clients/pricing/openai.d.ts +7 -0
  125. package/ts_build/src/clients/pricing/openai.js +137 -0
  126. package/ts_build/src/clients/pricing/openai.js.map +1 -0
  127. package/ts_build/src/clients/pricing/xai.d.ts +26 -0
  128. package/ts_build/src/clients/pricing/xai.js +59 -0
  129. package/ts_build/src/clients/pricing/xai.js.map +1 -0
  130. package/ts_build/src/clients/types.d.ts +135 -0
  131. package/ts_build/src/clients/xai.d.ts +9 -1
  132. package/ts_build/src/clients/xai.js +178 -46
  133. package/ts_build/src/clients/xai.js.map +1 -1
  134. package/ts_build/src/config.d.ts +1 -0
  135. package/ts_build/src/config.js +45 -16
  136. package/ts_build/src/config.js.map +1 -1
  137. package/ts_build/src/embeddings.js +8 -1
  138. package/ts_build/src/embeddings.js.map +1 -1
  139. package/ts_build/src/microphone.js +7 -9
  140. package/ts_build/src/microphone.js.map +1 -1
  141. package/ts_build/src/migrations.d.ts +17 -0
  142. package/ts_build/src/migrations.js +86 -0
  143. package/ts_build/src/migrations.js.map +1 -0
  144. package/ts_build/src/plugins/AgentsMdPlugin.d.ts +13 -0
  145. package/ts_build/src/plugins/AgentsMdPlugin.js +118 -0
  146. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -0
  147. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  148. package/ts_build/src/plugins/PluginBase.js +3 -0
  149. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  150. package/ts_build/src/plugins/downloader/downloader.js +5 -5
  151. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  152. package/ts_build/src/plugins/embedding.js +9 -8
  153. package/ts_build/src/plugins/embedding.js.map +1 -1
  154. package/ts_build/src/plugins/exec.d.ts +10 -0
  155. package/ts_build/src/plugins/exec.js +56 -0
  156. package/ts_build/src/plugins/exec.js.map +1 -0
  157. package/ts_build/src/plugins/github.js +93 -51
  158. package/ts_build/src/plugins/github.js.map +1 -1
  159. package/ts_build/src/plugins/language.js +14 -11
  160. package/ts_build/src/plugins/language.js.map +1 -1
  161. package/ts_build/src/plugins/plugins.d.ts +1 -0
  162. package/ts_build/src/plugins/plugins.js +19 -1
  163. package/ts_build/src/plugins/plugins.js.map +1 -1
  164. package/ts_build/src/plugins/tmux.d.ts +14 -0
  165. package/ts_build/src/plugins/tmux.js +108 -0
  166. package/ts_build/src/plugins/tmux.js.map +1 -0
  167. package/ts_build/src/plugins/types.d.ts +1 -0
  168. package/ts_build/src/plugins/vim.js +11 -1
  169. package/ts_build/src/plugins/vim.js.map +1 -1
  170. package/ts_build/src/services/AgentSyncFs.d.ts +34 -0
  171. package/ts_build/src/services/AgentSyncFs.js +325 -0
  172. package/ts_build/src/services/AgentSyncFs.js.map +1 -0
  173. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +29 -0
  174. package/ts_build/src/services/AgentSyncKnowhowWeb.js +178 -0
  175. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -0
  176. package/ts_build/src/services/AgentSynchronization.d.ts +1 -1
  177. package/ts_build/src/services/AgentSynchronization.js +3 -3
  178. package/ts_build/src/services/AgentSynchronization.js.map +1 -1
  179. package/ts_build/src/services/EventService.js.map +1 -1
  180. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  181. package/ts_build/src/services/KnowhowClient.js +58 -0
  182. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  183. package/ts_build/src/services/index.d.ts +2 -1
  184. package/ts_build/src/services/index.js +2 -1
  185. package/ts_build/src/services/index.js.map +1 -1
  186. package/ts_build/src/types.d.ts +26 -1
  187. package/ts_build/src/types.js +45 -4
  188. package/ts_build/src/types.js.map +1 -1
  189. package/ts_build/src/utils/PersistentInputManager.d.ts +28 -0
  190. package/ts_build/src/utils/PersistentInputManager.js +293 -0
  191. package/ts_build/src/utils/PersistentInputManager.js.map +1 -0
  192. package/ts_build/src/worker.js +2 -2
  193. package/ts_build/src/worker.js.map +1 -1
  194. package/ts_build/tests/manual/modalities/google.modalities.test.d.ts +1 -0
  195. package/ts_build/tests/manual/modalities/google.modalities.test.js +252 -0
  196. package/ts_build/tests/manual/modalities/google.modalities.test.js.map +1 -0
  197. package/ts_build/tests/manual/modalities/openai.modalities.test.d.ts +1 -0
  198. package/ts_build/tests/manual/modalities/openai.modalities.test.js +252 -0
  199. package/ts_build/tests/manual/modalities/openai.modalities.test.js.map +1 -0
  200. package/ts_build/tests/manual/modalities/streaming.test.d.ts +1 -0
  201. package/ts_build/tests/manual/modalities/streaming.test.js +206 -0
  202. package/ts_build/tests/manual/modalities/streaming.test.js.map +1 -0
  203. package/ts_build/tests/manual/modalities/xai.modalities.test.d.ts +1 -0
  204. package/ts_build/tests/manual/modalities/xai.modalities.test.js +226 -0
  205. package/ts_build/tests/manual/modalities/xai.modalities.test.js.map +1 -0
  206. package/ts_build/tests/manual/persistent-input-test.d.ts +1 -0
  207. package/ts_build/tests/manual/persistent-input-test.js +35 -0
  208. package/ts_build/tests/manual/persistent-input-test.js.map +1 -0
  209. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -5
  210. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  211. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +1 -1
  212. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
  213. package/ts_build/tests/plugins/language/languagePlugin.test.js +17 -7
  214. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Streaming Text Manual Test
3
+ *
4
+ * Tests streaming text generation across all providers:
5
+ * 1. OpenAI - GPT-4o streaming
6
+ * 2. Anthropic - Claude streaming
7
+ * 3. Google - Gemini streaming
8
+ * 4. XAI - Grok streaming
9
+ *
10
+ * Run with:
11
+ * npx jest tests/manual/modalities/streaming.test.ts --testTimeout=120000
12
+ */
13
+
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { AIClient } from "../../../src/clients";
17
+ import { Models } from "../../../src/types";
18
+
19
+ const OUTPUT_DIR = path.join(__dirname, "outputs", "streaming");
20
+
21
+ function ensureOutputDir() {
22
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
23
+ }
24
+
25
+ describe("Streaming Text Generation", () => {
26
+ let client: AIClient;
27
+
28
+ beforeAll(() => {
29
+ ensureOutputDir();
30
+ client = new AIClient();
31
+ });
32
+
33
+ // ─── 1. OpenAI Streaming ────────────────────────────────────────────────────
34
+
35
+ test("1. OpenAI GPT-4o – streaming text generation", async () => {
36
+ const outputPath = path.join(OUTPUT_DIR, "openai-streaming.txt");
37
+ if (fs.existsSync(outputPath)) {
38
+ console.log(`Skipping: output already exists at ${outputPath}`);
39
+ return;
40
+ }
41
+
42
+ if (!process.env.OPENAI_KEY) {
43
+ console.log("Skipping: OPENAI_KEY not set");
44
+ return;
45
+ }
46
+
47
+ const prompt = "Count from 1 to 10, one number per line.";
48
+
49
+ // Use the raw OpenAI client for streaming
50
+ const openaiClient = client.clients.openai as any;
51
+ if (!openaiClient) {
52
+ console.log("Skipping: OpenAI client not available");
53
+ return;
54
+ }
55
+
56
+ const stream = await openaiClient.client.chat.completions.create({
57
+ model: Models.openai.GPT_4o_Mini,
58
+ messages: [{ role: "user", content: prompt }],
59
+ stream: true,
60
+ max_tokens: 100,
61
+ });
62
+
63
+ let fullText = "";
64
+ const chunks: string[] = [];
65
+
66
+ for await (const chunk of stream) {
67
+ const content = chunk.choices[0]?.delta?.content || "";
68
+ if (content) {
69
+ fullText += content;
70
+ chunks.push(content);
71
+ }
72
+ }
73
+
74
+ fs.writeFileSync(
75
+ outputPath,
76
+ `Full text:\n${fullText}\n\nChunks (${chunks.length}):\n${chunks.join(" | ")}`
77
+ );
78
+
79
+ console.log(`✅ OpenAI streaming completed`);
80
+ console.log(` Output saved to: ${outputPath}`);
81
+ console.log(` Total chunks: ${chunks.length}`);
82
+ console.log(` Full text: ${fullText.substring(0, 100)}...`);
83
+
84
+ expect(fullText).toBeTruthy();
85
+ expect(chunks.length).toBeGreaterThan(0);
86
+ expect(fullText).toContain("1");
87
+ expect(fullText).toContain("10");
88
+ }, 60000);
89
+
90
+ // ─── 2. Anthropic Streaming ─────────────────────────────────────────────────
91
+
92
+ test("2. Anthropic Claude – streaming text generation", async () => {
93
+ const outputPath = path.join(OUTPUT_DIR, "anthropic-streaming.txt");
94
+ if (fs.existsSync(outputPath)) {
95
+ console.log(`Skipping: output already exists at ${outputPath}`);
96
+ return;
97
+ }
98
+
99
+ if (!process.env.ANTHROPIC_API_KEY) {
100
+ console.log("Skipping: ANTHROPIC_API_KEY not set");
101
+ return;
102
+ }
103
+
104
+ const prompt = "Count from 1 to 10, one number per line.";
105
+
106
+ const anthropicClient = client.clients.anthropic as any;
107
+ if (!anthropicClient) {
108
+ console.log("Skipping: Anthropic client not available");
109
+ return;
110
+ }
111
+
112
+ const stream = await anthropicClient.client.messages.create({
113
+ model: Models.anthropic.Haiku4_5,
114
+ max_tokens: 100,
115
+ messages: [{ role: "user", content: prompt }],
116
+ stream: true,
117
+ });
118
+
119
+ let fullText = "";
120
+ const chunks: string[] = [];
121
+
122
+ for await (const chunk of stream) {
123
+ if (
124
+ chunk.type === "content_block_delta" &&
125
+ chunk.delta.type === "text_delta"
126
+ ) {
127
+ const content = chunk.delta.text;
128
+ if (content) {
129
+ fullText += content;
130
+ chunks.push(content);
131
+ }
132
+ }
133
+ }
134
+
135
+ fs.writeFileSync(
136
+ outputPath,
137
+ `Full text:\n${fullText}\n\nChunks (${chunks.length}):\n${chunks.join(" | ")}`
138
+ );
139
+
140
+ console.log(`✅ Anthropic streaming completed`);
141
+ console.log(` Output saved to: ${outputPath}`);
142
+ console.log(` Total chunks: ${chunks.length}`);
143
+ console.log(` Full text: ${fullText.substring(0, 100)}...`);
144
+
145
+ expect(fullText).toBeTruthy();
146
+ expect(chunks.length).toBeGreaterThan(0);
147
+ expect(fullText).toContain("1");
148
+ expect(fullText).toContain("10");
149
+ }, 60000);
150
+
151
+ // ─── 3. Google Gemini Streaming ─────────────────────────────────────────────
152
+
153
+ test("3. Google Gemini – streaming text generation", async () => {
154
+ const outputPath = path.join(OUTPUT_DIR, "google-streaming.txt");
155
+ if (fs.existsSync(outputPath)) {
156
+ console.log(`Skipping: output already exists at ${outputPath}`);
157
+ return;
158
+ }
159
+
160
+ if (!process.env.GEMINI_API_KEY) {
161
+ console.log("Skipping: GEMINI_API_KEY not set");
162
+ return;
163
+ }
164
+
165
+ const prompt = "Count from 1 to 10, one number per line.";
166
+
167
+ const geminiClient = client.clients.google as any;
168
+ if (!geminiClient) {
169
+ console.log("Skipping: Google client not available");
170
+ return;
171
+ }
172
+
173
+ const result = await geminiClient.client.models.generateContentStream({
174
+ model: Models.google.Gemini_20_Flash,
175
+ contents: [{ role: 'user', parts: [{ text: prompt }] }]
176
+ });
177
+
178
+ let fullText = "";
179
+ const chunks: string[] = [];
180
+
181
+ for await (const chunk of result) {
182
+ const chunkText = chunk.candidates?.[0]?.content?.parts?.[0]?.text || '';
183
+ if (chunkText) {
184
+ fullText += chunkText;
185
+ chunks.push(chunkText);
186
+ }
187
+ }
188
+
189
+ fs.writeFileSync(
190
+ outputPath,
191
+ `Full text:\n${fullText}\n\nChunks (${chunks.length}):\n${chunks.join(" | ")}`
192
+ );
193
+
194
+ console.log(`✅ Google Gemini streaming completed`);
195
+ console.log(` Output saved to: ${outputPath}`);
196
+ console.log(` Total chunks: ${chunks.length}`);
197
+ console.log(` Full text: ${fullText.substring(0, 100)}...`);
198
+
199
+ expect(fullText).toBeTruthy();
200
+ expect(chunks.length).toBeGreaterThan(0);
201
+ expect(fullText).toContain("1");
202
+ expect(fullText).toContain("10");
203
+ }, 60000);
204
+
205
+ // ─── 4. XAI Grok Streaming ──────────────────────────────────────────────────
206
+
207
+ test("4. XAI Grok – streaming text generation", async () => {
208
+ const outputPath = path.join(OUTPUT_DIR, "xai-streaming.txt");
209
+ if (fs.existsSync(outputPath)) {
210
+ console.log(`Skipping: output already exists at ${outputPath}`);
211
+ return;
212
+ }
213
+
214
+ if (!process.env.XAI_API_KEY) {
215
+ console.log("Skipping: XAI_API_KEY not set");
216
+ return;
217
+ }
218
+
219
+ const prompt = "Count from 1 to 10, one number per line.";
220
+
221
+ const xaiClient = client.clients.xai as any;
222
+ if (!xaiClient) {
223
+ console.log("Skipping: XAI client not available");
224
+ return;
225
+ }
226
+
227
+ const stream = await xaiClient.client.chat.completions.create({
228
+ model: Models.xai.Grok3MiniFastBeta,
229
+ messages: [{ role: "user", content: prompt }],
230
+ stream: true,
231
+ max_tokens: 100,
232
+ });
233
+
234
+ let fullText = "";
235
+ const chunks: string[] = [];
236
+
237
+ for await (const chunk of stream) {
238
+ const content = chunk.choices[0]?.delta?.content || "";
239
+ if (content) {
240
+ fullText += content;
241
+ chunks.push(content);
242
+ }
243
+ }
244
+
245
+ fs.writeFileSync(
246
+ outputPath,
247
+ `Full text:\n${fullText}\n\nChunks (${chunks.length}):\n${chunks.join(" | ")}`
248
+ );
249
+
250
+ console.log(`✅ XAI Grok streaming completed`);
251
+ console.log(` Output saved to: ${outputPath}`);
252
+ console.log(` Total chunks: ${chunks.length}`);
253
+ console.log(` Full text: ${fullText.substring(0, 100)}...`);
254
+
255
+ expect(fullText).toBeTruthy();
256
+ expect(chunks.length).toBeGreaterThan(0);
257
+ expect(fullText).toContain("1");
258
+ expect(fullText).toContain("10");
259
+ }, 60000);
260
+ });
@@ -0,0 +1,307 @@
1
+ /**
2
+ * XAI (Grok) Modalities Manual Test
3
+ *
4
+ * Tests:
5
+ * 1. Image generation (grok-imagine-image) → saved to disk
6
+ * 2. Vision – send generated image to Grok vision model and describe it
7
+ * 3. Audio generation – not supported by XAI (documented)
8
+ * 4. Audio transcription – not supported by XAI (documented)
9
+ * 5. Video generation (grok-imagine-video) → polls until done, saves URL to disk
10
+ *
11
+ * Run with:
12
+ * npx jest tests/manual/modalities/xai.modalities.test.ts --testTimeout=720000
13
+ */
14
+
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ import { AIClient } from "../../../src/clients";
18
+ import { Models } from "../../../src/types";
19
+
20
+ const OUTPUT_DIR = path.join(__dirname, "outputs", "xai");
21
+
22
+ function ensureOutputDir() {
23
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
24
+ }
25
+
26
+ describe("XAI (Grok) Modalities", () => {
27
+ let client: AIClient;
28
+
29
+ beforeAll(() => {
30
+ if (!process.env.XAI_API_KEY) {
31
+ console.warn("XAI_API_KEY not set – skipping XAI modality tests");
32
+ }
33
+ ensureOutputDir();
34
+ client = new AIClient();
35
+ });
36
+
37
+ // ─── 1. Image Generation (grok-imagine-image) ────────────────────────────────
38
+
39
+ test("1. XAI grok-imagine-image – generate image and save to disk", async () => {
40
+ if (!process.env.XAI_API_KEY) {
41
+ console.log("Skipping: XAI_API_KEY not set");
42
+ return;
43
+ }
44
+
45
+ const prompt =
46
+ "A breathtaking aerial view of a neon-lit futuristic city at night, " +
47
+ "flying cars, holographic billboards, cyberpunk aesthetic";
48
+
49
+ const response = await client.createImageGeneration("xai", {
50
+ model: "grok-imagine-image",
51
+ prompt,
52
+ n: 1,
53
+ response_format: "b64_json",
54
+ });
55
+
56
+ expect(response.data.length).toBeGreaterThan(0);
57
+
58
+ const imageData = response.data[0];
59
+ const outputPath = path.join(OUTPUT_DIR, "aurora-output.png");
60
+
61
+ if (imageData.b64_json) {
62
+ const buffer = Buffer.from(imageData.b64_json, "base64");
63
+ fs.writeFileSync(outputPath, buffer);
64
+ console.log(`✅ Image saved to: ${outputPath}`);
65
+ console.log(` Size: ${buffer.length} bytes`);
66
+ } else if (imageData.url) {
67
+ fs.writeFileSync(
68
+ path.join(OUTPUT_DIR, "aurora-output-url.txt"),
69
+ imageData.url
70
+ );
71
+ console.log(`✅ Image URL saved: ${imageData.url}`);
72
+ }
73
+
74
+ console.log(` Estimated cost: $${response.usd_cost?.toFixed(6)}`);
75
+
76
+ expect(response.created).toBeGreaterThan(0);
77
+ expect(response.data.length).toBeGreaterThan(0);
78
+ }, 90000);
79
+
80
+ // ─── 2. Vision – send generated image to Grok Vision ────────────────────────
81
+
82
+ test("2. XAI Grok Vision – describe the generated image", async () => {
83
+ if (!process.env.XAI_API_KEY) {
84
+ console.log("Skipping: XAI_API_KEY not set");
85
+ return;
86
+ }
87
+
88
+ const imagePath = path.join(OUTPUT_DIR, "aurora-output.png");
89
+ if (!fs.existsSync(imagePath)) {
90
+ console.log(
91
+ "Skipping: aurora-output.png not found – run image generation test first"
92
+ );
93
+ return;
94
+ }
95
+
96
+ const imageBuffer = fs.readFileSync(imagePath);
97
+ const base64Image = imageBuffer.toString("base64");
98
+ const dataUrl = `data:image/png;base64,${base64Image}`;
99
+
100
+ const response = await client.createCompletion("xai", {
101
+ model: Models.xai.Grok2Vision1212,
102
+ messages: [
103
+ {
104
+ role: "user",
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: "Please describe this image in detail. What do you see?",
109
+ },
110
+ {
111
+ type: "image_url",
112
+ image_url: { url: dataUrl },
113
+ },
114
+ ],
115
+ },
116
+ ],
117
+ max_tokens: 500,
118
+ });
119
+
120
+ const description = response.choices[0]?.message?.content || "";
121
+ const outputPath = path.join(OUTPUT_DIR, "vision-description.txt");
122
+ fs.writeFileSync(outputPath, description);
123
+
124
+ console.log(`✅ Vision description saved to: ${outputPath}`);
125
+ console.log(` Description: ${description.substring(0, 200)}...`);
126
+ console.log(` Estimated cost: $${response.usd_cost?.toFixed(6)}`);
127
+
128
+ expect(description).toBeTruthy();
129
+ expect(description.length).toBeGreaterThan(10);
130
+ }, 60000);
131
+
132
+ // ─── 3. Audio Generation – not supported ─────────────────────────────────────
133
+
134
+ test("3. XAI Audio Generation – not supported (expected error)", async () => {
135
+ if (!process.env.XAI_API_KEY) {
136
+ console.log("Skipping: XAI_API_KEY not set");
137
+ return;
138
+ }
139
+
140
+ /**
141
+ * XAI does not support audio generation. The client throws an error.
142
+ * This test verifies that the error is thrown correctly and documents
143
+ * the limitation.
144
+ */
145
+ await expect(
146
+ client.createAudioGeneration("xai", {
147
+ model: "xai-tts",
148
+ input: "Hello world",
149
+ voice: "default",
150
+ })
151
+ ).rejects.toThrow();
152
+
153
+ console.log(
154
+ "✅ XAI correctly throws an error for unsupported audio generation"
155
+ );
156
+ }, 15000);
157
+
158
+ // ─── 4. Audio Transcription – not supported ───────────────────────────────────
159
+
160
+ test("4. XAI Audio Transcription – not supported (expected error)", async () => {
161
+ if (!process.env.XAI_API_KEY) {
162
+ console.log("Skipping: XAI_API_KEY not set");
163
+ return;
164
+ }
165
+
166
+ /**
167
+ * XAI does not support audio transcription. This test verifies the error.
168
+ */
169
+ const fakeStream = { name: "test.mp3" } as any;
170
+
171
+ await expect(
172
+ client.createAudioTranscription("xai", {
173
+ file: fakeStream,
174
+ model: "whisper",
175
+ })
176
+ ).rejects.toThrow();
177
+
178
+ console.log(
179
+ "✅ XAI correctly throws an error for unsupported audio transcription"
180
+ );
181
+ }, 15000);
182
+
183
+ // ─── 5. Video Generation (grok-imagine-video) ────────────────────────────────
184
+
185
+ test(
186
+ "5. XAI grok-imagine-video – generate video and save URL to disk",
187
+ async () => {
188
+ if (!process.env.XAI_API_KEY) {
189
+ console.log("Skipping: XAI_API_KEY not set");
190
+ return;
191
+ }
192
+
193
+ const outputPath = path.join(OUTPUT_DIR, "video-output.txt");
194
+ const jobIdPath = path.join(OUTPUT_DIR, "video-job-id.txt");
195
+
196
+ // If final output already exists, skip entirely
197
+ if (fs.existsSync(outputPath)) {
198
+ console.log(`⏭️ Skipping: output already exists at ${outputPath}`);
199
+ return;
200
+ }
201
+
202
+ const apiKey = process.env.XAI_API_KEY!;
203
+ const prompt =
204
+ "A cyberpunk cityscape at night with flying cars and neon lights, " +
205
+ "cinematic camera slowly panning upward to reveal the skyline";
206
+
207
+ // Helper: poll a known request ID until done, then save results
208
+ async function pollAndSave(requestId: string) {
209
+ const maxPollingTime = 20 * 60 * 1000; // 20 minutes
210
+ const pollingInterval = 5000; // 5 seconds
211
+ const startTime = Date.now();
212
+
213
+ console.log(`⏳ Polling request ID: ${requestId}`);
214
+
215
+ while (Date.now() - startTime < maxPollingTime) {
216
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval));
217
+
218
+ const pollResponse = await fetch(
219
+ `https://api.x.ai/v1/videos/${requestId}`,
220
+ { headers: { Authorization: `Bearer ${apiKey}` } }
221
+ );
222
+
223
+ if (!pollResponse.ok) {
224
+ const errorText = await pollResponse.text();
225
+ throw new Error(`XAI video polling failed: ${pollResponse.status} ${errorText}`);
226
+ }
227
+
228
+ const pollData = await pollResponse.json();
229
+ console.log(` Status: ${pollData.status || "unknown"}, has video: ${!!pollData.video}`);
230
+
231
+ // XAI returns video data directly (no status:"done") when complete
232
+ if (pollData.video?.url) {
233
+ const videoUrl = pollData.video.url;
234
+ const outputContent = [
235
+ `Generated at: ${new Date().toISOString()}`,
236
+ `Prompt: ${prompt}`,
237
+ `Request ID: ${requestId}`,
238
+ `Video URL: ${videoUrl}`,
239
+ ].join("\n");
240
+ fs.writeFileSync(outputPath, outputContent);
241
+ console.log(`✅ Video URL saved to: ${outputPath}`);
242
+ console.log(` Video URL: ${videoUrl}`);
243
+
244
+ // Clean up job ID file
245
+ if (fs.existsSync(jobIdPath)) fs.unlinkSync(jobIdPath);
246
+ return pollData;
247
+ } else if (pollData.status === "expired") {
248
+ throw new Error("XAI video generation request expired");
249
+ } else if (pollData.status === "failed") {
250
+ throw new Error(`XAI video generation failed: ${JSON.stringify(pollData)}`);
251
+ }
252
+ // pending – keep polling
253
+ }
254
+
255
+ throw new Error("XAI video generation timed out after 20 minutes of polling");
256
+ }
257
+
258
+ // If we already have a request ID from a previous (timed-out) run, resume
259
+ if (fs.existsSync(jobIdPath)) {
260
+ const requestId = fs.readFileSync(jobIdPath, "utf8").trim();
261
+ console.log(`🔄 Resuming poll for existing request ID: ${requestId}`);
262
+ await pollAndSave(requestId);
263
+ expect(fs.existsSync(outputPath)).toBe(true);
264
+ return;
265
+ }
266
+
267
+ // Otherwise start a new job
268
+ console.log("⏳ Submitting XAI video generation job...");
269
+
270
+ const startResponse = await fetch("https://api.x.ai/v1/videos/generations", {
271
+ method: "POST",
272
+ headers: {
273
+ "Content-Type": "application/json",
274
+ Authorization: `Bearer ${apiKey}`,
275
+ },
276
+ body: JSON.stringify({
277
+ model: "grok-imagine-video",
278
+ prompt,
279
+ duration: 5,
280
+ aspect_ratio: "16:9",
281
+ }),
282
+ });
283
+
284
+ if (!startResponse.ok) {
285
+ const errorText = await startResponse.text();
286
+ throw new Error(`XAI video generation start failed: ${startResponse.status} ${errorText}`);
287
+ }
288
+
289
+ const startData = await startResponse.json();
290
+ const requestId = startData.request_id;
291
+
292
+ if (!requestId) {
293
+ throw new Error(`No request_id in response: ${JSON.stringify(startData)}`);
294
+ }
295
+
296
+ // Persist the request ID so subsequent runs can resume if this run times out
297
+ fs.writeFileSync(jobIdPath, requestId);
298
+ console.log(`📝 Request ID saved to: ${jobIdPath} (ID: ${requestId})`);
299
+
300
+ await pollAndSave(requestId);
301
+
302
+ expect(fs.existsSync(outputPath)).toBe(true);
303
+ },
304
+ // 25 minute timeout
305
+ 1500000
306
+ );
307
+ });
@@ -82,7 +82,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
82
82
 
83
83
  mockedReadFile.mockResolvedValue(Buffer.from(fileContentWithTests));
84
84
  mockedConfig.mockResolvedValue({
85
- plugins: [],
85
+ plugins: { enabled: [], disabled: [] },
86
86
  modules: [],
87
87
  promptsDir: ".knowhow/prompts",
88
88
  sources: [],
@@ -153,7 +153,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
153
153
 
154
154
  mockedReadFile.mockResolvedValue(Buffer.from(fileContent));
155
155
  mockedConfig.mockResolvedValue({
156
- plugins: [],
156
+ plugins: { enabled: [], disabled: [] },
157
157
  modules: [],
158
158
  promptsDir: ".knowhow/prompts",
159
159
  sources: [],
@@ -196,7 +196,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
196
196
  // Mock readFile to throw an error
197
197
  mockedReadFile.mockRejectedValue(new Error("File not found"));
198
198
  mockedConfig.mockResolvedValue({
199
- plugins: [],
199
+ plugins: { enabled: [], disabled: [] },
200
200
  modules: [],
201
201
  promptsDir: ".knowhow/prompts",
202
202
  sources: [],
@@ -245,7 +245,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
245
245
 
246
246
  mockedReadFile.mockResolvedValue(Buffer.from(fileContent));
247
247
  mockedConfig.mockResolvedValue({
248
- plugins: [],
248
+ plugins: { enabled: [], disabled: [] },
249
249
  modules: [],
250
250
  promptsDir: ".knowhow/prompts",
251
251
  sources: [],
@@ -298,7 +298,7 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
298
298
 
299
299
  mockedReadFile.mockResolvedValue(Buffer.from(fileContent));
300
300
  mockedConfig.mockResolvedValue({
301
- plugins: [],
301
+ plugins: { enabled: [], disabled: [] },
302
302
  modules: [],
303
303
  promptsDir: ".knowhow/prompts",
304
304
  sources: [],
@@ -57,7 +57,7 @@ describe("LanguagePlugin - Integration Tests", () => {
57
57
 
58
58
  // Mock config functions
59
59
  (getConfig as jest.MockedFunction<typeof getConfig>).mockResolvedValue({
60
- plugins: [],
60
+ plugins: { enabled: [], disabled: [] },
61
61
  } as Config);
62
62
 
63
63
  // Setup language configuration with testing term
@@ -80,7 +80,9 @@ describe("LanguagePlugin", () => {
80
80
  mockPluginService.listPlugins = mockListPlugins;
81
81
  mockPluginService.call = mockCall;
82
82
 
83
- mockedConfig.mockResolvedValue({ plugins: ["github", "asana"] } as Config);
83
+ mockedConfig.mockResolvedValue({
84
+ plugins: { enabled: ["github", "asana"], disabled: [] },
85
+ } as Config);
84
86
  mockedLanguageConfig.mockResolvedValue({
85
87
  test: {
86
88
  events: [],
@@ -162,7 +164,9 @@ describe("LanguagePlugin", () => {
162
164
  });
163
165
 
164
166
  test("should handle file:post-edit event and emit agent:msg when file pattern matches", async () => {
165
- mockedConfig.mockResolvedValue({ plugins: ["github"] } as Config);
167
+ mockedConfig.mockResolvedValue({
168
+ plugins: { enabled: ["github"], disabled: [] },
169
+ } as Config);
166
170
  mockedLanguageConfig.mockResolvedValue({
167
171
  "**/*.ts": {
168
172
  events: ["file:post-edit"],
@@ -241,9 +245,11 @@ describe("LanguagePlugin", () => {
241
245
  });
242
246
 
243
247
  test("should handle multiple matching patterns for a single file", async () => {
244
- mockedConfig.mockResolvedValue({ plugins: [] } as Config);
248
+ mockedConfig.mockResolvedValue({
249
+ plugins: { enabled: [], disabled: [] },
250
+ } as Config);
245
251
  mockedLanguageConfig.mockResolvedValue({
246
- "*.ts": {
252
+ "**/*.ts": {
247
253
  events: ["file:post-edit"],
248
254
  sources: [{ kind: "text", data: ["TypeScript context"] }],
249
255
  },
@@ -308,9 +314,11 @@ describe("LanguagePlugin", () => {
308
314
  });
309
315
 
310
316
  test("should resolve different source types in event context", async () => {
311
- mockedConfig.mockResolvedValue({ plugins: ["github"] } as Config);
317
+ mockedConfig.mockResolvedValue({
318
+ plugins: { enabled: ["github"], disabled: [] },
319
+ } as Config);
312
320
  mockedLanguageConfig.mockResolvedValue({
313
- "*.json": {
321
+ "package.json": {
314
322
  events: ["file:post-edit"],
315
323
  sources: [
316
324
  { kind: "file", data: ["config/settings.json"] },
@@ -360,9 +368,10 @@ describe("LanguagePlugin", () => {
360
368
 
361
369
  mockPluginService.listPlugins = mockListPlugins;
362
370
 
363
- mockedConfig.mockResolvedValue({ plugins: ["github"] } as Config);
371
+ mockedConfig.mockResolvedValue({
372
+ plugins: { enabled: ["github"], disabled: [] },
373
+ } as Config);
364
374
  mockedLanguageConfig.mockResolvedValue({});
365
-
366
375
  const languagePlugin = new LanguagePlugin({
367
376
  Events: mockEventService,
368
377
  Plugins: mockPluginService,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.69",
3
+ "version": "0.0.71",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {