@tyvm/knowhow 0.0.99 → 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 +3 -1
- package/scripts/build-for-node.sh +162 -0
- package/src/agents/base/base.ts +32 -0
- package/src/cli.ts +23 -2
- package/src/clients/contextLimits.ts +0 -2
- package/src/clients/index.ts +19 -6
- 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 +3 -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 +12 -2
- 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/index.js +16 -6
- package/ts_build/src/clients/index.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,8 +9,10 @@
|
|
|
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",
|
|
15
|
+
"node:build": "bash scripts/build-for-node.sh",
|
|
14
16
|
"start": "npm run compile && node --no-node-snapshot ts_build/src/server/index.js",
|
|
15
17
|
"dataset:diffs:generate": "ts-node src/dataset/diffs/generate.ts",
|
|
16
18
|
"dataset:diffs:jsonl": "ts-node src/dataset/diffs/jsonl.ts",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage: bash scripts/build-for-node.sh [node-version]
|
|
3
|
+
# Example: bash scripts/build-for-node.sh 24 # any Node 24.x
|
|
4
|
+
# Example: bash scripts/build-for-node.sh 20.19.0 # exact version
|
|
5
|
+
# Example: npm run node:build 24
|
|
6
|
+
#
|
|
7
|
+
# This script:
|
|
8
|
+
# 1. Compiles TypeScript with Node 20 (required for workspace deps)
|
|
9
|
+
# 2. Creates /tmp/knowhow-node-<major> with the compiled output
|
|
10
|
+
# 3. Installs the correct isolated-vm version for the target node in that dir
|
|
11
|
+
# 4. Symlinks the package globally for ALL installed nvm versions matching the target
|
|
12
|
+
#
|
|
13
|
+
# This approach avoids polluting the workspace node_modules with a different
|
|
14
|
+
# isolated-vm ABI, so Node 20 and Node 24 builds can coexist.
|
|
15
|
+
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
TARGET_VERSION="${1:-}"
|
|
19
|
+
|
|
20
|
+
if [ -z "$TARGET_VERSION" ]; then
|
|
21
|
+
echo "Usage: $0 <node-version>"
|
|
22
|
+
echo "Example: $0 24"
|
|
23
|
+
echo "Example: $0 20.19.0"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Extract the major version number for staging dir naming and glob matching
|
|
28
|
+
TARGET_MAJOR="$(echo "$TARGET_VERSION" | cut -d. -f1)"
|
|
29
|
+
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
|
+
PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
32
|
+
|
|
33
|
+
echo "📦 Package dir: $PACKAGE_DIR"
|
|
34
|
+
|
|
35
|
+
# --- Find Node 20 for compiling TypeScript ---
|
|
36
|
+
NODE20_BIN=""
|
|
37
|
+
for dir in "$HOME/.nvm/versions/node"/v20.*/bin; do
|
|
38
|
+
if [ -f "$dir/node" ]; then
|
|
39
|
+
NODE20_BIN="$dir/node"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
if [ -z "$NODE20_BIN" ]; then
|
|
45
|
+
echo "⚠️ Node 20 not found via nvm, falling back to current node for TS compile"
|
|
46
|
+
NODE20_BIN="$(which node)"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
NODE20_NPM="$(dirname "$NODE20_BIN")/npm"
|
|
50
|
+
echo "🔨 Compiling TypeScript with: $NODE20_BIN ($(${NODE20_BIN} --version))"
|
|
51
|
+
|
|
52
|
+
# --- Compile TypeScript ---
|
|
53
|
+
cd "$PACKAGE_DIR"
|
|
54
|
+
"$NODE20_NPM" run compile
|
|
55
|
+
echo "✅ TypeScript compiled"
|
|
56
|
+
|
|
57
|
+
# --- Find target Node binaries ---
|
|
58
|
+
# If exact version given (e.g. 20.19.0), match exactly. Otherwise match all patch versions.
|
|
59
|
+
TARGET_NODE_BINS=()
|
|
60
|
+
|
|
61
|
+
if echo "$TARGET_VERSION" | grep -qE '^\d+\.\d+\.\d+$'; then
|
|
62
|
+
# Exact version like 20.19.0
|
|
63
|
+
exact_dir="$HOME/.nvm/versions/node/v${TARGET_VERSION}/bin"
|
|
64
|
+
if [ -f "$exact_dir/node" ]; then
|
|
65
|
+
TARGET_NODE_BINS+=("$exact_dir/node")
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
# Major only — collect all patch versions
|
|
69
|
+
for dir in "$HOME/.nvm/versions/node"/v${TARGET_MAJOR}.*/bin; do
|
|
70
|
+
if [ -f "$dir/node" ]; then
|
|
71
|
+
TARGET_NODE_BINS+=("$dir/node")
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if [ ${#TARGET_NODE_BINS[@]} -eq 0 ]; then
|
|
77
|
+
echo "❌ Node $TARGET_VERSION not found in ~/.nvm/versions/node/"
|
|
78
|
+
echo " Run: nvm install $TARGET_VERSION"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Use the last (latest patch) for building
|
|
83
|
+
TARGET_NODE_BIN="${TARGET_NODE_BINS[${#TARGET_NODE_BINS[@]}-1]}"
|
|
84
|
+
TARGET_NODE_NPM="$(dirname "$TARGET_NODE_BIN")/npm"
|
|
85
|
+
TARGET_NODE_DIR="$(dirname "$TARGET_NODE_BIN")"
|
|
86
|
+
TARGET_NODE_ACTUAL_VERSION="$("$TARGET_NODE_BIN" --version)"
|
|
87
|
+
|
|
88
|
+
echo "🎯 Found Node $TARGET_VERSION installs: ${TARGET_NODE_BINS[*]}"
|
|
89
|
+
echo "🔨 Building with: $TARGET_NODE_BIN ($TARGET_NODE_ACTUAL_VERSION)"
|
|
90
|
+
|
|
91
|
+
# --- Pick the right isolated-vm version for the target node ---
|
|
92
|
+
# isolated-vm@5.x supports Node <22, isolated-vm@6.x requires Node >=22
|
|
93
|
+
if [ "$TARGET_MAJOR" -ge 22 ]; then
|
|
94
|
+
IVM_VERSION="^6.0.0"
|
|
95
|
+
echo "📌 Using isolated-vm@6.x (Node >= 22)"
|
|
96
|
+
else
|
|
97
|
+
IVM_VERSION="^5.0.4"
|
|
98
|
+
echo "📌 Using isolated-vm@5.x (Node < 22)"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# --- Create staging directory ---
|
|
102
|
+
STAGING_DIR="/tmp/knowhow-node-${TARGET_MAJOR}"
|
|
103
|
+
rm -rf "$STAGING_DIR"
|
|
104
|
+
mkdir -p "$STAGING_DIR"
|
|
105
|
+
echo ""
|
|
106
|
+
echo "📁 Staging dir: $STAGING_DIR"
|
|
107
|
+
|
|
108
|
+
# --- Copy compiled output and package files into staging dir ---
|
|
109
|
+
echo "📋 Copying compiled output to staging dir..."
|
|
110
|
+
cp -r "$PACKAGE_DIR/ts_build" "$STAGING_DIR/ts_build"
|
|
111
|
+
cp -r "$PACKAGE_DIR/bin" "$STAGING_DIR/bin" 2>/dev/null || true
|
|
112
|
+
cp "$PACKAGE_DIR/package.json" "$STAGING_DIR/package.json"
|
|
113
|
+
for item in README.md LICENSE .npmignore; do
|
|
114
|
+
[ -e "$PACKAGE_DIR/$item" ] && cp "$PACKAGE_DIR/$item" "$STAGING_DIR/" || true
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
# --- Patch package.json for target isolated-vm version ---
|
|
118
|
+
echo "📝 Patching package.json for isolated-vm $IVM_VERSION..."
|
|
119
|
+
"$NODE20_BIN" -e "
|
|
120
|
+
const fs = require('fs');
|
|
121
|
+
const pkg = JSON.parse(fs.readFileSync('$STAGING_DIR/package.json', 'utf8'));
|
|
122
|
+
pkg.dependencies['isolated-vm'] = '$IVM_VERSION';
|
|
123
|
+
// Remove workspace protocol deps that won't resolve outside the monorepo
|
|
124
|
+
if (pkg.dependencies) {
|
|
125
|
+
for (const [k, v] of Object.entries(pkg.dependencies)) {
|
|
126
|
+
if (String(v).startsWith('workspace:')) delete pkg.dependencies[k];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
fs.writeFileSync('$STAGING_DIR/package.json', JSON.stringify(pkg, null, 2));
|
|
130
|
+
console.log('✅ package.json patched');
|
|
131
|
+
"
|
|
132
|
+
|
|
133
|
+
# --- Install deps in staging dir using target node ---
|
|
134
|
+
echo ""
|
|
135
|
+
echo "📦 Installing dependencies in staging dir with Node $TARGET_MAJOR..."
|
|
136
|
+
cd "$STAGING_DIR"
|
|
137
|
+
# Prepend target node bin to PATH so npm/node-gyp uses the correct node version
|
|
138
|
+
PATH="$TARGET_NODE_DIR:$PATH" "$TARGET_NODE_NPM" install --no-save 2>&1
|
|
139
|
+
echo "✅ Dependencies installed (isolated-vm compiled for Node $TARGET_MAJOR)"
|
|
140
|
+
|
|
141
|
+
# --- Symlink globally for ALL matching Node version installs ---
|
|
142
|
+
PKG_NAME="$("$NODE20_BIN" -e "console.log(require('$STAGING_DIR/package.json').name)")"
|
|
143
|
+
PKG_BIN_NAME="$("$NODE20_BIN" -e "const b=require('$STAGING_DIR/package.json').bin; console.log(Object.keys(b)[0])")"
|
|
144
|
+
PKG_BIN_FILE="$("$NODE20_BIN" -e "const b=require('$STAGING_DIR/package.json').bin; console.log(Object.values(b)[0])")"
|
|
145
|
+
|
|
146
|
+
echo ""
|
|
147
|
+
echo "🔗 Linking $PKG_NAME globally for all Node $TARGET_MAJOR installs..."
|
|
148
|
+
for node_bin in "${TARGET_NODE_BINS[@]}"; do
|
|
149
|
+
node_prefix="$("$node_bin" -e "const p=require('path');console.log(p.join(p.dirname(process.execPath),'..'))" 2>/dev/null)"
|
|
150
|
+
global_modules="$node_prefix/lib/node_modules"
|
|
151
|
+
global_bin="$node_prefix/bin"
|
|
152
|
+
mkdir -p "$global_modules/@tyvm"
|
|
153
|
+
rm -rf "$global_modules/$PKG_NAME"
|
|
154
|
+
ln -sf "$STAGING_DIR" "$global_modules/$PKG_NAME"
|
|
155
|
+
rm -f "$global_bin/$PKG_BIN_NAME"
|
|
156
|
+
ln -sf "$global_modules/$PKG_NAME/$PKG_BIN_FILE" "$global_bin/$PKG_BIN_NAME"
|
|
157
|
+
echo "✅ $("$node_bin" --version): $global_modules/$PKG_NAME → $STAGING_DIR"
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
echo ""
|
|
161
|
+
echo "🎉 Done! Switch to Node $TARGET_MAJOR and run: knowhow"
|
|
162
|
+
echo " nvm use $TARGET_MAJOR && knowhow"
|
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!");
|
|
@@ -138,7 +159,7 @@ async function main() {
|
|
|
138
159
|
console.log(`Current version: ${version}`);
|
|
139
160
|
|
|
140
161
|
console.log("📦 Installing latest version from npm...");
|
|
141
|
-
execSync("npm install -g knowhow@latest", {
|
|
162
|
+
execSync("npm install -g @tyvm/knowhow@latest", {
|
|
142
163
|
stdio: "inherit",
|
|
143
164
|
encoding: "utf-8",
|
|
144
165
|
});
|
|
@@ -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,
|
package/src/clients/index.ts
CHANGED
|
@@ -217,7 +217,12 @@ export class AIClient {
|
|
|
217
217
|
for (const entry of providers) {
|
|
218
218
|
const client = this.resolveClient(entry);
|
|
219
219
|
|
|
220
|
-
if (!client)
|
|
220
|
+
if (!client) {
|
|
221
|
+
if (entry.provider === "knowhow") {
|
|
222
|
+
console.warn(`⚠️ Knowhow provider is not logged in. Run 'knowhow login' to enable Knowhow models.`);
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
221
226
|
|
|
222
227
|
const reg = this.providerRegistry[entry.provider];
|
|
223
228
|
|
|
@@ -493,11 +498,19 @@ export class AIClient {
|
|
|
493
498
|
return foundByModel;
|
|
494
499
|
}
|
|
495
500
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
+
const allModels = this.listAllModels();
|
|
502
|
+
const hasKnowhowModels =
|
|
503
|
+
allModels["knowhow"] && allModels["knowhow"].length > 0;
|
|
504
|
+
const knowhowIsConfigured = Object.keys(allModels).includes("knowhow");
|
|
505
|
+
|
|
506
|
+
console.warn(`⚠️ Unable to find model '${model}' for provider '${provider}'.`);
|
|
507
|
+
console.warn(` Available providers: ${Object.keys(allModels).join(", ") || "(none)"}`);
|
|
508
|
+
|
|
509
|
+
if (!hasKnowhowModels && !knowhowIsConfigured) {
|
|
510
|
+
console.warn(` Tip: Run 'knowhow login' to enable Knowhow models.`);
|
|
511
|
+
} else if (!hasKnowhowModels) {
|
|
512
|
+
console.warn(` Tip: The Knowhow provider returned no models. Try running 'knowhow login' to re-authenticate.`);
|
|
513
|
+
}
|
|
501
514
|
|
|
502
515
|
return { provider, model };
|
|
503
516
|
}
|
|
@@ -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,
|