Merge remote-tracking branch 'origin/main' into pr-586-r2

# Conflicts:
#	packages/coding-agent/CHANGELOG.md
This commit is contained in:
can1357
2026-04-01 15:33:13 +02:00
55 changed files with 1048 additions and 588 deletions
+6 -1
View File
@@ -63,6 +63,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
cache-workspace-crates: true
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3"
@@ -120,6 +121,8 @@ jobs:
with:
toolchain: nightly-2026-03-27
- uses: Swatinem/rust-cache@v2
with:
cache-workspace-crates: true
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
@@ -156,6 +159,8 @@ jobs:
with:
toolchain: nightly-2026-03-27
- uses: Swatinem/rust-cache@v2
with:
cache-workspace-crates: true
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
@@ -252,7 +257,7 @@ jobs:
env:
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
for pkg in packages/*/; do
for pkg in packages/utils packages/ai packages/natives packages/tui packages/stats packages/agent packages/coding-agent; do
pkg_name=$(basename "$pkg")
# Skip private packages
if jq -e '.private == true' "$pkg/package.json" > /dev/null 2>&1; then
+1
View File
@@ -25,6 +25,7 @@ target/
*~
# OS
*.tmp
.DS_Store
# Logs & profiling
Generated
+43 -31
View File
@@ -291,9 +291,9 @@ dependencies = [
[[package]]
name = "blake3"
version = "1.8.3"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d"
checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e"
dependencies = [
"arrayref",
"arrayvec",
@@ -676,9 +676,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
@@ -1864,9 +1864,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.92"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -2165,9 +2165,9 @@ dependencies = [
[[package]]
name = "neo_frizbee"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "638d4811ea05dee397475493065555838c35ed43c2d7d1bdeb5ff674d1a3bd67"
checksum = "34a8a11f48357cecd5ada0751f1490d8da4dd935e595d34cc2727567d5288ead"
dependencies = [
"itertools 0.14.0",
"raw-cpuid",
@@ -2679,7 +2679,7 @@ dependencies = [
[[package]]
name = "pi-natives"
version = "13.17.0"
version = "13.17.1"
dependencies = [
"arboard",
"ast-grep-core",
@@ -2716,6 +2716,7 @@ dependencies = [
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-c-sharp",
"tree-sitter-clojure",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-diff",
@@ -3829,6 +3830,17 @@ dependencies = [
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-clojure"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4004884cc509449a1d78fa3e1f02b4e953d0a8065984445304795e72e885338c"
dependencies = [
"cc",
"tree-sitter",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-cpp"
version = "0.23.4"
@@ -4390,9 +4402,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.115"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [
"cfg-if",
"once_cell",
@@ -4403,9 +4415,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.115"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4413,9 +4425,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.115"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -4426,18 +4438,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.115"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wayland-backend"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406"
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
dependencies = [
"cc",
"downcast-rs",
@@ -4448,9 +4460,9 @@ dependencies = [
[[package]]
name = "wayland-client"
version = "0.31.13"
version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
dependencies = [
"bitflags 2.11.0",
"rustix",
@@ -4460,9 +4472,9 @@ dependencies = [
[[package]]
name = "wayland-protocols"
version = "0.32.11"
version = "0.32.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7"
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
dependencies = [
"bitflags 2.11.0",
"wayland-backend",
@@ -4472,9 +4484,9 @@ dependencies = [
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.11"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235"
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [
"bitflags 2.11.0",
"wayland-backend",
@@ -4485,9 +4497,9 @@ dependencies = [
[[package]]
name = "wayland-scanner"
version = "0.31.9"
version = "0.31.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
dependencies = [
"proc-macro2",
"quick-xml",
@@ -4496,18 +4508,18 @@ dependencies = [
[[package]]
name = "wayland-sys"
version = "0.31.10"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17"
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
dependencies = [
"pkg-config",
]
[[package]]
name = "web-sys"
version = "0.3.92"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94"
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
dependencies = [
"js-sys",
"wasm-bindgen",
+1 -1
View File
@@ -4,7 +4,7 @@ exclude = ["crates/brush-core-vendored", "crates/brush-builtins-vendored"]
resolver = "3"
[workspace.package]
version = "13.17.0"
version = "13.17.1"
edition = "2024"
license = "MIT"
authors = ["Can Boluk"]
+3 -3
View File
@@ -313,7 +313,7 @@ Create images directly from the agent:
Modern terminal interface with smart session management:
- **Auto session titles**: Sessions automatically titled based on first message using smol model
- **Auto session titles**: Sessions automatically titled based on first message using commit model, fallback to smol
- **Welcome screen**: Logo, tips, recent sessions with selection
- **Powerline footer**: Model, cwd, git branch/status, token usage, context %
- **LSP status**: Shows which language servers are active and ready
@@ -507,6 +507,7 @@ return config
| Qwen Portal (`qwen-portal`) | `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY` |
| vLLM (`vllm`) | `VLLM_API_KEY` |
| Cloudflare AI Gateway (`cloudflare-ai-gateway`) | `CLOUDFLARE_AI_GATEWAY_API_KEY` |
| Vercel AI Gateway (`vercel-ai-gateway`) | `AI_GATEWAY_API_KEY` |
| Qianfan (`qianfan`) | `QIANFAN_API_KEY` |
See [Environment Variables](docs/environment-variables.md) for the full list.
@@ -544,6 +545,7 @@ Use `/login` with supported providers:
- MiniMax Coding Plan (International / China)
- Qwen Portal (`qwen-portal`)
- Cloudflare AI Gateway (`cloudflare-ai-gateway`)
- Vercel AI Gateway (`vercel-ai-gateway`)
For `ollama`, API key is optional. Leave it unset for local no-auth instances, or set `OLLAMA_API_KEY` for authenticated hosts.
For `llama.cpp`, API key is optional. Leave it unset for local no-auth instances, or set `LLAMA_CPP_API_KEY` for authenticated hosts.
@@ -1238,8 +1240,6 @@ Use `--tools <list>` to restrict available built-in tools.
| `todo_write` | Phased task tracking with progress management |
| `fetch` | Fetch and extract URL content |
| `web_search` | Multi-provider web search |
| `deep_search` | AI-powered deep research with synthesized results (Exa) |
| `code_search` | Search code snippets and technical documentation (Exa) |
| `write` | Create/overwrite files |
| `generate_image` | Generate or edit images using Gemini image models |
+50 -52
View File
@@ -15,7 +15,7 @@
},
"packages/agent": {
"name": "@oh-my-pi/pi-agent-core",
"version": "13.17.0",
"version": "13.17.1",
"dependencies": {
"@oh-my-pi/pi-ai": "workspace:*",
"@oh-my-pi/pi-utils": "workspace:*",
@@ -27,7 +27,7 @@
},
"packages/ai": {
"name": "@oh-my-pi/pi-ai",
"version": "13.17.0",
"version": "13.17.1",
"bin": {
"pi-ai": "./src/cli.ts",
},
@@ -51,7 +51,7 @@
},
"packages/coding-agent": {
"name": "@oh-my-pi/pi-coding-agent",
"version": "13.17.0",
"version": "13.17.1",
"bin": {
"omp": "src/cli.ts",
},
@@ -80,7 +80,7 @@
},
"packages/natives": {
"name": "@oh-my-pi/pi-natives",
"version": "13.17.0",
"version": "13.17.1",
"dependencies": {
"@oh-my-pi/pi-utils": "workspace:*",
},
@@ -114,7 +114,7 @@
},
"packages/stats": {
"name": "@oh-my-pi/omp-stats",
"version": "13.17.0",
"version": "13.17.1",
"bin": {
"omp-stats": "./src/index.ts",
},
@@ -139,7 +139,7 @@
},
"packages/swarm-extension": {
"name": "@oh-my-pi/swarm-extension",
"version": "13.17.0",
"version": "13.17.1",
"bin": {
"omp-swarm": "src/cli.ts",
},
@@ -152,7 +152,7 @@
},
"packages/tui": {
"name": "@oh-my-pi/pi-tui",
"version": "13.17.0",
"version": "13.17.1",
"dependencies": {
"@oh-my-pi/pi-natives": "workspace:*",
"@oh-my-pi/pi-utils": "workspace:*",
@@ -165,7 +165,7 @@
},
"packages/utils": {
"name": "@oh-my-pi/pi-utils",
"version": "13.17.0",
"version": "13.17.1",
"dependencies": {
"beautiful-mermaid": "^1.1",
"winston": "^3.19",
@@ -191,25 +191,25 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1019.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.25", "@aws-sdk/credential-provider-node": "^3.972.27", "@aws-sdk/eventstream-handler-node": "^3.972.12", "@aws-sdk/middleware-eventstream": "^3.972.8", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.26", "@aws-sdk/middleware-websocket": "^3.972.14", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/token-providers": "3.1019.0", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.12", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Wq1uMAZfySYofuwkFMaMM+k7epsGBRcJGE+ZosGB+8jC8Xs1lycbjSEFMt0Mo3z1qhkgEKGCQyjCbPTICMkkVw=="],
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1021.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/eventstream-handler-node": "^3.972.12", "@aws-sdk/middleware-eventstream": "^3.972.8", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/middleware-websocket": "^3.972.14", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/token-providers": "3.1021.0", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FqAZq9ewaprev+BAUgUNeQKACVVEbGw8V3SZTKv0hEy5Ed5yhJhNuOL9G0QiUtdG6rX6NujGZjO5xBgKcvMLxQ=="],
"@aws-sdk/core": ["@aws-sdk/core@3.973.25", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.16", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ=="],
"@aws-sdk/core": ["@aws-sdk/core@3.973.26", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.16", "@smithy/core": "^3.23.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-EamaclJcCEaPHp6wiVknNMM2RlsPMjAHSsYSFLNENBM8Wz92QPc6cOn3dif6vPDQt0Oo4IEghDy3NMDCzY/IvA=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-qPymamdPcLp6ugoVocG1y5r69ScNiRzb0hogX25/ij+Wz7c7WnsgjLTaz7+eB5BfRxeyUwuw5hgULMuwOGOpcw=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/credential-provider-env": "^3.972.23", "@aws-sdk/credential-provider-http": "^3.972.25", "@aws-sdk/credential-provider-login": "^3.972.26", "@aws-sdk/credential-provider-process": "^3.972.23", "@aws-sdk/credential-provider-sso": "^3.972.26", "@aws-sdk/credential-provider-web-identity": "^3.972.26", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xKxEAMuP6GYx2y5GET+d3aGEroax3AgGfwBE65EQAUe090lzyJ/RzxPX9s8v7Z6qAk0XwfQl+LrmH05X7YvTeg=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-login": "^3.972.28", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.28", "@aws-sdk/credential-provider-web-identity": "^3.972.28", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-EFcM8RM3TUxnZOfMJo++3PnyxFu1fL/huzmn3Vh+8IWRgqZawUD3cRwwOr+/4bE9DpyHaLOWFAjY0lfK5X9ZkQ=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.27", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.23", "@aws-sdk/credential-provider-http": "^3.972.25", "@aws-sdk/credential-provider-ini": "^3.972.26", "@aws-sdk/credential-provider-process": "^3.972.23", "@aws-sdk/credential-provider-sso": "^3.972.26", "@aws-sdk/credential-provider-web-identity": "^3.972.26", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jXpxSolfFnPVj6GCTtx3xIdWNoDR7hYC/0SbetGZxOC9UnNmipHeX1k6spVstf7eWJrMhXNQEgXC0pD1r5tXIg=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.29", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-ini": "^3.972.28", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.28", "@aws-sdk/credential-provider-web-identity": "^3.972.28", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-IL/TFW59++b7MpHserjUblGrdP5UXy5Ekqqx1XQkERXBFJcZr74I7VaSrQT5dxdRMU16xGK4L0RQ5fQG1pMgnA=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/token-providers": "3.1019.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-c6ghvRb6gTlMznWhGxn/bpVCcp0HRaz4DobGVD9kI4vwHq186nU2xN/S7QGkm0lo0H2jQU8+dgpUFLxfTcwCOg=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/token-providers": "3.1021.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-cXcS3+XD3iwhoXkM44AmxjmbcKueoLCINr1e+IceMmCySda5ysNIfiGBGe9qn5EMiQ9Jd7pP0AGFtcd6OV3Lvg=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ=="],
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ruyc/MNR6e+cUrGCth7fLQ12RXBZDy/bV06tgqB9Z5n/0SN/C0m6bsQEV8FF9zPI6VSAOaRd0rNgmpYVnGawrQ=="],
@@ -221,15 +221,15 @@
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.28", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ=="],
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.14", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-format-url": "^3.972.8", "@smithy/eventstream-codec": "^4.2.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-qnfDlIHjm6DrTYNvWOUbnZdVKgtoKbO/Qzj+C0Wp5Y7VUrsvBRQtGKxD+hc+mRTS4N0kBJ6iZ3+zxm4N1OSyjg=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.16", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.25", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.26", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.12", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-L7Qzoj/qQU1cL5GnYLQP5LbI+wlLCLoINvcykR3htKcQ4tzrPf2DOs72x933BM7oArYj1SKrkb2lGlsJHIic3g=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.18", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1019.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OF+2RfRmUKyjzrRWlDcyju3RBsuqcrYDQ8TwrJg8efcOotMzuZN4U9mpVTIdATpmEc4lWNZBMSjPzrGm6JPnAQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1021.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.18", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
@@ -241,7 +241,7 @@
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.12", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.26", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.14", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.16", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A=="],
@@ -267,23 +267,23 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@biomejs/biome": ["@biomejs/biome@2.4.9", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.9", "@biomejs/cli-darwin-x64": "2.4.9", "@biomejs/cli-linux-arm64": "2.4.9", "@biomejs/cli-linux-arm64-musl": "2.4.9", "@biomejs/cli-linux-x64": "2.4.9", "@biomejs/cli-linux-x64-musl": "2.4.9", "@biomejs/cli-win32-arm64": "2.4.9", "@biomejs/cli-win32-x64": "2.4.9" }, "bin": { "biome": "bin/biome" } }, "sha512-wvZW92FrwitTcacvCBT8xdAbfbxWfDLwjYMmU3djjqQTh7Ni4ZdiWIT/x5VcZ+RQuxiKzIOzi5D+dcyJDFZMsA=="],
"@biomejs/biome": ["@biomejs/biome@2.4.10", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.10", "@biomejs/cli-darwin-x64": "2.4.10", "@biomejs/cli-linux-arm64": "2.4.10", "@biomejs/cli-linux-arm64-musl": "2.4.10", "@biomejs/cli-linux-x64": "2.4.10", "@biomejs/cli-linux-x64-musl": "2.4.10", "@biomejs/cli-win32-arm64": "2.4.10", "@biomejs/cli-win32-x64": "2.4.10" }, "bin": { "biome": "bin/biome" } }, "sha512-xxA3AphFQ1geij4JTHXv4EeSTda1IFn22ye9LdyVPoJU19fNVl0uzfEuhsfQ4Yue/0FaLs2/ccVi4UDiE7R30w=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d5G8Gf2RpH5pYwiHLPA+UpG3G9TLQu4WM+VK6sfL7K68AmhcEQ9r+nkj/DvR/GYhYox6twsHUtmWWWIKfcfQQA=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vuzzI1cWqDVzOMIkYyHbKqp+AkQq4K7k+UCXWpkYcY/HDn1UxdsbsfgtVpa40shem8Kax4TLDLlx8kMAecgqiw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-LNCLNgqDMG7BLdc3a8aY/dwKPK7+R8/JXJoXjCvZh2gx8KseqBdFDKbhrr7HCWF8SzNhbTaALhTBoh/I6rf9lA=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-14fzASRo+BPotwp7nWULy2W5xeUyFnTaq1V13Etrrxkrih+ez/2QfgFm5Ehtf5vSjtgx/IJycMMpn5kPd5ZNaA=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-4adnkAUi6K4C/emPRgYznMOcLlUqZdXWM6aIui4VP4LraE764g6Q4YguygnAUoxKjKIXIWPteKMgRbN0wsgwcg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-7MH1CMW5uuxQ/s7FLST63qF8B3Hgu2HRdZ7tA1X1+mk+St4JOuIrqdhIBnnyqeyWJNI+Bww7Es5QZ0wIc1Cmkw=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-8RCww5xnPn2wpK4L/QDGDOW0dq80uVWfppPxHIUg6mOs9B6gRmqPp32h1Ls3T8GnW8Wo5A8u7vpTwz4fExN+sw=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-WrJY6UuiSD/Dh+nwK2qOTu8kdMDlLV3dLMmychIghHPAysWFq1/DGC1pVZx8POE3ZkzKR3PUUnVrtZfMfaJjyQ=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.9", "", { "os": "linux", "cpu": "x64" }, "sha512-L10na7POF0Ks/cgLFNF1ZvIe+X4onLkTi5oP9hY+Rh60Q+7fWzKDDCeGyiHUFf1nGIa9dQOOUPGe2MyYg8nMSQ=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.10", "", { "os": "linux", "cpu": "x64" }, "sha512-tZLvEEi2u9Xu1zAqRjTcpIDGVtldigVvzug2fTuPG0ME/g8/mXpRPcNgLB22bGn6FvLJpHHnqLnwliOu8xjYrg=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.9", "", { "os": "linux", "cpu": "x64" }, "sha512-5TD+WS9v5vzXKzjetF0hgoaNFHMcpQeBUwKKVi3JbG1e9UCrFuUK3Gt185fyTzvRdwYkJJEMqglRPjmesmVv4A=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.10", "", { "os": "linux", "cpu": "x64" }, "sha512-kDTi3pI6PBN6CiczsWYOyP2zk0IJI08EWEQyDMQWW221rPaaEz6FvjLhnU07KMzLv8q3qSuoB93ua6inSQ55Tw=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-aDZr0RBC3sMGJOU10BvG7eZIlWLK/i51HRIfScE2lVhfts2dQTreowLiJJd+UYg/tHKxS470IbzpuKmd0MiD6g=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-umwQU6qPzH+ISTf/eHyJ/QoQnJs3V9Vpjz2OjZXe9MVBZ7prgGafMy7yYeRGnlmDAn87AKTF3Q6weLoMGpeqdQ=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.9", "", { "os": "win32", "cpu": "x64" }, "sha512-NS4g/2G9SoQ4ktKtz31pvyc/rmgzlcIDCGU/zWbmHJAqx6gcRj2gj5Q/guXhoWTzCUaQZDIqiCQXHS7BcGYc0w=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.10", "", { "os": "win32", "cpu": "x64" }, "sha512-aW/JU5GuyH4uxMrNYpoC2kjaHlyJGLgIa3XkhPEZI0uKhZhJZU8BuEyJmvgzSPQNGozBwWjC972RaNdcJ9KyJg=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
@@ -353,11 +353,9 @@
"@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="],
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
"@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
"@smithy/core": ["@smithy/core@3.23.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
@@ -381,17 +379,17 @@
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.28", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-serde": "^4.2.16", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.46", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.16", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA=="],
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
@@ -407,7 +405,7 @@
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="],
"@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
@@ -423,9 +421,9 @@
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.44", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.48", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
@@ -433,9 +431,9 @@
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.13", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.21", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q=="],
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="],
@@ -467,21 +465,21 @@
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260330.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260330.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260330.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260330.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260330.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260330.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260330.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260330.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-tyRn6/PAYpkN35bKz2RPfzV/QAACvle9EYlYyywn/p/qPI5yOBLka0zM/2dGKx+o5vHVQ1gr/iVrgW8w7Wqc8Q=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260331.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260331.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260331.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260331.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260331.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260331.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260331.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260331.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-Gfy2J/LhydkOHOw+ZWRw0M8Xl3O2bzQXLXIYITdMz2N4GpMm8misAvvCzhqMacOGvazKr1FsL9LIIW2kxk6kzw=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260330.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jX47OX4svh3/CzSDYd81+kELLP5r0+G7lFGiMAVUNpD+ZLmyh/nrkuuI643pGe8D0i9dTfg/aiZQq6d/00Feog=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260331.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1PRnBCN2csiCzj76YaSBtP4jPLEGBUmVhXHplC+yHOKaxx9nf3HFiFCg/19raInvN/lJ8+Bp1fZ/qIsWAAHiBw=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260330.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-JysPTOMnyje+LkdB3LR+Emnl7hJCvMzUVRcOVJyzmxWOMFNCYPWNK3wDdwuoS6jE5Hii9vInt/xtn3pc+kgb+A=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260331.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-llXnfLGjl+gXsANLD7UI/gSb3lj7aZW13Rf8sVXQnHJ3/dkJRAm/MgLqdjuuyvYq3pFaleiep+zoLd96rLRqUw=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260330.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rVwd9ZgjR8Le6ItUD/p59LCOYdZbi4DZYavXAMzPPpaJCnmdHwOcJFWc61vricsQljRjVBnunQknHfYABZDq9Q=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260331.1", "", { "os": "linux", "cpu": "arm" }, "sha512-+8AZzA0BRjMkLDvdQKZOMuheRxNGpSWn7sOtoKqo70R915D0TyEynEXX6B7/aw3+Jfn1H5hLRiBjxoVsmdKENw=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260330.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9zlw4ENr6HAUNO+OiWuoQpWUGk4lkqb8NUyXDvTqtlgZGvjyePNVy8OW7KlPuPtCaQS3btV7w4h5X1wlTm2bFA=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260331.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-sH5gALi89jl5ZjAL/UsLDPsjT/nCLRfHl/pw86ablRX10tYsJhJ/RD6J/cl3g39kJ18tIISSbsuIBn+ncanfSA=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260330.1", "", { "os": "linux", "cpu": "x64" }, "sha512-7cB4e12e61xaXgJu6N9rMP+6qdkODVsW8zGjVS5a+cobBCsJSdMnlshfitlL9a+BYWe9HseMqPnLcsaE+t22CQ=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260331.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Yic6MYfX7Uit5jLLENzWFIi6tjp4LTLF37KBiVaHZSvEFyX1kqVwu4j9WNeaz81O6fcB/1dZ1MrILgfcqalNBg=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260330.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-4wv/FX9F5oAQhzjqwL8KN4O5xgciBy5pT2yLP45xcQahxFUoPnQeF9Pz2x/jwXZSmuh8E1DQWX9evSVGA1MV2A=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260331.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-vGxK6gtGF97zSx9wOpiVME3h9v0tbZbrHHdKA+fLFNvDV0Df8ud89DEePL7l2yKnVVmf0OnjJy6sYoVyj+LIPA=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260330.1", "", { "os": "win32", "cpu": "x64" }, "sha512-zKDOG/nq1yTx3cjfc19VFO+TYK2+03Wc17pGdD+X+dWTj15LZ2PKk3s4OtT2b8siTcAUn7zmwLsMCWPYO7xXQw=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260331.1", "", { "os": "win32", "cpu": "x64" }, "sha512-oJnNiU9UTDPJp6dOmOUW+/Wzt3MQZXIHsDaU4qM0RiAjFE6S+PIX8s5z/ID0orr4MMroUMiLdolL4OVZolNDSw=="],
"@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="],
@@ -515,7 +513,7 @@
"bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="],
"bare-stream": ["bare-stream@2.11.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Y/+iQ49fL3rIn6w/AVxI/2+BRrpmzJvdWt5Jv8Za6Ngqc6V227c+pYjYYgLdpR3MwQ9ObVXD0ZrqoBztakM0rw=="],
"bare-stream": ["bare-stream@2.12.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g=="],
"bare-url": ["bare-url@2.4.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA=="],
+1
View File
@@ -40,6 +40,7 @@ ast-grep-core = { version = "0.39", default-features = false, features = [
tree-sitter = "0.25"
tree-sitter-bash = "0.25"
tree-sitter-c = "0.24"
tree-sitter-clojure = "0.1"
tree-sitter-c-sharp = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.25"
+6
View File
@@ -162,6 +162,12 @@ static LANG_ALIASES: phf::Map<&'static str, SupportLang> = phf::phf_map! {
"c#" => SupportLang::CSharp,
"cs" => SupportLang::CSharp,
"css" => SupportLang::Css,
"clj" => SupportLang::Clojure,
"cljc" => SupportLang::Clojure,
"cljs" => SupportLang::Clojure,
"clojure" => SupportLang::Clojure,
"clojurescript" => SupportLang::Clojure,
"edn" => SupportLang::Clojure,
"diff" => SupportLang::Diff,
"patch" => SupportLang::Diff,
"elixir" => SupportLang::Elixir,
+9 -3
View File
@@ -131,6 +131,7 @@ impl_lang_expando!(Zig, language_zig, 'µ');
// ── Stub languages ($ accepted in grammar) ──────────────────────────────
impl_lang!(Bash, language_bash);
impl_lang!(Clojure, language_clojure);
impl_lang!(Java, language_java);
impl_lang!(JavaScript, language_javascript);
impl_lang!(Json, language_json);
@@ -252,6 +253,7 @@ pub enum SupportLang {
C,
Cpp,
CSharp,
Clojure,
Css,
Diff,
Elixir,
@@ -292,9 +294,10 @@ impl SupportLang {
pub const fn all_langs() -> &'static [Self] {
use SupportLang::*;
&[
Bash, C, Cpp, CSharp, Css, Diff, Elixir, Go, Haskell, Hcl, Html, Java, JavaScript, Json,
Julia, Kotlin, Lua, Make, Markdown, Nix, ObjC, Odin, Php, Python, Regex, Ruby, Rust,
Scala, Solidity, Starlark, Swift, Toml, Tsx, TypeScript, Verilog, Xml, Yaml, Zig,
Bash, C, Cpp, CSharp, Clojure, Css, Diff, Elixir, Go, Haskell, Hcl, Html, Java,
JavaScript, Json, Julia, Kotlin, Lua, Make, Markdown, Nix, ObjC, Odin, Php, Python, Regex,
Ruby, Rust, Scala, Solidity, Starlark, Swift, Toml, Tsx, TypeScript, Verilog, Xml, Yaml,
Zig,
]
}
@@ -306,6 +309,7 @@ impl SupportLang {
Self::C => "c",
Self::Cpp => "cpp",
Self::CSharp => "csharp",
Self::Clojure => "clojure",
Self::Css => "css",
Self::Diff => "diff",
Self::Elixir => "elixir",
@@ -360,6 +364,7 @@ macro_rules! execute_lang_method {
S::C => C.$method($($pname,)*),
S::Cpp => Cpp.$method($($pname,)*),
S::CSharp => CSharp.$method($($pname,)*),
S::Clojure => Clojure.$method($($pname,)*),
S::Css => Css.$method($($pname,)*),
S::Diff => Diff.$method($($pname,)*),
S::Elixir => Elixir.$method($($pname,)*),
@@ -456,6 +461,7 @@ const fn extensions(lang: SupportLang) -> &'static [&'static str] {
C => &["c", "h"],
Cpp => &["cc", "hpp", "cpp", "c++", "hh", "cxx", "cu", "ino"],
CSharp => &["cs"],
Clojure => &["clj", "cljs", "cljc", "edn"],
Css => &["css", "scss"],
Diff => &["diff", "patch"],
Elixir => &["ex", "exs"],
@@ -8,6 +8,9 @@ pub fn language_bash() -> TSLanguage {
pub fn language_c() -> TSLanguage {
tree_sitter_c::LANGUAGE.into()
}
pub fn language_clojure() -> TSLanguage {
tree_sitter_clojure::LANGUAGE.into()
}
pub fn language_cpp() -> TSLanguage {
tree_sitter_cpp::LANGUAGE.into()
}
+2 -1
View File
@@ -34,7 +34,8 @@
"generate-models": "bun --cwd=packages/ai scripts/generate-models.ts",
"generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index",
"generate-template": "bun --cwd=packages/coding-agent run generate-template",
"sync-exports": "bun scripts/sync-exports.ts"
"sync-exports": "bun scripts/sync-exports.ts",
"check-spoofed-versions": "bun scripts/check-spoofed-versions.ts"
},
"devDependencies": {
"@biomejs/biome": "^2.4",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-agent-core",
"version": "13.17.0",
"version": "13.17.1",
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+24
View File
@@ -2,6 +2,30 @@
## [Unreleased]
## [13.17.1] - 2026-04-01
### Added
- Added `thinkingSignature` field to thinking content blocks to preserve the original reasoning field name (e.g., `reasoning_text`, `reasoning_content`) for accurate follow-up requests
- Added first-event timeout detection for streaming responses to abort stuck requests before user-visible content arrives
- Added `PI_STREAM_FIRST_EVENT_TIMEOUT_MS` environment variable to configure first-event timeout (defaults to 15 seconds or idle timeout, whichever is lower)
### Changed
- Changed thinking block handling to track and distinguish between different reasoning field types, enabling proper field name preservation across multiple turns
### Fixed
- Fixed Anthropic stream timeout errors to be properly retried by recognizing first-event timeout messages
- Fixed stream stall detection to distinguish between first-event timeouts and idle timeouts, enabling faster recovery for stuck connections
### Added
- Added Vercel AI Gateway to `/login` providers for interactive API key setup
### Fixed
- Fixed `omp commit` failing with HTTP 400 errors when using reasoning-enabled models on OpenAI-compatible endpoints that don't support the `developer` role (e.g., GitHub Copilot, custom proxies). Now falls back to `system` role when `developer` is unsupported.
## [13.17.0] - 2026-03-30
### Changed
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-ai",
"version": "13.17.0",
"version": "13.17.1",
"description": "Unified LLM API with automatic model discovery and provider configuration",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+6
View File
@@ -62,6 +62,7 @@ import { loginTavily } from "./utils/oauth/tavily";
import { loginTogether } from "./utils/oauth/together";
import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./utils/oauth/types";
import { loginVenice } from "./utils/oauth/venice";
import { loginVercelAiGateway } from "./utils/oauth/vercel-ai-gateway";
import { loginVllm } from "./utils/oauth/vllm";
import { loginXiaomi } from "./utils/oauth/xiaomi";
import { loginZai } from "./utils/oauth/zai";
@@ -903,6 +904,11 @@ export class AuthStorage {
await saveApiKeyCredential(apiKey);
return;
}
case "vercel-ai-gateway": {
const apiKey = await loginVercelAiGateway(ctrl);
await saveApiKeyCredential(apiKey);
return;
}
case "vllm": {
const apiKey = await loginVllm(ctrl);
await saveApiKeyCredential(apiKey);
+14 -4
View File
@@ -33,6 +33,7 @@ import type {
import { isAnthropicOAuthToken, normalizeToolCallId, resolveCacheRetention } from "../utils";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
import { getStreamFirstEventTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
import { parseStreamingJson } from "../utils/json-parse";
import {
buildCopilotDynamicHeaders,
@@ -572,9 +573,9 @@ function isTransientStreamParseError(error: unknown): boolean {
export function isProviderRetryableError(error: unknown): boolean {
if (!(error instanceof Error)) return false;
const msg = error.message;
const msg = error.message.toLowerCase();
return (
/rate.?limit|too many requests|overloaded|service.?unavailable|internal_error|stream error.*received from peer|1302/i.test(
/rate.?limit|too many requests|overloaded|service.?unavailable|internal_error|stream error.*received from peer|1302|timed?\s*out while waiting for the first event|timeout waiting for first/i.test(
msg,
) || isTransientStreamParseError(error)
);
@@ -674,13 +675,22 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
let providerRetryAttempt = 0;
let started = false;
do {
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
const requestAbortController = new AbortController();
const requestSignal = options?.signal
? AbortSignal.any([options.signal, requestAbortController.signal])
: requestAbortController.signal;
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: requestSignal });
if (copilotDynamicHeaders && output.usage.premiumRequests === undefined) {
output.usage.premiumRequests = copilotDynamicHeaders.premiumRequests;
}
try {
for await (const event of anthropicStream) {
for await (const event of iterateWithIdleTimeout(anthropicStream, {
firstItemTimeoutMs: getStreamFirstEventTimeoutMs(),
errorMessage: "Anthropic stream stalled while waiting for the next event",
firstItemErrorMessage: "Anthropic stream timed out while waiting for the first event",
onFirstItemTimeout: () => requestAbortController.abort(),
})) {
started = true;
if (event.type === "message_start") {
output.responseId = event.message.id;
@@ -20,8 +20,15 @@ import {
} from "../types";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
import { getOpenAIStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
import {
createFirstEventWatchdog,
getOpenAIStreamIdleTimeoutMs,
getStreamFirstEventTimeoutMs,
iterateWithIdleTimeout,
markFirstStreamEvent,
} from "../utils/idle-iterator";
import { mapToOpenAIResponsesToolChoice } from "../utils/tool-choice";
import { supportsDeveloperRole } from "./openai-responses";
import {
appendResponsesToolResultMessages,
convertResponsesAssistantMessage,
@@ -113,26 +120,31 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
// Create Azure OpenAI client
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
const client = createClient(model, apiKey, options);
const params = buildParams(model, context, options, deploymentName);
const { baseUrl } = resolveAzureConfig(model, options);
const params = buildParams(model, context, options, deploymentName, baseUrl);
const requestAbortController = new AbortController();
const requestSignal = options?.signal
? AbortSignal.any([options.signal, requestAbortController.signal])
: requestAbortController.signal;
const idleTimeoutMs = getOpenAIStreamIdleTimeoutMs();
const firstEventWatchdog = createFirstEventWatchdog(getStreamFirstEventTimeoutMs(idleTimeoutMs), () =>
requestAbortController.abort(),
);
options?.onPayload?.(params);
rawRequestDump = {
provider: model.provider,
api: output.api,
model: model.id,
method: "POST",
url: `${resolveAzureConfig(model, options).baseUrl}/responses`,
url: `${baseUrl}/responses`,
body: params,
};
const openaiStream = await client.responses.create(params, { signal: requestSignal });
stream.push({ type: "start", partial: output });
await processResponsesStream(
iterateWithIdleTimeout(openaiStream, {
idleTimeoutMs: getOpenAIStreamIdleTimeoutMs(),
iterateWithIdleTimeout(markFirstStreamEvent(openaiStream, firstEventWatchdog), {
idleTimeoutMs,
errorMessage: "Azure OpenAI responses stream stalled while waiting for the next event",
onIdle: () => requestAbortController.abort(),
}),
@@ -245,8 +257,9 @@ function buildParams(
context: Context,
options: AzureOpenAIResponsesOptions | undefined,
deploymentName: string,
resolvedBaseUrl?: string,
) {
const messages = convertMessages(model, context, true);
const messages = convertMessages(model, context, true, resolvedBaseUrl);
const params: AzureOpenAIResponsesSamplingParams = {
model: deploymentName,
@@ -323,13 +336,14 @@ function convertMessages(
model: Model<"azure-openai-responses">,
context: Context,
strictResponsesPairing: boolean,
resolvedBaseUrl?: string,
): ResponseInput {
const messages: ResponseInput = [];
const transformedMessages = transformMessages(context.messages, model, normalizeResponsesToolCallIdForTransform);
const knownCallIds = new Set<string>();
if (context.systemPrompt) {
const role = model.reasoning ? "developer" : "system";
const role = model.reasoning && supportsDeveloperRole(resolvedBaseUrl ?? model) ? "developer" : "system";
messages.push({
role,
content: context.systemPrompt.toWellFormed(),
@@ -65,14 +65,14 @@ const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_
/**
* Build a User-Agent string that identifies as Gemini CLI to unlock higher rate limits.
* Uses the same format as the official Gemini CLI:
* GeminiCLI/VERSION/MODEL (PLATFORM; ARCH) google-api-nodejs-client/10.5.0
* Uses the same format as the official Gemini CLI (v0.35+):
* GeminiCLI/VERSION/MODEL (PLATFORM; ARCH; SURFACE)
*/
export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): string {
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.34.0";
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.35.3";
const platform = process.platform === "win32" ? "win32" : process.platform;
const arch = process.arch === "x64" ? "x64" : process.arch;
return `GeminiCLI/${version}/${modelId} (${platform}; ${arch}) google-api-nodejs-client/10.5.0`;
return `GeminiCLI/${version}/${modelId} (${platform}; ${arch}; terminal)`;
}
const ANTIGRAVITY_USER_AGENT = (() => {
@@ -36,10 +36,14 @@ function detectStrictModeSupport(provider: string, baseUrl: string): boolean {
/**
* Detect compatibility settings from provider and baseUrl for known providers.
* Provider takes precedence over URL-based detection since it's explicitly configured.
* @param model - The model configuration
* @param resolvedBaseUrl - Optional resolved base URL (e.g., after GitHub Copilot proxy-ep resolution).
* If provided, this takes precedence over model.baseUrl for URL-based checks.
*/
export function detectOpenAICompat(model: Model<"openai-completions">): ResolvedOpenAICompat {
export function detectOpenAICompat(model: Model<"openai-completions">, resolvedBaseUrl?: string): ResolvedOpenAICompat {
const provider = model.provider;
const baseUrl = model.baseUrl;
// Use resolvedBaseUrl if provided (e.g., after GitHub Copilot proxy-ep resolution)
const baseUrl = resolvedBaseUrl ?? model.baseUrl;
const isCerebras = provider === "cerebras" || baseUrl.includes("cerebras.ai");
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
@@ -109,9 +113,15 @@ export function detectOpenAICompat(model: Model<"openai-completions">): Resolved
/**
* Resolve compatibility settings by layering explicit model.compat overrides onto
* the detected defaults. This is the canonical compat view for both metadata and transport.
* @param model - The model configuration
* @param resolvedBaseUrl - Optional resolved base URL (e.g., after GitHub Copilot proxy-ep resolution).
* If provided, this takes precedence over model.baseUrl for URL-based checks.
*/
export function resolveOpenAICompat(model: Model<"openai-completions">): ResolvedOpenAICompat {
const detected = detectOpenAICompat(model);
export function resolveOpenAICompat(
model: Model<"openai-completions">,
resolvedBaseUrl?: string,
): ResolvedOpenAICompat {
const detected = detectOpenAICompat(model, resolvedBaseUrl);
if (!model.compat) {
return detected;
}
+114 -86
View File
@@ -31,7 +31,13 @@ import {
} from "../types";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
import { getOpenAIStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
import {
createFirstEventWatchdog,
getOpenAIStreamIdleTimeoutMs,
getStreamFirstEventTimeoutMs,
iterateWithIdleTimeout,
markFirstStreamEvent,
} from "../utils/idle-iterator";
import { parseStreamingJson } from "../utils/json-parse";
import { getKimiCommonHeaders } from "../utils/oauth/kimi";
import { adaptSchemaForStrict, NO_STRICT } from "../utils/schema";
@@ -192,6 +198,10 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
const requestSignal = options?.signal
? AbortSignal.any([options.signal, requestAbortController.signal])
: requestAbortController.signal;
const idleTimeoutMs = getOpenAIStreamIdleTimeoutMs();
const firstEventWatchdog = createFirstEventWatchdog(getStreamFirstEventTimeoutMs(idleTimeoutMs), () =>
requestAbortController.abort(),
);
const { client, copilotPremiumRequests, baseUrl } = await createClient(
model,
context,
@@ -199,103 +209,105 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
options?.headers,
options?.initiatorOverride,
);
const params = buildParams(model, context, options);
const params = buildParams(model, context, options, baseUrl);
options?.onPayload?.(params);
rawRequestDump = {
provider: model.provider,
api: output.api,
model: model.id,
method: "POST",
url: `${baseUrl ?? "https://api.openai.com/v1"}/chat/completions`,
url: `${baseUrl}/chat/completions`,
body: params,
};
const openaiStream = await client.chat.completions.create(params, { signal: requestSignal });
if (copilotPremiumRequests !== undefined) output.usage.premiumRequests = copilotPremiumRequests;
stream.push({ type: "start", partial: output });
let currentBlock: TextContent | ThinkingContent | (ToolCall & { partialArgs?: string }) | null = null;
const blocks = output.content;
const blockIndex = () => blocks.length - 1;
const finishCurrentBlock = (block?: typeof currentBlock) => {
if (block) {
if (block.type === "text") {
stream.push({
type: "text_end",
contentIndex: blockIndex(),
content: block.text,
partial: output,
});
} else if (block.type === "thinking") {
stream.push({
type: "thinking_end",
contentIndex: blockIndex(),
content: block.thinking,
partial: output,
});
} else if (block.type === "toolCall") {
block.arguments = parseStreamingJson(block.partialArgs);
delete block.partialArgs;
stream.push({
type: "toolcall_end",
contentIndex: blockIndex(),
toolCall: block,
partial: output,
});
}
}
const parseMiniMaxThinkTags = model.provider === "minimax-code";
type OpenAIStreamBlock = TextContent | ThinkingContent | (ToolCall & { partialArgs: string });
let currentBlock: OpenAIStreamBlock | undefined;
const blockIndex = (block: OpenAIStreamBlock | undefined): number => {
if (!block) return Math.max(0, output.content.length - 1);
return output.content.indexOf(block);
};
const parseMiniMaxThinkTags = model.provider === "minimax-code" || model.provider === "minimax-code-cn";
let taggedTextBuffer = "";
let insideTaggedThinking = false;
const appendTextDelta = (delta: string) => {
if (delta.length === 0) return;
const finishCurrentBlock = (block: OpenAIStreamBlock | undefined): void => {
if (!block) return;
const contentIndex = blockIndex(block);
if (contentIndex < 0) return;
if (block.type === "text") {
stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });
return;
}
if (block.type === "thinking") {
stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: output });
return;
}
block.arguments = parseStreamingJson(block.partialArgs);
delete (block as { partialArgs?: string }).partialArgs;
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: output });
};
const appendText = (
message: AssistantMessage,
eventStream: AssistantMessageEventStream,
text: string,
): void => {
if (!currentBlock || currentBlock.type !== "text") {
finishCurrentBlock(currentBlock);
currentBlock = { type: "text", text: "" };
output.content.push(currentBlock);
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
}
if (currentBlock.type === "text") {
currentBlock.text += delta;
stream.push({
type: "text_delta",
contentIndex: blockIndex(),
delta,
partial: output,
});
message.content.push(currentBlock);
eventStream.push({ type: "text_start", contentIndex: blockIndex(currentBlock), partial: message });
}
currentBlock.text += text;
eventStream.push({
type: "text_delta",
contentIndex: blockIndex(currentBlock),
delta: text,
partial: message,
});
};
const appendThinkingDelta = (delta: string, signature?: string) => {
if (delta.length === 0) return;
const appendThinking = (
message: AssistantMessage,
eventStream: AssistantMessageEventStream,
thinking: string,
signature?: string,
): void => {
if (
!currentBlock ||
currentBlock.type !== "thinking" ||
(signature !== undefined && currentBlock.thinkingSignature !== signature)
) {
finishCurrentBlock(currentBlock);
currentBlock = {
type: "thinking",
thinking: "",
thinkingSignature: signature,
};
output.content.push(currentBlock);
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
}
if (currentBlock.type === "thinking") {
if (signature !== undefined && !currentBlock.thinkingSignature) {
currentBlock.thinkingSignature = signature;
}
currentBlock.thinking += delta;
stream.push({
type: "thinking_delta",
contentIndex: blockIndex(),
delta,
partial: output,
currentBlock = { type: "thinking", thinking: "", thinkingSignature: signature };
message.content.push(currentBlock);
eventStream.push({
type: "thinking_start",
contentIndex: blockIndex(currentBlock),
partial: message,
});
}
if (signature !== undefined && !currentBlock.thinkingSignature) {
currentBlock.thinkingSignature = signature;
}
currentBlock.thinking += thinking;
eventStream.push({
type: "thinking_delta",
contentIndex: blockIndex(currentBlock),
delta: thinking,
partial: message,
});
};
let taggedTextBuffer = "";
let insideTaggedThinking = false;
const appendTextDelta = (text: string) => {
if (!text) return;
if (!firstTokenTime) firstTokenTime = Date.now();
appendText(output, stream, text);
};
const appendThinkingDelta = (thinking: string, signature?: string) => {
if (!thinking) return;
if (!firstTokenTime) firstTokenTime = Date.now();
appendThinking(output, stream, thinking, signature);
};
const flushTaggedTextBuffer = () => {
@@ -332,8 +344,8 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
}
};
for await (const chunk of iterateWithIdleTimeout(openaiStream, {
idleTimeoutMs: getOpenAIStreamIdleTimeoutMs(),
for await (const chunk of iterateWithIdleTimeout(markFirstStreamEvent(openaiStream, firstEventWatchdog), {
idleTimeoutMs,
errorMessage: "OpenAI completions stream stalled while waiting for the next event",
onIdle: () => requestAbortController.abort(),
})) {
@@ -420,7 +432,11 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
partialArgs: "",
};
output.content.push(currentBlock);
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
stream.push({
type: "toolcall_start",
contentIndex: blockIndex(currentBlock),
partial: output,
});
}
if (currentBlock.type === "toolCall") {
@@ -434,7 +450,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
}
stream.push({
type: "toolcall_delta",
contentIndex: blockIndex(),
contentIndex: blockIndex(currentBlock),
delta,
partial: output,
});
@@ -554,8 +570,13 @@ async function createClient(
};
}
function buildParams(model: Model<"openai-completions">, context: Context, options?: OpenAICompletionsOptions) {
const compat = getCompat(model);
function buildParams(
model: Model<"openai-completions">,
context: Context,
options: OpenAICompletionsOptions | undefined,
resolvedBaseUrl?: string,
) {
const compat = getCompat(model, resolvedBaseUrl);
const messages = convertMessages(model, context, compat);
maybeAddOpenRouterAnthropicCacheControl(model, messages);
@@ -906,7 +927,13 @@ export function convertMessages(
}
if (compat.thinkingFormat === "openai") {
const reasoningField = compat.reasoningContentField ?? "reasoning_content";
const streamedReasoningField = nonEmptyThinkingBlocks[0]?.thinkingSignature;
const reasoningField =
streamedReasoningField === "reasoning_content" ||
streamedReasoningField === "reasoning" ||
streamedReasoningField === "reasoning_text"
? streamedReasoningField
: (compat.reasoningContentField ?? "reasoning_content");
const reasoningContent = (assistantMsg as any)[reasoningField];
if (!reasoningContent) {
const reasoning = (assistantMsg as any).reasoning;
@@ -962,10 +989,9 @@ export function convertMessages(
(assistantMsg as any).reasoning_details = reasoningDetails;
}
}
// Skip assistant messages that have no content and no tool calls.
// Mistral explicitly requires "either content or tool_calls, but not none".
// Other providers also don't accept empty assistant messages.
// This handles aborted assistant responses that got no content.
// Skip assistant messages that have no content, no tool calls, and no reasoning payload.
// Some OpenAI-compatible backends require replaying reasoning-only assistant turns
// so follow-up requests preserve the provider-specific reasoning field name.
const content = assistantMsg.content;
const hasContent =
content !== null &&
@@ -974,7 +1000,7 @@ export function convertMessages(
if (!hasContent && assistantMsg.tool_calls && compat.requiresAssistantContentForToolCalls) {
assistantMsg.content = ".";
}
if (!hasContent && !assistantMsg.tool_calls) {
if (!hasContent && !assistantMsg.tool_calls && !hasReasoningField) {
continue;
}
params.push(assistantMsg);
@@ -1118,7 +1144,9 @@ export function detectCompat(model: Model<"openai-completions">): ResolvedOpenAI
/**
* Get resolved compatibility settings for a model.
* Uses explicit model.compat if provided, otherwise auto-detects from provider/URL.
* @param model - The model configuration
* @param resolvedBaseUrl - Optional resolved base URL (e.g., after GitHub Copilot proxy-ep resolution).
*/
function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompat {
return resolveOpenAICompat(model);
function getCompat(model: Model<"openai-completions">, resolvedBaseUrl?: string): ResolvedOpenAICompat {
return resolveOpenAICompat(model, resolvedBaseUrl);
}
+29 -5
View File
@@ -30,7 +30,13 @@ import {
} from "../utils";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
import { getOpenAIStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
import {
createFirstEventWatchdog,
getOpenAIStreamIdleTimeoutMs,
getStreamFirstEventTimeoutMs,
iterateWithIdleTimeout,
markFirstStreamEvent,
} from "../utils/idle-iterator";
import { adaptSchemaForStrict, NO_STRICT } from "../utils/schema";
import { mapToOpenAIResponsesToolChoice } from "../utils/tool-choice";
import {
@@ -167,11 +173,15 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
options?.initiatorOverride,
);
const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
const { params } = buildParams(model, context, options, providerSessionState);
const { params } = buildParams(model, context, options, providerSessionState, baseUrl);
const requestAbortController = new AbortController();
const requestSignal = options?.signal
? AbortSignal.any([options.signal, requestAbortController.signal])
: requestAbortController.signal;
const idleTimeoutMs = getOpenAIStreamIdleTimeoutMs();
const firstEventWatchdog = createFirstEventWatchdog(getStreamFirstEventTimeoutMs(idleTimeoutMs), () =>
requestAbortController.abort(),
);
options?.onPayload?.(params);
rawRequestDump = {
provider: model.provider,
@@ -187,8 +197,8 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
const nativeOutputItems: Array<Record<string, unknown>> = [];
await processResponsesStream(
iterateWithIdleTimeout(openaiStream, {
idleTimeoutMs: getOpenAIStreamIdleTimeoutMs(),
iterateWithIdleTimeout(markFirstStreamEvent(openaiStream, firstEventWatchdog), {
idleTimeoutMs,
errorMessage: "OpenAI responses stream stalled while waiting for the next event",
onIdle: () => requestAbortController.abort(),
}),
@@ -290,6 +300,7 @@ function buildParams(
context: Context,
options: OpenAIResponsesOptions | undefined,
providerSessionState: OpenAIResponsesProviderSessionState | undefined,
resolvedBaseUrl?: string,
): { conversationMessages: ResponseInput; params: OpenAIResponsesSamplingParams } {
const strictResponsesPairing =
options?.strictResponsesPairing ??
@@ -303,7 +314,7 @@ function buildParams(
const messages: ResponseInput = [...conversationMessages];
if (context.systemPrompt) {
const role = model.reasoning ? "developer" : "system";
const role = model.reasoning && supportsDeveloperRole(resolvedBaseUrl ?? model) ? "developer" : "system";
messages.unshift({
role,
content: context.systemPrompt.toWellFormed(),
@@ -398,6 +409,19 @@ function supportsStrictMode(model: Model<"openai-responses">): boolean {
);
}
export function supportsDeveloperRole(modelOrBaseUrl: Pick<Model, "provider" | "baseUrl"> | string): boolean {
const baseUrl =
typeof modelOrBaseUrl === "string" ? modelOrBaseUrl.toLowerCase() : (modelOrBaseUrl.baseUrl ?? "").toLowerCase();
return (
baseUrl.includes("api.openai.com") ||
baseUrl.includes(".openai.azure.com") ||
baseUrl.includes("azure.com/openai") ||
baseUrl.includes("models.inference.ai.azure.com") ||
baseUrl.includes("githubcopilot.com") ||
baseUrl.includes("copilot-api.")
);
}
function convertConversationMessages(
model: Model<"openai-responses">,
context: Context,
+103 -4
View File
@@ -1,6 +1,7 @@
import { $env } from "@oh-my-pi/pi-utils";
const DEFAULT_OPENAI_STREAM_IDLE_TIMEOUT_MS = 45_000;
const DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS = 15_000;
function normalizeIdleTimeoutMs(value: string | undefined, fallback: number): number | undefined {
if (value === undefined) return fallback;
@@ -19,20 +20,95 @@ export function getOpenAIStreamIdleTimeoutMs(): number | undefined {
return normalizeIdleTimeoutMs($env.PI_OPENAI_STREAM_IDLE_TIMEOUT_MS, DEFAULT_OPENAI_STREAM_IDLE_TIMEOUT_MS);
}
/**
* Returns the timeout used while waiting for the first stream event.
*
* Set `PI_STREAM_FIRST_EVENT_TIMEOUT_MS=0` to disable the watchdog.
*/
export function getStreamFirstEventTimeoutMs(idleTimeoutMs?: number): number | undefined {
const fallback = Math.min(
DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS,
idleTimeoutMs ?? DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS,
);
return normalizeIdleTimeoutMs($env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS, fallback);
}
export interface FirstEventWatchdog {
markFirstEventReceived(): void;
cleanup(): void;
}
/**
* Starts a watchdog that aborts a request if no first stream event arrives in time.
* Call `markFirstEventReceived()` as soon as the first event is observed.
*/
export function createFirstEventWatchdog(timeoutMs: number | undefined, onTimeout: () => void): FirstEventWatchdog {
let timer: NodeJS.Timeout | undefined;
if (timeoutMs !== undefined && timeoutMs > 0) {
timer = setTimeout(() => {
timer = undefined;
onTimeout();
}, timeoutMs);
}
return {
markFirstEventReceived() {
if (!timer) return;
clearTimeout(timer);
timer = undefined;
},
cleanup() {
if (!timer) return;
clearTimeout(timer);
timer = undefined;
},
};
}
/**
* Wraps an async iterable and clears the watchdog once the first event arrives.
*/
export async function* markFirstStreamEvent<T>(
iterable: AsyncIterable<T>,
watchdog: FirstEventWatchdog,
): AsyncGenerator<T> {
let sawFirstEvent = false;
try {
for await (const item of iterable) {
if (!sawFirstEvent) {
sawFirstEvent = true;
watchdog.markFirstEventReceived();
}
yield item;
}
} finally {
watchdog.cleanup();
}
}
export interface IdleTimeoutIteratorOptions {
idleTimeoutMs?: number;
firstItemTimeoutMs?: number;
errorMessage: string;
firstItemErrorMessage?: string;
onIdle?: () => void;
onFirstItemTimeout?: () => void;
}
/**
* Yields items from an async iterable while enforcing a maximum idle gap between items.
*
* The first item may use a shorter timeout so stuck requests can be aborted and retried
* before any user-visible content has streamed.
*/
export async function* iterateWithIdleTimeout<T>(
iterable: AsyncIterable<T>,
options: IdleTimeoutIteratorOptions,
): AsyncGenerator<T> {
if (options.idleTimeoutMs === undefined || options.idleTimeoutMs <= 0) {
const firstItemTimeoutMs = options.firstItemTimeoutMs ?? options.idleTimeoutMs;
if (
(firstItemTimeoutMs === undefined || firstItemTimeoutMs <= 0) &&
(options.idleTimeoutMs === undefined || options.idleTimeoutMs <= 0)
) {
for await (const item of iterable) {
yield item;
}
@@ -40,26 +116,48 @@ export async function* iterateWithIdleTimeout<T>(
}
const iterator = iterable[Symbol.asyncIterator]();
let sawFirstItem = false;
while (true) {
const nextResultPromise = iterator.next().then(
result => ({ kind: "next" as const, result }),
error => ({ kind: "error" as const, error }),
);
const activeTimeoutMs = sawFirstItem ? options.idleTimeoutMs : firstItemTimeoutMs;
if (activeTimeoutMs === undefined || activeTimeoutMs <= 0) {
const outcome = await nextResultPromise;
if (outcome.kind === "error") {
throw outcome.error;
}
if (outcome.result.done) {
return;
}
sawFirstItem = true;
yield outcome.result.value;
continue;
}
const { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers<{
kind: "timeout";
}>();
const timer = setTimeout(() => resolveTimeout({ kind: "timeout" }), options.idleTimeoutMs);
const timer = setTimeout(() => resolveTimeout({ kind: "timeout" }), activeTimeoutMs);
try {
const outcome = await Promise.race([nextResultPromise, timeoutPromise]);
if (outcome.kind === "timeout") {
options.onIdle?.();
if (sawFirstItem) {
options.onIdle?.();
} else {
options.onFirstItemTimeout?.();
}
const returnPromise = iterator.return?.();
if (returnPromise) {
void returnPromise.catch(() => {});
}
throw new Error(options.errorMessage);
throw new Error(
sawFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage),
);
}
if (outcome.kind === "error") {
throw outcome.error;
@@ -67,6 +165,7 @@ export async function* iterateWithIdleTimeout<T>(
if (outcome.result.done) {
return;
}
sawFirstItem = true;
yield outcome.result.value;
} finally {
clearTimeout(timer);
+8
View File
@@ -113,6 +113,8 @@ export { loginTogether } from "./together";
export * from "./types";
// Venice (API key)
export { loginVenice } from "./venice";
// Vercel AI Gateway (API key)
export { loginVercelAiGateway } from "./vercel-ai-gateway";
// vLLM (API key)
export { loginVllm } from "./vllm";
// Xiaomi MiMo (API key)
@@ -303,6 +305,11 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
name: "Cloudflare AI Gateway",
available: true,
},
{
id: "vercel-ai-gateway",
name: "Vercel AI Gateway",
available: true,
},
];
const customOAuthProviders = new Map<string, OAuthProviderInterface>();
@@ -400,6 +407,7 @@ export async function refreshOAuthToken(
case "moonshot":
case "kagi":
case "cloudflare-ai-gateway":
case "vercel-ai-gateway":
case "qwen-portal":
case "zenmux":
case "vllm":
+1
View File
@@ -41,6 +41,7 @@ export type OAuthProvider =
| "tavily"
| "together"
| "venice"
| "vercel-ai-gateway"
| "vllm"
| "xiaomi"
| "zenmux"
@@ -0,0 +1,47 @@
/**
* Vercel AI Gateway login flow.
*
* Vercel AI Gateway proxies upstream model providers through a unified endpoint.
*
* This is not OAuth - it's a simple API key flow:
* 1. Open Vercel AI Gateway docs
* 2. User copies their API key
* 3. User pastes the API key into the CLI
*/
import type { OAuthController } from "./types";
const AUTH_URL = "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys";
/**
* Login to Vercel AI Gateway.
*
* Opens browser to Vercel AI Gateway docs and prompts for an API key.
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
*/
export async function loginVercelAiGateway(options: OAuthController): Promise<string> {
if (!options.onPrompt) {
throw new Error("Vercel AI Gateway login requires onPrompt callback");
}
options.onAuth?.({
url: AUTH_URL,
instructions: "Copy your Vercel AI Gateway API key from the Vercel dashboard",
});
const apiKey = await options.onPrompt({
message: "Paste your Vercel AI Gateway API key",
placeholder: "vck_...",
});
if (options.signal?.aborted) {
throw new Error("Login cancelled");
}
const trimmed = apiKey.trim();
if (!trimmed) {
throw new Error("API key is required");
}
return trimmed;
}
+6
View File
@@ -18,6 +18,12 @@ describe("isProviderRetryableError", () => {
).toBe(true);
});
it("retries first-event timeout errors", () => {
expect(isProviderRetryableError(new Error("Anthropic stream timed out while waiting for the first event"))).toBe(
true,
);
});
it("does not retry non-transient validation errors", () => {
expect(isProviderRetryableError(new Error("Invalid tool schema"))).toBe(false);
expect(isProviderRetryableError(new Error("Bad request"))).toBe(false);
@@ -0,0 +1,115 @@
import { afterEach, describe, expect, it, vi } from "bun:test";
import { Messages } from "@anthropic-ai/sdk/resources/messages/messages";
import { streamAnthropic } from "../src/providers/anthropic";
import type { Context, Model } from "../src/types";
const originalFirstEventTimeout = Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS;
const model: Model<"anthropic-messages"> = {
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
api: "anthropic-messages",
provider: "anthropic",
baseUrl: "https://api.anthropic.com",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 8_192,
};
const context: Context = {
messages: [{ role: "user", content: "Say hi", timestamp: Date.now() }],
};
function createHangingAnthropicStream(signal: AbortSignal | undefined): AsyncIterable<Record<string, unknown>> {
return {
async *[Symbol.asyncIterator]() {
const { promise, reject } = Promise.withResolvers<void>();
const onAbort = () => reject(new Error("request aborted"));
if (signal?.aborted) {
onAbort();
} else {
signal?.addEventListener("abort", onAbort, { once: true });
}
try {
await promise;
} catch {
return;
} finally {
signal?.removeEventListener("abort", onAbort);
}
},
};
}
function createSuccessfulAnthropicStream(text: string): AsyncIterable<Record<string, unknown>> {
return {
async *[Symbol.asyncIterator]() {
yield {
type: "message_start",
message: {
id: "msg_retry_success",
usage: {
input_tokens: 12,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
},
};
yield {
type: "content_block_start",
index: 0,
content_block: { type: "text", text: "" },
};
yield {
type: "content_block_delta",
index: 0,
delta: { type: "text_delta", text },
};
yield { type: "content_block_stop", index: 0 };
yield {
type: "message_delta",
delta: { stop_reason: "end_turn" },
usage: {
input_tokens: 12,
output_tokens: 4,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
};
},
};
}
afterEach(() => {
vi.restoreAllMocks();
if (originalFirstEventTimeout === undefined) {
delete Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS;
} else {
Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS = originalFirstEventTimeout;
}
});
describe("anthropic first-event timeout retries", () => {
it("retries when the provider never sends the first stream event", async () => {
Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS = "20";
let attempt = 0;
vi.spyOn(Messages.prototype, "stream").mockImplementation((_body, requestOptions) => {
attempt += 1;
const signal = (requestOptions as { signal?: AbortSignal } | undefined)?.signal;
return (
attempt === 1 ? createHangingAnthropicStream(signal) : createSuccessfulAnthropicStream("retry recovered")
) as never;
});
const result = await streamAnthropic(model, context, { apiKey: "sk-ant-test" }).result();
expect(attempt).toBe(2);
expect(result.stopReason).toBe("stop");
expect(result.content).toEqual([{ type: "text", text: "retry recovered" }]);
expect(result.responseId).toBe("msg_retry_success");
});
});
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "bun:test";
import { getBundledModel } from "../src/models";
import { convertMessages, streamOpenAICompletions } from "../src/providers/openai-completions";
import { convertMessages, detectCompat, streamOpenAICompletions } from "../src/providers/openai-completions";
import type { AssistantMessage, Context, Model, OpenAICompat } from "../src/types";
const originalFetch = global.fetch;
@@ -235,4 +235,48 @@ describe("openai-completions compatibility", () => {
}),
);
});
it("preserves the streamed reasoning field name for follow-up requests", async () => {
const model: Model<"openai-completions"> = {
...getBundledModel("openai", "gpt-4o-mini"),
api: "openai-completions",
};
global.fetch = createMockFetch([
{
id: "chatcmpl-reasoning-text",
object: "chat.completion.chunk",
created: 0,
model: model.id,
choices: [
{
index: 0,
delta: { reasoning_text: "inspect tool output" },
},
],
},
{
id: "chatcmpl-reasoning-text",
object: "chat.completion.chunk",
created: 0,
model: model.id,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
},
"[DONE]",
]);
const result = await streamOpenAICompletions(model, baseContext(), { apiKey: "test-key" }).result();
expect(result.content).toContainEqual({
type: "thinking",
thinking: "inspect tool output",
thinkingSignature: "reasoning_text",
});
const messages = convertMessages(model, { messages: [result] }, detectCompat(model));
const assistant = messages.find(message => message.role === "assistant");
expect(assistant).toBeDefined();
const assistantObject = toObject(assistant);
expect(assistantObject).toBeDefined();
expect(assistantObject ? Reflect.get(assistantObject, "reasoning_text") : undefined).toBe("inspect tool output");
expect(assistantObject ? Reflect.get(assistantObject, "reasoning_content") : undefined).toBeUndefined();
});
});
@@ -0,0 +1,78 @@
import { describe, expect, it } from "bun:test";
import { supportsDeveloperRole } from "../src/providers/openai-responses";
import type { Model } from "../src/types";
describe("supportsDeveloperRole", () => {
it("returns true for openai provider with official API base URL", () => {
const model = { provider: "openai", baseUrl: "https://api.openai.com/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns false for openai provider with custom proxy base URL", () => {
const model = { provider: "openai", baseUrl: "https://my-proxy.example.com/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(false);
});
it("returns true for github-copilot provider", () => {
const model = { provider: "github-copilot", baseUrl: "https://api.githubcopilot.com" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns false for github-copilot provider with custom proxy base URL", () => {
const model = { provider: "github-copilot", baseUrl: "https://proxy.example.com/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(false);
});
it("returns true for Azure OpenAI base URL", () => {
const model = { provider: "azure-openai", baseUrl: "https://my-resource.openai.azure.com/openai" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for Azure AI Inference base URL", () => {
const model = {
provider: "azure-openai",
baseUrl: "https://models.inference.ai.azure.com/v1/chat/completions",
} as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for api.openai.com base URL", () => {
const model = { provider: "custom", baseUrl: "https://api.openai.com/v1/chat/completions" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns false for generic third-party provider", () => {
const model = { provider: "custom", baseUrl: "https://api.example.com/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(false);
});
it("returns false for local/localhost endpoints", () => {
const model = { provider: "custom", baseUrl: "http://localhost:8080/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(false);
});
it("is case-insensitive for base URL matching", () => {
const model = { provider: "custom", baseUrl: "https://API.OPENAI.COM/v1" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for azure.com/openai base URL", () => {
const model = { provider: "custom", baseUrl: "https://azure.com/openai/deployments/my-model" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for github-copilot provider with api.individual.githubcopilot.com", () => {
const model = { provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for github-copilot provider with api.enterprise.githubcopilot.com", () => {
const model = { provider: "github-copilot", baseUrl: "https://api.enterprise.githubcopilot.com" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
it("returns true for github-copilot provider with copilot-api enterprise domain", () => {
const model = { provider: "github-copilot", baseUrl: "https://copilot-api.mycompany.com" } as Model;
expect(supportsDeveloperRole(model)).toBe(true);
});
});
+11 -1
View File
@@ -8,6 +8,16 @@
- `--scope user|project` flag added to `/marketplace uninstall`, `/marketplace upgrade`, `/plugins enable`, and `/plugins disable` to disambiguate when a plugin is installed in both scopes
- `omp plugin upgrade --scope project` with no plugin ID warns that `--scope` is ignored for bulk upgrades
## [13.17.1] - 2026-04-01
### Removed
- Removed `code_search` tool for code snippet and documentation search
### Fixed
- Fixed edit tool diff rendering to wrap long diff lines with continuation gutters instead of truncating them at terminal width ([#578](https://github.com/can1357/oh-my-pi/issues/578))
- Fixed `--list-models` and `/model` provider filtering to hide models from disabled providers ([#588](https://github.com/can1357/oh-my-pi/issues/588))
- Fixed edit tool diffstats to use diff-specific add/remove theme colors instead of success/error status colors ([#589](https://github.com/can1357/oh-my-pi/issues/589))
## [13.17.0] - 2026-03-30
### Added
@@ -6457,4 +6467,4 @@ Initial public release.
- Git branch display in footer
- Message queueing during streaming responses
- OAuth integration for Gmail and Google Calendar access
- HTML export with syntax highlighting and collapsible sections
- HTML export with syntax highlighting and collapsible sections
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-coding-agent",
"version": "13.17.0",
"version": "13.17.1",
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
@@ -189,12 +189,11 @@ function extractFileHeader(diff: string): string {
return headerLines.join("\n");
}
function joinPatch(parts: string[]): string {
return parts
export function joinPatch(parts: string[]): string {
return `${parts
.map(part => (part.endsWith("\n") ? part : `${part}\n`))
.join("\n")
.trimEnd()
.concat("\n");
.replace(/\n+$/, "")}\n`;
}
function selectHunks(file: FileHunks, selector: HunkSelection["hunks"]): FileHunks["hunks"] {
@@ -1,7 +1,7 @@
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Api, Model } from "@oh-my-pi/pi-ai";
import { MODEL_ROLE_IDS } from "../config/model-registry";
import { parseModelPattern, resolveModelRoleValue } from "../config/model-resolver";
import { parseModelPattern, resolveModelRoleValue, resolveRoleSelection } from "../config/model-resolver";
import type { Settings } from "../config/settings";
import MODEL_PRIO from "../priority.json" with { type: "json" };
@@ -11,24 +11,6 @@ export interface ResolvedCommitModel {
thinkingLevel?: ThinkingLevel;
}
function resolveRoleSelection(
roles: readonly string[],
settings: Settings,
availableModels: Model<Api>[],
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
for (const role of roles) {
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
settings,
matchPreferences,
});
if (resolved.model) {
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
}
}
return undefined;
}
export async function resolvePrimaryModel(
override: string | undefined,
settings: Settings,
@@ -31,7 +31,7 @@ import { type ConfigError, ConfigFile } from "../config";
import { parseModelString } from "../config/model-resolver";
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
import type { Settings } from "./settings";
import { type Settings, settings } from "./settings";
export const kNoAuth = "N/A";
@@ -730,6 +730,14 @@ function normalizeSuppressedSelector(selector: string): string {
return `${parsed.provider}/${parsed.id}`;
}
function getDisabledProviderIdsFromSettings(): Set<string> {
try {
return new Set(settings.get("disabledProviders"));
} catch {
return new Set();
}
}
/**
* Model registry - loads and manages models, resolves API keys via AuthStorage.
*/
@@ -1670,11 +1678,19 @@ export class ModelRegistry {
* This is a fast check that doesn't refresh OAuth tokens.
*/
getAvailable(): Model<Api>[] {
return this.#models.filter(m => this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider));
const disabledProviders = getDisabledProviderIdsFromSettings();
return this.#models.filter(
m =>
!disabledProviders.has(m.provider) &&
(this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider)),
);
}
getDiscoverableProviders(): string[] {
return this.#discoverableProviders.map(provider => provider.provider);
const disabledProviders = getDisabledProviderIdsFromSettings();
return this.#discoverableProviders
.filter(provider => !disabledProviders.has(provider.provider))
.map(provider => provider.provider);
}
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
@@ -587,6 +587,27 @@ export function resolveModelOverride(
return { explicitThinkingLevel: false };
}
/**
* Resolve a list of role patterns to the first matching model.
*/
export function resolveRoleSelection(
roles: readonly string[],
settings: Settings,
availableModels: Model<Api>[],
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
for (const role of roles) {
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
settings,
matchPreferences,
});
if (resolved.model) {
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
}
}
return undefined;
}
/**
* Resolve model patterns to actual Model objects with optional thinking levels
* Format: "pattern:level" where :level is optional
+1
View File
@@ -541,6 +541,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
}
if (parsedArgs.listModels !== undefined) {
await logger.timeAsync("settings:init:list-models", () => Settings.init({ cwd: getProjectDir() }));
await modelRegistry.refresh("online");
const searchPattern = typeof parsedArgs.listModels === "string" ? parsedArgs.listModels : undefined;
await listModels(modelRegistry, searchPattern);
+28 -3
View File
@@ -3,7 +3,7 @@
*/
import type { ToolCallContext } from "@oh-my-pi/pi-agent-core";
import type { Component } from "@oh-my-pi/pi-tui";
import { Text } from "@oh-my-pi/pi-tui";
import { Text, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
import type { FileDiagnosticsResult } from "../lsp";
import { renderDiff as renderDiffColored } from "../modes/components/diff";
@@ -21,7 +21,7 @@ import {
shortenPath,
truncateDiffByHunk,
} from "../tools/render-utils";
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
import { Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
import type { HashlineToolEdit } from "./index";
import type { DiffError, DiffResult, Operation } from "./types";
@@ -222,6 +222,31 @@ function renderDiffSection(
return text;
}
function wrapEditRendererLine(line: string, width: number): string[] {
if (width <= 0) return [line];
if (line.length === 0) return [""];
const startAnsi = line.match(/^((?:\x1b\[[0-9;]*m)*)/)?.[1] ?? "";
const bodyWithReset = line.slice(startAnsi.length);
const body = bodyWithReset.endsWith("\x1b[39m") ? bodyWithReset.slice(0, -"\x1b[39m".length) : bodyWithReset;
const diffMatch = /^([+\-\s])(\s*\d+)\|(.*)$/s.exec(body);
if (!diffMatch) {
return wrapTextWithAnsi(line, width);
}
const [, marker, lineNum, content] = diffMatch;
const prefix = `${marker}${lineNum}|`;
const prefixWidth = visibleWidth(prefix);
const contentWidth = Math.max(1, width - prefixWidth);
const continuationPrefix = `${" ".repeat(Math.max(0, prefixWidth - 1))}|`;
const wrappedContent = wrapTextWithAnsi(content, contentWidth);
return wrappedContent.map(
(segment, index) => `${startAnsi}${index === 0 ? prefix : continuationPrefix}${segment}\x1b[39m`,
);
}
export const editToolRenderer = {
mergeCallAndResult: true,
@@ -357,7 +382,7 @@ export const editToolRenderer = {
}
const lines =
width > 0 ? text.split("\n").map(line => truncateToWidth(line, width, Ellipsis.Omit)) : text.split("\n");
width > 0 ? text.split("\n").flatMap(line => wrapEditRendererLine(line, width)) : text.split("\n");
cached = { key, lines };
return lines;
},
@@ -1,45 +0,0 @@
Search code snippets, and technical content.
This tool behaves more like grep than natural-language web search.
<instruction>
- Query with exact symbols, identifiers, error strings, CLI flags, filenames, import paths, and short code fragments
- Start with the smallest distinctive token; widen or add one nearby token only if the first query is too broad
- Prefer exact syntax when punctuation carries meaning, such as `Promise.withResolvers`, `useEffect(`, `--watch`, or `"direnv loading"`
- Keep `query` terse; remove filler words, prose, and request framing
- Use `code_context` only for a few disambiguating tokens such as language, library, framework, repo, runtime, or API name
- If a multi-word literal matters exactly, quote the shortest stable phrase first, then refine
- When looking for usage examples of a specific API, search the symbol first; add surrounding call syntax only when needed
</instruction>
<parameters>
- query: Grep-style code search query; use exact tokens, short fragments, or short quoted phrases
- code_context: Optional disambiguation tokens only, not a sentence
</parameters>
<examples>
Good queries:
- `Promise.withResolvers`
- `DIRENV_LOG_FORMAT`
- `"direnv loading"`
- `useState` with `code_context: react hooks`
- `app.get(` with `code_context: express`
- `ERR_REQUIRE_ESM` with `code_context: node`
Bad queries:
- `Need the official or source-backed way to silence direnv loading output`
- `How do I use Promise.withResolvers in Bun?`
- `find examples of React state hooks in TypeScript projects`
- `search GitHub for express routing docs`
</examples>
<avoid>
- Do not use this tool for broad conceptual research, comparisons, or authoritative sourcing; use `web_search`, `web_search_deep`, or `fetch` instead
- Do not put full-sentence instructions into `query` or `code_context`
- Do not pack many weak terms into one query; one strong token plus minimal context usually works better
</avoid>
<critical>
- `query` should be grep-style code search, not a natural-language request
- `code_context` is optional and should stay short
- If you need explanations, best practices, or comprehensive answers, use broader web search tools instead of this one
</critical>
+3 -1
View File
@@ -1009,7 +1009,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
}
// Add web search tools
customTools.push(...getSearchTools());
if (options.toolNames?.includes("web_search")) {
customTools.push(...getSearchTools());
}
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
const builtInToolNames = builtinTools.map(t => t.name);
@@ -16,7 +16,7 @@ const CHECK_BYTE_COUNT = 1024;
const HEADER_LINE_LIMIT = 40;
const KNOWN_GENERATOR_PATTERN =
"(?:protoc(?:-gen-[\\w-]+)?|sqlc|buf|swagger(?:-codegen)?|openapi(?:-generator)?|grpc-gateway|mockery|stringer|easyjson|deepcopy-gen|defaulter-gen|conversion-gen|client-gen|lister-gen|informer-gen|kysely-codegen)";
"(?:protoc(?:-gen-[\\w-]+)?|sqlc|buf|swagger(?:-codegen)?|openapi(?:-generator)?|grpc-gateway|mockery|stringer|easyjson|deepcopy-gen|defaulter-gen|conversion-gen|client-gen|lister-gen|informer-gen|kysely-codegen|napi-rs)";
/**
* Strong marker patterns for generated-file headers.
@@ -382,8 +382,8 @@ export function getDiffStats(diffText: string): DiffStats {
export function formatDiffStats(added: number, removed: number, hunks: number, theme: Theme): string {
const parts: string[] = [];
if (added > 0) parts.push(theme.fg("success", `+${added}`));
if (removed > 0) parts.push(theme.fg("error", `-${removed}`));
if (added > 0) parts.push(theme.fg("toolDiffAdded", `+${added}`));
if (removed > 0) parts.push(theme.fg("toolDiffRemoved", `-${removed}`));
if (hunks > 0) parts.push(theme.fg("dim", `${hunks} hunk${hunks !== 1 ? "s" : ""}`));
return parts.join(theme.fg("dim", " / "));
}
@@ -7,7 +7,7 @@ import type { Api, Model } from "@oh-my-pi/pi-ai";
import { completeSimple } from "@oh-my-pi/pi-ai";
import { logger } from "@oh-my-pi/pi-utils";
import type { ModelRegistry } from "../config/model-registry";
import { resolveModelRoleValue } from "../config/model-resolver";
import { resolveRoleSelection } from "../config/model-resolver";
import { renderPromptTemplate } from "../config/prompt-templates";
import type { Settings } from "../config/settings";
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
@@ -28,13 +28,9 @@ function getTitleModel(
const availableModels = registry.getAvailable();
if (availableModels.length === 0) return undefined;
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
const configuredSmol = resolveModelRoleValue(settings.getModelRole("smol"), availableModels, {
settings,
matchPreferences,
});
if (configuredSmol.model) {
return { model: configuredSmol.model, thinkingLevel: configuredSmol.thinkingLevel };
const titleModel = resolveRoleSelection(["commit", "smol"], settings, availableModels);
if (titleModel) {
return { model: titleModel.model, thinkingLevel: titleModel.thinkingLevel };
}
if (currentModel) {
@@ -1,208 +0,0 @@
import type { Component } from "@oh-my-pi/pi-tui";
import { Text } from "@oh-my-pi/pi-tui";
import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../../exa/mcp-client";
import type { CustomToolResult, RenderResultOptions } from "../../extensibility/custom-tools/types";
import type { Theme } from "../../modes/theme/theme";
import {
formatCount,
formatExpandHint,
formatMoreItems,
formatStatusIcon,
replaceTabs,
truncateToWidth,
} from "../../tools/render-utils";
import type { CodeSearchProviderId } from "./types";
export interface CodeSearchToolParams {
query: string;
code_context?: string;
}
export interface CodeSearchSource {
title: string;
url: string;
repository: string;
path: string;
branch: string;
snippet?: string;
totalMatches?: string;
}
export interface CodeSearchResponse {
provider: CodeSearchProviderId;
query: string;
totalResults?: number;
sources: CodeSearchSource[];
}
export interface CodeSearchRenderDetails {
response?: CodeSearchResponse;
error?: string;
provider: CodeSearchProviderId;
}
function stringifyExaCodeResponse(payload: unknown): string {
if (typeof payload === "string") return payload;
if (typeof payload === "number" || typeof payload === "boolean") return String(payload);
if (payload === null || payload === undefined) return "";
const serialized = JSON.stringify(payload, null, 2);
return typeof serialized === "string" ? serialized : "";
}
function normalizeExaCodeSearchResponse(
params: CodeSearchToolParams,
payload: unknown,
formattedSearchResponse?: string,
): CodeSearchResponse {
const snippet = formattedSearchResponse ?? stringifyExaCodeResponse(payload);
return {
provider: "exa",
query: params.query,
sources: [
{
title: params.query,
url: "https://exa.ai/",
repository: "exa",
path: "code-search",
branch: "public-mcp",
snippet: snippet.length > 0 ? snippet : undefined,
},
],
};
}
async function searchCodeWithExa(params: CodeSearchToolParams): Promise<CodeSearchResponse> {
const exaParams = params.code_context
? { query: params.query, code_context: params.code_context }
: { query: params.query };
const response = await callExaTool("get_code_context_exa", exaParams, findExaKey());
if (isSearchResponse(response)) {
return normalizeExaCodeSearchResponse(params, response, formatSearchResults(response));
}
return normalizeExaCodeSearchResponse(params, response);
}
export function formatCodeSearchForLlm(response: CodeSearchResponse): string {
const parts: string[] = [];
const summaryParts: string[] = [response.provider];
if (response.totalResults !== undefined) {
summaryParts.push(`${response.totalResults.toLocaleString()} total matches`);
}
parts.push(`Code search via ${summaryParts.join(" · ")}`);
if (response.sources.length === 0) {
parts.push("No results found.");
return parts.join("\n");
}
for (const [index, source] of response.sources.entries()) {
const metadata: string[] = [source.repository, source.path];
if (source.totalMatches) metadata.push(`${source.totalMatches} matches`);
parts.push(`[${index + 1}] ${metadata.join(" · ")}`);
parts.push(` ${source.url}`);
if (source.snippet) {
for (const line of source.snippet.split("\n").slice(0, 8)) {
parts.push(` ${line}`);
}
}
}
return parts.join("\n");
}
export async function executeCodeSearch(
params: CodeSearchToolParams,
): Promise<CustomToolResult<CodeSearchRenderDetails>> {
try {
const response = await searchCodeWithExa(params);
return {
content: [{ type: "text", text: formatCodeSearchForLlm(response) }],
details: { provider: response.provider, response },
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${message}` }],
details: { provider: "exa", error: message },
};
}
}
export function renderCodeSearchCall(
args: CodeSearchToolParams,
_options: RenderResultOptions,
theme: Theme,
): Component {
let text = `${theme.fg("toolTitle", "Code Search")} ${theme.fg("accent", truncateToWidth(args.query, 80))}`;
if (args.code_context) {
text += ` ${theme.fg("dim", truncateToWidth(args.code_context, 40))}`;
}
return new Text(text, 0, 0);
}
export function renderCodeSearchResult(
result: { content: Array<{ type: string; text?: string }>; details?: CodeSearchRenderDetails },
options: RenderResultOptions,
uiTheme: Theme,
): Component {
const details = result.details;
if (details?.error) {
return new Text(
`${formatStatusIcon("error", uiTheme)} ${uiTheme.fg("error", `Error: ${replaceTabs(details.error)}`)}`,
0,
0,
);
}
const response = details?.response;
if (!response) {
return new Text(`${formatStatusIcon("warning", uiTheme)} ${uiTheme.fg("muted", "No code search results")}`, 0, 0);
}
const resultCount = response.sources.length;
const meta: string[] = [formatCount("result", resultCount), `provider:${response.provider}`];
if (response.totalResults !== undefined) {
meta.push(`${response.totalResults.toLocaleString()} total`);
}
const expandHint = formatExpandHint(uiTheme, options.expanded, resultCount > 1);
let text = `${formatStatusIcon(resultCount > 0 ? "success" : "warning", uiTheme)} ${uiTheme.fg("dim", meta.join(uiTheme.sep.dot))}${expandHint}`;
if (resultCount === 0) {
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", "No results")}`;
return new Text(text, 0, 0);
}
const visibleSources = options.expanded ? response.sources : response.sources.slice(0, 1);
for (const [index, source] of visibleSources.entries()) {
const isLast = index === visibleSources.length - 1;
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
const cont = isLast ? " " : uiTheme.tree.vertical;
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("accent", truncateToWidth(replaceTabs(source.title), 100))}`;
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("mdLinkUrl", source.url)}`;
if (source.totalMatches) {
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("muted", `Matches: ${source.totalMatches}`)}`;
}
if (source.snippet) {
const snippetLines = source.snippet.split("\n").slice(0, options.expanded ? 6 : 3);
for (const line of snippetLines) {
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg(
"toolOutput",
truncateToWidth(replaceTabs(line), 100),
)}`;
}
}
}
if (!options.expanded && response.sources.length > visibleSources.length) {
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
"muted",
formatMoreItems(response.sources.length - visibleSources.length, "result"),
)}`;
}
return new Text(text, 0, 0);
}
+1 -36
View File
@@ -4,7 +4,6 @@
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Tavily, Kagi, Z.AI, and Synthetic
* providers with provider-specific parameters exposed conditionally.
*
* Code search is also supported via the code_search tool.
*/
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
import { StringEnum } from "@oh-my-pi/pi-ai";
@@ -13,17 +12,9 @@ import { renderPromptTemplate } from "../../config/prompt-templates";
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
import type { Theme } from "../../modes/theme/theme";
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
import codeSearchDescription from "../../prompts/tools/code-search.md" with { type: "text" };
import webSearchDescription from "../../prompts/tools/web-search.md" with { type: "text" };
import type { ToolSession } from "../../tools";
import { formatAge } from "../../tools/render-utils";
import {
type CodeSearchRenderDetails,
type CodeSearchToolParams,
executeCodeSearch,
renderCodeSearchCall,
renderCodeSearchResult,
} from "./code-search";
import { getSearchProvider, resolveProviderChain, type SearchProvider } from "./provider";
import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
import type { SearchProviderId, SearchResponse } from "./types";
@@ -262,34 +253,8 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
},
};
/** Schema for code context search */
const codeSearchParameters = Type.Object({
query: Type.String({ description: "Grep-style code search query; use exact tokens or short quoted phrases" }),
code_context: Type.Optional(Type.String({ description: "Optional disambiguation tokens only, not a sentence" })),
});
/** Code context search - optimized for code snippets and documentation */
export const codeSearchTool: CustomTool<typeof codeSearchParameters, CodeSearchRenderDetails> = {
name: "code_search",
label: "Code Search",
description: renderPromptTemplate(codeSearchDescription),
parameters: codeSearchParameters,
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
return executeCodeSearch(params);
},
renderCall(args: CodeSearchToolParams, options: RenderResultOptions, theme: Theme) {
return renderCodeSearchCall(args, options, theme);
},
renderResult(result, options, theme) {
return renderCodeSearchResult(result, options, theme);
},
};
export function getSearchTools(): CustomTool<any, any>[] {
return [webSearchCustomTool, codeSearchTool];
return [webSearchCustomTool];
}
export { getSearchProvider, setPreferredSearchProvider } from "./provider";
@@ -20,8 +20,6 @@ export type SearchProviderId =
| "kagi"
| "synthetic";
export type CodeSearchProviderId = "exa";
export function isSearchProviderId(value: string): value is SearchProviderId {
return [
"exa",
@@ -0,0 +1,41 @@
import { describe, expect, test } from "bun:test";
import { joinPatch } from "@oh-my-pi/pi-coding-agent/commit/git";
describe("joinPatch", () => {
test("preserves space character in empty context line at end of patch", () => {
// This simulates a hunk ending with an empty context line represented as " \n"
const parts = [
"@@ -1,2 +1,2 @@\n",
"foo\n",
"@@ -10,4 +10,4 @@\n",
"line1\n",
"-old\n",
"+new\n",
" \n", // Empty context line = space + newline
];
const result = joinPatch(parts);
// The result should end with a space character (the empty context line)
// but NOT start/end with multiple newlines
expect(result.endsWith(" \n")).toBe(true);
expect(result.replace(/[ \t]+$/, "")).toEqual(result); // No trailing spaces should be removed
});
test("normalizes multiple trailing newlines in parts", () => {
const parts = ["line1\n", "line2\n", "line3"];
const result = joinPatch(parts);
// Should join with single newlines and end with one newline
expect(result.endsWith("\n")).toBe(true);
});
test("adds newline to parts that are missing them", () => {
const parts = ["line1", "line2"];
const result = joinPatch(parts);
// Should add newlines to both parts
expect(result.includes("line1\n")).toBe(true);
expect(result.endsWith("line2\n")).toBe(true);
});
});
@@ -4,6 +4,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { Effort, type OpenAICompat, type ThinkingConfig } from "@oh-my-pi/pi-ai";
import { kNoAuth, MODEL_ROLES, ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { hookFetch, Snowflake } from "@oh-my-pi/pi-utils";
@@ -18,6 +19,7 @@ describe("ModelRegistry", () => {
});
beforeEach(async () => {
_resetSettingsForTest();
tempDir = path.join(os.tmpdir(), `pi-test-model-registry-${Snowflake.next()}`);
fs.mkdirSync(tempDir, { recursive: true });
modelsJsonPath = path.join(tempDir, "models.json");
@@ -25,6 +27,7 @@ describe("ModelRegistry", () => {
});
afterEach(() => {
_resetSettingsForTest();
authStorage.close();
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true });
@@ -1034,6 +1037,38 @@ describe("ModelRegistry", () => {
expect(model.baseUrl).toBe(initialBaseUrl);
});
});
describe("disabled provider filtering", () => {
test("getAvailable and getDiscoverableProviders exclude disabled providers from settings", async () => {
writeRawModelsJson({
ollama: {
baseUrl: "http://127.0.0.1:11434/v1",
api: "openai-completions",
auth: "none",
discovery: { type: "ollama" },
},
});
await authStorage.set("github-copilot", [
{
type: "oauth",
access: "tid=1;proxy-ep=proxy.individual.githubcopilot.com;exp=9999999999",
refresh: "refresh-individual",
expires: Date.now() + 60_000,
},
]);
await Settings.init({
inMemory: true,
overrides: {
disabledProviders: ["github-copilot", "ollama"],
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
expect(registry.getAvailable().some(model => model.provider === "github-copilot")).toBe(false);
expect(registry.getDiscoverableProviders()).not.toContain("ollama");
});
});
describe("runtime discovery", () => {
test("auto-discovers ollama models without provider config", async () => {
using _hook = hookFetch(input => {
@@ -1,39 +0,0 @@
import { describe, expect, it } from "bun:test";
import { hookFetch } from "@oh-my-pi/pi-utils";
import { executeCodeSearch } from "../../src/web/search/code-search";
function getFirstTextContent(result: { content: Array<{ type: string; text?: string }> }): string {
const firstContent = result.content[0];
if (firstContent?.type === "text") return firstContent.text ?? "";
return "";
}
describe("code_search", () => {
it("normalizes Exa response and surfaces it as code search result", async () => {
using _hook = hookFetch(
() =>
new Response(
JSON.stringify({
result: {
content: [
{
type: "text",
text: "Need the official or source-backed way to silence direnv loading output.",
},
],
},
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
const result = await executeCodeSearch({ query: "DIRENV_LOG_FORMAT direnv loading silence" });
const output = getFirstTextContent(result);
expect(output).toContain("Code search via exa");
expect(output).toContain("Need the official or source-backed way to silence direnv loading output.");
expect(result.details?.provider).toBe("exa");
expect(result.details?.response?.sources[0]?.snippet).toContain(
"Need the official or source-backed way to silence direnv loading output.",
);
});
});
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-natives",
"version": "13.17.0",
"version": "13.17.1",
"description": "Native Rust bindings for grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/omp-stats",
"version": "13.17.0",
"version": "13.17.1",
"description": "Local observability dashboard for pi AI usage statistics",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/swarm-extension",
"version": "13.17.0",
"version": "13.17.1",
"description": "Swarm orchestration extension for omp",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Derek Rynd",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-tui",
"version": "13.17.0",
"version": "13.17.1",
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "@oh-my-pi/pi-utils",
"version": "13.17.0",
"version": "13.17.1",
"description": "Shared utilities for pi packages",
"homepage": "https://github.com/can1357/oh-my-pi",
"author": "Can Boluk",
+1 -1
View File
@@ -1,4 +1,4 @@
[toolchain]
channel = "nightly"
channel = "nightly-2026-03-27"
components = ["rustfmt", "clippy", "rust-analyzer"]
targets = ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bun
/**
* Checks spoofed external tool versions against their latest GitHub releases.
*
* We impersonate several external tools (Gemini CLI, Antigravity) via User-Agent
* strings. When these tools release new versions, the upstream service may start
* rejecting or deprioritizing older versions. This script detects drift so we
* can bump before users hit 403s/429s.
*
* Usage:
* bun scripts/check-spoofed-versions.ts # check and report
* bun scripts/check-spoofed-versions.ts --update # update source in-place
*/
import * as path from "node:path";
const PROVIDER_FILE = path.join(
import.meta.dir,
"../packages/ai/src/providers/google-gemini-cli.ts",
);
interface VersionCheck {
/** Human label for the report. */
name: string;
/** Regex to extract the current hardcoded version from PROVIDER_FILE. */
sourcePattern: RegExp;
/** GitHub owner/repo to fetch latest release from. */
repo: string;
/** Extract semver from the release tag name (e.g. "v0.35.3" -> "0.35.3"). */
parseTag: (tag: string) => string | null;
}
/** Fetch latest non-prerelease tag from a GitHub repo. */
async function fetchLatestGitHubRelease(repo: string, parseTag: (tag: string) => string | null): Promise<string | null> {
try {
// /releases/latest only returns non-prerelease, non-draft releases
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
headers: { Accept: "application/vnd.github+json", "User-Agent": "oh-my-pi/version-check" },
});
if (!res.ok) return null;
const data = (await res.json()) as { tag_name?: string };
return data.tag_name ? parseTag(data.tag_name) : null;
} catch {
return null;
}
}
const SEMVER_RE = /(\d+\.\d+\.\d+)/;
const checks: VersionCheck[] = [
{
name: "Gemini CLI",
sourcePattern: /PI_AI_GEMINI_CLI_VERSION\s*\|\|\s*"(\d+\.\d+\.\d+)"/,
repo: "google-gemini/gemini-cli",
parseTag: (tag) => SEMVER_RE.exec(tag)?.[1] ?? null,
},
];
async function run() {
const doUpdate = process.argv.includes("--update");
let source = await Bun.file(PROVIDER_FILE).text();
let anyDrift = false;
let anyUpdate = false;
let anyChecked = false;
for (const check of checks) {
const match = check.sourcePattern.exec(source);
if (!match?.[1]) {
console.error(`[WARN] Could not extract current ${check.name} version from source`);
continue;
}
const current = match[1];
const latest = await fetchLatestGitHubRelease(check.repo, check.parseTag);
if (!latest) {
console.error(`[FAIL] Could not fetch latest ${check.name} version from ${check.repo}`);
continue;
}
anyChecked = true;
if (current === latest) {
console.log(`[OK] ${check.name}: ${current} (up to date)`);
} else {
console.log(`[DRIFT] ${check.name}: ${current} -> ${latest}`);
anyDrift = true;
if (doUpdate) {
source = source.replace(match[0], match[0].replace(current, latest));
anyUpdate = true;
console.log(` Updated in source.`);
}
}
}
if (anyUpdate) {
await Bun.write(PROVIDER_FILE, source);
console.log(`\nWrote updates to ${path.relative(process.cwd(), PROVIDER_FILE)}`);
}
if (!anyChecked) {
console.error("\nNo version checks succeeded. Cannot verify freshness.");
process.exit(1);
}
if (anyDrift && !doUpdate) {
console.log("\nRun with --update to apply version bumps.");
process.exit(1);
}
}
run();