@tyvm/knowhow 0.0.100 → 0.0.101
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/jest.manual.config.js +19 -0
- package/package.json +2 -1
- package/src/agents/base/base.ts +32 -0
- package/src/cli.ts +22 -1
- package/src/clients/contextLimits.ts +0 -2
- package/src/clients/pricing/anthropic.ts +0 -6
- package/src/clients/pricing/google.ts +0 -4
- package/src/clients/pricing/xai.ts +11 -0
- package/src/login.ts +1 -4
- package/src/services/Mcp.ts +17 -3
- package/src/types.ts +17 -17
- package/tests/manual/clients/completions.json +454 -0
- package/tests/manual/clients/completions.test.ts +166 -0
- package/ts_build/package.json +2 -1
- package/ts_build/src/agents/base/base.js +15 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/cli.js +11 -1
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +0 -6
- package/ts_build/src/clients/contextLimits.js +0 -2
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +0 -6
- package/ts_build/src/clients/pricing/anthropic.js +0 -6
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/google.js +0 -4
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +10 -0
- package/ts_build/src/clients/pricing/xai.js +10 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/xai.d.ts +13 -4
- package/ts_build/src/login.js +1 -4
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/services/Mcp.js +11 -3
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/types.d.ts +2 -2
- package/ts_build/src/types.js +12 -13
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/tests/manual/clients/completions.test.d.ts +1 -0
- package/ts_build/tests/manual/clients/completions.test.js +135 -0
- package/ts_build/tests/manual/clients/completions.test.js.map +1 -0
- package/test-ai-completion.ts +0 -39
- package/test-mcp-args.ts +0 -71
- package/test-tools-service.ts +0 -45
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
3
|
+
moduleNameMapper: {
|
|
4
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
5
|
+
},
|
|
6
|
+
transform: {
|
|
7
|
+
'^.+\\.ts?$': [
|
|
8
|
+
'ts-jest',
|
|
9
|
+
{
|
|
10
|
+
useESM: true,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
testEnvironment: 'node',
|
|
15
|
+
testRegex: '/tests/manual/.*\.(test|spec)\.(ts|tsx|js)$',
|
|
16
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
17
|
+
modulePathIgnorePatterns: ["ts_build", "benchmarks"],
|
|
18
|
+
testPathIgnorePatterns: [],
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.101",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "jest --testTimeout 300000",
|
|
11
11
|
"test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest.js --detectOpenHandles --forceExit --testTimeout 300000",
|
|
12
|
+
"build": "npm run compile",
|
|
12
13
|
"compile": "npm run clean && tsc",
|
|
13
14
|
"clean": "rm -rf ts_build",
|
|
14
15
|
"node:build": "bash scripts/build-for-node.sh",
|
package/src/agents/base/base.ts
CHANGED
|
@@ -619,6 +619,17 @@ export abstract class BaseAgent implements IAgent {
|
|
|
619
619
|
tool_choice: "auto",
|
|
620
620
|
});
|
|
621
621
|
|
|
622
|
+
// If the agent was paused while the completion was in-flight, wait here
|
|
623
|
+
// before processing tool calls. This allows the user to send messages
|
|
624
|
+
// (via addPendingUserMessage) and prevents the agent from proceeding to
|
|
625
|
+
// tool calls (e.g. finalAnswer) without seeing those interactions.
|
|
626
|
+
if (this.status === this.eventTypes.pause) {
|
|
627
|
+
this.log(
|
|
628
|
+
"Agent was paused after completion, waiting before processing tool calls"
|
|
629
|
+
);
|
|
630
|
+
await this.unpaused();
|
|
631
|
+
}
|
|
632
|
+
|
|
622
633
|
if (response?.usd_cost === undefined) {
|
|
623
634
|
this.log(
|
|
624
635
|
`Response cost is undefined: ${JSON.stringify(response, null, 2)}`,
|
|
@@ -662,6 +673,13 @@ export abstract class BaseAgent implements IAgent {
|
|
|
662
673
|
this.updateCurrentThread(messages);
|
|
663
674
|
|
|
664
675
|
for (const toolCall of toolCalls) {
|
|
676
|
+
if(this.status === this.eventTypes.pause) {
|
|
677
|
+
this.log(
|
|
678
|
+
"Agent was paused before tool call, waiting before processing tool calls"
|
|
679
|
+
);
|
|
680
|
+
await this.unpaused();
|
|
681
|
+
}
|
|
682
|
+
|
|
665
683
|
const toolMessages = await this.processToolMessages(toolCall);
|
|
666
684
|
// Add the tool responses to the thread
|
|
667
685
|
messages.push(...(toolMessages as Message[]));
|
|
@@ -676,6 +694,18 @@ export abstract class BaseAgent implements IAgent {
|
|
|
676
694
|
);
|
|
677
695
|
|
|
678
696
|
if (finalMessage) {
|
|
697
|
+
// If user added pending messages after finalAnswer was called,
|
|
698
|
+
// continue running to respond to that feedback instead of returning
|
|
699
|
+
if (this.pendingUserMessages.length > 0) {
|
|
700
|
+
this.log(
|
|
701
|
+
"finalAnswer called but pending user messages exist, continuing to respond to feedback"
|
|
702
|
+
);
|
|
703
|
+
messages.push(...this.pendingUserMessages);
|
|
704
|
+
this.pendingUserMessages = [];
|
|
705
|
+
this.updateCurrentThread(messages);
|
|
706
|
+
return this.call(userInput, messages);
|
|
707
|
+
}
|
|
708
|
+
|
|
679
709
|
// Emit task completion event for plugins (like GitPlugin)
|
|
680
710
|
this.events.emit(this.eventTypes.agentTaskComplete, {
|
|
681
711
|
taskId:
|
|
@@ -685,6 +715,8 @@ export abstract class BaseAgent implements IAgent {
|
|
|
685
715
|
result: finalMessage.content || "Done",
|
|
686
716
|
});
|
|
687
717
|
const doneMsg = finalMessage.content || "Done";
|
|
718
|
+
|
|
719
|
+
|
|
688
720
|
this.agentEvents.emit(this.eventTypes.done, doneMsg);
|
|
689
721
|
this.status = this.eventTypes.done;
|
|
690
722
|
return doneMsg;
|
package/src/cli.ts
CHANGED
|
@@ -38,6 +38,20 @@ import { readPromptFile } from "./ai";
|
|
|
38
38
|
import { SetupModule } from "./chat/modules/SetupModule";
|
|
39
39
|
import { CliChatService } from "./chat/CliChatService";
|
|
40
40
|
|
|
41
|
+
// Handle unhandled promise rejections gracefully — particularly from MCP SDK
|
|
42
|
+
// which fires errors via event emitters that can bypass Promise.allSettled.
|
|
43
|
+
// Without this, a single failing MCP server (e.g. expired Notion token) will
|
|
44
|
+
// crash the entire CLI with an unhandled rejection.
|
|
45
|
+
process.on("unhandledRejection", (reason: unknown) => {
|
|
46
|
+
const message =
|
|
47
|
+
reason instanceof Error ? reason.message : String(reason);
|
|
48
|
+
// Only warn — don't exit. The MCP connect errors are recoverable;
|
|
49
|
+
// the server will simply be unavailable but others continue working.
|
|
50
|
+
console.warn(
|
|
51
|
+
`⚠ Unhandled MCP/async error (non-fatal): ${message}`
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
41
55
|
async function setupServices() {
|
|
42
56
|
const { Agents, Mcp, Clients, Tools: OldTools } = services();
|
|
43
57
|
const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
|
|
@@ -74,7 +88,14 @@ async function setupServices() {
|
|
|
74
88
|
Agents.setAgentContext(agentContext);
|
|
75
89
|
|
|
76
90
|
console.log("🔌 Connecting to MCP...");
|
|
77
|
-
|
|
91
|
+
try {
|
|
92
|
+
await Mcp.connectToConfigured(Tools);
|
|
93
|
+
} catch (mcpError) {
|
|
94
|
+
const msg = mcpError instanceof Error ? mcpError.message : String(mcpError);
|
|
95
|
+
console.warn(
|
|
96
|
+
`⚠ Some MCP servers failed to connect (continuing without them): ${msg}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
78
99
|
console.log("Connecting to clients...");
|
|
79
100
|
await Clients.registerConfiguredModels();
|
|
80
101
|
console.log("✓ Services are set up and ready to go!");
|
|
@@ -52,7 +52,6 @@ export const ContextLimits: Record<string, number> = {
|
|
|
52
52
|
[Models.anthropic.Haiku4_5]: 200_000,
|
|
53
53
|
[Models.anthropic.Sonnet3_7]: 200_000,
|
|
54
54
|
[Models.anthropic.Sonnet3_5]: 200_000,
|
|
55
|
-
[Models.anthropic.Haiku3_5]: 200_000,
|
|
56
55
|
[Models.anthropic.Opus3]: 200_000,
|
|
57
56
|
[Models.anthropic.Haiku3]: 200_000,
|
|
58
57
|
|
|
@@ -74,7 +73,6 @@ export const ContextLimits: Record<string, number> = {
|
|
|
74
73
|
[Models.google.Gemini_25_Pro_TTS]: 1_000_000,
|
|
75
74
|
[Models.google.Gemini_20_Flash]: 1_000_000,
|
|
76
75
|
[Models.google.Gemini_20_Flash_Preview_Image_Generation]: 1_000_000,
|
|
77
|
-
[Models.google.Gemini_20_Flash_Lite]: 1_000_000,
|
|
78
76
|
[Models.google.Gemini_20_Flash_Live]: 1_000_000,
|
|
79
77
|
[Models.google.Gemini_20_Flash_TTS]: 1_000_000,
|
|
80
78
|
[Models.google.Gemini_15_Flash]: 1_000_000,
|
|
@@ -65,12 +65,6 @@ export const AnthropicTextPricing = {
|
|
|
65
65
|
cache_hit: 0.3,
|
|
66
66
|
output: 15.0,
|
|
67
67
|
},
|
|
68
|
-
[Models.anthropic.Haiku3_5]: {
|
|
69
|
-
input: 0.8,
|
|
70
|
-
cache_write: 1.0,
|
|
71
|
-
cache_hit: 0.08,
|
|
72
|
-
output: 4.0,
|
|
73
|
-
},
|
|
74
68
|
[Models.anthropic.Opus3]: {
|
|
75
69
|
input: 15.0,
|
|
76
70
|
cache_write: 18.75,
|
|
@@ -174,10 +174,6 @@ export const GeminiPricing: Record<string, GeminiModelPricing> = {
|
|
|
174
174
|
output: 0.4,
|
|
175
175
|
image_generation: 0.039,
|
|
176
176
|
},
|
|
177
|
-
[Models.google.Gemini_20_Flash_Lite]: {
|
|
178
|
-
input: 0.075,
|
|
179
|
-
output: 0.3,
|
|
180
|
-
},
|
|
181
177
|
|
|
182
178
|
// ── Gemini 1.5 (legacy) ───────────────────────────────────────────────────
|
|
183
179
|
[Models.google.Gemini_15_Flash]: {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { Models } from "../../types";
|
|
2
2
|
|
|
3
3
|
export const XaiTextPricing = {
|
|
4
|
+
|
|
5
|
+
[Models.xai.Grok_4_20_Reasoning]: {
|
|
6
|
+
input: 2.0,
|
|
7
|
+
cache_hit: 0.20,
|
|
8
|
+
output: 6.0,
|
|
9
|
+
},
|
|
10
|
+
[Models.xai.Grok_4_20_NonReasoning]: {
|
|
11
|
+
input: 2.0,
|
|
12
|
+
cache_hit: 0.20,
|
|
13
|
+
output: 6.0,
|
|
14
|
+
},
|
|
4
15
|
[Models.xai.Grok4_1_Fast_NonReasoning]: {
|
|
5
16
|
input: 0.2,
|
|
6
17
|
cache_hit: 0.05,
|
package/src/login.ts
CHANGED
|
@@ -49,20 +49,17 @@ export async function login(jwtFlag?: boolean): Promise<void> {
|
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
const config = await getConfig();
|
|
52
|
-
const proxyUrl = KNOWHOW_API_URL + "/api/proxy";
|
|
53
52
|
|
|
54
53
|
if (!config.modelProviders) {
|
|
55
54
|
config.modelProviders = [];
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
const hasProvider = config.modelProviders.find(
|
|
59
|
-
(provider) => provider.provider === "knowhow"
|
|
58
|
+
(provider) => provider.provider === "knowhow"
|
|
60
59
|
);
|
|
61
60
|
if (!hasProvider) {
|
|
62
61
|
config.modelProviders.push({
|
|
63
62
|
provider: "knowhow",
|
|
64
|
-
url: proxyUrl,
|
|
65
|
-
jwtFile: ".knowhow/.jwt",
|
|
66
63
|
});
|
|
67
64
|
|
|
68
65
|
await updateConfig(config);
|
package/src/services/Mcp.ts
CHANGED
|
@@ -136,13 +136,24 @@ export class McpService {
|
|
|
136
136
|
if (shouldAutoConnect && !this.connected[index]) {
|
|
137
137
|
console.log(`Connecting to MCP server: ${config.name}`);
|
|
138
138
|
try {
|
|
139
|
-
|
|
139
|
+
// Wrap connect in a race with a timeout to prevent hanging,
|
|
140
|
+
// and use a wrapping Promise to catch errors that may come
|
|
141
|
+
// from event-emitter callbacks (unhandled rejection pattern in MCP SDK)
|
|
142
|
+
await new Promise<void>(async (resolve, reject) => {
|
|
143
|
+
try {
|
|
144
|
+
await client.connect(this.transports[index]);
|
|
145
|
+
resolve();
|
|
146
|
+
} catch (err) {
|
|
147
|
+
reject(err);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
140
150
|
} catch (error) {
|
|
141
151
|
console.error(
|
|
142
152
|
`Failed to connect to MCP server '${config.name}':`,
|
|
143
153
|
error.message || error
|
|
144
154
|
);
|
|
145
|
-
|
|
155
|
+
// Don't re-throw — just log and continue so other servers can still connect
|
|
156
|
+
return;
|
|
146
157
|
}
|
|
147
158
|
this.connected[index] = true;
|
|
148
159
|
} else if (!shouldAutoConnect) {
|
|
@@ -155,7 +166,10 @@ export class McpService {
|
|
|
155
166
|
|
|
156
167
|
// Log summary of auto-connection results
|
|
157
168
|
const successful = results.filter((r) => r.status === "fulfilled").length;
|
|
158
|
-
|
|
169
|
+
// Since we no longer re-throw, count servers that are NOT connected after the attempt
|
|
170
|
+
const failed = this.clients.filter(
|
|
171
|
+
(_, i) => this.config[i]?.autoConnect !== false && !this.connected[i]
|
|
172
|
+
).length;
|
|
159
173
|
if (failed > 0) {
|
|
160
174
|
console.warn(
|
|
161
175
|
`Auto-connected ${successful}/${this.clients.length} MCP servers (${failed} failed)`
|
package/src/types.ts
CHANGED
|
@@ -89,9 +89,9 @@ export type Config = {
|
|
|
89
89
|
auth?: {
|
|
90
90
|
required?: boolean;
|
|
91
91
|
passkey?: {
|
|
92
|
-
publicKey?: string;
|
|
93
|
-
credentialId?: string;
|
|
94
|
-
algorithm?: string;
|
|
92
|
+
publicKey?: string; // base64-encoded public key
|
|
93
|
+
credentialId?: string; // base64-encoded credential ID
|
|
94
|
+
algorithm?: string; // e.g. "ES256"
|
|
95
95
|
};
|
|
96
96
|
sessionDurationHours?: number;
|
|
97
97
|
};
|
|
@@ -187,19 +187,20 @@ export const Models = {
|
|
|
187
187
|
anthropic: {
|
|
188
188
|
Opus4_6: "claude-opus-4-6",
|
|
189
189
|
Sonnet4_6: "claude-sonnet-4-6",
|
|
190
|
-
Opus4_5: "claude-opus-4-5
|
|
191
|
-
Opus4: "claude-opus-4
|
|
192
|
-
Opus4_1: "claude-opus-4-1
|
|
193
|
-
Sonnet4_5: "claude-sonnet-4-5
|
|
194
|
-
Haiku4_5: "claude-haiku-4-5
|
|
195
|
-
Sonnet4: "claude-sonnet-4
|
|
196
|
-
Sonnet3_7: "claude-3-7-sonnet
|
|
197
|
-
Sonnet3_5: "claude-3-5-sonnet
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Haiku3: "claude-3-haiku-20240307",
|
|
190
|
+
Opus4_5: "claude-opus-4-5",
|
|
191
|
+
Opus4: "claude-opus-4",
|
|
192
|
+
Opus4_1: "claude-opus-4-1",
|
|
193
|
+
Sonnet4_5: "claude-sonnet-4-5",
|
|
194
|
+
Haiku4_5: "claude-haiku-4-5",
|
|
195
|
+
Sonnet4: "claude-sonnet-4",
|
|
196
|
+
Sonnet3_7: "claude-3-7-sonnet",
|
|
197
|
+
Sonnet3_5: "claude-3-5-sonnet",
|
|
198
|
+
Opus3: "claude-3-opus",
|
|
199
|
+
Haiku3: "claude-3-haiku",
|
|
201
200
|
},
|
|
202
201
|
xai: {
|
|
202
|
+
Grok_4_20_Reasoning: "grok-4.20-0309-reasoning",
|
|
203
|
+
Grok_4_20_NonReasoning: "grok-4.20-0309-non-reasoning",
|
|
203
204
|
Grok4_1_Fast_Reasoning: "grok-4-1-fast-reasoning",
|
|
204
205
|
Grok4_1_Fast_NonReasoning: "grok-4-1-fast-non-reasoning",
|
|
205
206
|
GrokCodeFast: "grok-code-fast-1",
|
|
@@ -277,13 +278,13 @@ export const Models = {
|
|
|
277
278
|
Gemini_25_Pro_Preview: "gemini-2.5-pro-preview-05-06",
|
|
278
279
|
Gemini_25_Flash_Image: "gemini-2.5-flash-image",
|
|
279
280
|
Gemini_25_Flash_Live: "gemini-2.5-flash-live-preview",
|
|
280
|
-
Gemini_25_Flash_Native_Audio:
|
|
281
|
+
Gemini_25_Flash_Native_Audio:
|
|
282
|
+
"gemini-2.5-flash-native-audio-preview-12-2025",
|
|
281
283
|
Gemini_25_Pro_TTS: "gemini-2.5-pro-preview-tts",
|
|
282
284
|
// Gemini 2.0 (deprecated)
|
|
283
285
|
Gemini_20_Flash: "gemini-2.0-flash",
|
|
284
286
|
Gemini_20_Flash_Preview_Image_Generation:
|
|
285
287
|
"gemini-2.0-flash-exp-image-generation",
|
|
286
|
-
Gemini_20_Flash_Lite: "gemini-2.0-flash-lite",
|
|
287
288
|
// Gemini 1.5 (legacy)
|
|
288
289
|
Gemini_15_Flash: "gemini-1.5-flash",
|
|
289
290
|
Gemini_15_Flash_8B: "gemini-1.5-flash-8b",
|
|
@@ -380,7 +381,6 @@ export const GoogleReasoningModels = [
|
|
|
380
381
|
Models.google.Gemini_25_Flash_Preview,
|
|
381
382
|
Models.google.Gemini_25_Pro_Preview,
|
|
382
383
|
Models.google.Gemini_20_Flash,
|
|
383
|
-
Models.google.Gemini_20_Flash_Lite,
|
|
384
384
|
Models.google.Gemini_15_Flash,
|
|
385
385
|
Models.google.Gemini_15_Flash_8B,
|
|
386
386
|
Models.google.Gemini_15_Pro,
|