@tyvm/knowhow 0.0.97 → 0.0.99

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 (84) hide show
  1. package/package.json +1 -1
  2. package/src/agents/base/base.ts +12 -4
  3. package/src/agents/patcher/patcher.ts +1 -1
  4. package/src/agents/tools/list.ts +1 -0
  5. package/src/chat/modules/AgentModule.ts +24 -22
  6. package/src/chat/modules/SessionsModule.ts +1 -3
  7. package/src/chat/modules/SystemModule.ts +23 -8
  8. package/src/cli.ts +17 -0
  9. package/src/clients/anthropic.ts +10 -4
  10. package/src/clients/gemini.ts +23 -5
  11. package/src/clients/http.ts +5 -3
  12. package/src/clients/index.ts +261 -139
  13. package/src/clients/knowhow.ts +15 -4
  14. package/src/clients/openai.ts +213 -10
  15. package/src/clients/types.ts +7 -1
  16. package/src/clients/xai.ts +13 -6
  17. package/src/cloudWorker.ts +314 -0
  18. package/src/config.ts +9 -1
  19. package/src/processors/TokenCompressor.ts +8 -1
  20. package/src/services/KnowhowClient.ts +56 -12
  21. package/src/services/LazyToolsService.ts +15 -14
  22. package/src/services/Tools.ts +10 -1
  23. package/src/types.ts +11 -2
  24. package/src/utils/InputQueueManager.ts +131 -20
  25. package/test-ai-completion.ts +39 -0
  26. package/test-mcp-args.ts +71 -0
  27. package/test-tools-service.ts +45 -0
  28. package/ts_build/package.json +1 -1
  29. package/ts_build/src/agents/base/base.js +8 -2
  30. package/ts_build/src/agents/base/base.js.map +1 -1
  31. package/ts_build/src/agents/patcher/patcher.js +1 -1
  32. package/ts_build/src/agents/patcher/patcher.js.map +1 -1
  33. package/ts_build/src/agents/tools/list.js +1 -0
  34. package/ts_build/src/agents/tools/list.js.map +1 -1
  35. package/ts_build/src/chat/modules/AgentModule.js +17 -19
  36. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  37. package/ts_build/src/chat/modules/SessionsModule.js +1 -3
  38. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  39. package/ts_build/src/chat/modules/SystemModule.js +16 -8
  40. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  41. package/ts_build/src/cli.js +17 -0
  42. package/ts_build/src/cli.js.map +1 -1
  43. package/ts_build/src/clients/anthropic.d.ts +9 -7
  44. package/ts_build/src/clients/anthropic.js +9 -4
  45. package/ts_build/src/clients/anthropic.js.map +1 -1
  46. package/ts_build/src/clients/gemini.d.ts +2 -1
  47. package/ts_build/src/clients/gemini.js +13 -4
  48. package/ts_build/src/clients/gemini.js.map +1 -1
  49. package/ts_build/src/clients/http.d.ts +1 -1
  50. package/ts_build/src/clients/http.js +2 -2
  51. package/ts_build/src/clients/http.js.map +1 -1
  52. package/ts_build/src/clients/index.d.ts +23 -47
  53. package/ts_build/src/clients/index.js +152 -99
  54. package/ts_build/src/clients/index.js.map +1 -1
  55. package/ts_build/src/clients/knowhow.d.ts +3 -2
  56. package/ts_build/src/clients/knowhow.js +6 -3
  57. package/ts_build/src/clients/knowhow.js.map +1 -1
  58. package/ts_build/src/clients/openai.d.ts +20 -18
  59. package/ts_build/src/clients/openai.js +166 -8
  60. package/ts_build/src/clients/openai.js.map +1 -1
  61. package/ts_build/src/clients/types.d.ts +3 -1
  62. package/ts_build/src/clients/xai.d.ts +3 -2
  63. package/ts_build/src/clients/xai.js +10 -4
  64. package/ts_build/src/clients/xai.js.map +1 -1
  65. package/ts_build/src/cloudWorker.d.ts +8 -0
  66. package/ts_build/src/cloudWorker.js +239 -0
  67. package/ts_build/src/cloudWorker.js.map +1 -0
  68. package/ts_build/src/config.js +8 -1
  69. package/ts_build/src/config.js.map +1 -1
  70. package/ts_build/src/processors/TokenCompressor.js +7 -1
  71. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  72. package/ts_build/src/services/KnowhowClient.d.ts +24 -1
  73. package/ts_build/src/services/KnowhowClient.js +14 -2
  74. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  75. package/ts_build/src/services/LazyToolsService.js +5 -7
  76. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  77. package/ts_build/src/services/Tools.js +9 -1
  78. package/ts_build/src/services/Tools.js.map +1 -1
  79. package/ts_build/src/types.d.ts +3 -1
  80. package/ts_build/src/types.js +8 -1
  81. package/ts_build/src/types.js.map +1 -1
  82. package/ts_build/src/utils/InputQueueManager.d.ts +2 -0
  83. package/ts_build/src/utils/InputQueueManager.js +76 -10
  84. package/ts_build/src/utils/InputQueueManager.js.map +1 -1
