mirror of
https://github.com/cline/cline.git
synced 2026-09-07 04:44:58 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e45137c24 |
+10
@@ -51,3 +51,13 @@ test-results
|
||||
|
||||
# Smoke test results (generated)
|
||||
evals/smoke-tests/results/
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tg
|
||||
.next
|
||||
|
||||
target
|
||||
packages-wip
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[submodule "evals/cline-bench"]
|
||||
path = evals/cline-bench
|
||||
url = https://github.com/cline/cline-bench.git
|
||||
[submodule "sdk-wip"]
|
||||
path = sdk-wip
|
||||
url = https://github.com/cline/sdk-wip.git
|
||||
|
||||
@@ -12,7 +12,6 @@ import type { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import Spinner from "ink-spinner"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { buildApiHandler } from "@/core/api"
|
||||
import type { Controller } from "@/core/controller"
|
||||
import { refreshOcaModels } from "@/core/controller/models/refreshOcaModels"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
@@ -745,7 +744,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
}
|
||||
const currentMode = stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfig = stateManager.getApiConfiguration()
|
||||
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
controller.task.updateApiHandler(apiConfig, currentMode)
|
||||
}, [controller, stateManager])
|
||||
|
||||
const setReasoningEffortForMode = useCallback(
|
||||
@@ -1046,7 +1045,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
if (controller?.task) {
|
||||
const currentMode = stateManager.getGlobalSettingsKey("mode")
|
||||
const freshApiConfig = stateManager.getApiConfiguration()
|
||||
controller.task.api = buildApiHandler({ ...freshApiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
controller.task.updateApiHandler(freshApiConfig, currentMode)
|
||||
}
|
||||
|
||||
refreshModelIds()
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import type { ApiProvider } from "@shared/api"
|
||||
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@shared/storage"
|
||||
import { buildApiHandler } from "@/core/api"
|
||||
import type { Controller } from "@/core/controller"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import type { BedrockConfig } from "../components/BedrockSetup"
|
||||
@@ -73,7 +72,7 @@ export async function applyProviderConfig(options: ApplyProviderConfigOptions):
|
||||
if (controller?.task) {
|
||||
const currentMode = stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfig = stateManager.getApiConfiguration()
|
||||
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
controller.task.updateApiHandler(apiConfig, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +136,6 @@ export async function applyBedrockConfig(options: ApplyBedrockConfigOptions): Pr
|
||||
if (controller?.task) {
|
||||
const currentMode = stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfig = stateManager.getApiConfiguration()
|
||||
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
|
||||
controller.task.updateApiHandler(apiConfig, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,18 @@
|
||||
"useDefineForClassFields": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"paths": {
|
||||
"@cline/agents": [
|
||||
"../packages/agents/dist/index.d.ts"
|
||||
],
|
||||
"@cline/agents/*": [
|
||||
"../packages/agents/dist/*"
|
||||
],
|
||||
"@cline/llms": [
|
||||
"../packages/llms/dist/index.d.ts"
|
||||
],
|
||||
"@cline/llms/*": [
|
||||
"../packages/llms/dist/*"
|
||||
],
|
||||
"@/*": [
|
||||
"../src/*"
|
||||
],
|
||||
|
||||
@@ -19,6 +19,11 @@ const aliasResolverPlugin = {
|
||||
name: "alias-resolver",
|
||||
setup(build) {
|
||||
const aliases = {
|
||||
"@cline/agents/extensions": path.resolve(__dirname, "sdk-wip/packages/agents/dist/index.js"),
|
||||
"@cline/agents/hooks": path.resolve(__dirname, "sdk-wip/packages/agents/dist/index.js"),
|
||||
"@cline/agents": path.resolve(__dirname, "sdk-wip/packages/agents/dist"),
|
||||
"@cline/core": path.resolve(__dirname, "sdk-wip/packages/core/dist"),
|
||||
"@cline/llms": path.resolve(__dirname, "sdk-wip/packages/llms/dist"),
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
"@core": path.resolve(__dirname, "src/core"),
|
||||
"@integrations": path.resolve(__dirname, "src/integrations"),
|
||||
|
||||
Generated
+44
-79
@@ -162,7 +162,7 @@
|
||||
},
|
||||
"cli": {
|
||||
"name": "cline",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
@@ -1576,6 +1576,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -3129,6 +3130,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
@@ -4106,6 +4108,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -5796,8 +5799,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.57.1",
|
||||
@@ -5810,8 +5812,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.57.1",
|
||||
@@ -5824,8 +5825,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.57.1",
|
||||
@@ -5838,8 +5838,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.57.1",
|
||||
@@ -5852,8 +5851,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.57.1",
|
||||
@@ -5866,8 +5864,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.57.1",
|
||||
@@ -5880,8 +5877,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.57.1",
|
||||
@@ -5894,8 +5890,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -5908,8 +5903,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.57.1",
|
||||
@@ -5922,8 +5916,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -5936,8 +5929,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.57.1",
|
||||
@@ -5950,8 +5942,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -5964,8 +5955,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.57.1",
|
||||
@@ -5978,8 +5968,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -5992,8 +5981,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.57.1",
|
||||
@@ -6006,8 +5994,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -6020,8 +6007,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -6034,8 +6020,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.57.1",
|
||||
@@ -6048,8 +6033,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.57.1",
|
||||
@@ -6062,8 +6046,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.57.1",
|
||||
@@ -6076,8 +6059,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.57.1",
|
||||
@@ -6090,8 +6072,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.57.1",
|
||||
@@ -6104,8 +6085,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.57.1",
|
||||
@@ -6118,8 +6098,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.57.1",
|
||||
@@ -6132,8 +6111,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@sap-ai-sdk/ai-api": {
|
||||
"version": "2.7.0",
|
||||
@@ -7805,6 +7783,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz",
|
||||
"integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -7867,6 +7846,7 @@
|
||||
"integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -8801,6 +8781,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -9765,6 +9746,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -11167,7 +11149,8 @@
|
||||
"version": "0.0.1367902",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
|
||||
"integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==",
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.2",
|
||||
@@ -12184,6 +12167,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -13491,6 +13475,7 @@
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -13769,6 +13754,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.7.tgz",
|
||||
"integrity": "sha512-QHyxhNF5VonF5cRmdAJD/UPucB9nRx3FozWMjQrDGfBxfAL9lpyu72/MlFPgloS1TMTGsOt7YN6dTPPA6mh0Aw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
@@ -15022,6 +15008,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
@@ -15371,6 +15358,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
@@ -19107,6 +19095,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -21814,6 +21803,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -22135,6 +22125,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -22216,7 +22207,6 @@
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22233,7 +22223,6 @@
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22250,7 +22239,6 @@
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22267,7 +22255,6 @@
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22284,7 +22271,6 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22301,7 +22287,6 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22318,7 +22303,6 @@
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22335,7 +22319,6 @@
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22352,7 +22335,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22369,7 +22351,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22386,7 +22367,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22403,7 +22383,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22420,7 +22399,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22437,7 +22415,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22454,7 +22431,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22471,7 +22447,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22488,7 +22463,6 @@
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22505,7 +22479,6 @@
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22522,7 +22495,6 @@
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22539,7 +22511,6 @@
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22556,7 +22527,6 @@
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22573,7 +22543,6 @@
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22590,7 +22559,6 @@
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22607,7 +22575,6 @@
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22624,7 +22591,6 @@
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22641,7 +22607,6 @@
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -22697,7 +22662,6 @@
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
@@ -23632,6 +23596,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
Submodule
+1
Submodule sdk-wip added at 814a2e42b7
@@ -0,0 +1,226 @@
|
||||
import { type ApiHandler, buildApiHandler } from "@core/api"
|
||||
import type {
|
||||
ApiStream,
|
||||
ApiStreamChunk,
|
||||
ApiStreamTextChunk,
|
||||
ApiStreamThinkingChunk,
|
||||
ApiStreamToolCallsChunk,
|
||||
ApiStreamUsageChunk,
|
||||
} from "@core/api/transform/stream"
|
||||
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
|
||||
import { isNextGenModelFamily } from "@utils/model-utils"
|
||||
import { ClineError, ClineErrorType } from "@/services/error/ClineError"
|
||||
import type { ApiConfiguration } from "@/shared/api"
|
||||
import type { Mode } from "@/shared/storage/types"
|
||||
|
||||
export interface InitialStreamRetryDecision {
|
||||
isAuthError: boolean
|
||||
isBalanceError: boolean
|
||||
shouldRetry: boolean
|
||||
}
|
||||
|
||||
export type CoreLoopStatus = "continue" | "complete" | "failed"
|
||||
|
||||
export interface CoreLoopTurnResult<TInput, TOutput> {
|
||||
status: CoreLoopStatus
|
||||
nextInput?: TInput
|
||||
output?: TOutput
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface CoreLoopParams<TInput, TOutput> {
|
||||
initialInput: TInput
|
||||
runTurn: (input: TInput, iteration: number) => Promise<CoreLoopTurnResult<TInput, TOutput>>
|
||||
shouldAbort?: () => boolean
|
||||
}
|
||||
|
||||
export type CoreLoopResult<TOutput> =
|
||||
| { status: "complete"; output: TOutput }
|
||||
| { status: "failed"; error?: string }
|
||||
| { status: "aborted" }
|
||||
|
||||
export interface CoreStreamUsage {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
cacheWriteTokens: number
|
||||
cacheReadTokens: number
|
||||
totalCost?: number
|
||||
}
|
||||
|
||||
export interface CoreStreamResult {
|
||||
requestId?: string
|
||||
assistantText: string
|
||||
assistantTextSignature?: string
|
||||
usage: CoreStreamUsage
|
||||
aborted: boolean
|
||||
interrupted: boolean
|
||||
}
|
||||
|
||||
export interface CoreStreamParams {
|
||||
stream: ApiStream
|
||||
onUsageChunk?: (chunk: ApiStreamUsageChunk, state: CoreStreamResult) => Promise<void> | void
|
||||
onTextChunk?: (chunk: ApiStreamTextChunk, state: CoreStreamResult) => Promise<void> | void
|
||||
onToolCallChunk?: (chunk: ApiStreamToolCallsChunk, state: CoreStreamResult) => Promise<void> | void
|
||||
onReasoningChunk?: (chunk: ApiStreamThinkingChunk, state: CoreStreamResult) => Promise<void> | void
|
||||
onChunkProcessed?: (chunk: ApiStreamChunk, state: CoreStreamResult) => Promise<"break" | void> | "break" | void
|
||||
shouldAbort?: () => boolean
|
||||
onAbort?: (state: CoreStreamResult) => Promise<void> | void
|
||||
}
|
||||
|
||||
export class CoreAgent {
|
||||
static readonly AUTO_CONDENSE_THRESHOLD = 0.75
|
||||
private apiHandler?: ApiHandler
|
||||
|
||||
constructor(params?: { apiConfiguration: ApiConfiguration; mode: Mode }) {
|
||||
if (params) {
|
||||
this.apiHandler = buildApiHandler(params.apiConfiguration, params.mode)
|
||||
}
|
||||
}
|
||||
|
||||
initializeApiHandler(apiConfiguration: ApiConfiguration, mode: Mode): ApiHandler {
|
||||
this.apiHandler = buildApiHandler(apiConfiguration, mode)
|
||||
return this.apiHandler
|
||||
}
|
||||
|
||||
getApiHandler(): ApiHandler {
|
||||
if (!this.apiHandler) {
|
||||
throw new Error("CoreAgent API handler has not been initialized")
|
||||
}
|
||||
return this.apiHandler
|
||||
}
|
||||
|
||||
getModel(): ReturnType<ApiHandler["getModel"]> {
|
||||
return this.getApiHandler().getModel()
|
||||
}
|
||||
|
||||
createMessage(...args: Parameters<ApiHandler["createMessage"]>): ReturnType<ApiHandler["createMessage"]> {
|
||||
return this.getApiHandler().createMessage(...args)
|
||||
}
|
||||
|
||||
abortCurrentRequest(): void {
|
||||
this.getApiHandler().abort?.()
|
||||
}
|
||||
|
||||
getApiStreamUsage(): ReturnType<NonNullable<ApiHandler["getApiStreamUsage"]>> | undefined {
|
||||
return this.getApiHandler().getApiStreamUsage?.()
|
||||
}
|
||||
|
||||
getLastRequestIdSafe(): string | undefined {
|
||||
const apiLike = this.getApiHandler() as Partial<{
|
||||
getLastRequestId: () => string | undefined
|
||||
lastGenerationId?: string
|
||||
}>
|
||||
return apiLike.getLastRequestId?.() ?? apiLike.lastGenerationId
|
||||
}
|
||||
|
||||
async runLoop<TInput, TOutput>(params: CoreLoopParams<TInput, TOutput>): Promise<CoreLoopResult<TOutput>> {
|
||||
let iteration = 0
|
||||
let currentInput = params.initialInput
|
||||
|
||||
while (!params.shouldAbort?.()) {
|
||||
iteration += 1
|
||||
const turn = await params.runTurn(currentInput, iteration)
|
||||
if (turn.status === "complete") {
|
||||
return { status: "complete", output: turn.output as TOutput }
|
||||
}
|
||||
if (turn.status === "failed") {
|
||||
return { status: "failed", error: turn.error }
|
||||
}
|
||||
currentInput = turn.nextInput as TInput
|
||||
}
|
||||
|
||||
return { status: "aborted" }
|
||||
}
|
||||
|
||||
async consumeStream(params: CoreStreamParams): Promise<CoreStreamResult> {
|
||||
const state: CoreStreamResult = {
|
||||
requestId: undefined,
|
||||
assistantText: "",
|
||||
assistantTextSignature: undefined,
|
||||
usage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalCost: undefined,
|
||||
},
|
||||
aborted: false,
|
||||
interrupted: false,
|
||||
}
|
||||
|
||||
for await (const chunk of params.stream) {
|
||||
state.requestId = state.requestId ?? chunk.id
|
||||
|
||||
switch (chunk.type) {
|
||||
case "usage":
|
||||
state.usage.inputTokens += chunk.inputTokens
|
||||
state.usage.outputTokens += chunk.outputTokens
|
||||
state.usage.cacheWriteTokens += chunk.cacheWriteTokens ?? 0
|
||||
state.usage.cacheReadTokens += chunk.cacheReadTokens ?? 0
|
||||
state.usage.totalCost = chunk.totalCost ?? state.usage.totalCost
|
||||
await params.onUsageChunk?.(chunk, state)
|
||||
break
|
||||
case "text":
|
||||
state.assistantText += chunk.text || ""
|
||||
state.assistantTextSignature = chunk.signature || state.assistantTextSignature
|
||||
await params.onTextChunk?.(chunk, state)
|
||||
break
|
||||
case "tool_calls":
|
||||
await params.onToolCallChunk?.(chunk, state)
|
||||
break
|
||||
case "reasoning":
|
||||
await params.onReasoningChunk?.(chunk, state)
|
||||
break
|
||||
}
|
||||
|
||||
const shouldBreak = await params.onChunkProcessed?.(chunk, state)
|
||||
if (shouldBreak === "break") {
|
||||
state.interrupted = true
|
||||
break
|
||||
}
|
||||
|
||||
if (params.shouldAbort?.()) {
|
||||
state.aborted = true
|
||||
await params.onAbort?.(state)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
isAutoCondenseEnabledForModel(useAutoCondense: boolean, modelId: string): boolean {
|
||||
return useAutoCondense && isNextGenModelFamily(modelId)
|
||||
}
|
||||
|
||||
getAutoCondenseThresholdTokens(contextWindow: number, maxAllowedSize: number): number {
|
||||
const roundedThreshold = Math.floor(contextWindow * CoreAgent.AUTO_CONDENSE_THRESHOLD)
|
||||
return Math.min(roundedThreshold, maxAllowedSize)
|
||||
}
|
||||
|
||||
shouldCompactBeforeNextRequest(
|
||||
previousRequestTotalTokens: number,
|
||||
api: ApiHandler,
|
||||
modelId: string,
|
||||
useAutoCondense: boolean,
|
||||
): boolean {
|
||||
const { contextWindow, maxAllowedSize } = getContextWindowInfo(api)
|
||||
if (this.isAutoCondenseEnabledForModel(useAutoCondense, modelId)) {
|
||||
const thresholdTokens = this.getAutoCondenseThresholdTokens(contextWindow, maxAllowedSize)
|
||||
return previousRequestTotalTokens >= thresholdTokens
|
||||
}
|
||||
|
||||
return previousRequestTotalTokens >= maxAllowedSize
|
||||
}
|
||||
|
||||
classifyInitialStreamRetry(error: unknown, providerId: string, modelId: string): InitialStreamRetryDecision {
|
||||
const parsedError = ClineError.transform(error, modelId, providerId)
|
||||
const isAuthError = parsedError.isErrorType(ClineErrorType.Auth)
|
||||
const isBalanceError = parsedError.isErrorType(ClineErrorType.Balance)
|
||||
return {
|
||||
isAuthError,
|
||||
isBalanceError,
|
||||
shouldRetry: !isAuthError && !isBalanceError,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
import { providers } from "@cline/llms"
|
||||
import type { ClineContent, ClineImageContentBlock, ClineStorageMessage, ClineTextContentBlock } from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { SELECTOR_SEPARATOR } from "@/shared/vsCodeSelectorUtils"
|
||||
import { VsCodeLmHandler } from "./vscode-lm"
|
||||
|
||||
export const VSCODE_LM_SELECTOR_HEADER = "x-cline-vscode-lm-selector"
|
||||
|
||||
let createHandlersPatched = false
|
||||
let vscodeLmProviderRegistered = false
|
||||
let originalCreateHandler: ((config: providers.ProviderConfig) => providers.ApiHandler) | undefined
|
||||
let originalCreateHandlerAsync: ((config: providers.ProviderConfig) => Promise<providers.ApiHandler>) | undefined
|
||||
const extensionProviderFactories = new Map<string, (config: providers.ProviderConfig) => providers.ApiHandler>()
|
||||
|
||||
function ensureProvidersCreateHandlerPatched() {
|
||||
if (createHandlersPatched) {
|
||||
return
|
||||
}
|
||||
const mutableProviders = providers as typeof providers & {
|
||||
createHandler: (config: providers.ProviderConfig) => providers.ApiHandler
|
||||
createHandlerAsync: (config: providers.ProviderConfig) => Promise<providers.ApiHandler>
|
||||
}
|
||||
|
||||
originalCreateHandler = mutableProviders.createHandler.bind(providers)
|
||||
originalCreateHandlerAsync = mutableProviders.createHandlerAsync.bind(providers)
|
||||
|
||||
mutableProviders.createHandler = (config: providers.ProviderConfig): providers.ApiHandler => {
|
||||
const extensionFactory = extensionProviderFactories.get(config.providerId)
|
||||
if (extensionFactory) {
|
||||
return extensionFactory(config)
|
||||
}
|
||||
if (!originalCreateHandler) {
|
||||
throw new Error("LLMS createHandler has not been initialized")
|
||||
}
|
||||
return originalCreateHandler(config)
|
||||
}
|
||||
|
||||
mutableProviders.createHandlerAsync = async (config: providers.ProviderConfig): Promise<providers.ApiHandler> => {
|
||||
const extensionFactory = extensionProviderFactories.get(config.providerId)
|
||||
if (extensionFactory) {
|
||||
return extensionFactory(config)
|
||||
}
|
||||
if (!originalCreateHandlerAsync) {
|
||||
throw new Error("LLMS createHandlerAsync has not been initialized")
|
||||
}
|
||||
return originalCreateHandlerAsync(config)
|
||||
}
|
||||
|
||||
createHandlersPatched = true
|
||||
}
|
||||
|
||||
function normalizeImageMediaType(mediaType: string): "image/jpeg" | "image/png" | "image/gif" | "image/webp" {
|
||||
switch (mediaType) {
|
||||
case "image/jpeg":
|
||||
case "image/png":
|
||||
case "image/gif":
|
||||
case "image/webp":
|
||||
return mediaType
|
||||
default:
|
||||
return "image/png"
|
||||
}
|
||||
}
|
||||
|
||||
function toClineContentBlock(block: providers.ContentBlock): ClineContent {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { type: "text", text: block.text }
|
||||
case "image":
|
||||
return {
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: normalizeImageMediaType(block.mediaType),
|
||||
data: block.data,
|
||||
},
|
||||
}
|
||||
case "file":
|
||||
return {
|
||||
type: "text",
|
||||
text: `File: ${block.path}\n${block.content}`,
|
||||
}
|
||||
case "tool_use":
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: block.id,
|
||||
name: block.name,
|
||||
input: block.input,
|
||||
}
|
||||
case "tool_result":
|
||||
return {
|
||||
type: "tool_result",
|
||||
tool_use_id: block.tool_use_id,
|
||||
content:
|
||||
typeof block.content === "string"
|
||||
? block.content
|
||||
: block.content.map((contentBlock): ClineTextContentBlock | ClineImageContentBlock => {
|
||||
switch (contentBlock.type) {
|
||||
case "text":
|
||||
return { type: "text", text: contentBlock.text }
|
||||
case "image":
|
||||
return {
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: normalizeImageMediaType(contentBlock.mediaType),
|
||||
data: contentBlock.data,
|
||||
},
|
||||
}
|
||||
case "file":
|
||||
return { type: "text", text: `File: ${contentBlock.path}\n${contentBlock.content}` }
|
||||
default: {
|
||||
const exhaustiveCheck: never = contentBlock
|
||||
throw new Error(
|
||||
`Unsupported provider tool_result content block type: ${JSON.stringify(exhaustiveCheck)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
is_error: block.is_error,
|
||||
}
|
||||
case "thinking":
|
||||
return {
|
||||
type: "thinking",
|
||||
thinking: block.thinking,
|
||||
signature: block.signature ?? "",
|
||||
}
|
||||
case "redacted_thinking":
|
||||
return {
|
||||
type: "redacted_thinking",
|
||||
data: block.data,
|
||||
}
|
||||
default: {
|
||||
const exhaustiveCheck: never = block
|
||||
throw new Error(`Unsupported provider content block type: ${JSON.stringify(exhaustiveCheck)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toClineMessages(messages: providers.Message[]): ClineStorageMessage[] {
|
||||
return messages.map((message) => {
|
||||
if (typeof message.content === "string") {
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content.map((block) => toClineContentBlock(block)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function parseVsCodeLmSelector(modelId?: string): Record<string, string> | undefined {
|
||||
if (!modelId) {
|
||||
return undefined
|
||||
}
|
||||
const parts = modelId.split(SELECTOR_SEPARATOR).filter(Boolean)
|
||||
if (parts.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
const [vendor, family, version, id] = parts
|
||||
const selector: Record<string, string> = {}
|
||||
if (vendor) {
|
||||
selector.vendor = vendor
|
||||
}
|
||||
if (family) {
|
||||
selector.family = family
|
||||
}
|
||||
if (version) {
|
||||
selector.version = version
|
||||
}
|
||||
if (id) {
|
||||
selector.id = id
|
||||
}
|
||||
return Object.keys(selector).length > 0 ? selector : undefined
|
||||
}
|
||||
|
||||
function toRegisteredModelInfo(modelId: string, modelInfo: ReturnType<VsCodeLmHandler["getModel"]>["info"]): providers.ModelInfo {
|
||||
const capabilities: providers.ModelInfo["capabilities"] = []
|
||||
if (modelInfo.supportsImages) {
|
||||
capabilities.push("images")
|
||||
}
|
||||
if (modelInfo.supportsPromptCache) {
|
||||
capabilities.push("prompt-cache")
|
||||
}
|
||||
if (modelInfo.supportsReasoning) {
|
||||
capabilities.push("reasoning")
|
||||
}
|
||||
if (modelInfo.supportsGlobalEndpoint) {
|
||||
capabilities.push("global-endpoint")
|
||||
}
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
name: modelInfo.name,
|
||||
description: modelInfo.description,
|
||||
maxTokens: modelInfo.maxTokens,
|
||||
contextWindow: modelInfo.contextWindow,
|
||||
temperature: modelInfo.temperature,
|
||||
capabilities: capabilities.length > 0 ? capabilities : undefined,
|
||||
pricing:
|
||||
modelInfo.inputPrice !== undefined ||
|
||||
modelInfo.outputPrice !== undefined ||
|
||||
modelInfo.cacheWritesPrice !== undefined ||
|
||||
modelInfo.cacheReadsPrice !== undefined
|
||||
? {
|
||||
input: modelInfo.inputPrice,
|
||||
output: modelInfo.outputPrice,
|
||||
cacheWrite: modelInfo.cacheWritesPrice,
|
||||
cacheRead: modelInfo.cacheReadsPrice,
|
||||
}
|
||||
: undefined,
|
||||
thinkingConfig: modelInfo.thinkingConfig
|
||||
? {
|
||||
maxBudget: modelInfo.thinkingConfig.maxBudget,
|
||||
outputPrice: modelInfo.thinkingConfig.outputPrice,
|
||||
thinkingLevel: modelInfo.thinkingConfig.geminiThinkingLevel,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureVsCodeLmProviderRegistered() {
|
||||
if (vscodeLmProviderRegistered) {
|
||||
return
|
||||
}
|
||||
ensureProvidersCreateHandlerPatched()
|
||||
|
||||
extensionProviderFactories.set("vscode-lm", (config: providers.ProviderConfig): providers.ApiHandler => {
|
||||
const selectorFromHeader = config?.headers?.[VSCODE_LM_SELECTOR_HEADER]
|
||||
let selector: Record<string, string> | undefined
|
||||
|
||||
if (selectorFromHeader) {
|
||||
try {
|
||||
selector = JSON.parse(selectorFromHeader)
|
||||
} catch {
|
||||
selector = undefined
|
||||
}
|
||||
}
|
||||
selector = selector ?? parseVsCodeLmSelector(config?.modelId)
|
||||
|
||||
const handler = new VsCodeLmHandler({
|
||||
vsCodeLmModelSelector: selector,
|
||||
})
|
||||
|
||||
return {
|
||||
getMessages: (systemPrompt: string, messages: providers.Message[]) => ({
|
||||
systemPrompt,
|
||||
messages: toClineMessages(messages),
|
||||
}),
|
||||
createMessage: (
|
||||
systemPrompt: string,
|
||||
messages: providers.Message[],
|
||||
_tools?: providers.ToolDefinition[],
|
||||
): providers.ApiStream =>
|
||||
handler.createMessage(systemPrompt, toClineMessages(messages)) as unknown as providers.ApiStream,
|
||||
getModel: () => {
|
||||
const model = handler.getModel()
|
||||
return {
|
||||
id: model.id,
|
||||
info: toRegisteredModelInfo(model.id, model.info),
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vscodeLmProviderRegistered = true
|
||||
Logger.debug("Registered extension provider handler for vscode-lm")
|
||||
}
|
||||
@@ -392,7 +392,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
const totalInputTokens: number = await this.calculateTotalInputTokens(vsCodeLmMessages)
|
||||
|
||||
// Accumulate the text and count at the end of the stream to reduce token counting overhead.
|
||||
let accumulatedText: string = ""
|
||||
let accumulatedText = ""
|
||||
|
||||
try {
|
||||
// Create the response stream with minimal required options
|
||||
@@ -498,17 +498,17 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
|
||||
// Return original error if it's already an Error instance
|
||||
throw error
|
||||
} else if (typeof error === "object" && error !== null) {
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
// Handle error-like objects
|
||||
const errorDetails = JSON.stringify(error, null, 2)
|
||||
Logger.error("Cline <Language Model API>: Stream error object:", errorDetails)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorDetails}`)
|
||||
} else {
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
Logger.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
Logger.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { getHooksEnabledSafe } from "@core/hooks/hooks-utils"
|
||||
import { tryAcquireTaskLockWithRetry } from "@core/task/TaskLockUtils"
|
||||
import { detectWorkspaceRoots } from "@core/workspace/detection"
|
||||
@@ -370,10 +369,10 @@ export class Controller {
|
||||
// Switch to act mode
|
||||
this.stateManager.setGlobalState("mode", modeToSwitchTo)
|
||||
|
||||
// Update API handler with new mode (buildApiHandler now selects provider based on mode)
|
||||
// Update API handler with new mode
|
||||
if (this.task) {
|
||||
const apiConfiguration = this.stateManager.getApiConfiguration()
|
||||
this.task.api = buildApiHandler({ ...apiConfiguration, ulid: this.task.ulid }, modeToSwitchTo)
|
||||
this.task.updateApiHandler(apiConfiguration, modeToSwitchTo)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
@@ -394,10 +393,10 @@ export class Controller {
|
||||
// Capture mode switch telemetry | Capture regardless of if we know the taskId
|
||||
telemetryService.captureModeSwitch(this.task?.ulid ?? "0", modeToSwitchTo)
|
||||
|
||||
// Update API handler with new mode (buildApiHandler now selects provider based on mode)
|
||||
// Update API handler with new mode
|
||||
if (this.task) {
|
||||
const apiConfiguration = this.stateManager.getApiConfiguration()
|
||||
this.task.api = buildApiHandler({ ...apiConfiguration, ulid: this.task.ulid }, modeToSwitchTo)
|
||||
this.task.updateApiHandler(apiConfiguration, modeToSwitchTo)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
@@ -547,7 +546,7 @@ export class Controller {
|
||||
await fetchRemoteConfig(this)
|
||||
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
|
||||
this.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
@@ -598,7 +597,7 @@ export class Controller {
|
||||
this.stateManager.setGlobalState("welcomeViewCompleted", true)
|
||||
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
|
||||
this.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
@@ -716,7 +715,7 @@ export class Controller {
|
||||
|
||||
await this.postStateToWebview()
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
|
||||
this.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
// Dont send settingsButtonClicked because its bad ux if user is on welcome
|
||||
}
|
||||
@@ -736,7 +735,7 @@ export class Controller {
|
||||
this.stateManager.setApiConfiguration(updatedConfig)
|
||||
await this.postStateToWebview()
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
|
||||
this.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,7 +775,7 @@ export class Controller {
|
||||
await this.postStateToWebview()
|
||||
this.accountService
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
|
||||
this.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import { convertProtoToApiProvider } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
import { buildApiHandler } from "@/core/api"
|
||||
import { ApiHandlerOptions, ApiProvider } from "@/shared/api"
|
||||
import { UpdateApiConfigurationRequestNew } from "@/shared/proto/index.cline"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
@@ -41,7 +40,8 @@ function parseFieldMask(updateMask: string[]): {
|
||||
function getAlternateModeField(fieldName: string): string | null {
|
||||
if (fieldName.startsWith("planMode")) {
|
||||
return fieldName.replace("planMode", "actMode")
|
||||
} else if (fieldName.startsWith("actMode")) {
|
||||
}
|
||||
if (fieldName.startsWith("actMode")) {
|
||||
return fieldName.replace("actMode", "planMode")
|
||||
}
|
||||
return null
|
||||
@@ -141,14 +141,7 @@ export async function updateApiConfiguration(controller: Controller, request: Up
|
||||
// Update the task's API handler if there's an active task
|
||||
if (controller.task) {
|
||||
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
|
||||
// Build updated config
|
||||
controller.task.api = buildApiHandler(
|
||||
{
|
||||
...controller.stateManager.getApiConfiguration(),
|
||||
ulid: controller.task.ulid,
|
||||
},
|
||||
currentMode,
|
||||
)
|
||||
controller.task.updateApiHandler(controller.stateManager.getApiConfiguration(), currentMode)
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import { UpdateApiConfigurationPartialRequest } from "@shared/proto/cline/models"
|
||||
import { convertProtoToApiConfiguration } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
@@ -44,7 +43,7 @@ export async function updateApiConfigurationPartial(
|
||||
controller.stateManager.setApiConfiguration(updatedConfig)
|
||||
if (controller.task) {
|
||||
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
|
||||
controller.task.api = buildApiHandler({ ...updatedConfig, ulid: controller.task.ulid }, currentMode)
|
||||
controller.task.updateApiHandler(updatedConfig, currentMode)
|
||||
}
|
||||
|
||||
// Notify webview
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
fromProtobufOpenAiCompatibleModelInfo,
|
||||
} from "@shared/proto-conversions/models/typeConversion"
|
||||
import { OpenaiReasoningEffort } from "@shared/storage/types"
|
||||
import { buildApiHandler } from "@/core/api"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
@@ -124,10 +123,7 @@ export async function updateApiConfigurationProto(
|
||||
// Update the task's API handler if there's an active task
|
||||
if (controller.task) {
|
||||
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
|
||||
controller.task.api = buildApiHandler(
|
||||
{ ...convertedApiConfigurationFromProto, ulid: controller.task.ulid },
|
||||
currentMode,
|
||||
)
|
||||
controller.task.updateApiHandler(convertedApiConfigurationFromProto, currentMode)
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import { PlanActMode, McpDisplayMode as ProtoMcpDisplayMode, UpdateSettingsRequest } from "@shared/proto/cline/state"
|
||||
import { convertProtoToApiProvider } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
@@ -49,11 +48,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
|
||||
if (controller.task) {
|
||||
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfigForHandler = {
|
||||
...convertedApiConfigurationFromProto,
|
||||
ulid: controller.task.ulid,
|
||||
}
|
||||
controller.task.api = buildApiHandler(apiConfigForHandler, currentMode)
|
||||
controller.task.updateApiHandler(convertedApiConfigurationFromProto, currentMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +180,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
telemetryService.captureAutoCondenseToggle(
|
||||
controller.task.ulid,
|
||||
request.useAutoCondense,
|
||||
controller.task.api.getModel().id,
|
||||
controller.task.getModel().id,
|
||||
)
|
||||
}
|
||||
controller.stateManager.setGlobalState("useAutoCondense", request.useAutoCondense)
|
||||
@@ -312,7 +307,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
controller.task.ulid,
|
||||
"native-tool-call",
|
||||
request.nativeToolCallEnabled,
|
||||
controller.task.api.getModel().id,
|
||||
controller.task.getModel().id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { buildApiHandler } from "@core/api"
|
||||
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import { PlanActMode, UpdateSettingsRequestCli } from "@shared/proto/cline/state"
|
||||
import { convertProtoToApiProvider } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
@@ -118,11 +116,7 @@ export async function updateSettingsCli(controller: Controller, request: UpdateS
|
||||
|
||||
if (controller.task) {
|
||||
const currentMode = controller.stateManager.getGlobalSettingsKey("mode")
|
||||
const apiConfigForHandler = {
|
||||
...controller.stateManager.getApiConfiguration(),
|
||||
ulid: controller.task.ulid,
|
||||
}
|
||||
controller.task.api = buildApiHandler(apiConfigForHandler, currentMode)
|
||||
controller.task.updateApiHandler(controller.stateManager.getApiConfiguration(), currentMode)
|
||||
}
|
||||
|
||||
// Update telemetry setting
|
||||
@@ -144,7 +138,7 @@ export async function updateSettingsCli(controller: Controller, request: UpdateS
|
||||
telemetryService.captureAutoCondenseToggle(
|
||||
controller.task.ulid,
|
||||
useAutoCondense,
|
||||
controller.task.api.getModel().id,
|
||||
controller.task.getModel().id,
|
||||
)
|
||||
}
|
||||
controller.stateManager.setGlobalState("useAutoCondense", useAutoCondense)
|
||||
|
||||
@@ -4,6 +4,8 @@ import * as fs from "fs/promises"
|
||||
import type { McpHub } from "@/services/mcp/McpHub"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
const REMOTE_CONFIG_WATCHER_GRACE_MS = 300
|
||||
|
||||
/**
|
||||
* Synchronizes remote MCP servers from remote config to the local MCP settings file
|
||||
* This allows admins to centrally configure MCP servers that are automatically deployed to users
|
||||
@@ -33,6 +35,7 @@ export async function syncRemoteMcpServersToSettings(
|
||||
// Read current settings
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
const initialSerializedConfig = JSON.stringify(config)
|
||||
|
||||
// Ensure mcpServers object exists
|
||||
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
||||
@@ -81,9 +84,18 @@ export async function syncRemoteMcpServersToSettings(
|
||||
}
|
||||
|
||||
try {
|
||||
const nextSerializedConfig = JSON.stringify(config)
|
||||
if (initialSerializedConfig === nextSerializedConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
// Write back to file
|
||||
await fs.writeFile(settingsPath, JSON.stringify(config, null, 2))
|
||||
} finally {
|
||||
// Keep the flag set briefly to outlive chokidar's awaitWriteFinish delay.
|
||||
// Otherwise the watcher may process our own remote-config write and reconnect servers.
|
||||
await new Promise((resolve) => setTimeout(resolve, REMOTE_CONFIG_WATCHER_GRACE_MS))
|
||||
|
||||
// Always clear flag, even if write fails
|
||||
if (mcpHub) {
|
||||
mcpHub.setIsUpdatingFromRemoteConfig(false)
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
import { Agent, type AgentEvent, type AgentHooks, getClineDefaultSystemPrompt, type Tool } from "@cline/agents"
|
||||
import { createBuiltinTools } from "@cline/core"
|
||||
import { providers } from "@cline/llms"
|
||||
import type { Hooks as HookInputs } from "@core/hooks/hook-factory"
|
||||
import { getHooksEnabledSafe } from "@core/hooks/hooks-utils"
|
||||
import { formatContentBlockToMarkdown } from "@integrations/misc/export-markdown"
|
||||
import { telemetryService } from "@services/telemetry"
|
||||
import type { ApiConfiguration } from "@shared/api"
|
||||
import type { ClineSay } from "@shared/ExtensionMessage"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { type ClineContent, ClineMessageModelInfo } from "@/shared/messages"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { ApiProviderInfo } from "../api"
|
||||
import { ensureVsCodeLmProviderRegistered, VSCODE_LM_SELECTOR_HEADER } from "../api/providers/registry"
|
||||
import type { MessageStateHandler } from "./message-state"
|
||||
import type { CreateTaskAgentHooksOptions } from "./TaskHookExtensionAdapter"
|
||||
import type { HookExecution } from "./types/HookExecution"
|
||||
|
||||
type ProviderConnectionConfig = {
|
||||
apiKey?: string
|
||||
baseUrl?: string
|
||||
headers?: Record<string, string>
|
||||
knownModels?: Record<string, providers.ModelInfo>
|
||||
}
|
||||
|
||||
type RegisteredModelInfo = providers.ModelInfo
|
||||
|
||||
function toRegisteredModelInfo(modelId: string, modelInfo: ApiProviderInfo["model"]["info"]): RegisteredModelInfo {
|
||||
const capabilities: RegisteredModelInfo["capabilities"] = []
|
||||
if (modelInfo.supportsImages) {
|
||||
capabilities.push("images")
|
||||
}
|
||||
if (modelInfo.supportsPromptCache) {
|
||||
capabilities.push("prompt-cache")
|
||||
}
|
||||
if (modelInfo.supportsReasoning) {
|
||||
capabilities.push("reasoning")
|
||||
}
|
||||
if (modelInfo.supportsGlobalEndpoint) {
|
||||
capabilities.push("global-endpoint")
|
||||
}
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
name: modelInfo.name,
|
||||
description: modelInfo.description,
|
||||
maxTokens: modelInfo.maxTokens,
|
||||
contextWindow: modelInfo.contextWindow,
|
||||
temperature: modelInfo.temperature,
|
||||
capabilities: capabilities.length > 0 ? capabilities : undefined,
|
||||
pricing:
|
||||
modelInfo.inputPrice !== undefined ||
|
||||
modelInfo.outputPrice !== undefined ||
|
||||
modelInfo.cacheWritesPrice !== undefined ||
|
||||
modelInfo.cacheReadsPrice !== undefined
|
||||
? {
|
||||
input: modelInfo.inputPrice,
|
||||
output: modelInfo.outputPrice,
|
||||
cacheWrite: modelInfo.cacheWritesPrice,
|
||||
cacheRead: modelInfo.cacheReadsPrice,
|
||||
}
|
||||
: undefined,
|
||||
thinkingConfig: modelInfo.thinkingConfig
|
||||
? {
|
||||
maxBudget: modelInfo.thinkingConfig.maxBudget,
|
||||
outputPrice: modelInfo.thinkingConfig.outputPrice,
|
||||
thinkingLevel: modelInfo.thinkingConfig.geminiThinkingLevel,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
type AgentRuntimeHost = {
|
||||
taskId: string
|
||||
ulid: string
|
||||
cwd: string
|
||||
taskState: {
|
||||
apiRequestCount: number
|
||||
apiRequestsSinceLastTodoUpdate: number
|
||||
abort: boolean
|
||||
}
|
||||
say: (type: ClineSay, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<number | undefined>
|
||||
postStateToWebview: () => Promise<void>
|
||||
getCurrentProviderInfo: () => ApiProviderInfo
|
||||
getApiConfiguration: () => ApiConfiguration
|
||||
loadContext: (
|
||||
userContent: ClineContent[],
|
||||
includeFileDetails: boolean,
|
||||
useCompactPrompt: boolean,
|
||||
) => Promise<[ClineContent[], string, boolean]>
|
||||
messageStateHandler: MessageStateHandler
|
||||
setActiveHookExecution: (hookExecution: HookExecution) => Promise<void>
|
||||
clearActiveHookExecution: () => Promise<void>
|
||||
runTaskLifecycleHook: <K extends "TaskStart" | "TaskResume">(params: {
|
||||
hookName: K
|
||||
hookInput: HookInputs[K]
|
||||
}) => Promise<{ cancel?: boolean; contextModification?: string; errorMessage?: string; wasCancelled: boolean }>
|
||||
runUserPromptSubmitHook: (
|
||||
userContent: ClineContent[],
|
||||
) => Promise<{ cancel?: boolean; contextModification?: string; errorMessage?: string; wasCancelled: boolean }>
|
||||
handleHookCancellation: (hookName: string, wasCancelled: boolean) => Promise<void>
|
||||
createAgentHooks: (options: CreateTaskAgentHooksOptions) => AgentHooks
|
||||
cancelTask: () => Promise<void>
|
||||
}
|
||||
|
||||
export type TaskAgentRunContext = {
|
||||
phase: "initial_task" | "resume" | "continue"
|
||||
initialTask?: string
|
||||
resumePreviousState?: {
|
||||
lastMessageTs: string
|
||||
messageCount: string
|
||||
conversationHistoryDeleted: string
|
||||
}
|
||||
userPromptHookContent?: ClineContent[]
|
||||
}
|
||||
|
||||
export class TaskAgentRuntime {
|
||||
private agent: InstanceType<typeof Agent> | undefined
|
||||
private agentSignature: string | undefined
|
||||
private readonly builtInTools: Tool[]
|
||||
private readonly host: AgentRuntimeHost
|
||||
private pendingRunContext: TaskAgentRunContext | undefined
|
||||
private streamedReasoning = ""
|
||||
|
||||
constructor(host: AgentRuntimeHost) {
|
||||
this.host = host
|
||||
this.builtInTools = createBuiltinTools({ cwd: host.cwd })
|
||||
}
|
||||
|
||||
async run(userContent: ClineContent[], runContext: TaskAgentRunContext): Promise<void> {
|
||||
this.host.taskState.apiRequestCount++
|
||||
this.host.taskState.apiRequestsSinceLastTodoUpdate++
|
||||
this.streamedReasoning = ""
|
||||
|
||||
// const [parsedUserContent, environmentDetails] = await this.host.loadContext(userContent, includeFileDetails, false)
|
||||
// const normalizedUserContent = [...parsedUserContent]
|
||||
// if (environmentDetails) {
|
||||
// normalizedUserContent.push({ type: "text", text: environmentDetails })
|
||||
// }
|
||||
const normalizedUserContent = [...userContent]
|
||||
|
||||
const userMessage = normalizedUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n")
|
||||
await this.host.say("api_req_started", JSON.stringify({ request: userMessage }))
|
||||
|
||||
const providerInfo = this.host.getCurrentProviderInfo()
|
||||
const modelInfo: ClineMessageModelInfo = {
|
||||
modelId: providerInfo.model.id,
|
||||
providerId: providerInfo.providerId,
|
||||
mode: providerInfo.mode,
|
||||
}
|
||||
|
||||
await this.host.messageStateHandler.addToApiConversationHistory({
|
||||
role: "user",
|
||||
content: normalizedUserContent,
|
||||
ts: Date.now(),
|
||||
})
|
||||
|
||||
const customHeader = await buildClineExtraHeaders()
|
||||
|
||||
this.pendingRunContext = runContext
|
||||
const { agent, reusedExistingAgent } = this.getOrCreateAgent(providerInfo, customHeader)
|
||||
const result = await (async () => {
|
||||
try {
|
||||
return reusedExistingAgent ? await agent.continue(userMessage) : await agent.run(userMessage)
|
||||
} finally {
|
||||
this.pendingRunContext = undefined
|
||||
}
|
||||
})()
|
||||
this.agent = agent
|
||||
|
||||
if (result.text) {
|
||||
await this.host.say("text", result.text, undefined, undefined, false)
|
||||
}
|
||||
|
||||
await this.host.messageStateHandler.addToApiConversationHistory({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: result.text || "" }],
|
||||
modelInfo,
|
||||
metrics: {
|
||||
tokens: {
|
||||
prompt: result.usage.inputTokens || 0,
|
||||
completion: result.usage.outputTokens || 0,
|
||||
cached: (result.usage.cacheWriteTokens || 0) + (result.usage.cacheReadTokens || 0),
|
||||
},
|
||||
cost: result.usage.totalCost,
|
||||
},
|
||||
ts: Date.now(),
|
||||
})
|
||||
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.host.ulid,
|
||||
modelInfo.providerId,
|
||||
modelInfo.modelId,
|
||||
"assistant",
|
||||
modelInfo.mode,
|
||||
{
|
||||
tokensIn: result.usage.inputTokens || 0,
|
||||
tokensOut: result.usage.outputTokens || 0,
|
||||
cacheWriteTokens: result.usage.cacheWriteTokens || 0,
|
||||
cacheReadTokens: result.usage.cacheReadTokens || 0,
|
||||
totalCost: result.usage.totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
await this.host.postStateToWebview()
|
||||
}
|
||||
|
||||
public abort(): void {
|
||||
this.agent?.abort()
|
||||
}
|
||||
|
||||
private getOrCreateAgent(
|
||||
providerInfo: ReturnType<AgentRuntimeHost["getCurrentProviderInfo"]>,
|
||||
customHeader: Record<string, string>,
|
||||
): {
|
||||
agent: InstanceType<typeof Agent>
|
||||
reusedExistingAgent: boolean
|
||||
} {
|
||||
const apiConfig = this.host.getApiConfiguration()
|
||||
if (providerInfo.providerId === "vscode-lm") {
|
||||
ensureVsCodeLmProviderRegistered()
|
||||
}
|
||||
const providerConfig = this.resolveProviderConfig(providerInfo, apiConfig)
|
||||
const nextSignature = JSON.stringify({
|
||||
providerId: providerInfo.providerId,
|
||||
modelId: providerInfo.model.id,
|
||||
apiKey: providerConfig.apiKey ?? "",
|
||||
baseUrl: providerConfig.baseUrl ?? "",
|
||||
headers: providerConfig.headers ?? {},
|
||||
knownModels: providerConfig.knownModels ?? {},
|
||||
})
|
||||
|
||||
if (this.agent && this.agentSignature === nextSignature) {
|
||||
return { agent: this.agent, reusedExistingAgent: true }
|
||||
}
|
||||
|
||||
const defaultHeaders: Record<string, string> = {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
"X-Task-ID": this.host.ulid || "",
|
||||
}
|
||||
Object.assign(defaultHeaders, customHeader)
|
||||
|
||||
const hooks = this.createAgentHooks()
|
||||
const agent = new Agent({
|
||||
providerId: providerInfo.providerId,
|
||||
modelId: providerInfo.model.id,
|
||||
apiKey: providerConfig.apiKey,
|
||||
baseUrl: providerConfig.baseUrl,
|
||||
headers: providerConfig.headers,
|
||||
knownModels: providerConfig.knownModels,
|
||||
systemPrompt: getClineDefaultSystemPrompt("VS Code", this.host.cwd),
|
||||
tools: this.builtInTools,
|
||||
hooks,
|
||||
maxIterations: 50,
|
||||
onEvent: async (event: AgentEvent) => {
|
||||
switch (event.type) {
|
||||
case "content_start":
|
||||
if (event.contentType === "text") {
|
||||
const nextText = event.accumulated ?? event.text
|
||||
if (nextText !== undefined) {
|
||||
await this.host.say("text", nextText, undefined, undefined, true)
|
||||
}
|
||||
}
|
||||
if (event.contentType === "reasoning" && event.reasoning !== undefined) {
|
||||
this.streamedReasoning += event.reasoning
|
||||
await this.host.say("reasoning", this.streamedReasoning, undefined, undefined, true)
|
||||
}
|
||||
if (event.contentType === "tool" && event.toolName) {
|
||||
Logger.debug(`[Task ${this.host.taskId}] Agent tool call start: ${event.toolName}`)
|
||||
}
|
||||
break
|
||||
case "content_end":
|
||||
if (event.contentType === "reasoning") {
|
||||
const finalReasoning = event.reasoning ?? this.streamedReasoning
|
||||
if (finalReasoning) {
|
||||
await this.host.say("reasoning", finalReasoning, undefined, undefined, false)
|
||||
}
|
||||
}
|
||||
break
|
||||
case "error":
|
||||
Logger.error(`[Task ${this.host.taskId}] Agent runtime error`, event.error)
|
||||
break
|
||||
}
|
||||
},
|
||||
})
|
||||
this.agent = agent
|
||||
this.agentSignature = nextSignature
|
||||
return { agent, reusedExistingAgent: false }
|
||||
}
|
||||
|
||||
private createAgentHooks(): AgentHooks {
|
||||
return this.host.createAgentHooks({
|
||||
onRunStart: () => this.handleRunStartHook(),
|
||||
buildPendingToolInfo: (toolName, toolInput) => this.buildPendingToolInfo(toolName, toolInput),
|
||||
toHookStringParameters: (input) => this.toHookStringParameters(input),
|
||||
createHookContextBlock: (hookName, contextModification) => this.createHookContextBlock(hookName, contextModification),
|
||||
safeJsonStringify: (value) => this.safeJsonStringify(value),
|
||||
})
|
||||
}
|
||||
|
||||
private async handleRunStartHook(): Promise<{ cancel?: boolean; context?: string } | undefined> {
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const runContext = this.pendingRunContext
|
||||
if (!runContext) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contextBlocks: string[] = []
|
||||
|
||||
if (runContext.phase === "initial_task") {
|
||||
const taskStartResult = await this.host.runTaskLifecycleHook({
|
||||
hookName: "TaskStart",
|
||||
hookInput: {
|
||||
taskStart: {
|
||||
taskMetadata: {
|
||||
taskId: this.host.taskId,
|
||||
ulid: this.host.ulid,
|
||||
initialTask: runContext.initialTask || "",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (taskStartResult.cancel) {
|
||||
await this.host.handleHookCancellation("TaskStart", taskStartResult.wasCancelled)
|
||||
await this.host.cancelTask()
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const taskStartContext = this.createHookContextBlock("TaskStart", taskStartResult.contextModification)
|
||||
if (taskStartContext) {
|
||||
contextBlocks.push(taskStartContext)
|
||||
}
|
||||
}
|
||||
|
||||
if (runContext.phase === "resume") {
|
||||
const taskResumeResult = await this.host.runTaskLifecycleHook({
|
||||
hookName: "TaskResume",
|
||||
hookInput: {
|
||||
taskResume: {
|
||||
taskMetadata: {
|
||||
taskId: this.host.taskId,
|
||||
ulid: this.host.ulid,
|
||||
},
|
||||
previousState: runContext.resumePreviousState || {
|
||||
lastMessageTs: "",
|
||||
messageCount: "0",
|
||||
conversationHistoryDeleted: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (taskResumeResult.cancel) {
|
||||
await this.host.handleHookCancellation("TaskResume", taskResumeResult.wasCancelled)
|
||||
await this.host.cancelTask()
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const taskResumeContext = this.createHookContextBlock("TaskResume", taskResumeResult.contextModification)
|
||||
if (taskResumeContext) {
|
||||
contextBlocks.push(taskResumeContext)
|
||||
}
|
||||
}
|
||||
|
||||
const userPromptHookContent = runContext.userPromptHookContent ?? []
|
||||
const userPromptResult = await this.host.runUserPromptSubmitHook(userPromptHookContent)
|
||||
|
||||
if (userPromptResult.cancel) {
|
||||
await this.host.handleHookCancellation("UserPromptSubmit", userPromptResult.wasCancelled)
|
||||
await this.host.cancelTask()
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const userPromptContext = this.createHookContextBlock("UserPromptSubmit", userPromptResult.contextModification)
|
||||
if (userPromptContext) {
|
||||
contextBlocks.push(userPromptContext)
|
||||
}
|
||||
|
||||
if (contextBlocks.length > 0) {
|
||||
return { context: contextBlocks.join("\n") }
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
private createHookContextBlock(source: string, contextModification?: string): string | undefined {
|
||||
if (!contextModification) {
|
||||
return
|
||||
}
|
||||
|
||||
const contextText = contextModification.trim()
|
||||
if (!contextText) {
|
||||
return
|
||||
}
|
||||
|
||||
const lines = contextText.split("\n")
|
||||
const firstLine = lines[0]
|
||||
let contextType = "general"
|
||||
let content = contextText
|
||||
const typeMatch = /^([A-Z_]+):\s*(.*)/.exec(firstLine)
|
||||
if (typeMatch) {
|
||||
contextType = typeMatch[1].toLowerCase()
|
||||
const remainingLines = lines.slice(1).filter((line) => line.trim())
|
||||
content = typeMatch[2] ? [typeMatch[2], ...remainingLines].join("\n") : remainingLines.join("\n")
|
||||
}
|
||||
|
||||
if (contextType === "general") {
|
||||
return `<hook_context source="${source}">\n${content}\n</hook_context>`
|
||||
}
|
||||
|
||||
return `<hook_context source="${source}" type="${contextType}">\n${content}\n</hook_context>`
|
||||
}
|
||||
|
||||
private toHookParameters(input: unknown): Record<string, unknown> {
|
||||
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
||||
return {}
|
||||
}
|
||||
return { ...(input as Record<string, unknown>) }
|
||||
}
|
||||
|
||||
private toHookStringParameters(input: unknown): Record<string, string> {
|
||||
const params = this.toHookParameters(input)
|
||||
const entries = Object.entries(params).map(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return [key, value] as const
|
||||
}
|
||||
return [key, this.safeJsonStringify(value)] as const
|
||||
})
|
||||
return Object.fromEntries(entries)
|
||||
}
|
||||
|
||||
private buildPendingToolInfo(toolName: string, input: unknown): Record<string, unknown> {
|
||||
const pendingToolInfo: Record<string, unknown> = { tool: toolName }
|
||||
const params = this.toHookParameters(input)
|
||||
|
||||
for (const key of ["path", "command", "regex", "url", "tool_name", "server_name", "uri"]) {
|
||||
if (params[key] !== undefined) {
|
||||
pendingToolInfo[key] = params[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof params.content === "string") {
|
||||
pendingToolInfo.content = params.content.slice(0, 200)
|
||||
}
|
||||
if (typeof params.diff === "string") {
|
||||
pendingToolInfo.diff = params.diff.slice(0, 200)
|
||||
}
|
||||
|
||||
return pendingToolInfo
|
||||
}
|
||||
|
||||
private safeJsonStringify(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
private resolveProviderConfig(providerInfo: ApiProviderInfo, apiConfig: ApiConfiguration): ProviderConnectionConfig {
|
||||
const { providerId, mode, model } = providerInfo
|
||||
switch (providerId) {
|
||||
case "anthropic":
|
||||
return { apiKey: apiConfig.apiKey, baseUrl: apiConfig.anthropicBaseUrl }
|
||||
case "openrouter":
|
||||
return { apiKey: apiConfig.openRouterApiKey }
|
||||
case "cline":
|
||||
return {
|
||||
apiKey: apiConfig.clineAccountId,
|
||||
knownModels:
|
||||
model?.id && model?.info ? { [model.id]: toRegisteredModelInfo(model.id, model.info) } : undefined,
|
||||
}
|
||||
case "openai":
|
||||
return {
|
||||
apiKey: apiConfig.openAiApiKey,
|
||||
baseUrl: apiConfig.openAiBaseUrl,
|
||||
headers: apiConfig.openAiHeaders,
|
||||
}
|
||||
case "gemini":
|
||||
return { apiKey: apiConfig.geminiApiKey, baseUrl: apiConfig.geminiBaseUrl }
|
||||
case "vertex":
|
||||
return { apiKey: apiConfig.geminiApiKey, baseUrl: apiConfig.geminiBaseUrl }
|
||||
case "ollama":
|
||||
return { apiKey: apiConfig.ollamaApiKey, baseUrl: apiConfig.ollamaBaseUrl }
|
||||
case "lmstudio":
|
||||
return { baseUrl: apiConfig.lmStudioBaseUrl }
|
||||
case "vscode-lm": {
|
||||
const selector =
|
||||
mode === "plan" ? apiConfig.planModeVsCodeLmModelSelector : apiConfig.actModeVsCodeLmModelSelector
|
||||
return {
|
||||
headers: selector ? { [VSCODE_LM_SELECTOR_HEADER]: JSON.stringify(selector) } : undefined,
|
||||
}
|
||||
}
|
||||
case "deepseek":
|
||||
return { apiKey: apiConfig.deepSeekApiKey }
|
||||
case "together":
|
||||
return { apiKey: apiConfig.togetherApiKey }
|
||||
case "litellm":
|
||||
return { apiKey: apiConfig.liteLlmApiKey, baseUrl: apiConfig.liteLlmBaseUrl }
|
||||
case "nebius":
|
||||
return { apiKey: apiConfig.nebiusApiKey }
|
||||
case "sambanova":
|
||||
return { apiKey: apiConfig.sambanovaApiKey }
|
||||
case "cerebras":
|
||||
return { apiKey: apiConfig.cerebrasApiKey }
|
||||
case "baseten":
|
||||
return { apiKey: apiConfig.basetenApiKey }
|
||||
case "huggingface":
|
||||
return { apiKey: apiConfig.huggingFaceApiKey }
|
||||
case "huawei-cloud-maas":
|
||||
return { apiKey: apiConfig.huaweiCloudMaasApiKey }
|
||||
case "vercel-ai-gateway":
|
||||
return { apiKey: apiConfig.vercelAiGatewayApiKey }
|
||||
case "aihubmix":
|
||||
return {
|
||||
apiKey: apiConfig.aihubmixApiKey,
|
||||
baseUrl: apiConfig.aihubmixBaseUrl,
|
||||
headers: apiConfig.aihubmixAppCode ? { "APP-Code": apiConfig.aihubmixAppCode } : undefined,
|
||||
}
|
||||
case "hicap":
|
||||
return {
|
||||
apiKey: apiConfig.hicapApiKey,
|
||||
headers: apiConfig.hicapApiKey ? { "api-key": apiConfig.hicapApiKey } : undefined,
|
||||
}
|
||||
case "nousResearch":
|
||||
return { apiKey: apiConfig.nousResearchApiKey }
|
||||
case "requesty":
|
||||
return { apiKey: apiConfig.requestyApiKey, baseUrl: apiConfig.requestyBaseUrl }
|
||||
case "xai":
|
||||
return { apiKey: apiConfig.xaiApiKey }
|
||||
case "groq":
|
||||
return { apiKey: apiConfig.groqApiKey }
|
||||
case "fireworks":
|
||||
return { apiKey: apiConfig.fireworksApiKey }
|
||||
default:
|
||||
Logger.warn(`[Task ${this.host.taskId}] No explicit provider config mapping for provider "${providerId}"`)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
import type { AgentHooks } from "@cline/agents"
|
||||
import { executeHook } from "@core/hooks/hook-executor"
|
||||
import type { Hooks as HookInputs } from "@core/hooks/hook-factory"
|
||||
import { getHooksEnabledSafe } from "@core/hooks/hooks-utils"
|
||||
import { executePreCompactHookWithCleanup, type HookExecution } from "@core/hooks/precompact-executor"
|
||||
import type { ClineMessage, ClineSay } from "@shared/ExtensionMessage"
|
||||
import type { ClineContent, ClineStorageMessage } from "@shared/messages/content"
|
||||
import type { ContextManager } from "@/core/context/context-management/ContextManager"
|
||||
import type { MessageStateHandler } from "@/core/task/message-state"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
type WithStateLock = <T>(fn: () => T | Promise<T>) => Promise<T>
|
||||
|
||||
export interface TaskHookResult {
|
||||
cancel?: boolean
|
||||
contextModification?: string
|
||||
errorMessage?: string
|
||||
wasCancelled: boolean
|
||||
}
|
||||
|
||||
export interface TaskHookAdapterContext {
|
||||
taskId: string
|
||||
ulid: string
|
||||
taskState: {
|
||||
abort: boolean
|
||||
didFinishAbortingStream: boolean
|
||||
activeHookExecution?: HookExecution
|
||||
}
|
||||
messageStateHandler: MessageStateHandler
|
||||
say: (type: ClineSay, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<number | undefined>
|
||||
postStateToWebview: () => Promise<void>
|
||||
cancelTask: () => Promise<void>
|
||||
withStateLock?: WithStateLock
|
||||
}
|
||||
|
||||
export interface CreateTaskAgentHooksOptions {
|
||||
onRunStart: () => Promise<{ cancel?: boolean; context?: string } | undefined>
|
||||
buildPendingToolInfo: (toolName: string, toolInput: unknown) => Record<string, unknown> | undefined
|
||||
toHookStringParameters: (input: unknown) => Record<string, string>
|
||||
createHookContextBlock: (hookName: string, contextModification?: string) => string | undefined
|
||||
safeJsonStringify: (value: unknown) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter that ports task-coupled hook behavior to the agents-extension boundary.
|
||||
* This keeps hook semantics in one place while task/runtime migration completes.
|
||||
*/
|
||||
export class TaskHookExtensionAdapter {
|
||||
constructor(private readonly ctx: TaskHookAdapterContext) {}
|
||||
|
||||
async setActiveHookExecution(hookExecution: HookExecution): Promise<void> {
|
||||
if (this.ctx.withStateLock) {
|
||||
await this.ctx.withStateLock(() => {
|
||||
this.ctx.taskState.activeHookExecution = hookExecution
|
||||
})
|
||||
return
|
||||
}
|
||||
this.ctx.taskState.activeHookExecution = hookExecution
|
||||
}
|
||||
|
||||
async clearActiveHookExecution(): Promise<void> {
|
||||
if (this.ctx.withStateLock) {
|
||||
await this.ctx.withStateLock(() => {
|
||||
this.ctx.taskState.activeHookExecution = undefined
|
||||
})
|
||||
return
|
||||
}
|
||||
this.ctx.taskState.activeHookExecution = undefined
|
||||
}
|
||||
|
||||
async getActiveHookExecution(): Promise<HookExecution | undefined> {
|
||||
if (this.ctx.withStateLock) {
|
||||
return await this.ctx.withStateLock(() => this.ctx.taskState.activeHookExecution)
|
||||
}
|
||||
return this.ctx.taskState.activeHookExecution
|
||||
}
|
||||
|
||||
async runTaskLifecycleHook<K extends "TaskStart" | "TaskResume">(params: {
|
||||
hookName: K
|
||||
hookInput: HookInputs[K]
|
||||
}): Promise<TaskHookResult> {
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return { wasCancelled: false }
|
||||
}
|
||||
|
||||
const result = await executeHook({
|
||||
hookName: params.hookName,
|
||||
hookInput: params.hookInput,
|
||||
isCancellable: true,
|
||||
say: this.ctx.say,
|
||||
setActiveHookExecution: async (execution) => this.setActiveHookExecution(execution as HookExecution),
|
||||
clearActiveHookExecution: async () => this.clearActiveHookExecution(),
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
taskId: this.ctx.taskId,
|
||||
hooksEnabled,
|
||||
})
|
||||
|
||||
return {
|
||||
cancel: result.cancel,
|
||||
contextModification: result.contextModification,
|
||||
errorMessage: result.errorMessage,
|
||||
wasCancelled: result.wasCancelled,
|
||||
}
|
||||
}
|
||||
|
||||
async runUserPromptSubmitHook(userContent: ClineContent[]): Promise<TaskHookResult> {
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return { wasCancelled: false }
|
||||
}
|
||||
|
||||
const { extractUserPromptFromContent } = await import("@/core/task/utils/extractUserPromptFromContent")
|
||||
const promptText = extractUserPromptFromContent(userContent)
|
||||
|
||||
const result = await executeHook({
|
||||
hookName: "UserPromptSubmit",
|
||||
hookInput: {
|
||||
userPromptSubmit: {
|
||||
prompt: promptText,
|
||||
attachments: [],
|
||||
},
|
||||
},
|
||||
isCancellable: true,
|
||||
say: this.ctx.say,
|
||||
setActiveHookExecution: async (execution) => this.setActiveHookExecution(execution as HookExecution),
|
||||
clearActiveHookExecution: async () => this.clearActiveHookExecution(),
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
taskId: this.ctx.taskId,
|
||||
hooksEnabled,
|
||||
})
|
||||
|
||||
if (result.cancel === true && result.wasCancelled) {
|
||||
this.ctx.taskState.didFinishAbortingStream = true
|
||||
await this.ctx.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
await this.ctx.messageStateHandler.overwriteApiConversationHistory(
|
||||
this.ctx.messageStateHandler.getApiConversationHistory(),
|
||||
)
|
||||
await this.ctx.postStateToWebview()
|
||||
}
|
||||
|
||||
return {
|
||||
cancel: result.cancel,
|
||||
contextModification: result.contextModification,
|
||||
errorMessage: result.errorMessage,
|
||||
wasCancelled: result.wasCancelled,
|
||||
}
|
||||
}
|
||||
|
||||
async runTaskCancelHook(taskMetadata: { taskId: string; ulid: string; completionStatus: string }): Promise<void> {
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
await executeHook({
|
||||
hookName: "TaskCancel",
|
||||
hookInput: {
|
||||
taskCancel: {
|
||||
taskMetadata,
|
||||
},
|
||||
},
|
||||
isCancellable: false,
|
||||
say: this.ctx.say,
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
taskId: this.ctx.taskId,
|
||||
hooksEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
async runPreCompactHookWithCleanup(params: {
|
||||
apiConversationHistory: ClineStorageMessage[]
|
||||
conversationHistoryDeletedRange: [number, number] | undefined
|
||||
contextManager: ContextManager
|
||||
clineMessages: ClineMessage[]
|
||||
deletedRange?: [number, number]
|
||||
}): Promise<{ contextModification?: string }> {
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return await executePreCompactHookWithCleanup({
|
||||
taskId: this.ctx.taskId,
|
||||
ulid: this.ctx.ulid,
|
||||
apiConversationHistory: params.apiConversationHistory,
|
||||
conversationHistoryDeletedRange: params.conversationHistoryDeletedRange,
|
||||
contextManager: params.contextManager,
|
||||
clineMessages: params.clineMessages,
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
compactionStrategy: "standard-truncation-lastquarter",
|
||||
deletedRange: params.deletedRange,
|
||||
say: this.ctx.say,
|
||||
setActiveHookExecution: async (hookExecution: HookExecution | undefined) => {
|
||||
if (hookExecution) {
|
||||
await this.setActiveHookExecution(hookExecution)
|
||||
}
|
||||
},
|
||||
clearActiveHookExecution: async () => this.clearActiveHookExecution(),
|
||||
postStateToWebview: this.ctx.postStateToWebview,
|
||||
taskState: this.ctx.taskState,
|
||||
cancelTask: this.ctx.cancelTask,
|
||||
hooksEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
async cancelHookExecution(logError: (message: string, error: unknown) => void): Promise<boolean> {
|
||||
const activeHook = await this.getActiveHookExecution()
|
||||
if (!activeHook) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { hookName, toolName, messageTs, abortController } = activeHook
|
||||
try {
|
||||
abortController.abort()
|
||||
|
||||
const clineMessages = this.ctx.messageStateHandler.getClineMessages()
|
||||
const hookMessageIndex = clineMessages.findIndex((m) => m.ts === messageTs)
|
||||
if (hookMessageIndex !== -1) {
|
||||
await this.ctx.messageStateHandler.updateClineMessage(hookMessageIndex, {
|
||||
text: JSON.stringify({
|
||||
hookName,
|
||||
toolName,
|
||||
status: "cancelled",
|
||||
exitCode: 130,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
await this.ctx.say("hook_output_stream", "\nHook execution cancelled by user")
|
||||
return true
|
||||
} catch (error) {
|
||||
logError("Failed to cancel hook execution", error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async handleHookCancellation(hookName: string, wasCancelled: boolean): Promise<void> {
|
||||
this.ctx.taskState.didFinishAbortingStream = true
|
||||
await this.ctx.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
await this.ctx.messageStateHandler.overwriteApiConversationHistory(
|
||||
this.ctx.messageStateHandler.getApiConversationHistory(),
|
||||
)
|
||||
await this.ctx.postStateToWebview()
|
||||
Logger.log(`[Task ${this.ctx.taskId}] ${hookName} hook cancelled (userInitiated: ${wasCancelled})`)
|
||||
}
|
||||
|
||||
createAgentHooks(options: CreateTaskAgentHooksOptions): AgentHooks {
|
||||
return {
|
||||
onRunStart: async () => options.onRunStart(),
|
||||
onToolCallStart: async (ctx) => {
|
||||
if (this.ctx.taskState.abort) {
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const pendingToolInfo = options.buildPendingToolInfo(ctx.call.name, ctx.call.input)
|
||||
const result = await executeHook({
|
||||
hookName: "PreToolUse",
|
||||
hookInput: {
|
||||
preToolUse: {
|
||||
toolName: ctx.call.name,
|
||||
parameters: options.toHookStringParameters(ctx.call.input),
|
||||
},
|
||||
},
|
||||
isCancellable: true,
|
||||
say: this.ctx.say,
|
||||
setActiveHookExecution: async (execution) => this.setActiveHookExecution(execution as HookExecution),
|
||||
clearActiveHookExecution: async () => this.clearActiveHookExecution(),
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
taskId: this.ctx.taskId,
|
||||
hooksEnabled,
|
||||
toolName: ctx.call.name,
|
||||
pendingToolInfo,
|
||||
})
|
||||
|
||||
if (result.cancel) {
|
||||
await this.clearActiveHookExecution()
|
||||
if (result.errorMessage) {
|
||||
await this.ctx.say("error", result.errorMessage)
|
||||
}
|
||||
await this.ctx.cancelTask()
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const context = options.createHookContextBlock("PreToolUse", result.contextModification)
|
||||
return context ? { context } : undefined
|
||||
},
|
||||
onToolCallEnd: async (ctx) => {
|
||||
if (this.ctx.taskState.abort) {
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (!hooksEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const toolResult = ctx.record.error
|
||||
? { error: ctx.record.error }
|
||||
: typeof ctx.record.output === "string"
|
||||
? ctx.record.output
|
||||
: options.safeJsonStringify(ctx.record.output)
|
||||
const result = await executeHook({
|
||||
hookName: "PostToolUse",
|
||||
hookInput: {
|
||||
postToolUse: {
|
||||
toolName: ctx.record.name,
|
||||
parameters: options.toHookStringParameters(ctx.record.input),
|
||||
result: typeof toolResult === "string" ? toolResult : options.safeJsonStringify(toolResult),
|
||||
success: !ctx.record.error,
|
||||
executionTimeMs: ctx.record.durationMs,
|
||||
},
|
||||
},
|
||||
isCancellable: true,
|
||||
say: this.ctx.say,
|
||||
setActiveHookExecution: async (execution) => this.setActiveHookExecution(execution as HookExecution),
|
||||
clearActiveHookExecution: async () => this.clearActiveHookExecution(),
|
||||
messageStateHandler: this.ctx.messageStateHandler,
|
||||
taskId: this.ctx.taskId,
|
||||
hooksEnabled,
|
||||
toolName: ctx.record.name,
|
||||
})
|
||||
|
||||
if (result.cancel) {
|
||||
if (result.errorMessage) {
|
||||
await this.ctx.say("error", result.errorMessage)
|
||||
}
|
||||
await this.ctx.cancelTask()
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
const context = options.createHookContextBlock("PostToolUse", result.contextModification)
|
||||
return context ? { context } : undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@ export class ToolExecutor {
|
||||
private autoApprover: AutoApprove
|
||||
private coordinator: ToolExecutorCoordinator
|
||||
|
||||
public setApiHandler(api: ApiHandler): void {
|
||||
this.api = api
|
||||
}
|
||||
|
||||
// Auto-approval methods using the AutoApprove class
|
||||
private shouldAutoApproveTool(toolName: ClineDefaultTool): boolean | [boolean, boolean] {
|
||||
return this.autoApprover.shouldAutoApproveTool(toolName)
|
||||
|
||||
+98
-2363
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,8 @@
|
||||
import type { ToolUse } from "@core/assistant-message"
|
||||
import { getHooksEnabledSafe } from "@core/hooks/hooks-utils"
|
||||
import { executePreCompactHookWithCleanup, HookCancellationError } from "@core/hooks/precompact-executor"
|
||||
import { continuationPrompt } from "@core/prompts/contextManagement"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { ensureTaskDirectoryExists } from "@core/storage/disk"
|
||||
import { StateManager } from "@core/storage/StateManager"
|
||||
import { TaskHookExtensionAdapter } from "@core/task/TaskHookExtensionAdapter"
|
||||
import { resolveWorkspacePath } from "@core/workspace"
|
||||
import { extractFileContent } from "@integrations/misc/extract-file-content"
|
||||
import { ClineSayTool } from "@shared/ExtensionMessage"
|
||||
@@ -41,63 +39,43 @@ export class SummarizeTaskHandler implements IToolHandler, IPartialBlockHandler
|
||||
// Variable to store context modification from PreCompact hook
|
||||
let hookContextModification: string | undefined
|
||||
|
||||
// Run PreCompact hook right before showing the condensing message
|
||||
const hooksEnabled = getHooksEnabledSafe()
|
||||
if (hooksEnabled) {
|
||||
try {
|
||||
// Determine compaction strategy
|
||||
const useAutoCondense = StateManager.get().getGlobalSettingsKey("useAutoCondense")
|
||||
const strategy = useAutoCondense ? "auto-condense" : "standard-truncation-firstpair"
|
||||
|
||||
const apiHistory = config.messageState.getApiConversationHistory()
|
||||
|
||||
const result = await executePreCompactHookWithCleanup({
|
||||
taskId: config.taskId,
|
||||
ulid: config.ulid,
|
||||
apiConversationHistory: apiHistory,
|
||||
conversationHistoryDeletedRange: config.taskState.conversationHistoryDeletedRange,
|
||||
contextManager: config.services.contextManager,
|
||||
clineMessages: config.messageState.getClineMessages(),
|
||||
messageStateHandler: config.messageState,
|
||||
compactionStrategy: strategy,
|
||||
say: config.callbacks.say,
|
||||
setActiveHookExecution: async (hookExecution) => {
|
||||
if (hookExecution) {
|
||||
await config.callbacks.setActiveHookExecution(hookExecution)
|
||||
}
|
||||
},
|
||||
clearActiveHookExecution: config.callbacks.clearActiveHookExecution,
|
||||
postStateToWebview: config.callbacks.postStateToWebview,
|
||||
taskState: config.taskState,
|
||||
cancelTask: config.callbacks.cancelTask,
|
||||
hooksEnabled,
|
||||
})
|
||||
|
||||
// Hook completed successfully - capture context modification if provided
|
||||
if (result.contextModification) {
|
||||
hookContextModification = result.contextModification
|
||||
Logger.log(`[PreCompact] Hook provided context modification for task ${config.taskId}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// Check if this is a hook cancellation error
|
||||
if (error instanceof HookCancellationError) {
|
||||
// Hook was cancelled - show message and return early without executing summarization
|
||||
// (State already saved and task already cancelled by executePreCompactHookWithCleanup)
|
||||
await config.callbacks.say(
|
||||
"error",
|
||||
"Context compaction was cancelled by PreCompact hook. Task has been aborted.",
|
||||
)
|
||||
return "Context compaction was cancelled. Task has been aborted."
|
||||
}
|
||||
|
||||
// Graceful degradation: Show warning but continue with compaction
|
||||
// Hook UI already shows "Failed" status with error details
|
||||
// Run PreCompact hook right before showing the condensing message.
|
||||
try {
|
||||
const adapter = new TaskHookExtensionAdapter({
|
||||
taskId: config.taskId,
|
||||
ulid: config.ulid,
|
||||
taskState: config.taskState,
|
||||
messageStateHandler: config.messageState,
|
||||
say: config.callbacks.say,
|
||||
postStateToWebview: config.callbacks.postStateToWebview,
|
||||
cancelTask: config.callbacks.cancelTask,
|
||||
})
|
||||
const preCompactResult = await adapter.runPreCompactHookWithCleanup({
|
||||
apiConversationHistory: config.messageState.getApiConversationHistory(),
|
||||
conversationHistoryDeletedRange: config.taskState.conversationHistoryDeletedRange,
|
||||
contextManager: config.services.contextManager,
|
||||
clineMessages: config.messageState.getClineMessages(),
|
||||
})
|
||||
if (preCompactResult.contextModification) {
|
||||
hookContextModification = preCompactResult.contextModification
|
||||
Logger.log(`[PreCompact] Hook provided context modification for task ${config.taskId}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// If cancellation happened, cancelTask has already run inside the adapter.
|
||||
if (config.taskState.abort) {
|
||||
await config.callbacks.say(
|
||||
"error",
|
||||
`PreCompact hook failed, continuing with compaction: ${error instanceof Error ? error.message : String(error)}`,
|
||||
"Context compaction was cancelled by PreCompact hook. Task has been aborted.",
|
||||
)
|
||||
Logger.error("[PreCompact] Hook execution failed, continuing with compaction:", error)
|
||||
return "Context compaction was cancelled. Task has been aborted."
|
||||
}
|
||||
|
||||
// Graceful degradation: Show warning but continue with compaction.
|
||||
await config.callbacks.say(
|
||||
"error",
|
||||
`PreCompact hook failed, continuing with compaction: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
Logger.error("[PreCompact] Hook execution failed, continuing with compaction:", error)
|
||||
}
|
||||
|
||||
// Show completed summary in tool UI
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { setTimeout as delay } from "node:timers/promises"
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import type { ApiHandler } from "@core/api"
|
||||
import { parseAssistantMessageV2, ToolUse } from "@core/assistant-message"
|
||||
import { ContextManager } from "@core/context/context-management/ContextManager"
|
||||
import { checkContextWindowExceededError } from "@core/context/context-management/context-error-handling"
|
||||
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
|
||||
import { discoverSkills, getAvailableSkills } from "@core/context/instructions/user-instructions/skills"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { PromptRegistry } from "@core/prompts/system-prompt"
|
||||
@@ -14,10 +13,9 @@ import { ClineAssistantToolUseBlock, ClineStorageMessage, ClineTextContentBlock
|
||||
import { Logger } from "@shared/services/Logger"
|
||||
import type { ClineTool } from "@shared/tools"
|
||||
import { ClineDefaultTool } from "@shared/tools"
|
||||
import { isNextGenModelFamily } from "@utils/model-utils"
|
||||
import * as path from "path"
|
||||
import { CoreAgent } from "@/core/agents/CoreAgent"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ClineError, ClineErrorType } from "@/services/error/ClineError"
|
||||
import { ApiFormat } from "@/shared/proto/cline/models"
|
||||
import { calculateApiCostAnthropic } from "@/utils/cost"
|
||||
import { TaskState } from "../../TaskState"
|
||||
@@ -225,6 +223,7 @@ export class SubagentRunner {
|
||||
private abortRequested = false
|
||||
private activeCommandExecutions = 0
|
||||
private abortingCommands = false
|
||||
private coreAgent = new CoreAgent()
|
||||
|
||||
constructor(private baseConfig: TaskConfig) {}
|
||||
|
||||
@@ -302,15 +301,15 @@ export class SubagentRunner {
|
||||
...apiConfiguration,
|
||||
ulid: this.baseConfig.ulid,
|
||||
}
|
||||
const api = buildApiHandler(effectiveApiConfiguration, mode)
|
||||
this.activeApiAbort = api.abort?.bind(api)
|
||||
const api = this.coreAgent.initializeApiHandler(effectiveApiConfiguration, mode)
|
||||
this.activeApiAbort = () => this.coreAgent.abortCurrentRequest()
|
||||
|
||||
const providerId = (
|
||||
mode === "plan" ? apiConfiguration.planModeApiProvider : apiConfiguration.actModeApiProvider
|
||||
) as string
|
||||
const providerInfo = {
|
||||
providerId,
|
||||
model: api.getModel(),
|
||||
model: this.coreAgent.getModel(),
|
||||
mode,
|
||||
customPrompt: this.baseConfig.services.stateManager.getGlobalSettingsKey("customPrompt"),
|
||||
}
|
||||
@@ -377,66 +376,59 @@ export class SubagentRunner {
|
||||
},
|
||||
]
|
||||
|
||||
while (true) {
|
||||
if (
|
||||
previousRequestTotalTokens !== undefined &&
|
||||
this.shouldCompactBeforeNextRequest(previousRequestTotalTokens, api, providerInfo.model.id)
|
||||
) {
|
||||
const didCompact = this.compactConversationForContextWindow(conversation)
|
||||
if (didCompact) {
|
||||
Logger.warn("[SubagentRunner] Proactively compacted context before next subagent request.")
|
||||
const loopResult = await this.coreAgent.runLoop<void, SubagentRunResult>({
|
||||
initialInput: undefined,
|
||||
shouldAbort: () => this.shouldAbort(),
|
||||
runTurn: async () => {
|
||||
if (
|
||||
previousRequestTotalTokens !== undefined &&
|
||||
this.coreAgent.shouldCompactBeforeNextRequest(
|
||||
previousRequestTotalTokens,
|
||||
api,
|
||||
providerInfo.model.id,
|
||||
this.baseConfig.services.stateManager.getGlobalSettingsKey("useAutoCondense"),
|
||||
)
|
||||
) {
|
||||
const didCompact = this.compactConversationForContextWindow(conversation)
|
||||
if (didCompact) {
|
||||
Logger.warn("[SubagentRunner] Proactively compacted context before next subagent request.")
|
||||
}
|
||||
previousRequestTotalTokens = undefined
|
||||
}
|
||||
// Prevent repeated compaction attempts off the same token sample.
|
||||
previousRequestTotalTokens = undefined
|
||||
}
|
||||
|
||||
const streamHandler = new StreamResponseHandler()
|
||||
const { toolUseHandler } = streamHandler.getHandlers()
|
||||
let requestInputTokens = 0
|
||||
let requestOutputTokens = 0
|
||||
let requestCacheWriteTokens = 0
|
||||
let requestCacheReadTokens = 0
|
||||
let requestTotalCost: number | undefined
|
||||
const streamHandler = new StreamResponseHandler()
|
||||
const { toolUseHandler } = streamHandler.getHandlers()
|
||||
|
||||
let assistantText = ""
|
||||
let assistantTextSignature: string | undefined
|
||||
let requestId: string | undefined
|
||||
const stream = this.createMessageWithInitialChunkRetry(
|
||||
api,
|
||||
systemPrompt,
|
||||
conversation,
|
||||
nativeTools,
|
||||
providerInfo.providerId,
|
||||
providerInfo.model.id,
|
||||
)
|
||||
|
||||
const stream = this.createMessageWithInitialChunkRetry(
|
||||
api,
|
||||
systemPrompt,
|
||||
conversation,
|
||||
nativeTools,
|
||||
providerInfo.providerId,
|
||||
providerInfo.model.id,
|
||||
)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk.type) {
|
||||
case "usage":
|
||||
requestId = requestId ?? chunk.id
|
||||
const streamResult = await this.coreAgent.consumeStream({
|
||||
stream,
|
||||
shouldAbort: () => this.shouldAbort(),
|
||||
onAbort: async () => {
|
||||
await this.abort()
|
||||
},
|
||||
onUsageChunk: (chunk, state) => {
|
||||
stats.inputTokens += chunk.inputTokens || 0
|
||||
stats.outputTokens += chunk.outputTokens || 0
|
||||
stats.cacheWriteTokens += chunk.cacheWriteTokens || 0
|
||||
stats.cacheReadTokens += chunk.cacheReadTokens || 0
|
||||
requestInputTokens += chunk.inputTokens || 0
|
||||
requestOutputTokens += chunk.outputTokens || 0
|
||||
requestCacheWriteTokens += chunk.cacheWriteTokens || 0
|
||||
requestCacheReadTokens += chunk.cacheReadTokens || 0
|
||||
requestTotalCost = chunk.totalCost ?? requestTotalCost
|
||||
stats.contextTokens =
|
||||
requestInputTokens + requestOutputTokens + requestCacheWriteTokens + requestCacheReadTokens
|
||||
state.usage.inputTokens +
|
||||
state.usage.outputTokens +
|
||||
state.usage.cacheWriteTokens +
|
||||
state.usage.cacheReadTokens
|
||||
stats.contextUsagePercentage =
|
||||
stats.contextWindow > 0 ? (stats.contextTokens / stats.contextWindow) * 100 : 0
|
||||
onProgress({ stats: { ...stats } })
|
||||
break
|
||||
case "text":
|
||||
requestId = requestId ?? chunk.id
|
||||
assistantText += chunk.text || ""
|
||||
assistantTextSignature = chunk.signature || assistantTextSignature
|
||||
break
|
||||
case "tool_calls":
|
||||
requestId = requestId ?? chunk.id
|
||||
},
|
||||
onToolCallChunk: (chunk) => {
|
||||
toolUseHandler.processToolUseDelta(
|
||||
{
|
||||
id: chunk.tool_call.function?.id,
|
||||
@@ -447,188 +439,184 @@ export class SubagentRunner {
|
||||
},
|
||||
chunk.tool_call.call_id,
|
||||
)
|
||||
break
|
||||
case "reasoning":
|
||||
requestId = requestId ?? chunk.id
|
||||
break
|
||||
}
|
||||
|
||||
if (this.shouldAbort()) {
|
||||
await this.abort()
|
||||
const error = "Subagent run cancelled."
|
||||
onProgress({ status: "failed", error, stats: { ...stats } })
|
||||
return { status: "failed", error, stats }
|
||||
}
|
||||
}
|
||||
|
||||
const calculatedRequestCost =
|
||||
requestTotalCost ??
|
||||
calculateApiCostAnthropic(
|
||||
providerInfo.model.info,
|
||||
requestInputTokens,
|
||||
requestOutputTokens,
|
||||
requestCacheWriteTokens,
|
||||
requestCacheReadTokens,
|
||||
)
|
||||
stats.totalCost += calculatedRequestCost || 0
|
||||
previousRequestTotalTokens =
|
||||
requestInputTokens + requestOutputTokens + requestCacheWriteTokens + requestCacheReadTokens
|
||||
|
||||
const nativeFinalizedToolCalls = toolUseHandler.getAllFinalizedToolUses().map((toolCall, index) => ({
|
||||
toolUseId: resolveToolUseId(toolCall, index),
|
||||
id: toolCall.id,
|
||||
call_id: toolCall.call_id,
|
||||
signature: toolCall.signature,
|
||||
name: toolCall.name,
|
||||
input: toolCall.input,
|
||||
isNativeToolCall: true,
|
||||
}))
|
||||
const parsedNonNativeToolCalls = parseNonNativeToolCalls(assistantText)
|
||||
const fallbackNonNativeToolCalls = nativeFinalizedToolCalls.map((toolCall) => ({
|
||||
...toolCall,
|
||||
isNativeToolCall: false,
|
||||
}))
|
||||
|
||||
let finalizedToolCalls: SubagentToolCall[] = []
|
||||
if (useNativeToolCalls) {
|
||||
finalizedToolCalls = nativeFinalizedToolCalls
|
||||
} else if (parsedNonNativeToolCalls.length > 0) {
|
||||
finalizedToolCalls = parsedNonNativeToolCalls
|
||||
} else if (fallbackNonNativeToolCalls.length > 0) {
|
||||
// Defensive fallback: if non-native mode receives structured tool call chunks,
|
||||
// execute them but serialize results as plain text to avoid tool_result pairing mismatches.
|
||||
Logger.warn(
|
||||
"[SubagentRunner] Received structured tool_calls while native tool calling is disabled; falling back to non-native result serialization.",
|
||||
)
|
||||
finalizedToolCalls = fallbackNonNativeToolCalls
|
||||
}
|
||||
const assistantContent = [] as any[]
|
||||
if (assistantText.trim().length > 0) {
|
||||
assistantContent.push({
|
||||
type: "text",
|
||||
text: assistantText,
|
||||
signature: assistantTextSignature,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (useNativeToolCalls) {
|
||||
assistantContent.push(...finalizedToolCalls.map(toAssistantToolUseBlock))
|
||||
}
|
||||
|
||||
if (assistantContent.length > 0) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: assistantContent,
|
||||
id: requestId,
|
||||
})
|
||||
}
|
||||
|
||||
if (finalizedToolCalls.length === 0) {
|
||||
emptyAssistantResponseRetries += 1
|
||||
if (emptyAssistantResponseRetries > MAX_EMPTY_ASSISTANT_RETRIES) {
|
||||
const error = "Subagent did not call attempt_completion."
|
||||
onProgress({ status: "failed", error, stats: { ...stats } })
|
||||
return { status: "failed", error, stats }
|
||||
if (streamResult.aborted) {
|
||||
return { status: "failed", error: "Subagent run cancelled." }
|
||||
}
|
||||
|
||||
// Mirror the main loop's no-tools-used nudge so empty/blank model turns
|
||||
// can recover without surfacing an immediate hard failure in subagent UI.
|
||||
if (assistantContent.length === 0) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Failure: I did not provide a response.",
|
||||
},
|
||||
],
|
||||
id: requestId,
|
||||
const calculatedRequestCost =
|
||||
streamResult.usage.totalCost ??
|
||||
calculateApiCostAnthropic(
|
||||
providerInfo.model.info,
|
||||
streamResult.usage.inputTokens,
|
||||
streamResult.usage.outputTokens,
|
||||
streamResult.usage.cacheWriteTokens,
|
||||
streamResult.usage.cacheReadTokens,
|
||||
)
|
||||
stats.totalCost += calculatedRequestCost || 0
|
||||
previousRequestTotalTokens =
|
||||
streamResult.usage.inputTokens +
|
||||
streamResult.usage.outputTokens +
|
||||
streamResult.usage.cacheWriteTokens +
|
||||
streamResult.usage.cacheReadTokens
|
||||
|
||||
const nativeFinalizedToolCalls = toolUseHandler.getAllFinalizedToolUses().map((toolCall, index) => ({
|
||||
toolUseId: resolveToolUseId(toolCall, index),
|
||||
id: toolCall.id,
|
||||
call_id: toolCall.call_id,
|
||||
signature: toolCall.signature,
|
||||
name: toolCall.name,
|
||||
input: toolCall.input,
|
||||
isNativeToolCall: true,
|
||||
}))
|
||||
const parsedNonNativeToolCalls = parseNonNativeToolCalls(streamResult.assistantText)
|
||||
const fallbackNonNativeToolCalls = nativeFinalizedToolCalls.map((toolCall) => ({
|
||||
...toolCall,
|
||||
isNativeToolCall: false,
|
||||
}))
|
||||
|
||||
let finalizedToolCalls: SubagentToolCall[] = []
|
||||
if (useNativeToolCalls) {
|
||||
finalizedToolCalls = nativeFinalizedToolCalls
|
||||
} else if (parsedNonNativeToolCalls.length > 0) {
|
||||
finalizedToolCalls = parsedNonNativeToolCalls
|
||||
} else if (fallbackNonNativeToolCalls.length > 0) {
|
||||
Logger.warn(
|
||||
"[SubagentRunner] Received structured tool_calls while native tool calling is disabled; falling back to non-native result serialization.",
|
||||
)
|
||||
finalizedToolCalls = fallbackNonNativeToolCalls
|
||||
}
|
||||
const assistantContent = [] as any[]
|
||||
if (streamResult.assistantText.trim().length > 0) {
|
||||
assistantContent.push({
|
||||
type: "text",
|
||||
text: streamResult.assistantText,
|
||||
signature: streamResult.assistantTextSignature,
|
||||
})
|
||||
}
|
||||
conversation.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: formatResponse.noToolsUsed(useNativeToolCalls),
|
||||
},
|
||||
],
|
||||
})
|
||||
await delay(0)
|
||||
continue
|
||||
}
|
||||
emptyAssistantResponseRetries = 0
|
||||
if (useNativeToolCalls) {
|
||||
assistantContent.push(...finalizedToolCalls.map(toAssistantToolUseBlock))
|
||||
}
|
||||
|
||||
const toolResultBlocks = [] as any[]
|
||||
for (const call of finalizedToolCalls) {
|
||||
const toolName = call.name as ClineDefaultTool
|
||||
const toolCallParams = toToolUseParams(call.input)
|
||||
if (assistantContent.length > 0) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: assistantContent,
|
||||
id: streamResult.requestId,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolName === ClineDefaultTool.ATTEMPT) {
|
||||
const completionResult = toolCallParams.result?.trim()
|
||||
if (!completionResult) {
|
||||
const missingResultError = formatResponse.missingToolParameterError("result")
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolName, missingResultError)
|
||||
if (finalizedToolCalls.length === 0) {
|
||||
emptyAssistantResponseRetries += 1
|
||||
if (emptyAssistantResponseRetries > MAX_EMPTY_ASSISTANT_RETRIES) {
|
||||
return { status: "failed", error: "Subagent did not call attempt_completion." }
|
||||
}
|
||||
|
||||
if (assistantContent.length === 0) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Failure: I did not provide a response." }],
|
||||
id: streamResult.requestId,
|
||||
})
|
||||
}
|
||||
conversation.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: formatResponse.noToolsUsed(useNativeToolCalls) }],
|
||||
})
|
||||
await delay(0)
|
||||
return { status: "continue", nextInput: undefined }
|
||||
}
|
||||
emptyAssistantResponseRetries = 0
|
||||
|
||||
const toolResultBlocks = [] as any[]
|
||||
for (const call of finalizedToolCalls) {
|
||||
const toolName = call.name as ClineDefaultTool
|
||||
const toolCallParams = toToolUseParams(call.input)
|
||||
|
||||
if (toolName === ClineDefaultTool.ATTEMPT) {
|
||||
const completionResult = toolCallParams.result?.trim()
|
||||
if (!completionResult) {
|
||||
const missingResultError = formatResponse.missingToolParameterError("result")
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolName, missingResultError)
|
||||
continue
|
||||
}
|
||||
|
||||
stats.toolCalls += 1
|
||||
onProgress({ stats: { ...stats } })
|
||||
const completed: SubagentRunResult = { status: "completed", result: completionResult, stats }
|
||||
return { status: "complete", output: completed }
|
||||
}
|
||||
|
||||
if (!SUBAGENT_ALLOWED_TOOLS.includes(toolName)) {
|
||||
const deniedResult = formatResponse.toolError(
|
||||
`Tool '${toolName}' is not available inside subagent runs.`,
|
||||
)
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolName, deniedResult)
|
||||
continue
|
||||
}
|
||||
|
||||
const toolCallBlock: ToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: toolCallParams,
|
||||
partial: false,
|
||||
isNativeToolCall: call.isNativeToolCall,
|
||||
call_id: call.call_id || call.toolUseId,
|
||||
signature: call.signature,
|
||||
}
|
||||
|
||||
if (call.call_id) {
|
||||
state.toolUseIdMap.set(call.call_id, call.toolUseId)
|
||||
}
|
||||
|
||||
onProgress({ latestToolCall: formatToolCallPreview(toolName, toolCallParams) })
|
||||
|
||||
const subagentConfig = this.createSubagentTaskConfig(state)
|
||||
const handler = this.baseConfig.coordinator.getHandler(toolName)
|
||||
let toolResult: unknown
|
||||
|
||||
if (!handler) {
|
||||
toolResult = formatResponse.toolError(`No handler registered for tool '${toolName}'.`)
|
||||
} else {
|
||||
try {
|
||||
toolResult = await this.baseConfig.coordinator.execute(subagentConfig, toolCallBlock)
|
||||
} catch (error) {
|
||||
toolResult = formatResponse.toolError((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
stats.toolCalls += 1
|
||||
onProgress({ stats: { ...stats } })
|
||||
onProgress({ status: "completed", result: completionResult, stats: { ...stats } })
|
||||
return { status: "completed", result: completionResult, stats }
|
||||
|
||||
const serializedToolResult = serializeToolResult(toolResult)
|
||||
const toolDescription = handler?.getDescription(toolCallBlock) || `[${toolName}]`
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolDescription, serializedToolResult)
|
||||
}
|
||||
|
||||
if (!SUBAGENT_ALLOWED_TOOLS.includes(toolName)) {
|
||||
const deniedResult = formatResponse.toolError(`Tool '${toolName}' is not available inside subagent runs.`)
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolName, deniedResult)
|
||||
continue
|
||||
}
|
||||
conversation.push({
|
||||
role: "user",
|
||||
content: toolResultBlocks,
|
||||
})
|
||||
|
||||
const toolCallBlock: ToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: toolCallParams,
|
||||
partial: false,
|
||||
isNativeToolCall: call.isNativeToolCall,
|
||||
call_id: call.call_id || call.toolUseId,
|
||||
signature: call.signature,
|
||||
}
|
||||
await delay(0)
|
||||
return { status: "continue", nextInput: undefined }
|
||||
},
|
||||
})
|
||||
|
||||
if (call.call_id) {
|
||||
state.toolUseIdMap.set(call.call_id, call.toolUseId)
|
||||
}
|
||||
|
||||
const latestToolCall = formatToolCallPreview(toolName, toolCallParams)
|
||||
onProgress({ latestToolCall })
|
||||
|
||||
const subagentConfig = this.createSubagentTaskConfig(state)
|
||||
const handler = this.baseConfig.coordinator.getHandler(toolName)
|
||||
let toolResult: unknown
|
||||
|
||||
if (!handler) {
|
||||
toolResult = formatResponse.toolError(`No handler registered for tool '${toolName}'.`)
|
||||
} else {
|
||||
try {
|
||||
toolResult = await handler.execute(subagentConfig, toolCallBlock)
|
||||
} catch (error) {
|
||||
toolResult = formatResponse.toolError((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
stats.toolCalls += 1
|
||||
onProgress({ stats: { ...stats } })
|
||||
|
||||
const serializedToolResult = serializeToolResult(toolResult)
|
||||
const toolDescription = handler?.getDescription(toolCallBlock) || `[${toolName}]`
|
||||
pushSubagentToolResultBlock(toolResultBlocks, call, toolDescription, serializedToolResult)
|
||||
}
|
||||
|
||||
conversation.push({
|
||||
role: "user",
|
||||
content: toolResultBlocks,
|
||||
})
|
||||
|
||||
await delay(0)
|
||||
if (loopResult.status === "complete") {
|
||||
onProgress({ status: "completed", result: loopResult.output.result, stats: { ...stats } })
|
||||
return loopResult.output
|
||||
}
|
||||
if (loopResult.status === "failed") {
|
||||
const error = loopResult.error || "Subagent execution failed."
|
||||
onProgress({ status: "failed", error, stats: { ...stats } })
|
||||
return { status: "failed", error, stats }
|
||||
}
|
||||
const cancelledError = "Subagent run cancelled."
|
||||
onProgress({ status: "failed", error: cancelledError, stats: { ...stats } })
|
||||
return { status: "failed", error: cancelledError, stats }
|
||||
} catch (error) {
|
||||
if (this.shouldAbort()) {
|
||||
const cancelledError = "Subagent run cancelled."
|
||||
@@ -673,19 +661,6 @@ export class SubagentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldRetryInitialStreamError(error: unknown, providerId: string, modelId: string): boolean {
|
||||
// Mirror main loop behavior: do not auto-retry auth/balance failures.
|
||||
const parsedError = ClineError.transform(error, modelId, providerId)
|
||||
const isAuthError = parsedError.isErrorType(ClineErrorType.Auth)
|
||||
const isBalanceError = parsedError.isErrorType(ClineErrorType.Balance)
|
||||
|
||||
if (isAuthError || isBalanceError) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private compactConversationForContextWindow(conversation: ClineStorageMessage[]): boolean {
|
||||
const contextManager = new ContextManager()
|
||||
const optimizationResult = this.optimizeConversationForContextWindow(contextManager, conversation)
|
||||
@@ -729,25 +704,8 @@ export class SubagentRunner {
|
||||
return { didOptimize: true, needToTruncate: optimizationResult.needToTruncate }
|
||||
}
|
||||
|
||||
private shouldCompactBeforeNextRequest(
|
||||
previousRequestTotalTokens: number,
|
||||
api: ReturnType<typeof buildApiHandler>,
|
||||
modelId: string,
|
||||
): boolean {
|
||||
const { contextWindow, maxAllowedSize } = getContextWindowInfo(api)
|
||||
const useAutoCondense = this.baseConfig.services.stateManager.getGlobalSettingsKey("useAutoCondense")
|
||||
if (useAutoCondense && isNextGenModelFamily(modelId)) {
|
||||
const autoCondenseThreshold = 0.75
|
||||
const roundedThreshold = autoCondenseThreshold ? Math.floor(contextWindow * autoCondenseThreshold) : maxAllowedSize
|
||||
const thresholdTokens = Math.min(roundedThreshold, maxAllowedSize)
|
||||
return previousRequestTotalTokens >= thresholdTokens
|
||||
}
|
||||
|
||||
return previousRequestTotalTokens >= maxAllowedSize
|
||||
}
|
||||
|
||||
private async *createMessageWithInitialChunkRetry(
|
||||
api: ReturnType<typeof buildApiHandler>,
|
||||
api: ApiHandler,
|
||||
systemPrompt: string,
|
||||
conversation: ClineStorageMessage[],
|
||||
nativeTools: ClineTool[] | undefined,
|
||||
@@ -755,7 +713,7 @@ export class SubagentRunner {
|
||||
modelId: string,
|
||||
) {
|
||||
for (let attempt = 1; attempt <= MAX_INITIAL_STREAM_ATTEMPTS; attempt += 1) {
|
||||
const stream = api.createMessage(systemPrompt, conversation, nativeTools)
|
||||
const stream = this.coreAgent.createMessage(systemPrompt, conversation, nativeTools)
|
||||
const iterator = stream[Symbol.asyncIterator]()
|
||||
|
||||
try {
|
||||
@@ -781,7 +739,7 @@ export class SubagentRunner {
|
||||
const shouldRetry =
|
||||
!this.shouldAbort() &&
|
||||
attempt < MAX_INITIAL_STREAM_ATTEMPTS &&
|
||||
this.shouldRetryInitialStreamError(error, providerId, modelId)
|
||||
this.coreAgent.classifyInitialStreamRetry(error, providerId, modelId).shouldRetry
|
||||
if (!shouldRetry) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -25,6 +25,24 @@
|
||||
"useUnknownInCatchVariables": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@cline/agents": [
|
||||
"sdk-wip/packages/agents/dist/index.d.ts"
|
||||
],
|
||||
"@cline/agents/*": [
|
||||
"sdk-wip/packages/agents/dist/*"
|
||||
],
|
||||
"@cline/llms": [
|
||||
"sdk-wip/packages/llms/dist/index.d.ts"
|
||||
],
|
||||
"@cline/llms/*": [
|
||||
"sdk-wip/packages/llms/dist/*"
|
||||
],
|
||||
"@cline/core": [
|
||||
"sdk-wip/packages/core/dist/index.d.ts"
|
||||
],
|
||||
"@cline/core/*": [
|
||||
"sdk-wip/packages/core/dist/*"
|
||||
],
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
|
||||
Generated
+35
-12
@@ -160,6 +160,7 @@
|
||||
"version": "7.28.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -500,6 +501,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -521,6 +523,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -528,6 +531,7 @@
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.2",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1"
|
||||
}
|
||||
@@ -1033,6 +1037,7 @@
|
||||
"node_modules/@firebase/app": {
|
||||
"version": "0.13.2",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.6.18",
|
||||
"@firebase/logger": "0.4.4",
|
||||
@@ -1089,6 +1094,7 @@
|
||||
"node_modules/@firebase/app-compat": {
|
||||
"version": "0.4.2",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/app": "0.13.2",
|
||||
"@firebase/component": "0.6.18",
|
||||
@@ -1102,7 +1108,8 @@
|
||||
},
|
||||
"node_modules/@firebase/app-types": {
|
||||
"version": "0.9.3",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@firebase/auth-compat": {
|
||||
"version": "0.5.28",
|
||||
@@ -1489,6 +1496,7 @@
|
||||
"version": "1.12.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -2585,6 +2593,7 @@
|
||||
"node_modules/@heroui/system": {
|
||||
"version": "2.4.22",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@heroui/react-utils": "2.1.13",
|
||||
"@heroui/system-rsc": "2.3.19",
|
||||
@@ -2669,6 +2678,7 @@
|
||||
"node_modules/@heroui/theme": {
|
||||
"version": "2.4.22",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@heroui/shared-utils": "2.1.11",
|
||||
"clsx": "^1.2.1",
|
||||
@@ -6351,8 +6361,7 @@
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -6729,6 +6738,7 @@
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.18",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -6738,6 +6748,7 @@
|
||||
"version": "18.3.7",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
@@ -7129,6 +7140,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001741",
|
||||
@@ -7258,6 +7270,7 @@
|
||||
"node_modules/chevrotain": {
|
||||
"version": "11.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@chevrotain/cst-dts-gen": "11.0.3",
|
||||
"@chevrotain/gast": "11.0.3",
|
||||
@@ -7528,6 +7541,7 @@
|
||||
"node_modules/cytoscape": {
|
||||
"version": "3.33.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
@@ -7860,6 +7874,7 @@
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -8086,8 +8101,7 @@
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.16",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.7",
|
||||
@@ -8150,6 +8164,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -8430,6 +8445,7 @@
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.18",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.18",
|
||||
"motion-utils": "^12.23.6",
|
||||
@@ -9372,6 +9388,7 @@
|
||||
"version": "26.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -9842,7 +9859,6 @@
|
||||
"version": "1.5.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -11745,7 +11761,6 @@
|
||||
"version": "27.5.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -11759,7 +11774,6 @@
|
||||
"version": "5.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -11822,6 +11836,7 @@
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -11860,6 +11875,7 @@
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -11871,8 +11887,7 @@
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "10.1.0",
|
||||
@@ -12536,6 +12551,7 @@
|
||||
"version": "4.52.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -12859,6 +12875,7 @@
|
||||
"version": "9.1.17",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
@@ -13124,6 +13141,7 @@
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.3.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
@@ -13148,7 +13166,8 @@
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.13",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
@@ -13360,12 +13379,14 @@
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"license": "0BSD"
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.2",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -13718,6 +13739,7 @@
|
||||
"version": "7.2.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13839,6 +13861,7 @@
|
||||
"version": "3.2.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
|
||||
Reference in New Issue
Block a user