feat: improve provider settings and model selection
This commit is contained in:
Generated
+396
-347
@@ -126,6 +126,26 @@
|
|||||||
"zod": "^4.0.0"
|
"zod": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@anthropic-ai/sdk": {
|
||||||
|
"version": "0.90.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.90.0.tgz",
|
||||||
|
"integrity": "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema-to-ts": "^3.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"anthropic-ai-sdk": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@aws-crypto/crc32": {
|
"node_modules/@aws-crypto/crc32": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
|
||||||
@@ -8443,31 +8463,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard": {
|
"node_modules/@mariozechner/clipboard": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.3.tgz",
|
||||||
"integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==",
|
"integrity": "sha512-e7jASirzfm+ROiOGFh843+cFZTy3DfzP+jldCvh8RnEk0C3QihDTn7dd7Yh7KAJydwIJ18FJSZ2swHvCJhk18g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@mariozechner/clipboard-darwin-arm64": "0.3.2",
|
"@mariozechner/clipboard-darwin-arm64": "0.3.3",
|
||||||
"@mariozechner/clipboard-darwin-universal": "0.3.2",
|
"@mariozechner/clipboard-darwin-universal": "0.3.3",
|
||||||
"@mariozechner/clipboard-darwin-x64": "0.3.2",
|
"@mariozechner/clipboard-darwin-x64": "0.3.3",
|
||||||
"@mariozechner/clipboard-linux-arm64-gnu": "0.3.2",
|
"@mariozechner/clipboard-linux-arm64-gnu": "0.3.3",
|
||||||
"@mariozechner/clipboard-linux-arm64-musl": "0.3.2",
|
"@mariozechner/clipboard-linux-arm64-musl": "0.3.3",
|
||||||
"@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2",
|
"@mariozechner/clipboard-linux-riscv64-gnu": "0.3.3",
|
||||||
"@mariozechner/clipboard-linux-x64-gnu": "0.3.2",
|
"@mariozechner/clipboard-linux-x64-gnu": "0.3.3",
|
||||||
"@mariozechner/clipboard-linux-x64-musl": "0.3.2",
|
"@mariozechner/clipboard-linux-x64-musl": "0.3.3",
|
||||||
"@mariozechner/clipboard-win32-arm64-msvc": "0.3.2",
|
"@mariozechner/clipboard-win32-arm64-msvc": "0.3.3",
|
||||||
"@mariozechner/clipboard-win32-x64-msvc": "0.3.2"
|
"@mariozechner/clipboard-win32-x64-msvc": "0.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-darwin-arm64": {
|
"node_modules/@mariozechner/clipboard-darwin-arm64": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.3.tgz",
|
||||||
"integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==",
|
"integrity": "sha512-+zhuZGXqVrdkbIRdnwiZNbTJ7V3elq/A+C5d5laJoyhJgWs41eO5NUMkBkj6f23F2L4PRXEhdn5/ktlPx+bG3Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8481,9 +8501,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-darwin-universal": {
|
"node_modules/@mariozechner/clipboard-darwin-universal": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.3.tgz",
|
||||||
"integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==",
|
"integrity": "sha512-x9aRfTyndVqpEQ44LNNCK/EXZd9y8rWkLQgNhmWpby9PXrjPhNxfjUc2Db4mt4nJjU/4zzO8F5v/XyzlUGSdhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8494,9 +8514,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-darwin-x64": {
|
"node_modules/@mariozechner/clipboard-darwin-x64": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.3.tgz",
|
||||||
"integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==",
|
"integrity": "sha512-6ut/NawB0KiYPCwrirgNp6Br62LntL978q7G6d/Rs2pmPvQb53bP96eUMYl+Y3a7Qk13bGZ4w9rVPFxRE9m9ag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8510,9 +8530,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
|
"node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.3.tgz",
|
||||||
"integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==",
|
"integrity": "sha512-gf3dH4kBddU1AOyHVB53mjLUFfJAKlTmxTMw51jdeg7eE7IjfEBXVvM4bifMtBxbWkT0eA0FUZ1C0KQ6Z5l6pw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8526,9 +8546,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-linux-arm64-musl": {
|
"node_modules/@mariozechner/clipboard-linux-arm64-musl": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.3.tgz",
|
||||||
"integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==",
|
"integrity": "sha512-o1paj2+zmAQ/LaPS85XJCxhNowNQpxYM2cGY6pWvB5Kqmz6hZjl6CzDg5tbf1hZkn/Em6jpOaE2UtMxKdELBDA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8542,9 +8562,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
|
"node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.3.tgz",
|
||||||
"integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==",
|
"integrity": "sha512-dkEhE4ekePJwMbBq9HP1//CFMNmDzA/iV9AXqBfvL5CWmmDIRXqh4A3YZt3tWO/HdMerX+xNCEiR7WiOsIG+UA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -8558,9 +8578,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-linux-x64-gnu": {
|
"node_modules/@mariozechner/clipboard-linux-x64-gnu": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.3.tgz",
|
||||||
"integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==",
|
"integrity": "sha512-lT2yANtTLlEtFBIH3uGoRa/CQas/eBoLNi3qr9axQFoRgF4RGPSJ66yHOSnMECBneTIb1Iqv3UxokTfX27CdoQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8574,9 +8594,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-linux-x64-musl": {
|
"node_modules/@mariozechner/clipboard-linux-x64-musl": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.3.tgz",
|
||||||
"integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==",
|
"integrity": "sha512-saq/MCB0QHK/7ZZLjAZ0QkbY944dyjOsur8gneGCfMitt+GOiE1CU4OUipHC4b6x8UDY9bRLsR4aBaxu22OFPA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8590,9 +8610,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
|
"node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.3.tgz",
|
||||||
"integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==",
|
"integrity": "sha512-cGuvSj0/2X2w983yEcKw+i+r1EBej6ZZIN+fXG3eY2G/HaIQpbXpLvMxKyZ9LKtbZx+Z6q/gELEoSBMLML6BaQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8606,9 +8626,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/clipboard-win32-x64-msvc": {
|
"node_modules/@mariozechner/clipboard-win32-x64-msvc": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.3.tgz",
|
||||||
"integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==",
|
"integrity": "sha512-5hvaEq/bgYovTIGx43O/S7loIHYV3ue90WcV1dz0wdMXroVKZKeU/yfwM0PALQA1OcrEHiGXGySFReXr72lGtA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8634,6 +8654,330 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mariozechner/pi-agent-core": {
|
||||||
|
"version": "0.70.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.70.2.tgz",
|
||||||
|
"integrity": "sha512-g1hIdKyDwmQOoBGO0R4OhpemKeMENeK0vE5FJtuQKqEcsdCAkVBgZAK6aZUARYZVxMA718JS6WPLFWoddzjD7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@mariozechner/pi-ai": "^0.70.2",
|
||||||
|
"typebox": "^1.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-ai": {
|
||||||
|
"version": "0.70.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.70.2.tgz",
|
||||||
|
"integrity": "sha512-+30LRPjXsXF+oI96DvGWMbdPGeqoLJvadh6UPev7wx2DzhC9FEqXkQcoMZ0usbCm7E9pl8ua8a9s/pQ5ikaUbg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.90.0",
|
||||||
|
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
|
||||||
|
"@google/genai": "^1.40.0",
|
||||||
|
"@mistralai/mistralai": "^2.2.0",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"openai": "6.26.0",
|
||||||
|
"partial-json": "^0.1.7",
|
||||||
|
"proxy-agent": "^6.5.0",
|
||||||
|
"typebox": "^1.1.24",
|
||||||
|
"undici": "^7.19.1",
|
||||||
|
"zod-to-json-schema": "^3.24.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pi-ai": "dist/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-ai/node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-ai/node_modules/openai": {
|
||||||
|
"version": "6.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz",
|
||||||
|
"integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"openai": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.25 || ^4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ws": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent": {
|
||||||
|
"version": "0.70.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.70.2.tgz",
|
||||||
|
"integrity": "sha512-asfNqV89HKAmKvJ1wENBY/UQMIf77kLtkzBrvXnMQV4YbH7D/6KT+VeVzPG6zm5PAZP2UtdLY9B9Cge7IxH37w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@mariozechner/jiti": "^2.6.2",
|
||||||
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
||||||
|
"@mariozechner/pi-ai": "^0.70.2",
|
||||||
|
"@mariozechner/pi-tui": "^0.70.2",
|
||||||
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
||||||
|
"chalk": "^5.5.0",
|
||||||
|
"cli-highlight": "^2.1.11",
|
||||||
|
"diff": "^8.0.2",
|
||||||
|
"extract-zip": "^2.0.1",
|
||||||
|
"file-type": "^21.1.1",
|
||||||
|
"glob": "^13.0.1",
|
||||||
|
"hosted-git-info": "^9.0.2",
|
||||||
|
"ignore": "^7.0.5",
|
||||||
|
"marked": "^15.0.12",
|
||||||
|
"minimatch": "^10.2.3",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
|
"strip-ansi": "^7.1.0",
|
||||||
|
"typebox": "^1.1.24",
|
||||||
|
"undici": "^7.19.1",
|
||||||
|
"uuid": "^14.0.0",
|
||||||
|
"yaml": "^2.8.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pi": "dist/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.6.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@mariozechner/clipboard": "^0.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/balanced-match": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/brace-expansion": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/glob": {
|
||||||
|
"version": "13.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||||
|
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"minimatch": "^10.2.2",
|
||||||
|
"minipass": "^7.1.3",
|
||||||
|
"path-scurry": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/hosted-git-info": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/ignore": {
|
||||||
|
"version": "7.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||||
|
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/lru-cache": {
|
||||||
|
"version": "11.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
|
||||||
|
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/minimatch": {
|
||||||
|
"version": "10.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
|
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^5.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/path-scurry": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"minipass": "^7.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/strip-ansi": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-coding-agent/node_modules/uuid": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist-node/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-tui": {
|
||||||
|
"version": "0.70.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.70.2.tgz",
|
||||||
|
"integrity": "sha512-PtKC0NepnrYcqMx6MXkWTrBzC9tI62KeC6w940oT46lCbfvgmfqXciR15+9BZpxxc1H4jd3CMrKsmOPVeUqZ0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"chalk": "^5.5.0",
|
||||||
|
"get-east-asian-width": "^1.3.0",
|
||||||
|
"marked": "^15.0.12",
|
||||||
|
"mime-types": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"koffi": "^2.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-tui/node_modules/@types/mime-types": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-tui/node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mariozechner/pi-tui/node_modules/mime-types": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "^1.54.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mistralai/mistralai": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"zod": "^3.25.0 || ^4.0.0",
|
||||||
|
"zod-to-json-schema": "^3.25.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.27.1",
|
"version": "1.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
||||||
@@ -36216,6 +36560,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typebox": {
|
||||||
|
"version": "1.1.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.33.tgz",
|
||||||
|
"integrity": "sha512-+/MWwlQ1q2GSVwoxi/+u5JsHkgLQKcCN2Nsjree9c+K7GJu40qbaHrFETmfV1i9Fs1TcOVfynW+jJvIWcXtvjw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/typed-array-buffer": {
|
"node_modules/typed-array-buffer": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||||
@@ -38357,9 +38707,9 @@
|
|||||||
"@getpaseo/highlight": "0.1.63-beta.1",
|
"@getpaseo/highlight": "0.1.63-beta.1",
|
||||||
"@getpaseo/relay": "0.1.63-beta.1",
|
"@getpaseo/relay": "0.1.63-beta.1",
|
||||||
"@isaacs/ttlcache": "^2.1.4",
|
"@isaacs/ttlcache": "^2.1.4",
|
||||||
"@mariozechner/pi-agent-core": "^0.67.68",
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
||||||
"@mariozechner/pi-ai": "^0.67.68",
|
"@mariozechner/pi-ai": "^0.70.2",
|
||||||
"@mariozechner/pi-coding-agent": "^0.67.68",
|
"@mariozechner/pi-coding-agent": "^0.70.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@opencode-ai/sdk": "1.2.6",
|
"@opencode-ai/sdk": "1.2.6",
|
||||||
"@sctg/sentencepiece-js": "^1.1.0",
|
"@sctg/sentencepiece-js": "^1.1.0",
|
||||||
@@ -38401,26 +38751,6 @@
|
|||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/@anthropic-ai/sdk": {
|
|
||||||
"version": "0.90.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.90.0.tgz",
|
|
||||||
"integrity": "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"json-schema-to-ts": "^3.1.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"anthropic-ai-sdk": "bin/cli"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.0 || ^4.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"zod": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@isaacs/ttlcache": {
|
"packages/server/node_modules/@isaacs/ttlcache": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-2.1.4.tgz",
|
||||||
@@ -38430,147 +38760,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/@mariozechner/pi-agent-core": {
|
|
||||||
"version": "0.67.68",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.67.68.tgz",
|
|
||||||
"integrity": "sha512-anwFuzeUL7Qjbyih4DWY7w1zrOTrBxaz1L6+duLUuuzpHOun0EiP4KWIGTXPT5oJA7ZaeRNTyXJ7PlWfGQG33g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@mariozechner/pi-ai": "^0.67.68"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mariozechner/pi-ai": {
|
|
||||||
"version": "0.67.68",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.67.68.tgz",
|
|
||||||
"integrity": "sha512-DWWQmcb3IV3mbGXmzYBScfKA6kA52n/stY029eiBikrIxVT7DGLG6n7KSvTA2R4qBSgi1iFL3nGHtwxmtIn6Lg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@anthropic-ai/sdk": "^0.90.0",
|
|
||||||
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
|
|
||||||
"@google/genai": "^1.40.0",
|
|
||||||
"@mistralai/mistralai": "^2.2.0",
|
|
||||||
"@sinclair/typebox": "^0.34.41",
|
|
||||||
"ajv": "^8.17.1",
|
|
||||||
"ajv-formats": "^3.0.1",
|
|
||||||
"chalk": "^5.6.2",
|
|
||||||
"openai": "6.26.0",
|
|
||||||
"partial-json": "^0.1.7",
|
|
||||||
"proxy-agent": "^6.5.0",
|
|
||||||
"undici": "^7.19.1",
|
|
||||||
"zod-to-json-schema": "^3.24.6"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"pi-ai": "dist/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mariozechner/pi-ai/node_modules/openai": {
|
|
||||||
"version": "6.26.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz",
|
|
||||||
"integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"openai": "bin/cli"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"ws": "^8.18.0",
|
|
||||||
"zod": "^3.25 || ^4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"ws": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"zod": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mariozechner/pi-coding-agent": {
|
|
||||||
"version": "0.67.68",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.67.68.tgz",
|
|
||||||
"integrity": "sha512-Bai2yUBpgjftqGvg3GNV9pxcMAatqJwQYsqM+7j39+tm+IpoW7hbMBnzXZQvykAwXIrTXpZFF5yp5ajeDI5Atg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@mariozechner/jiti": "^2.6.2",
|
|
||||||
"@mariozechner/pi-agent-core": "^0.67.68",
|
|
||||||
"@mariozechner/pi-ai": "^0.67.68",
|
|
||||||
"@mariozechner/pi-tui": "^0.67.68",
|
|
||||||
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
||||||
"ajv": "^8.17.1",
|
|
||||||
"chalk": "^5.5.0",
|
|
||||||
"cli-highlight": "^2.1.11",
|
|
||||||
"diff": "^8.0.2",
|
|
||||||
"extract-zip": "^2.0.1",
|
|
||||||
"file-type": "^21.1.1",
|
|
||||||
"glob": "^13.0.1",
|
|
||||||
"hosted-git-info": "^9.0.2",
|
|
||||||
"ignore": "^7.0.5",
|
|
||||||
"marked": "^15.0.12",
|
|
||||||
"minimatch": "^10.2.3",
|
|
||||||
"proper-lockfile": "^4.1.2",
|
|
||||||
"strip-ansi": "^7.1.0",
|
|
||||||
"undici": "^7.19.1",
|
|
||||||
"uuid": "^11.1.0",
|
|
||||||
"yaml": "^2.8.2"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"pi": "dist/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.6.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@mariozechner/clipboard": "^0.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mariozechner/pi-coding-agent/node_modules/uuid": {
|
|
||||||
"version": "11.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
|
||||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
|
||||||
"funding": [
|
|
||||||
"https://github.com/sponsors/broofa",
|
|
||||||
"https://github.com/sponsors/ctavan"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/esm/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mariozechner/pi-tui": {
|
|
||||||
"version": "0.67.68",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.67.68.tgz",
|
|
||||||
"integrity": "sha512-RhcMaGz88lNOm5+9yx+YCIfXZALLbMxB2cwsoHzyOzs+OZAItw8tz6xJZPAnX0RHY+ENQEGMMDY9TF6pxxnkbA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/mime-types": "^2.1.4",
|
|
||||||
"chalk": "^5.5.0",
|
|
||||||
"get-east-asian-width": "^1.3.0",
|
|
||||||
"marked": "^15.0.12",
|
|
||||||
"mime-types": "^3.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"koffi": "^2.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@mistralai/mistralai": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-JQUGIXjFWnw/J9LpTSf/ZXwVW3Sh8FBAcfTo5QvAHqkl4CfSiIwnjRJhMoAFcP6ncCe84YPU1ncDGX+p3OXnfg==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"ws": "^8.18.0",
|
|
||||||
"zod": "^3.25.0 || ^4.0.0",
|
|
||||||
"zod-to-json-schema": "^3.25.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@modelcontextprotocol/sdk": {
|
"packages/server/node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz",
|
||||||
@@ -38658,18 +38847,6 @@
|
|||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/@sinclair/typebox": {
|
|
||||||
"version": "0.34.49",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
|
|
||||||
"integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/@types/mime-types": {
|
|
||||||
"version": "2.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
|
|
||||||
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/accepts": {
|
"packages/server/node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
@@ -38699,23 +38876,6 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/ajv-formats": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"ajv": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/ansi-regex": {
|
"packages/server/node_modules/ansi-regex": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
@@ -38728,15 +38888,6 @@
|
|||||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/balanced-match": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/body-parser": {
|
"packages/server/node_modules/body-parser": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||||
@@ -38757,30 +38908,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/brace-expansion": {
|
|
||||||
"version": "5.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
||||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/chalk": {
|
|
||||||
"version": "5.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
|
||||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/content-disposition": {
|
"packages/server/node_modules/content-disposition": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||||
@@ -38828,44 +38955,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/glob": {
|
|
||||||
"version": "13.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
|
||||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"minimatch": "^10.2.2",
|
|
||||||
"minipass": "^7.1.3",
|
|
||||||
"path-scurry": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/hosted-git-info": {
|
|
||||||
"version": "9.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",
|
|
||||||
"integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^11.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.17.0 || >=22.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/ignore": {
|
|
||||||
"version": "7.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
|
||||||
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/isexe": {
|
"packages/server/node_modules/isexe": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
|
||||||
@@ -38875,15 +38964,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/lru-cache": {
|
|
||||||
"version": "11.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
|
|
||||||
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": "20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/media-typer": {
|
"packages/server/node_modules/media-typer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||||
@@ -38917,21 +38997,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/minimatch": {
|
|
||||||
"version": "10.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
|
||||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^5.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/negotiator": {
|
"packages/server/node_modules/negotiator": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
@@ -38956,22 +39021,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/path-scurry": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^11.0.0",
|
|
||||||
"minipass": "^7.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/raw-body": {
|
"packages/server/node_modules/raw-body": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AlertCircle, RotateCw, Search } from "lucide-react-native";
|
import { AlertCircle, Plus, RotateCw, Search, Trash2 } from "lucide-react-native";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
@@ -13,11 +13,13 @@ import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-mod
|
|||||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
import { Fonts } from "@/constants/theme";
|
import { Fonts } from "@/constants/theme";
|
||||||
|
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
||||||
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
||||||
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
||||||
import { resolveProviderLabel } from "@/utils/provider-definitions";
|
import { resolveProviderLabel } from "@/utils/provider-definitions";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||||
|
import type { ProviderProfileModel } from "@server/server/agent/provider-launch-config";
|
||||||
|
|
||||||
interface ProviderDiagnosticSheetProps {
|
interface ProviderDiagnosticSheetProps {
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -43,6 +45,182 @@ function ModelRow({ model, isFirst }: { model: AgentModelDefinition; isFirst: bo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AdditionalModelRow(props: {
|
||||||
|
model: ProviderProfileModel;
|
||||||
|
isFirst: boolean;
|
||||||
|
deleting: boolean;
|
||||||
|
onDelete: (modelId: string) => void;
|
||||||
|
}) {
|
||||||
|
const { theme } = useUnistyles();
|
||||||
|
const { model, isFirst, deleting, onDelete } = props;
|
||||||
|
const rowStyle = useMemo(
|
||||||
|
() => [sheetStyles.additionalModelRow, !isFirst && sheetStyles.modelRowBorder],
|
||||||
|
[isFirst],
|
||||||
|
);
|
||||||
|
const handleDelete = useCallback(() => onDelete(model.id), [model.id, onDelete]);
|
||||||
|
const deleteButtonStyle = useCallback(
|
||||||
|
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||||
|
sheetStyles.iconButton,
|
||||||
|
(Boolean(hovered) || pressed) && sheetStyles.iconButtonHovered,
|
||||||
|
deleting ? sheetStyles.disabled : null,
|
||||||
|
],
|
||||||
|
[deleting],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={rowStyle}>
|
||||||
|
<View style={sheetStyles.additionalModelText}>
|
||||||
|
<Text style={sheetStyles.modelLabel} numberOfLines={1}>
|
||||||
|
{model.label}
|
||||||
|
</Text>
|
||||||
|
<Text style={sheetStyles.modelId} numberOfLines={1} selectable>
|
||||||
|
{model.id}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Pressable
|
||||||
|
onPress={handleDelete}
|
||||||
|
disabled={deleting}
|
||||||
|
hitSlop={8}
|
||||||
|
style={deleteButtonStyle}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={`Remove ${model.id}`}
|
||||||
|
>
|
||||||
|
<Trash2 size={theme.iconSize.sm} color={theme.colors.destructive} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AdditionalModelsEditor(props: {
|
||||||
|
provider: string;
|
||||||
|
serverId: string;
|
||||||
|
refresh: (providers?: AgentProvider[]) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const { provider, serverId, refresh } = props;
|
||||||
|
const { theme } = useUnistyles();
|
||||||
|
const { config, patchConfig } = useDaemonConfig(serverId);
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [deletingModelId, setDeletingModelId] = useState<string | null>(null);
|
||||||
|
const providerConfig = config?.providers?.[provider];
|
||||||
|
const additionalModels = useMemo(
|
||||||
|
() => providerConfig?.additionalModels ?? [],
|
||||||
|
[providerConfig?.additionalModels],
|
||||||
|
);
|
||||||
|
const trimmedInput = input.trim();
|
||||||
|
const canAdd =
|
||||||
|
trimmedInput.length > 0 && !additionalModels.some((model) => model.id === trimmedInput);
|
||||||
|
|
||||||
|
const patchAdditionalModels = useCallback(
|
||||||
|
async (nextModels: ProviderProfileModel[]) => {
|
||||||
|
await patchConfig({
|
||||||
|
providers: {
|
||||||
|
[provider]: {
|
||||||
|
additionalModels: nextModels,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await refresh([provider as AgentProvider]);
|
||||||
|
},
|
||||||
|
[patchConfig, provider, refresh],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAdd = useCallback(() => {
|
||||||
|
if (!canAdd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
setSaving(true);
|
||||||
|
void patchAdditionalModels([
|
||||||
|
...additionalModels,
|
||||||
|
{
|
||||||
|
id: trimmedInput,
|
||||||
|
label: trimmedInput,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.then(() => setInput(""))
|
||||||
|
.catch((err) => {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to save model");
|
||||||
|
})
|
||||||
|
.finally(() => setSaving(false));
|
||||||
|
}, [additionalModels, canAdd, patchAdditionalModels, trimmedInput]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(modelId: string) => {
|
||||||
|
setError(null);
|
||||||
|
setDeletingModelId(modelId);
|
||||||
|
void patchAdditionalModels(additionalModels.filter((model) => model.id !== modelId))
|
||||||
|
.catch((err) => {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to delete model");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setDeletingModelId((current) => (current === modelId ? null : current));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[additionalModels, patchAdditionalModels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addButtonStyle = useCallback(
|
||||||
|
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||||
|
sheetStyles.addModelButton,
|
||||||
|
(Boolean(hovered) || pressed) && sheetStyles.addModelButtonHovered,
|
||||||
|
!canAdd || saving ? sheetStyles.disabled : null,
|
||||||
|
],
|
||||||
|
[canAdd, saving],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={sheetStyles.section}>
|
||||||
|
<Text style={sheetStyles.sectionTitle}>Additional Models</Text>
|
||||||
|
<View style={sheetStyles.addModelRow}>
|
||||||
|
<View style={sheetStyles.addModelInputContainer}>
|
||||||
|
<AdaptiveTextInput
|
||||||
|
value={input}
|
||||||
|
onChangeText={setInput}
|
||||||
|
onSubmitEditing={handleAdd}
|
||||||
|
placeholder="Model ID"
|
||||||
|
placeholderTextColor={theme.colors.foregroundMuted}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
returnKeyType="done"
|
||||||
|
// @ts-expect-error - outlineStyle is web-only
|
||||||
|
style={DIAGNOSTIC_ADD_MODEL_INPUT_STYLE}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Pressable
|
||||||
|
onPress={handleAdd}
|
||||||
|
disabled={!canAdd || saving}
|
||||||
|
style={addButtonStyle}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Add model"
|
||||||
|
>
|
||||||
|
{saving ? (
|
||||||
|
<LoadingSpinner size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
||||||
|
) : (
|
||||||
|
<Plus size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
{error ? <Text style={sheetStyles.errorText}>{error}</Text> : null}
|
||||||
|
{additionalModels.length > 0 ? (
|
||||||
|
<View style={sheetStyles.additionalModelsList}>
|
||||||
|
{additionalModels.map((model, index) => (
|
||||||
|
<AdditionalModelRow
|
||||||
|
key={model.id}
|
||||||
|
model={model}
|
||||||
|
isFirst={index === 0}
|
||||||
|
deleting={deletingModelId === model.id}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function DiagnosticCodeBlock(props: {
|
function DiagnosticCodeBlock(props: {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
diagnostic: string | null;
|
diagnostic: string | null;
|
||||||
@@ -255,6 +433,8 @@ export function ProviderDiagnosticSheet({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={sheetStyles.modelsSection}>
|
<View style={sheetStyles.modelsSection}>
|
||||||
|
<AdditionalModelsEditor provider={provider} serverId={serverId} refresh={refresh} />
|
||||||
|
|
||||||
<View style={sheetStyles.modelsHeader}>
|
<View style={sheetStyles.modelsHeader}>
|
||||||
<Text style={sheetStyles.sectionTitle}>Models</Text>
|
<Text style={sheetStyles.sectionTitle}>Models</Text>
|
||||||
<View style={sheetStyles.modelsHeaderMeta}>
|
<View style={sheetStyles.modelsHeaderMeta}>
|
||||||
@@ -308,6 +488,10 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
errorText: {
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.destructive,
|
||||||
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
@@ -380,6 +564,50 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
},
|
},
|
||||||
|
addModelRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
|
addModelInputContainer: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
borderRadius: theme.borderRadius.md,
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
},
|
||||||
|
addModelInput: {
|
||||||
|
paddingVertical: theme.spacing[2],
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
},
|
||||||
|
addModelButton: {
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
borderRadius: theme.borderRadius.full,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: theme.colors.accent,
|
||||||
|
},
|
||||||
|
addModelButtonHovered: {
|
||||||
|
backgroundColor: theme.colors.accentBright,
|
||||||
|
},
|
||||||
|
additionalModelsList: {
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: theme.colors.border,
|
||||||
|
},
|
||||||
|
additionalModelRow: {
|
||||||
|
minHeight: 48,
|
||||||
|
paddingVertical: theme.spacing[2],
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
|
additionalModelText: {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
},
|
||||||
searchInput: {
|
searchInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
@@ -419,3 +647,7 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
|
|
||||||
const DIAGNOSTIC_SHEET_SNAP_POINTS = ["50%", "85%"];
|
const DIAGNOSTIC_SHEET_SNAP_POINTS = ["50%", "85%"];
|
||||||
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.searchInput, isWeb && { outlineStyle: "none" }];
|
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.searchInput, isWeb && { outlineStyle: "none" }];
|
||||||
|
const DIAGNOSTIC_ADD_MODEL_INPUT_STYLE = [
|
||||||
|
sheetStyles.addModelInput,
|
||||||
|
isWeb && { outlineStyle: "none" },
|
||||||
|
];
|
||||||
|
|||||||
@@ -61,9 +61,9 @@
|
|||||||
"@getpaseo/highlight": "0.1.63-beta.1",
|
"@getpaseo/highlight": "0.1.63-beta.1",
|
||||||
"@getpaseo/relay": "0.1.63-beta.1",
|
"@getpaseo/relay": "0.1.63-beta.1",
|
||||||
"@isaacs/ttlcache": "^2.1.4",
|
"@isaacs/ttlcache": "^2.1.4",
|
||||||
"@mariozechner/pi-agent-core": "^0.67.68",
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
||||||
"@mariozechner/pi-ai": "^0.67.68",
|
"@mariozechner/pi-ai": "^0.70.2",
|
||||||
"@mariozechner/pi-coding-agent": "^0.67.68",
|
"@mariozechner/pi-coding-agent": "^0.70.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@opencode-ai/sdk": "1.2.6",
|
"@opencode-ai/sdk": "1.2.6",
|
||||||
"@sctg/sentencepiece-js": "^1.1.0",
|
"@sctg/sentencepiece-js": "^1.1.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
buildConfigOverrides,
|
buildConfigOverrides,
|
||||||
buildSessionConfig,
|
buildSessionConfig,
|
||||||
extractTimestamps,
|
extractTimestamps,
|
||||||
|
isStoredAgentProviderAvailable,
|
||||||
toAgentPersistenceHandle,
|
toAgentPersistenceHandle,
|
||||||
} from "../persistence-hooks.js";
|
} from "../persistence-hooks.js";
|
||||||
|
|
||||||
@@ -40,7 +41,11 @@ export async function ensureAgentLoaded(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validProviders = deps.validProviders ?? deps.agentManager.getRegisteredProviderIds();
|
const validProviders = deps.validProviders ?? deps.agentManager.getRegisteredProviderIds();
|
||||||
const handle = toAgentPersistenceHandle(deps.logger, validProviders, record.persistence);
|
if (!isStoredAgentProviderAvailable(record, validProviders)) {
|
||||||
|
throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = toAgentPersistenceHandle(validProviders, record.persistence);
|
||||||
|
|
||||||
let snapshot: ManagedAgent;
|
let snapshot: ManagedAgent;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
@@ -54,7 +59,6 @@ export async function ensureAgentLoaded(
|
|||||||
} else {
|
} else {
|
||||||
const config = buildSessionConfig(record, {
|
const config = buildSessionConfig(record, {
|
||||||
validProviders,
|
validProviders,
|
||||||
logger: deps.logger,
|
|
||||||
});
|
});
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
|
throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ class TestAgentClient implements AgentClient {
|
|||||||
id: "gpt-5.4-mini",
|
id: "gpt-5.4-mini",
|
||||||
label: "GPT-5.4 Mini",
|
label: "GPT-5.4 Mini",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provider: "codex",
|
||||||
|
id: "gpt-5.2-codex",
|
||||||
|
label: "GPT-5.2 Codex",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,6 +792,86 @@ test("createAgent fails when cwd does not exist", async () => {
|
|||||||
).rejects.toThrow("Working directory does not exist");
|
).rejects.toThrow("Working directory does not exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("createAgent reports configured providers when provider is unknown", async () => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||||
|
const storagePath = join(workdir, "agents");
|
||||||
|
const storage = new AgentStorage(storagePath, logger);
|
||||||
|
const manager = new AgentManager({
|
||||||
|
clients: {
|
||||||
|
codex: new TestAgentClient(),
|
||||||
|
},
|
||||||
|
registry: storage,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
manager.createAgent({
|
||||||
|
provider: "missing-provider",
|
||||||
|
cwd: workdir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("Unknown provider 'missing-provider'. Configured providers: codex.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createAgent reports available providers when selected provider is unavailable", async () => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||||
|
const storagePath = join(workdir, "agents");
|
||||||
|
const storage = new AgentStorage(storagePath, logger);
|
||||||
|
|
||||||
|
class UnavailableCodexClient extends TestAgentClient {
|
||||||
|
override async isAvailable(): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const manager = new AgentManager({
|
||||||
|
clients: {
|
||||||
|
codex: new UnavailableCodexClient(),
|
||||||
|
claude: new TestAgentClient(),
|
||||||
|
},
|
||||||
|
registry: storage,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
manager.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd: workdir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
"Provider 'codex' is not available. Available providers: claude. Use one of those providers, or install/configure 'codex'.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createAgent passes explicit model strings through to the provider", async () => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||||
|
const storagePath = join(workdir, "agents");
|
||||||
|
const storage = new AgentStorage(storagePath, logger);
|
||||||
|
class CaptureModelClient extends TestAgentClient {
|
||||||
|
lastConfig: AgentSessionConfig | null = null;
|
||||||
|
|
||||||
|
override async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||||
|
this.lastConfig = config;
|
||||||
|
return new TestAgentSession(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const client = new CaptureModelClient();
|
||||||
|
const manager = new AgentManager({
|
||||||
|
clients: {
|
||||||
|
codex: client,
|
||||||
|
},
|
||||||
|
registry: storage,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
await manager.createAgent({
|
||||||
|
provider: "codex",
|
||||||
|
cwd: workdir,
|
||||||
|
model: "not-a-real-model",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.lastConfig?.model).toBe("not-a-real-model");
|
||||||
|
});
|
||||||
|
|
||||||
test("resumeAgentFromPersistence keeps metadata config, applies overrides, and passes launch env", async () => {
|
test("resumeAgentFromPersistence keeps metadata config, applies overrides, and passes launch env", async () => {
|
||||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-resume-"));
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-resume-"));
|
||||||
const storagePath = join(workdir, "agents");
|
const storagePath = join(workdir, "agents");
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ interface TimeoutOptions {
|
|||||||
onLateError?: (error: unknown) => void;
|
onLateError?: (error: unknown) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatProviderList(providers: readonly string[]): string {
|
||||||
|
return providers.length > 0 ? providers.join(", ") : "none";
|
||||||
|
}
|
||||||
|
|
||||||
export { AGENT_LIFECYCLE_STATUSES, type AgentLifecycleStatus };
|
export { AGENT_LIFECYCLE_STATUSES, type AgentLifecycleStatus };
|
||||||
export type {
|
export type {
|
||||||
AgentTimelineCursor,
|
AgentTimelineCursor,
|
||||||
@@ -716,13 +720,9 @@ export class AgentManager {
|
|||||||
};
|
};
|
||||||
const normalizedConfig = await this.normalizeConfig(injectedConfig);
|
const normalizedConfig = await this.normalizeConfig(injectedConfig);
|
||||||
const launchContext = this.buildLaunchContext(resolvedAgentId);
|
const launchContext = this.buildLaunchContext(resolvedAgentId);
|
||||||
const client = this.requireClient(normalizedConfig.provider);
|
const client = await this.requireAvailableClient({
|
||||||
const available = await client.isAvailable();
|
provider: normalizedConfig.provider,
|
||||||
if (!available) {
|
});
|
||||||
throw new Error(
|
|
||||||
`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const session = await client.createSession(normalizedConfig, launchContext);
|
const session = await client.createSession(normalizedConfig, launchContext);
|
||||||
return this.registerSession(session, normalizedConfig, resolvedAgentId, {
|
return this.registerSession(session, normalizedConfig, resolvedAgentId, {
|
||||||
labels: options?.labels,
|
labels: options?.labels,
|
||||||
@@ -3079,6 +3079,37 @@ export class AgentManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async requireAvailableClient(options: { provider: AgentProvider }): Promise<AgentClient> {
|
||||||
|
const client = this.clients.get(options.provider);
|
||||||
|
if (!client) {
|
||||||
|
const configuredProviders = Array.from(this.clients.keys());
|
||||||
|
throw new Error(
|
||||||
|
`Unknown provider '${options.provider}'. Configured providers: ${formatProviderList(
|
||||||
|
configuredProviders,
|
||||||
|
)}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let unavailableReason: string | null = null;
|
||||||
|
try {
|
||||||
|
const available = await client.isAvailable();
|
||||||
|
if (available) {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
unavailableReason = error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableProviders = (await this.listProviderAvailability())
|
||||||
|
.filter((entry) => entry.available)
|
||||||
|
.map((entry) => entry.provider);
|
||||||
|
const providerList = formatProviderList(availableProviders);
|
||||||
|
const reason = unavailableReason ? ` Reason: ${unavailableReason}.` : "";
|
||||||
|
throw new Error(
|
||||||
|
`Provider '${options.provider}' is not available.${reason} Available providers: ${providerList}. Use one of those providers, or install/configure '${options.provider}'.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private requireClient(provider: AgentProvider): AgentClient {
|
private requireClient(provider: AgentProvider): AgentClient {
|
||||||
const client = this.clients.get(provider);
|
const client = this.clients.get(provider);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import type {
|
|||||||
AgentMode,
|
AgentMode,
|
||||||
AgentPermissionRequest,
|
AgentPermissionRequest,
|
||||||
AgentPersistenceHandle,
|
AgentPersistenceHandle,
|
||||||
|
AgentProvider,
|
||||||
AgentSessionConfig,
|
AgentSessionConfig,
|
||||||
AgentRuntimeInfo,
|
AgentRuntimeInfo,
|
||||||
AgentUsage,
|
AgentUsage,
|
||||||
} from "./agent-sdk-types.js";
|
} from "./agent-sdk-types.js";
|
||||||
import type { ManagedAgent } from "./agent-manager.js";
|
import type { ManagedAgent } from "./agent-manager.js";
|
||||||
import type { JsonValue } from "../json-utils.js";
|
import type { JsonValue } from "../json-utils.js";
|
||||||
import type { Logger } from "pino";
|
import { isStoredAgentProviderAvailable, toAgentPersistenceHandle } from "../persistence-hooks.js";
|
||||||
import { buildProviderRegistry } from "./provider-registry.js";
|
|
||||||
import { coerceAgentProvider, toAgentPersistenceHandle } from "../persistence-hooks.js";
|
|
||||||
|
|
||||||
export type { ManagedAgent };
|
export type { ManagedAgent };
|
||||||
|
|
||||||
interface ProjectionOptions {
|
interface ProjectionOptions {
|
||||||
@@ -143,15 +141,11 @@ export function toAgentPayload(
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStoredRuntimeInfo(
|
function buildStoredRuntimeInfo(record: StoredAgentRecord): AgentRuntimeInfo | undefined {
|
||||||
record: StoredAgentRecord,
|
|
||||||
providerRegistry: ReturnType<typeof buildProviderRegistry>,
|
|
||||||
logger: Logger,
|
|
||||||
): AgentRuntimeInfo | undefined {
|
|
||||||
if (!record.runtimeInfo) return undefined;
|
if (!record.runtimeInfo) return undefined;
|
||||||
const ri = record.runtimeInfo;
|
const ri = record.runtimeInfo;
|
||||||
const runtimeInfo: AgentRuntimeInfo = {
|
const runtimeInfo: AgentRuntimeInfo = {
|
||||||
provider: coerceAgentProvider(logger, providerRegistry, ri.provider, record.id),
|
provider: ri.provider,
|
||||||
sessionId: ri.sessionId,
|
sessionId: ri.sessionId,
|
||||||
};
|
};
|
||||||
if (Object.prototype.hasOwnProperty.call(ri, "model")) {
|
if (Object.prototype.hasOwnProperty.call(ri, "model")) {
|
||||||
@@ -169,10 +163,19 @@ function buildStoredRuntimeInfo(
|
|||||||
return runtimeInfo;
|
return runtimeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildStoredPersistenceHandle(
|
||||||
|
record: StoredAgentRecord,
|
||||||
|
validProviders: Iterable<AgentProvider>,
|
||||||
|
): AgentPersistenceHandle | null {
|
||||||
|
if (!isStoredAgentProviderAvailable(record, validProviders)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toAgentPersistenceHandle(validProviders, record.persistence);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildStoredAgentPayload(
|
export function buildStoredAgentPayload(
|
||||||
record: StoredAgentRecord,
|
record: StoredAgentRecord,
|
||||||
providerRegistry: ReturnType<typeof buildProviderRegistry>,
|
validProviders: Iterable<AgentProvider>,
|
||||||
logger: Logger,
|
|
||||||
): AgentSnapshotPayload {
|
): AgentSnapshotPayload {
|
||||||
const defaultCapabilities = {
|
const defaultCapabilities = {
|
||||||
supportsStreaming: false,
|
supportsStreaming: false,
|
||||||
@@ -187,12 +190,13 @@ export function buildStoredAgentPayload(
|
|||||||
const updatedAt = new Date(resolveStoredAgentPayloadUpdatedAt(record));
|
const updatedAt = new Date(resolveStoredAgentPayloadUpdatedAt(record));
|
||||||
const lastUserMessageAt = record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null;
|
const lastUserMessageAt = record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null;
|
||||||
|
|
||||||
const provider = coerceAgentProvider(logger, providerRegistry, record.provider, record.id);
|
const runtimeInfo = buildStoredRuntimeInfo(record);
|
||||||
const runtimeInfo = buildStoredRuntimeInfo(record, providerRegistry, logger);
|
const providerAvailable = isStoredAgentProviderAvailable(record, validProviders);
|
||||||
|
const persistence = buildStoredPersistenceHandle(record, validProviders);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
provider,
|
provider: record.provider,
|
||||||
cwd: record.cwd,
|
cwd: record.cwd,
|
||||||
model: record.config?.model ?? null,
|
model: record.config?.model ?? null,
|
||||||
thinkingOptionId: record.config?.thinkingOptionId ?? null,
|
thinkingOptionId: record.config?.thinkingOptionId ?? null,
|
||||||
@@ -209,13 +213,14 @@ export function buildStoredAgentPayload(
|
|||||||
currentModeId: record.lastModeId ?? null,
|
currentModeId: record.lastModeId ?? null,
|
||||||
availableModes: [],
|
availableModes: [],
|
||||||
pendingPermissions: [],
|
pendingPermissions: [],
|
||||||
persistence: toAgentPersistenceHandle(logger, providerRegistry, record.persistence),
|
persistence,
|
||||||
title: record.title ?? record.config?.title ?? null,
|
title: record.title ?? record.config?.title ?? null,
|
||||||
requiresAttention: record.requiresAttention ?? false,
|
requiresAttention: record.requiresAttention ?? false,
|
||||||
attentionReason: record.attentionReason ?? null,
|
attentionReason: record.attentionReason ?? null,
|
||||||
attentionTimestamp: record.attentionTimestamp ?? null,
|
attentionTimestamp: record.attentionTimestamp ?? null,
|
||||||
archivedAt: record.archivedAt ?? null,
|
archivedAt: record.archivedAt ?? null,
|
||||||
labels: normalizeLabels(record.labels),
|
labels: normalizeLabels(record.labels),
|
||||||
|
...(providerAvailable ? {} : { providerUnavailable: true }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +243,7 @@ export function toAgentListItemPayload(agent: AgentSnapshotPayload): AgentListIt
|
|||||||
attentionReason: agent.attentionReason ?? null,
|
attentionReason: agent.attentionReason ?? null,
|
||||||
attentionTimestamp: agent.attentionTimestamp ?? null,
|
attentionTimestamp: agent.attentionTimestamp ?? null,
|
||||||
labels: agent.labels,
|
labels: agent.labels,
|
||||||
|
...(agent.providerUnavailable ? { providerUnavailable: true } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,13 +141,32 @@ function createTestDeps(): TestDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProviderDefinition(overrides: Partial<ProviderDefinition>): ProviderDefinition {
|
function createProviderDefinition(overrides: Partial<ProviderDefinition>): ProviderDefinition {
|
||||||
|
const provider = (overrides.id ?? "claude") as AgentProvider;
|
||||||
return {
|
return {
|
||||||
id: "claude",
|
id: provider,
|
||||||
label: "Claude",
|
label: "Claude",
|
||||||
description: "Test provider",
|
description: "Test provider",
|
||||||
defaultModeId: "default",
|
defaultModeId: "default",
|
||||||
modes: [],
|
modes: [],
|
||||||
createClient: vi.fn() as ProviderDefinition["createClient"],
|
createClient: vi.fn(() => ({
|
||||||
|
provider,
|
||||||
|
capabilities: {
|
||||||
|
supportsStreaming: false,
|
||||||
|
supportsSessionPersistence: false,
|
||||||
|
supportsDynamicModes: false,
|
||||||
|
supportsMcpServers: false,
|
||||||
|
supportsReasoningStream: false,
|
||||||
|
supportsToolInvocations: false,
|
||||||
|
},
|
||||||
|
createSession: async () => {
|
||||||
|
throw new Error("createSession is not used by this MCP provider test");
|
||||||
|
},
|
||||||
|
resumeSession: async () => {
|
||||||
|
throw new Error("resumeSession is not used by this MCP provider test");
|
||||||
|
},
|
||||||
|
listModels: vi.fn().mockResolvedValue([]),
|
||||||
|
isAvailable: vi.fn().mockResolvedValue(true),
|
||||||
|
})),
|
||||||
fetchModels: vi.fn().mockResolvedValue([]),
|
fetchModels: vi.fn().mockResolvedValue([]),
|
||||||
fetchModes: vi.fn().mockResolvedValue([]),
|
fetchModes: vi.fn().mockResolvedValue([]),
|
||||||
...overrides,
|
...overrides,
|
||||||
@@ -913,7 +932,7 @@ describe("create_agent MCP tool", () => {
|
|||||||
describe("create_schedule MCP tool", () => {
|
describe("create_schedule MCP tool", () => {
|
||||||
const logger = createTestLogger();
|
const logger = createTestLogger();
|
||||||
|
|
||||||
it("preserves default new-agent schedule behavior without requiring provider", async () => {
|
it("requires provider for new-agent schedules", async () => {
|
||||||
const { agentManager, agentStorage } = createTestDeps();
|
const { agentManager, agentStorage } = createTestDeps();
|
||||||
const create = vi.fn(async (input: CreateScheduleInput) => createStoredSchedule(input));
|
const create = vi.fn(async (input: CreateScheduleInput) => createStoredSchedule(input));
|
||||||
const server = await createAgentMcpServer({
|
const server = await createAgentMcpServer({
|
||||||
@@ -924,31 +943,14 @@ describe("create_schedule MCP tool", () => {
|
|||||||
});
|
});
|
||||||
const tool = registeredTool(server, "create_schedule");
|
const tool = registeredTool(server, "create_schedule");
|
||||||
|
|
||||||
const response = await tool.callback({
|
await expect(
|
||||||
prompt: "say hello",
|
tool.callback({
|
||||||
every: "5m",
|
|
||||||
name: "Default schedule",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(create).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
prompt: "say hello",
|
prompt: "say hello",
|
||||||
target: {
|
every: "5m",
|
||||||
type: "new-agent",
|
name: "Default schedule",
|
||||||
config: {
|
|
||||||
provider: "claude",
|
|
||||||
cwd: process.cwd(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
).rejects.toThrow("provider is required when target is new-agent");
|
||||||
expect(response.structuredContent.target).toEqual({
|
expect(create).not.toHaveBeenCalled();
|
||||||
type: "new-agent",
|
|
||||||
config: {
|
|
||||||
provider: "claude",
|
|
||||||
cwd: process.cwd(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps create_schedule provider overrides compatible with provider and provider/model forms", async () => {
|
it("keeps create_schedule provider overrides compatible with provider and provider/model forms", async () => {
|
||||||
@@ -1035,11 +1037,13 @@ describe("provider listing MCP tool", () => {
|
|||||||
{
|
{
|
||||||
id: "claude",
|
id: "claude",
|
||||||
label: "Claude",
|
label: "Claude",
|
||||||
|
status: "available",
|
||||||
modes: [{ id: "default", label: "Default", description: "Built-in mode" }],
|
modes: [{ id: "default", label: "Default", description: "Built-in mode" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "zai",
|
id: "zai",
|
||||||
label: "ZAI",
|
label: "ZAI",
|
||||||
|
status: "available",
|
||||||
modes: [{ id: "default", label: "Default", description: "Custom mode" }],
|
modes: [{ id: "default", label: "Default", description: "Custom mode" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { curateAgentActivity } from "./activity-curator.js";
|
import { curateAgentActivity } from "./activity-curator.js";
|
||||||
import type { AgentStorage } from "./agent-storage.js";
|
import type { AgentStorage } from "./agent-storage.js";
|
||||||
import { ensureAgentLoaded } from "./agent-loading.js";
|
import { ensureAgentLoaded } from "./agent-loading.js";
|
||||||
|
import { isStoredAgentProviderAvailable } from "../persistence-hooks.js";
|
||||||
import {
|
import {
|
||||||
appendTimelineItemIfAgentKnown,
|
appendTimelineItemIfAgentKnown,
|
||||||
emitLiveTimelineItemIfAgentKnown,
|
emitLiveTimelineItemIfAgentKnown,
|
||||||
@@ -159,6 +160,15 @@ function resolveAgentListActivityTime(agent: AgentListItemPayload): number {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveRegisteredProviderIds(
|
||||||
|
agentManager: AgentManager,
|
||||||
|
providerRegistry: Record<AgentProvider, ProviderDefinition> | null | undefined,
|
||||||
|
): AgentProvider[] {
|
||||||
|
return providerRegistry
|
||||||
|
? (Object.keys(providerRegistry) as AgentProvider[])
|
||||||
|
: agentManager.getRegisteredProviderIds();
|
||||||
|
}
|
||||||
|
|
||||||
function compareAgentListItems(a: AgentListItemPayload, b: AgentListItemPayload): number {
|
function compareAgentListItems(a: AgentListItemPayload, b: AgentListItemPayload): number {
|
||||||
const attentionDelta =
|
const attentionDelta =
|
||||||
Number(b.requiresAttention ?? false) - Number(a.requiresAttention ?? false);
|
Number(b.requiresAttention ?? false) - Number(a.requiresAttention ?? false);
|
||||||
@@ -289,13 +299,6 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
version: "2.0.0",
|
version: "2.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
const requireProviderRegistry = (): Record<AgentProvider, ProviderDefinition> => {
|
|
||||||
if (!providerRegistry) {
|
|
||||||
throw new Error("Provider registry is required to load stored agent records");
|
|
||||||
}
|
|
||||||
return providerRegistry;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveCallerAgent = () => {
|
const resolveCallerAgent = () => {
|
||||||
if (!callerAgentId) {
|
if (!callerAgentId) {
|
||||||
return null;
|
return null;
|
||||||
@@ -389,6 +392,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resolveNewAgentScheduleTarget = (params?: { provider?: string; cwd?: string }) => {
|
const resolveNewAgentScheduleTarget = (params?: { provider?: string; cwd?: string }) => {
|
||||||
|
if (!params?.provider?.trim()) {
|
||||||
|
throw new Error("provider is required when target is new-agent");
|
||||||
|
}
|
||||||
|
|
||||||
const callerAgent = resolveCallerAgent();
|
const callerAgent = resolveCallerAgent();
|
||||||
if (callerAgent) {
|
if (callerAgent) {
|
||||||
return {
|
return {
|
||||||
@@ -399,7 +406,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
|
|
||||||
const resolvedProviderModel = resolveScheduleProviderAndModel({
|
const resolvedProviderModel = resolveScheduleProviderAndModel({
|
||||||
provider: params?.provider,
|
provider: params?.provider,
|
||||||
defaultProvider: "claude",
|
defaultProvider: params.provider as AgentProvider,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
type: "new-agent" as const,
|
type: "new-agent" as const,
|
||||||
@@ -675,7 +682,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
{
|
{
|
||||||
title: "Create agent",
|
title: "Create agent",
|
||||||
description:
|
description:
|
||||||
"Create an agent tied to a working directory. Requires provider/model, for example codex/gpt-5.4. Optionally run an initial prompt immediately or create a git worktree for the agent.",
|
"Create an agent tied to a working directory. Requires provider/model, for example codex/gpt-5.4. Do not guess; call list_providers and list_models first if uncertain. Optionally run an initial prompt immediately or create a git worktree for the agent.",
|
||||||
inputSchema: createAgentInputSchema,
|
inputSchema: createAgentInputSchema,
|
||||||
outputSchema: {
|
outputSchema: {
|
||||||
agentId: z.string(),
|
agentId: z.string(),
|
||||||
@@ -1052,8 +1059,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
|
|
||||||
const structuredSnapshot = buildStoredAgentPayload(
|
const structuredSnapshot = buildStoredAgentPayload(
|
||||||
record,
|
record,
|
||||||
requireProviderRegistry(),
|
resolveRegisteredProviderIds(agentManager, providerRegistry),
|
||||||
childLogger,
|
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [],
|
content: [],
|
||||||
@@ -1100,10 +1106,15 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
);
|
);
|
||||||
const liveIds = new Set(liveSnapshots.map((snapshot) => snapshot.id));
|
const liveIds = new Set(liveSnapshots.map((snapshot) => snapshot.id));
|
||||||
const storedRecords = await agentStorage.list();
|
const storedRecords = await agentStorage.list();
|
||||||
|
const registeredProviderIds = resolveRegisteredProviderIds(agentManager, providerRegistry);
|
||||||
const storedAgents = storedRecords
|
const storedAgents = storedRecords
|
||||||
.filter((record) => !record.internal && !liveIds.has(record.id))
|
.filter((record) => !record.internal && !liveIds.has(record.id))
|
||||||
.filter((record) => includeArchived || !record.archivedAt)
|
.filter((record) => includeArchived || !record.archivedAt)
|
||||||
.map((record) => buildStoredAgentPayload(record, requireProviderRegistry(), childLogger));
|
.filter(
|
||||||
|
(record) =>
|
||||||
|
includeArchived || isStoredAgentProviderAvailable(record, registeredProviderIds),
|
||||||
|
)
|
||||||
|
.map((record) => buildStoredAgentPayload(record, registeredProviderIds));
|
||||||
const agents = [...liveAgents, ...storedAgents]
|
const agents = [...liveAgents, ...storedAgents]
|
||||||
.map(toAgentListItemPayload)
|
.map(toAgentListItemPayload)
|
||||||
.filter((agent) => !requestedCwd || isSameOrDescendantPath(requestedCwd, agent.cwd))
|
.filter((agent) => !requestedCwd || isSameOrDescendantPath(requestedCwd, agent.cwd))
|
||||||
@@ -1631,26 +1642,49 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
|||||||
"list_providers",
|
"list_providers",
|
||||||
{
|
{
|
||||||
title: "List providers",
|
title: "List providers",
|
||||||
description: "List available agent providers and their modes.",
|
description: "List configured agent providers, availability, and their modes.",
|
||||||
inputSchema: {},
|
inputSchema: {},
|
||||||
outputSchema: {
|
outputSchema: {
|
||||||
providers: z.array(ProviderSummarySchema),
|
providers: z.array(ProviderSummarySchema),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async () => ({
|
async () => {
|
||||||
content: [],
|
const providers = await Promise.all(
|
||||||
structuredContent: ensureValidJson({
|
Object.values(providerRegistry ?? {}).map(async (provider) => {
|
||||||
providers: Object.values(providerRegistry ?? {}).map((provider) => ({
|
let status = "unknown";
|
||||||
id: provider.id,
|
let error: string | undefined;
|
||||||
label: provider.label,
|
try {
|
||||||
modes: provider.modes.map((mode) => ({
|
status = (await provider.createClient(childLogger).isAvailable())
|
||||||
id: mode.id,
|
? "available"
|
||||||
label: mode.label,
|
: "unavailable";
|
||||||
...(mode.description ? { description: mode.description } : {}),
|
} catch (availabilityError) {
|
||||||
})),
|
status = "unavailable";
|
||||||
})),
|
error =
|
||||||
}),
|
availabilityError instanceof Error
|
||||||
}),
|
? availabilityError.message
|
||||||
|
: String(availabilityError);
|
||||||
|
}
|
||||||
|
const summary = {
|
||||||
|
id: provider.id,
|
||||||
|
label: provider.label,
|
||||||
|
status,
|
||||||
|
modes: provider.modes.map((mode) => ({
|
||||||
|
id: mode.id,
|
||||||
|
label: mode.label,
|
||||||
|
...(mode.description ? { description: mode.description } : {}),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
if (error) {
|
||||||
|
return Object.assign(summary, { error });
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [],
|
||||||
|
structuredContent: ensureValidJson({ providers }),
|
||||||
|
};
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
|
|||||||
@@ -1047,6 +1047,49 @@ describe("ProviderSnapshotManager", () => {
|
|||||||
manager.destroy();
|
manager.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("replaceRegistry removes providers that were disabled at runtime", async () => {
|
||||||
|
const { registry } = createRegistry([
|
||||||
|
createMockProvider({
|
||||||
|
provider: "codex",
|
||||||
|
fetchModels: async () => [createModel("codex", "gpt-5.2")],
|
||||||
|
}),
|
||||||
|
createMockProvider({
|
||||||
|
provider: "claude",
|
||||||
|
fetchModels: async () => [createModel("claude", "sonnet")],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const manager = new ProviderSnapshotManager(registry, createTestLogger());
|
||||||
|
const listener = vi.fn<(entries: ProviderSnapshotEntry[], cwd: string) => void>();
|
||||||
|
manager.on("change", listener);
|
||||||
|
|
||||||
|
manager.getSnapshot(projectCwd);
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(getProviderEntry(manager.getSnapshot(projectCwd), "claude")?.status).toBe("ready");
|
||||||
|
expect(getProviderEntry(manager.getSnapshot(projectCwd), "codex")?.status).toBe("ready");
|
||||||
|
});
|
||||||
|
|
||||||
|
const { registry: nextRegistry } = createRegistry([
|
||||||
|
createMockProvider({
|
||||||
|
provider: "codex",
|
||||||
|
fetchModels: async () => [createModel("codex", "gpt-5.2")],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
manager.replaceRegistry(nextRegistry);
|
||||||
|
|
||||||
|
expect(manager.getSnapshot(projectCwd).map((entry) => entry.provider)).toEqual(["codex"]);
|
||||||
|
expect(listener).toHaveBeenCalledWith(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: "codex",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
homedir(),
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
test("snapshot includes user-defined providers from the registry", async () => {
|
test("snapshot includes user-defined providers from the registry", async () => {
|
||||||
const { registry } = createRegistry([
|
const { registry } = createRegistry([
|
||||||
createMockProvider({ provider: "claude" }),
|
createMockProvider({ provider: "claude" }),
|
||||||
|
|||||||
@@ -39,12 +39,14 @@ export class ProviderSnapshotManager {
|
|||||||
private readonly ttlMs: number;
|
private readonly ttlMs: number;
|
||||||
private readonly refreshTimeoutMs: number;
|
private readonly refreshTimeoutMs: number;
|
||||||
private readonly now: () => number;
|
private readonly now: () => number;
|
||||||
|
private providerRegistry: Record<AgentProvider, ProviderDefinition>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly providerRegistry: Record<AgentProvider, ProviderDefinition>,
|
providerRegistry: Record<AgentProvider, ProviderDefinition>,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
options: ProviderSnapshotManagerOptions = {},
|
options: ProviderSnapshotManagerOptions = {},
|
||||||
) {
|
) {
|
||||||
|
this.providerRegistry = providerRegistry;
|
||||||
this.ttlMs = options.ttlMs ?? DEFAULT_SNAPSHOT_TTL_MS;
|
this.ttlMs = options.ttlMs ?? DEFAULT_SNAPSHOT_TTL_MS;
|
||||||
this.refreshTimeoutMs = options.refreshTimeoutMs ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
this.refreshTimeoutMs = options.refreshTimeoutMs ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
||||||
this.now = options.now ?? Date.now;
|
this.now = options.now ?? Date.now;
|
||||||
@@ -144,6 +146,18 @@ export class ProviderSnapshotManager {
|
|||||||
this.providerLoads.clear();
|
this.providerLoads.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replaceRegistry(providerRegistry: Record<AgentProvider, ProviderDefinition>): void {
|
||||||
|
this.providerRegistry = providerRegistry;
|
||||||
|
|
||||||
|
for (const cwd of this.snapshots.keys()) {
|
||||||
|
this.providerLoads.delete(cwd);
|
||||||
|
this.lastCheckedAts.delete(cwd);
|
||||||
|
this.snapshots.set(cwd, this.createLoadingEntries());
|
||||||
|
this.emitChange(cwd);
|
||||||
|
void this.warmUp(cwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private createLoadingEntries(): Map<AgentProvider, ProviderSnapshotEntry> {
|
private createLoadingEntries(): Map<AgentProvider, ProviderSnapshotEntry> {
|
||||||
const entries = new Map<AgentProvider, ProviderSnapshotEntry>();
|
const entries = new Map<AgentProvider, ProviderSnapshotEntry>();
|
||||||
for (const provider of this.getProviderIds()) {
|
for (const provider of this.getProviderIds()) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test, vi } from "vitest";
|
import { describe, expect, test, vi } from "vitest";
|
||||||
import type { PromptResponse, SessionUpdate } from "@agentclientprotocol/sdk";
|
import type { PromptResponse, SessionConfigOption, SessionUpdate } from "@agentclientprotocol/sdk";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ACPAgentClient,
|
ACPAgentClient,
|
||||||
@@ -21,6 +21,18 @@ interface ACPSessionInternals {
|
|||||||
translateSessionUpdate(update: SessionUpdate): unknown;
|
translateSessionUpdate(update: SessionUpdate): unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ACPModelSelectionInternals {
|
||||||
|
sessionId: string | null;
|
||||||
|
connection: {
|
||||||
|
setSessionConfigOption: (input: {
|
||||||
|
sessionId: string;
|
||||||
|
configId: string;
|
||||||
|
value: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
};
|
||||||
|
configOptions: SessionConfigOption[];
|
||||||
|
}
|
||||||
|
|
||||||
function createSession(): ACPAgentSession {
|
function createSession(): ACPAgentSession {
|
||||||
return new ACPAgentSession(
|
return new ACPAgentSession(
|
||||||
{
|
{
|
||||||
@@ -44,6 +56,32 @@ function createSession(): ACPAgentSession {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("ACP setModel forwards model ids that are absent from the advertised catalog", async () => {
|
||||||
|
const session = createSession();
|
||||||
|
const setSessionConfigOption = vi.fn(async () => undefined);
|
||||||
|
const internals = session as unknown as ACPModelSelectionInternals;
|
||||||
|
internals.sessionId = "session-1";
|
||||||
|
internals.connection = { setSessionConfigOption };
|
||||||
|
internals.configOptions = [
|
||||||
|
{
|
||||||
|
id: "model-option",
|
||||||
|
name: "Model",
|
||||||
|
category: "model",
|
||||||
|
type: "select",
|
||||||
|
currentValue: "sonnet",
|
||||||
|
options: [{ value: "sonnet", name: "Sonnet" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await session.setModel("new-provider-model");
|
||||||
|
|
||||||
|
expect(setSessionConfigOption).toHaveBeenCalledWith({
|
||||||
|
sessionId: "session-1",
|
||||||
|
configId: "model-option",
|
||||||
|
value: "new-provider-model",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("createLoggedNdJsonStream", () => {
|
describe("createLoggedNdJsonStream", () => {
|
||||||
test("routes malformed ACP stdout through the provider logger instead of console.error", async () => {
|
test("routes malformed ACP stdout through the provider logger instead of console.error", async () => {
|
||||||
const input = new TransformStream<Uint8Array, Uint8Array>();
|
const input = new TransformStream<Uint8Array, Uint8Array>();
|
||||||
@@ -71,6 +109,10 @@ describe("createLoggedNdJsonStream", () => {
|
|||||||
expect(parsed.value).toEqual({ jsonrpc: "2.0", method: "ok", params: {} });
|
expect(parsed.value).toEqual({ jsonrpc: "2.0", method: "ok", params: {} });
|
||||||
expect(logger.warn).toHaveBeenCalledWith(
|
expect(logger.warn).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
err: {
|
||||||
|
type: "SyntaxError",
|
||||||
|
message: "ACP stdout line was not valid JSON",
|
||||||
|
},
|
||||||
provider: "gemini",
|
provider: "gemini",
|
||||||
}),
|
}),
|
||||||
"ACP agent emitted non-JSON stdout; ignoring line",
|
"ACP agent emitted non-JSON stdout; ignoring line",
|
||||||
@@ -82,6 +124,39 @@ describe("createLoggedNdJsonStream", () => {
|
|||||||
reader.releaseLock();
|
reader.releaseLock();
|
||||||
consoleError.mockRestore();
|
consoleError.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("does not log terminal control sequences from malformed ACP stdout", async () => {
|
||||||
|
const input = new TransformStream<Uint8Array, Uint8Array>();
|
||||||
|
const output = new TransformStream<Uint8Array, Uint8Array>();
|
||||||
|
const logger = {
|
||||||
|
warn: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = createLoggedNdJsonStream(output.writable, input.readable, {
|
||||||
|
logger: logger as unknown as ReturnType<typeof createTestLogger>,
|
||||||
|
provider: "gemini",
|
||||||
|
});
|
||||||
|
const reader = stream.readable.getReader();
|
||||||
|
const writer = input.writable.getWriter();
|
||||||
|
|
||||||
|
await writer.write(new TextEncoder().encode('\u001b[1G\u001b[0JEn\n{"ok":true}\n'));
|
||||||
|
|
||||||
|
const parsed = await reader.read();
|
||||||
|
|
||||||
|
expect(parsed.value).toEqual({ ok: true });
|
||||||
|
expect(JSON.stringify(logger.warn.mock.calls)).not.toContain("\u001b");
|
||||||
|
expect(JSON.stringify(logger.warn.mock.calls)).not.toContain("[1G");
|
||||||
|
expect(logger.warn.mock.calls[0]?.[0]).toEqual({
|
||||||
|
err: {
|
||||||
|
type: "SyntaxError",
|
||||||
|
message: "ACP stdout line was not valid JSON",
|
||||||
|
},
|
||||||
|
provider: "gemini",
|
||||||
|
});
|
||||||
|
|
||||||
|
await writer.close();
|
||||||
|
reader.releaseLock();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mapACPUsage", () => {
|
describe("mapACPUsage", () => {
|
||||||
|
|||||||
@@ -113,6 +113,13 @@ const COPILOT_AUTOPILOT_MODE = "https://agentclientprotocol.com/protocol/session
|
|||||||
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
|
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
|
||||||
const PROBE_ENV: Record<string, string> = { NO_BROWSER: "true" };
|
const PROBE_ENV: Record<string, string> = { NO_BROWSER: "true" };
|
||||||
|
|
||||||
|
function summarizeMalformedACPStdoutError(error: unknown): { type: string; message: string } {
|
||||||
|
return {
|
||||||
|
type: error instanceof Error ? error.name : typeof error,
|
||||||
|
message: "ACP stdout line was not valid JSON",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createLoggedNdJsonStream(
|
export function createLoggedNdJsonStream(
|
||||||
output: WritableStream<Uint8Array>,
|
output: WritableStream<Uint8Array>,
|
||||||
input: ReadableStream<Uint8Array>,
|
input: ReadableStream<Uint8Array>,
|
||||||
@@ -151,7 +158,7 @@ export function createLoggedNdJsonStream(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
options.logger.warn(
|
options.logger.warn(
|
||||||
{
|
{
|
||||||
err: error,
|
err: summarizeMalformedACPStdoutError(error),
|
||||||
provider: options.provider,
|
provider: options.provider,
|
||||||
},
|
},
|
||||||
"ACP agent emitted non-JSON stdout; ignoring line",
|
"ACP agent emitted non-JSON stdout; ignoring line",
|
||||||
@@ -696,7 +703,6 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
private sessionId: string | null = null;
|
private sessionId: string | null = null;
|
||||||
private currentMode: string | null = null;
|
private currentMode: string | null = null;
|
||||||
private availableModes: AgentMode[];
|
private availableModes: AgentMode[];
|
||||||
private availableModels: AgentModelDefinition[] = [];
|
|
||||||
private currentModel: string | null = null;
|
private currentModel: string | null = null;
|
||||||
private thinkingOptionId: string | null = null;
|
private thinkingOptionId: string | null = null;
|
||||||
private currentTitle: string | null = null;
|
private currentTitle: string | null = null;
|
||||||
@@ -1065,11 +1071,6 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelExists = this.availableModels.some((model) => model.id === modelId);
|
|
||||||
if (!modelExists && this.availableModels.length > 0) {
|
|
||||||
throw new Error(`Unknown ${this.provider} model '${modelId}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("unstable_setSessionModel" in this.connection) {
|
if ("unstable_setSessionModel" in this.connection) {
|
||||||
try {
|
try {
|
||||||
await this.connection.unstable_setSessionModel({
|
await this.connection.unstable_setSessionModel({
|
||||||
@@ -1467,7 +1468,6 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
|
|
||||||
const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, this.configOptions);
|
const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, this.configOptions);
|
||||||
this.availableModes = modeInfo.modes;
|
this.availableModes = modeInfo.modes;
|
||||||
this.availableModels = this.deriveAvailableModels(transformed.models);
|
|
||||||
this.currentMode = modeInfo.currentModeId ?? this.currentMode;
|
this.currentMode = modeInfo.currentModeId ?? this.currentMode;
|
||||||
|
|
||||||
this.currentModel =
|
this.currentModel =
|
||||||
@@ -1598,24 +1598,12 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
|||||||
this.configOptions = update.configOptions;
|
this.configOptions = update.configOptions;
|
||||||
const modeInfo = deriveModesFromACP(this.defaultModes, null, this.configOptions);
|
const modeInfo = deriveModesFromACP(this.defaultModes, null, this.configOptions);
|
||||||
this.availableModes = modeInfo.modes;
|
this.availableModes = modeInfo.modes;
|
||||||
this.availableModels = this.deriveAvailableModels(null);
|
|
||||||
this.currentMode = modeInfo.currentModeId ?? this.currentMode;
|
this.currentMode = modeInfo.currentModeId ?? this.currentMode;
|
||||||
this.currentModel = deriveCurrentConfigValue(this.configOptions, "model") ?? this.currentModel;
|
this.currentModel = deriveCurrentConfigValue(this.configOptions, "model") ?? this.currentModel;
|
||||||
this.thinkingOptionId =
|
this.thinkingOptionId =
|
||||||
deriveCurrentConfigValue(this.configOptions, "thought_level") ?? this.thinkingOptionId;
|
deriveCurrentConfigValue(this.configOptions, "thought_level") ?? this.thinkingOptionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private deriveAvailableModels(
|
|
||||||
models: SessionModelState | null | undefined,
|
|
||||||
): AgentModelDefinition[] {
|
|
||||||
const availableModels = deriveModelDefinitionsFromACP(
|
|
||||||
this.provider,
|
|
||||||
models,
|
|
||||||
this.configOptions,
|
|
||||||
);
|
|
||||||
return this.modelTransformer ? this.modelTransformer(availableModels) : availableModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleSessionInfoUpdate(update: SessionInfoUpdate): void {
|
private handleSessionInfoUpdate(update: SessionInfoUpdate): void {
|
||||||
if ("title" in update) {
|
if ("title" in update) {
|
||||||
this.currentTitle = update.title ?? null;
|
this.currentTitle = update.title ?? null;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ function createPiSession(prompt: () => Promise<void>): PiDirectSessionAdapter {
|
|||||||
abort: vi.fn(),
|
abort: vi.fn(),
|
||||||
dispose: vi.fn(),
|
dispose: vi.fn(),
|
||||||
getSessionStats: vi.fn(() => ({})),
|
getSessionStats: vi.fn(() => ({})),
|
||||||
|
setModel: vi.fn(),
|
||||||
setThinkingLevel: vi.fn(),
|
setThinkingLevel: vi.fn(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,13 @@ function createPiModel(provider: string, id: string): Model<Api> {
|
|||||||
provider,
|
provider,
|
||||||
id,
|
id,
|
||||||
name: id,
|
name: id,
|
||||||
|
api: "openai-completions",
|
||||||
|
baseUrl: "https://example.invalid/v1",
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 200000,
|
||||||
|
maxTokens: 8192,
|
||||||
} as Model<Api>;
|
} as Model<Api>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +83,37 @@ describe("PiDirectAgentSession", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("setModel creates a minimal model for new ids under a known provider", async () => {
|
||||||
|
const sdkSession = createPiSession(async () => undefined);
|
||||||
|
const session = new PiDirectAgentSession(
|
||||||
|
sdkSession,
|
||||||
|
{
|
||||||
|
find: vi.fn(() => undefined),
|
||||||
|
getAll: vi.fn(() => [createPiModel("openrouter", "known-model")]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "pi",
|
||||||
|
cwd: "/tmp/paseo-pi-test",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await session.setModel("openrouter/blabal");
|
||||||
|
|
||||||
|
expect(sdkSession.setModel).toHaveBeenCalledWith({
|
||||||
|
id: "blabal",
|
||||||
|
name: "blabal",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://example.invalid/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 16384,
|
||||||
|
compat: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("PiDirectAgentClient", () => {
|
describe("PiDirectAgentClient", () => {
|
||||||
|
|||||||
@@ -787,7 +787,13 @@ function findModelInRegistry(
|
|||||||
parsedReference: PiModelReference,
|
parsedReference: PiModelReference,
|
||||||
): Model<Api> | undefined {
|
): Model<Api> | undefined {
|
||||||
if (parsedReference.provider) {
|
if (parsedReference.provider) {
|
||||||
return registry.find(parsedReference.provider, parsedReference.id);
|
return (
|
||||||
|
registry.find(parsedReference.provider, parsedReference.id) ??
|
||||||
|
createProviderModelFallback(registry, {
|
||||||
|
provider: parsedReference.provider,
|
||||||
|
id: parsedReference.id,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return registry.getAll().find((entry) => {
|
return registry.getAll().find((entry) => {
|
||||||
@@ -798,6 +804,32 @@ function findModelInRegistry(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createProviderModelFallback(
|
||||||
|
registry: PiDirectModelRegistry,
|
||||||
|
parsedReference: { provider: string; id: string },
|
||||||
|
): Model<Api> | undefined {
|
||||||
|
const providerDefault = registry
|
||||||
|
.getAll()
|
||||||
|
.find((model) => model.provider === parsedReference.provider);
|
||||||
|
if (!providerDefault) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: parsedReference.id,
|
||||||
|
name: parsedReference.id,
|
||||||
|
api: providerDefault.api,
|
||||||
|
provider: parsedReference.provider,
|
||||||
|
baseUrl: providerDefault.baseUrl,
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 16384,
|
||||||
|
compat: providerDefault.compat,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class PiDirectAgentSession implements AgentSession {
|
export class PiDirectAgentSession implements AgentSession {
|
||||||
readonly provider = PI_PROVIDER;
|
readonly provider = PI_PROVIDER;
|
||||||
readonly capabilities = PI_CAPABILITIES;
|
readonly capabilities = PI_CAPABILITIES;
|
||||||
|
|||||||
@@ -225,6 +225,15 @@ export async function createPaseoDaemon(
|
|||||||
config.paseoHome,
|
config.paseoHome,
|
||||||
{
|
{
|
||||||
mcp: { injectIntoAgents: config.mcpInjectIntoAgents ?? true },
|
mcp: { injectIntoAgents: config.mcpInjectIntoAgents ?? true },
|
||||||
|
providers: Object.fromEntries(
|
||||||
|
Object.entries(config.providerOverrides ?? {}).map(([providerId, override]) => [
|
||||||
|
providerId,
|
||||||
|
{
|
||||||
|
...(override.enabled !== undefined ? { enabled: override.enabled } : {}),
|
||||||
|
...(override.additionalModels ? { additionalModels: override.additionalModels } : {}),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { DaemonConfigStore, applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
|
||||||
|
import { loadPersistedConfig } from "./persisted-config.js";
|
||||||
|
|
||||||
|
describe("applyMutableProviderConfigToOverrides", () => {
|
||||||
|
test("merges mutable provider fields onto provider overrides", () => {
|
||||||
|
expect(
|
||||||
|
applyMutableProviderConfigToOverrides(
|
||||||
|
{
|
||||||
|
gemini: {
|
||||||
|
extends: "acp",
|
||||||
|
label: "Gemini",
|
||||||
|
command: ["gemini", "--acp"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini: { enabled: false },
|
||||||
|
claude: {
|
||||||
|
additionalModels: [
|
||||||
|
{
|
||||||
|
id: "claude-custom",
|
||||||
|
label: "claude-custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
gemini: {
|
||||||
|
extends: "acp",
|
||||||
|
label: "Gemini",
|
||||||
|
command: ["gemini", "--acp"],
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
claude: {
|
||||||
|
additionalModels: [
|
||||||
|
{
|
||||||
|
id: "claude-custom",
|
||||||
|
label: "claude-custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("DaemonConfigStore", () => {
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const dir of tempDirs) {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patch persists provider enabled flags into config.json", () => {
|
||||||
|
const paseoHome = mkdtempSync(path.join(tmpdir(), "paseo-daemon-config-store-"));
|
||||||
|
tempDirs.push(paseoHome);
|
||||||
|
|
||||||
|
const initial = loadPersistedConfig(paseoHome);
|
||||||
|
initial.agents = {
|
||||||
|
providers: {
|
||||||
|
gemini: {
|
||||||
|
extends: "acp",
|
||||||
|
label: "Gemini",
|
||||||
|
command: ["gemini", "--acp"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const configPath = path.join(paseoHome, "config.json");
|
||||||
|
// Reuse the validated serializer through the store path by seeding the file directly.
|
||||||
|
// This keeps the test focused on the merge behavior.
|
||||||
|
const seeded = JSON.stringify(initial, null, 2) + "\n";
|
||||||
|
writeFileSync(configPath, seeded);
|
||||||
|
|
||||||
|
const store = new DaemonConfigStore(
|
||||||
|
paseoHome,
|
||||||
|
{
|
||||||
|
mcp: { injectIntoAgents: false },
|
||||||
|
providers: {},
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
store.patch({
|
||||||
|
providers: {
|
||||||
|
gemini: { enabled: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const persisted = loadPersistedConfig(paseoHome);
|
||||||
|
expect(persisted.agents?.providers?.gemini).toEqual({
|
||||||
|
extends: "acp",
|
||||||
|
label: "Gemini",
|
||||||
|
command: ["gemini", "--acp"],
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patch persists provider additional models into config.json", () => {
|
||||||
|
const paseoHome = mkdtempSync(path.join(tmpdir(), "paseo-daemon-config-store-"));
|
||||||
|
tempDirs.push(paseoHome);
|
||||||
|
|
||||||
|
const store = new DaemonConfigStore(
|
||||||
|
paseoHome,
|
||||||
|
{
|
||||||
|
mcp: { injectIntoAgents: false },
|
||||||
|
providers: {},
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
store.patch({
|
||||||
|
providers: {
|
||||||
|
claude: {
|
||||||
|
additionalModels: [
|
||||||
|
{
|
||||||
|
id: "claude-custom",
|
||||||
|
label: "claude-custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const persisted = loadPersistedConfig(paseoHome);
|
||||||
|
expect(persisted.agents?.providers?.claude).toEqual({
|
||||||
|
additionalModels: [
|
||||||
|
{
|
||||||
|
id: "claude-custom",
|
||||||
|
label: "claude-custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@ export type { MutableDaemonConfig, MutableDaemonConfigPatch } from "../shared/me
|
|||||||
|
|
||||||
type MutableDaemonConfig = import("../shared/messages.js").MutableDaemonConfig;
|
type MutableDaemonConfig = import("../shared/messages.js").MutableDaemonConfig;
|
||||||
type MutableDaemonConfigPatch = import("../shared/messages.js").MutableDaemonConfigPatch;
|
type MutableDaemonConfigPatch = import("../shared/messages.js").MutableDaemonConfigPatch;
|
||||||
|
type ProviderOverride = import("./agent/provider-launch-config.js").ProviderOverride;
|
||||||
|
|
||||||
interface LoggerLike {
|
interface LoggerLike {
|
||||||
child(bindings: Record<string, unknown>): LoggerLike;
|
child(bindings: Record<string, unknown>): LoggerLike;
|
||||||
@@ -57,6 +58,30 @@ function isEqualValue(a: unknown, b: unknown): boolean {
|
|||||||
return JSON.stringify(a) === JSON.stringify(b);
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyMutableProviderConfigToOverrides(
|
||||||
|
baseOverrides: Record<string, ProviderOverride> | undefined,
|
||||||
|
mutableProviders: MutableDaemonConfig["providers"] | undefined,
|
||||||
|
): Record<string, ProviderOverride> | undefined {
|
||||||
|
if (!baseOverrides && (!mutableProviders || Object.keys(mutableProviders).length === 0)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextOverrides: Record<string, ProviderOverride> = { ...baseOverrides };
|
||||||
|
for (const [providerId, providerConfig] of Object.entries(mutableProviders ?? {})) {
|
||||||
|
nextOverrides[providerId] = {
|
||||||
|
...nextOverrides[providerId],
|
||||||
|
};
|
||||||
|
if (providerConfig.enabled !== undefined) {
|
||||||
|
nextOverrides[providerId].enabled = providerConfig.enabled;
|
||||||
|
}
|
||||||
|
if (providerConfig.additionalModels !== undefined) {
|
||||||
|
nextOverrides[providerId].additionalModels = providerConfig.additionalModels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
export class DaemonConfigStore {
|
export class DaemonConfigStore {
|
||||||
private current: MutableDaemonConfig;
|
private current: MutableDaemonConfig;
|
||||||
private readonly paseoHome: string;
|
private readonly paseoHome: string;
|
||||||
@@ -148,6 +173,14 @@ function mergeMutableConfigIntoPersistedConfig(params: {
|
|||||||
mutable: MutableDaemonConfig;
|
mutable: MutableDaemonConfig;
|
||||||
}): PersistedConfig {
|
}): PersistedConfig {
|
||||||
const { persisted, mutable } = params;
|
const { persisted, mutable } = params;
|
||||||
|
const providerOverrides = applyMutableProviderConfigToOverrides(
|
||||||
|
persisted.agents?.providers as Record<string, ProviderOverride> | undefined,
|
||||||
|
mutable.providers,
|
||||||
|
);
|
||||||
|
const persistedAgents = persisted.agents as
|
||||||
|
| ({ providers?: Record<string, ProviderOverride> } & Record<string, unknown>)
|
||||||
|
| undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...persisted,
|
...persisted,
|
||||||
daemon: {
|
daemon: {
|
||||||
@@ -157,5 +190,12 @@ function mergeMutableConfigIntoPersistedConfig(params: {
|
|||||||
injectIntoAgents: mutable.mcp.injectIntoAgents,
|
injectIntoAgents: mutable.mcp.injectIntoAgents,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
agents:
|
||||||
|
providerOverrides && Object.keys(providerOverrides).length > 0
|
||||||
|
? {
|
||||||
|
...persistedAgents,
|
||||||
|
providers: providerOverrides,
|
||||||
|
}
|
||||||
|
: persisted.agents,
|
||||||
|
} as PersistedConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import type { Logger } from "pino";
|
import { describe, expect, test } from "vitest";
|
||||||
import { describe, expect, test, vi } from "vitest";
|
|
||||||
import type { StoredAgentRecord } from "./agent/agent-storage.js";
|
import type { StoredAgentRecord } from "./agent/agent-storage.js";
|
||||||
import { buildConfigOverrides, buildSessionConfig } from "./persistence-hooks.js";
|
import {
|
||||||
|
buildConfigOverrides,
|
||||||
const testLogger = {
|
buildSessionConfig,
|
||||||
child: () => testLogger,
|
toAgentPersistenceHandle,
|
||||||
error: vi.fn(),
|
} from "./persistence-hooks.js";
|
||||||
warn: vi.fn(),
|
|
||||||
} as unknown as Logger;
|
|
||||||
|
|
||||||
function createRecord(overrides?: Partial<StoredAgentRecord>): StoredAgentRecord {
|
function createRecord(overrides?: Partial<StoredAgentRecord>): StoredAgentRecord {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
@@ -127,12 +124,16 @@ describe("persistence hooks", () => {
|
|||||||
expect(
|
expect(
|
||||||
buildSessionConfig(record, {
|
buildSessionConfig(record, {
|
||||||
validProviders: ["claude", "codex"],
|
validProviders: ["claude", "codex"],
|
||||||
logger: testLogger,
|
|
||||||
}),
|
}),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
expect(testLogger.warn).toHaveBeenCalledWith(
|
});
|
||||||
{ agentId: "agent-missing-provider", provider: "zai" },
|
|
||||||
"Skipping persisted agent with unknown provider 'zai'",
|
test("toAgentPersistenceHandle rejects handles for unavailable providers", () => {
|
||||||
);
|
const handle = toAgentPersistenceHandle(["claude", "codex"], {
|
||||||
|
provider: "gemini",
|
||||||
|
sessionId: "session-123",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(handle).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ interface LoggerLike {
|
|||||||
warn(...args: unknown[]): void;
|
warn(...args: unknown[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_AGENT_PROVIDER = "claude";
|
|
||||||
|
|
||||||
function getLogger(logger: LoggerLike): LoggerLike {
|
function getLogger(logger: LoggerLike): LoggerLike {
|
||||||
return logger.child({ module: "persistence" });
|
return logger.child({ module: "persistence" });
|
||||||
}
|
}
|
||||||
@@ -24,7 +22,6 @@ type AgentManagerStateSource = Pick<AgentManager, "subscribe">;
|
|||||||
|
|
||||||
interface BuildSessionConfigOptions {
|
interface BuildSessionConfigOptions {
|
||||||
validProviders?: Iterable<AgentProvider>;
|
validProviders?: Iterable<AgentProvider>;
|
||||||
logger?: LoggerLike;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisteredProviders = ReturnType<typeof buildProviderRegistry> | Iterable<AgentProvider>;
|
type RegisteredProviders = ReturnType<typeof buildProviderRegistry> | Iterable<AgentProvider>;
|
||||||
@@ -85,10 +82,6 @@ export function buildSessionConfig(
|
|||||||
const validProviders = options?.validProviders;
|
const validProviders = options?.validProviders;
|
||||||
const isValidProvider = validProviders ? new Set(validProviders).has(record.provider) : true;
|
const isValidProvider = validProviders ? new Set(validProviders).has(record.provider) : true;
|
||||||
if (!isValidProvider) {
|
if (!isValidProvider) {
|
||||||
options?.logger?.warn(
|
|
||||||
{ agentId: record.id, provider: record.provider },
|
|
||||||
`Skipping persisted agent with unknown provider '${record.provider}'`,
|
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const overrides = buildConfigOverrides(record);
|
const overrides = buildConfigOverrides(record);
|
||||||
@@ -106,6 +99,13 @@ export function buildSessionConfig(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isStoredAgentProviderAvailable(
|
||||||
|
record: StoredAgentRecord,
|
||||||
|
validProviders?: Iterable<AgentProvider>,
|
||||||
|
): boolean {
|
||||||
|
return buildSessionConfig(record, { validProviders }) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
export function extractTimestamps(record: StoredAgentRecord): {
|
export function extractTimestamps(record: StoredAgentRecord): {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
@@ -134,24 +134,7 @@ export function isRegisteredProvider(
|
|||||||
return hasRegisteredProvider(providerRegistry, value);
|
return hasRegisteredProvider(providerRegistry, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function coerceAgentProvider(
|
|
||||||
logger: LoggerLike,
|
|
||||||
providerRegistry: ReturnType<typeof buildProviderRegistry>,
|
|
||||||
value: string,
|
|
||||||
agentId?: string,
|
|
||||||
): AgentProvider {
|
|
||||||
if (isRegisteredProvider(providerRegistry, value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
logger.warn(
|
|
||||||
{ value, agentId, defaultProvider: DEFAULT_AGENT_PROVIDER },
|
|
||||||
`Unknown provider '${value}' for agent ${agentId ?? "unknown"}; defaulting to '${DEFAULT_AGENT_PROVIDER}'`,
|
|
||||||
);
|
|
||||||
return DEFAULT_AGENT_PROVIDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toAgentPersistenceHandle(
|
export function toAgentPersistenceHandle(
|
||||||
logger: LoggerLike,
|
|
||||||
registeredProviders: RegisteredProviders,
|
registeredProviders: RegisteredProviders,
|
||||||
handle: StoredAgentRecord["persistence"],
|
handle: StoredAgentRecord["persistence"],
|
||||||
): AgentPersistenceHandle | null {
|
): AgentPersistenceHandle | null {
|
||||||
@@ -160,11 +143,9 @@ export function toAgentPersistenceHandle(
|
|||||||
}
|
}
|
||||||
const provider = handle.provider;
|
const provider = handle.provider;
|
||||||
if (!hasRegisteredProvider(registeredProviders, provider)) {
|
if (!hasRegisteredProvider(registeredProviders, provider)) {
|
||||||
logger.warn({ provider }, `Ignoring persistence handle with unknown provider '${provider}'`);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!handle.sessionId) {
|
if (!handle.sessionId) {
|
||||||
logger.warn("Ignoring persistence handle missing sessionId");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -221,7 +221,13 @@ function createSessionForTest(options?: {
|
|||||||
checkoutDiffManager: checkoutDiffManager as unknown as SessionOptions["checkoutDiffManager"],
|
checkoutDiffManager: checkoutDiffManager as unknown as SessionOptions["checkoutDiffManager"],
|
||||||
github: github as unknown as SessionOptions["github"],
|
github: github as unknown as SessionOptions["github"],
|
||||||
workspaceGitService: workspaceGitService as unknown as SessionOptions["workspaceGitService"],
|
workspaceGitService: workspaceGitService as unknown as SessionOptions["workspaceGitService"],
|
||||||
daemonConfigStore: {} as unknown as SessionOptions["daemonConfigStore"],
|
daemonConfigStore: {
|
||||||
|
get: vi.fn(() => ({
|
||||||
|
mcp: { injectIntoAgents: false },
|
||||||
|
providers: {},
|
||||||
|
})),
|
||||||
|
onChange: vi.fn(() => () => {}),
|
||||||
|
} as unknown as SessionOptions["daemonConfigStore"],
|
||||||
stt: null,
|
stt: null,
|
||||||
tts: null,
|
tts: null,
|
||||||
terminalManager: (options?.terminalManager ?? null) as SessionOptions["terminalManager"],
|
terminalManager: (options?.terminalManager ?? null) as SessionOptions["terminalManager"],
|
||||||
@@ -357,12 +363,12 @@ describe("session provider refresh cwd routing", () => {
|
|||||||
const session = createSessionForTest({ messages });
|
const session = createSessionForTest({ messages });
|
||||||
const fetchModels = vi.fn(async () => []);
|
const fetchModels = vi.fn(async () => []);
|
||||||
const fetchModes = vi.fn(async () => []);
|
const fetchModes = vi.fn(async () => []);
|
||||||
(session as unknown as { providerRegistry: unknown }).providerRegistry = {
|
(session as unknown as { getProviderRegistry: () => unknown }).getProviderRegistry = () => ({
|
||||||
codex: {
|
codex: {
|
||||||
fetchModels,
|
fetchModels,
|
||||||
fetchModes,
|
fetchModes,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
await session.handleMessage({
|
await session.handleMessage({
|
||||||
type: "list_provider_models_request",
|
type: "list_provider_models_request",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
buildConfigOverrides,
|
buildConfigOverrides,
|
||||||
extractTimestamps,
|
extractTimestamps,
|
||||||
|
isStoredAgentProviderAvailable,
|
||||||
toAgentPersistenceHandle,
|
toAgentPersistenceHandle,
|
||||||
} from "./persistence-hooks.js";
|
} from "./persistence-hooks.js";
|
||||||
import { ensureAgentLoaded } from "./agent/agent-loading.js";
|
import { ensureAgentLoaded } from "./agent/agent-loading.js";
|
||||||
@@ -81,6 +82,7 @@ import type { ScriptHealthState } from "./script-health-monitor.js";
|
|||||||
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
||||||
import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
||||||
import type { DaemonConfigStore } from "./daemon-config-store.js";
|
import type { DaemonConfigStore } from "./daemon-config-store.js";
|
||||||
|
import { applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
|
||||||
import type { WorkspaceGitRuntimeSnapshot, WorkspaceGitService } from "./workspace-git-service.js";
|
import type { WorkspaceGitRuntimeSnapshot, WorkspaceGitService } from "./workspace-git-service.js";
|
||||||
|
|
||||||
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
||||||
@@ -801,7 +803,6 @@ export class Session {
|
|||||||
private readonly mcpBaseUrl: string | null;
|
private readonly mcpBaseUrl: string | null;
|
||||||
private readonly downloadTokenStore: DownloadTokenStore;
|
private readonly downloadTokenStore: DownloadTokenStore;
|
||||||
private readonly pushTokenStore: PushTokenStore;
|
private readonly pushTokenStore: PushTokenStore;
|
||||||
private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>;
|
|
||||||
private unsubscribeAgentEvents: (() => void) | null = null;
|
private unsubscribeAgentEvents: (() => void) | null = null;
|
||||||
private agentUpdatesSubscription: AgentUpdatesSubscriptionState | null = null;
|
private agentUpdatesSubscription: AgentUpdatesSubscriptionState | null = null;
|
||||||
private workspaceUpdatesSubscription: WorkspaceUpdatesSubscriptionState | null = null;
|
private workspaceUpdatesSubscription: WorkspaceUpdatesSubscriptionState | null = null;
|
||||||
@@ -947,12 +948,6 @@ export class Session {
|
|||||||
this.providerOverrides = providerOverrides;
|
this.providerOverrides = providerOverrides;
|
||||||
this.isDev = isDev === true;
|
this.isDev = isDev === true;
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
this.providerRegistry = buildProviderRegistry(this.sessionLogger, {
|
|
||||||
runtimeSettings: this.agentProviderRuntimeSettings,
|
|
||||||
providerOverrides: this.providerOverrides,
|
|
||||||
workspaceGitService: this.workspaceGitService,
|
|
||||||
isDev: this.isDev,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.initializePerSessionManagers({ tts, stt, dictation });
|
this.initializePerSessionManagers({ tts, stt, dictation });
|
||||||
|
|
||||||
@@ -1360,8 +1355,27 @@ export class Session {
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildStoredAgentPayload(record: StoredAgentRecord): AgentSnapshotPayload {
|
private getProviderRegistry(): ReturnType<typeof buildProviderRegistry> {
|
||||||
return buildStoredAgentPayload(record, this.providerRegistry, this.sessionLogger);
|
return buildProviderRegistry(this.sessionLogger, {
|
||||||
|
runtimeSettings: this.agentProviderRuntimeSettings,
|
||||||
|
providerOverrides: applyMutableProviderConfigToOverrides(
|
||||||
|
this.providerOverrides,
|
||||||
|
this.daemonConfigStore.get().providers,
|
||||||
|
),
|
||||||
|
workspaceGitService: this.workspaceGitService,
|
||||||
|
isDev: this.isDev,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRegisteredProviderIds(): AgentProvider[] {
|
||||||
|
return Object.keys(this.getProviderRegistry()) as AgentProvider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildStoredAgentPayload(
|
||||||
|
record: StoredAgentRecord,
|
||||||
|
registeredProviderIds = this.getRegisteredProviderIds(),
|
||||||
|
): AgentSnapshotPayload {
|
||||||
|
return buildStoredAgentPayload(record, registeredProviderIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isProviderVisibleToClient(provider: string): boolean {
|
private isProviderVisibleToClient(provider: string): boolean {
|
||||||
@@ -3082,11 +3096,13 @@ export class Session {
|
|||||||
if (!record) {
|
if (!record) {
|
||||||
throw new Error(`Agent not found: ${agentId}`);
|
throw new Error(`Agent not found: ${agentId}`);
|
||||||
}
|
}
|
||||||
const handle = toAgentPersistenceHandle(
|
const providerRegistry = this.getProviderRegistry();
|
||||||
this.sessionLogger,
|
if (
|
||||||
this.providerRegistry,
|
!isStoredAgentProviderAvailable(record, Object.keys(providerRegistry) as AgentProvider[])
|
||||||
record.persistence,
|
) {
|
||||||
);
|
throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
|
||||||
|
}
|
||||||
|
const handle = toAgentPersistenceHandle(providerRegistry, record.persistence);
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
throw new Error(`Agent ${agentId} cannot be refreshed because it lacks persistence`);
|
throw new Error(`Agent ${agentId} cannot be refreshed because it lacks persistence`);
|
||||||
}
|
}
|
||||||
@@ -3183,7 +3199,7 @@ export class Session {
|
|||||||
|
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
try {
|
try {
|
||||||
const models = await this.providerRegistry[msg.provider].fetchModels({
|
const models = await this.getProviderRegistry()[msg.provider].fetchModels({
|
||||||
cwd,
|
cwd,
|
||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
@@ -3315,7 +3331,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const modes = await this.providerRegistry[msg.provider].fetchModes({
|
const modes = await this.getProviderRegistry()[msg.provider].fetchModes({
|
||||||
cwd,
|
cwd,
|
||||||
force: false,
|
force: false,
|
||||||
});
|
});
|
||||||
@@ -3497,7 +3513,7 @@ export class Session {
|
|||||||
msg: Extract<SessionInboundMessage, { type: "provider_diagnostic_request" }>,
|
msg: Extract<SessionInboundMessage, { type: "provider_diagnostic_request" }>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const client = this.providerRegistry[msg.provider].createClient(this.sessionLogger);
|
const client = this.getProviderRegistry()[msg.provider].createClient(this.sessionLogger);
|
||||||
const diagnostic = client.getDiagnostic
|
const diagnostic = client.getDiagnostic
|
||||||
? (await client.getDiagnostic()).diagnostic
|
? (await client.getDiagnostic()).diagnostic
|
||||||
: "No diagnostic available for this provider.";
|
: "No diagnostic available for this provider.";
|
||||||
@@ -5334,6 +5350,7 @@ export class Session {
|
|||||||
*/
|
*/
|
||||||
private async listAgentPayloads(filter?: {
|
private async listAgentPayloads(filter?: {
|
||||||
labels?: Record<string, string>;
|
labels?: Record<string, string>;
|
||||||
|
includeUnavailablePersisted?: boolean;
|
||||||
}): Promise<AgentSnapshotPayload[]> {
|
}): Promise<AgentSnapshotPayload[]> {
|
||||||
// Get live agents with session modes
|
// Get live agents with session modes
|
||||||
const agentSnapshots = this.agentManager.listAgents();
|
const agentSnapshots = this.agentManager.listAgents();
|
||||||
@@ -5345,9 +5362,15 @@ export class Session {
|
|||||||
// (excluding internal agents which are for ephemeral system tasks)
|
// (excluding internal agents which are for ephemeral system tasks)
|
||||||
const registryRecords = await this.agentStorage.list();
|
const registryRecords = await this.agentStorage.list();
|
||||||
const liveIds = new Set(agentSnapshots.map((a) => a.id));
|
const liveIds = new Set(agentSnapshots.map((a) => a.id));
|
||||||
|
const registeredProviderIds = this.getRegisteredProviderIds();
|
||||||
const persistedAgents = registryRecords
|
const persistedAgents = registryRecords
|
||||||
.filter((record) => !liveIds.has(record.id) && !record.internal)
|
.filter((record) => !liveIds.has(record.id) && !record.internal)
|
||||||
.map((record) => this.buildStoredAgentPayload(record));
|
.filter(
|
||||||
|
(record) =>
|
||||||
|
filter?.includeUnavailablePersisted === true ||
|
||||||
|
isStoredAgentProviderAvailable(record, registeredProviderIds),
|
||||||
|
)
|
||||||
|
.map((record) => this.buildStoredAgentPayload(record, registeredProviderIds));
|
||||||
|
|
||||||
let agents = [...liveAgents, ...persistedAgents];
|
let agents = [...liveAgents, ...persistedAgents];
|
||||||
|
|
||||||
@@ -5710,6 +5733,7 @@ export class Session {
|
|||||||
|
|
||||||
let agents = await this.listAgentPayloads({
|
let agents = await this.listAgentPayloads({
|
||||||
labels: filter?.labels,
|
labels: filter?.labels,
|
||||||
|
includeUnavailablePersisted: request.type === "fetch_agent_history_request",
|
||||||
});
|
});
|
||||||
const activePlacementsByCwd =
|
const activePlacementsByCwd =
|
||||||
scope === "active" ? await this.buildActiveProjectPlacementsByWorkspaceCwd() : null;
|
scope === "active" ? await this.buildActiveProjectPlacementsByWorkspaceCwd() : null;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { LoopService } from "./loop-service.js";
|
|||||||
import type { ScheduleService } from "./schedule/service.js";
|
import type { ScheduleService } from "./schedule/service.js";
|
||||||
import type { CheckoutDiffManager, CheckoutDiffMetrics } from "./checkout-diff-manager.js";
|
import type { CheckoutDiffManager, CheckoutDiffMetrics } from "./checkout-diff-manager.js";
|
||||||
import type { DaemonConfigStore, MutableDaemonConfig } from "./daemon-config-store.js";
|
import type { DaemonConfigStore, MutableDaemonConfig } from "./daemon-config-store.js";
|
||||||
|
import { applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
|
||||||
import {
|
import {
|
||||||
type ServerInfoStatusPayload,
|
type ServerInfoStatusPayload,
|
||||||
type SessionOutboundMessage,
|
type SessionOutboundMessage,
|
||||||
@@ -492,6 +493,17 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
this.publishSpeechReadiness(snapshot);
|
this.publishSpeechReadiness(snapshot);
|
||||||
}) ?? null;
|
}) ?? null;
|
||||||
this.unsubscribeDaemonConfigChange = this.daemonConfigStore.onChange((config) => {
|
this.unsubscribeDaemonConfigChange = this.daemonConfigStore.onChange((config) => {
|
||||||
|
this.providerOverrides = applyMutableProviderConfigToOverrides(
|
||||||
|
this.providerOverrides,
|
||||||
|
config.providers,
|
||||||
|
);
|
||||||
|
this.providerSnapshotManager.replaceRegistry(
|
||||||
|
buildProviderRegistry(providerSnapshotLogger, {
|
||||||
|
runtimeSettings: this.agentProviderRuntimeSettings,
|
||||||
|
providerOverrides: this.providerOverrides,
|
||||||
|
isDev: this.isDev,
|
||||||
|
}),
|
||||||
|
);
|
||||||
this.broadcastDaemonConfigChanged(config);
|
this.broadcastDaemonConfigChanged(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,22 @@ import {
|
|||||||
// Mutable daemon config schemas (shared between server store and client)
|
// Mutable daemon config schemas (shared between server store and client)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const MutableDaemonProviderModelSchema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
label: z.string().min(1),
|
||||||
|
description: z.string().optional(),
|
||||||
|
isDefault: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
const MutableDaemonProviderConfigSchema = z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
additionalModels: z.array(MutableDaemonProviderModelSchema).optional(),
|
||||||
|
})
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
export const MutableDaemonConfigSchema = z
|
export const MutableDaemonConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
mcp: z
|
mcp: z
|
||||||
@@ -58,12 +74,16 @@ export const MutableDaemonConfigSchema = z
|
|||||||
injectIntoAgents: z.boolean(),
|
injectIntoAgents: z.boolean(),
|
||||||
})
|
})
|
||||||
.passthrough(),
|
.passthrough(),
|
||||||
|
providers: z.record(z.string(), MutableDaemonProviderConfigSchema).default({}),
|
||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
|
||||||
export const MutableDaemonConfigPatchSchema = z
|
export const MutableDaemonConfigPatchSchema = z
|
||||||
.object({
|
.object({
|
||||||
mcp: MutableDaemonConfigSchema.shape.mcp.partial().optional(),
|
mcp: MutableDaemonConfigSchema.shape.mcp.partial().optional(),
|
||||||
|
providers: z
|
||||||
|
.record(z.string(), MutableDaemonProviderConfigSchema.partial().passthrough())
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.passthrough();
|
.passthrough();
|
||||||
@@ -578,6 +598,7 @@ export const AgentSnapshotPayloadSchema = z.object({
|
|||||||
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
||||||
attentionTimestamp: z.string().nullable().optional(),
|
attentionTimestamp: z.string().nullable().optional(),
|
||||||
archivedAt: z.string().nullable().optional(),
|
archivedAt: z.string().nullable().optional(),
|
||||||
|
providerUnavailable: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AgentSnapshotPayload = z.infer<typeof AgentSnapshotPayloadSchema>;
|
export type AgentSnapshotPayload = z.infer<typeof AgentSnapshotPayloadSchema>;
|
||||||
@@ -600,6 +621,7 @@ export const AgentListItemPayloadSchema = z.object({
|
|||||||
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
||||||
attentionTimestamp: z.string().nullable().optional(),
|
attentionTimestamp: z.string().nullable().optional(),
|
||||||
labels: z.record(z.string(), z.string()).default({}),
|
labels: z.record(z.string(), z.string()).default({}),
|
||||||
|
providerUnavailable: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AgentListItemPayload = z.infer<typeof AgentListItemPayloadSchema>;
|
export type AgentListItemPayload = z.infer<typeof AgentListItemPayloadSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user