@@ -144,7 +144,6 @@ export class KnowhowSimpleClient {
144
144
  const currentOrg = orgs.find((org) => {
145
145
  return org.organizationId === orgId;
146
146
  });
147
-
148
147
  } catch (error) {
149
148
  throw new Error("Invalid JWT. Please login again.");
150
149
  }
@@ -203,13 +202,9 @@ export class KnowhowSimpleClient {
203
202
  }
204
203
  ) {
205
204
  await this.checkJwt();
206
- return http.put(
207
- `${this.baseUrl}/api/org-embeddings/${id}`,
208
- data,
209
- {
210
- headers: this.headers,
211
- }
212
- );
205
+ return http.put(`${this.baseUrl}/api/org-embeddings/${id}`, data, {
206
+ headers: this.headers,
207
+ });
213
208
  }
214
209
 
215
210
  async createChatCompletion(options: CompletionOptions) {
@@ -234,9 +229,9 @@ export class KnowhowSimpleClient {
234
229
  );
235
230
  }
236
231
 
237
- async getModels() {
232
+ async getModels(type = "all") {
238
233
  await this.checkJwt();
239
- return http.get(`${this.baseUrl}/api/proxy/v1/models?type=all`, {
234
+ return http.get(`${this.baseUrl}/api/proxy/v1/models?type=${type}`, {
240
235
  headers: this.headers,
241
236
  });
242
237
  }
@@ -609,7 +604,11 @@ export class KnowhowSimpleClient {
609
604
  throw new Error(`File not found: ${filePath}`);
610
605
  }
611
606
 
612
- await http.post(`${this.baseUrl}/api/org-files/upload/${file.id}/complete`, {}, { headers: this.headers });
607
+ await http.post(
608
+ `${this.baseUrl}/api/org-files/upload/${file.id}/complete`,
609
+ {},
610
+ { headers: this.headers }
611
+ );
613
612
  }
614
613
 
615
614
  /**
@@ -624,7 +623,8 @@ export class KnowhowSimpleClient {
624
623
 
625
624
  // Extract just the filename from the path
626
625
  const lastSlash = filePath.lastIndexOf("/");
627
- const fileName = lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
626
+ const fileName =
627
+ lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
628
628
 
629
629
  // Get upload URL using the file ID
630
630
  const response = await http.post<{ uploadUrl: string }>(
@@ -648,4 +648,48 @@ export class KnowhowSimpleClient {
648
648
  );
649
649
  return response.data;
650
650
  }
651
+
652
+ // ============================================
653
+ // Cloud Worker Methods
654
+ // ============================================
655
+
656
+ /**
657
+ * List all cloud workers for the current user's org
658
+ */
659
+ async listCloudWorkers() {
660
+ await this.checkJwt();
661
+ return http.get<
662
+ { id: string; name: string; status: string; workerConfigJson?: Record<string, unknown> }[]
663
+ >(`${this.baseUrl}/api/cloud-workers`, { headers: this.headers });
664
+ }
665
+
666
+ /**
667
+ * Create a new cloud worker
668
+ */
669
+ async createCloudWorker(data: {
670
+ name: string;
671
+ workerConfigJson?: Record<string, unknown>;
672
+ }) {
673
+ await this.checkJwt();
674
+ return http.post<{ id: string; name: string; status: string; workerConfigJson?: Record<string, unknown> }>(
675
+ `${this.baseUrl}/api/cloud-workers`,
676
+ data,
677
+ { headers: this.headers }
678
+ );
679
+ }
680
+
681
+ /**
682
+ * Update an existing cloud worker
683
+ */
684
+ async updateCloudWorker(
685
+ id: string,
686
+ data: { workerConfigJson?: Record<string, unknown> }
687
+ ) {
688
+ await this.checkJwt();
689
+ return http.put<{ id: string; name: string; status: string; workerConfigJson?: Record<string, unknown> }>(
690
+ `${this.baseUrl}/api/cloud-workers/${id}`,
691
+ data,
692
+ { headers: this.headers }
693
+ );
694
+ }
651
695
  }
@@ -125,23 +125,24 @@ export class LazyToolsService extends ToolsService {
125
125
  async callTool(toolCall: ToolCall, enabledTools?: string[]) {
126
126
  const functionName = toolCall.function.name;
127
127
 
128
- // If no explicit enabledTools list was provided and the tool isn't currently
129
- // visible, check if it exists in allTools and auto-enable it
130
- if (!enabledTools) {
131
- const isCurrentlyEnabled = this.tools.some(
132
- (t) => t.function.name === functionName
133
- );
134
- const existsInAll = this.allTools.some(
135
- (t) => t.function.name === functionName
136
- );
128
+ // If the tool isn't currently visible but exists in allTools, auto-enable it.
129
+ // This handles the case where the agent explicitly calls a tool without first
130
+ // enabling it via listAvailableTools/enableTools.
131
+ const isCurrentlyEnabled = this.tools.some(
132
+ (t) => t.function.name === functionName
133
+ );
134
+ const existsInAll = this.allTools.some(
135
+ (t) => t.function.name === functionName
136
+ );
137
137
 
138
- if (!isCurrentlyEnabled && existsInAll) {
139
- // Auto-enable by adding the tool name as an exact pattern
140
- this.enableTools([functionName]);
141
- }
138
+ if (!isCurrentlyEnabled && existsInAll) {
139
+ // Auto-enable by adding the tool name as an exact pattern
140
+ this.enableTools([functionName]);
142
141
  }
143
142
 
144
- return super.callTool(toolCall, enabledTools ?? this.getToolNames());
143
+ // Always use the current enabled tool names after any auto-enable above,
144
+ // so the base class check sees the freshly-enabled tool in the allowed list.
145
+ return super.callTool(toolCall, this.getToolNames());
145
146
  }
146
147
 
147
148
  // Internal: Update visible tools based on patterns
@@ -73,7 +73,12 @@ export class ToolsService {
73
73
  this.addTools(tools);
74
74
 
75
75
  for (const name of toolNames) {
76
- this.setFunction(name, toolsService.getFunction(name));
76
+ const fn = toolsService.getFunction(name);
77
+ if (!fn) {
78
+ continue;
79
+ }
80
+
81
+ this.setFunction(name, fn);
77
82
  }
78
83
  }
79
84
 
@@ -118,6 +123,10 @@ export class ToolsService {
118
123
 
119
124
  setFunctions(names: string[], funcs: ((...args: any) => any)[]) {
120
125
  for (let i = 0; i < names.length; i++) {
126
+ if (!names[i] || typeof funcs[i] !== "function") {
127
+ console.warn("Ignoring invalid function entry at index", i, "with name", names[i]);
128
+ continue;
129
+ }
121
130
  this.setFunction(names[i], funcs[i]);
122
131
  }
123
132
  }
package/src/types.ts CHANGED
@@ -132,8 +132,9 @@ export type McpConfig = {
132
132
  };
133
133
 
134
134
  export type ModelProvider = {
135
- url: string;
135
+ url?: string;
136
136
  provider: string;
137
+ envKey?: string;
137
138
  headers?: { [key: string]: string };
138
139
  jwtFile?: string;
139
140
  };
@@ -359,7 +360,15 @@ export const OpenAiEmbeddingModels = [
359
360
  EmbeddingModels.openai.EmbeddingLarge3,
360
361
  EmbeddingModels.openai.EmbeddingSmall3,
361
362
  ];
362
- // export const OpenAiResponseOnlyModels = [Models.openai.Codex_Mini];
363
+
364
+ // Models that ONLY support the Responses API (not Chat Completions)
365
+ export const OpenAiResponsesOnlyModels = [
366
+ Models.openai.GPT_53_Codex,
367
+ Models.openai.GPT_54,
368
+ Models.openai.GPT_54_Mini,
369
+ Models.openai.GPT_54_Nano,
370
+ Models.openai.GPT_54_Pro,
371
+ ];
363
372
 
364
373
  export const GoogleReasoningModels = [
365
374
  Models.google.Gemini_31_Pro_Preview,
@@ -16,6 +16,9 @@ export class InputQueueManager {
16
16
  private static rl: readline.Interface | null = null;
17
17
  private static keypressListenerSetup = false;
18
18
 
19
+ // Tab completion state
20
+ private lastKeypressWasTab = false;
21
+
19
22
  // We keep one "live" buffer shared across stacked questions
20
23
  // (so typing is preserved when questions change)
21
24
  private currentLine = "";
@@ -57,27 +60,11 @@ export class InputQueueManager {
57
60
  historySize: 0,
58
61
 
59
62
  /**
60
- * Use readline's built-in completion system so Tab does NOT insert a literal tab.
63
+ * Disable readline's built-in completion display - we handle Tab ourselves
64
+ * in the keypress listener to avoid readline's kRefreshLine overwriting
65
+ * small completion lists (lists that fit in 1-2 lines get erased).
61
66
  */
62
- completer: (line: string) => {
63
- const current = this.peek();
64
- const opts = current?.options ?? [];
65
- if (opts.length === 0) return [[], line];
66
-
67
- // Identify the "word" at the end of the line that we want to complete
68
- // (default readline behavior is word-based completion)
69
- const lastSpace = Math.max(
70
- line.lastIndexOf(" "),
71
- line.lastIndexOf("\t")
72
- );
73
- const word = line.slice(lastSpace + 1); // the token to complete
74
-
75
- const hits = opts.filter((c) => c.startsWith(word));
76
-
77
- // Return [matches, wordToReplace]
78
- // Readline will replace `word` with the selected match (or extend if unique)
79
- return [hits, word];
80
- },
67
+ completer: (line: string) => [[], line],
81
68
  });
82
69
 
83
70
  // When user presses Enter, buffer the line for paste detection
@@ -177,6 +164,16 @@ export class InputQueueManager {
177
164
  instance.savedLineBeforeHistory = "";
178
165
  }
179
166
 
167
+ // Handle Tab completion ourselves to avoid readline's kRefreshLine
168
+ // overwriting small completion lists (1-2 line lists get erased by readline).
169
+ if (key?.name === "tab") {
170
+ instance.handleTabCompletion();
171
+ return;
172
+ }
173
+
174
+ // Any non-tab keypress resets the "last was tab" state
175
+ instance.lastKeypressWasTab = false;
176
+
180
177
  // Custom Up/Down history navigation using only passed-in history
181
178
  if (key?.name === "up") {
182
179
  const history = instance.getHistory();
@@ -311,6 +308,120 @@ export class InputQueueManager {
311
308
  return out;
312
309
  }
313
310
 
311
+ /**
312
+ * Handle Tab key completion manually.
313
+ *
314
+ * We do this ourselves instead of using readline's built-in completer because
315
+ * readline's kRefreshLine (called after displaying completions) uses
316
+ * clearScreenDown which erases small completion lists (1-2 lines) that fit
317
+ * within the terminal's current scroll region. Large lists scroll past and
318
+ * survive, but small lists get wiped out before the user can read them.
319
+ *
320
+ * Behavior:
321
+ * - First Tab: extend line to longest common prefix of matches (if possible)
322
+ * - Second consecutive Tab (no extension possible): print the matches list
323
+ */
324
+ private handleTabCompletion(): void {
325
+ const current = this.peek();
326
+ const opts = current?.options ?? [];
327
+
328
+ // Sync current line from readline before we do anything
329
+ this.syncFromReadline();
330
+ const line = this.currentLine;
331
+
332
+ if (opts.length === 0) {
333
+ this.lastKeypressWasTab = false;
334
+ return;
335
+ }
336
+
337
+ // Find the "word" to complete (everything after last space/tab)
338
+ const lastSpace = Math.max(line.lastIndexOf(" "), line.lastIndexOf("\t"));
339
+ const word = line.slice(lastSpace + 1);
340
+
341
+ const hits = opts.filter((c) => c.startsWith(word));
342
+
343
+ let effectiveHits: string[];
344
+ let completionWord: string; // the replacement for `word` in the line
345
+
346
+ if (hits.length > 0) {
347
+ // Exact prefix matches: use normal longest-common-prefix logic
348
+ effectiveHits = hits;
349
+ completionWord = this.longestCommonPrefix(effectiveHits);
350
+ } else {
351
+ // Contains matches: find options that contain the typed word somewhere
352
+ const containsHits = opts.filter((c) => c.includes(word));
353
+ effectiveHits = containsHits;
354
+
355
+ if (containsHits.length > 0) {
356
+ // If there's only one match, complete to the full option string
357
+ if (containsHits.length === 1) {
358
+ completionWord = containsHits[0];
359
+ } else {
360
+ // For each match, find the position of `word` inside it, then take
361
+ // the substring from that position and find the longest common prefix
362
+ // of all those suffixes. This gives us the longest unambiguous extension
363
+ // starting from the user's typed word.
364
+ const suffixes = containsHits.map((c) => {
365
+ const idx = c.indexOf(word);
366
+ return c.slice(idx); // e.g. "opus-4-5" from "anthropic/claude-opus-4-5"
367
+ });
368
+ completionWord = this.longestCommonPrefix(suffixes);
369
+ }
370
+ } else {
371
+ completionWord = word;
372
+ }
373
+ }
374
+
375
+ if (effectiveHits.length === 0) {
376
+ this.lastKeypressWasTab = false;
377
+ return;
378
+ }
379
+
380
+ if (completionWord.length > word.length) {
381
+ // Can extend - replace word with the longer completion
382
+ const before = line.slice(0, lastSpace + 1);
383
+ const newLine = before + completionWord;
384
+ this.replaceLine(newLine);
385
+ this.currentLine = newLine;
386
+ this.lastKeypressWasTab = false;
387
+ return;
388
+ }
389
+
390
+ // No extension possible - show list on second consecutive Tab
391
+ if (!this.lastKeypressWasTab) {
392
+ // First tab with no extension: just set flag, user needs to tab again
393
+ this.lastKeypressWasTab = true;
394
+ return;
395
+ }
396
+
397
+ // Second consecutive tab: print the completions list
398
+ this.lastKeypressWasTab = false;
399
+ const columns = process.stdout.columns || 80;
400
+ const maxWidth = Math.max(...effectiveHits.map((h) => h.length)) + 2;
401
+ const numCols = Math.max(1, Math.floor(columns / maxWidth));
402
+ const rows: string[] = [];
403
+ for (let i = 0; i < effectiveHits.length; i += numCols) {
404
+ const row = effectiveHits.slice(i, i + numCols);
405
+ rows.push(row.map((h) => h.padEnd(maxWidth)).join(""));
406
+ }
407
+ // Write completions. We intentionally do NOT call rl.prompt() here because
408
+ // readline tracks the cursor row internally. When prompt(true) is called,
409
+ // readline moves the cursor back UP to where it thinks the prompt is and
410
+ // redraws — overwriting any completion output that fit within the same scroll
411
+ // region (i.e., small lists that didn't cause the terminal to scroll).
412
+ //
413
+ // Instead, we write the completions and then a fresh prompt line manually,
414
+ // without involving readline's cursor tracking at all.
415
+ const promptText = current!.question;
416
+ const inputText = this.currentLine;
417
+ process.stdout.write("\r\n" + rows.join("\r\n") + "\r\n" + promptText + inputText);
418
+ // Now sync readline's internal state to match what we drew manually.
419
+ // We replace the line content via ctrl+u then re-type it so readline's
420
+ // internal `line` buffer and cursor position are correct.
421
+ InputQueueManager.rl?.write(null, { ctrl: true, name: "u" });
422
+ if (inputText) InputQueueManager.rl?.write(inputText);
423
+ }
424
+
314
425
  private render(): void {
315
426
  if (!InputQueueManager.rl) return;
316
427
  const current = this.peek();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Direct test of createAiCompletion outside the script sandbox
3
+ */
4
+ import { createAiCompletion } from './src/agents/tools/aiClient';
5
+ import { services } from './src/services';
6
+
7
+ async function main() {
8
+ // Initialize services
9
+ const svc = services();
10
+
11
+ // Wait for clients to initialize
12
+ await new Promise(resolve => setTimeout(resolve, 2000));
13
+
14
+ console.log('Testing createAiCompletion directly...');
15
+
16
+ // Test with anthropic (empty provider - auto-detect from model)
17
+ try {
18
+ const result = await createAiCompletion.call(svc.Tools, 'anthropic', {
19
+ model: 'claude-3-5-haiku-20241022',
20
+ messages: [{ role: 'user', content: 'Say hello in 5 words' }]
21
+ });
22
+ console.log('Direct call result:', result?.choices?.[0]?.message?.content);
23
+ } catch(e) {
24
+ console.error('Direct call error:', e.message);
25
+ }
26
+
27
+ // Test with empty provider
28
+ try {
29
+ const result2 = await createAiCompletion.call(svc.Tools, '', {
30
+ model: 'claude-3-5-haiku-20241022',
31
+ messages: [{ role: 'user', content: 'Say hello in 5 words' }]
32
+ });
33
+ console.log('Empty provider result:', result2?.choices?.[0]?.message?.content);
34
+ } catch(e) {
35
+ console.error('Empty provider error:', e.message);
36
+ }
37
+ }
38
+
39
+ main().catch(console.error).finally(() => process.exit(0));
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Test how McpServer's toZodSchema + Object.values parses createAiCompletion args
3
+ */
4
+ import { McpServerService } from './src/services/McpServer';
5
+ import { ToolsService } from './src/services/Tools';
6
+ import { services } from './src/services';
7
+
8
+ // Simulate the properties from createAiCompletion tool definition
9
+ const createAiCompletionProps = {
10
+ provider: {
11
+ type: "string",
12
+ description: "The AI provider to use",
13
+ },
14
+ options: {
15
+ type: "object",
16
+ description: "Provider-specific completion options",
17
+ properties: {
18
+ model: { type: "string", description: "The model to use" },
19
+ messages: {
20
+ type: "array",
21
+ description: "The chat history",
22
+ items: {
23
+ type: "object",
24
+ properties: {
25
+ role: { type: "string" },
26
+ content: { type: "string" },
27
+ },
28
+ },
29
+ },
30
+ max_tokens: { type: "number" },
31
+ },
32
+ required: ["model", "messages"],
33
+ },
34
+ };
35
+
36
+ const outerRequired = ["provider"];
37
+
38
+ async function main() {
39
+ const svc = services();
40
+ const mcpServer = new McpServerService(svc.Tools);
41
+
42
+ // Simulate toZodSchema
43
+ const zodSchema = mcpServer.toZodSchema(createAiCompletionProps as any, outerRequired);
44
+
45
+ // Simulate what MCP SDK passes as args after zod parsing
46
+ const incomingArgs = {
47
+ provider: "",
48
+ options: {
49
+ model: "gpt-5-nano",
50
+ messages: [{ role: "user", content: "hello" }],
51
+ max_tokens: 5000
52
+ }
53
+ };
54
+
55
+ try {
56
+ const parsed = zodSchema.parse(incomingArgs);
57
+ console.log('Parsed args:', JSON.stringify(parsed, null, 2));
58
+ console.log('Object.values(parsed):', JSON.stringify(Object.values(parsed), null, 2));
59
+ console.log('Keys order:', Object.keys(parsed));
60
+
61
+ // Simulate what McpServer does
62
+ const [providerArg, optionsArg] = Object.values(parsed) as any[];
63
+ console.log('provider arg:', JSON.stringify(providerArg));
64
+ console.log('options arg:', JSON.stringify(optionsArg));
65
+ console.log('options.model:', optionsArg?.model);
66
+ } catch(e) {
67
+ console.error('Zod parse error:', e.message);
68
+ }
69
+ }
70
+
71
+ main().catch(console.error).finally(() => process.exit(0));
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Test ToolsService.callTool for createAiCompletion directly
3
+ */
4
+ import { services } from './src/services';
5
+
6
+ async function main() {
7
+ const svc = services();
8
+
9
+ // Wait for services to init
10
+ await new Promise(resolve => setTimeout(resolve, 2000));
11
+
12
+ const toolsService = svc.Tools;
13
+
14
+ // Check tool definition
15
+ const toolDef = toolsService.getTool('createAiCompletion');
16
+ console.log('Tool found:', !!toolDef);
17
+ console.log('positional:', toolDef?.function?.parameters?.positional);
18
+ console.log('properties keys:', Object.keys(toolDef?.function?.parameters?.properties || {}));
19
+
20
+ // Simulate what SandboxContext.callTool does
21
+ const toolCall = {
22
+ id: 'test-123',
23
+ type: 'function' as const,
24
+ function: {
25
+ name: 'createAiCompletion',
26
+ arguments: JSON.stringify({
27
+ provider: '',
28
+ options: {
29
+ model: 'claude-3-haiku-20240307',
30
+ messages: [{ role: 'user', content: 'Say hello in 5 words' }]
31
+ }
32
+ })
33
+ }
34
+ };
35
+
36
+ try {
37
+ const result = await toolsService.callTool(toolCall);
38
+ console.log('Result functionResp:', JSON.stringify(result?.functionResp)?.substring(0, 200));
39
+ console.log('Tool messages content:', result?.toolMessages?.[0]?.content?.substring(0, 200));
40
+ } catch(e) {
41
+ console.error('Error:', e.message);
42
+ }
43
+ }
44
+
45
+ main().catch(console.error).finally(() => process.exit(0));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.97",
3
+ "version": "0.0.99",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -166,11 +166,17 @@ class BaseAgent {
166
166
  if (!this.client) {
167
167
  if (this.provider) {
168
168
  this.log(`Getting client for provider ${this.provider}`);
169
- this.client = this.clientService.getClient(this.provider)?.client;
169
+ const clientInfo = this.clientService.getClient(this.getProvider());
170
+ if (clientInfo) {
171
+ this.client = clientInfo.client;
172
+ }
170
173
  }
171
174
  if (!this.client) {
172
175
  this.log(`Getting client for model ${this.modelName}`);
173
- this.client = this.clientService.getClient(undefined, this.modelName)?.client;
176
+ const clientInfo = this.clientService.getClient(undefined, this.getModel());
177
+ if (clientInfo) {
178
+ this.client = clientInfo.client;
179
+ }
174
180
  }
175
181
  }
176
182
  return this.client;