Compare commits

..

1 Commits

Author SHA1 Message Date
abeatrix 43ddc8c846 fix(test): increase hook test timeouts for Windows in taskcancel tests
Add WINDOWS_HOOK_TEST_TIMEOUT_MS (15s) to taskcancel hook tests to
account for the slower PowerShell bridge that spawns a child Node
process on Windows CI. The double-process startup is variable and can
exceed Mocha's default 2s timeout, causing flaky failures.

Mirrors the approach already used in taskresume, hook-factory, and
user-prompt-submit tests.
2026-06-01 20:36:24 -07:00
1247 changed files with 10315 additions and 32506 deletions
+13 -9
View File
@@ -140,10 +140,12 @@ Adding a new key to global state requires updates in multiple places. Missing an
Required steps:
1. Type definition in `src/shared/storage/state-keys.ts` - Add to `GlobalState` or `Settings` interface
2. Add any default value or transform in `src/shared/storage/state-keys.ts` if the key needs one
3. Read and write the value through `StateManager` (`setGlobalState()` / `getGlobalStateKey()`) after initialization
2. Read from globalState in `src/core/storage/utils/state-helpers.ts`:
- Add `const myKey = context.globalState.get<GlobalStateAndSettings["myKey"]>("myKey")` in `readGlobalStateFromDisk()`
- Add to the return object: `myKey: myKey ?? defaultValue,`
3. StateManager handles read/write via `setGlobalState()`/`getGlobalStateKey()` after initialization
Persistent state is file-backed through `StateManager`; do not add new runtime reads or writes against VS Code `ExtensionContext` storage. That storage is only a legacy migration source.
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
Settings plumbing gotcha: if a key is user-toggleable from settings, wire both controller update paths:
- `src/core/controller/state/updateSettings.ts` for webview `updateSetting(...)`
@@ -157,20 +159,22 @@ Webview toggle gotcha: settings changes must also round-trip back in state paylo
If this round-trip wiring is missing, the backend value can update but the toggle in webview appears stuck or reverts.
## StateManager Cache vs Direct globalState Access
StateManager uses an in-memory cache populated during `StateManager.initialize()` from file-backed storage. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
Exception: host migration code may read legacy VS Code storage before file-backed storage is initialized.
Exception: State needed immediately at extension startup (before cache is ready)
Example pattern:
When Window A sets state and immediately opens Window B, the new window's StateManager cache is populated from `context.globalState` during initialization. If you need to read state in Window B right at startup (e.g., in `common.ts` during `initialize()`), read directly from `context.globalState.get()` instead of StateManager's cache.
Example pattern (see `lastShownAnnouncementId` and `worktreeAutoOpenPath`):
```typescript
// Writing (normal pattern)
controller.stateManager.setGlobalState("myKey", value)
// Reading after initialization
const value = controller.stateManager.getGlobalStateKey("myKey")
// Reading at startup in common.ts (bypass cache)
const value = context.globalState.get<string>("myKey")
```
Use `context.globalState` only in VS Code migration code that copies legacy ExtensionContext values into the shared file-backed stores.
This is only needed for cross-window state read during the brief startup window before StateManager cache is fully usable. Normal state access after initialization should use StateManager.
## ChatRow Cancelled/Interrupted States
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
+3 -15
View File
@@ -7,10 +7,10 @@ body:
value: |
**Important:** All bug reports must be reproducible using Claude Sonnet 4.5. Cline uses complex prompts so less capable models may not work as expected.
- type: dropdown
id: cline-surface
id: plugin-type
attributes:
label: Cline Surface
description: Which Cline surface are you reporting a bug for?
label: Plugin Type
description: Which plugin are you reporting a bug for?
options:
- VSCode Extension
- JetBrains Plugin
@@ -59,18 +59,6 @@ body:
placeholder: 'e.g., cline:anthropic/claude-sonnet-4.5, gemini:gemini-2.5-pro-exp-03-25'
validations:
required: false
- type: textarea
id: ide-diagnostics
attributes:
label: IDE / CLI Diagnostics
description: |
Paste the "About" diagnostics for your Cline surface. This captures the IDE build, runtime, and host details we need.
- VSCode Extension: open `Help → About` (Windows/Linux) or `Code → About Visual Studio Code` (macOS), then copy the info.
- JetBrains Plugin: open `Help → About` (Windows/Linux) or `<IDE name> → About` (macOS), then click `Copy` to grab build, runtime, OS, memory, and cores.
- CLI: there is no About dialog. Run `cline --version` and paste the output.
placeholder: Paste the copied About info or `cline --version` output here.
validations:
required: false
- type: textarea
id: system-info
attributes:
+1 -1
View File
@@ -44,7 +44,7 @@ For Responses API providers: add to `isNextGenModelProvider()` in `src/utils/mod
Modular: `components/` (shared) + `variants/` (model-specific) + `templates/` (`{{PLACEHOLDER}}`). Variants override components via `componentOverrides` in `config.ts` or custom `template.ts`. XS variant is heavily condensed inline. Always regenerate snapshots after changes.
## Global State Keys (silent failure risk)
Adding a key requires updating the typed storage definitions in `src/shared/storage/state-keys.ts`; runtime reads and writes should go through `StateManager`, not VS Code `ExtensionContext` storage. Persistent state is file-backed so it works across VS Code, CLI, and JetBrains hosts.
Adding a key requires: type in `src/shared/storage/state-keys.ts`, read via `context.globalState.get()` in `src/core/storage/utils/state-helpers.ts` `readGlobalStateFromDisk()`, and add to return object. Missing the `.get()` call compiles fine but value is always `undefined`.
## Slash Commands (3 places)
- `src/core/slash-commands/index.ts` — definitions.
+14 -14
View File
@@ -102,15 +102,15 @@ jobs:
fi
VERSION="${TAG#cli-v}"
PACKAGE_VERSION=$(node -p "require('./apps/cli/package.json').version")
PACKAGE_VERSION=$(node -p "require('./sdk/apps/cli/package.json').version")
if [ "$PACKAGE_VERSION" != "$VERSION" ]; then
echo "apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}"
echo "sdk/apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}"
exit 1
fi
if ! printf "%s\n" "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "apps/cli/package.json has invalid version: ${VERSION}"
echo "sdk/apps/cli/package.json has invalid version: ${VERSION}"
exit 1
fi
@@ -147,7 +147,7 @@ jobs:
- name: Build platform binaries
run: bun script/build.ts --install-native-variants --skip-sdk-build
working-directory: apps/cli
working-directory: sdk/apps/cli
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
@@ -172,7 +172,7 @@ jobs:
)
for package_name in "${EXPECTED[@]}"; do
dir="apps/cli/dist/${package_name#@cline/}"
dir="sdk/apps/cli/dist/${package_name#@cline/}"
if [ ! -f "$dir/package.json" ]; then
echo "Missing package manifest: $dir/package.json"
exit 1
@@ -194,7 +194,7 @@ jobs:
env:
NPM_CONFIG_PROVENANCE: "true"
run: bun script/publish-npm.ts --tag latest
working-directory: apps/cli
working-directory: sdk/apps/cli
- name: Get Previous CLI Tag
id: prev_tag
@@ -207,8 +207,8 @@ jobs:
- name: Get Changelog Entry
id: changelog
run: |
# Grab content between the first "## " header and the next one in apps/cli/CHANGELOG.md
CONTENT=$(awk '/^## [0-9]/{if(found) exit; found=1; next} found{print}' apps/cli/CHANGELOG.md)
# Grab content between the first "## " header and the next one in sdk/apps/cli/CHANGELOG.md
CONTENT=$(awk '/^## [0-9]/{if(found) exit; found=1; next} found{print}' sdk/apps/cli/CHANGELOG.md)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
@@ -349,7 +349,7 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
id: version
run: |
BASE_VERSION=$(node -p "require('./apps/cli/package.json').version")
BASE_VERSION=$(node -p "require('./sdk/apps/cli/package.json').version")
TIMESTAMP=$(date +%s)
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
@@ -365,17 +365,17 @@ jobs:
run: |
node -e '
const fs = require("node:fs");
const path = "apps/cli/package.json";
const path = "sdk/apps/cli/package.json";
const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
pkg.version = process.env.VERSION;
fs.writeFileSync(path, `${JSON.stringify(pkg, null, "\t")}\n`);
'
cat apps/cli/package.json | grep '"version"'
cat sdk/apps/cli/package.json | grep '"version"'
- name: Build platform binaries
if: steps.check_commits.outputs.skip != 'true'
run: bun script/build.ts --install-native-variants --skip-sdk-build
working-directory: apps/cli
working-directory: sdk/apps/cli
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
@@ -401,7 +401,7 @@ jobs:
)
for package_name in "${EXPECTED[@]}"; do
dir="apps/cli/dist/${package_name#@cline/}"
dir="sdk/apps/cli/dist/${package_name#@cline/}"
if [ ! -f "$dir/package.json" ]; then
echo "Missing package manifest: $dir/package.json"
exit 1
@@ -424,7 +424,7 @@ jobs:
env:
NPM_CONFIG_PROVENANCE: "true"
run: bun script/publish-npm.ts --tag nightly
working-directory: apps/cli
working-directory: sdk/apps/cli
- name: Summary
if: steps.check_commits.outputs.skip != 'true'
@@ -31,9 +31,6 @@ jobs:
if: github.repository == 'cline/cline' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dpc/sdk-migration-simpler-login')
runs-on: ubuntu-latest
environment: PublishNightly
defaults:
run:
working-directory: apps/vscode
steps:
- name: Checkout selected branch
@@ -57,13 +54,11 @@ jobs:
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
node-version: 22
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci --include=optional
- name: Install root dependencies
run: npm ci --include=optional
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci --include=optional
run: cd webview-ui && npm ci --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
@@ -114,13 +114,11 @@ jobs:
with:
node-version: 22
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode install --include=optional
- name: Install root dependencies
run: npm install --include=optional
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui install --include=optional
run: cd webview-ui && npm install --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
+3 -5
View File
@@ -128,15 +128,13 @@ jobs:
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
- name: Install root dependencies
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
run: cd webview-ui && npm ci
- name: Install vsce
run: npm install -g @vscode/vsce
+10 -18
View File
@@ -91,15 +91,13 @@ jobs:
apps/vscode/package-lock.json
apps/vscode/webview-ui/package-lock.json
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
- name: Install root dependencies
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
run: cd webview-ui && npm ci
- name: Run Quality Checks (Parallel)
run: npm run ci:check-all
@@ -132,19 +130,16 @@ jobs:
apps/vscode/package-lock.json
apps/vscode/webview-ui/package-lock.json
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
- name: Install root dependencies
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
run: cd webview-ui && npm ci
- name: Set up NPM on Windows
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
run: |
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
@@ -234,15 +229,13 @@ jobs:
apps/vscode/webview-ui/package-lock.json
apps/vscode/testing-platform/package-lock.json
- name: Install extension dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode ci
- name: Install root dependencies
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/webview-ui ci
run: cd webview-ui && npm ci
- name: Download ripgrep binaries
run: npm run download-ripgrep
@@ -251,8 +244,7 @@ jobs:
run: npm run compile-standalone
- name: Install testing platform dependencies
working-directory: ${{ github.workspace }}
run: npm --prefix apps/vscode/testing-platform ci
run: cd testing-platform && npm ci
- name: Running testing platform integration spec tests
timeout-minutes: 7
+3 -3
View File
@@ -17,7 +17,7 @@ jobs:
const labels = context.payload.issue.labels.map(l => l.name);
// Check if JetBrains Plugin is selected
if (body.match(/###\s*Cline Surface\s*\n+JetBrains Plugin/i)) {
if (body.match(/###\s*Plugin Type\s*\n+JetBrains Plugin/i)) {
if (!labels.includes('JetBrains')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
@@ -29,7 +29,7 @@ jobs:
}
// Check if VSCode Extension is selected
if (body.match(/###\s*Cline Surface\s*\n+VSCode Extension/i)) {
if (body.match(/###\s*Plugin Type\s*\n+VSCode Extension/i)) {
if (!labels.includes('VS Code')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
@@ -41,7 +41,7 @@ jobs:
}
// Check if CLI is selected
if (body.match(/###\s*Cline Surface\s*\n+CLI/i)) {
if (body.match(/###\s*Plugin Type\s*\n+CLI/i)) {
if (!labels.includes('CLI')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
-11
View File
@@ -61,17 +61,6 @@ tests/**/cache
# Should never be committed: only exists if a publish aborts mid-swap.
.README.github.bak
# Tauri generated code
apps/*/src-tauri/gen
apps/*/src-tauri/bin
apps/examples/*/src-tauri/gen
apps/examples/*/src-tauri/bin
# Tauri UI test snapshots
apps/*/src/tests/.tui-test
apps/*/src/tests/tui-traces
apps/vscode/webview-ui/src/**/*.js
apps/vscode/webview-ui/src/**/*.js.map
# SDK Session files / User data
.cline/data
-63
View File
@@ -1,68 +1,5 @@
# Changelog
## [3.89.2]
### Fixed
- Complete the fix for the Anthropic provider on VS Code 1.123 and later by upgrading the bundled Anthropic SDK to a release compatible with the Node 24 runtime.
- Update the Vertex AI provider to a compatible Anthropic Vertex SDK release so it works with the upgraded Anthropic SDK.
## [3.89.1]
### Fixed
- Restore the Anthropic provider on VS Code 1.123 and later, where the updated Node 24 runtime broke the bundled Anthropic SDK.
- Handle the DeepSeek V4 reasoning format.
## [3.89.0]
### Added
- Add Claude Fable 5 model support.
### Fixed
- Fix MiniMax M3 thinking controls across gateways.
### Changed
- Clean up the Codex model list.
## [3.88.1]
### Added
- Add a debug section in settings for Cline testers.
### Fixed
- Include the walkthrough markdown files in the VS Code extension package so the first-run walkthrough steps load correctly.
## [3.88.0]
### Added
- Add the latest Fireworks AI serverless models and update the default Fireworks model to Kimi K2.6.
### Fixed
- Fix MCP server delete/add flows so settings writes do not cause the MCP server list to be emptied by the file watcher.
- Remove stale Fireworks AI models and correct Fireworks model metadata and cache pricing.
### Changed
- Always use the upstream Cline recommended models endpoint instead of gating it behind a feature flag.
## [3.87.0]
### Added
- Add MiniMax M3 model support.
### Fixed
- Update VS Code extension dependencies to resolve security issues in `@xmldom/xmldom`, `basic-ftp`, `axios`, `undici`, and other direct/transitive packages.
## [3.86.2]
### Fixed
+3 -7
View File
@@ -51,7 +51,7 @@ for CI/CD and scripting.
npm i -g cline
```
<a href="./apps/cli/README.md">Learn more</a>
<a href="./sdk/apps/cli/README.md">Learn more</a>
<br><br>
</td>
@@ -129,7 +129,7 @@ npm install @cline/sdk
| Product | Description | Location | CHANGELOG |
|---------|------------|--------------|--------------|
| **SDK** | Node.js programmatic agent API and extension exports. | [`sdk/`](https://github.com/cline/cline/tree/main/sdk) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/sdk/CHANGELOG.md) |
| **CLI** | Terminal UI, headless mode, shell commands, and CLI-specific flows. | [`apps/cli/`](https://github.com/cline/cline/tree/main/apps/cli) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/apps/cli/CHANGELOG.md) |
| **CLI** | Terminal UI, headless mode, shell commands, and CLI-specific flows. | [`sdk/apps/cli/`](https://github.com/cline/cline/tree/main/sdk/apps/cli) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/sdk/apps/cli/CHANGELOG.md) |
| **VS Code Extension** | The Marketplace extension and extension host integration. | [`/`](https://github.com/cline/cline/tree/main) (WIP migrating) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/CHANGELOG.md) |
| **JetBrains Plugin** | JetBrains-hosted client that talks to the shared agent core. | Currently we are not open-sourcing JetBrains plugins | - |
| **Kanban** | Web-based multi-agent task board. | [`cline/kanban`](https://github.com/cline/kanban) | [CHANGELOG.md](https://github.com/cline/kanban/blob/main/CHANGELOG.md) |
@@ -212,12 +212,8 @@ cline schedule create "PR summary" \
Chat with your agent from any messaging platform: Telegram, Slack, Discord, Google Chat, WhatsApp, and Linear. Each conversation thread maps to an agent session with full context. Set up access control to restrict who can interact with your agent.
```bash
# Connect to Telegram
cline connect telegram -k $BOT_TOKEN
# Connect to Slack through webhook
cline connect slack --bot-token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
# Connect to Slack using socket mode
cline connect slack --bot-token $SLACK_TOKEN --app-token $SLACK_APP_TOKEN
cline connect slack --token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
```
## Headless CLI for CI/CD
-14
View File
@@ -1,14 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.5/schema.json",
"root": false,
"extends": [
"../sdk/biome.json"
],
"linter": {
"rules": {
"a11y": {
"noStaticElementInteractions": "warn"
}
}
}
}
File diff suppressed because it is too large Load Diff
-255
View File
@@ -1,255 +0,0 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
autoUpdateOnStartup,
checkForUpdates,
getInstallationInfo,
PackageManager,
resolveCliHubOwnerContext,
withMinimumReleaseAgeBypass,
} from "./update";
const originalArgv = [...process.argv];
const originalBuildEnv = process.env.CLINE_BUILD_ENV;
const originalDataDir = process.env.CLINE_DATA_DIR;
const originalHubDiscoveryPath = process.env.CLINE_HUB_DISCOVERY_PATH;
const originalWrapperPath = process.env.CLINE_WRAPPER_PATH;
const originalGlobalSettingsPath = process.env.CLINE_GLOBAL_SETTINGS_PATH;
const originalIsDev = process.env.IS_DEV;
const originalNoAutoUpdate = process.env.CLINE_NO_AUTO_UPDATE;
const tempDirs: string[] = [];
function createFile(path: string): string {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, "");
return path;
}
function createTempFile(pathSuffix: string): string {
const root = mkdtempSync(join(tmpdir(), "cline-update-test-"));
tempDirs.push(root);
return createFile(join(root, pathSuffix));
}
describe("getInstallationInfo", () => {
afterEach(() => {
process.argv = [...originalArgv];
if (originalBuildEnv === undefined) {
delete process.env.CLINE_BUILD_ENV;
} else {
process.env.CLINE_BUILD_ENV = originalBuildEnv;
}
if (originalDataDir === undefined) {
delete process.env.CLINE_DATA_DIR;
} else {
process.env.CLINE_DATA_DIR = originalDataDir;
}
if (originalHubDiscoveryPath === undefined) {
delete process.env.CLINE_HUB_DISCOVERY_PATH;
} else {
process.env.CLINE_HUB_DISCOVERY_PATH = originalHubDiscoveryPath;
}
if (originalWrapperPath === undefined) {
delete process.env.CLINE_WRAPPER_PATH;
} else {
process.env.CLINE_WRAPPER_PATH = originalWrapperPath;
}
if (originalGlobalSettingsPath === undefined) {
delete process.env.CLINE_GLOBAL_SETTINGS_PATH;
} else {
process.env.CLINE_GLOBAL_SETTINGS_PATH = originalGlobalSettingsPath;
}
if (originalIsDev === undefined) {
delete process.env.IS_DEV;
} else {
process.env.IS_DEV = originalIsDev;
}
if (originalNoAutoUpdate === undefined) {
delete process.env.CLINE_NO_AUTO_UPDATE;
} else {
process.env.CLINE_NO_AUTO_UPDATE = originalNoAutoUpdate;
}
vi.restoreAllMocks();
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
it("detects npm installs from the wrapper path passed to the compiled binary", () => {
const wrapperPath = createTempFile("lib/node_modules/cline/bin/cline");
process.env.CLINE_WRAPPER_PATH = wrapperPath;
process.argv = ["bun", "/$bunfs/root/cline", "update", "--verbose"];
expect(getInstallationInfo("1.2.3")).toEqual({
packageManager: PackageManager.NPM,
packageName: "cline",
updateCommand: "npm update -g cline --tag latest",
});
});
it("uses the nightly tag when the current CLI version is nightly", () => {
const wrapperPath = createTempFile("lib/node_modules/cline/bin/cline");
process.env.CLINE_WRAPPER_PATH = wrapperPath;
process.argv = ["bun", "/$bunfs/root/cline", "update", "--verbose"];
expect(getInstallationInfo("1.2.3-nightly.456")).toEqual({
packageManager: PackageManager.NPM,
packageName: "cline",
updateCommand: "npm update -g cline --tag nightly",
});
});
it("falls back to unknown when only Bun's virtual compiled path is available", () => {
delete process.env.CLINE_WRAPPER_PATH;
process.argv = ["bun", "/$bunfs/root/cline", "update", "--verbose"];
expect(getInstallationInfo("1.2.3")).toEqual({
packageManager: PackageManager.UNKNOWN,
packageName: "cline",
});
});
});
describe("auto update settings", () => {
afterEach(() => {
process.argv = [...originalArgv];
if (originalBuildEnv === undefined) {
delete process.env.CLINE_BUILD_ENV;
} else {
process.env.CLINE_BUILD_ENV = originalBuildEnv;
}
if (originalDataDir === undefined) {
delete process.env.CLINE_DATA_DIR;
} else {
process.env.CLINE_DATA_DIR = originalDataDir;
}
if (originalHubDiscoveryPath === undefined) {
delete process.env.CLINE_HUB_DISCOVERY_PATH;
} else {
process.env.CLINE_HUB_DISCOVERY_PATH = originalHubDiscoveryPath;
}
if (originalWrapperPath === undefined) {
delete process.env.CLINE_WRAPPER_PATH;
} else {
process.env.CLINE_WRAPPER_PATH = originalWrapperPath;
}
if (originalGlobalSettingsPath === undefined) {
delete process.env.CLINE_GLOBAL_SETTINGS_PATH;
} else {
process.env.CLINE_GLOBAL_SETTINGS_PATH = originalGlobalSettingsPath;
}
if (originalIsDev === undefined) {
delete process.env.IS_DEV;
} else {
process.env.IS_DEV = originalIsDev;
}
if (originalNoAutoUpdate === undefined) {
delete process.env.CLINE_NO_AUTO_UPDATE;
} else {
process.env.CLINE_NO_AUTO_UPDATE = originalNoAutoUpdate;
}
vi.restoreAllMocks();
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
it("skips startup auto update when disabled globally", () => {
const settingsPath = createTempFile("data/global-settings.json");
writeFileSync(settingsPath, JSON.stringify({ autoUpdateEnabled: false }));
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
delete process.env.IS_DEV;
delete process.env.CLINE_NO_AUTO_UPDATE;
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockRejectedValue(new Error("should not fetch"));
autoUpdateOnStartup();
expect(fetchSpy).not.toHaveBeenCalled();
});
it("still lets manual update checks run when startup auto update is disabled", async () => {
const settingsPath = createTempFile("data/global-settings.json");
writeFileSync(settingsPath, JSON.stringify({ autoUpdateEnabled: false }));
process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath;
delete process.env.CLINE_NO_AUTO_UPDATE;
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ version: "0.0.0" }),
} as Response);
await checkForUpdates({ includeKanban: false });
expect(fetchSpy).toHaveBeenCalled();
});
});
describe("hub restart owner selection", () => {
afterEach(() => {
if (originalBuildEnv === undefined) {
delete process.env.CLINE_BUILD_ENV;
} else {
process.env.CLINE_BUILD_ENV = originalBuildEnv;
}
if (originalDataDir === undefined) {
delete process.env.CLINE_DATA_DIR;
} else {
process.env.CLINE_DATA_DIR = originalDataDir;
}
if (originalHubDiscoveryPath === undefined) {
delete process.env.CLINE_HUB_DISCOVERY_PATH;
} else {
process.env.CLINE_HUB_DISCOVERY_PATH = originalHubDiscoveryPath;
}
});
it("uses the shared hub owner outside production builds", () => {
process.env.CLINE_BUILD_ENV = "development";
process.env.CLINE_DATA_DIR = "/tmp/cline-update-test-data";
delete process.env.CLINE_HUB_DISCOVERY_PATH;
const owner = resolveCliHubOwnerContext();
expect(owner.discoveryPath).toContain("/locks/hub/owners/");
expect(owner.discoveryPath).not.toBe(
"/tmp/cline-update-test-data/locks/hub/production.json",
);
});
});
describe("withMinimumReleaseAgeBypass", () => {
it("adds the package-manager-specific cooldown bypass", () => {
expect(
withMinimumReleaseAgeBypass(
"npm update -g cline --tag latest",
PackageManager.NPM,
).command,
).toBe("npm update -g cline --tag latest --min-release-age=0");
expect(
withMinimumReleaseAgeBypass("bun add -g cline@latest", PackageManager.BUN)
.command,
).toBe("bun add -g cline@latest --minimum-release-age=0");
expect(
withMinimumReleaseAgeBypass(
"yarn global add cline@latest",
PackageManager.YARN,
).command,
).toBe("yarn global add cline@latest");
expect(
withMinimumReleaseAgeBypass(
"yarn global add cline@latest",
PackageManager.YARN,
).env?.YARN_NPM_MINIMAL_AGE_GATE,
).toBe("0");
expect(
withMinimumReleaseAgeBypass(
"pnpm add -g cline@latest",
PackageManager.PNPM,
).env?.pnpm_config_minimum_release_age,
).toBe("0");
});
});
@@ -1,163 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const {
mockGetLastUsedProviderSettings,
mockGetProviderSettings,
mockResolveSystemPrompt,
mockGetProviderCollection,
mockGetBooleanFlagEnabled,
} = vi.hoisted(() => ({
mockGetLastUsedProviderSettings: vi.fn(),
mockGetProviderSettings: vi.fn(),
mockResolveSystemPrompt: vi.fn(),
mockGetProviderCollection: vi.fn(),
mockGetBooleanFlagEnabled: vi.fn(),
}));
vi.mock("@cline/core", async () => {
const actual =
await vi.importActual<typeof import("@cline/core")>("@cline/core");
return {
...actual,
ProviderSettingsManager: class {
getLastUsedProviderSettings(options?: unknown) {
return mockGetLastUsedProviderSettings(options);
}
getProviderSettings(providerId: string) {
return mockGetProviderSettings(providerId);
}
},
CoreSessionService: class {},
SqliteSessionStore: class {},
Llms: {
...actual.Llms,
getProviderCollection: mockGetProviderCollection,
},
};
});
vi.mock("../runtime/prompt", () => ({
resolveSystemPrompt: mockResolveSystemPrompt,
}));
vi.mock("../utils/helpers", () => ({
resolveWorkspaceRoot: vi.fn((cwd: string) => cwd),
}));
vi.mock("../utils/feature-flags", () => ({
getCliFeatureFlagsService: () => ({
getBooleanFlagEnabled: mockGetBooleanFlagEnabled,
}),
}));
vi.mock("../commands/auth", async () => {
const actual =
await vi.importActual<typeof import("../commands/auth")>(
"../commands/auth",
);
return {
...actual,
ensureOAuthProviderApiKey: vi.fn(),
};
});
import { buildConnectorStartRequest } from "./session-runtime";
describe("buildConnectorStartRequest", () => {
beforeEach(() => {
mockGetBooleanFlagEnabled.mockReturnValue(false);
});
afterEach(() => {
vi.clearAllMocks();
delete process.env.OPENROUTER_API_KEY;
});
it("falls back to provider env vars when persisted settings have no api key", async () => {
mockGetLastUsedProviderSettings.mockReturnValue({ provider: "openrouter" });
mockGetProviderSettings.mockReturnValue({
provider: "openrouter",
model: "anthropic/claude-sonnet-4.6",
});
mockGetProviderCollection.mockReturnValue({
provider: { env: ["OPENROUTER_API_KEY"] },
});
mockResolveSystemPrompt.mockResolvedValue("system");
process.env.OPENROUTER_API_KEY = "env-openrouter-key";
const request = await buildConnectorStartRequest({
options: {
cwd: "/tmp/work",
mode: "act",
enableTools: false,
},
io: { writeln: vi.fn(), writeErr: vi.fn() },
loggerConfig: { enabled: false, level: "info", destination: "stdout" },
systemRules: "Rules",
});
expect(request.provider).toBe("openrouter");
expect(request.apiKey).toBe("env-openrouter-key");
expect(request.model).toBe("anthropic/claude-sonnet-4.6");
expect(mockGetLastUsedProviderSettings).toHaveBeenCalledWith({
isClinePassEnabled: false,
});
});
it("uses auth material resolved by provider settings manager", async () => {
mockGetLastUsedProviderSettings.mockReturnValue({ provider: "cline-pass" });
mockGetProviderSettings.mockReturnValue({
provider: "cline-pass",
auth: { accessToken: "workos:resolved-token" },
});
mockGetProviderCollection.mockReturnValue({
provider: { env: ["CLINE_API_KEY"] },
});
mockResolveSystemPrompt.mockResolvedValue("system");
const request = await buildConnectorStartRequest({
options: {
cwd: "/tmp/work",
mode: "act",
enableTools: false,
},
io: { writeln: vi.fn(), writeErr: vi.fn() },
loggerConfig: { enabled: false, level: "info", destination: "stdout" },
systemRules: "Rules",
defaultModel: "cline-pass/glm-5.1",
});
expect(request.provider).toBe("cline-pass");
expect(request.apiKey).toBe("workos:resolved-token");
expect(request.model).toBe("cline-pass/glm-5.1");
});
it("uses auth material resolved by provider settings manager", async () => {
mockGetLastUsedProviderSettings.mockReturnValue({ provider: "cline-pass" });
mockGetProviderSettings.mockReturnValue({
provider: "cline-pass",
auth: { accessToken: "workos:resolved-token" },
});
mockGetProviderCollection.mockReturnValue({
provider: { env: ["CLINE_API_KEY"] },
});
mockResolveSystemPrompt.mockResolvedValue("system");
const request = await buildConnectorStartRequest({
options: {
cwd: "/tmp/work",
mode: "act",
enableTools: false,
},
io: { writeln: vi.fn(), writeErr: vi.fn() },
loggerConfig: { enabled: false, level: "info", destination: "stdout" },
systemRules: "Rules",
defaultModel: "cline-pass/glm-5.1",
});
expect(request.provider).toBe("cline-pass");
expect(request.apiKey).toBe("workos:resolved-token");
expect(request.model).toBe("cline-pass/glm-5.1");
});
});
-251
View File
@@ -1,251 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { Config } from "../utils/types";
const coreMocks = vi.hoisted(() => {
const serviceOptions: Array<{
apiBaseUrl: string;
getAuthToken: () => Promise<string | undefined | null>;
}> = [];
return {
getProviderSettings: vi.fn(),
saveProviderSettings: vi.fn(),
fetchMe: vi.fn(),
fetchBalance: vi.fn(),
fetchOrganizationBalance: vi.fn(),
serviceOptions,
};
});
const telemetryMocks = vi.hoisted(() => ({
identifyTelemetryAccount: vi.fn(),
}));
vi.mock("@cline/core", async (importOriginal) => {
const actual = await importOriginal<typeof import("@cline/core")>();
return {
...actual,
ClineAccountService: class {
constructor(options: {
apiBaseUrl: string;
getAuthToken: () => Promise<string | undefined | null>;
}) {
coreMocks.serviceOptions.push(options);
}
fetchMe() {
return coreMocks.fetchMe();
}
fetchBalance(userId?: string) {
return coreMocks.fetchBalance(userId);
}
fetchOrganizationBalance(organizationId: string) {
return coreMocks.fetchOrganizationBalance(organizationId);
}
},
ProviderSettingsManager: class {
getProviderSettings(providerId: string) {
return coreMocks.getProviderSettings(providerId);
}
saveProviderSettings(settings: unknown, options?: unknown) {
coreMocks.saveProviderSettings(settings, options);
}
},
};
});
vi.mock("../utils/telemetry", () => ({
identifyTelemetryAccount: telemetryMocks.identifyTelemetryAccount,
}));
function makeConfig(overrides: Partial<Config> = {}): Config {
return {
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
apiKey: "",
verbose: false,
sandbox: false,
thinking: false,
outputMode: "text",
mode: "act",
defaultToolAutoApprove: false,
toolPolicies: {},
enableTools: true,
cwd: "/tmp/workspace",
logger: {
debug: vi.fn(),
log: vi.fn(),
error: vi.fn(),
},
...overrides,
} as unknown as Config;
}
function mockFetchJson(body: unknown, status = 200): void {
vi.stubGlobal(
"fetch",
vi.fn(
async () =>
new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
}),
) as unknown as typeof fetch,
);
}
describe("createClineAccountService", () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
coreMocks.getProviderSettings.mockReset();
coreMocks.saveProviderSettings.mockReset();
coreMocks.fetchMe.mockReset();
coreMocks.fetchBalance.mockReset();
coreMocks.fetchOrganizationBalance.mockReset();
coreMocks.serviceOptions.length = 0;
telemetryMocks.identifyTelemetryAccount.mockReset();
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it("refreshes persisted Cline OAuth credentials before creating the account service", async () => {
vi.spyOn(Date, "now").mockReturnValue(100_000);
mockFetchJson({
success: true,
data: {
accessToken: "new-access",
refreshToken: "new-refresh",
tokenType: "Bearer",
expiresAt: "2096-10-02T07:06:40.000Z",
userInfo: {
subject: "sub-new",
email: "new@example.com",
name: "New User",
clineUserId: "acct-new",
accounts: [],
},
},
});
coreMocks.getProviderSettings.mockReturnValue({
provider: "cline",
auth: {
accessToken: "workos:old-access",
refreshToken: "refresh-token",
accountId: "acct-old",
expiresAt: 1,
},
});
const { createClineAccountService } = await import("./cline-account");
const service = await createClineAccountService({ config: makeConfig() });
expect(service).toBeDefined();
expect(globalThis.fetch).toHaveBeenCalled();
expect(coreMocks.saveProviderSettings).toHaveBeenCalledWith(
expect.objectContaining({
provider: "cline",
auth: expect.objectContaining({
accessToken: "workos:new-access",
refreshToken: "new-refresh",
accountId: "acct-new",
expiresAt: 4_000_000_000_000,
}),
}),
{ setLastUsed: false, tokenSource: "oauth" },
);
expect(await coreMocks.serviceOptions[0]?.getAuthToken()).toBe(
"workos:new-access",
);
});
it("asks the user to re-authenticate when Cline OAuth credentials cannot refresh", async () => {
vi.spyOn(Date, "now").mockReturnValue(100_000);
mockFetchJson(
{
error: "invalid_grant",
error_description: "refresh expired",
},
401,
);
coreMocks.getProviderSettings.mockReturnValue({
provider: "cline",
auth: {
accessToken: "workos:old-access",
refreshToken: "refresh-token",
expiresAt: 1,
},
});
const { createClineAccountService } = await import("./cline-account");
await expect(
createClineAccountService({ config: makeConfig() }),
).rejects.toThrow(
"Cline account requires re-authentication. Run cline auth cline.",
);
});
});
describe("loadClineAccountSnapshot", () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
coreMocks.getProviderSettings.mockReset();
coreMocks.saveProviderSettings.mockReset();
coreMocks.fetchMe.mockReset();
coreMocks.fetchBalance.mockReset();
coreMocks.fetchOrganizationBalance.mockReset();
coreMocks.serviceOptions.length = 0;
telemetryMocks.identifyTelemetryAccount.mockReset();
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it("identifies the loaded Cline account for telemetry and feature flags", async () => {
coreMocks.getProviderSettings.mockReturnValue({
provider: "cline",
apiKey: "account-token",
});
const { loadClineAccountSnapshot } = await import("./cline-account");
coreMocks.fetchMe.mockResolvedValue({
id: "user-1",
email: "user@example.com",
displayName: "User One",
photoUrl: "",
createdAt: "",
updatedAt: "",
organizations: [
{
active: true,
memberId: "member-1",
name: "Acme",
organizationId: "org-1",
roles: ["member"],
},
],
});
coreMocks.fetchBalance.mockResolvedValue({ balance: 10, userId: "user-1" });
coreMocks.fetchOrganizationBalance.mockResolvedValue({
balance: 20,
organizationId: "org-1",
});
await loadClineAccountSnapshot({ config: makeConfig() });
expect(telemetryMocks.identifyTelemetryAccount).toHaveBeenCalledWith(
{
id: "user-1",
email: "user@example.com",
provider: "cline",
organizationId: "org-1",
organizationName: "Acme",
memberId: "member-1",
},
expect.any(Object),
);
});
});
-19
View File
@@ -1,19 +0,0 @@
import { afterEach, describe, expect, it } from "vitest";
import {
disposeCliFeatureFlagsService,
getCliFeatureFlagsService,
} from "./feature-flags";
describe("CLI feature flags singleton", () => {
afterEach(async () => {
await disposeCliFeatureFlagsService();
});
it("recreates the singleton after disposal", async () => {
const service = getCliFeatureFlagsService();
await disposeCliFeatureFlagsService();
expect(getCliFeatureFlagsService()).not.toBe(service);
});
});
-111
View File
@@ -1,111 +0,0 @@
import { join } from "node:path";
import {
type BasicLogger,
type FeatureFlagsContext,
FeatureFlagsService,
type ITelemetryService,
NoOpFeatureFlagsProvider,
registerDisposable,
resolveCoreDistinctId,
} from "@cline/core";
import {
buildClinePostHogClient,
PostHogFeatureFlagsProvider,
} from "@cline/core/services/feature-flags/posthog";
import { resolveClineDataDir } from "@cline/shared/storage";
let cliFeatureFlagsContext: FeatureFlagsContext = { clientName: "cline-cli" };
let cliFeatureFlagsService: FeatureFlagsService | undefined;
const CLI_FEATURE_FLAGS_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
function resolveCliFeatureFlagsCachePath(): string {
return join(resolveClineDataDir(), "cache", "feature-flags.json");
}
function ensureCliDistinctId(): string {
const distinctId = cliFeatureFlagsContext.distinctId?.trim();
if (distinctId) {
return distinctId;
}
const resolved = resolveCoreDistinctId();
cliFeatureFlagsContext.distinctId = resolved;
return resolved;
}
export function getCliFeatureFlagsContext(): FeatureFlagsContext {
ensureCliDistinctId();
return { ...cliFeatureFlagsContext };
}
export function getCliFeatureFlagsService(options?: {
logger?: BasicLogger;
telemetry?: ITelemetryService;
}): FeatureFlagsService {
if (!cliFeatureFlagsService) {
const apiKey = process.env.TELEMETRY_SERVICE_API_KEY;
const provider =
apiKey &&
process.env.IS_TEST !== "true" &&
process.env.E2E_TEST !== "true"
? new PostHogFeatureFlagsProvider({
client: buildClinePostHogClient(apiKey),
config: {
logger: options?.logger,
},
})
: new NoOpFeatureFlagsProvider();
cliFeatureFlagsService = new FeatureFlagsService({
provider,
telemetry: options?.telemetry,
logger: options?.logger,
context: getCliFeatureFlagsContext(),
cacheFilePath: resolveCliFeatureFlagsCachePath(),
persistentCacheMaxAgeMs: CLI_FEATURE_FLAGS_CACHE_MAX_AGE_MS,
});
registerDisposable(disposeCliFeatureFlagsService);
}
return cliFeatureFlagsService;
}
export function refreshCliFeatureFlagsInBackground(logger?: BasicLogger): void {
const service = getCliFeatureFlagsService({ logger });
void service.poll().catch((error) => {
logger?.error?.("Error refreshing CLI feature flags", { error });
});
}
export async function disposeCliFeatureFlagsService(): Promise<void> {
if (!cliFeatureFlagsService) {
return;
}
const current = cliFeatureFlagsService;
cliFeatureFlagsService = undefined;
await current.dispose();
}
export async function identifyFeatureFlagsAccount(
account: { id?: string; email?: string },
logger?: BasicLogger,
): Promise<void> {
const accountId = account.id?.trim();
cliFeatureFlagsContext = {
...cliFeatureFlagsContext,
...(accountId ? { distinctId: accountId, userId: accountId } : {}),
...(account.email?.trim() ? { email: account.email.trim() } : {}),
};
if (!cliFeatureFlagsService) {
return;
}
cliFeatureFlagsService.setContext(getCliFeatureFlagsContext());
try {
await cliFeatureFlagsService.poll();
} catch (error) {
logger?.error?.("Error polling CLI feature flags", { error });
}
}
-99
View File
@@ -1,99 +0,0 @@
import { describe, expect, it } from "vitest";
import { buildHistoryResumeArgs } from "./history-resume";
describe("buildHistoryResumeArgs", () => {
it("replaces the history subcommand with --id", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["history"],
remainingArgs: ["history"],
}),
).toEqual(["--id", "sess_1"]);
});
it("preserves global flags that precede the subcommand", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: [
"--data-dir",
"/tmp/data",
"-m",
"claude-sonnet-4-6",
"history",
"--limit",
"5",
],
remainingArgs: ["history", "--limit", "5"],
}),
).toEqual([
"--data-dir",
"/tmp/data",
"-m",
"claude-sonnet-4-6",
"--id",
"sess_1",
]);
});
it("keeps a global flag value that matches the subcommand alias", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["-m", "h", "h"],
remainingArgs: ["h"],
}),
).toEqual(["-m", "h", "--id", "sess_1"]);
});
it("forwards a config dir passed as a subcommand option", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["history", "--config", "/tmp/conf"],
remainingArgs: ["history", "--config", "/tmp/conf"],
configDir: "/tmp/conf",
}),
).toEqual(["--config", "/tmp/conf", "--id", "sess_1"]);
});
it("does not duplicate a config dir already in the global flags", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["--config", "/tmp/conf", "history"],
remainingArgs: ["history"],
configDir: "/tmp/conf",
}),
).toEqual(["--config", "/tmp/conf", "--id", "sess_1"]);
});
it("recognizes the --config=<dir> spelling in global flags", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["--config=/tmp/conf", "history"],
remainingArgs: ["history"],
configDir: "/tmp/conf",
}),
).toEqual(["--config=/tmp/conf", "--id", "sess_1"]);
});
it("returns undefined when remaining args are not a suffix of argv", () => {
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["history", "--limit", "5"],
remainingArgs: ["history", "--limit", "9"],
}),
).toBeUndefined();
expect(
buildHistoryResumeArgs({
sessionId: "sess_1",
normalizedArgs: ["history"],
remainingArgs: ["extra", "history"],
}),
).toBeUndefined();
});
});
-115
View File
@@ -1,115 +0,0 @@
import { resolveCliLaunchSpec } from "./internal-launch";
export interface HistoryResumeCommand {
launcher: string;
childArgs: string[];
}
export interface BuildHistoryResumeArgsInput {
sessionId: string;
/** Full normalized CLI args (process.argv.slice(2) after normalization). */
normalizedArgs: string[];
/**
* Commander's `program.args` after parsing: the `history` subcommand token
* and everything following it. Must be a suffix of `normalizedArgs`.
*/
remainingArgs: string[];
/**
* Config dir resolved from the full argv. Forwarded explicitly because
* `--config` may have been passed as a `history` subcommand option, which
* would otherwise be dropped with the rest of the subcommand args.
*/
configDir?: string;
}
/**
* Builds argv for relaunching the CLI as `cline <globalFlags> --id <sessionId>`
* after a session is picked in `cline history`. Returns undefined when the
* global-flag prefix cannot be derived safely (caller falls back to resuming
* in-process).
*/
export function buildHistoryResumeArgs(
input: BuildHistoryResumeArgsInput,
): string[] | undefined {
const { sessionId, normalizedArgs, remainingArgs, configDir } = input;
const splitIndex = normalizedArgs.length - remainingArgs.length;
if (splitIndex < 0) {
return undefined;
}
for (let i = 0; i < remainingArgs.length; i++) {
if (normalizedArgs[splitIndex + i] !== remainingArgs[i]) {
return undefined;
}
}
const globalArgs = normalizedArgs.slice(0, splitIndex);
const args = [...globalArgs];
const hasConfigFlag = globalArgs.some(
(arg) => arg === "--config" || arg.startsWith("--config="),
);
if (configDir && !hasConfigFlag) {
args.push("--config", configDir);
}
args.push("--id", sessionId);
return args;
}
export function buildHistoryResumeCommand(
input: BuildHistoryResumeArgsInput,
): HistoryResumeCommand | undefined {
const childArgs = buildHistoryResumeArgs(input);
if (!childArgs) {
return undefined;
}
const spec = resolveCliLaunchSpec();
if (!spec) {
return undefined;
}
return {
launcher: spec.launcher,
childArgs: [...spec.childArgsPrefix, ...childArgs],
};
}
/**
* Resumes a history-picked session in a fresh `cline --id <sessionId>` child
* process with inherited stdio, and returns its exit code. Creating a second
* OpenTUI renderer in the picker's process can crash natively during teardown
* (Bun "panic(main thread): Segmentation fault" on Ctrl+C), so the resumed
* interactive TUI must get a process of its own.
*
* Returns undefined when the child cannot be launched; the caller should fall
* back to resuming in-process.
*/
export async function spawnHistoryResume(
input: BuildHistoryResumeArgsInput,
): Promise<number | undefined> {
const command = buildHistoryResumeCommand(input);
if (!command) {
return undefined;
}
const { spawn } = await import("node:child_process");
return await new Promise<number | undefined>((resolve) => {
let child: ReturnType<typeof spawn>;
try {
child = spawn(command.launcher, command.childArgs, {
stdio: "inherit",
});
} catch {
resolve(undefined);
return;
}
// The child shares this foreground process group, so terminal-generated
// Ctrl+C already reaches it. Keep the parent alive to reap the child
// without re-forwarding a second signal into the TUI teardown path.
const suppressParentSignal = () => {};
process.on("SIGINT", suppressParentSignal);
process.on("SIGTERM", suppressParentSignal);
const finish = (value: number | undefined) => {
process.off("SIGINT", suppressParentSignal);
process.off("SIGTERM", suppressParentSignal);
resolve(value);
};
child.once("error", () => finish(undefined));
child.once("exit", (code, signal) => finish(signal ? 1 : (code ?? 0)));
});
}
@@ -1,34 +0,0 @@
import { describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
listLocalProviders: vi.fn(async () => ({ providers: [], settingsPath: "" })),
getBooleanFlagEnabled: vi.fn(() => true),
}));
vi.mock("@cline/core", async (importOriginal) => {
const actual = await importOriginal<typeof import("@cline/core")>();
return {
...actual,
listLocalProviders: mocks.listLocalProviders,
};
});
vi.mock("./feature-flags", () => ({
getCliFeatureFlagsService: () => ({
getBooleanFlagEnabled: mocks.getBooleanFlagEnabled,
}),
}));
describe("listLocalProviders", () => {
it("passes the ClinePass feature flag into the SDK provider list", async () => {
const { listLocalProviders } = await import("./provider-catalog");
const manager = {} as never;
await listLocalProviders(manager);
expect(mocks.getBooleanFlagEnabled).toHaveBeenCalledWith("ext-cline-pass");
expect(mocks.listLocalProviders).toHaveBeenCalledWith(manager, {
isClinePassEnabled: true,
});
});
});
-14
View File
@@ -1,14 +0,0 @@
import {
listLocalProviders as internalListLocalProviders,
type ProviderSettingsManager,
} from "@cline/core";
import { getCliFeatureFlagsService } from "./feature-flags";
export async function listLocalProviders(
manager: ProviderSettingsManager,
): ReturnType<typeof internalListLocalProviders> {
return await internalListLocalProviders(manager, {
isClinePassEnabled:
getCliFeatureFlagsService().getBooleanFlagEnabled("ext-cline-pass"),
});
}
@@ -1,81 +0,0 @@
import { describe, expect, it } from "vitest";
import { PLATFORMS, shouldIncludeField } from "./platforms";
describe("connect wizard platform security fields", () => {
it("does not ask Telegram users to re-enter the bot username", () => {
const telegram = PLATFORMS.find((platform) => platform.id === "telegram");
expect(telegram?.fields.map((field) => field.label)).toEqual(["Bot token"]);
expect(telegram?.fields.map((field) => field.flag)).toEqual(["-k"]);
});
it("rejects unsafe Telegram and Slack access restriction identifiers", () => {
const telegram = PLATFORMS.find((platform) => platform.id === "telegram");
const slack = PLATFORMS.find((platform) => platform.id === "slack");
const telegramUser = telegram?.security?.fields.find(
(field) => field.key === "userId",
);
const slackTeam = slack?.security?.fields.find(
(field) => field.key === "teamId",
);
const slackUser = slack?.security?.fields.find(
(field) => field.key === "userId",
);
expect(telegramUser?.validate?.("123456")).toBeUndefined();
expect(telegramUser?.validate?.("123; rm -rf /")).toContain("digits");
expect(slackTeam?.validate?.("T01ABC123")).toBeUndefined();
expect(slackTeam?.validate?.("T01;bad")).toContain("Slack workspace");
expect(slackUser?.validate?.("U01ABC123")).toBeUndefined();
expect(slackUser?.validate?.("U01$(bad)")).toContain("Slack member");
});
it("uses the Telegram allowed user ID flag for wizard security", () => {
const telegram = PLATFORMS.find((platform) => platform.id === "telegram");
const args = telegram?.security?.buildArgs({
userId: "123456",
});
expect(args).toEqual(["--allowed-user-id", "123456"]);
});
it("builds an exact-match Slack authorization hook", () => {
const slack = PLATFORMS.find((platform) => platform.id === "slack");
const args = slack?.security?.buildArgs({
teamId: "T01ABC123",
userId: "U01ABC123",
});
expect(args).toEqual([
"--hook-command",
`jq -r ".payload.actor.participantKey" | grep -qx "slack:team:T01ABC123:user:U01ABC123" && echo '{"action":"allow"}' || echo '{"action":"deny"}'`,
]);
});
it("asks Slack users for mode-specific setup fields", () => {
const slack = PLATFORMS.find((platform) => platform.id === "slack");
const fields = slack?.fields ?? [];
const webhookValues = { "--base-url": "https://example.test" };
const socketValues = { "--base-url": "" };
expect(fields.map((field) => field.flag)).toEqual([
"--bot-token",
"--base-url",
"--signing-secret",
"--app-token",
]);
expect(
fields
.filter((field) => shouldIncludeField(field, webhookValues))
.map((field) => field.flag),
).toEqual(["--bot-token", "--base-url", "--signing-secret"]);
expect(
fields
.filter((field) => shouldIncludeField(field, socketValues))
.map((field) => field.flag),
).toEqual(["--bot-token", "--base-url", "--app-token"]);
});
});
-41
View File
@@ -1,41 +0,0 @@
import * as p from "@clack/prompts";
import {
authorizeMcpServerOAuth,
resolveDefaultMcpSettingsPath,
} from "@cline/core";
import open from "open";
function toErrorMessage(error: unknown): string {
if (error instanceof Error) {
const message = error.message.trim();
if (message.length > 0) {
return message;
}
}
return String(error);
}
export async function authorizeMcpServerOAuthWithBrowser(
name: string,
): Promise<void> {
p.log.info("Opening browser for MCP OAuth authorization");
try {
const result = await authorizeMcpServerOAuth({
serverName: name,
filePath: resolveDefaultMcpSettingsPath(),
openUrl: async (url) => {
p.log.message(`Authorization URL: ${url}`);
await open(url, { wait: false });
},
onServerListening: (info) => {
p.log.message(`Waiting for OAuth callback at ${info.callbackUrl}`);
},
});
p.log.success(result.message);
} catch (error) {
p.log.error(`OAuth authorization failed: ${toErrorMessage(error)}`);
p.log.warn(
`Server "${name}" is still saved. Choose "Authorize OAuth" to retry.`,
);
}
}
-21
View File
@@ -1,21 +0,0 @@
{
"extends": "../../sdk/packages/tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"types": ["bun", "node"],
"paths": {
"@cline/core": ["../../sdk/packages/core/src/index.ts"],
"@cline/core/*": [
"../../sdk/packages/core/src/*",
"../../sdk/packages/core/src/*/index.ts"
],
"@cline/shared": ["../../sdk/packages/shared/src/index.ts"],
"@cline/shared/*": [
"../../sdk/packages/shared/src/*",
"../../sdk/packages/shared/src/*/index.ts"
]
}
},
"include": ["src/**/*.ts"],
"exclude": ["src/webview/**"]
}
@@ -1,23 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": ["./*"],
"@cline/agents": ["../../../sdk/packages/agents/src/index.ts"],
"@cline/core": ["../../../sdk/packages/core/src/index.ts"],
"@cline/core/hub": ["../../../sdk/packages/core/src/hub/index.ts"],
"@cline/llms": ["../../../sdk/packages/llms/src/index.ts"],
"@cline/shared": ["../../../sdk/packages/shared/src/index.ts"],
"@cline/shared/storage": [
"../../../sdk/packages/shared/src/storage/index.ts"
],
"@cline/shared/db": ["../../../sdk/packages/shared/src/db/index.ts"],
"@cline/shared/*": [
"../../../sdk/packages/shared/src/*",
"../../../sdk/packages/shared/src/*/index.ts"
]
}
},
"include": ["sidecar/**/*.ts", "scripts/**/*.ts", "global.d.ts", "bun.mts"],
"exclude": ["node_modules", "webview"]
}
@@ -1,28 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": ["./*"],
"@cline/agents": ["../../../../sdk/packages/agents/src/index.ts"],
"@cline/core": ["../../../../sdk/packages/core/src/index.ts"],
"@cline/llms": ["../../../../sdk/packages/llms/src/index.ts"],
"@cline/shared": ["../../../../sdk/packages/shared/src/index.ts"],
"@cline/shared/storage": [
"../../../../sdk/packages/shared/src/storage/index.ts"
],
"@cline/shared/db": ["../../../../sdk/packages/shared/src/db/index.ts"],
"@cline/shared/*": [
"../../../../sdk/packages/shared/src/*",
"../../../../sdk/packages/shared/src/*/index.ts"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}
-35
View File
@@ -1,35 +0,0 @@
{
"extends": "../../../sdk/packages/tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"module": "ESNext",
"moduleResolution": "Bundler",
"target": "ESNext",
"paths": {
"@cline/agents": ["../../../sdk/packages/agents/src/index.ts"],
"@cline/agents/*": [
"../../../sdk/packages/agents/src/*",
"../../../sdk/packages/agents/src/*/index.ts"
],
"@cline/core": ["../../../sdk/packages/core/src/index.ts"],
"@cline/core/hub/daemon-entry": [
"../../../sdk/packages/core/src/hub/daemon/entry.ts"
],
"@cline/core/*": [
"../../../sdk/packages/core/src/*",
"../../../sdk/packages/core/src/*/index.ts"
],
"@cline/llms": ["../../../sdk/packages/llms/src/index.ts"],
"@cline/shared": ["../../../sdk/packages/shared/src/index.ts"],
"@cline/shared/storage": [
"../../../sdk/packages/shared/src/storage/index.ts"
],
"@cline/shared/db": ["../../../sdk/packages/shared/src/db/index.ts"],
"@cline/shared/*": [
"../../../sdk/packages/shared/src/*",
"../../../sdk/packages/shared/src/*/index.ts"
]
}
},
"include": ["sidecar/**/*.ts"]
}
-31
View File
@@ -1,31 +0,0 @@
{
"compilerOptions": {
"paths": {
"@cline/agents": ["../sdk/packages/agents/src/index.ts"],
"@cline/agents/*": [
"../sdk/packages/agents/src/*",
"../sdk/packages/agents/src/*/index.ts"
],
"@cline/cline-hub": ["./cline-hub/src/server.ts"],
"@cline/core": ["../sdk/packages/core/src/index.ts"],
"@cline/core/hub/daemon-entry": [
"../sdk/packages/core/src/hub/daemon/entry.ts"
],
"@cline/core/*": [
"../sdk/packages/core/src/*",
"../sdk/packages/core/src/*/index.ts"
],
"@cline/core/telemetry": [
"../sdk/packages/core/src/services/telemetry/index.ts"
],
"@cline/llms": ["../sdk/packages/llms/src/index.ts"],
"@cline/shared": ["../sdk/packages/shared/src/index.ts"],
"@cline/shared/storage": ["../sdk/packages/shared/src/storage/index.ts"],
"@cline/shared/db": ["../sdk/packages/shared/src/db/index.ts"],
"@cline/shared/*": [
"../sdk/packages/shared/src/*",
"../sdk/packages/shared/src/*/index.ts"
]
}
}
}
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Cline Bot Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+14 -7
View File
@@ -50,8 +50,8 @@
"useEnumInitializers": "off",
"useSelfClosingElements": "info",
"useSingleVarDeclarator": "off",
"useNumberNamespace": "off",
"noInferrableTypes": "off",
"useNumberNamespace": "info",
"noInferrableTypes": "info",
"useTemplate": "info",
"noUselessElse": "info"
},
@@ -122,7 +122,6 @@
"!!**/out",
"!!**/evals",
"!!**/playwright",
"!!**/.vscode-test",
"!!**/test-results",
"!!**/node_modules",
"!!**/webview-ui/build",
@@ -131,7 +130,9 @@
"!!**/tests/specs"
]
},
"plugins": ["src/dev/grit/process-env.grit"],
"plugins": [
"src/dev/grit/process-env.grit"
],
"overrides": [
{
"includes": [
@@ -146,11 +147,15 @@
"!!src/integrations/terminal/**",
"!!src/core/controller/ui/openWalkthrough.ts"
],
"plugins": ["src/dev/grit/vscode-api.grit"]
"plugins": [
"src/dev/grit/vscode-api.grit"
]
},
{
// Do not use console logging directly, use the Logger service instead.
"plugins": ["src/dev/grit/console-log.grit"],
"plugins": [
"src/dev/grit/console-log.grit"
],
"includes": [
"**",
"!!**/esbuild.*",
@@ -183,7 +188,9 @@
"!!src/core/storage/utils/state-helpers.ts",
"!!src/extension.ts"
],
"plugins": ["src/dev/grit/use-cache-service.grit"]
"plugins": [
"src/dev/grit/use-cache-service.grit"
]
}
]
}
+48 -23
View File
@@ -1,16 +1,19 @@
{
"name": "claude-dev",
"version": "3.89.2",
"version": "3.86.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.89.2",
"version": "3.86.2",
"license": "Apache-2.0",
"workspaces": [
"."
],
"dependencies": {
"@anthropic-ai/sdk": "^0.50.4",
"@anthropic-ai/vertex-sdk": "^0.11.5",
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@azure/identity": "^4.13.0",
@@ -156,24 +159,42 @@
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.50.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.50.4.tgz",
"integrity": "sha512-zZOWyIuznx2uqiRcCNuidkAsLC8IBHgS9lTwSVEB29sUCwEcqL95MWpWP1nccHSKfiZga+hbZaI0btR2zjuMmw==",
"license": "MIT",
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@anthropic-ai/vertex-sdk": {
"version": "0.11.5",
"resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.11.5.tgz",
"integrity": "sha512-V7sB5nY80unEQu8lSQaEzh1WhYwpIdpC3iXNRHUskghkuQDhS6dQu2ASZBgA5MNuJ1Yv5PhY61NM15dLaQhPQw==",
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.37.0.tgz",
"integrity": "sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": ">=0.50.3 <1",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/@anthropic-ai/vertex-sdk": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.6.4.tgz",
"integrity": "sha512-rMBlO2jF53TfMRmsQMm1bPO2JRUh4jYddjq/OJLj8DSAkfbCrNWhc0yhDed6oLYJg5s+VpDbvlPzMggqHhTfMw==",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": ">=0.35 <1",
"google-auth-library": "^9.4.2"
}
},
@@ -9361,6 +9382,10 @@
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
"license": "MIT"
},
"node_modules/claude-dev": {
"resolved": "",
"link": true
},
"node_modules/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -18525,9 +18550,9 @@
}
},
"node_modules/shell-quote": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
"integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
+12 -9
View File
@@ -2,8 +2,11 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.89.2",
"version": "3.86.2",
"icon": "assets/icons/icon.png",
"workspaces": [
"."
],
"engines": {
"vscode": "^1.84.0"
},
@@ -389,18 +392,18 @@
"protos": "node scripts/build-proto.mjs",
"protos-python": "node scripts/build-python-proto.mjs",
"download-ripgrep": "node scripts/download-ripgrep.mjs",
"postprotos": "biome format --config-path ./biome.jsonc src/shared/proto src/generated webview-ui/src/services/grpc-client.ts --write --no-errors-on-unmatched",
"postprotos": "biome format src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --no-errors-on-unmatched",
"clean:build": "rimraf dist dist-standalone webview-ui/build src/generated out/",
"clean:deps": "rimraf node_modules webview-ui/node_modules",
"clean:all": "npm run clean:build && npm run clean:deps",
"compile-tests": "node ./scripts/build-tests.js",
"watch-tests": "tsc -p . -w --outDir out",
"check-types": "npm run protos && tsc --noEmit && cd webview-ui && tsc --noEmit",
"lint": "biome lint --config-path ./biome.jsonc --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && npm run lint:proto",
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc --noEmit",
"lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && npm run lint:proto",
"lint:proto": "bash ./scripts/proto-lint.sh",
"format": "biome format --config-path ./biome.jsonc --changed --since main --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
"format:fix": "biome check --config-path ./biome.jsonc --changed --since main --no-errors-on-unmatched --files-ignore-unknown=true --write",
"fix:all": "biome check --config-path ./biome.jsonc --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
"format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
"format:fix": "biome check --changed --no-errors-on-unmatched --files-ignore-unknown=true --write",
"fix:all": "biome check --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
"ci:check-all": "npx npm-run-all -p check-types lint format",
"ci:build": "npm run protos && npm run build:webview && node esbuild.mjs && npm run compile-tests",
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
@@ -486,8 +489,8 @@
"typescript": "^5.4.5"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.50.4",
"@anthropic-ai/vertex-sdk": "^0.11.5",
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@azure/identity": "^4.13.0",
@@ -87,30 +87,6 @@ describe("AnthropicHandler", () => {
result.id.should.equal("claude-opus-4-8:1m")
result.info.should.deepEqual(anthropicModels["claude-opus-4-8:1m"])
})
it("should return the Fable 5 model when configured", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-fable-5",
})
const result = handler.getModel()
result.id.should.equal("claude-fable-5")
result.info.should.deepEqual(anthropicModels["claude-fable-5"])
})
it("should return the Fable 5 1m model when configured", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-fable-5:1m",
})
const result = handler.getModel()
result.id.should.equal("claude-fable-5:1m")
result.info.should.deepEqual(anthropicModels["claude-fable-5:1m"])
})
})
describe("createMessage", () => {
@@ -215,11 +215,6 @@ describe("AwsBedrockHandler", () => {
bedrockModels["anthropic.claude-opus-4-8:1m"].supportsGlobalEndpoint.should.equal(true)
})
it("should mark Bedrock Fable 5 variants as global-endpoint capable", () => {
bedrockModels["anthropic.claude-fable-5"].supportsGlobalEndpoint.should.equal(true)
bedrockModels["anthropic.claude-fable-5:1m"].supportsGlobalEndpoint.should.equal(true)
})
it("should include Vertex Opus 4.7 variants in the derived global model list", () => {
vertexModels["claude-opus-4-7"].supportsGlobalEndpoint.should.equal(true)
vertexModels["claude-opus-4-7:1m"].supportsGlobalEndpoint.should.equal(true)
@@ -233,13 +228,6 @@ describe("AwsBedrockHandler", () => {
vertexGlobalModels.should.have.property("claude-opus-4-8")
vertexGlobalModels.should.have.property("claude-opus-4-8:1m")
})
it("should include Vertex Fable 5 variants in the derived global model list", () => {
vertexModels["claude-fable-5"].supportsGlobalEndpoint.should.equal(true)
vertexModels["claude-fable-5:1m"].supportsGlobalEndpoint.should.equal(true)
vertexGlobalModels.should.have.property("claude-fable-5")
vertexGlobalModels.should.have.property("claude-fable-5:1m")
})
})
const mockOptions: AwsBedrockHandlerOptions = {
@@ -416,26 +416,6 @@ describe("ClaudeCodeHandler", () => {
model.info.contextWindow.should.equal(1_000_000)
})
it("should support Fable 5 model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "claude-fable-5",
})
const model = handler.getModel()
model.id.should.equal("claude-fable-5")
model.info.contextWindow.should.equal(200_000)
})
it("should support Fable 5 1m model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "claude-fable-5[1m]",
})
const model = handler.getModel()
model.id.should.equal("claude-fable-5[1m]")
model.info.contextWindow.should.equal(1_000_000)
})
it("should support Opus 1m alias model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "opus[1m]",
@@ -1,43 +0,0 @@
import "should";
import {
huggingFaceDefaultModelId,
huggingFaceModels,
} from "../../../../shared/api";
import { HuggingFaceHandler } from "../huggingface";
describe("HuggingFaceHandler", () => {
it("uses dynamic Hugging Face model info for models outside the static list", () => {
const modelInfo = {
maxTokens: 8192,
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "Available on providers: test-provider",
};
const handler = new HuggingFaceHandler({
huggingFaceApiKey: "test-api-key",
huggingFaceModelId: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
huggingFaceModelInfo: modelInfo,
});
handler.getModel().should.deepEqual({
id: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
info: modelInfo,
});
});
it("preserves unknown model IDs when model info is unavailable", () => {
const handler = new HuggingFaceHandler({
huggingFaceApiKey: "test-api-key",
huggingFaceModelId: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
});
handler.getModel().should.deepEqual({
id: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
info: huggingFaceModels[huggingFaceDefaultModelId],
});
});
});
+9 -10
View File
@@ -81,12 +81,11 @@ export class DeepSeekHandler implements ApiHandler {
const client = this.ensureClient()
const model = this.getModel()
const isDeepSeekReasonerModel = model.id.includes("deepseek-reasoner")
const isDeepSeekThinkingModel =
isDeepSeekReasonerModel || model.id === "deepseek-v4-flash" || model.id === "deepseek-v4-pro"
model.id.includes("deepseek-reasoner") || model.id === "deepseek-v4-flash" || model.id === "deepseek-v4-pro"
const convertedMessages = convertToOpenAiMessages(messages)
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = isDeepSeekReasonerModel
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = isDeepSeekThinkingModel
? [{ role: "system", content: systemPrompt }, ...addReasoningContent(convertedMessages, messages)]
: [{ role: "system", content: systemPrompt }, ...convertedMessages]
@@ -105,13 +104,6 @@ export class DeepSeekHandler implements ApiHandler {
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield {
type: "reasoning",
reasoning: (delta.reasoning_content as string | undefined) || "",
}
}
if (delta?.content) {
yield {
type: "text",
@@ -123,6 +115,13 @@ export class DeepSeekHandler implements ApiHandler {
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
}
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield {
type: "reasoning",
reasoning: (delta.reasoning_content as string | undefined) || "",
}
}
if (chunk.usage) {
yield* this.yieldUsage(model.info, chunk.usage)
}
@@ -1,66 +1,56 @@
import {
huggingFaceDefaultModelId,
huggingFaceModels,
type ModelInfo,
} from "@shared/api";
import { calculateApiCostOpenAI } from "@utils/cost";
import type OpenAI from "openai";
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions";
import type { ClineStorageMessage } from "@/shared/messages/content";
import { createOpenAIClient } from "@/shared/net";
import type { ApiHandler, CommonApiHandlerOptions } from "../";
import { withRetry } from "../retry";
import { convertToOpenAiMessages } from "../transform/openai-format";
import type { ApiStream } from "../transform/stream";
import {
getOpenAIToolParams,
ToolCallProcessor,
} from "../transform/tool-call-processor";
import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, ModelInfo } from "@shared/api"
import { calculateApiCostOpenAI } from "@utils/cost"
import OpenAI from "openai"
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
import { ClineStorageMessage } from "@/shared/messages/content"
import { createOpenAIClient } from "@/shared/net"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-processor"
interface HuggingFaceHandlerOptions extends CommonApiHandlerOptions {
huggingFaceApiKey?: string;
huggingFaceModelId?: string;
huggingFaceModelInfo?: ModelInfo;
huggingFaceApiKey?: string
huggingFaceModelId?: string
huggingFaceModelInfo?: ModelInfo
}
export class HuggingFaceHandler implements ApiHandler {
private options: HuggingFaceHandlerOptions;
private client: OpenAI | undefined;
private cachedModel: { id: string; info: ModelInfo } | undefined;
private options: HuggingFaceHandlerOptions
private client: OpenAI | undefined
private cachedModel: { id: HuggingFaceModelId; info: ModelInfo } | undefined
constructor(options: HuggingFaceHandlerOptions) {
this.options = options;
this.options = options
}
private ensureClient(): OpenAI {
if (!this.client) {
if (!this.options.huggingFaceApiKey) {
throw new Error("Hugging Face API key is required");
throw new Error("Hugging Face API key is required")
}
try {
this.client = createOpenAIClient({
baseURL: "https://router.huggingface.co/v1",
apiKey: this.options.huggingFaceApiKey,
});
})
} catch (error: any) {
throw new Error(`Error creating Hugging Face client: ${error.message}`);
throw new Error(`Error creating Hugging Face client: ${error.message}`)
}
}
return this.client;
return this.client
}
private async *yieldUsage(
info: ModelInfo,
usage: OpenAI.Completions.CompletionUsage | undefined,
): ApiStream {
private async *yieldUsage(info: ModelInfo, usage: OpenAI.Completions.CompletionUsage | undefined): ApiStream {
if (!usage) {
return;
return
}
const inputTokens = usage.prompt_tokens || 0;
const outputTokens = usage.completion_tokens || 0;
const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens);
const inputTokens = usage.prompt_tokens || 0
const outputTokens = usage.completion_tokens || 0
const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens)
const usageData = {
type: "usage" as const,
@@ -69,25 +59,21 @@ export class HuggingFaceHandler implements ApiHandler {
cacheWriteTokens: 0,
cacheReadTokens: 0,
totalCost: totalCost,
};
}
yield usageData;
yield usageData
}
@withRetry()
async *createMessage(
systemPrompt: string,
messages: ClineStorageMessage[],
tools?: OpenAITool[],
): ApiStream {
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
try {
const client = this.ensureClient();
const model = this.getModel();
const client = this.ensureClient()
const model = this.getModel()
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
];
]
const requestParams = {
model: model.id,
@@ -97,71 +83,66 @@ export class HuggingFaceHandler implements ApiHandler {
stream_options: { include_usage: true },
temperature: 0,
...getOpenAIToolParams(tools),
};
}
const toolCallProcessor = new ToolCallProcessor();
const stream = (await client.chat.completions.create(
requestParams,
)) as any;
const toolCallProcessor = new ToolCallProcessor()
const stream = (await client.chat.completions.create(requestParams)) as any
let _chunkCount = 0;
let _totalContent = "";
let _chunkCount = 0
let _totalContent = ""
for await (const chunk of stream) {
_chunkCount++;
const delta = chunk.choices?.[0]?.delta;
_chunkCount++
const delta = chunk.choices?.[0]?.delta
if (delta?.content) {
_totalContent += delta.content;
_totalContent += delta.content
yield {
type: "text",
text: delta.content,
};
}
}
if (delta?.tool_calls) {
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls);
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
}
if (chunk.usage) {
yield* this.yieldUsage(model.info, chunk.usage);
yield* this.yieldUsage(model.info, chunk.usage)
}
}
} catch (error: any) {
throw error;
throw error
}
}
getModel(): { id: string; info: ModelInfo } {
getModel(): { id: HuggingFaceModelId; info: ModelInfo } {
// Return cached model if available
if (this.cachedModel) {
return this.cachedModel;
return this.cachedModel
}
const modelId = this.options.huggingFaceModelId;
let result: { id: string; info: ModelInfo };
const modelId = this.options.huggingFaceModelId
// List all available models for debugging
const _availableModels = Object.keys(huggingFaceModels)
let result: { id: HuggingFaceModelId; info: ModelInfo }
if (modelId && modelId in huggingFaceModels) {
const id = modelId as keyof typeof huggingFaceModels;
const modelInfo = huggingFaceModels[id];
result = { id, info: modelInfo };
} else if (modelId) {
const defaultInfo = huggingFaceModels[huggingFaceDefaultModelId];
result = {
id: modelId,
info: this.options.huggingFaceModelInfo || defaultInfo,
};
const id = modelId as HuggingFaceModelId
const modelInfo = huggingFaceModels[id]
result = { id, info: modelInfo }
} else {
const defaultInfo = huggingFaceModels[huggingFaceDefaultModelId];
const defaultInfo = huggingFaceModels[huggingFaceDefaultModelId]
result = {
id: huggingFaceDefaultModelId,
info: defaultInfo,
};
}
}
// Cache the result for future calls
this.cachedModel = result;
this.cachedModel = result
return result;
return result
}
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,5 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import {
type ClineStorageMessage,
convertClineStorageToAnthropicMessage,
} from "@/shared/messages/content";
import { Anthropic } from "@anthropic-ai/sdk"
import { ClineStorageMessage, convertClineStorageToAnthropicMessage } from "@/shared/messages/content"
/**
* Converts Cline storage messages to Anthropic API format with optional cache control.
@@ -15,7 +12,7 @@ import {
* @returns Array of Anthropic-compatible messages with cache control applied
*/
export function sanitizeAnthropicMessages(
clineMessages: ClineStorageMessage[],
clineMessages: Array<ClineStorageMessage | Anthropic.MessageParam>,
supportCache: boolean,
): Array<Anthropic.MessageParam> {
// The latest message will be the new user message, one before will be the assistant message from a previous request,
@@ -24,37 +21,32 @@ export function sanitizeAnthropicMessages(
// know the last message to retrieve from the cache for the current request.
const userMsgIndices = clineMessages.reduce((acc, msg, index) => {
if (msg.role === "user") {
acc.push(index);
acc.push(index)
}
return acc;
}, [] as number[]);
return acc
}, [] as number[])
// Set to -1 if there are no user messages so the indices are invalid
const indicesLength = userMsgIndices.length ?? -1;
const lastUserMsgIndex = userMsgIndices[indicesLength - 1];
const secondLastMsgUserIndex = userMsgIndices[indicesLength - 2];
const indicesLength = userMsgIndices.length ?? -1
const lastUserMsgIndex = userMsgIndices[indicesLength - 1]
const secondLastMsgUserIndex = userMsgIndices[indicesLength - 2]
return clineMessages.map((msg, index) => {
const anthropicMsg = convertClineStorageToAnthropicMessage(msg);
const anthropicMsg = convertClineStorageToAnthropicMessage(msg)
// Add cache control to the last two user messages
if (
supportCache &&
(index === lastUserMsgIndex || index === secondLastMsgUserIndex)
) {
return addCacheControl(anthropicMsg);
if (supportCache && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
return addCacheControl(anthropicMsg)
}
return anthropicMsg;
});
return anthropicMsg
})
}
const isThinkingBlock = (
block: Anthropic.ContentBlockParam,
): block is
| Anthropic.Messages.ThinkingBlockParam
| Anthropic.Messages.RedactedThinkingBlockParam => {
return block.type === "thinking" || block.type === "redacted_thinking";
};
): block is Anthropic.Messages.ThinkingBlockParam | Anthropic.Messages.RedactedThinkingBlockParam => {
return block.type === "thinking" || block.type === "redacted_thinking"
}
/**
* Adds ephemeral cache control to the last content block of a message.
@@ -63,9 +55,7 @@ const isThinkingBlock = (
* @param message - The Anthropic message to add cache control to
* @returns A new message with cache control added to the last content block
*/
function addCacheControl(
message: Anthropic.MessageParam,
): Anthropic.MessageParam {
function addCacheControl(message: Anthropic.MessageParam): Anthropic.MessageParam {
// Convert string content to array format
if (typeof message.content === "string") {
return {
@@ -77,24 +67,24 @@ function addCacheControl(
cache_control: { type: "ephemeral" },
} satisfies Anthropic.TextBlockParam,
],
};
}
}
// Handle array content - add cache control to the last block
const content = [...message.content];
const lastIndex = content.length - 1;
const content = [...message.content]
const lastIndex = content.length - 1
if (lastIndex >= 0) {
const lastBlock = content[lastIndex];
const lastBlock = content[lastIndex]
// Only add cache_control to block types that support it (not ThinkingBlockParam)
if (!isThinkingBlock(lastBlock)) {
content[lastIndex] = {
...lastBlock,
cache_control: { type: "ephemeral" },
} satisfies Anthropic.ContentBlockParam;
} satisfies Anthropic.ContentBlockParam
}
}
return { ...message, content };
return { ...message, content }
}
@@ -1,6 +1,6 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type { Content, GenerateContentResponse, Part } from "@google/genai";
import type { ClineStorageMessage } from "@/shared/messages/content";
import { Anthropic } from "@anthropic-ai/sdk"
import { Content, GenerateContentResponse, Part } from "@google/genai"
import { ClineStorageMessage } from "@/shared/messages/content"
// Source: https://ai.google.dev/gemini-api/docs/thought-signatures#faqs
// While injecting custom function call blocks into the request is strongly discouraged,
@@ -8,29 +8,27 @@ import type { ClineStorageMessage } from "@/shared/messages/content";
// calls and responses that were executed deterministically by the client, or transferring a
// trace from a different model that does not include thought signatures, you can set the following dummy signatures of either
// "context_engineering_is_the_way_to_go" or "skip_thought_signature_validator" in the thought signature field to skip validation.
const GEMINI_DUMMY_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
const GEMINI_DUMMY_THOUGHT_SIGNATURE = "skip_thought_signature_validator"
export function convertAnthropicContentToGemini(
content: string | ClineStorageMessage["content"],
): Part[] {
export function convertAnthropicContentToGemini(content: string | ClineStorageMessage["content"]): Part[] {
if (typeof content === "string") {
return [{ text: content }];
return [{ text: content }]
}
return content
.flatMap((block): Part | undefined => {
switch (block.type) {
case "text":
return { text: block.text, thoughtSignature: block.signature };
return { text: block.text, thoughtSignature: block.signature }
case "image":
if (block.source.type !== "base64") {
throw new Error("Unsupported image source type");
throw new Error("Unsupported image source type")
}
return {
inlineData: {
data: block.source.data,
mimeType: block.source.media_type,
},
};
}
case "tool_use":
return {
functionCall: {
@@ -39,7 +37,7 @@ export function convertAnthropicContentToGemini(
},
// Thought signature is required, so provide a dummy one if not present
thoughtSignature: block.signature || GEMINI_DUMMY_THOUGHT_SIGNATURE,
};
}
case "tool_result":
return {
functionResponse: {
@@ -48,66 +46,57 @@ export function convertAnthropicContentToGemini(
result: block.content,
},
},
};
}
case "thinking":
return {
text: block.thinking,
thought: true,
thoughtSignature: block.signature || GEMINI_DUMMY_THOUGHT_SIGNATURE,
};
}
default:
return undefined;
return undefined
}
})
.filter((part): part is Part => part !== undefined); // Filter out unsupported blocks
.filter((part): part is Part => part !== undefined) // Filter out unsupported blocks
}
export function convertAnthropicMessageToGemini(
message: ClineStorageMessage,
): Content {
export function convertAnthropicMessageToGemini(message: Anthropic.Messages.MessageParam): Content {
return {
role: message.role === "assistant" ? "model" : "user",
parts: convertAnthropicContentToGemini(message.content),
};
}
}
/*
It looks like gemini likes to double escape certain characters when writing file contents: https://discuss.ai.google.dev/t/function-call-string-property-is-double-escaped/37867
*/
export function unescapeGeminiContent(content: string) {
return content
.replace(/\\n/g, "\n")
.replace(/\\'/g, "'")
.replace(/\\"/g, '"')
.replace(/\\r/g, "\r")
.replace(/\\t/g, "\t");
return content.replace(/\\n/g, "\n").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\r/g, "\r").replace(/\\t/g, "\t")
}
export function convertGeminiResponseToAnthropic(
response: GenerateContentResponse,
): Anthropic.Messages.Message {
const content: Anthropic.Messages.ContentBlock[] = [];
export function convertGeminiResponseToAnthropic(response: GenerateContentResponse): Anthropic.Messages.Message {
const content: Anthropic.Messages.ContentBlock[] = []
const text = response.text;
const text = response.text
if (text) {
content.push({ type: "text", text, citations: null });
content.push({ type: "text", text, citations: null })
}
let stop_reason: Anthropic.Messages.Message["stop_reason"] = null;
const finishReason = response.candidates?.[0]?.finishReason;
let stop_reason: Anthropic.Messages.Message["stop_reason"] = null
const finishReason = response.candidates?.[0]?.finishReason
if (finishReason) {
switch (finishReason) {
case "STOP":
stop_reason = "end_turn";
break;
stop_reason = "end_turn"
break
case "MAX_TOKENS":
stop_reason = "max_tokens";
break;
stop_reason = "max_tokens"
break
case "SAFETY":
case "RECITATION":
case "OTHER":
stop_reason = "stop_sequence";
break;
stop_reason = "stop_sequence"
break
}
}
@@ -124,7 +113,6 @@ export function convertGeminiResponseToAnthropic(
output_tokens: response.usageMetadata?.candidatesTokenCount ?? 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
};
}
}
@@ -1,32 +1,29 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type { AssistantMessage } from "@mistralai/mistralai/models/components/assistantmessage";
import type { SystemMessage } from "@mistralai/mistralai/models/components/systemmessage";
import type { ToolMessage } from "@mistralai/mistralai/models/components/toolmessage";
import type { UserMessage } from "@mistralai/mistralai/models/components/usermessage";
import { getImageDataUrl } from "@/shared/messages/content";
import { Anthropic } from "@anthropic-ai/sdk"
import { AssistantMessage } from "@mistralai/mistralai/models/components/assistantmessage"
import { SystemMessage } from "@mistralai/mistralai/models/components/systemmessage"
import { ToolMessage } from "@mistralai/mistralai/models/components/toolmessage"
import { UserMessage } from "@mistralai/mistralai/models/components/usermessage"
export type MistralMessage =
| (SystemMessage & { role: "system" })
| (UserMessage & { role: "user" })
| (AssistantMessage & { role: "assistant" })
| (ToolMessage & { role: "tool" });
| (ToolMessage & { role: "tool" })
export function convertToMistralMessages(
anthropicMessages: Anthropic.Messages.MessageParam[],
): MistralMessage[] {
const mistralMessages: MistralMessage[] = [];
export function convertToMistralMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): MistralMessage[] {
const mistralMessages: MistralMessage[] = []
for (const anthropicMessage of anthropicMessages) {
if (typeof anthropicMessage.content === "string") {
mistralMessages.push({
role: anthropicMessage.role,
content: anthropicMessage.content,
});
})
} else {
if (anthropicMessage.role === "user") {
// Filter to only include text and image blocks
const textAndImageBlocks = anthropicMessage.content.filter(
(part) => part.type === "text" || part.type === "image",
);
)
if (textAndImageBlocks.length > 0) {
mistralMessages.push({
@@ -36,31 +33,29 @@ export function convertToMistralMessages(
return {
type: "image_url",
imageUrl: {
url: getImageDataUrl(part.source),
url: `data:${part.source.media_type};base64,${part.source.data}`,
},
};
}
}
return { type: "text", text: part.text };
return { type: "text", text: part.text }
}),
});
})
}
} else if (anthropicMessage.role === "assistant") {
// Only process text blocks - assistant cannot send images or other content types in Mistral's API format
const textBlocks = anthropicMessage.content.filter(
(part) => part.type === "text",
);
const textBlocks = anthropicMessage.content.filter((part) => part.type === "text")
if (textBlocks.length > 0) {
const content = textBlocks.map((part) => part.text).join("\n");
const content = textBlocks.map((part) => part.text).join("\n")
mistralMessages.push({
role: "assistant",
content,
});
})
}
}
}
}
return mistralMessages;
return mistralMessages
}
+75 -90
View File
@@ -1,6 +1,6 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type OpenAI from "openai";
import { Logger } from "@/shared/services/Logger";
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { Logger } from "@/shared/services/Logger"
const o1SystemPrompt = (systemPrompt: string) => `
# System Prompt
@@ -164,7 +164,7 @@ I've analyzed the project structure, but I need more information to proceed. Let
<ask_followup_question>
<question>Which specific feature would you like me to implement in the example.py file?</question>
</ask_followup_question>
`;
`
export function convertToO1Messages(
openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[],
@@ -176,26 +176,26 @@ export function convertToO1Messages(
acc.push({
role: "user",
content: message.content || "",
});
})
} else if (message.role === "assistant" && message.tool_calls) {
// Convert tool calls to content and remove tool_calls
let content = message.content || "";
let content = message.content || ""
message.tool_calls.forEach((toolCall) => {
if (toolCall.type === "function") {
content += `\nTool Call: ${toolCall.function.name}\nArguments: ${toolCall.function.arguments}`;
content += `\nTool Call: ${toolCall.function.name}\nArguments: ${toolCall.function.arguments}`
}
});
})
acc.push({
role: "assistant",
content: content,
tool_calls: undefined,
});
})
} else {
// Keep other messages as they are
acc.push(message);
acc.push(message)
}
return acc;
}, [] as OpenAI.Chat.ChatCompletionMessageParam[]);
return acc
}, [] as OpenAI.Chat.ChatCompletionMessageParam[])
// Find the index of the last assistant message
// const lastAssistantIndex = findLastIndex(toolsReplaced, (message) => message.role === "assistant")
@@ -207,7 +207,7 @@ export function convertToO1Messages(
content: o1SystemPrompt(systemPrompt),
} as OpenAI.Chat.ChatCompletionUserMessageParam,
...toolsReplaced,
];
]
// If there's an assistant message, insert the system prompt after it
// if (lastAssistantIndex !== -1) {
@@ -226,12 +226,12 @@ export function convertToO1Messages(
// })
// }
return messagesWithSystemPrompt;
return messagesWithSystemPrompt
}
interface ToolCall {
tool: string;
tool_input: Record<string, string>;
tool: string
tool_input: Record<string, string>
}
const toolNames = [
@@ -243,116 +243,106 @@ const toolNames = [
"write_to_file",
"ask_followup_question",
"attempt_completion",
];
]
function parseAIResponse(response: string): {
normalText: string;
toolCalls: ToolCall[];
normalText: string
toolCalls: ToolCall[]
} {
// Create a regex pattern to match any tool call opening tag
const toolCallPattern = new RegExp(`<(${toolNames.join("|")})`, "i");
const match = response.match(toolCallPattern);
const toolCallPattern = new RegExp(`<(${toolNames.join("|")})`, "i")
const match = response.match(toolCallPattern)
if (!match) {
// No tool calls found
return { normalText: response.trim(), toolCalls: [] };
return { normalText: response.trim(), toolCalls: [] }
}
const toolCallStart = match.index!;
const normalText = response.slice(0, toolCallStart).trim();
const toolCallsText = response.slice(toolCallStart);
const toolCallStart = match.index!
const normalText = response.slice(0, toolCallStart).trim()
const toolCallsText = response.slice(toolCallStart)
const toolCalls = parseToolCalls(toolCallsText);
const toolCalls = parseToolCalls(toolCallsText)
return { normalText, toolCalls };
return { normalText, toolCalls }
}
function parseToolCalls(toolCallsText: string): ToolCall[] {
const toolCalls: ToolCall[] = [];
const toolCalls: ToolCall[] = []
let remainingText = toolCallsText;
let remainingText = toolCallsText
while (remainingText.length > 0) {
const toolMatch = toolNames.find((tool) =>
new RegExp(`<${tool}`, "i").test(remainingText),
);
const toolMatch = toolNames.find((tool) => new RegExp(`<${tool}`, "i").test(remainingText))
if (!toolMatch) {
break; // No more tool calls found
break // No more tool calls found
}
const startTag = `<${toolMatch}`;
const endTag = `</${toolMatch}>`;
const startIndex = remainingText.indexOf(startTag);
const endIndex = remainingText.indexOf(endTag, startIndex);
const startTag = `<${toolMatch}`
const endTag = `</${toolMatch}>`
const startIndex = remainingText.indexOf(startTag)
const endIndex = remainingText.indexOf(endTag, startIndex)
if (endIndex === -1) {
break; // Malformed XML, no closing tag found
break // Malformed XML, no closing tag found
}
const toolCallContent = remainingText.slice(
startIndex,
endIndex + endTag.length,
);
remainingText = remainingText.slice(endIndex + endTag.length).trim();
const toolCallContent = remainingText.slice(startIndex, endIndex + endTag.length)
remainingText = remainingText.slice(endIndex + endTag.length).trim()
const toolCall = parseToolCall(toolMatch, toolCallContent);
const toolCall = parseToolCall(toolMatch, toolCallContent)
if (toolCall) {
toolCalls.push(toolCall);
toolCalls.push(toolCall)
}
}
return toolCalls;
return toolCalls
}
function parseToolCall(toolName: string, content: string): ToolCall | null {
const tool_input: Record<string, string> = {};
const tool_input: Record<string, string> = {}
// Remove the outer tool tags
const innerContent = content
.replace(new RegExp(`^<${toolName}>|</${toolName}>$`, "g"), "")
.trim();
const innerContent = content.replace(new RegExp(`^<${toolName}>|</${toolName}>$`, "g"), "").trim()
// Parse nested XML elements
const paramRegex = /<(\w+)>([\s\S]*?)<\/\1>/gs;
let match: RegExpExecArray | null;
const paramRegex = /<(\w+)>([\s\S]*?)<\/\1>/gs
let match: RegExpExecArray | null
while ((match = paramRegex.exec(innerContent)) !== null) {
const [, paramName, paramValue] = match;
const [, paramName, paramValue] = match
// Preserve newlines and trim only leading/trailing whitespace
tool_input[paramName] = paramValue.replace(/^\s+|\s+$/g, "");
tool_input[paramName] = paramValue.replace(/^\s+|\s+$/g, "")
}
// Validate required parameters
if (!validateToolInput(toolName, tool_input)) {
Logger.error(`Invalid tool call for ${toolName}:`, content);
return null;
Logger.error(`Invalid tool call for ${toolName}:`, content)
return null
}
return { tool: toolName, tool_input };
return { tool: toolName, tool_input }
}
function validateToolInput(
toolName: string,
tool_input: Record<string, string>,
): boolean {
function validateToolInput(toolName: string, tool_input: Record<string, string>): boolean {
switch (toolName) {
case "execute_command":
return "command" in tool_input;
return "command" in tool_input
case "read_file":
case "list_code_definition_names":
case "list_files":
return "path" in tool_input;
return "path" in tool_input
case "search_files":
return "path" in tool_input && "regex" in tool_input;
return "path" in tool_input && "regex" in tool_input
case "write_to_file":
return "path" in tool_input && "content" in tool_input;
return "path" in tool_input && "content" in tool_input
case "ask_followup_question":
return "question" in tool_input;
return "question" in tool_input
case "attempt_completion":
return "result" in tool_input;
return "result" in tool_input
default:
return false;
return false
}
}
@@ -376,10 +366,8 @@ function validateToolInput(
export function convertO1ResponseToAnthropicMessage(
completion: OpenAI.Chat.Completions.ChatCompletion,
): Anthropic.Messages.Message {
const openAiMessage = completion.choices[0].message;
const { normalText, toolCalls } = parseAIResponse(
openAiMessage.content || "",
);
const openAiMessage = completion.choices[0].message
const { normalText, toolCalls } = parseAIResponse(openAiMessage.content || "")
const anthropicMessage: Anthropic.Messages.Message = {
id: completion.id,
@@ -396,14 +384,14 @@ export function convertO1ResponseToAnthropicMessage(
stop_reason: (() => {
switch (completion.choices[0].finish_reason) {
case "stop":
return "end_turn";
return "end_turn"
case "length":
return "max_tokens";
return "max_tokens"
case "tool_calls":
return "tool_use";
return "tool_use"
case "content_filter": // Anthropic doesn't have an exact equivalent
default:
return null;
return null
}
})(),
stop_sequence: null, // which custom stop_sequence was generated, if any (not applicable if you don't use stop_sequence)
@@ -412,26 +400,23 @@ export function convertO1ResponseToAnthropicMessage(
output_tokens: completion.usage?.completion_tokens || 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
};
}
if (toolCalls.length > 0) {
anthropicMessage.content.push(
...toolCalls.map(
(toolCall: ToolCall, index: number): Anthropic.ToolUseBlock => {
return {
type: "tool_use",
id: `call_${index}_${Date.now()}`, // Generate a unique ID for each tool call
name: toolCall.tool,
input: toolCall.tool_input,
};
},
),
);
...toolCalls.map((toolCall: ToolCall, index: number): Anthropic.ToolUseBlock => {
return {
type: "tool_use",
id: `call_${index}_${Date.now()}`, // Generate a unique ID for each tool call
name: toolCall.tool,
input: toolCall.tool_input,
}
}),
)
}
return anthropicMessage;
return anthropicMessage
}
// Example usage:
@@ -1,68 +1,64 @@
import type { Message } from "ollama";
import { Message } from "ollama"
import {
type ClineAssistantToolUseBlock,
type ClineImageContentBlock,
type ClineStorageMessage,
type ClineTextContentBlock,
type ClineUserToolResultContentBlock,
getImageDataUrl,
} from "@/shared/messages/content";
ClineAssistantToolUseBlock,
ClineImageContentBlock,
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
} from "@/shared/messages/content"
export function convertToOllamaMessages(
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
): Message[] {
const ollamaMessages: Message[] = [];
export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[]): Message[] {
const ollamaMessages: Message[] = []
for (const anthropicMessage of anthropicMessages) {
if (typeof anthropicMessage.content === "string") {
ollamaMessages.push({
role: anthropicMessage.role,
content: anthropicMessage.content,
});
})
} else {
if (anthropicMessage.role === "user") {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[];
toolMessages: ClineUserToolResultContentBlock[];
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
}
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineUserToolResultContentBlock[]
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
}
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process tool result messages FIRST since they must follow the tool use messages
const toolResultImages: string[] = [];
const toolResultImages: string[] = []
toolMessages.forEach((toolMessage) => {
// The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the Ollama SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility.
let content: string;
let content: string
if (typeof toolMessage.content === "string") {
content = toolMessage.content;
content = toolMessage.content
} else {
content =
toolMessage.content
?.map((part) => {
if (part.type === "image") {
toolResultImages.push(getImageDataUrl(part.source));
return "(see following user message for image)";
toolResultImages.push(`data:${part.source.media_type};base64,${part.source.data}`)
return "(see following user message for image)"
}
return part.text;
return part.text
})
.join("\n") ?? "";
.join("\n") ?? ""
}
ollamaMessages.push({
role: "user",
images: toolResultImages.length > 0 ? toolResultImages : undefined,
content: content,
});
});
})
})
// Process non-tool messages
if (nonToolMessages.length > 0) {
@@ -71,50 +67,49 @@ export function convertToOllamaMessages(
content: nonToolMessages
.map((part) => {
if (part.type === "image") {
return getImageDataUrl(part.source);
return `data:${part.source.media_type};base64,${part.source.data}`
}
return part.text;
return part.text
})
.join("\n"),
});
})
}
} else if (anthropicMessage.role === "assistant") {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[];
toolMessages: ClineAssistantToolUseBlock[];
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
} // assistant cannot send tool_result messages
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineAssistantToolUseBlock[]
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
} // assistant cannot send tool_result messages
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process non-tool messages
let content: string = "";
let content: string = ""
if (nonToolMessages.length > 0) {
content = nonToolMessages
.map((part) => {
if (part.type === "image") {
return ""; // impossible as the assistant cannot send images
return "" // impossible as the assistant cannot send images
}
return part.text;
return part.text
})
.join("\n");
.join("\n")
}
ollamaMessages.push({
role: "assistant",
content,
});
})
}
}
}
return ollamaMessages;
return ollamaMessages
}
@@ -1,19 +1,19 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type OpenAI from "openai";
import type { ApiProvider } from "@/shared/api";
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { ApiProvider } from "@/shared/api"
import {
type ClineAssistantRedactedThinkingBlock,
type ClineAssistantThinkingBlock,
type ClineAssistantToolUseBlock,
type ClineImageContentBlock,
type ClineTextContentBlock,
type ClineUserToolResultContentBlock,
getImageDataUrl,
} from "@/shared/messages/content";
import { Logger } from "@/shared/services/Logger";
ClineAssistantRedactedThinkingBlock,
ClineAssistantThinkingBlock,
ClineAssistantToolUseBlock,
ClineImageContentBlock,
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
} from "@/shared/messages/content"
import { Logger } from "@/shared/services/Logger"
// OpenAI API has a maximum tool call ID length of 40 characters
const MAX_TOOL_CALL_ID_LENGTH = 40;
const MAX_TOOL_CALL_ID_LENGTH = 40
/**
* Determines if a given tool ID follows the OpenAI Responses API format for tool calls.
@@ -23,7 +23,7 @@ const MAX_TOOL_CALL_ID_LENGTH = 40;
* @returns True if the tool ID matches the OpenAI Responses API format, false otherwise
*/
function isOpenAIResponseToolId(callId: string): boolean {
return callId.startsWith("fc_") && callId.length === 53;
return callId.startsWith("fc_") && callId.length === 53
}
/**
@@ -37,24 +37,21 @@ function isOpenAIResponseToolId(callId: string): boolean {
* @param provider - The API provider that the OpenAI formatted messages will be sent to
* @returns The transformed ID suitable for OpenAI API
*/
function transformToolCallIdForNativeApi(
toolId: string,
provider?: ApiProvider,
): string {
function transformToolCallIdForNativeApi(toolId: string, provider?: ApiProvider): string {
// OpenAI Responses API uses "fc_" prefix with 53 char length
// Convert these to "call_" prefix format for Chat Completions API
if (isOpenAIResponseToolId(toolId)) {
// Use the last 33 chars + "call_" (5 chars) to stay under the 40-char limit.
return `call_${toolId.slice(toolId.length - (MAX_TOOL_CALL_ID_LENGTH - 5))}`;
return `call_${toolId.slice(toolId.length - (MAX_TOOL_CALL_ID_LENGTH - 5))}`
}
if (provider !== "openai-native") {
return toolId;
return toolId
}
// Ensure ID doesn't exceed max length
if (toolId.length > MAX_TOOL_CALL_ID_LENGTH) {
return toolId.slice(0, MAX_TOOL_CALL_ID_LENGTH);
return toolId.slice(0, MAX_TOOL_CALL_ID_LENGTH)
}
return toolId;
return toolId
}
/**
@@ -68,17 +65,17 @@ function transformToolCallIdForNativeApi(
* @returns Array of OpenAI.Chat.ChatCompletionMessageParam objects
*/
export function convertToOpenAiMessages(
anthropicMessages: Anthropic.Messages.MessageParam[],
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
provider?: ApiProvider,
): OpenAI.Chat.ChatCompletionMessageParam[] {
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [];
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = []
for (const anthropicMessage of anthropicMessages) {
if (typeof anthropicMessage.content === "string") {
openAiMessages.push({
role: anthropicMessage.role,
content: anthropicMessage.content,
});
})
} else {
// image_url.url is base64 encoded image data
// ensure it contains the content-type of the image: data:image/png;base64,
@@ -89,56 +86,52 @@ export function convertToOpenAiMessages(
{ role: "tool", tool_call_id: "", content: ""}
*/
if (anthropicMessage.role === "user") {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[];
toolMessages: ClineUserToolResultContentBlock[];
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
} // user cannot send tool_use messages
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineUserToolResultContentBlock[]
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
} // user cannot send tool_use messages
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process tool result messages FIRST since they must follow the tool use messages
const toolResultImages: ClineImageContentBlock[] = [];
const toolResultImages: ClineImageContentBlock[] = []
toolMessages.forEach((toolMessage) => {
// The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the OpenAI SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility.
let content: string;
let content: string
if (typeof toolMessage.content === "string") {
content = toolMessage.content;
content = toolMessage.content
} else if (Array.isArray(toolMessage.content)) {
content =
toolMessage.content
?.map((part) => {
if (part.type === "image") {
toolResultImages.push(part);
return "(see following user message for image)";
toolResultImages.push(part)
return "(see following user message for image)"
}
return part.text;
return part.text
})
.join("\n") ?? "";
.join("\n") ?? ""
} else {
// Handle undefined content
content = "";
content = ""
}
openAiMessages.push({
role: "tool",
// The tool_call_id must match the id used in the assistant's tool_calls array.
// Use the same transformation logic as tool_calls to ensure IDs match.
tool_call_id: transformToolCallIdForNativeApi(
toolMessage.tool_use_id,
provider,
),
tool_call_id: transformToolCallIdForNativeApi(toolMessage.tool_use_id, provider),
content: content,
});
});
})
})
// If tool results contain images, send as a separate user message
// I ran into an issue where if I gave feedback for one of many tool uses, the request would fail.
@@ -151,9 +144,9 @@ export function convertToOpenAiMessages(
role: "user",
content: toolResultImages.map((part) => ({
type: "image_url",
image_url: { url: getImageDataUrl(part.source) },
image_url: { url: `data:${part.source.media_type};base64,${part.source.data}` },
})),
});
})
}
// Process non-tool messages
@@ -165,117 +158,106 @@ export function convertToOpenAiMessages(
return {
type: "image_url",
image_url: {
url: getImageDataUrl(part.source),
url: `data:${part.source.media_type};base64,${part.source.data}`,
},
};
}
}
return { type: "text", text: part.text };
return { type: "text", text: part.text }
}),
});
})
}
} else if (anthropicMessage.role === "assistant") {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (
| ClineTextContentBlock
| ClineImageContentBlock
| ClineAssistantThinkingBlock
| ClineAssistantRedactedThinkingBlock
)[];
toolMessages: ClineAssistantToolUseBlock[];
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
} // assistant cannot send tool_result messages
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (
| ClineTextContentBlock
| ClineImageContentBlock
| ClineAssistantThinkingBlock
| ClineAssistantRedactedThinkingBlock
)[]
toolMessages: ClineAssistantToolUseBlock[]
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
} // assistant cannot send tool_result messages
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process non-tool messages
let content: string | undefined;
const reasoningDetails: any[] = [];
const thinkingBlock = [];
let content: string | undefined
const reasoningDetails: any[] = []
const thinkingBlock = []
if (nonToolMessages.length > 0) {
nonToolMessages.forEach((part) => {
const anyPart = part as any;
const anyPart = part as any
if (part.type === "text" && anyPart.reasoning_details) {
if (Array.isArray(anyPart.reasoning_details)) {
reasoningDetails.push(...anyPart.reasoning_details);
reasoningDetails.push(...anyPart.reasoning_details)
} else {
reasoningDetails.push(anyPart.reasoning_details);
reasoningDetails.push(anyPart.reasoning_details)
}
}
if (part.type === "thinking" && part.thinking) {
// Reasoning details should have been moved to the text block
thinkingBlock.push(part);
thinkingBlock.push(part)
}
});
})
content = nonToolMessages
.map((part) => {
if (part.type === "text" && part.text) {
return part.text;
return part.text
}
return "";
return ""
})
.join("\n");
.join("\n")
}
// Process tool use messages
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] =
toolMessages.map((toolMessage) => {
const toolDetails = toolMessage.reasoning_details;
const toolId = toolMessage.id;
if (toolDetails) {
if (Array.isArray(toolDetails)) {
// For Gemini: reasoning details must be linkable back to the tool call.
// Sometimes OpenRouter/Gemini returns entries without `id`; those poison the next request.
// Keep only entries with an id matching the tool call id.
// See: https://github.com/cline/cline/issues/8214
const validDetails = toolDetails.filter(
(detail: any) => detail?.id === toolId,
);
if (validDetails.length > 0) {
reasoningDetails.push(...validDetails);
}
} else {
// Single reasoning detail - only include if it has matching id
const detail = toolDetails as any;
if (detail?.id === toolId) {
reasoningDetails.push(toolDetails);
}
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => {
const toolDetails = toolMessage.reasoning_details
const toolId = toolMessage.id
if (toolDetails) {
if (Array.isArray(toolDetails)) {
// For Gemini: reasoning details must be linkable back to the tool call.
// Sometimes OpenRouter/Gemini returns entries without `id`; those poison the next request.
// Keep only entries with an id matching the tool call id.
// See: https://github.com/cline/cline/issues/8214
const validDetails = toolDetails.filter((detail: any) => detail?.id === toolId)
if (validDetails.length > 0) {
reasoningDetails.push(...validDetails)
}
} else {
// Single reasoning detail - only include if it has matching id
const detail = toolDetails as any
if (detail?.id === toolId) {
reasoningDetails.push(toolDetails)
}
}
}
return {
// Use the same transformation as tool_call_id to ensure IDs match
id: transformToolCallIdForNativeApi(toolId, provider),
type: "function",
function: {
name: toolMessage.name,
// json string
arguments: JSON.stringify(toolMessage.input),
},
};
});
return {
// Use the same transformation as tool_call_id to ensure IDs match
id: transformToolCallIdForNativeApi(toolId, provider),
type: "function",
function: {
name: toolMessage.name,
// json string
arguments: JSON.stringify(toolMessage.input),
},
}
})
// Set content to blank when tool_calls are present but content has no text, per OpenAI API spec
const hasToolCalls = tool_calls.length > 0;
const hasMeaningfulContent =
content !== undefined && content.trim() !== "";
const finalContent = hasMeaningfulContent
? content
: hasToolCalls
? null
: undefined;
const hasToolCalls = tool_calls.length > 0
const hasMeaningfulContent = content !== undefined && content.trim() !== ""
const finalContent = hasMeaningfulContent ? content : hasToolCalls ? null : undefined
const consolidatedReasoningDetails =
reasoningDetails.length > 0
? consolidateReasoningDetails(reasoningDetails as any)
: [];
reasoningDetails.length > 0 ? consolidateReasoningDetails(reasoningDetails as any) : []
openAiMessages.push({
role: "assistant",
@@ -284,91 +266,86 @@ export function convertToOpenAiMessages(
tool_calls: tool_calls?.length > 0 ? tool_calls : undefined,
// Only include reasoning_details when non-empty; sending [] can trigger provider validation issues.
// @ts-expect-error
reasoning_details:
consolidatedReasoningDetails.length > 0
? consolidatedReasoningDetails
: undefined,
});
reasoning_details: consolidatedReasoningDetails.length > 0 ? consolidatedReasoningDetails : undefined,
})
}
}
}
return openAiMessages;
return openAiMessages
}
// Type for OpenRouter's reasoning detail elements
// https://openrouter.ai/docs/use-cases/reasoning-tokens#streaming-response
type ReasoningDetail = {
// https://openrouter.ai/docs/use-cases/reasoning-tokens#reasoning-detail-types
type: string; // "reasoning.summary" | "reasoning.encrypted" | "reasoning.text"
text?: string;
data?: string; // Encrypted reasoning data
signature?: string | null;
id?: string | null; // Unique identifier for the reasoning detail
type: string // "reasoning.summary" | "reasoning.encrypted" | "reasoning.text"
text?: string
data?: string // Encrypted reasoning data
signature?: string | null
id?: string | null // Unique identifier for the reasoning detail
/*
The format of the reasoning detail, with possible values:
"unknown" - Format is not specified
"openai-responses-v1" - OpenAI responses format version 1
"anthropic-claude-v1" - Anthropic Claude format version 1 (default)
*/
format: string; //"unknown" | "openai-responses-v1" | "anthropic-claude-v1" | "xai-responses-v1"
index?: number; // Sequential index of the reasoning detail
};
format: string //"unknown" | "openai-responses-v1" | "anthropic-claude-v1" | "xai-responses-v1"
index?: number // Sequential index of the reasoning detail
}
// Helper function to convert reasoning_details array to the format OpenRouter API expects
// Takes an array of reasoning detail objects and consolidates them by index
function consolidateReasoningDetails(
reasoningDetails: ReasoningDetail[],
): ReasoningDetail[] {
function consolidateReasoningDetails(reasoningDetails: ReasoningDetail[]): ReasoningDetail[] {
if (!reasoningDetails || reasoningDetails.length === 0) {
return [];
return []
}
// Group by index
const groupedByIndex = new Map<number, ReasoningDetail[]>();
const groupedByIndex = new Map<number, ReasoningDetail[]>()
for (const detail of reasoningDetails) {
// Drop corrupted encrypted reasoning blocks that would otherwise trigger:
// "Invalid input: expected string, received undefined" for reasoning_details.*.data
// See: https://github.com/cline/cline/issues/8214
if (detail.type === "reasoning.encrypted" && !detail.data) continue;
if (detail.type === "reasoning.encrypted" && !detail.data) continue
const index = detail.index ?? 0;
const index = detail.index ?? 0
if (!groupedByIndex.has(index)) {
groupedByIndex.set(index, []);
groupedByIndex.set(index, [])
}
groupedByIndex.get(index)!.push(detail);
groupedByIndex.get(index)!.push(detail)
}
// Consolidate each group
const consolidated: ReasoningDetail[] = [];
const consolidated: ReasoningDetail[] = []
for (const [index, details] of groupedByIndex.entries()) {
// Concatenate all text parts
let concatenatedText = "";
let signature: string | undefined;
let id: string | undefined;
let format = "unknown";
let type = "reasoning.text";
let concatenatedText = ""
let signature: string | undefined
let id: string | undefined
let format = "unknown"
let type = "reasoning.text"
for (const detail of details) {
if (detail.text) {
concatenatedText += detail.text;
concatenatedText += detail.text
}
// Keep the signature from the last item that has one
if (detail.signature) {
signature = detail.signature;
signature = detail.signature
}
// Keep the id from the last item that has one
if (detail.id) {
id = detail.id;
id = detail.id
}
// Keep format and type from any item (they should all be the same)
if (detail.format) {
format = detail.format;
format = detail.format
}
if (detail.type) {
type = detail.type;
type = detail.type
}
}
@@ -381,12 +358,12 @@ function consolidateReasoningDetails(
id: id,
format: format,
index: index,
};
consolidated.push(consolidatedEntry);
}
consolidated.push(consolidatedEntry)
}
// For encrypted chunks (data), only keep the last one
let lastDataEntry: ReasoningDetail | undefined;
let lastDataEntry: ReasoningDetail | undefined
for (const detail of details) {
if (detail.data) {
lastDataEntry = {
@@ -396,25 +373,23 @@ function consolidateReasoningDetails(
id: detail.id,
format: detail.format,
index: index,
};
}
}
}
if (lastDataEntry) {
consolidated.push(lastDataEntry);
consolidated.push(lastDataEntry)
}
}
return consolidated;
return consolidated
}
// Unique name to use to filter out tool call that cannot be parsed correctly
const UNIQUE_ERROR_TOOL_NAME = "_cline_error_unknown_function_";
const UNIQUE_ERROR_TOOL_NAME = "_cline_error_unknown_function_"
// Convert OpenAI response to Anthropic format
export function convertToAnthropicMessage(
completion: OpenAI.Chat.Completions.ChatCompletion,
): Anthropic.Messages.Message {
const openAiMessage = completion.choices[0].message;
export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.ChatCompletion): Anthropic.Messages.Message {
const openAiMessage = completion.choices[0].message
const anthropicMessage: Anthropic.Messages.Message = {
id: completion.id,
type: "message",
@@ -430,14 +405,14 @@ export function convertToAnthropicMessage(
stop_reason: (() => {
switch (completion.choices[0].finish_reason) {
case "stop":
return "end_turn";
return "end_turn"
case "length":
return "max_tokens";
return "max_tokens"
case "tool_calls":
return "tool_use";
return "tool_use"
case "content_filter": // Anthropic doesn't have an exact equivalent
default:
return null;
return null
}
})(),
stop_sequence: null, // which custom stop_sequence was generated, if any (not applicable if you don't use stop_sequence)
@@ -446,40 +421,37 @@ export function convertToAnthropicMessage(
output_tokens: completion.usage?.completion_tokens || 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
};
}
try {
if (openAiMessage?.tool_calls?.length) {
const functionCalls = openAiMessage.tool_calls.filter(
(tc: any) => tc?.type === "function" && tc.function,
);
const functionCalls = openAiMessage.tool_calls.filter((tc: any) => tc?.type === "function" && tc.function)
if (functionCalls.length > 0) {
anthropicMessage.content.push(
...functionCalls.map((toolCall: any): Anthropic.ToolUseBlock => {
let parsedInput = {};
let parsedInput = {}
try {
parsedInput = JSON.parse(toolCall.function?.arguments || "{}");
parsedInput = JSON.parse(toolCall.function?.arguments || "{}")
} catch (error) {
Logger.error("Failed to parse tool arguments:", error);
Logger.error("Failed to parse tool arguments:", error)
}
return {
type: "tool_use",
id: toolCall.id,
name: toolCall.function?.name || UNIQUE_ERROR_TOOL_NAME,
input: parsedInput,
};
}
}),
);
)
}
return anthropicMessage;
return anthropicMessage
}
} catch (error) {
Logger.error("Error converting OpenAI message to Anthropic format:", error);
Logger.error("Error converting OpenAI message to Anthropic format:", error)
}
return anthropicMessage;
return anthropicMessage
}
/**
@@ -498,47 +470,43 @@ export function sanitizeGeminiMessages(
modelId: string,
): OpenAI.Chat.ChatCompletionMessageParam[] {
if (!modelId.includes("gemini")) {
return messages;
return messages
}
const droppedToolCallIds = new Set<string>();
const sanitized: OpenAI.Chat.ChatCompletionMessageParam[] = [];
const droppedToolCallIds = new Set<string>()
const sanitized: OpenAI.Chat.ChatCompletionMessageParam[] = []
for (const msg of messages) {
if (msg.role === "assistant") {
const anyMsg = msg as any;
const toolCalls = anyMsg.tool_calls;
const anyMsg = msg as any
const toolCalls = anyMsg.tool_calls
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
const reasoningDetails = anyMsg.reasoning_details;
const hasReasoningDetails =
Array.isArray(reasoningDetails) && reasoningDetails.length > 0;
const reasoningDetails = anyMsg.reasoning_details
const hasReasoningDetails = Array.isArray(reasoningDetails) && reasoningDetails.length > 0
if (!hasReasoningDetails) {
for (const tc of toolCalls) {
if (tc?.id) {
droppedToolCallIds.add(tc.id);
droppedToolCallIds.add(tc.id)
}
}
// Keep any textual content, but drop the tool_calls themselves.
if (anyMsg.content) {
sanitized.push({
role: "assistant",
content: anyMsg.content,
} as any);
sanitized.push({ role: "assistant", content: anyMsg.content } as any)
}
continue;
continue
}
}
}
if (msg.role === "tool") {
const anyMsg = msg as any;
const anyMsg = msg as any
if (anyMsg.tool_call_id && droppedToolCallIds.has(anyMsg.tool_call_id)) {
continue;
continue
}
}
sanitized.push(msg);
sanitized.push(msg)
}
return sanitized;
return sanitized
}
@@ -1,13 +1,5 @@
import type {
ResponseInput,
ResponseInputMessageContentList,
ResponseReasoningItem,
} from "openai/resources/responses/responses";
import {
type ClineStorageMessage,
getBase64ImageSource,
getImageDataUrl,
} from "@/shared/messages/content";
import { ResponseInput, ResponseInputMessageContentList, ResponseReasoningItem } from "openai/resources/responses/responses"
import { ClineStorageMessage } from "@/shared/messages/content"
/**
* Converts an array of ClineStorageMessage objects (extension of Anthropic format) to a ResponseInput array to use with OpenAI's Responses API.
@@ -83,69 +75,56 @@ export function convertToOpenAIResponsesInput(
_messages: ClineStorageMessage[],
options?: { usePreviousResponseId?: boolean },
): {
input: ResponseInput;
previousResponseId?: string;
input: ResponseInput
previousResponseId?: string
} {
// Chain from the latest stored Responses API assistant message when available.
// When chaining, only send new items after that assistant turn.
let previousResponseId: string | undefined;
let messages = _messages;
let previousResponseId: string | undefined
let messages = _messages
if (options?.usePreviousResponseId) {
for (let i = _messages.length - 1; i >= 0; i--) {
const msg = _messages[i];
const msg = _messages[i]
// Must be less than 24 hours old to be considered for chaining as the previous Id is only valid for 24 hours.
// Set to 23 hours to account for any potential delays in processing.
const isLessThan23HoursOld = msg.ts
? Date.now() - msg.ts < 23 * 60 * 60 * 1000
: false;
const isLessThan23HoursOld = msg.ts ? Date.now() - msg.ts < 23 * 60 * 60 * 1000 : false
if (msg.role === "assistant" && msg.id && isLessThan23HoursOld) {
previousResponseId = msg.id;
messages = _messages.slice(i + 1);
break;
previousResponseId = msg.id
messages = _messages.slice(i + 1)
break
}
}
}
const allItems: any[] = [];
const toolUseIdToCallId = new Map<string, string>();
const allItems: any[] = []
const toolUseIdToCallId = new Map<string, string>()
for (const m of messages) {
if (typeof m.content === "string") {
allItems.push({
role: m.role,
content: [{ type: "input_text", text: m.content }],
});
continue;
allItems.push({ role: m.role, content: [{ type: "input_text", text: m.content }] })
continue
}
if (m.role === "assistant") {
// For assistant messages, we must ensure reasoning items are IMMEDIATELY followed
// by their corresponding message or function_call. Process the entire assistant
// turn and ensure proper pairing.
const assistantItems: any[] = [];
const assistantItems: any[] = []
for (const part of m.content) {
switch (part.type) {
case "thinking": {
case "thinking":
// Only include reasoning item if it has actual content (thinking text or summary)
// Empty reasoning items cause API errors: "Item 'rs_...' of type 'reasoning' was provided without its required following item"
const hasThinkingContent =
part.thinking && part.thinking.trim().length > 0;
const hasSummaryContent =
part.summary &&
Array.isArray(part.summary) &&
part.summary.length > 0;
const hasThinkingContent = part.thinking && part.thinking.trim().length > 0
const hasSummaryContent = part.summary && Array.isArray(part.summary) && part.summary.length > 0
if (
part.call_id &&
part.call_id.length > 0 &&
(hasThinkingContent || hasSummaryContent)
) {
if (part.call_id && part.call_id.length > 0 && (hasThinkingContent || hasSummaryContent)) {
// Use summary if available, otherwise use thinking text
let summary: any[] = [];
let summary: any[] = []
if (hasSummaryContent) {
// part.summary is already in the correct format from OpenAI Responses API
summary = part.summary as any[];
summary = part.summary as any[]
} else if (hasThinkingContent) {
// Convert thinking text to summary format
summary = [
@@ -153,17 +132,16 @@ export function convertToOpenAIResponsesInput(
type: "summary_text",
text: part.thinking,
},
];
]
}
assistantItems.push({
id: part.call_id,
type: "reasoning",
summary,
} as ResponseReasoningItem);
} as ResponseReasoningItem)
}
break;
}
break
case "redacted_thinking":
// Include reasoning item with encrypted content if it has a call_id
// Even if data is missing, we need to maintain the reasoning-function_call pairing
@@ -172,115 +150,100 @@ export function convertToOpenAIResponsesInput(
id: part.call_id,
type: "reasoning",
summary: [],
};
}
// Only include encrypted_content if data exists
if (part.data) {
reasoningItem.encrypted_content = part.data;
reasoningItem.encrypted_content = part.data
}
assistantItems.push(reasoningItem as ResponseReasoningItem);
assistantItems.push(reasoningItem as ResponseReasoningItem)
}
break;
case "text": {
break
case "text":
// Message ID goes at the message level, not in the content
// The reasoning item and message can have different IDs - they just need to be adjacent
const messageItem: any = {
type: "message",
role: "assistant",
content: [{ type: "output_text", text: part.text }],
};
}
// Set message-level id if available
if (part.call_id) {
messageItem.id = part.call_id;
messageItem.id = part.call_id
}
assistantItems.push(messageItem);
break;
}
case "image": {
assistantItems.push(messageItem)
break
case "image":
// Message ID goes at the message level, not in the content
const imageItem: any = {
type: "message",
role: "assistant",
content: [
{
type: "output_text",
text: `[image:${getBase64ImageSource(part.source).mediaType}]`,
},
],
};
content: [{ type: "output_text", text: `[image:${part.source.media_type}]` }],
}
// Set message-level id if available (though images typically don't have call_id)
if (part.call_id) {
imageItem.id = part.call_id;
imageItem.id = part.call_id
}
assistantItems.push(imageItem);
break;
}
assistantItems.push(imageItem)
break
case "tool_use": {
// Function calls use call_id, not related to reasoning item ID
const call_id = part.call_id || part.id;
const call_id = part.call_id || part.id
if (part.call_id) {
toolUseIdToCallId.set(part.id, part.call_id);
toolUseIdToCallId.set(part.id, part.call_id)
}
assistantItems.push({
type: "function_call",
call_id,
// MAX 53 characters for OpenAI Responses API tool IDs
id: !part.id.startsWith("fc_")
? `fc_${part.id.slice(0, 50)}`
: part.id,
id: !part.id.startsWith("fc_") ? `fc_${part.id.slice(0, 50)}` : part.id,
name: part.name,
arguments: JSON.stringify(part.input ?? {}),
});
break;
})
break
}
}
}
allItems.push(...assistantItems);
allItems.push(...assistantItems)
} else {
// User messages - collect all content
const messageContent: ResponseInputMessageContentList = [];
const messageContent: ResponseInputMessageContentList = []
for (const part of m.content) {
switch (part.type) {
case "text":
messageContent.push({ type: "input_text", text: part.text });
break;
messageContent.push({ type: "input_text", text: part.text })
break
case "image":
messageContent.push({
type: "input_image",
detail: "auto",
image_url: getImageDataUrl(part.source),
});
break;
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
})
break
case "tool_result": {
// Flush any pending message content before adding tool result
if (messageContent.length > 0) {
allItems.push({ role: m.role, content: [...messageContent] });
messageContent.length = 0;
allItems.push({ role: m.role, content: [...messageContent] })
messageContent.length = 0
}
const call_id =
part.call_id ||
toolUseIdToCallId.get(part.tool_use_id) ||
part.tool_use_id;
const call_id = part.call_id || toolUseIdToCallId.get(part.tool_use_id) || part.tool_use_id
allItems.push({
type: "function_call_output",
call_id,
output:
typeof part.content === "string"
? part.content
: JSON.stringify(part.content),
});
break;
output: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
})
break
}
}
}
// Flush any remaining user message content
if (messageContent.length > 0) {
allItems.push({ role: m.role, content: [...messageContent] });
allItems.push({ role: m.role, content: [...messageContent] })
}
}
}
return { input: allItems, previousResponseId };
return { input: allItems, previousResponseId }
}
@@ -3,7 +3,6 @@ import {
CLAUDE_SONNET_1M_SUFFIX,
ModelInfo,
OPENROUTER_PROVIDER_PREFERENCES,
openRouterClaudeFable51mModelId,
openRouterClaudeOpus461mModelId,
openRouterClaudeOpus471mModelId,
openRouterClaudeOpus481mModelId,
@@ -64,8 +63,7 @@ export async function createOpenRouterStream(
model.id === openRouterClaudeSonnet461mModelId ||
model.id === openRouterClaudeOpus461mModelId ||
model.id === openRouterClaudeOpus471mModelId ||
model.id === openRouterClaudeOpus481mModelId ||
model.id === openRouterClaudeFable51mModelId
model.id === openRouterClaudeOpus481mModelId
if (isClaude1m) {
// remove the custom :1m suffix, to create the model id openrouter API expects
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
+88 -114
View File
@@ -1,17 +1,13 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type OpenAI from "openai";
import {
type ClineAssistantThinkingBlock,
type ClineStorageMessage,
getImageDataUrl,
} from "@/shared/messages/content";
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { ClineAssistantThinkingBlock, ClineStorageMessage } from "@/shared/messages/content"
/**
* DeepSeek Reasoner message format with reasoning_content support.
*/
export type DeepSeekReasonerMessage = OpenAI.Chat.ChatCompletionMessageParam & {
reasoning_content?: string;
};
reasoning_content?: string
}
/**
* Adds reasoning_content to OpenAI messages for DeepSeek Reasoner.
@@ -25,45 +21,43 @@ export function addReasoningContent(
// Find last user message index (start of current turn)
// If no user message exists (lastUserIndex = -1), all messages are in the "current turn",
// so reasoning_content will be added to all assistant messages. This is intentional.
let lastUserIndex = -1;
let lastUserIndex = -1
for (let i = openAiMessages.length - 1; i >= 0; i--) {
if (openAiMessages[i].role === "user") {
lastUserIndex = i;
break;
lastUserIndex = i
break
}
}
// Extract thinking content from original messages, keyed by assistant index
const thinkingByIndex = new Map<number, string>();
let assistantIdx = 0;
const thinkingByIndex = new Map<number, string>()
let assistantIdx = 0
for (const msg of originalMessages) {
if (msg.role === "assistant") {
if (Array.isArray(msg.content)) {
const thinking = msg.content
.filter(
(p): p is ClineAssistantThinkingBlock => p.type === "thinking",
)
.filter((p): p is ClineAssistantThinkingBlock => p.type === "thinking")
.map((p) => p.thinking)
.join("\n");
.join("\n")
if (thinking) {
thinkingByIndex.set(assistantIdx, thinking);
thinkingByIndex.set(assistantIdx, thinking)
}
}
assistantIdx++;
assistantIdx++
}
}
// Add reasoning_content only to assistant messages in current turn
let aiIdx = 0;
let aiIdx = 0
return openAiMessages.map((msg, i): DeepSeekReasonerMessage => {
if (msg.role === "assistant") {
const thinking = thinkingByIndex.get(aiIdx++);
const thinking = thinkingByIndex.get(aiIdx++)
if (thinking && i >= lastUserIndex) {
return { ...msg, reasoning_content: thinking };
return { ...msg, reasoning_content: thinking }
}
}
return msg;
});
return msg
})
}
/**
@@ -74,104 +68,84 @@ export function addReasoningContent(
* @param messages Array of Anthropic messages
* @returns Array of OpenAI messages where consecutive messages with the same role are merged together
*/
export function convertToR1Format(
messages: Anthropic.Messages.MessageParam[],
): OpenAI.Chat.ChatCompletionMessageParam[] {
return messages.reduce<OpenAI.Chat.ChatCompletionMessageParam[]>(
(merged, message) => {
const lastMessage = merged[merged.length - 1];
let messageContent:
| string
| (
| OpenAI.Chat.ChatCompletionContentPartText
| OpenAI.Chat.ChatCompletionContentPartImage
)[] = "";
let hasImages = false;
export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]): OpenAI.Chat.ChatCompletionMessageParam[] {
return messages.reduce<OpenAI.Chat.ChatCompletionMessageParam[]>((merged, message) => {
const lastMessage = merged[merged.length - 1]
let messageContent: string | (OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage)[] =
""
let hasImages = false
if (Array.isArray(message.content)) {
const textParts: string[] = [];
const imageParts: OpenAI.Chat.ChatCompletionContentPartImage[] = [];
if (Array.isArray(message.content)) {
const textParts: string[] = []
const imageParts: OpenAI.Chat.ChatCompletionContentPartImage[] = []
message.content.forEach((part) => {
if (part.type === "text") {
textParts.push(part.text);
}
if (part.type === "image") {
hasImages = true;
imageParts.push({
type: "image_url",
image_url: { url: getImageDataUrl(part.source) },
});
}
});
if (hasImages) {
const parts: (
| OpenAI.Chat.ChatCompletionContentPartText
| OpenAI.Chat.ChatCompletionContentPartImage
)[] = [];
if (textParts.length > 0) {
parts.push({ type: "text", text: textParts.join("\n") });
}
parts.push(...imageParts);
messageContent = parts;
} else {
messageContent = textParts.join("\n");
message.content.forEach((part) => {
if (part.type === "text") {
textParts.push(part.text)
}
if (part.type === "image") {
hasImages = true
imageParts.push({
type: "image_url",
image_url: { url: `data:${part.source.media_type};base64,${part.source.data}` },
})
}
})
if (hasImages) {
const parts: (OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage)[] = []
if (textParts.length > 0) {
parts.push({ type: "text", text: textParts.join("\n") })
}
parts.push(...imageParts)
messageContent = parts
} else {
messageContent = message.content;
messageContent = textParts.join("\n")
}
} else {
messageContent = message.content
}
// If the last message has the same role, merge the content
if (lastMessage?.role === message.role) {
if (
typeof lastMessage.content === "string" &&
typeof messageContent === "string"
) {
lastMessage.content += `\n${messageContent}`;
} else {
const lastContent = Array.isArray(lastMessage.content)
? lastMessage.content
: [{ type: "text" as const, text: lastMessage.content || "" }];
const newContent = Array.isArray(messageContent)
? messageContent
: [{ type: "text" as const, text: messageContent }];
if (message.role === "assistant") {
const mergedContent = [
...lastContent,
...newContent,
] as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"];
lastMessage.content = mergedContent;
} else {
const mergedContent = [
...lastContent,
...newContent,
] as OpenAI.Chat.ChatCompletionUserMessageParam["content"];
lastMessage.content = mergedContent;
}
}
// If the last message has the same role, merge the content
if (lastMessage?.role === message.role) {
if (typeof lastMessage.content === "string" && typeof messageContent === "string") {
lastMessage.content += `\n${messageContent}`
} else {
// Adds new message with the correct type based on role
const lastContent = Array.isArray(lastMessage.content)
? lastMessage.content
: [{ type: "text" as const, text: lastMessage.content || "" }]
const newContent = Array.isArray(messageContent)
? messageContent
: [{ type: "text" as const, text: messageContent }]
if (message.role === "assistant") {
const newMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {
role: "assistant",
content:
messageContent as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"],
};
merged.push(newMessage);
const mergedContent = [
...lastContent,
...newContent,
] as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"]
lastMessage.content = mergedContent
} else {
const newMessage: OpenAI.Chat.ChatCompletionUserMessageParam = {
role: "user",
content:
messageContent as OpenAI.Chat.ChatCompletionUserMessageParam["content"],
};
merged.push(newMessage);
const mergedContent = [...lastContent, ...newContent] as OpenAI.Chat.ChatCompletionUserMessageParam["content"]
lastMessage.content = mergedContent
}
}
return merged;
},
[],
);
} else {
// Adds new message with the correct type based on role
if (message.role === "assistant") {
const newMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {
role: "assistant",
content: messageContent as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"],
}
merged.push(newMessage)
} else {
const newMessage: OpenAI.Chat.ChatCompletionUserMessageParam = {
role: "user",
content: messageContent as OpenAI.Chat.ChatCompletionUserMessageParam["content"],
}
merged.push(newMessage)
}
}
return merged
}, [])
}
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
import {
CLAUDE_SONNET_1M_SUFFIX,
ModelInfo,
openRouterClaudeFable51mModelId,
openRouterClaudeOpus461mModelId,
openRouterClaudeOpus471mModelId,
openRouterClaudeOpus481mModelId,
@@ -40,8 +39,7 @@ export async function createVercelAIGatewayStream(
model.id === openRouterClaudeSonnet461mModelId ||
model.id === openRouterClaudeOpus461mModelId ||
model.id === openRouterClaudeOpus471mModelId ||
model.id === openRouterClaudeOpus481mModelId ||
model.id === openRouterClaudeFable51mModelId
model.id === openRouterClaudeOpus481mModelId
if (isClaude1m) {
// remove the custom :1m suffix, to create the model id the API expects
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
@@ -1,6 +1,6 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import * as vscode from "vscode";
import { Logger } from "@/shared/services/Logger";
import { Anthropic } from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import { Logger } from "@/shared/services/Logger"
/**
* Safely converts a value into a plain object.
@@ -8,31 +8,31 @@ import { Logger } from "@/shared/services/Logger";
export function asObjectSafe(value: any): object {
// Handle null/undefined
if (!value) {
return {};
return {}
}
try {
// Handle strings that might be JSON
if (typeof value === "string") {
return JSON.parse(value);
return JSON.parse(value)
}
// Handle pre-existing objects
if (typeof value === "object") {
return Object.assign({}, value);
return Object.assign({}, value)
}
return {};
return {}
} catch (error) {
Logger.warn("Cline <Language Model API>: Failed to parse object:", error);
return {};
Logger.warn("Cline <Language Model API>: Failed to parse object:", error)
return {}
}
}
export function convertToVsCodeLmMessages(
anthropicMessages: Anthropic.Messages.MessageParam[],
): vscode.LanguageModelChatMessage[] {
const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = [];
const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = []
for (const anthropicMessage of anthropicMessages) {
// Handle simple string messages
@@ -41,31 +41,27 @@ export function convertToVsCodeLmMessages(
anthropicMessage.role === "assistant"
? vscode.LanguageModelChatMessage.Assistant(anthropicMessage.content)
: vscode.LanguageModelChatMessage.User(anthropicMessage.content),
);
continue;
)
continue
}
// Handle complex message structures
switch (anthropicMessage.role) {
case "user": {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (
| Anthropic.TextBlockParam
| Anthropic.ImageBlockParam
)[];
toolMessages: Anthropic.ToolResultBlockParam[];
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
}
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolResultBlockParam[]
}>(
(acc, part) => {
if (part.type === "tool_result") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
}
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process tool messages first then non-tool messages
const contentParts = [
@@ -78,55 +74,46 @@ export function convertToVsCodeLmMessages(
: (toolMessage.content?.map((part) => {
if (part.type === "image") {
return new vscode.LanguageModelTextPart(
`[Image (${part.source?.type || "Unknown source-type"}): ${(part.source?.type === "base64" && part.source.media_type) || "unknown media-type"} not supported by VSCode LM API]`,
);
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
)
}
return new vscode.LanguageModelTextPart(part.text);
}) ?? [new vscode.LanguageModelTextPart("")]);
return new vscode.LanguageModelTextPart(part.text)
}) ?? [new vscode.LanguageModelTextPart("")])
return new vscode.LanguageModelToolResultPart(
toolMessage.tool_use_id,
toolContentParts,
);
return new vscode.LanguageModelToolResultPart(toolMessage.tool_use_id, toolContentParts)
}),
// Convert non-tool messages to TextParts after tool messages
...nonToolMessages.map((part) => {
if (part.type === "image") {
return new vscode.LanguageModelTextPart(
`[Image (${part.source?.type || "Unknown source-type"}): ${(part.source?.type === "base64" && part.source.media_type) || "unknown media-type"} not supported by VSCode LM API]`,
);
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
)
}
return new vscode.LanguageModelTextPart(part.text);
return new vscode.LanguageModelTextPart(part.text)
}),
];
]
// Add single user message with all content parts
vsCodeLmMessages.push(
vscode.LanguageModelChatMessage.User(contentParts),
);
break;
vsCodeLmMessages.push(vscode.LanguageModelChatMessage.User(contentParts))
break
}
case "assistant": {
const { nonToolMessages, toolMessages } =
anthropicMessage.content.reduce<{
nonToolMessages: (
| Anthropic.TextBlockParam
| Anthropic.ImageBlockParam
)[];
toolMessages: Anthropic.ToolUseBlockParam[];
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part);
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part);
}
return acc;
},
{ nonToolMessages: [], toolMessages: [] },
);
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolUseBlockParam[]
}>(
(acc, part) => {
if (part.type === "tool_use") {
acc.toolMessages.push(part)
} else if (part.type === "text" || part.type === "image") {
acc.nonToolMessages.push(part)
}
return acc
},
{ nonToolMessages: [], toolMessages: [] },
)
// Process tool messages first then non-tool messages
const contentParts = [
@@ -143,24 +130,20 @@ export function convertToVsCodeLmMessages(
// Convert non-tool messages to TextParts after tool messages
...nonToolMessages.map((part) => {
if (part.type === "image") {
return new vscode.LanguageModelTextPart(
"[Image generation not supported by VSCode LM API]",
);
return new vscode.LanguageModelTextPart("[Image generation not supported by VSCode LM API]")
}
return new vscode.LanguageModelTextPart(part.text);
return new vscode.LanguageModelTextPart(part.text)
}),
];
]
// Add the assistant message to the list of messages
vsCodeLmMessages.push(
vscode.LanguageModelChatMessage.Assistant(contentParts),
);
break;
vsCodeLmMessages.push(vscode.LanguageModelChatMessage.Assistant(contentParts))
break
}
}
}
return vsCodeLmMessages;
return vsCodeLmMessages
}
export function convertToAnthropicRole(
@@ -168,22 +151,18 @@ export function convertToAnthropicRole(
): Anthropic.Messages.MessageParam["role"] | null {
switch (vsCodeLmMessageRole) {
case vscode.LanguageModelChatMessageRole.Assistant:
return "assistant";
return "assistant"
case vscode.LanguageModelChatMessageRole.User:
return "user";
return "user"
default:
return null;
return null
}
}
export function convertToAnthropicMessage(
vsCodeLmMessage: vscode.LanguageModelChatMessage,
): Anthropic.Messages.Message {
const anthropicRole = convertToAnthropicRole(vsCodeLmMessage.role);
export function convertToAnthropicMessage(vsCodeLmMessage: vscode.LanguageModelChatMessage): Anthropic.Messages.Message {
const anthropicRole = convertToAnthropicRole(vsCodeLmMessage.role)
if (anthropicRole !== "assistant") {
throw new Error(
"Cline <Language Model API>: Only assistant messages are supported.",
);
throw new Error("Cline <Language Model API>: Only assistant messages are supported.")
}
return {
@@ -198,7 +177,7 @@ export function convertToAnthropicMessage(
type: "text",
text: part.value,
citations: null,
};
}
}
if (part instanceof vscode.LanguageModelToolCallPart) {
@@ -207,10 +186,10 @@ export function convertToAnthropicMessage(
id: part.callId || crypto.randomUUID(),
name: part.name,
input: asObjectSafe(part.input),
};
}
}
return null;
return null
})
.filter((part): part is Anthropic.ContentBlock => part !== null),
stop_reason: null,
@@ -220,7 +199,6 @@ export function convertToAnthropicMessage(
output_tokens: 0,
cache_creation_input_tokens: null,
cache_read_input_tokens: null,
server_tool_use: null,
},
};
}
}
@@ -1,12 +1,10 @@
import * as disk from "@core/storage/disk"
import { openRouterClaudeFable51mModelId } from "@shared/api"
import axios from "axios"
import { expect } from "chai"
import fs from "fs/promises"
import { afterEach, beforeEach, describe, it } from "mocha"
import sinon from "sinon"
import { ClineEnv, Environment } from "@/config"
import type { Controller } from "@/core/controller"
import { StateManager } from "@/core/storage/StateManager"
import { getFeatureFlagsService } from "@/services/feature-flags"
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
@@ -39,7 +37,7 @@ describe("refreshClineModels", () => {
sandbox.stub(StateManager, "get").returns({
getModelsCache: () => null,
setModelsCache: () => {},
} as unknown as StateManager)
} as any)
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp")
sandbox.stub(fs, "writeFile").resolves()
sandbox.stub(axios, "get").resolves({
@@ -69,70 +67,11 @@ describe("refreshClineModels", () => {
},
})
const models = await refreshClineModels({} as Controller)
const models = await refreshClineModels({} as any)
const qwen37 = models["qwen/qwen3.7-max"]
expect(qwen37.supportsPromptCache).to.equal(true)
expect(qwen37.cacheReadsPrice).to.equal(0.25)
expect(qwen37.cacheWritesPrice).to.equal(undefined)
})
it("adds Claude Fable 5 context variants to the Cline model list", async () => {
sandbox.stub(getFeatureFlagsService(), "getBooleanFlagEnabled").callsFake((flag) => {
return flag === FeatureFlag.EXTENSION_CLINE_MODELS_ENDPOINT
})
sandbox.stub(ClineEnv, "config").returns({
environment: Environment.production,
appBaseUrl: "https://app.cline-mock.bot",
apiBaseUrl: "https://api.cline-mock.bot",
mcpBaseUrl: "https://api.cline-mock.bot/v1/mcp",
})
sandbox.stub(StateManager, "get").returns({
getModelsCache: () => null,
setModelsCache: () => {},
} as unknown as StateManager)
sandbox.stub(disk, "ensureCacheDirectoryExists").resolves("/tmp")
sandbox.stub(fs, "writeFile").resolves()
sandbox.stub(axios, "get").resolves({
data: {
data: [
{
id: "anthropic/claude-fable-5",
name: "Claude Fable 5",
description: "Fetched description",
context_length: 1_000_000,
top_provider: {
max_completion_tokens: 128_000,
context_length: 1_000_000,
is_moderated: false,
},
architecture: {
modality: ["text", "image"],
},
pricing: {
prompt: "0.00001",
completion: "0.00005",
input_cache_read: "0.000001",
input_cache_write: "0.0000125",
},
supported_parameters: ["include_reasoning", "reasoning"],
},
],
},
})
const models = await refreshClineModels({} as Controller)
const fable = models["anthropic/claude-fable-5"]
const fable1m = models[openRouterClaudeFable51mModelId]
expect(fable.contextWindow).to.equal(200_000)
expect(fable.maxTokens).to.equal(128_000)
expect(fable.supportsPromptCache).to.equal(true)
expect(fable.inputPrice).to.equal(10)
expect(fable.outputPrice).to.equal(50)
expect(fable.cacheWritesPrice).to.equal(12.5)
expect(fable.cacheReadsPrice).to.equal(1)
expect(fable1m.contextWindow).to.equal(1_000_000)
expect(fable1m.tiers).to.not.equal(undefined)
})
})
@@ -5,6 +5,9 @@ import fs from "fs/promises"
import { afterEach, beforeEach, describe, it } from "mocha"
import sinon from "sinon"
import { ClineEnv, Environment } from "@/config"
import { getFeatureFlagsService } from "@/services/feature-flags"
import { CLINE_RECOMMENDED_MODELS_FALLBACK } from "@/shared/cline/recommended-models"
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
import { Logger } from "@/shared/services/Logger"
import { refreshClineRecommendedModels, resetClineRecommendedModelsCacheForTests } from "../refreshClineRecommendedModels"
@@ -23,7 +26,20 @@ describe("refreshClineRecommendedModels", () => {
sandbox.restore()
})
it("fetches from upstream", async () => {
it("returns hardcoded models and skips upstream fetch when rollout flag is off", async () => {
sandbox.stub(getFeatureFlagsService(), "getBooleanFlagEnabled").returns(false)
const axiosGetStub = sandbox.stub(axios, "get")
const result = await refreshClineRecommendedModels()
expect(result).to.deep.equal(CLINE_RECOMMENDED_MODELS_FALLBACK)
expect(axiosGetStub.called).to.equal(false)
})
it("fetches from upstream when rollout flag is on", async () => {
sandbox.stub(getFeatureFlagsService(), "getBooleanFlagEnabled").callsFake((flag) => {
return flag === FeatureFlag.CLINE_RECOMMENDED_MODELS_UPSTREAM
})
sandbox.stub(ClineEnv, "config").returns({
environment: Environment.production,
appBaseUrl: "https://app.cline-mock.bot",
@@ -62,7 +78,10 @@ describe("refreshClineRecommendedModels", () => {
})
})
it("uses the in-memory cache after upstream cache is populated", async () => {
it("uses hardcoded models when rollout flag is turned off after upstream cache is populated", async () => {
const flagStub = sandbox.stub(getFeatureFlagsService(), "getBooleanFlagEnabled")
flagStub.onFirstCall().returns(true)
flagStub.onSecondCall().returns(false)
sandbox.stub(ClineEnv, "config").returns({
environment: Environment.production,
appBaseUrl: "https://app.cline-mock.bot",
@@ -82,6 +101,7 @@ describe("refreshClineRecommendedModels", () => {
const secondResult = await refreshClineRecommendedModels()
expect(axiosGetStub.calledOnce).to.equal(true)
expect(secondResult).to.deep.equal(firstResult)
expect(firstResult).to.not.deep.equal(CLINE_RECOMMENDED_MODELS_FALLBACK)
expect(secondResult).to.deep.equal(CLINE_RECOMMENDED_MODELS_FALLBACK)
})
})
@@ -11,10 +11,8 @@ import { StateManager } from "@/core/storage/StateManager"
import { featureFlagsService } from "@/services/feature-flags"
import {
ANTHROPIC_MAX_THINKING_BUDGET,
CLAUDE_FABLE_1M_TIERS,
CLAUDE_OPUS_1M_TIERS,
CLAUDE_SONNET_1M_TIERS,
openRouterClaudeFable51mModelId,
openRouterClaudeOpus461mModelId,
openRouterClaudeOpus471mModelId,
openRouterClaudeOpus481mModelId,
@@ -80,7 +78,7 @@ interface ClineRawModelInfo {
input_cache_write?: string
} | null
supports_global_endpoint?: boolean | null
tiers?: ModelInfo["tiers"] | null
tiers?: any[] | null
supported_parameters?: ClineSupportedParams[] | null
}
@@ -140,7 +138,7 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
let models: Record<string, ModelInfo> = {}
try {
const rawModels = await fetchRawClineModels()
const parsePrice = (price: unknown) => {
const parsePrice = (price: any) => {
if (price === undefined || price === null || price === "") {
return undefined
}
@@ -206,14 +204,6 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
modelInfo.cacheWritesPrice = 6.25
modelInfo.cacheReadsPrice = 0.5
break
case "anthropic/claude-fable-5":
modelInfo.contextWindow = 200_000
modelInfo.supportsPromptCache = true
modelInfo.inputPrice = 10
modelInfo.outputPrice = 50
modelInfo.cacheWritesPrice = 12.5
modelInfo.cacheReadsPrice = 1
break
case "anthropic/claude-opus-4.5":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 6.25
@@ -309,12 +299,6 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
models[openRouterClaudeOpus481mModelId] = claudeOpus1mModelInfo
}
}
if (rawModel.id === "anthropic/claude-fable-5") {
const claudeFable1mModelInfo = cloneDeep(modelInfo)
claudeFable1mModelInfo.contextWindow = 1_000_000
claudeFable1mModelInfo.tiers = CLAUDE_FABLE_1M_TIERS
models[openRouterClaudeFable51mModelId] = claudeFable1mModelInfo
}
}
if (Object.keys(models).length === 0) {
throw new Error("No Cline models returned from API")
@@ -3,7 +3,10 @@ import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { ClineEnv } from "@/config"
import { featureFlagsService } from "@/services/feature-flags"
import { CLINE_RECOMMENDED_MODELS_FALLBACK } from "@/shared/cline/recommended-models"
import { getAxiosSettings } from "@/shared/net"
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
import { Logger } from "@/shared/services/Logger"
export interface ClineRecommendedModelData {
@@ -23,6 +26,14 @@ const RECOMMENDED_MODELS_CACHE_TTL_MS = 60 * 60 * 1000
let pendingRefresh: Promise<ClineRecommendedModelsData> | null = null
let inMemoryCache: { data: ClineRecommendedModelsData; timestamp: number } | null = null
function getHardcodedRecommendedModels(): ClineRecommendedModelsData {
return CLINE_RECOMMENDED_MODELS_FALLBACK
}
function useUpstreamRecommendedModels(): boolean {
return featureFlagsService.getBooleanFlagEnabled(FeatureFlag.CLINE_RECOMMENDED_MODELS_UPSTREAM)
}
function normalizeRecommendedModel(raw: unknown): ClineRecommendedModelData | null {
if (!raw || typeof raw !== "object") {
return null
@@ -69,6 +80,10 @@ function normalizeRecommendedModelsResponse(raw: unknown): ClineRecommendedModel
}
export async function refreshClineRecommendedModels(): Promise<ClineRecommendedModelsData> {
if (!useUpstreamRecommendedModels()) {
return getHardcodedRecommendedModels()
}
if (inMemoryCache && Date.now() - inMemoryCache.timestamp <= RECOMMENDED_MODELS_CACHE_TTL_MS) {
return inMemoryCache.data
}
@@ -8,10 +8,8 @@ import path from "path"
import { StateManager } from "@/core/storage/StateManager"
import {
ANTHROPIC_MAX_THINKING_BUDGET,
CLAUDE_FABLE_1M_TIERS,
CLAUDE_OPUS_1M_TIERS,
CLAUDE_SONNET_1M_TIERS,
openRouterClaudeFable51mModelId,
openRouterClaudeOpus461mModelId,
openRouterClaudeOpus471mModelId,
openRouterClaudeOpus481mModelId,
@@ -181,14 +179,6 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
modelInfo.cacheWritesPrice = 6.25
modelInfo.cacheReadsPrice = 0.5
break
case "anthropic/claude-fable-5":
modelInfo.contextWindow = 200_000 // restrict to 200k, 1m variant created below
modelInfo.supportsPromptCache = true
modelInfo.inputPrice = 10
modelInfo.outputPrice = 50
modelInfo.cacheWritesPrice = 12.5
modelInfo.cacheReadsPrice = 1
break
case "anthropic/claude-opus-4.5":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 6.25
@@ -332,12 +322,6 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
models[openRouterClaudeOpus481mModelId] = claudeOpus1mModelInfo
}
}
if (rawModel.id === "anthropic/claude-fable-5") {
const claudeFable1mModelInfo = cloneDeep(modelInfo)
claudeFable1mModelInfo.contextWindow = 1_000_000
claudeFable1mModelInfo.tiers = CLAUDE_FABLE_1M_TIERS
models[openRouterClaudeFable51mModelId] = claudeFable1mModelInfo
}
}
// Save models and cache them in memory
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
@@ -12,6 +12,12 @@ describe("TaskCancel Hook", () => {
let getEnv: () => { tempDir: string }
let hookTestEnv: HookTestEnv
// On Windows, hooks execute via a PowerShell bridge that spawns a child
// Node process. That double-process startup is slow and variable on CI and
// can easily exceed Mocha's default 2 s timeout, so spawning tests opt into a
// larger timeout. Mirrors taskresume/hook-factory/user-prompt-submit tests.
const WINDOWS_HOOK_TEST_TIMEOUT_MS = 15000
const writeHookScript = async (hookPath: string, nodeScript: string): Promise<void> => {
await writeHookScriptForPlatform(hookPath, nodeScript)
}
@@ -29,7 +35,11 @@ describe("TaskCancel Hook", () => {
})
describe("Hook Input Format", () => {
it("should receive task metadata with completionStatus", async () => {
it("should receive task metadata with completionStatus", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
@@ -61,7 +71,11 @@ console.log(JSON.stringify({
// Note: contextModification is ignored for TaskCancel hooks
})
it("should handle 'abandoned' completion status", async () => {
it("should handle 'abandoned' completion status", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
@@ -96,7 +110,11 @@ console.log(JSON.stringify({
// Note: contextModification is ignored for TaskCancel hooks
})
it("should receive all common hook input fields", async () => {
it("should receive all common hook input fields", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
@@ -136,7 +154,11 @@ console.log(JSON.stringify({
})
describe("Fire-and-Forget Behavior", () => {
it("should ignore contextModification regardless of content", async () => {
it("should ignore contextModification regardless of content", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
console.log(JSON.stringify({
@@ -192,7 +214,11 @@ console.log(JSON.stringify({
// The contextModification value is different but behavior is identical (fire-and-forget)
})
it("should succeed regardless of hook return value", async () => {
it("should succeed regardless of hook return value", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
// Note: contextModification is ignored for TaskCancel hooks
@@ -222,7 +248,11 @@ console.log(JSON.stringify({
result.cancel.should.be.false()
})
it("should return error message when hook returns cancel: true", async () => {
it("should return error message when hook returns cancel: true", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
console.log(JSON.stringify({
@@ -254,7 +284,11 @@ console.log(JSON.stringify({
result.errorMessage?.should.equal("Hook tried to block cancellation")
})
it("should execute without errors for cleanup purposes", async () => {
it("should execute without errors for cleanup purposes", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
@@ -288,7 +322,11 @@ console.log(JSON.stringify({
})
describe("Error Handling", () => {
it("should surface hook errors to the user", async () => {
it("should surface hook errors to the user", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
console.error("Hook execution error");
@@ -317,7 +355,11 @@ process.exit(1);`
}
})
it("should handle malformed JSON output from hook", async () => {
it("should handle malformed JSON output from hook", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const hookPath = path.join(tempDir, ".clinerules", "hooks", "TaskCancel")
const hookScript = `#!/usr/bin/env node
console.log("not valid json")`
@@ -359,7 +401,11 @@ console.log("not valid json")`
stubHookDirs(sandbox, [globalHooksDir, workspaceHooksDir])
})
it("should execute both global and workspace TaskCancel hooks", async () => {
it("should execute both global and workspace TaskCancel hooks", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
// Create global hook
const globalHookPath = path.join(globalHooksDir, "TaskCancel")
const globalHookScript = `#!/usr/bin/env node
@@ -399,7 +445,11 @@ console.log(JSON.stringify({
// Both hooks executed successfully
})
it("should execute both hooks with different completion statuses", async () => {
it("should execute both hooks with different completion statuses", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const globalHookPath = path.join(globalHooksDir, "TaskCancel")
const globalHookScript = `#!/usr/bin/env node
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
@@ -441,7 +491,11 @@ console.log(JSON.stringify({
})
describe("No Hook Behavior", () => {
it("should succeed when no hook exists", async () => {
it("should succeed when no hook exists", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
const factory = new HookFactory()
const runner = await factory.create("TaskCancel")
@@ -461,7 +515,11 @@ console.log(JSON.stringify({
})
describe("Fixture-Based Tests", () => {
it("should handle cancel: true with no error message", async () => {
it("should handle cancel: true with no error message", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
await loadFixture("hooks/taskcancel/false-no-error", getEnv().tempDir)
const factory = new HookFactory()
@@ -484,7 +542,11 @@ console.log(JSON.stringify({
// Cancellation still proceeds (fire-and-forget)
})
it("should handle cancel: true with error message", async () => {
it("should handle cancel: true with error message", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
await loadFixture("hooks/taskcancel/false-with-error", getEnv().tempDir)
const factory = new HookFactory()
@@ -507,7 +569,11 @@ console.log(JSON.stringify({
// Cancellation still proceeds (fire-and-forget)
})
it("should handle cancel: false with no error message", async () => {
it("should handle cancel: false with no error message", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
await loadFixture("hooks/taskcancel/true-no-error", getEnv().tempDir)
const factory = new HookFactory()
@@ -529,7 +595,11 @@ console.log(JSON.stringify({
// Normal success case - no errors to surface
})
it("should handle cancel: false with error message", async () => {
it("should handle cancel: false with error message", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
await loadFixture("hooks/taskcancel/true-with-error", getEnv().tempDir)
const factory = new HookFactory()
@@ -553,7 +623,11 @@ console.log(JSON.stringify({
// Cancellation still proceeds (fire-and-forget)
})
it("should handle hook that exits with non-zero status code", async () => {
it("should handle hook that exits with non-zero status code", async function () {
if (process.platform === "win32") {
this.timeout(WINDOWS_HOOK_TEST_TIMEOUT_MS)
}
await loadFixture("hooks/taskcancel/error", getEnv().tempDir)
const factory = new HookFactory()
+86 -132
View File
@@ -1,33 +1,33 @@
import { findLastIndex } from "@shared/array";
import type { ClineMessage } from "@shared/ExtensionMessage";
import type { ClineStorageMessage } from "@shared/messages/content";
import { Logger } from "@/shared/services/Logger";
import type { ContextManager } from "../context/context-management/ContextManager";
import type { MessageStateHandler } from "../task/message-state";
import type { HookModelInputContext } from "./hook-factory";
import { findLastIndex } from "@shared/array"
import type { ClineMessage } from "@shared/ExtensionMessage"
import type { ClineStorageMessage } from "@shared/messages/content"
import { Logger } from "@/shared/services/Logger"
import type { ContextManager } from "../context/context-management/ContextManager"
import type { MessageStateHandler } from "../task/message-state"
import type { HookModelInputContext } from "./hook-factory"
/**
* Active hook execution state
* Represents a hook process that is currently running
*/
export type HookExecution = {
hookName: string;
toolName?: string;
messageTs: number;
abortController: AbortController;
};
hookName: string
toolName?: string
messageTs: number
abortController: AbortController
}
/**
* Custom error class for hook cancellation
* Used to signal that a hook cancelled an operation
*/
export class HookCancellationError extends Error {
public readonly wasCancelled: boolean;
public readonly wasCancelled: boolean
constructor(wasCancelled: boolean) {
super("Hook cancelled the operation");
this.name = "HookCancellationError";
this.wasCancelled = wasCancelled;
super("Hook cancelled the operation")
this.name = "HookCancellationError"
this.wasCancelled = wasCancelled
}
}
@@ -35,10 +35,10 @@ export class HookCancellationError extends Error {
* Token usage information extracted from an API request message
*/
export interface TokenUsage {
tokensIn: number;
tokensOut: number;
tokensInCache: number;
tokensOutCache: number;
tokensIn: number
tokensOut: number
tokensInCache: number
tokensOutCache: number
}
/**
@@ -46,34 +46,29 @@ export interface TokenUsage {
* @param message The API request message to parse
* @returns Token usage information, or zeros if parsing fails
*/
export function extractTokenUsageFromMessage(
message: ClineMessage | undefined,
): TokenUsage {
export function extractTokenUsageFromMessage(message: ClineMessage | undefined): TokenUsage {
const defaultUsage: TokenUsage = {
tokensIn: 0,
tokensOut: 0,
tokensInCache: 0,
tokensOutCache: 0,
};
}
if (!message?.text) {
return defaultUsage;
return defaultUsage
}
try {
const apiReqInfo = JSON.parse(message.text);
const apiReqInfo = JSON.parse(message.text)
return {
tokensIn: apiReqInfo.tokensIn || 0,
tokensOut: apiReqInfo.tokensOut || 0,
tokensInCache: apiReqInfo.cacheWrites || 0,
tokensOutCache: apiReqInfo.cacheReads || 0,
};
}
} catch (error) {
Logger.error(
"[PreCompact] Failed to parse API request token usage:",
error,
);
return defaultUsage;
Logger.error("[PreCompact] Failed to parse API request token usage:", error)
return defaultUsage
}
}
@@ -81,9 +76,9 @@ export function extractTokenUsageFromMessage(
* Context files written for hook access
*/
export interface PreCompactContextFiles {
contextJsonPath: string;
contextRawPath: string;
hookTimestamp: number;
contextJsonPath: string
contextRawPath: string
hookTimestamp: number
}
/**
@@ -96,32 +91,23 @@ export async function writePreCompactContextFiles(
taskId: string,
currentContext: ClineStorageMessage[],
): Promise<PreCompactContextFiles> {
const { writeConversationHistoryJson, writeConversationHistoryText } =
await import("../storage/disk");
const { writeConversationHistoryJson, writeConversationHistoryText } = await import("../storage/disk")
// Generate single timestamp for both files to ensure they match
const hookTimestamp = Date.now();
const hookTimestamp = Date.now()
// Write context files for hook access
const contextJsonPath = await writeConversationHistoryJson(
taskId,
currentContext,
hookTimestamp,
);
const contextRawPath = await writeConversationHistoryText(
taskId,
currentContext,
hookTimestamp,
);
const contextJsonPath = await writeConversationHistoryJson(taskId, currentContext, hookTimestamp)
const contextRawPath = await writeConversationHistoryText(taskId, currentContext, hookTimestamp)
return { contextJsonPath, contextRawPath, hookTimestamp };
return { contextJsonPath, contextRawPath, hookTimestamp }
}
/**
* Task state interface for cancellation handling
*/
export interface TaskStateForCancellation {
didFinishAbortingStream: boolean;
didFinishAbortingStream: boolean
}
/**
@@ -131,61 +117,53 @@ export interface TaskStateForCancellation {
export interface PreCompactHookParams {
// Task identification
/** Task identifier */
taskId: string;
taskId: string
/** ULID for telemetry */
ulid: string;
ulid: string
/** Active hook model context */
modelContext: HookModelInputContext;
modelContext: HookModelInputContext
// Conversation state
/** API conversation history */
apiConversationHistory: ClineStorageMessage[];
apiConversationHistory: ClineStorageMessage[]
/** Current deleted range (if any) */
conversationHistoryDeletedRange?: [number, number];
conversationHistoryDeletedRange?: [number, number]
/** Cline messages for extracting token usage */
clineMessages: ClineMessage[];
clineMessages: ClineMessage[]
// Services
/** Context manager for getting truncated messages */
contextManager: ContextManager;
contextManager: ContextManager
/** Message state handler for accessing conversation data */
messageStateHandler: MessageStateHandler;
messageStateHandler: MessageStateHandler
// Compaction metadata
/** Compaction strategy to report in hook data */
compactionStrategy: string;
compactionStrategy: string
/** Optional: Pre-calculated deleted range to report */
deletedRange?: [number, number];
deletedRange?: [number, number]
// UI callbacks
/** Callback to display messages */
say: (
type: any,
text?: string,
images?: string[],
files?: string[],
partial?: boolean,
) => Promise<number | undefined>;
say: (type: any, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<number | undefined>
/** Callback to save state and post to webview */
postStateToWebview: () => Promise<void>;
postStateToWebview: () => Promise<void>
// Hook management callbacks
/** Callback to set active hook execution */
setActiveHookExecution: (
hookExecution: HookExecution | undefined,
) => Promise<void>;
setActiveHookExecution: (hookExecution: HookExecution | undefined) => Promise<void>
/** Callback to clear active hook execution */
clearActiveHookExecution: () => Promise<void>;
clearActiveHookExecution: () => Promise<void>
// Cancellation dependencies
/** Task state object for setting abort flag */
taskState: TaskStateForCancellation;
taskState: TaskStateForCancellation
/** Callback to cancel the task */
cancelTask: () => Promise<void>;
cancelTask: () => Promise<void>
// Configuration
/** Whether hooks are enabled */
hooksEnabled: boolean;
hooksEnabled: boolean
}
/**
@@ -193,7 +171,7 @@ export interface PreCompactHookParams {
*/
export interface PreCompactHookResult {
/** Context modification provided by the hook */
contextModification?: string;
contextModification?: string
}
/**
@@ -206,52 +184,37 @@ export interface PreCompactHookResult {
* @throws HookCancellationError if the hook cancels the operation
* @throws Re-throws other errors after cleanup (caller should handle gracefully)
*/
export async function executePreCompactHookWithCleanup(
params: PreCompactHookParams,
): Promise<PreCompactHookResult> {
const { executeHook } = await import("./hook-executor");
const { cleanupConversationHistoryFile } = await import("../storage/disk");
export async function executePreCompactHookWithCleanup(params: PreCompactHookParams): Promise<PreCompactHookResult> {
const { executeHook } = await import("./hook-executor")
const { cleanupConversationHistoryFile } = await import("../storage/disk")
let contextJsonPath: string | undefined;
let contextRawPath: string | undefined;
let contextJsonPath: string | undefined
let contextRawPath: string | undefined
try {
// Get current active context (respects previous compactions).
// getTruncatedMessages types its output as Anthropic.MessageParam[], but it slices the Cline-stored
// conversation history (ClineStorageMessage[]) passed in, so narrow it back here.
// Get current active context (respects previous compactions)
const currentContext = params.contextManager.getTruncatedMessages(
params.apiConversationHistory,
params.conversationHistoryDeletedRange,
) as ClineStorageMessage[];
)
// Write context files for hook access
const contextFiles = await writePreCompactContextFiles(
params.taskId,
currentContext,
);
contextJsonPath = contextFiles.contextJsonPath;
contextRawPath = contextFiles.contextRawPath;
const contextFiles = await writePreCompactContextFiles(params.taskId, currentContext)
contextJsonPath = contextFiles.contextJsonPath
contextRawPath = contextFiles.contextRawPath
// Extract token usage from the most recent API request
const previousApiReqIndex = findLastIndex(
params.clineMessages,
(m) => m.say === "api_req_started",
);
const previousRequest =
previousApiReqIndex !== -1
? params.clineMessages[previousApiReqIndex]
: undefined;
const { tokensIn, tokensOut, tokensInCache, tokensOutCache } =
extractTokenUsageFromMessage(previousRequest);
const previousApiReqIndex = findLastIndex(params.clineMessages, (m) => m.say === "api_req_started")
const previousRequest = previousApiReqIndex !== -1 ? params.clineMessages[previousApiReqIndex] : undefined
const { tokensIn, tokensOut, tokensInCache, tokensOutCache } = extractTokenUsageFromMessage(previousRequest)
// Extract truncation range - use provided range or extract from conversationHistoryDeletedRange
let deletedRangeStart = 0;
let deletedRangeEnd = 0;
let deletedRangeStart = 0
let deletedRangeEnd = 0
if (params.deletedRange) {
[deletedRangeStart, deletedRangeEnd] = params.deletedRange;
;[deletedRangeStart, deletedRangeEnd] = params.deletedRange
} else if (params.conversationHistoryDeletedRange) {
[deletedRangeStart, deletedRangeEnd] =
params.conversationHistoryDeletedRange;
;[deletedRangeStart, deletedRangeEnd] = params.conversationHistoryDeletedRange
}
// Execute the hook
@@ -282,62 +245,53 @@ export async function executePreCompactHookWithCleanup(
taskId: params.taskId,
hooksEnabled: params.hooksEnabled,
model: params.modelContext,
});
})
// Handle cancellation from hook
if (preCompactResult.cancel === true) {
// Log cancellation for debugging
const cancellationSource = preCompactResult.wasCancelled
? "user"
: "PreCompact hook";
Logger.log(
`[PreCompact] Context compaction cancelled by ${cancellationSource} for task ${params.taskId}`,
);
const cancellationSource = preCompactResult.wasCancelled ? "user" : "PreCompact hook"
Logger.log(`[PreCompact] Context compaction cancelled by ${cancellationSource} for task ${params.taskId}`)
// Internalized cancellation state management (replaces handleCancellation callback)
// Always save state before cancelling, regardless of cancellation source
params.taskState.didFinishAbortingStream = true;
await params.messageStateHandler.saveClineMessagesAndUpdateHistory();
params.taskState.didFinishAbortingStream = true
await params.messageStateHandler.saveClineMessagesAndUpdateHistory()
await params.messageStateHandler.overwriteApiConversationHistory(
params.messageStateHandler.getApiConversationHistory(),
);
await params.postStateToWebview();
)
await params.postStateToWebview()
// Trigger full cancellation flow
await params.cancelTask();
await params.cancelTask()
// Throw error to signal cancellation to caller
throw new HookCancellationError(preCompactResult.wasCancelled);
throw new HookCancellationError(preCompactResult.wasCancelled)
}
// Hook completed successfully - log if context modification provided
if (preCompactResult.contextModification) {
Logger.log(
`[PreCompact] Hook provided context modification for task ${params.taskId}`,
);
Logger.log(`[PreCompact] Hook provided context modification for task ${params.taskId}`)
}
return {
contextModification: preCompactResult.contextModification,
};
}
} catch (error) {
// Re-throw error for caller to handle
throw error;
throw error
} finally {
// Clean up temporary files - always executed regardless of success or error
// Wrap in try-catch to prevent cleanup failures from masking original errors
try {
if (contextJsonPath) {
await cleanupConversationHistoryFile(contextJsonPath);
await cleanupConversationHistoryFile(contextJsonPath)
}
if (contextRawPath) {
await cleanupConversationHistoryFile(contextRawPath);
await cleanupConversationHistoryFile(contextRawPath)
}
} catch (cleanupError) {
Logger.error(
"[PreCompact] Failed to cleanup context files:",
cleanupError,
);
Logger.error("[PreCompact] Failed to cleanup context files:", cleanupError)
// Don't throw - cleanup failure shouldn't mask original error
}
}
+237 -359
View File
@@ -1,26 +1,22 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type {
EnvironmentMetadataEntry,
TaskMetadata,
} from "@core/context/context-tracking/ContextTrackerTypes";
import { execa } from "@packages/execa";
import type { ClineMessage } from "@shared/ExtensionMessage";
import type { HistoryItem } from "@shared/HistoryItem";
import type { RemoteConfig } from "@shared/remote-config/schema";
import type { GlobalState, Settings } from "@shared/storage/state-keys";
import { fileExistsAtPath, isDirectory } from "@utils/fs";
import fs from "fs/promises";
import os from "os";
import * as path from "path";
import { HostProvider } from "@/hosts/host-provider";
import { ExtensionRegistryInfo } from "@/registry";
import { telemetryService } from "@/services/telemetry";
import type { McpMarketplaceCatalog } from "@/shared/mcp";
import type { ClineStorageMessage } from "@/shared/messages/content";
import { Logger } from "@/shared/services/Logger";
import { syncWorker } from "@/shared/services/worker/sync";
import { reconstructTaskHistory } from "../commands/reconstructTaskHistory";
import { StateManager } from "./StateManager";
import { Anthropic } from "@anthropic-ai/sdk"
import { EnvironmentMetadataEntry, TaskMetadata } from "@core/context/context-tracking/ContextTrackerTypes"
import { execa } from "@packages/execa"
import { ClineMessage } from "@shared/ExtensionMessage"
import { HistoryItem } from "@shared/HistoryItem"
import { RemoteConfig } from "@shared/remote-config/schema"
import { GlobalState, Settings } from "@shared/storage/state-keys"
import { fileExistsAtPath, isDirectory } from "@utils/fs"
import fs from "fs/promises"
import os from "os"
import * as path from "path"
import { HostProvider } from "@/hosts/host-provider"
import { ExtensionRegistryInfo } from "@/registry"
import { telemetryService } from "@/services/telemetry"
import { McpMarketplaceCatalog } from "@/shared/mcp"
import { Logger } from "@/shared/services/Logger"
import { syncWorker } from "@/shared/services/worker/sync"
import { reconstructTaskHistory } from "../commands/reconstructTaskHistory"
import { StateManager } from "./StateManager"
/**
* Atomically write data to a file using temp file + rename pattern.
@@ -32,16 +28,16 @@ import { StateManager } from "./StateManager";
* @param data - The data to write
*/
async function atomicWriteFile(filePath: string, data: string): Promise<void> {
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substring(7)}.json`;
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substring(7)}.json`
try {
// Write to temporary file first
await fs.writeFile(tmpPath, data, "utf8");
await fs.writeFile(tmpPath, data, "utf8")
// Rename temp file to target (atomic in most cases)
await fs.rename(tmpPath, filePath);
await fs.rename(tmpPath, filePath)
} catch (error) {
// Clean up temp file if it exists
fs.unlink(tmpPath).catch(() => {});
throw error;
fs.unlink(tmpPath).catch(() => {})
throw error
}
}
@@ -71,7 +67,7 @@ export const GlobalFileNames = {
taskMetadata: "task_metadata.json",
mcpMarketplaceCatalog: "mcp_marketplace_catalog.json",
remoteConfig: (orgId: string) => `remote_config_${orgId}.json`,
};
}
export async function getDocumentsPath(): Promise<string> {
if (process.platform === "win32") {
@@ -80,37 +76,33 @@ export async function getDocumentsPath(): Promise<string> {
"-NoProfile", // Ignore user's PowerShell profile(s)
"-Command",
"[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)",
]);
const trimmedPath = docsPath.trim();
])
const trimmedPath = docsPath.trim()
if (trimmedPath) {
return trimmedPath;
return trimmedPath
}
} catch (_err) {
Logger.error(
"Failed to retrieve Windows Documents path. Falling back to homedir/Documents.",
);
Logger.error("Failed to retrieve Windows Documents path. Falling back to homedir/Documents.")
}
} else if (process.platform === "linux") {
try {
// First check if xdg-user-dir exists
await execa("which", ["xdg-user-dir"]);
await execa("which", ["xdg-user-dir"])
// If it exists, try to get XDG documents path
const { stdout } = await execa("xdg-user-dir", ["DOCUMENTS"]);
const trimmedPath = stdout.trim();
const { stdout } = await execa("xdg-user-dir", ["DOCUMENTS"])
const trimmedPath = stdout.trim()
if (trimmedPath) {
return trimmedPath;
return trimmedPath
}
} catch {
// Log error but continue to fallback
Logger.error(
"Failed to retrieve XDG Documents path. Falling back to homedir/Documents.",
);
Logger.error("Failed to retrieve XDG Documents path. Falling back to homedir/Documents.")
}
}
// Default fallback for all platforms
return path.join(os.homedir(), "Documents");
return path.join(os.homedir(), "Documents")
}
/**
@@ -123,68 +115,66 @@ export async function getDocumentsPath(): Promise<string> {
* This is intended to eventually replace ~/Documents/Cline as the global config location.
*/
export function getClineHomePath(): string {
return path.join(os.homedir(), ".cline");
return path.join(os.homedir(), ".cline")
}
export async function ensureTaskDirectoryExists(
taskId: string,
): Promise<string> {
return getGlobalStorageDir("tasks", taskId);
export async function ensureTaskDirectoryExists(taskId: string): Promise<string> {
return getGlobalStorageDir("tasks", taskId)
}
export async function ensureRulesDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath();
const clineRulesDir = path.join(userDocumentsPath, "Cline", "Rules");
const userDocumentsPath = await getDocumentsPath()
const clineRulesDir = path.join(userDocumentsPath, "Cline", "Rules")
try {
await fs.mkdir(clineRulesDir, { recursive: true });
await fs.mkdir(clineRulesDir, { recursive: true })
} catch (_error) {
return path.join(os.homedir(), "Documents", "Cline", "Rules"); // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
return path.join(os.homedir(), "Documents", "Cline", "Rules") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
}
return clineRulesDir;
return clineRulesDir
}
export async function ensureWorkflowsDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath();
const clineWorkflowsDir = path.join(userDocumentsPath, "Cline", "Workflows");
const userDocumentsPath = await getDocumentsPath()
const clineWorkflowsDir = path.join(userDocumentsPath, "Cline", "Workflows")
try {
await fs.mkdir(clineWorkflowsDir, { recursive: true });
await fs.mkdir(clineWorkflowsDir, { recursive: true })
} catch (_error) {
return path.join(os.homedir(), "Documents", "Cline", "Workflows"); // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
return path.join(os.homedir(), "Documents", "Cline", "Workflows") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
}
return clineWorkflowsDir;
return clineWorkflowsDir
}
export async function ensureMcpServersDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath();
const mcpServersDir = path.join(userDocumentsPath, "Cline", "MCP");
const userDocumentsPath = await getDocumentsPath()
const mcpServersDir = path.join(userDocumentsPath, "Cline", "MCP")
try {
await fs.mkdir(mcpServersDir, { recursive: true });
await fs.mkdir(mcpServersDir, { recursive: true })
} catch (_error) {
return path.join(os.homedir(), "Documents", "Cline", "MCP"); // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
return path.join(os.homedir(), "Documents", "Cline", "MCP") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
}
return mcpServersDir;
return mcpServersDir
}
export async function ensureHooksDirectoryExists(): Promise<string> {
const userDocumentsPath = await getDocumentsPath();
const clineHooksDir = path.join(userDocumentsPath, "Cline", "Hooks");
const userDocumentsPath = await getDocumentsPath()
const clineHooksDir = path.join(userDocumentsPath, "Cline", "Hooks")
try {
await fs.mkdir(clineHooksDir, { recursive: true });
await fs.mkdir(clineHooksDir, { recursive: true })
} catch (_error) {
return path.join(os.homedir(), "Documents", "Cline", "Hooks"); // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
return path.join(os.homedir(), "Documents", "Cline", "Hooks") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine because we will fail gracefully with a path that does not exist
}
return clineHooksDir;
return clineHooksDir
}
/**
* Returns the global skills directory path (~/.cline/skills) without creating it.
*/
function getClineSkillsDirectoryPath(): string {
return path.join(getClineHomePath(), "skills");
return path.join(getClineHomePath(), "skills")
}
function getAgentSkillsDirectoryPath(): string {
return path.join(os.homedir(), ".agents", "skills");
return path.join(os.homedir(), ".agents", "skills")
}
/**
@@ -192,55 +182,41 @@ function getAgentSkillsDirectoryPath(): string {
* Creates the directory if it doesn't exist.
* This is the opinionated location for new global skills.
*/
export async function ensureAgentSkillsDirectoryExists(options: {
isGlobal: boolean;
workspacePath?: string;
}): Promise<string> {
export async function ensureAgentSkillsDirectoryExists(options: { isGlobal: boolean; workspacePath?: string }): Promise<string> {
const agentSkillsDir = options.isGlobal
? getAgentSkillsDirectoryPath()
: path.join(options.workspacePath ?? "", GlobalFileNames.agentsSkillsDir);
: path.join(options.workspacePath ?? "", GlobalFileNames.agentsSkillsDir)
try {
await fs.mkdir(agentSkillsDir, { recursive: true });
await fs.mkdir(agentSkillsDir, { recursive: true })
} catch (_error) {
// Fallback - return the path even if mkdir fails, we'll fail gracefully later
return agentSkillsDir;
return agentSkillsDir
}
return agentSkillsDir;
return agentSkillsDir
}
export type SkillsScanDirectory = {
path: string;
source: "project" | "global";
};
path: string
source: "project" | "global"
}
/**
* Returns the list of skills directories to scan without creating them.
* Order is project directories first, then global directories.
*/
export function getSkillsDirectoriesForScan(
cwd: string,
): SkillsScanDirectory[] {
export function getSkillsDirectoriesForScan(cwd: string): SkillsScanDirectory[] {
return [
{
path: path.join(cwd, GlobalFileNames.clineruleSkillsDir),
source: "project",
},
{ path: path.join(cwd, GlobalFileNames.clineruleSkillsDir), source: "project" },
{ path: path.join(cwd, GlobalFileNames.clineSkillsDir), source: "project" },
{
path: path.join(cwd, GlobalFileNames.claudeSkillsDir),
source: "project",
},
{
path: path.join(cwd, GlobalFileNames.agentsSkillsDir),
source: "project",
},
{ path: path.join(cwd, GlobalFileNames.claudeSkillsDir), source: "project" },
{ path: path.join(cwd, GlobalFileNames.agentsSkillsDir), source: "project" },
{ path: getClineSkillsDirectoryPath(), source: "global" },
{ path: getAgentSkillsDirectoryPath(), source: "global" },
];
]
}
export async function ensureSettingsDirectoryExists(): Promise<string> {
return getGlobalStorageDir("settings");
return getGlobalStorageDir("settings")
}
/**
@@ -248,93 +224,63 @@ export async function ensureSettingsDirectoryExists(): Promise<string> {
* @param settingsDirectoryPath Path to the settings directory
* @returns Path to the MCP settings file
*/
export async function getMcpSettingsFilePath(
settingsDirectoryPath: string,
): Promise<string> {
const mcpSettingsFilePath = path.join(
settingsDirectoryPath,
GlobalFileNames.mcpSettings,
);
const fileExists = await fileExistsAtPath(mcpSettingsFilePath);
export async function getMcpSettingsFilePath(settingsDirectoryPath: string): Promise<string> {
const mcpSettingsFilePath = path.join(settingsDirectoryPath, GlobalFileNames.mcpSettings)
const fileExists = await fileExistsAtPath(mcpSettingsFilePath)
if (!fileExists) {
await fs.writeFile(
mcpSettingsFilePath,
JSON.stringify({ mcpServers: {} }, null, 2),
);
await fs.writeFile(mcpSettingsFilePath, JSON.stringify({ mcpServers: {} }, null, 2))
}
return mcpSettingsFilePath;
return mcpSettingsFilePath
}
export async function getSavedApiConversationHistory(
taskId: string,
): Promise<ClineStorageMessage[]> {
const filePath = path.join(
await ensureTaskDirectoryExists(taskId),
GlobalFileNames.apiConversationHistory,
);
const fileExists = await fileExistsAtPath(filePath);
export async function getSavedApiConversationHistory(taskId: string): Promise<Anthropic.MessageParam[]> {
const filePath = path.join(await ensureTaskDirectoryExists(taskId), GlobalFileNames.apiConversationHistory)
const fileExists = await fileExistsAtPath(filePath)
if (fileExists) {
return JSON.parse(await fs.readFile(filePath, "utf8"));
return JSON.parse(await fs.readFile(filePath, "utf8"))
}
return [];
return []
}
export async function saveApiConversationHistory(
taskId: string,
apiConversationHistory: Anthropic.MessageParam[],
) {
export async function saveApiConversationHistory(taskId: string, apiConversationHistory: Anthropic.MessageParam[]) {
try {
if (apiConversationHistory.length > 0) {
const fileName = GlobalFileNames.apiConversationHistory;
const data = JSON.stringify(apiConversationHistory);
const fileName = GlobalFileNames.apiConversationHistory
const data = JSON.stringify(apiConversationHistory)
// Queue for remote sync without blocking
syncWorker().enqueue(taskId, fileName, data);
syncWorker().enqueue(taskId, fileName, data)
// Store locally
const filePath = path.join(
await ensureTaskDirectoryExists(taskId),
fileName,
);
await atomicWriteFile(filePath, data);
const filePath = path.join(await ensureTaskDirectoryExists(taskId), fileName)
await atomicWriteFile(filePath, data)
}
} catch (error) {
// in the off chance this fails, we don't want to stop the task
Logger.error("Failed to save API conversation history:", error);
Logger.error("Failed to save API conversation history:", error)
}
}
export async function getSavedClineMessages(
taskId: string,
): Promise<ClineMessage[]> {
const filePath = path.join(
await ensureTaskDirectoryExists(taskId),
GlobalFileNames.uiMessages,
);
export async function getSavedClineMessages(taskId: string): Promise<ClineMessage[]> {
const filePath = path.join(await ensureTaskDirectoryExists(taskId), GlobalFileNames.uiMessages)
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"));
return JSON.parse(await fs.readFile(filePath, "utf8"))
}
// check old location
const oldPath = path.join(
await ensureTaskDirectoryExists(taskId),
"claude_messages.json",
);
const oldPath = path.join(await ensureTaskDirectoryExists(taskId), "claude_messages.json")
if (await fileExistsAtPath(oldPath)) {
const data = JSON.parse(await fs.readFile(oldPath, "utf8"));
await fs.unlink(oldPath); // remove old file
return data;
const data = JSON.parse(await fs.readFile(oldPath, "utf8"))
await fs.unlink(oldPath) // remove old file
return data
}
return [];
return []
}
export async function saveClineMessages(
taskId: string,
uiMessages: ClineMessage[],
) {
export async function saveClineMessages(taskId: string, uiMessages: ClineMessage[]) {
try {
const taskDir = await ensureTaskDirectoryExists(taskId);
const filePath = path.join(taskDir, GlobalFileNames.uiMessages);
await atomicWriteFile(filePath, JSON.stringify(uiMessages));
const taskDir = await ensureTaskDirectoryExists(taskId)
const filePath = path.join(taskDir, GlobalFileNames.uiMessages)
await atomicWriteFile(filePath, JSON.stringify(uiMessages))
} catch (error) {
Logger.error("Failed to save ui messages:", error);
Logger.error("Failed to save ui messages:", error)
}
}
@@ -343,11 +289,9 @@ export async function saveClineMessages(
* This information is used for debugging and task portability.
* Returns metadata without timestamp - timestamp is added by EnvironmentContextTracker.
*/
export async function collectEnvironmentMetadata(): Promise<
Omit<EnvironmentMetadataEntry, "ts">
> {
export async function collectEnvironmentMetadata(): Promise<Omit<EnvironmentMetadataEntry, "ts">> {
try {
const hostVersion = await HostProvider.env.getHostVersion({});
const hostVersion = await HostProvider.env.getHostVersion({})
return {
os_name: os.platform(),
@@ -356,9 +300,9 @@ export async function collectEnvironmentMetadata(): Promise<
host_name: hostVersion.platform || "Unknown",
host_version: hostVersion.version || "Unknown",
cline_version: ExtensionRegistryInfo.version,
};
}
} catch (error) {
Logger.error("Failed to collect environment metadata:", error);
Logger.error("Failed to collect environment metadata:", error)
// Return fallback values if collection fails
return {
os_name: os.platform(),
@@ -367,245 +311,191 @@ export async function collectEnvironmentMetadata(): Promise<
host_name: "Unknown",
host_version: "Unknown",
cline_version: "Unknown",
};
}
}
}
export async function getTaskMetadata(taskId: string): Promise<TaskMetadata> {
const filePath = path.join(
await ensureTaskDirectoryExists(taskId),
GlobalFileNames.taskMetadata,
);
const filePath = path.join(await ensureTaskDirectoryExists(taskId), GlobalFileNames.taskMetadata)
try {
if (await fileExistsAtPath(filePath)) {
return JSON.parse(await fs.readFile(filePath, "utf8"));
return JSON.parse(await fs.readFile(filePath, "utf8"))
}
} catch (error) {
Logger.error("Failed to read task metadata:", error);
Logger.error("Failed to read task metadata:", error)
}
return { files_in_context: [], model_usage: [], environment_history: [] };
return { files_in_context: [], model_usage: [], environment_history: [] }
}
export async function saveTaskMetadata(taskId: string, metadata: TaskMetadata) {
try {
const taskDir = await ensureTaskDirectoryExists(taskId);
const filePath = path.join(taskDir, GlobalFileNames.taskMetadata);
await fs.writeFile(filePath, JSON.stringify(metadata, null, 2));
const taskDir = await ensureTaskDirectoryExists(taskId)
const filePath = path.join(taskDir, GlobalFileNames.taskMetadata)
await fs.writeFile(filePath, JSON.stringify(metadata, null, 2))
} catch (error) {
Logger.error("Failed to save task metadata:", error);
Logger.error("Failed to save task metadata:", error)
}
}
export async function ensureStateDirectoryExists(): Promise<string> {
return getGlobalStorageDir("state");
return getGlobalStorageDir("state")
}
export async function ensureCacheDirectoryExists(): Promise<string> {
return getGlobalStorageDir("cache");
return getGlobalStorageDir("cache")
}
export async function readMcpMarketplaceCatalogFromCache(): Promise<
McpMarketplaceCatalog | undefined
> {
export async function readMcpMarketplaceCatalogFromCache(): Promise<McpMarketplaceCatalog | undefined> {
try {
const mcpMarketplaceCatalogFilePath = path.join(
await ensureCacheDirectoryExists(),
GlobalFileNames.mcpMarketplaceCatalog,
);
const fileExists = await fileExistsAtPath(mcpMarketplaceCatalogFilePath);
const mcpMarketplaceCatalogFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.mcpMarketplaceCatalog)
const fileExists = await fileExistsAtPath(mcpMarketplaceCatalogFilePath)
if (fileExists) {
const fileContents = await fs.readFile(
mcpMarketplaceCatalogFilePath,
"utf8",
);
return JSON.parse(fileContents);
const fileContents = await fs.readFile(mcpMarketplaceCatalogFilePath, "utf8")
return JSON.parse(fileContents)
}
return undefined;
return undefined
} catch (error) {
Logger.error("Failed to read MCP marketplace catalog from cache:", error);
return undefined;
Logger.error("Failed to read MCP marketplace catalog from cache:", error)
return undefined
}
}
export async function writeMcpMarketplaceCatalogToCache(
catalog: McpMarketplaceCatalog,
): Promise<void> {
export async function writeMcpMarketplaceCatalogToCache(catalog: McpMarketplaceCatalog): Promise<void> {
try {
const mcpMarketplaceCatalogFilePath = path.join(
await ensureCacheDirectoryExists(),
GlobalFileNames.mcpMarketplaceCatalog,
);
await fs.writeFile(mcpMarketplaceCatalogFilePath, JSON.stringify(catalog));
const mcpMarketplaceCatalogFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.mcpMarketplaceCatalog)
await fs.writeFile(mcpMarketplaceCatalogFilePath, JSON.stringify(catalog))
} catch (error) {
Logger.error("Failed to write MCP marketplace catalog to cache:", error);
Logger.error("Failed to write MCP marketplace catalog to cache:", error)
}
}
async function getGlobalStorageDir(...subdirs: string[]) {
const fullPath = path.resolve(
HostProvider.get().globalStorageFsPath,
...subdirs,
);
await fs.mkdir(fullPath, { recursive: true });
return fullPath;
const fullPath = path.resolve(HostProvider.get().globalStorageFsPath, ...subdirs)
await fs.mkdir(fullPath, { recursive: true })
return fullPath
}
export async function getTaskHistoryStateFilePath(): Promise<string> {
return path.join(await ensureStateDirectoryExists(), "taskHistory.json");
return path.join(await ensureStateDirectoryExists(), "taskHistory.json")
}
export async function taskHistoryStateFileExists(): Promise<boolean> {
const filePath = await getTaskHistoryStateFilePath();
return fileExistsAtPath(filePath);
const filePath = await getTaskHistoryStateFilePath()
return fileExistsAtPath(filePath)
}
export async function readTaskHistoryFromState(): Promise<HistoryItem[]> {
try {
const filePath = await getTaskHistoryStateFilePath();
const filePath = await getTaskHistoryStateFilePath()
if (!(await fileExistsAtPath(filePath))) {
return [];
return []
}
const contents = await fs.readFile(filePath, "utf8");
const contents = await fs.readFile(filePath, "utf8")
try {
return JSON.parse(contents);
return JSON.parse(contents)
} catch (parseError) {
telemetryService.captureExtensionStorageError(
parseError,
"parseError_attemptingRecovery",
);
telemetryService.captureExtensionStorageError(parseError, "parseError_attemptingRecovery")
const result = await reconstructTaskHistory(false);
const result = await reconstructTaskHistory(false)
if (result && result.reconstructedTasks > 0) {
// Read the reconstructed file
const newContents = await fs.readFile(filePath, "utf8");
return JSON.parse(newContents);
const newContents = await fs.readFile(filePath, "utf8")
return JSON.parse(newContents)
}
// Recovery failed, all we can do is return an empty array or throw an error, thus preventing the app from starting up
// This will wipe out the taskHistory
return [];
return []
}
} catch (error) {
// Filesystem or other errors - throw them for the caller to handle
telemetryService.captureExtensionStorageError(
error,
"readTaskHistoryFromState",
);
throw error;
telemetryService.captureExtensionStorageError(error, "readTaskHistoryFromState")
throw error
}
}
export async function writeTaskHistoryToState(
items: HistoryItem[],
): Promise<void> {
export async function writeTaskHistoryToState(items: HistoryItem[]): Promise<void> {
try {
const filePath = await getTaskHistoryStateFilePath();
await atomicWriteFile(filePath, JSON.stringify(items));
const filePath = await getTaskHistoryStateFilePath()
await atomicWriteFile(filePath, JSON.stringify(items))
} catch (error) {
Logger.error("[Disk] Failed to write task history:", error);
throw error;
Logger.error("[Disk] Failed to write task history:", error)
throw error
}
}
export async function readTaskSettingsFromStorage(
taskId: string,
): Promise<Partial<GlobalState>> {
export async function readTaskSettingsFromStorage(taskId: string): Promise<Partial<GlobalState>> {
try {
const taskDirectoryFilePath = await ensureTaskDirectoryExists(taskId);
const settingsFilePath = path.join(taskDirectoryFilePath, "settings.json");
const taskDirectoryFilePath = await ensureTaskDirectoryExists(taskId)
const settingsFilePath = path.join(taskDirectoryFilePath, "settings.json")
if (await fileExistsAtPath(settingsFilePath)) {
const settingsContent = await fs.readFile(settingsFilePath, "utf8");
return JSON.parse(settingsContent);
const settingsContent = await fs.readFile(settingsFilePath, "utf8")
return JSON.parse(settingsContent)
}
// Return empty object if settings file doesn't exist (new task)
return {};
return {}
} catch (error) {
Logger.error("[Disk] Failed to read task settings:", error);
throw error;
Logger.error("[Disk] Failed to read task settings:", error)
throw error
}
}
export async function writeTaskSettingsToStorage(
taskId: string,
settings: Partial<Settings>,
) {
export async function writeTaskSettingsToStorage(taskId: string, settings: Partial<Settings>) {
try {
const taskDirectoryFilePath = await ensureTaskDirectoryExists(taskId);
const settingsFilePath = path.join(taskDirectoryFilePath, "settings.json");
const taskDirectoryFilePath = await ensureTaskDirectoryExists(taskId)
const settingsFilePath = path.join(taskDirectoryFilePath, "settings.json")
let existingSettings = {};
let existingSettings = {}
if (await fileExistsAtPath(settingsFilePath)) {
const existingSettingsContent = await fs.readFile(
settingsFilePath,
"utf8",
);
existingSettings = JSON.parse(existingSettingsContent);
const existingSettingsContent = await fs.readFile(settingsFilePath, "utf8")
existingSettings = JSON.parse(existingSettingsContent)
}
const updatedSettings = { ...existingSettings, ...settings };
await fs.writeFile(
settingsFilePath,
JSON.stringify(updatedSettings, null, 2),
);
const updatedSettings = { ...existingSettings, ...settings }
await fs.writeFile(settingsFilePath, JSON.stringify(updatedSettings, null, 2))
} catch (error) {
Logger.error("[Disk] Failed to write task settings:", error);
throw error;
Logger.error("[Disk] Failed to write task settings:", error)
throw error
}
}
export async function readRemoteConfigFromCache(
organizationId: string,
): Promise<RemoteConfig | undefined> {
export async function readRemoteConfigFromCache(organizationId: string): Promise<RemoteConfig | undefined> {
try {
const remoteConfigFilePath = path.join(
await ensureCacheDirectoryExists(),
GlobalFileNames.remoteConfig(organizationId),
);
const fileExists = await fileExistsAtPath(remoteConfigFilePath);
const remoteConfigFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.remoteConfig(organizationId))
const fileExists = await fileExistsAtPath(remoteConfigFilePath)
if (fileExists) {
const fileContents = await fs.readFile(remoteConfigFilePath, "utf8");
return JSON.parse(fileContents);
const fileContents = await fs.readFile(remoteConfigFilePath, "utf8")
return JSON.parse(fileContents)
}
return undefined;
return undefined
} catch (error) {
Logger.error("Failed to read remote config from cache:", error);
return undefined;
Logger.error("Failed to read remote config from cache:", error)
return undefined
}
}
export async function writeRemoteConfigToCache(
organizationId: string,
config: RemoteConfig,
): Promise<void> {
export async function writeRemoteConfigToCache(organizationId: string, config: RemoteConfig): Promise<void> {
try {
const remoteConfigFilePath = path.join(
await ensureCacheDirectoryExists(),
GlobalFileNames.remoteConfig(organizationId),
);
await fs.writeFile(remoteConfigFilePath, JSON.stringify(config));
const remoteConfigFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.remoteConfig(organizationId))
await fs.writeFile(remoteConfigFilePath, JSON.stringify(config))
} catch (error) {
Logger.error("Failed to write remote config to cache:", error);
Logger.error("Failed to write remote config to cache:", error)
}
}
export async function deleteRemoteConfigFromCache(
organizationId: string,
): Promise<void> {
export async function deleteRemoteConfigFromCache(organizationId: string): Promise<void> {
try {
const remoteConfigFilePath = path.join(
await ensureCacheDirectoryExists(),
GlobalFileNames.remoteConfig(organizationId),
);
const fileExists = await fileExistsAtPath(remoteConfigFilePath);
const remoteConfigFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.remoteConfig(organizationId))
const fileExists = await fileExistsAtPath(remoteConfigFilePath)
if (fileExists) {
await fs.unlink(remoteConfigFilePath);
await fs.unlink(remoteConfigFilePath)
}
} catch (error) {
Logger.error("Failed to delete remote config from cache:", error);
Logger.error("Failed to delete remote config from cache:", error)
}
}
@@ -614,11 +504,11 @@ export async function deleteRemoteConfigFromCache(
* Returns undefined if the directory doesn't exist.
*/
export async function getGlobalHooksDir(): Promise<string | undefined> {
const globalHooksDir = await ensureHooksDirectoryExists();
return (await isDirectory(globalHooksDir)) ? globalHooksDir : undefined;
const globalHooksDir = await ensureHooksDirectoryExists()
return (await isDirectory(globalHooksDir)) ? globalHooksDir : undefined
}
let runtimeHooksDir: string | undefined;
let runtimeHooksDir: string | undefined
/**
* Sets a runtime hooks directory, typically passed via the --hooks-dir CLI flag.
@@ -626,7 +516,7 @@ let runtimeHooksDir: string | undefined;
* when discovering hooks.
*/
export function setRuntimeHooksDir(dir: string | undefined): void {
runtimeHooksDir = dir;
runtimeHooksDir = dir
}
/**
@@ -641,24 +531,24 @@ export function setRuntimeHooksDir(dir: string | undefined): void {
* multi-root workspace may have multiple hooks directories.
*/
export async function getAllHooksDirs(): Promise<string[]> {
const hooksDirs: string[] = [];
const hooksDirs: string[] = []
// Add runtime hooks directory (set by --hooks-dir CLI flag)
if (runtimeHooksDir && (await isDirectory(runtimeHooksDir))) {
hooksDirs.push(runtimeHooksDir);
hooksDirs.push(runtimeHooksDir)
}
// Add global hooks directory (if it exists)
const globalHooksDir = await getGlobalHooksDir();
const globalHooksDir = await getGlobalHooksDir()
if (globalHooksDir) {
hooksDirs.push(globalHooksDir);
hooksDirs.push(globalHooksDir)
}
// Add workspace hooks directories
const workspaceHooksDirs = await getWorkspaceHooksDirs();
hooksDirs.push(...workspaceHooksDirs);
const workspaceHooksDirs = await getWorkspaceHooksDirs()
hooksDirs.push(...workspaceHooksDirs)
return hooksDirs;
return hooksDirs
}
/**
@@ -670,20 +560,17 @@ export async function getWorkspaceHooksDirs(): Promise<string[]> {
const workspaceRootPaths =
StateManager.get()
.getGlobalStateKey("workspaceRoots")
?.map((root) => root.path) || [];
?.map((root) => root.path) || []
return (
await Promise.all(
workspaceRootPaths.map(async (workspaceRootPath) => {
// Look for a .clinerules/hooks folder in this workspace root.
const candidate = path.join(
workspaceRootPath,
GlobalFileNames.hooksDir,
);
return (await isDirectory(candidate)) ? candidate : undefined;
const candidate = path.join(workspaceRootPath, GlobalFileNames.hooksDir)
return (await isDirectory(candidate)) ? candidate : undefined
}),
)
).filter((path): path is string => Boolean(path));
).filter((path): path is string => Boolean(path))
}
/**
@@ -701,20 +588,17 @@ export async function writeConversationHistoryJson(
apiConversationHistory: Anthropic.MessageParam[],
timestamp?: number,
): Promise<string> {
const taskDir = await ensureTaskDirectoryExists(taskId);
const fileTimestamp = timestamp ?? Date.now();
const tempFileName = `conversation_history_${fileTimestamp}.json`;
const tempFilePath = path.join(taskDir, tempFileName);
const taskDir = await ensureTaskDirectoryExists(taskId)
const fileTimestamp = timestamp ?? Date.now()
const tempFileName = `conversation_history_${fileTimestamp}.json`
const tempFilePath = path.join(taskDir, tempFileName)
try {
await atomicWriteFile(
tempFilePath,
JSON.stringify(apiConversationHistory, null, 2),
);
return tempFilePath;
await atomicWriteFile(tempFilePath, JSON.stringify(apiConversationHistory, null, 2))
return tempFilePath
} catch (error) {
Logger.error("Failed to write conversation history JSON for hook:", error);
throw error;
Logger.error("Failed to write conversation history JSON for hook:", error)
throw error
}
}
@@ -724,20 +608,14 @@ export async function writeConversationHistoryJson(
*
* @param filePath The path to the temporary file to delete
*/
export async function cleanupConversationHistoryFile(
filePath: string,
): Promise<void> {
export async function cleanupConversationHistoryFile(filePath: string): Promise<void> {
try {
if (await fileExistsAtPath(filePath)) {
await fs.unlink(filePath);
await fs.unlink(filePath)
}
} catch (error) {
// Silently handle errors - this is cleanup, not critical
Logger.debug(
"Failed to cleanup conversation history file:",
filePath,
error,
);
Logger.debug("Failed to cleanup conversation history file:", filePath, error)
}
}
@@ -756,59 +634,59 @@ export async function writeConversationHistoryText(
conversationHistory: Anthropic.MessageParam[],
timestamp?: number,
): Promise<string> {
const taskDir = await ensureTaskDirectoryExists(taskId);
const fileTimestamp = timestamp ?? Date.now();
const tempFileName = `conversation_history_${fileTimestamp}.txt`;
const tempFilePath = path.join(taskDir, tempFileName);
const taskDir = await ensureTaskDirectoryExists(taskId)
const fileTimestamp = timestamp ?? Date.now()
const tempFileName = `conversation_history_${fileTimestamp}.txt`
const tempFilePath = path.join(taskDir, tempFileName)
try {
// Build the formatted conversation history (excluding system prompt)
let fullContext = "=== CONVERSATION HISTORY ===\n\n";
let fullContext = "=== CONVERSATION HISTORY ===\n\n"
// Format each message in the conversation
for (let i = 0; i < conversationHistory.length; i++) {
const message = conversationHistory[i];
fullContext += `--- Message ${i + 1} (${message.role.toUpperCase()}) ---\n`;
const message = conversationHistory[i]
fullContext += `--- Message ${i + 1} (${message.role.toUpperCase()}) ---\n`
// Handle content which can be a string or array
if (typeof message.content === "string") {
fullContext += message.content;
fullContext += message.content
} else if (Array.isArray(message.content)) {
for (const block of message.content) {
if (block.type === "text") {
fullContext += block.text;
fullContext += block.text
} else if (block.type === "image") {
fullContext += `[IMAGE: ${block.source?.type || "unknown"}]`;
fullContext += `[IMAGE: ${block.source?.type || "unknown"}]`
} else if (block.type === "tool_use") {
fullContext += `[TOOL USE: ${block.name}]\n`;
fullContext += `Input: ${JSON.stringify(block.input, null, 2)}`;
fullContext += `[TOOL USE: ${block.name}]\n`
fullContext += `Input: ${JSON.stringify(block.input, null, 2)}`
} else if (block.type === "tool_result") {
fullContext += `[TOOL RESULT: ${block.tool_use_id}]\n`;
fullContext += `[TOOL RESULT: ${block.tool_use_id}]\n`
if (typeof block.content === "string") {
fullContext += block.content;
fullContext += block.content
} else if (Array.isArray(block.content)) {
for (const resultBlock of block.content) {
if (resultBlock.type === "text") {
fullContext += resultBlock.text;
fullContext += resultBlock.text
} else if (resultBlock.type === "image") {
fullContext += `[IMAGE]`;
fullContext += `[IMAGE]`
}
}
}
}
fullContext += "\n\n";
fullContext += "\n\n"
}
}
fullContext += "\n";
fullContext += "\n"
}
fullContext += "=== END OF CONTEXT ===\n";
fullContext += "=== END OF CONTEXT ===\n"
await atomicWriteFile(tempFilePath, fullContext);
return tempFilePath;
await atomicWriteFile(tempFilePath, fullContext)
return tempFilePath
} catch (error) {
Logger.error("Failed to write conversation history text for hook:", error);
throw error;
Logger.error("Failed to write conversation history text for hook:", error)
throw error
}
}
File diff suppressed because it is too large Load Diff
@@ -1,37 +1,33 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type { Anthropic } from "@anthropic-ai/sdk"
/**
* Filters out image blocks from messages since Claude Code doesn't support images.
* Replaces image blocks with text placeholders similar to how VSCode LM provider handles it.
*/
export function filterMessagesForClaudeCode(
messages: Anthropic.Messages.MessageParam[],
): Anthropic.Messages.MessageParam[] {
export function filterMessagesForClaudeCode(messages: Anthropic.Messages.MessageParam[]): Anthropic.Messages.MessageParam[] {
return messages.map((message) => {
// Handle simple string messages
if (typeof message.content === "string") {
return message;
return message
}
// Handle complex message structures
const filteredContent = message.content.map((block) => {
if (block.type === "image") {
// Replace image blocks with text placeholders
const sourceType = block.source?.type || "unknown";
const mediaType =
(block.source?.type === "base64" && block.source.media_type) ||
"unknown";
const sourceType = block.source?.type || "unknown"
const mediaType = block.source?.media_type || "unknown"
return {
type: "text" as const,
text: `[Image (${sourceType}): ${mediaType} not supported by Claude Code]`,
};
}
}
return block;
});
return block
})
return {
...message,
content: filteredContent,
};
});
}
})
}
-14
View File
@@ -1421,8 +1421,6 @@ export class McpHub {
}
public async addRemoteServer(serverName: string, serverUrl: string, transportType = "streamableHttp"): Promise<McpServer[]> {
// Set flag to prevent file watcher from triggering during our update
this.isUpdatingClineSettings = true
try {
const settings = await this.readAndValidateMcpSettingsFile()
if (!settings) {
@@ -1470,11 +1468,6 @@ export class McpHub {
} catch (error) {
Logger.error("Failed to add remote MCP server:", error)
throw error
} finally {
// Clear flag after a delay to ensure file watcher event has been processed
setTimeout(() => {
this.isUpdatingClineSettings = false
}, 300)
}
}
@@ -1484,8 +1477,6 @@ export class McpHub {
* @returns Array of remaining MCP servers
*/
public async deleteServerRPC(serverName: string): Promise<McpServer[]> {
// Set flag to prevent file watcher from triggering during our update
this.isUpdatingClineSettings = true
try {
// Clear OAuth data BEFORE removing from config (while we still have the connection/URL)
await this.clearOAuthForConnection(serverName)
@@ -1513,11 +1504,6 @@ export class McpHub {
} catch (error) {
Logger.error(`Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`)
throw error
} finally {
// Clear flag after a delay to ensure file watcher event has been processed
setTimeout(() => {
this.isUpdatingClineSettings = false
}, 300)
}
}
@@ -1,133 +0,0 @@
import { afterEach, beforeEach, describe, it } from "mocha"
import "should"
import * as diskModule from "@core/storage/disk"
import fs from "fs/promises"
import os from "os"
import path from "path"
import sinon from "sinon"
import { McpHub } from "../McpHub"
// Regression tests for McpHub.deleteServerRPC(): deleting one server must not
// empty the list. Tests bypass the constructor's watcher via
// Object.create(McpHub.prototype), matching McpHub.callTool.test.ts.
type FakeConnection = {
server: { name: string; config: string; status: string; disabled: boolean }
client: Record<string, unknown>
transport: Record<string, unknown>
}
function makeConnection(name: string): FakeConnection {
return {
server: {
name,
config: JSON.stringify({ type: "stdio", command: "test", timeout: 60 }),
status: "connected",
disabled: false,
},
client: {},
transport: {},
}
}
describe("McpHub.deleteServerRPC", () => {
let sandbox: sinon.SinonSandbox
let tempDir: string
let settingsPath: string
let hub: McpHub
const writeSettings = async (mcpServers: Record<string, unknown>) => {
await fs.writeFile(settingsPath, JSON.stringify({ mcpServers }, null, 2))
}
beforeEach(async () => {
sandbox = sinon.createSandbox()
tempDir = path.join(os.tmpdir(), `mcp-delete-test-${Date.now()}-${Math.random().toString(36).slice(2)}`)
await fs.mkdir(tempDir, { recursive: true })
settingsPath = path.join(tempDir, "cline_mcp_settings.json")
sandbox.stub(diskModule, "getMcpSettingsFilePath").resolves(settingsPath)
hub = Object.create(McpHub.prototype) as McpHub
;(hub as any).getSettingsDirectoryPath = async () => tempDir
;(hub as any).isUpdatingClineSettings = false
;(hub as any).connections = [makeConnection("alpha"), makeConnection("beta")]
// clearOAuthForConnection touches the OAuth manager; stub it out.
sandbox.stub(hub as any, "clearOAuthForConnection").resolves()
// updateServerConnectionsRPC normally opens real transports; reproduce only
// the relevant behavior: drop connections no longer present in the new set.
sandbox.stub(hub as any, "updateServerConnectionsRPC").callsFake((...args: unknown[]) => {
const newServers = args[0] as Record<string, unknown>
;(hub as any).connections = (hub as any).connections.filter((c: FakeConnection) =>
Object.hasOwn(newServers, c.server.name),
)
return Promise.resolve()
})
})
afterEach(async () => {
sandbox.restore()
try {
await fs.rm(tempDir, { recursive: true, force: true })
} catch {
// Ignore cleanup errors
}
})
it("returns the remaining servers (not an empty list) after deleting one", async () => {
await writeSettings({ alpha: { type: "stdio", command: "a" }, beta: { type: "stdio", command: "b" } })
const result = await hub.deleteServerRPC("alpha")
result.should.have.length(1)
result.map((s) => s.name).should.deepEqual(["beta"])
})
it("persists the remaining server to the settings file", async () => {
await writeSettings({ alpha: { type: "stdio", command: "a" }, beta: { type: "stdio", command: "b" } })
await hub.deleteServerRPC("alpha")
const persisted = JSON.parse(await fs.readFile(settingsPath, "utf-8"))
Object.keys(persisted.mcpServers).should.deepEqual(["beta"])
})
it("guards the write with isUpdatingClineSettings so the watcher skips its own event", async () => {
const clock = sandbox.useFakeTimers()
await writeSettings({ alpha: { type: "stdio", command: "a" }, beta: { type: "stdio", command: "b" } })
// Capture the flag at the moment the settings file is written.
let flagDuringWrite: boolean | undefined
const realWriteFile = fs.writeFile.bind(fs)
sandbox.stub(fs, "writeFile").callsFake((...args: unknown[]) => {
flagDuringWrite = (hub as any).isUpdatingClineSettings
return (realWriteFile as (...a: unknown[]) => Promise<void>)(...args)
})
await hub.deleteServerRPC("alpha")
// True during the write and still true immediately after (cleared on a timer).
flagDuringWrite!.should.be.true()
;(hub as any).isUpdatingClineSettings.should.be.true()
// The flag is cleared on a 300ms timer so external edits resume.
clock.tick(300)
;(hub as any).isUpdatingClineSettings.should.be.false()
})
it("throws and still clears the guard when the server is not found", async () => {
const clock = sandbox.useFakeTimers()
await writeSettings({ beta: { type: "stdio", command: "b" } })
let threw: Error | undefined
try {
await hub.deleteServerRPC("missing")
} catch (err) {
threw = err as Error
}
;(threw === undefined).should.be.false()
threw!.message.should.match(/not found in MCP configuration/)
clock.tick(300)
;(hub as any).isUpdatingClineSettings.should.be.false()
})
})
+132 -256
View File
@@ -144,22 +144,6 @@ export const CLAUDE_OPUS_1M_TIERS = [
cacheReadsPrice: 1.0,
},
]
export const CLAUDE_FABLE_1M_TIERS = [
{
contextWindow: 200000,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
},
{
contextWindow: Number.MAX_SAFE_INTEGER,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
},
]
export interface HicapCompatibleModelInfo extends ModelInfo {
temperature?: number
@@ -334,29 +318,6 @@ export const anthropicModels = {
cacheReadsPrice: 0.5,
tiers: CLAUDE_OPUS_1M_TIERS,
},
"claude-fable-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
},
"claude-fable-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
tiers: CLAUDE_FABLE_1M_TIERS,
},
"claude-opus-4-7": {
maxTokens: 128_000,
contextWindow: 200_000,
@@ -544,17 +505,6 @@ export const claudeCodeModels = {
supportsImages: false,
supportsPromptCache: false,
},
"claude-fable-5": {
...anthropicModels["claude-fable-5"],
contextWindow: 200_000,
supportsImages: false,
supportsPromptCache: false,
},
"claude-fable-5[1m]": {
...anthropicModels["claude-fable-5:1m"],
supportsImages: false,
supportsPromptCache: false,
},
"claude-opus-4-7": {
...anthropicModels["claude-opus-4-7"],
contextWindow: 200_000,
@@ -735,31 +685,6 @@ export const bedrockModels = {
cacheReadsPrice: 0.5,
tiers: CLAUDE_OPUS_1M_TIERS,
},
"anthropic.claude-fable-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
supportsGlobalEndpoint: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
},
"anthropic.claude-fable-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
supportsGlobalEndpoint: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
tiers: CLAUDE_FABLE_1M_TIERS,
},
"anthropic.claude-opus-4-7": {
maxTokens: 128_000,
contextWindow: 200_000,
@@ -997,7 +922,6 @@ export const openRouterClaudeSonnet461mModelId = `anthropic/claude-sonnet-4.6${C
export const openRouterClaudeOpus461mModelId = `anthropic/claude-opus-4.6${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeOpus471mModelId = `anthropic/claude-opus-4.7${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeOpus481mModelId = `anthropic/claude-opus-4.8${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeFable51mModelId = `anthropic/claude-fable-5${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterDefaultModelInfo: ModelInfo = {
maxTokens: 64_000,
contextWindow: 200_000,
@@ -1283,31 +1207,6 @@ export const vertexModels = {
supportsReasoning: true,
tiers: CLAUDE_OPUS_1M_TIERS,
},
"claude-fable-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsGlobalEndpoint: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
supportsReasoning: true,
},
"claude-fable-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsGlobalEndpoint: true,
inputPrice: 10,
outputPrice: 50,
cacheWritesPrice: 12.5,
cacheReadsPrice: 1,
supportsReasoning: true,
tiers: CLAUDE_FABLE_1M_TIERS,
},
"claude-opus-4-7": {
maxTokens: 128_000,
contextWindow: 200_000,
@@ -4101,6 +4000,33 @@ export const xaiModels = {
export type SambanovaModelId = keyof typeof sambanovaModels
export const sambanovaDefaultModelId: SambanovaModelId = "Meta-Llama-3.3-70B-Instruct"
export const sambanovaModels = {
"DeepSeek-R1-0528": {
maxTokens: 7168,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.6,
inputPrice: 5.0,
outputPrice: 7.0,
},
"DeepSeek-R1-Distill-Llama-70B": {
maxTokens: 4096,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.6,
inputPrice: 0.7,
outputPrice: 1.4,
},
"DeepSeek-V3-0324": {
maxTokens: 7168,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.3,
inputPrice: 3.0,
outputPrice: 4.5,
},
"DeepSeek-V3.1": {
maxTokens: 7168,
contextWindow: 131072,
@@ -4110,9 +4036,9 @@ export const sambanovaModels = {
inputPrice: 3.0,
outputPrice: 4.5,
},
"DeepSeek-V3.2": {
"DeepSeek-V3.1-Terminus": {
maxTokens: 7168,
contextWindow: 32768,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.6,
@@ -4128,6 +4054,15 @@ export const sambanovaModels = {
inputPrice: 0.63,
outputPrice: 1.8,
},
"Meta-Llama-3.1-8B-Instruct": {
maxTokens: 4096,
contextWindow: 16384,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.6,
inputPrice: 0.1,
outputPrice: 0.2,
},
"Meta-Llama-3.3-70B-Instruct": {
maxTokens: 3072,
contextWindow: 131072,
@@ -4137,14 +4072,32 @@ export const sambanovaModels = {
inputPrice: 0.6,
outputPrice: 1.2,
},
"MiniMax-M2.7": {
maxTokens: 196608,
contextWindow: 196608,
"MiniMax-M2.5": {
maxTokens: 16384,
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: false,
temperature: 1.0,
inputPrice: 0.6,
outputPrice: 2.4,
inputPrice: 0.3,
outputPrice: 1.2,
},
"Qwen3-235B": {
maxTokens: 4096,
contextWindow: 65536,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.7,
inputPrice: 0.4,
outputPrice: 0.8,
},
"Qwen3-32B": {
maxTokens: 4096,
contextWindow: 32768,
supportsImages: false,
supportsPromptCache: false,
temperature: 0.6,
inputPrice: 0.4,
outputPrice: 0.8,
},
} as const satisfies Record<string, ModelInfo>
@@ -5105,156 +5058,107 @@ export const mainlandZAiModels = {
// Fireworks AI
export type FireworksModelId = keyof typeof fireworksModels
export const fireworksDefaultModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2p6"
export const fireworksDefaultModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2p5"
export const fireworksModels = {
"accounts/fireworks/models/kimi-k2p7-code": {
maxTokens: 262000,
contextWindow: 262000,
"accounts/fireworks/models/kimi-k2p5": {
maxTokens: 16384,
contextWindow: 262144,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.95,
outputPrice: 4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.19,
inputPrice: 0.6,
outputPrice: 3,
cacheWritesPrice: 0.6,
cacheReadsPrice: 0.1,
description:
"Moonshot's latest open coding model. Kimi K2.7 Code unifies vision and text, thinking and non-thinking modes, and single-agent and multi-agent execution.",
"Moonshot's flagship open agentic model. Kimi K2.5 unifies vision and text, thinking and non-thinking modes, and single-agent and multi-agent execution.",
},
"accounts/fireworks/models/kimi-k2p6": {
maxTokens: 262000,
contextWindow: 262000,
"accounts/fireworks/models/qwen3-vl-30b-a3b-thinking": {
maxTokens: 32768,
contextWindow: 262144,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.95,
outputPrice: 4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.16,
inputPrice: 0.15,
outputPrice: 0.6,
cacheWritesPrice: 0.15,
cacheReadsPrice: 0.07,
description:
"Moonshot's latest open agentic model. Kimi K2.6 unifies vision and text, thinking and non-thinking modes, and single-agent and multi-agent execution.",
"Reasoning-enabled Qwen3-VL model with strong multimodal understanding, long context support, and function calling.",
},
"accounts/fireworks/routers/kimi-k2p6-turbo": {
maxTokens: 262000,
contextWindow: 262000,
"accounts/fireworks/models/qwen3-vl-30b-a3b-instruct": {
maxTokens: 32768,
contextWindow: 262144,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.15,
outputPrice: 0.6,
description: "Qwen3-VL instruct model with strong multimodal reasoning, long context support, and function calling.",
},
"accounts/fireworks/models/deepseek-v3p2": {
maxTokens: 16384,
contextWindow: 163840,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 2,
outputPrice: 8,
cacheWritesPrice: 0,
inputPrice: 0.56,
outputPrice: 1.68,
cacheWritesPrice: 0.56,
cacheReadsPrice: 0.28,
description: "DeepSeek V3.2 model tuned for high computational efficiency and strong reasoning and agent performance.",
},
"accounts/fireworks/models/glm-4p7": {
maxTokens: 16384,
contextWindow: 202752,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.6,
outputPrice: 2.2,
cacheWritesPrice: 0.6,
cacheReadsPrice: 0.3,
description:
"Kimi K2.6 Turbo router for high-performance agentic workloads with vision and text reasoning.",
description: "GLM-4.7 is a next-generation general-purpose model optimized for coding, reasoning, and agentic workflows.",
},
"accounts/fireworks/routers/kimi-k2p7-code-fast": {
maxTokens: 262000,
contextWindow: 262000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 1.9,
outputPrice: 8,
cacheWritesPrice: 0,
cacheReadsPrice: 0.38,
description:
"Kimi K2.7 Code Fast router for high-performance coding workloads with vision and text reasoning.",
},
"accounts/fireworks/models/deepseek-v4-flash": {
maxTokens: 384000,
contextWindow: 1000000,
"accounts/fireworks/models/glm-5": {
maxTokens: 16384,
contextWindow: 202752,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.14,
outputPrice: 0.28,
cacheWritesPrice: 0,
cacheReadsPrice: 0.03,
description:
"DeepSeek V4 Flash is a fast, cost-efficient reasoning model with a 1M context window and strong tool-use capabilities.",
inputPrice: 1.0,
outputPrice: 3.2,
cacheWritesPrice: 1.0,
cacheReadsPrice: 0.2,
description: "GLM-5 is Z.ai's flagship reasoning model for complex systems engineering and long-horizon agentic tasks.",
},
"accounts/fireworks/models/deepseek-v4-pro": {
maxTokens: 384000,
contextWindow: 1000000,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 1.74,
outputPrice: 3.48,
cacheWritesPrice: 0,
cacheReadsPrice: 0.145,
description:
"DeepSeek V4 Pro is a flagship reasoning model with a 1M context window, advanced structured output, and agentic performance.",
},
"accounts/fireworks/models/glm-5p1": {
maxTokens: 131072,
contextWindow: 202800,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 1.4,
outputPrice: 4.4,
cacheWritesPrice: 0,
cacheReadsPrice: 0.26,
description: "GLM 5.1 is a next-generation general-purpose model optimized for coding, reasoning, and agentic workflows.",
},
"accounts/fireworks/routers/glm-5p1-fast": {
maxTokens: 131072,
contextWindow: 202800,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 2.8,
outputPrice: 8.8,
cacheWritesPrice: 0,
cacheReadsPrice: 0.52,
description: "GLM 5.1 Fast router for high-throughput coding, reasoning, and agentic workflows.",
},
"accounts/fireworks/models/minimax-m3": {
maxTokens: 512000,
contextWindow: 512000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 0.3,
outputPrice: 1.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0.06,
description: "MiniMax M3 is built for state-of-the-art coding, agentic tool use, and long-context multimodal tasks.",
},
"accounts/fireworks/models/minimax-m2p7": {
maxTokens: 196608,
"accounts/fireworks/models/minimax-m2p5": {
maxTokens: 16384,
contextWindow: 196608,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.3,
outputPrice: 1.2,
cacheWritesPrice: 0,
cacheReadsPrice: 0.06,
description: "MiniMax M2.7 is tuned for strong real-world performance across coding, agent-driven, and workflow-heavy tasks.",
cacheWritesPrice: 0.3,
cacheReadsPrice: 0.03,
description: "MiniMax M2.5 is built for state-of-the-art coding, agentic tool use.",
},
"accounts/fireworks/models/qwen3p7-plus": {
maxTokens: 262144,
contextWindow: 262144,
supportsImages: true,
"accounts/fireworks/models/minimax-m2p1": {
maxTokens: 16384,
contextWindow: 196608,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.4,
outputPrice: 1.6,
cacheWritesPrice: 0,
cacheReadsPrice: 0.08,
description: "Qwen 3.7 Plus with strong multimodal reasoning, long context support, and function calling.",
inputPrice: 0.3,
outputPrice: 1.2,
cacheWritesPrice: 0.3,
cacheReadsPrice: 0.03,
description:
"MiniMax M2.1 is tuned for strong real-world performance across coding, agent-driven, and workflow-heavy tasks.",
},
"accounts/fireworks/models/gpt-oss-120b": {
maxTokens: 32768,
maxTokens: 16384,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.15,
outputPrice: 0.6,
cacheWritesPrice: 0,
cacheReadsPrice: 0.015,
description: "OpenAI GPT OSS 120B open-weight model for production and high-reasoning use cases.",
},
"accounts/fireworks/models/gpt-oss-20b": {
maxTokens: 32768,
contextWindow: 131072,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.07,
outputPrice: 0.3,
cacheWritesPrice: 0,
cacheReadsPrice: 0.035,
description: "OpenAI GPT OSS 20B open-weight model for efficient production and reasoning use cases.",
cacheWritesPrice: 0.15,
cacheReadsPrice: 0.01,
description: "OpenAI gpt-oss-120b open-weight model for production and high-reasoning use cases.",
},
} as const satisfies Record<string, ModelInfo>
@@ -5293,34 +5197,6 @@ export const qwenCodeDefaultModelId: QwenCodeModelId = "qwen3-coder-plus"
export type MinimaxModelId = keyof typeof minimaxModels
export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M2.7"
export const minimaxModels = {
"MiniMax-M3": {
maxTokens: 32_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 0.6,
outputPrice: 2.4,
cacheWritesPrice: 0.6,
cacheReadsPrice: 0.12,
tiers: [
{
contextWindow: 512_000,
inputPrice: 0.6,
outputPrice: 2.4,
cacheWritesPrice: 0.6,
cacheReadsPrice: 0.12,
},
{
contextWindow: Number.MAX_SAFE_INTEGER,
inputPrice: 1.2,
outputPrice: 4.8,
cacheWritesPrice: 1.2,
cacheReadsPrice: 0.24,
},
],
description: "Latest M-series model for coding, agentic reasoning, tool use, and long-context multimodal tasks",
},
"MiniMax-M2.7": {
maxTokens: 128_000,
contextWindow: 192_000,
+44 -87
View File
@@ -1,84 +1,68 @@
import type { Anthropic } from "@anthropic-ai/sdk";
import type { ClineMessageMetricsInfo, ClineMessageModelInfo } from "./metrics";
import { Anthropic } from "@anthropic-ai/sdk"
import { ClineMessageMetricsInfo, ClineMessageModelInfo } from "./metrics"
export type ClinePromptInputContent = string;
export type ClinePromptInputContent = string
export type ClineMessageRole = "user" | "assistant";
export type ClineMessageRole = "user" | "assistant"
export interface ClineReasoningDetailParam {
type: "reasoning.text" | string;
text: string;
signature: string;
format: "anthropic-claude-v1" | string;
index: number;
type: "reasoning.text" | string
text: string
signature: string
format: "anthropic-claude-v1" | string
index: number
}
interface ClineSharedMessageParam {
// The id of the response that the block belongs to
call_id?: string;
call_id?: string
}
export const REASONING_DETAILS_PROVIDERS = ["cline", "openrouter"];
export const REASONING_DETAILS_PROVIDERS = ["cline", "openrouter"]
/**
* An extension of Anthropic.MessageParam that includes Cline-specific fields: reasoning_details.
* This ensures backward compatibility where the messages were stored in Anthropic format with additional
* fields unknown to Anthropic SDK.
*/
export interface ClineTextContentBlock
extends Anthropic.TextBlockParam,
ClineSharedMessageParam {
export interface ClineTextContentBlock extends Anthropic.TextBlockParam, ClineSharedMessageParam {
// reasoning_details only exists for providers listed in REASONING_DETAILS_PROVIDERS
reasoning_details?: ClineReasoningDetailParam[];
reasoning_details?: ClineReasoningDetailParam[]
// Thought Signature associates with Gemini
signature?: string;
signature?: string
}
export interface ClineImageContentBlock
extends Anthropic.ImageBlockParam,
ClineSharedMessageParam {}
export interface ClineImageContentBlock extends Anthropic.ImageBlockParam, ClineSharedMessageParam {}
export interface ClineDocumentContentBlock
extends Anthropic.DocumentBlockParam,
ClineSharedMessageParam {}
export interface ClineDocumentContentBlock extends Anthropic.DocumentBlockParam, ClineSharedMessageParam {}
export interface ClineUserToolResultContentBlock
extends Anthropic.ToolResultBlockParam,
ClineSharedMessageParam {}
export interface ClineUserToolResultContentBlock extends Anthropic.ToolResultBlockParam, ClineSharedMessageParam {}
/**
* Assistant only content types
*/
export interface ClineAssistantToolUseBlock
extends Anthropic.ToolUseBlockParam,
ClineSharedMessageParam {
export interface ClineAssistantToolUseBlock extends Anthropic.ToolUseBlockParam, ClineSharedMessageParam {
// reasoning_details only exists for providers listed in REASONING_DETAILS_PROVIDERS
reasoning_details?: unknown[] | ClineReasoningDetailParam[];
reasoning_details?: unknown[] | ClineReasoningDetailParam[]
// Thought Signature associates with Gemini
signature?: string;
signature?: string
}
export interface ClineAssistantThinkingBlock
extends Anthropic.ThinkingBlock,
ClineSharedMessageParam {
export interface ClineAssistantThinkingBlock extends Anthropic.ThinkingBlock, ClineSharedMessageParam {
// The summary items returned by OpenAI response API
// The reasoning details that will be moved to the text block when finalized
summary?: unknown[] | ClineReasoningDetailParam[];
summary?: unknown[] | ClineReasoningDetailParam[]
}
export interface ClineAssistantRedactedThinkingBlock
extends Anthropic.RedactedThinkingBlockParam,
ClineSharedMessageParam {}
export interface ClineAssistantRedactedThinkingBlock extends Anthropic.RedactedThinkingBlockParam, ClineSharedMessageParam {}
export type ClineToolResponseContent =
| ClinePromptInputContent
| Array<ClineTextContentBlock | ClineImageContentBlock>;
export type ClineToolResponseContent = ClinePromptInputContent | Array<ClineTextContentBlock | ClineImageContentBlock>
export type ClineUserContent =
| ClineTextContentBlock
| ClineImageContentBlock
| ClineDocumentContentBlock
| ClineUserToolResultContentBlock;
| ClineUserToolResultContentBlock
export type ClineAssistantContent =
| ClineTextContentBlock
@@ -86,9 +70,9 @@ export type ClineAssistantContent =
| ClineDocumentContentBlock
| ClineAssistantToolUseBlock
| ClineAssistantThinkingBlock
| ClineAssistantRedactedThinkingBlock;
| ClineAssistantRedactedThinkingBlock
export type ClineContent = ClineUserContent | ClineAssistantContent;
export type ClineContent = ClineUserContent | ClineAssistantContent
/**
* An extension of Anthropic.MessageParam that includes Cline-specific fields.
@@ -100,24 +84,24 @@ export interface ClineStorageMessage extends Anthropic.MessageParam {
/**
* Response ID associated with this message
*/
id?: string;
role: ClineMessageRole;
content: ClinePromptInputContent | ClineContent[];
id?: string
role: ClineMessageRole
content: ClinePromptInputContent | ClineContent[]
/**
* NOTE: model information used when generating this message.
* Internal use for message conversion only.
* MUST be removed before sending message to any LLM provider.
*/
modelInfo?: ClineMessageModelInfo;
modelInfo?: ClineMessageModelInfo
/**
* LLM operational and performance metrics for this message
* Includes token counts, costs.
*/
metrics?: ClineMessageMetricsInfo;
metrics?: ClineMessageMetricsInfo
/**
* Timestamp of when the message was created
*/
ts?: number;
ts?: number
}
/**
@@ -128,50 +112,23 @@ export function convertClineStorageToAnthropicMessage(
clineMessage: ClineStorageMessage,
provider = "anthropic",
): Anthropic.MessageParam {
const { role, content } = clineMessage;
const { role, content } = clineMessage
// Handle string content - fast path
if (typeof content === "string") {
return { role, content };
return { role, content }
}
// Removes thinking block that has no signature (invalid thinking block that's incompatible with Anthropic API)
const filteredContent = content.filter(
(b) => b.type !== "thinking" || !!b.signature,
);
const filteredContent = content.filter((b) => b.type !== "thinking" || !!b.signature)
// Handle array content - strip Cline-specific fields for non-reasoning_details providers
const shouldCleanContent = !REASONING_DETAILS_PROVIDERS.includes(provider);
const shouldCleanContent = !REASONING_DETAILS_PROVIDERS.includes(provider)
const cleanedContent = shouldCleanContent
? filteredContent.map(cleanContentBlock)
: (filteredContent as Anthropic.MessageParam["content"]);
: (filteredContent as Anthropic.MessageParam["content"])
return { role, content: cleanedContent };
}
/**
* Cline stores images as base64, so an image block's source is always a base64 source.
* The Anthropic SDK types the source as a Base64ImageSource | URLImageSource union, so this
* narrows to the base64 variant for the transform layer. URL sources are not produced by Cline,
* so they degrade to empty values rather than throwing.
*/
export function getBase64ImageSource(
source: Anthropic.ImageBlockParam["source"],
): { mediaType: string; data: string } {
if (source.type === "base64") {
return { mediaType: source.media_type, data: source.data };
}
return { mediaType: "", data: "" };
}
/**
* Builds a base64 data URL from an image block's source. See getBase64ImageSource.
*/
export function getImageDataUrl(
source: Anthropic.ImageBlockParam["source"],
): string {
const { mediaType, data } = getBase64ImageSource(source);
return `data:${mediaType};base64,${data}`;
return { role, content: cleanedContent }
}
/**
@@ -183,19 +140,19 @@ export function cleanContentBlock(block: ClineContent): Anthropic.ContentBlock {
"reasoning_details" in block ||
"call_id" in block ||
"summary" in block ||
(block.type !== "thinking" && "signature" in block);
(block.type !== "thinking" && "signature" in block)
if (!hasClineFields) {
return block as Anthropic.ContentBlock;
return block as Anthropic.ContentBlock
}
// Removes Cline-specific fields & the signature field that's added for Gemini.
const { reasoning_details, call_id, summary, ...rest } = block as any;
const { reasoning_details, call_id, summary, ...rest } = block as any
// Remove signature from non-thinking blocks that were added for Gemini
if (block.type !== "thinking" && rest.signature) {
rest.signature = undefined;
rest.signature = undefined
}
return rest satisfies Anthropic.ContentBlock;
return rest satisfies Anthropic.ContentBlock
}
@@ -12,6 +12,8 @@ export enum FeatureFlag {
// Feature flag for DB-backed welcome banners (What's New modal)
// When off, hardcoded welcome items are shown instead
REMOTE_WELCOME_BANNERS = "remote-welcome-banners",
// Feature flag for upstream Cline recommended model cards
CLINE_RECOMMENDED_MODELS_UPSTREAM = "cline-recommended-models-upstream",
// Rollout flag for Cline provider model sourcing:
// off => OpenRouter model list, on => Cline endpoint model list.
EXTENSION_CLINE_MODELS_ENDPOINT = "extension_cline_models_endpoint",
@@ -26,6 +28,7 @@ export const FeatureFlagDefaultValue: Partial<Record<FeatureFlag, FeatureFlagPay
[FeatureFlag.REMOTE_BANNERS]: process.env.E2E_TEST === "true" || process.env.IS_DEV === "true",
[FeatureFlag.EXTENSION_REMOTE_BANNERS_TTL]: 24 * 60 * 60 * 1000,
[FeatureFlag.REMOTE_WELCOME_BANNERS]: process.env.E2E_TEST === "true" || process.env.IS_DEV === "true",
[FeatureFlag.CLINE_RECOMMENDED_MODELS_UPSTREAM]: false,
[FeatureFlag.EXTENSION_CLINE_MODELS_ENDPOINT]: false,
[FeatureFlag.OPENAI_RESPONSES_WEBSOCKET_MODE]: false,
}
@@ -12,10 +12,7 @@ export function isClaudeOpusAdaptiveThinkingModel(modelId?: string): boolean {
const id = modelId.toLowerCase()
const adaptiveVersions = ["4-6", "4.6", "4-7", "4.7", "4-8", "4.8"]
return (
id.includes("claude-fable-5") ||
adaptiveVersions.some((version) => id.includes(`claude-opus-${version}`) || id.includes(`claude-${version}-opus`))
)
return adaptiveVersions.some((version) => id.includes(`claude-opus-${version}`) || id.includes(`claude-${version}-opus`))
}
export function resolveClaudeOpusAdaptiveThinking(
@@ -2,8 +2,6 @@ export const E2E_REGISTERED_MOCK_ENDPOINTS = {
"/api/v1": {
GET: [
"/generation",
"/ai/cline/models",
"/ai/cline/recommended-models",
"/organizations/{orgId}/balance",
"/organizations/{orgId}/members/{memberId}/usages",
"/organizations/{orgId}/api-keys",
@@ -82,65 +80,3 @@ export const E2E_MOCK_API_RESPONSES = {
REPLACE_REQUEST: replace_in_file,
EDIT_REQUEST: edit_request,
}
export const E2E_MOCK_CLINE_RECOMMENDED_MODELS = {
free: [
{
id: "z-ai/glm-5",
name: "z-ai/glm-5",
description: "Free model for e2e onboarding",
tags: [],
},
],
recommended: [
{
id: "anthropic/claude-sonnet-4.6",
name: "anthropic/claude-sonnet-4.6",
description: "Recommended model for e2e onboarding",
tags: ["BEST"],
},
],
}
export const E2E_MOCK_CLINE_MODELS = [
{
id: "z-ai/glm-5",
name: "z-ai/glm-5",
description: "Free model for e2e onboarding",
context_length: 131_072,
top_provider: {
max_completion_tokens: 8_192,
context_length: 131_072,
is_moderated: false,
},
architecture: {
modality: "text->text",
},
pricing: {
prompt: "0",
completion: "0",
},
supported_parameters: [],
},
{
id: "anthropic/claude-sonnet-4.6",
name: "anthropic/claude-sonnet-4.6",
description: "Recommended model for e2e onboarding",
context_length: 200_000,
top_provider: {
max_completion_tokens: 64_000,
context_length: 200_000,
is_moderated: false,
},
architecture: {
modality: "text->text",
},
pricing: {
prompt: "0.000003",
completion: "0.000015",
input_cache_read: "0.0000003",
input_cache_write: "0.00000375",
},
supported_parameters: ["include_reasoning"],
},
]
@@ -2,12 +2,7 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse }
import type { Socket } from "node:net"
import { v4 as uuidv4 } from "uuid"
import type { BalanceResponse, OrganizationBalanceResponse, UserResponse } from "../../../../shared/ClineAccount"
import {
E2E_MOCK_API_RESPONSES,
E2E_MOCK_CLINE_MODELS,
E2E_MOCK_CLINE_RECOMMENDED_MODELS,
E2E_REGISTERED_MOCK_ENDPOINTS,
} from "./api"
import { E2E_MOCK_API_RESPONSES, E2E_REGISTERED_MOCK_ENDPOINTS } from "./api"
import { ClineDataMock } from "./data"
const E2E_API_SERVER_PORT = 7777
@@ -171,11 +166,7 @@ export class ClineApiServerMock {
// Authentication middleware
const authHeader = req.headers.authorization
const isPublicApiRoute =
path === "/api/v1/auth/token" ||
path === "/api/v1/ai/cline/models" ||
path === "/api/v1/ai/cline/recommended-models"
const isAuthRequired = !path.startsWith("/.test/") && path !== "/health" && !isPublicApiRoute
const isAuthRequired = !path.startsWith("/.test/") && path !== "/health" && path !== "/api/v1/auth/token"
if (isAuthRequired && (!authHeader || !authHeader.startsWith("Bearer "))) {
return sendApiError("Unauthorized", 401)
@@ -224,14 +215,6 @@ export class ClineApiServerMock {
// API v1 endpoints
if (baseRoute === "/api/v1") {
if (endpoint === "/ai/cline/recommended-models" && method === "GET") {
return sendJson(E2E_MOCK_CLINE_RECOMMENDED_MODELS)
}
if (endpoint === "/ai/cline/models" && method === "GET") {
return sendJson({ data: E2E_MOCK_CLINE_MODELS })
}
// User endpoints
if (endpoint === "/users/me" && method === "GET") {
const currentUser = controller.currentUser
+1 -1
View File
@@ -98,7 +98,7 @@ export function getReadablePath(cwd: string, relPath?: string): string {
if (isLocatedInPath(cwd, absolutePath)) {
return normalizedRelPath.toPosix()
}
// we are outside the cwd, so show the absolute path (useful for when Cline passes in '../../' for example)
// we are outside the cwd, so show the absolute path (useful for when cline passes in '../../' for example)
return absolutePath.toPosix()
}
+37 -13
View File
@@ -5,7 +5,10 @@
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["es2022", "DOM"],
"lib": [
"es2022",
"DOM"
],
"module": "esnext",
"moduleResolution": "Bundler",
"noFallthroughCasesInSwitch": true,
@@ -18,23 +21,44 @@
"sourceMap": true,
"strict": true,
"target": "es2022",
"typeRoots": ["./node_modules/@types", "./src/types"],
"useDefineForClassFields": true,
"useUnknownInCatchVariables": false,
"paths": {
"@/*": ["./src/*"],
"@api/*": ["./src/core/api/*"],
"@core/*": ["./src/core/*"],
"@generated/*": ["./src/generated/*"],
"@hosts/*": ["./src/hosts/*"],
"@integrations/*": ["./src/integrations/*"],
"@packages/*": ["./src/packages/*"],
"@services/*": ["./src/services/*"],
"@shared/*": ["./src/shared/*"],
"@utils/*": ["./src/utils/*"]
"@/*": [
"./src/*"
],
"@api/*": [
"./src/core/api/*"
],
"@core/*": [
"./src/core/*"
],
"@generated/*": [
"./src/generated/*"
],
"@hosts/*": [
"./src/hosts/*"
],
"@integrations/*": [
"./src/integrations/*"
],
"@packages/*": [
"./src/packages/*"
],
"@services/*": [
"./src/services/*"
],
"@shared/*": [
"./src/shared/*"
],
"@utils/*": [
"./src/utils/*"
]
}
},
"include": ["./src/**/*"],
"include": [
"./src/**/*"
],
"exclude": [
"node_modules",
".vscode-test",
@@ -5,16 +5,19 @@ import { type ReactNode, useEffect, useState } from "react"
import { useExtensionState } from "./context/ExtensionStateContext"
export function CustomPostHogProvider({ children }: { children: ReactNode }) {
const { distinctId, version, userInfo, environment, telemetrySetting } = useExtensionState()
const { distinctId, version, userInfo, environment } = useExtensionState()
// Skip PostHog entirely in self-hosted mode or when environment is unknown (safety fallback)
const isSelfHostedOrUnknown = !environment || environment === "selfHosted"
const isTelemetryEnabled = telemetrySetting !== "disabled"
// NOTE: This is a hack to stop recording webview click events temporarily.
// Remove this to re-enable.
// const isTelemetryEnabled = telemetrySetting !== "disabled";
const isTelemetryEnabled = false
const [isActive, setIsActive] = useState(false)
useEffect(() => {
if (isSelfHostedOrUnknown || isActive || !posthogConfig.apiKey) {
if (isSelfHostedOrUnknown || isActive || !isTelemetryEnabled || !posthogConfig.apiKey) {
return
}
// At this point, we know apiKey is defined due to the check above
@@ -24,7 +27,7 @@ export function CustomPostHogProvider({ children }: { children: ReactNode }) {
ui_host: posthogConfig.uiHost,
disable_session_recording: true,
capture_pageview: false,
capture_dead_clicks: false,
capture_dead_clicks: true,
// Feature flags should work regardless of telemetry opt-out
advanced_disable_decide: false,
// Autocapture should respect telemetry settings
@@ -34,7 +37,7 @@ export function CustomPostHogProvider({ children }: { children: ReactNode }) {
}, [isSelfHostedOrUnknown])
useEffect(() => {
if (!isActive || !distinctId || !version) {
if (!isTelemetryEnabled || !isActive || !distinctId || !version) {
return
}
@@ -15,7 +15,7 @@ import ContextWindow from "./ContextWindow"
import { FocusChain } from "./FocusChain"
import { highlightText } from "./Highlights"
const IS_DEV = process.env.IS_DEV === "true"
const IS_DEV = process.env.IS_DEV === '"true"'
interface TaskHeaderProps {
task: ClineMessage
tokensIn: number
@@ -512,14 +512,6 @@ const ClineModelPicker: React.FC<ClineModelPickerProps> = ({ isPopup, currentMod
)}
</DropdownWrapper>
{/* Context window switcher for Claude Fable 5 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-fable-5${CLAUDE_SONNET_1M_SUFFIX}`}
base200kModelId="anthropic/claude-fable-5"
onModelChange={handleModelChange}
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Opus 4.8 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-opus-4.8${CLAUDE_SONNET_1M_SUFFIX}`}
@@ -323,14 +323,6 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
)}
</DropdownWrapper>
{/* Context window switcher for Claude Fable 5 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-fable-5${CLAUDE_SONNET_1M_SUFFIX}`}
base200kModelId="anthropic/claude-fable-5"
onModelChange={handleModelChange}
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Opus 4.8 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-opus-4.8${CLAUDE_SONNET_1M_SUFFIX}`}
@@ -1,7 +1,6 @@
import type { ExtensionMessage } from "@shared/ExtensionMessage";
import { isClineInternalTester } from "@shared/internal/account";
import { ResetStateRequest } from "@shared/proto/cline/state";
import type { UserOrganization } from "@shared/proto/index.cline";
import type { ExtensionMessage } from "@shared/ExtensionMessage"
import { ResetStateRequest } from "@shared/proto/cline/state"
import { UserOrganization } from "@shared/proto/index.cline"
import {
CheckCheck,
FlaskConical,
@@ -12,53 +11,38 @@ import {
SquareMousePointer,
SquareTerminal,
Wrench,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useEvent } from "react-use";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { type ClineUser, useClineAuth } from "@/context/ClineAuthContext";
import { useExtensionState } from "@/context/ExtensionStateContext";
import { cn } from "@/lib/utils";
import { StateServiceClient } from "@/services/grpc-client";
import { isAdminOrOwner } from "../account/helpers";
import { Tab, TabContent, TabList, TabTrigger } from "../common/Tab";
import ViewHeader from "../common/ViewHeader";
import SectionHeader from "./SectionHeader";
import AboutSection from "./sections/AboutSection";
import ApiConfigurationSection from "./sections/ApiConfigurationSection";
import BrowserSettingsSection from "./sections/BrowserSettingsSection";
import DebugSection from "./sections/DebugSection";
import FeatureSettingsSection from "./sections/FeatureSettingsSection";
import GeneralSettingsSection from "./sections/GeneralSettingsSection";
import { RemoteConfigSection } from "./sections/RemoteConfigSection";
import TerminalSettingsSection from "./sections/TerminalSettingsSection";
} from "lucide-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useEvent } from "react-use"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { cn } from "@/lib/utils"
import { StateServiceClient } from "@/services/grpc-client"
import { isAdminOrOwner } from "../account/helpers"
import { Tab, TabContent, TabList, TabTrigger } from "../common/Tab"
import ViewHeader from "../common/ViewHeader"
import SectionHeader from "./SectionHeader"
import AboutSection from "./sections/AboutSection"
import ApiConfigurationSection from "./sections/ApiConfigurationSection"
import BrowserSettingsSection from "./sections/BrowserSettingsSection"
import DebugSection from "./sections/DebugSection"
import FeatureSettingsSection from "./sections/FeatureSettingsSection"
import GeneralSettingsSection from "./sections/GeneralSettingsSection"
import { RemoteConfigSection } from "./sections/RemoteConfigSection"
import TerminalSettingsSection from "./sections/TerminalSettingsSection"
const IS_DEV = process.env.IS_DEV;
const IS_DEV = process.env.IS_DEV
// Tab definitions
type SettingsTabID =
| "api-config"
| "features"
| "browser"
| "terminal"
| "general"
| "about"
| "debug"
| "remote-config";
type SettingsTabID = "api-config" | "features" | "browser" | "terminal" | "general" | "about" | "debug" | "remote-config"
interface SettingsTab {
id: SettingsTabID;
name: string;
tooltipText: string;
headerText: string;
icon: LucideIcon;
hidden?: (params?: {
user: ClineUser | null;
activeOrganization: UserOrganization | null;
}) => boolean;
id: SettingsTabID
name: string
tooltipText: string
headerText: string
icon: LucideIcon
hidden?: (params?: { activeOrganization: UserOrganization | null }) => boolean
}
export const SETTINGS_TABS: SettingsTab[] = [
@@ -103,9 +87,8 @@ export const SETTINGS_TABS: SettingsTab[] = [
tooltipText: "Remotely configured fields",
headerText: "Remote Config",
icon: HardDriveDownload,
hidden: (
{ activeOrganization } = { user: null, activeOrganization: null },
) => !activeOrganization || !isAdminOrOwner(activeOrganization),
hidden: ({ activeOrganization } = { activeOrganization: null }) =>
!activeOrganization || !isAdminOrOwner(activeOrganization),
},
{
id: "about",
@@ -121,21 +104,20 @@ export const SETTINGS_TABS: SettingsTab[] = [
tooltipText: "Debug Tools",
headerText: "Debug",
icon: FlaskConical,
hidden: ({ user } = { user: null, activeOrganization: null }) =>
!IS_DEV && !isClineInternalTester(user?.email || ""),
hidden: () => !IS_DEV,
},
];
]
type SettingsViewProps = {
onDone: () => void;
targetSection?: string;
};
onDone: () => void
targetSection?: string
}
// Helper to render section header - moved outside component for better performance
const renderSectionHeader = (tabId: string) => {
const tab = SETTINGS_TABS.find((t) => t.id === tabId);
const tab = SETTINGS_TABS.find((t) => t.id === tabId)
if (!tab) {
return null;
return null
}
return (
@@ -145,8 +127,8 @@ const renderSectionHeader = (tabId: string) => {
<div>{tab.headerText}</div>
</div>
</SectionHeader>
);
};
)
}
const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
// Memoize to avoid recreation
@@ -162,85 +144,76 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
debug: DebugSection,
}),
[],
); // Empty deps - these imports never change
) // Empty deps - these imports never change
const { version, environment, settingsInitialModelTab } = useExtensionState();
const { activeOrganization, clineUser } = useClineAuth();
const { version, environment, settingsInitialModelTab } = useExtensionState()
const { activeOrganization } = useClineAuth()
const [activeTab, setActiveTab] = useState<string>(
targetSection || SETTINGS_TABS[0].id,
);
const [activeTab, setActiveTab] = useState<string>(targetSection || SETTINGS_TABS[0].id)
// Optimized message handler with early returns
const handleMessage = useCallback((event: MessageEvent) => {
const message: ExtensionMessage = event.data;
const message: ExtensionMessage = event.data
if (message.type !== "grpc_response") {
return;
return
}
const grpcMessage = message.grpc_response?.message;
const grpcMessage = message.grpc_response?.message
if (grpcMessage?.key !== "scrollToSettings") {
return;
return
}
const tabId = grpcMessage.value;
const tabId = grpcMessage.value
if (!tabId) {
return;
return
}
// Check if valid tab ID
if (SETTINGS_TABS.some((tab) => tab.id === tabId)) {
setActiveTab(tabId);
return;
setActiveTab(tabId)
return
}
// Fallback to element scrolling
requestAnimationFrame(() => {
const element = document.getElementById(tabId);
const element = document.getElementById(tabId)
if (!element) {
return;
return
}
element.scrollIntoView({ behavior: "smooth" });
element.style.transition = "background-color 0.5s ease";
element.style.backgroundColor = "var(--vscode-textPreformat-background)";
element.scrollIntoView({ behavior: "smooth" })
element.style.transition = "background-color 0.5s ease"
element.style.backgroundColor = "var(--vscode-textPreformat-background)"
setTimeout(() => {
element.style.backgroundColor = "transparent";
}, 1200);
});
}, []);
element.style.backgroundColor = "transparent"
}, 1200)
})
}, [])
useEvent("message", handleMessage);
useEvent("message", handleMessage)
// Memoized reset state handler
const handleResetState = useCallback(async (resetGlobalState?: boolean) => {
try {
await StateServiceClient.resetState(
ResetStateRequest.create({ global: resetGlobalState }),
);
await StateServiceClient.resetState(ResetStateRequest.create({ global: resetGlobalState }))
} catch (error) {
console.error("Failed to reset state:", error);
console.error("Failed to reset state:", error)
}
}, []);
}, [])
// Update active tab when targetSection changes
useEffect(() => {
if (targetSection) {
setActiveTab(targetSection);
setActiveTab(targetSection)
}
}, [targetSection]);
}, [targetSection])
// Memoized tab item renderer
const renderTabItem = useCallback(
(tab: (typeof SETTINGS_TABS)[0]) => {
return (
<TabTrigger
className="flex justify-baseline"
data-testid={`tab-${tab.id}`}
key={tab.id}
value={tab.id}
>
<TabTrigger className="flex justify-baseline" data-testid={`tab-${tab.id}`} key={tab.id} value={tab.id}>
<Tooltip key={tab.id}>
<TooltipTrigger>
<div
@@ -250,8 +223,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
"opacity-100 border-l-2 border-l-foreground border-t-0 border-r-0 border-b-0 bg-selection":
activeTab === tab.id,
},
)}
>
)}>
<tab.icon className="w-4 h-4" />
<span className="hidden sm:block">{tab.name}</span>
</div>
@@ -259,37 +231,30 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
<TooltipContent side="right">{tab.tooltipText}</TooltipContent>
</Tooltip>
</TabTrigger>
);
)
},
[activeTab],
);
)
// Memoized active content component
const ActiveContent = useMemo(() => {
const Component =
TAB_CONTENT_MAP[activeTab as keyof typeof TAB_CONTENT_MAP];
const Component = TAB_CONTENT_MAP[activeTab as keyof typeof TAB_CONTENT_MAP]
if (!Component) {
return null;
return null
}
// Special props for specific components
const props: any = { renderSectionHeader };
const props: any = { renderSectionHeader }
if (activeTab === "debug") {
props.onResetState = handleResetState;
props.onResetState = handleResetState
} else if (activeTab === "about") {
props.version = version;
props.version = version
} else if (activeTab === "api-config") {
props.initialModelTab = settingsInitialModelTab;
props.initialModelTab = settingsInitialModelTab
}
return <Component {...props} />;
}, [
activeTab,
handleResetState,
settingsInitialModelTab,
version,
TAB_CONTENT_MAP,
]);
return <Component {...props} />
}, [activeTab, handleResetState, settingsInitialModelTab, version])
return (
<Tab>
@@ -299,19 +264,14 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
<TabList
className="shrink-0 flex flex-col overflow-y-auto border-r border-sidebar-background"
onValueChange={setActiveTab}
value={activeTab}
>
{SETTINGS_TABS.filter(
(tab) => !tab.hidden?.({ user: clineUser, activeOrganization }),
).map(renderTabItem)}
value={activeTab}>
{SETTINGS_TABS.filter((tab) => !tab.hidden?.({ activeOrganization })).map(renderTabItem)}
</TabList>
<TabContent className="flex-1 overflow-auto">
{ActiveContent}
</TabContent>
<TabContent className="flex-1 overflow-auto">{ActiveContent}</TabContent>
</div>
</Tab>
);
};
)
}
export default SettingsView;
export default SettingsView
@@ -138,7 +138,7 @@ describe("ApiOptions Component", () => {
)
const modelIdSelect = screen.getByLabelText("Model")
expect(modelIdSelect).toBeInTheDocument()
expect(modelIdSelect).toHaveValue("accounts/fireworks/models/kimi-k2p6")
expect(modelIdSelect).toHaveValue("accounts/fireworks/models/kimi-k2p5")
})
})
@@ -13,7 +13,6 @@ const SUPPORTED_CLAUDE_CODE_THINKING_MODELS = [
...SUPPORTED_ANTHROPIC_THINKING_MODELS,
"sonnet",
"sonnet[1m]",
"claude-fable-5[1m]",
"claude-opus-4-8[1m]",
"claude-opus-4-7[1m]",
"claude-sonnet-4-6[1m]",
@@ -41,15 +41,13 @@ export const isChrome = userAgent.indexOf("Chrome") >= 0
export const isSafari = !isChrome && userAgent.indexOf("Safari") >= 0
declare const __NODE_PLATFORM__: string
/**
* Gets the current platform: 'windows', 'mac', or 'linux'
* Defaults to 'linux' if platform cannot be determined
*/
export function getCurrentPlatform() {
// Fallback to linux if platform is not available
switch (__NODE_PLATFORM__) {
switch (process?.platform) {
case "win32":
return "windows"
case "darwin":
+13 -17
View File
@@ -116,23 +116,19 @@ export default defineConfig({
},
define: {
__PLATFORM__: JSON.stringify(platform),
__NODE_PLATFORM__: JSON.stringify(process.platform),
"process.env.CLINE_ENVIRONMENT": JSON.stringify(process.env.CLINE_ENVIRONMENT ?? "production"),
"process.env.IS_DEV": JSON.stringify(process.env.IS_DEV),
"process.env.IS_TEST": JSON.stringify(process.env.IS_TEST),
"process.env.CI": JSON.stringify(process.env.CI),
// PostHog environment variables
"process.env.TELEMETRY_SERVICE_API_KEY": JSON.stringify(process.env.TELEMETRY_SERVICE_API_KEY),
"process.env.ERROR_SERVICE_API_KEY": JSON.stringify(process.env.ERROR_SERVICE_API_KEY),
"process.env.ENABLE_ERROR_AUTOCAPTURE": JSON.stringify(process.env.ENABLE_ERROR_AUTOCAPTURE),
// OpenTelemetry environment variables
"process.env.OTEL_TELEMETRY_ENABLED": JSON.stringify(process.env.OTEL_TELEMETRY_ENABLED),
"process.env.OTEL_METRICS_EXPORTER": JSON.stringify(process.env.OTEL_METRICS_EXPORTER),
"process.env.OTEL_LOGS_EXPORTER": JSON.stringify(process.env.OTEL_LOGS_EXPORTER),
"process.env.OTEL_EXPORTER_OTLP_PROTOCOL": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_PROTOCOL),
"process.env.OTEL_EXPORTER_OTLP_ENDPOINT": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
"process.env.OTEL_EXPORTER_OTLP_HEADERS": JSON.stringify(process.env.OTEL_EXPORTER_OTLP_HEADERS),
"process.env.OTEL_METRIC_EXPORT_INTERVAL": JSON.stringify(process.env.OTEL_METRIC_EXPORT_INTERVAL),
process: JSON.stringify({
platform: JSON.stringify(process?.platform),
env: {
NODE_ENV: JSON.stringify(process?.env?.IS_DEV ? "development" : "production"),
CLINE_ENVIRONMENT: JSON.stringify(process?.env?.CLINE_ENVIRONMENT ?? "production"),
IS_DEV: JSON.stringify(process?.env?.IS_DEV),
IS_TEST: JSON.stringify(process?.env?.IS_TEST),
CI: JSON.stringify(process?.env?.CI),
// PostHog environment variables
TELEMETRY_SERVICE_API_KEY: JSON.stringify(process?.env?.TELEMETRY_SERVICE_API_KEY),
ERROR_SERVICE_API_KEY: JSON.stringify(process?.env?.ERROR_SERVICE_API_KEY),
},
}),
},
resolve: {
alias: {
+1 -105
View File
@@ -15,109 +15,5 @@
},
"linter": {
"enabled": true
},
"overrides": [
{
"includes": [
"apps/vscode/**"
],
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on",
"useSortedAttributes": "on"
}
}
},
"linter": {
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "info",
"noUndeclaredVariables": "off",
"noEmptyPattern": "info",
"useJsxKeyInIterable": "off",
"noInnerDeclarations": "off",
"useHookAtTopLevel": "info",
"useYield": "info",
"noConstructorReturn": "off",
"noInvalidPositionAtImportRule": "off",
"noSwitchDeclarations": "off",
"noUnusedImports": "error"
},
"a11y": "info",
"style": {
"useNodejsImportProtocol": "off",
"useImportType": "off",
"useBlockStatements": "off",
"useNamingConvention": "off",
"useThrowOnlyError": "info",
"useConsistentArrayType": "off",
"noParameterAssign": "off",
"useAsConstAssertion": "off",
"useDefaultParameterLast": "off",
"noNonNullAssertion": "info",
"useEnumInitializers": "off",
"useSelfClosingElements": "info",
"useSingleVarDeclarator": "off",
"useNumberNamespace": "off",
"noInferrableTypes": "off",
"useTemplate": "info",
"noUselessElse": "info"
},
"suspicious": {
"noDoubleEquals": "warn",
"noImplicitAnyLet": "info",
"noThenProperty": "off",
"noAsyncPromiseExecutor": "info",
"noImportAssign": "off",
"noExplicitAny": "info",
"noControlCharactersInRegex": "warn",
"noShadowRestrictedNames": "off",
"noArrayIndexKey": "info",
"noAssignInExpressions": "info",
"useIterableCallbackReturn": "info"
},
"complexity": {
"noUselessConstructor": "info",
"useOptionalChain": "info",
"noBannedTypes": "warn",
"useLiteralKeys": "info",
"noUselessCatch": "info",
"noUselessSwitchCase": "info",
"noStaticOnlyClass": "info"
},
"security": {
"noDangerouslySetInnerHtml": "info"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 130,
"lineEnding": "lf",
"formatWithErrors": true
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSameLine": true,
"bracketSpacing": true,
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all"
}
},
"json": {
"formatter": {
"trailingCommas": "none",
"expand": "always"
}
}
}
]
}
}
+154 -174
View File
@@ -17,9 +17,9 @@
"typescript": "^5.9.3",
},
},
"apps/cli": {
"sdk/apps/cli": {
"name": "@cline/cli",
"version": "3.0.23",
"version": "3.0.15",
"bin": {
"cline": "src/index.ts",
},
@@ -41,11 +41,9 @@
"commander": "^14.0.3",
"fzf": "^0.5.2",
"marked": "^15.0.12",
"nanoid": "^5.1.7",
"open": "^10.2.0",
"opentui-spinner": "^0.0.6",
"pino": "^10.3.1",
"posthog-node": "^5.8.0",
"react": "19.2.4",
"react-devtools-core": "^7.0.1",
"react-reconciler": "0.32.0",
@@ -56,12 +54,10 @@
"@cline/core": "workspace:*",
"@cline/shared": "workspace:*",
"@microsoft/tui-test": "^0.0.2",
"@types/bun": "^1.3.10",
"@types/react": "19.2.14",
"vitest": "^4.0.18",
},
},
"apps/cline-hub": {
"sdk/apps/cline-hub": {
"name": "@cline/cline-hub",
"version": "0.0.0",
"dependencies": {
@@ -70,7 +66,7 @@
"@cline/shared": "workspace:*",
},
},
"apps/cline-hub/src/webview": {
"sdk/apps/cline-hub/src/webview": {
"name": "@cline/cline-hub-webview",
"version": "0.0.0",
"dependencies": {
@@ -127,7 +123,7 @@
"vite": "^8.0.0",
},
},
"apps/examples/cli-agent": {
"sdk/apps/examples/cli-agent": {
"name": "@cline/example-cli-agent",
"version": "0.0.0",
"dependencies": {
@@ -138,7 +134,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/cline-core-cli-agent": {
"sdk/apps/examples/cline-core-cli-agent": {
"name": "@cline/example-cline-core-cli-agent",
"version": "0.0.0",
"dependencies": {
@@ -148,7 +144,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/code-review-bot": {
"sdk/apps/examples/code-review-bot": {
"name": "@cline/example-code-review-bot",
"version": "0.0.0",
"dependencies": {
@@ -159,7 +155,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/desktop-app": {
"sdk/apps/examples/desktop-app": {
"name": "@cline/code",
"version": "0.0.0",
"dependencies": {
@@ -226,17 +222,15 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.2.0",
"@tauri-apps/cli": "^2.0.0",
"@types/bun": "^1.3.14",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"postcss": "^8.5",
"tailwindcss": "^4.2.0",
"tw-animate-css": "1.3.3",
"typescript": "5.7.3",
"vitest": "^4.0.18",
},
},
"apps/examples/menubar": {
"sdk/apps/examples/menubar": {
"name": "@cline/menubar",
"version": "0.1.0",
"dependencies": {
@@ -250,7 +244,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/multi-agent": {
"sdk/apps/examples/multi-agent": {
"name": "@cline/example-multi-agent",
"version": "0.0.0",
"dependencies": {
@@ -260,7 +254,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/quickstart": {
"sdk/apps/examples/quickstart": {
"name": "@cline/example-quickstart",
"version": "0.0.0",
"dependencies": {
@@ -270,7 +264,7 @@
"typescript": "^5.9.3",
},
},
"apps/examples/vscode": {
"sdk/apps/examples/vscode": {
"name": "@cline/vscode",
"version": "0.0.0",
"dependencies": {
@@ -284,10 +278,9 @@
"@types/node": "^25.3.5",
"@types/vscode": "^1.90.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
},
},
"apps/examples/vscode/src/webview": {
"sdk/apps/examples/vscode/src/webview": {
"name": "webview",
"version": "0.0.0",
"dependencies": {
@@ -372,7 +365,7 @@
},
"sdk/packages/agents": {
"name": "@cline/agents",
"version": "0.0.47",
"version": "0.0.42",
"dependencies": {
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
@@ -381,7 +374,7 @@
},
"sdk/packages/core": {
"name": "@cline/core",
"version": "0.0.47",
"version": "0.0.42",
"dependencies": {
"@cline/agents": "workspace:*",
"@cline/llms": "workspace:*",
@@ -408,18 +401,11 @@
},
"devDependencies": {
"@types/ws": "^8.18.1",
"posthog-node": "^5.8.0",
},
"peerDependencies": {
"posthog-node": "^5.8.0",
},
"optionalPeers": [
"posthog-node",
],
},
"sdk/packages/llms": {
"name": "@cline/llms",
"version": "0.0.47",
"version": "0.0.42",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.89",
"@ai-sdk/anthropic": "^3.0.68",
@@ -453,14 +439,14 @@
},
"sdk/packages/sdk": {
"name": "@cline/sdk",
"version": "0.0.47",
"version": "0.0.42",
"dependencies": {
"@cline/core": "workspace:*",
},
},
"sdk/packages/shared": {
"name": "@cline/shared",
"version": "0.0.47",
"version": "0.0.42",
"dependencies": {
"aws4fetch": "^1.0.20",
"jsonrepair": "^3.13.2",
@@ -472,15 +458,15 @@
"packages": {
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.16.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/openai": "3.0.67", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-PsSh7a6qW+3kQXPs1kD4wDwuZby0t1PIaB6j/1aMKmPFJ5LxcIcULLMF/bjITLt5o/8lc0t6TXIwG0zlhH7uZw=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.111", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cH71k96tcnLuq1x3xi0KP384Jxio8qM6VQzHDUfU4OuX2P83FC/pBvksR5YVRm17GdQliAfR1t5o6z1iJRtfpA=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.81", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.124", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-h8CrmbSG+8X0C+M/E1M4oiDHYevqwbzAPN+uLRHS0eJaatF2MZ+juNtOHXNOjk7Bsk9mD2RjYMjJO9dFkb9I7Q=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.121", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uY248djJRxa5W68MHiyqO8WLdOeKQoRClGg7PVX/VPhVW8SJNM7/l5DcrA5WAM3YfQrLyNkgZa2VOu8T0t8LUw=="],
"@ai-sdk/google": ["@ai-sdk/google@3.0.80", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5ORbm/yFUPO0MEvZsxBMN0cdKw2+lwU/wVn5KN3KF8Dmk1LughuDuUohMh/7iU/XFTiyB0OvmTW/tdV/J7O9zg=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.142", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bTWfj0ITBHjAVJHWCA0DB7PO+aDX8bWxTI9hpNAKH7e5uO74URKdi22zlQAOsROEufcLYiAw0LjrYmmXiksErw=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.140", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Fz3STER9hcrY0uJ6wMg8tSasS5+FbLnBU9N89cw9KBkSUtq+Hefeert7y/UA7RUgPS+d6Z+kzmkqbdAU5dn+oA=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-KkdaMjs4C2y+vrZWJE990E3ZxBFiOTHQ94ZlquuIttpphcqJTMxNoIpnKT/4UzMVWXL0BUEE2vs+1UEVXkN8Kg=="],
@@ -492,7 +478,7 @@
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.198", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.196", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-ozlxMidzvKXAefvnq95Y34rJ5MipXABIv1bg2RLEnWUBxGrKxNHrYl0fTfi6grTN88wSXMZYaMY2oHMCHDFuJw=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.195", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.193", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-+yIH84d4bBNzLKfaDDf4EocEH0XQKKNwNShxbrz5xAiJMNIPnWVWT9cyrSerYaGH3iNVS/g2io42PE4HNbc4RA=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -528,41 +514,41 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1062.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-node": "^3.972.51", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QA5z/Pl3aTMR3+bmiHoC6MpKYa4FMk/9lNP7k104uKuUsjMqP4ysRa43IwdcbI9sH023T//kSJCLxrxa2CP/Tw=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1056.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-node": "^3.972.46", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Fywg6+B39uGiYZRYFEsOXbIeHQ8wvtMqlt6FUwWev8N2H+V0pVdgCKn32pSOzud1i17wnm5gpB2VXZEoyVHc2A=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@aws-sdk/xml-builder": "^3.972.27", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-r8o4h2K7j6P9ngno+8ei0aK0U/4JwDb7A2fMMxGVoSqDN8AFlIzSDeZHME9LcVLR2codyhtr1WAAg+/nmkeeMA=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.41", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-byGPybEQe9ejeyUzhWjtjfh0ctv25HsRx2djF/Tl2j9+DAuAmhjq0NqSRqYZEoSe8vJObXz5RYDtJYAmdupBig=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.38", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-OHkK6xOx/IHkSbQdDWxnVCLU+j28EFl8wyWgBILQDFAPY8n240C/O4gjmFx+zFU12lL8njgJQ5GWAIWq88CnSQ=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-g0XVQKzaA/4cq1vz1IvCQwYM+1Pkv01J9yHDpCTXekVuGZRDEz0wqBQ1AuYTq7FM6uik4uBGH8Tb5d9YvgeA7g=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-n1EbJ98yvPWWdHZZv8bRBMqqDQJrtgtxyJ4xLy2Uqrh25BCOZQ7nnS1CsFXvuH8r0b0KVHDZEGEH5FxmEMP8jg=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-w9PuOoKCt6+xoESvY+zlV0u3PKQ0mVL259PcsVR6a3S/uYJJHnIi4r1NxdJHEcNldUVRIciltWnFMGBR4YEm3g=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-TT76RN1NkI9WoyZqCNxOw6/WBMF7pYOTJcXbMokNFU+euSG40Kaf/t/FhDACVZWP+43wEM6ZynIPIkzS1wR1iA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-login": "^3.972.48", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-83r5MK+PERv9irzky1o5aNbXiLuaLfeB7N8MrktB9USpoebdNtuG0Ek9ieIxpGH1aZ9a0nIaDaLjEr3EmOV3Ng=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-login": "^3.972.45", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-sJe5ZWibO4s7RWjFQ8Zol76KxoJcIYyEZH1/wxQSBMSIAAxzaJ8cS/ITAaIHWUQvDKQdt18+cJAHKWB7n1Jmrg=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-amPGeF6fcvLInK4Pu2k2Y2jHFR6MpaIKrZrbaf0QUnV3tjzjWh442eifZ2+KcmzFdsqyvyjBqAhq2JNLt1C5gA=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-MZQv4SNjByk1iOKmrqmzcUF/uCB05wjvEHyXKxmGQTUANTIVayX6HPUF0bzkWLvtnkH7sAn9kUCfkXbSpj9sDA=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.51", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-ini": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-mbhSY3ytXIGMuBoJsWCivk+63dtVlenT6wstUra07Lar4Ln2MVL8/j5zCTIOog+ig5/FlFJ8gcFU4nQZV+Jh4Q=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.46", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.45", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-cS4w0jzDRb1jOlkiJS3y80OxddHzkky/MN9k3NYs5jganNKVLjF0lpvjlwS118oGMr3cdAfOlVdo8gLurTSE7w=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.43", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-GPokLNyvTfCmuaHk+v3GKVs4ZT3cMu5kgS2a+NPkOMt96cq6fSIK0g+mZHpGS6Cd4QGrPKesANEaLUKgOskTzg=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-7I/n1zkysouLOWvkEhjNEP4vMnD2v4kzzr3/3QBdrripEpn7ap1/I5DF3Hou1SUqkKWo1f3oPGMyFAA1FAMvsQ=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/token-providers": "3.1062.0", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-tf0sD47SeTgCDfOWYssctzGgwAuk8/ECjb7bom4wZ7P1om0qE8i2yjniUdvysmANm5haARr35O8vZnTe/UEtpQ=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/token-providers": "3.1056.0", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-oHgbz/eFD8IKiksqDsz9ZMU4A59BpQq4QwJedBnGD80ZqYcHPPHZBwjBnxLVkB7iRVVHWpDclR8yWdD2PkQIUA=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YYsumc2oe09gl4l+fjfmR64JDn6+0o4Ql5HMBkMuhFazO1tZlE5NjSnZM3oXHwenPjh2qow0TFgSIVjfWfsojg=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-CDhzKdb2onv5bpnjn/acgdNmJOQthPDLsPizU7rZflsEcgMMp8Mlri+U5hdxf8ldvZJpvM3vLU6D56vfJm5AMQ=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1062.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1062.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/credential-provider-cognito-identity": "^3.972.41", "@aws-sdk/credential-provider-env": "^3.972.43", "@aws-sdk/credential-provider-http": "^3.972.45", "@aws-sdk/credential-provider-ini": "^3.972.49", "@aws-sdk/credential-provider-login": "^3.972.48", "@aws-sdk/credential-provider-node": "^3.972.51", "@aws-sdk/credential-provider-process": "^3.972.43", "@aws-sdk/credential-provider-sso": "^3.972.48", "@aws-sdk/credential-provider-web-identity": "^3.972.48", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QS2UT3srjNppZv6mq7V0igqK/ThYKqRWwDscxDsMEmmEE5JqCPPSqFW71aEpkvXaMdgmG8xEpt4RtNHpZ30cTA=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1056.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1056.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.45", "@aws-sdk/credential-provider-login": "^3.972.45", "@aws-sdk/credential-provider-node": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Qp7ndCG+dZldiaURze6BM/dLkHQJxwi6WNRR1sR9lhX9jS9QG5ZIOiY3jm6T668vgGqHuNQS7r/P9pimxnHyyg=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.16", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.17", "@aws-sdk/signature-v4-multi-region": "^3.996.31", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-bGvfDgC2KQePjEmZdltScPPLKFoyjPElAXeZcLfvZ58J1AO283//WGtvp9GdnryLHTi7gis0UoCezqh0vl/nig=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/signature-v4-multi-region": "^3.996.30", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-2pA6eyb5nSo/ZD2cayhOTEMoGQYgspq0RI05GDLkzQ3ajZ6isS6waV6E92Am/hz4LIlLUTrbwPLurJ/fuiHvkg=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.31", "", { "dependencies": { "@aws-sdk/types": "^3.973.10", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Kn2up9SlG1KC6wRtwf0d7waTGF6rvp9DxYqB54x6UCKdQ6kyaXCqHL4WGb5vUJga5kS8FxnjhY0LqM28aMvnNQ=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.30", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1062.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.17", "@aws-sdk/nested-clients": "^3.997.16", "@aws-sdk/types": "^3.973.10", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-fvHh53zSm2FoQPgkw9thH5D7sd13bC0nPyuZb+mQJ85l5v7lQnsZ97u6e6YkJJN/LU1Mxm1/DLGrIIRR2L7tZw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1056.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.15", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-81duvlltQlsfn5K+o8zILcystBRdbT1G2JJYVCML5NZHBz4CL/zf+sAemCtBh/uh6RQUMyInGeZLQ7/8igZhbA=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.10", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-992QrTO7G9qCvKD0fx1rMlqcL14plUcRAbwmqqYVsuF3GrqcvlAL9qxR+baMafarEZ+l7DUQ5lCMmt5mbMhF7g=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.27", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-hpsCXCOI436kxWpjtRuIHVvuPP81MOw8f18jzfZeg+UOiiOvlqWcmWChzEhJEu16cOC6+ku4ncBN+7rdt+DZ9g=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.26", "", { "dependencies": { "@smithy/types": "^4.14.2", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
@@ -648,57 +634,57 @@
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
"@chat-adapter/discord": ["@chat-adapter/discord@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "chat": "4.30.0", "discord-api-types": "^0.37.119", "discord-interactions": "^4.4.0", "discord.js": "^14.25.1" } }, "sha512-hWMwCwVgMeQWB5F2JL91GLTWcF8rlE4eewzXzfPSoAsM7Y71yBDCfQ9QfL5VODwPKtYnVPU5Go2pGsuauaZ/yw=="],
"@chat-adapter/discord": ["@chat-adapter/discord@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "chat": "4.29.0", "discord-api-types": "^0.37.119", "discord-interactions": "^4.4.0", "discord.js": "^14.25.1" } }, "sha512-C6pDVKn7s0PhvgeqldYxoShWXJzthwtlo+sCesqenh56M03eHsioinqxeJttmtK8y1ZD8/qiP2lAZufDwuEQhA=="],
"@chat-adapter/gchat": ["@chat-adapter/gchat@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "@googleapis/chat": "^44.6.0", "@googleapis/workspaceevents": "^9.1.0", "chat": "4.30.0" } }, "sha512-s3rqHJqW17RE1drBhS9H+YYDpiasfd4YBaXdLrNqao5AS5sNWR/ekWiDGPeF/zlzDkOUNdnyDq3NbA7/2HsHsQ=="],
"@chat-adapter/gchat": ["@chat-adapter/gchat@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "@googleapis/chat": "^44.6.0", "@googleapis/workspaceevents": "^9.1.0", "chat": "4.29.0" } }, "sha512-SZ5ZgzFmZ4oRj0AAISbjPztPiICTpw+RdPiPywNwqGWt2LItUOL+8+dl8zZMk+F86E/r0sqRjdbc8+9GqUe2pA=="],
"@chat-adapter/linear": ["@chat-adapter/linear@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "@linear/sdk": "^76.0.0", "chat": "4.30.0" } }, "sha512-L0/B71Sdx8XuMbhaw0YShzR18DwhBS+Yfna61SnTpyoS+BzIRKxP1xEQU6LYGCiOiS+587GZOUm2vSWPakMAWw=="],
"@chat-adapter/linear": ["@chat-adapter/linear@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "@linear/sdk": "^76.0.0", "chat": "4.29.0" } }, "sha512-4cq7pBv0CICka8V0uuae7bNfb1rTdmYjsNjOs7Gkr4vChlhDxRUEGUoiBqvOEYKqxFtFOkRk9Wod0tIQmbPySw=="],
"@chat-adapter/shared": ["@chat-adapter/shared@4.30.0", "", { "dependencies": { "chat": "4.30.0" } }, "sha512-IuYtbn/p1FBXvp7JYGEMLCt07GHOMlyjx7OlZXPJwLTravcyJuP7Q6N31r6c1yubMhM8PLb8eT8l/YnjwYjs9Q=="],
"@chat-adapter/shared": ["@chat-adapter/shared@4.29.0", "", { "dependencies": { "chat": "4.29.0" } }, "sha512-ARqTDoHJHKN9rpytbFPJbNmqqx3fOg5xwsTZdlingQPAssOSeHDBdqrFJkgDhyCRGbmDtG09cuS0FkVzeoh2qg=="],
"@chat-adapter/slack": ["@chat-adapter/slack@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "@slack/socket-mode": "^2.0.5", "@slack/web-api": "^7.14.0", "chat": "4.30.0" } }, "sha512-ZB+G/JBKmaXzvl+DuUQPBb/gCwXP3fOtUK5Cyj6wLbbeeDYzi3NQPvpvieVum/GI4iuR8OUVcNAnVQiH6P6DOQ=="],
"@chat-adapter/slack": ["@chat-adapter/slack@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "@slack/socket-mode": "^2.0.5", "@slack/web-api": "^7.14.0", "chat": "4.29.0" } }, "sha512-s2DXAwkTpmiIKSATXgrO879s1pqFwS70Y0JPd+TRGRzDeh6nfqt5dnKt5Bug0P1zwkB6DoPurhnYS9nqhSmD/w=="],
"@chat-adapter/telegram": ["@chat-adapter/telegram@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "chat": "4.30.0" } }, "sha512-OX98fYMorz3gRvNoCVidYKOnb89Rf9UZQaUDQMhtk5IsgHX0k2qiUpEb2VOquMHyH8Pb7xkqrG52NFLnzGpspQ=="],
"@chat-adapter/telegram": ["@chat-adapter/telegram@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "chat": "4.29.0" } }, "sha512-015tU3HEjFQWw7DgebXWkDeQA6lTdTVEO4btAV3f6U1kEnOfjLVJq98latcsM1WkEvv+3LIFKpsz9pG53aamBw=="],
"@chat-adapter/whatsapp": ["@chat-adapter/whatsapp@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "chat": "4.30.0" } }, "sha512-4iXroN/FYRsWdhgVFUNzf4VXX67a9u6xcYoBGgX2eqTwlq5THywYSe7FSA5GP7Hg0SK0mRExdpR/Hvk9+tP9xw=="],
"@chat-adapter/whatsapp": ["@chat-adapter/whatsapp@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "chat": "4.29.0" } }, "sha512-CDKmlDHmiiJ2xtca0wZ2DIYnySUFuiKn6f3ZQvschFVa6pSaDmaldmJc3iul+ELi10+RE7x5+immfc//+TTzFg=="],
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
"@clack/core": ["@clack/core@1.4.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw=="],
"@clack/core": ["@clack/core@1.4.0", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-7Wctjq6f7c1CPz8sPpkwUnz8yRgVANkpNupb81q432FjcJg4l+Sw7XANdNSdWfAKq0IHI0JTcUeK5dxs/HrGPw=="],
"@clack/prompts": ["@clack/prompts@1.5.1", "", { "dependencies": { "@clack/core": "1.4.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw=="],
"@clack/prompts": ["@clack/prompts@1.5.0", "", { "dependencies": { "@clack/core": "1.4.0", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-wKh+wTjmrUoUdkZg8KpJO5X+p9PWV+KE9mePseq9UYWkukgTKsGS47RRL2HstwVcvDQH+PenrPJWII8+MfiiyA=="],
"@cline/agents": ["@cline/agents@workspace:sdk/packages/agents"],
"@cline/cli": ["@cline/cli@workspace:apps/cli"],
"@cline/cli": ["@cline/cli@workspace:sdk/apps/cli"],
"@cline/cline-hub": ["@cline/cline-hub@workspace:apps/cline-hub"],
"@cline/cline-hub": ["@cline/cline-hub@workspace:sdk/apps/cline-hub"],
"@cline/cline-hub-webview": ["@cline/cline-hub-webview@workspace:apps/cline-hub/src/webview"],
"@cline/cline-hub-webview": ["@cline/cline-hub-webview@workspace:sdk/apps/cline-hub/src/webview"],
"@cline/code": ["@cline/code@workspace:apps/examples/desktop-app"],
"@cline/code": ["@cline/code@workspace:sdk/apps/examples/desktop-app"],
"@cline/core": ["@cline/core@workspace:sdk/packages/core"],
"@cline/example-cli-agent": ["@cline/example-cli-agent@workspace:apps/examples/cli-agent"],
"@cline/example-cli-agent": ["@cline/example-cli-agent@workspace:sdk/apps/examples/cli-agent"],
"@cline/example-cline-core-cli-agent": ["@cline/example-cline-core-cli-agent@workspace:apps/examples/cline-core-cli-agent"],
"@cline/example-cline-core-cli-agent": ["@cline/example-cline-core-cli-agent@workspace:sdk/apps/examples/cline-core-cli-agent"],
"@cline/example-code-review-bot": ["@cline/example-code-review-bot@workspace:apps/examples/code-review-bot"],
"@cline/example-code-review-bot": ["@cline/example-code-review-bot@workspace:sdk/apps/examples/code-review-bot"],
"@cline/example-multi-agent": ["@cline/example-multi-agent@workspace:apps/examples/multi-agent"],
"@cline/example-multi-agent": ["@cline/example-multi-agent@workspace:sdk/apps/examples/multi-agent"],
"@cline/example-quickstart": ["@cline/example-quickstart@workspace:apps/examples/quickstart"],
"@cline/example-quickstart": ["@cline/example-quickstart@workspace:sdk/apps/examples/quickstart"],
"@cline/llms": ["@cline/llms@workspace:sdk/packages/llms"],
"@cline/menubar": ["@cline/menubar@workspace:apps/examples/menubar"],
"@cline/menubar": ["@cline/menubar@workspace:sdk/apps/examples/menubar"],
"@cline/sdk": ["@cline/sdk@workspace:sdk/packages/sdk"],
"@cline/shared": ["@cline/shared@workspace:sdk/packages/shared"],
"@cline/vscode": ["@cline/vscode@workspace:apps/examples/vscode"],
"@cline/vscode": ["@cline/vscode@workspace:sdk/apps/examples/vscode"],
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
@@ -720,7 +706,7 @@
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-KEUw/mGu+EDRhYWRTNGHIimVCs9NvMFaIXOGrHSXoCteKLE5EsJnmPjOPpYorjXVg/0xG0fbdVw720azw1z4ag=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.69.1", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-kwQB5KcAegxw/+NGUgXAo5ovyOSjlMhoXSSnSEpDhoHJwzMcMO0HE1U0VCYZ7jbAeCMGamed9XdWzOA5ixtTNg=="],
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
@@ -888,15 +874,15 @@
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="],
"@inquirer/ansi": ["@inquirer/ansi@2.0.6", "", {}, "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ=="],
"@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="],
"@inquirer/confirm": ["@inquirer/confirm@6.1.0", "", { "dependencies": { "@inquirer/core": "^11.2.0", "@inquirer/type": "^4.0.6" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA=="],
"@inquirer/core": ["@inquirer/core@11.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA=="],
"@inquirer/core": ["@inquirer/core@11.2.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.6", "@inquirer/figures": "^2.0.6", "@inquirer/type": "^4.0.6", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^4.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g=="],
"@inquirer/figures": ["@inquirer/figures@2.0.7", "", {}, "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw=="],
"@inquirer/figures": ["@inquirer/figures@2.0.6", "", {}, "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw=="],
"@inquirer/type": ["@inquirer/type@4.0.7", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g=="],
"@inquirer/type": ["@inquirer/type@4.0.6", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
@@ -1046,7 +1032,7 @@
"@openai/codex-win32-x64": ["@openai/codex@0.130.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-FzMznm7fr5/nbjZgOujZ9Y9AbdGm7ji1FOoWiY3U+srqauvZaTgn6o6aCheSL7kuymu7nTLOO/cAyWV6NuesqQ=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.15.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-4TwojIoQ8EG6/mVBuUVYZXiFcwNmiiytEnjnvyuvSJjGwFIlw2YIBFxtSVC3FbwwbwHT63teh1RHiQUUC4U5xw=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.15.12", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-lOaBNX93dkakZe6C42ttX1bkSx3K2c6+Yv+w8Qv02v5rPlu1vCXbmdfYDh9/bw+oq+NKPSaBm9d6kPA19hA5Lg=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
@@ -1096,16 +1082,12 @@
"@opentui/react": ["@opentui/react@0.1.102", "", { "dependencies": { "@opentui/core": "0.1.102", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-c7EiK30xlvaHc6WBOLAH7TJ8xC4QbiD5ZA6tb4zZ74XM4SH5Tb+uBee8b4TztTLseq+hGtW85qrIUJM3OrdtNw=="],
"@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
"@oxc-project/types": ["@oxc-project/types@0.132.0", "", {}, "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@posthog/core": ["@posthog/core@1.32.3", "", { "dependencies": { "@posthog/types": "1.386.3" } }, "sha512-vwOEMfZvGv5XxNWV7p9I52NSmvFNMhyW2IHpIoUHW5jLkgUrknzJW1H/qxVGSIrNNVQkfsoaDFzDhJdg10pgrA=="],
"@posthog/types": ["@posthog/types@1.386.3", "", {}, "sha512-LqJoiQi2eyWn7rCUgnn+D+F3Efp6+04o72bjSX6kWHx0nFaYNC/nJuAIRliDTY/X7GPIUAaHAcSjbMI/9wfX1Q=="],
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
@@ -1250,35 +1232,35 @@
"@rive-app/webgl2": ["@rive-app/webgl2@2.37.8", "", {}, "sha512-Y2nXPwAeQtZrADNIzY7v3Lk7XZRMy4Gd+bpGFyzkaQ/EQ91Hp/6sCdd8yTCyjH4b71N/T6kU0MHIYA0IDmpjyQ=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.2", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
@@ -1314,19 +1296,19 @@
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
"@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="],
"@shikijs/core": ["@shikijs/core@4.1.0", "", { "dependencies": { "@shikijs/primitive": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg=="],
"@shikijs/langs": ["@shikijs/langs@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ=="],
"@shikijs/langs": ["@shikijs/langs@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg=="],
"@shikijs/primitive": ["@shikijs/primitive@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA=="],
"@shikijs/primitive": ["@shikijs/primitive@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw=="],
"@shikijs/themes": ["@shikijs/themes@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w=="],
"@shikijs/themes": ["@shikijs/themes@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw=="],
"@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
"@shikijs/types": ["@shikijs/types@4.1.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
@@ -1346,25 +1328,25 @@
"@slack/web-api": ["@slack/web-api@7.16.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.21.0", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.16.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-68SAV77uuGKuhyyaRytX8UijVnqSLsTSKslGXw17cjQYXn+jtNl7gbaEjHgC5x2rhCuFdahBrEC2VCLppbzReg=="],
"@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="],
"@smithy/core": ["@smithy/core@3.24.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-tHhdiWZfG1ZIh2YcRfPJmY2gHcBmqbAzqm3ER4TIDFYsSEqTD5tICT7cgQ/kI8LRakxp12myOYyK68XPn7MnHw=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-Ussyv240JxwQP8AmkYdm26wGP/1I8QmIv0ZosgDJDlSzD73FEdj1BOpXMc06VrxX5KxTKhadFNomT2SWutUnpg=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.3.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-sM98Snchk/k9dFKwf3F2pyDUaiKilgOU/I+EAQin8Y1XXYusIP5EVRMpjKFVeIHXDnwijxg+6RmIM6HCDvYQoA=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-SK3VMeH0fibgdTg2QeB+O4p7Yy/2E5HBOHJeC58FshkDdeuX8lOgO7PfjYfLyPLP1ch55j91cQqKBzDS0mRjSQ=="],
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-3dA9TQ+ybRSZ/m0wnbZhiBy4Dezjgq1Ib/ZZrYTpJDBgpoLLU/SDzZc/g0x0MNAdOJe1wPcM+x2PBRmoOur+Sw=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.4.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-QBJKWGqIknH0dc9LWpfH1mkdokAx6iXYN3UcQ3eY6uIEyScuoQAhfl94ge7ozUy9WgFUdE8xsvwBjaYBbWmPNA=="],
"@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="],
"@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="],
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-tAa4sePYB7mlJzdYbdBqdv37KwFKWixmM/r3ihcI0HFOVjf+a5oGvtcLXcGm4S1bY4DFsLAIOHgjubtp+oRufw=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.3.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-l1d7I7YP2LjXjAZDC7eXqkzuEB75KfCANwhNj/knmT6+0a9XG3QasvI8kEn8WAI3tx/q8PdmSuuXcM+MTkk/7Q=="],
"@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="],
@@ -1608,25 +1590,25 @@
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/type-utils": "8.60.1", "@typescript-eslint/utils": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.1", "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.0", "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1" } }, "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.60.1", "", {}, "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.1", "@typescript-eslint/tsconfig-utils": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.0", "@typescript-eslint/tsconfig-utils": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
@@ -1638,19 +1620,19 @@
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0", "@swc/core": "^1.15.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-PaeokKjAGraNN+s5SIApgsktnJprIyt3zgEIu7awnEdfn29QiB2crTcCzyi2XGpX9rUnTc0cKU07Wm0N0g7H2w=="],
"@vitest/expect": ["@vitest/expect@4.1.8", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ=="],
"@vitest/expect": ["@vitest/expect@4.1.7", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w=="],
"@vitest/mocker": ["@vitest/mocker@4.1.8", "", { "dependencies": { "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw=="],
"@vitest/mocker": ["@vitest/mocker@4.1.7", "", { "dependencies": { "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA=="],
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="],
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.7", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw=="],
"@vitest/runner": ["@vitest/runner@4.1.8", "", { "dependencies": { "@vitest/utils": "4.1.8", "pathe": "^2.0.3" } }, "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg=="],
"@vitest/runner": ["@vitest/runner@4.1.7", "", { "dependencies": { "@vitest/utils": "4.1.7", "pathe": "^2.0.3" } }, "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw=="],
"@vitest/snapshot": ["@vitest/snapshot@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ=="],
"@vitest/snapshot": ["@vitest/snapshot@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw=="],
"@vitest/spy": ["@vitest/spy@4.1.8", "", {}, "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA=="],
"@vitest/spy": ["@vitest/spy@4.1.7", "", {}, "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q=="],
"@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="],
"@vitest/utils": ["@vitest/utils@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw=="],
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="],
@@ -1660,9 +1642,9 @@
"@xterm/headless": ["@xterm/headless@5.5.0", "", {}, "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g=="],
"@xyflow/react": ["@xyflow/react@12.11.0", "", { "dependencies": { "@xyflow/system": "0.0.77", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "@types/react": ">=17", "@types/react-dom": ">=17", "react": ">=17", "react-dom": ">=17" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA=="],
"@xyflow/react": ["@xyflow/react@12.10.2", "", { "dependencies": { "@xyflow/system": "0.0.76", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ=="],
"@xyflow/system": ["@xyflow/system@0.0.77", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-qCDCMCQAAgUu8yHnhloHG9F5mwPX5E+Wl8McpYIOPSSXfzFJJoZcwOcsDiAjitVKIg2de1WmJbCHfpcvxprsgg=="],
"@xyflow/system": ["@xyflow/system@0.0.76", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
@@ -1674,7 +1656,7 @@
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ai": ["ai@6.0.196", "", { "dependencies": { "@ai-sdk/gateway": "3.0.124", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2T45UeqKL4a11KQ14I5i1YYHOvCFrMF478E1k6PVjlQSGUvXSv4xrxIaQbUL4qgv91DADSbddwv3oR49pPAK3g=="],
"ai": ["ai@6.0.193", "", { "dependencies": { "@ai-sdk/gateway": "3.0.121", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VQOTOse8+X8kMtg61DNSXlYJzwOW4NjMLDJNk/qxClWsFe4oiyFJDHGGG1oezfGcFzuYuQe/8Z7r4kwiZWh2YQ=="],
"ai-sdk-provider-claude-code": ["ai-sdk-provider-claude-code@3.4.4", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "@anthropic-ai/claude-agent-sdk": "^0.2.63" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-iHcup5SHh4Tul1RIi9J+bnpngen8WX66yC3lsz1YlbtwAmRhUEzZUuGKzmFGIN8Pmx9uQrerGfLJdbFxIxKkyw=="],
@@ -1726,7 +1708,7 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="],
"axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
@@ -1734,7 +1716,7 @@
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
@@ -1796,7 +1778,7 @@
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
"chat": ["chat@4.30.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" }, "peerDependencies": { "ai": "^6.0.182", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai", "zod"] }, "sha512-8LXrauKckMmR83FcYC/R8nNEda5VJDDdIhZwUUu+hzaSbk4lqsro0IWm7rB1GGYXONRrUOG2XJlkNr4C15vgMA=="],
"chat": ["chat@4.29.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" }, "peerDependencies": { "ai": "^6.0.182", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai", "zod"] }, "sha512-KdPfzaie5ivYytyRICTERg5xT+LeCbYefokvNAqTHe92eqkFaoTMXXkSitikxJVWhZIb2YoXF1b9UZHyzSzKzw=="],
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
@@ -1868,7 +1850,7 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="],
"cytoscape": ["cytoscape@3.33.4", "", {}, "sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww=="],
"cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="],
@@ -1994,7 +1976,7 @@
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"dompurify": ["dompurify@3.4.8", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ=="],
"dompurify": ["dompurify@3.4.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
@@ -2008,7 +1990,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.367", "", {}, "sha512-4Mk/mrynCNQ+atY40D3UpmhLWB6AHMbYMlIrPhHcMF6x0L7O0b052FCAsxw1LlaR++UFuNg3D/A6XCuGDa0guQ=="],
"electron-to-chromium": ["electron-to-chromium@1.5.364", "", {}, "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw=="],
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
@@ -2022,7 +2004,7 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"enhanced-resolve": ["enhanced-resolve@5.22.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag=="],
"enhanced-resolve": ["enhanced-resolve@5.22.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww=="],
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
@@ -2224,7 +2206,7 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphql": ["graphql@16.14.1", "", {}, "sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg=="],
"graphql": ["graphql@16.14.0", "", {}, "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q=="],
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
@@ -2380,7 +2362,7 @@
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
@@ -2544,7 +2526,7 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"media-chrome": ["media-chrome@4.19.1", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-1+x2l0mNulHKZN0lBxGJwJ+TV2W/KzLjaAd//UCGZz8GE5O5YNafFskWTcv/D6Ty0d9drX9SSfimOzGwob8eVQ=="],
"media-chrome": ["media-chrome@4.19.0", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-HWhDTwts+BSbdPkkB1VsJXp5kvL0IxY7xFT5tBwliM2+89kTPVTnHnev+9it2f9PweANjT/C8/C/S0PW9oyZbA=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -2648,7 +2630,7 @@
"msw": ["msw@2.14.6", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.11", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg=="],
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
"mute-stream": ["mute-stream@4.0.0", "", {}, "sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg=="],
"nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="],
@@ -2676,7 +2658,7 @@
"node-pty": ["node-pty@1.2.0-beta.11", "", { "dependencies": { "node-addon-api": "^7.1.0" } }, "sha512-THcUyu1WwdgoIyUvgXOZ70EOMXzheGa0q3tbEb5kUIfKgcpBJ+AJ9Q1kq0bKtYmQzr77usXiTORZTLmAUQlnoQ=="],
"node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="],
"node-releases": ["node-releases@2.0.46", "", {}, "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ=="],
"node-rsa": ["node-rsa@1.1.1", "", { "dependencies": { "asn1": "^0.2.4" } }, "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw=="],
@@ -2688,7 +2670,7 @@
"object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
"obug": ["obug@2.1.2", "", {}, "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
@@ -2800,8 +2782,6 @@
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"posthog-node": ["posthog-node@5.36.17", "", { "dependencies": { "@posthog/core": "1.32.3" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-ed1LT4a9hhiFJizB6XX7dkYYLVPAFHfUpkQSns7BRxoUyhFnvMq15QENKeAOUEKQgPmnaq2I+xNLdAHN0o9eAA=="],
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@@ -2820,9 +2800,9 @@
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
"property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="],
"protobufjs": ["protobufjs@7.6.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
@@ -2850,7 +2830,7 @@
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"react-hook-form": ["react-hook-form@7.77.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-Sslh9YDYc0GDlWT/lxasnIduNo4v3yyvqRGvmGKUre5AFjDs/HV9/OafHGD8d+sB2yoL4UIL9L8X9i0WlZZebg=="],
"react-hook-form": ["react-hook-form@7.76.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-rYM7tPiWlu3nZchkR/ex7piyzui2vFPyaLnXnI/RnblB/L4qfMmyses8llJVtF1NpE9WBBsJlGtcSZzPCXW1qQ=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -2934,7 +2914,7 @@
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
"rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="],
"rolldown": ["rolldown@1.0.2", "", { "dependencies": { "@oxc-project/types": "=0.132.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.2", "@rolldown/binding-darwin-arm64": "1.0.2", "@rolldown/binding-darwin-x64": "1.0.2", "@rolldown/binding-freebsd-x64": "1.0.2", "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", "@rolldown/binding-linux-arm64-gnu": "1.0.2", "@rolldown/binding-linux-arm64-musl": "1.0.2", "@rolldown/binding-linux-ppc64-gnu": "1.0.2", "@rolldown/binding-linux-s390x-gnu": "1.0.2", "@rolldown/binding-linux-x64-gnu": "1.0.2", "@rolldown/binding-linux-x64-musl": "1.0.2", "@rolldown/binding-openharmony-arm64": "1.0.2", "@rolldown/binding-wasm32-wasi": "1.0.2", "@rolldown/binding-win32-arm64-msvc": "1.0.2", "@rolldown/binding-win32-x64-msvc": "1.0.2" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g=="],
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
@@ -2966,7 +2946,7 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shadcn": ["shadcn@4.10.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-84IJhUsK0xqSCRJx3QxyZe2NpUXj2Nwk8Vc8Ow/tCOND3yz4CT6uU4655vqicNXhzG9Q1cyUt+TBl2SiCJwNgg=="],
"shadcn": ["shadcn@4.8.3", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-cnP485rIqtDb8waOp+IKUIfifVf64/PCd5VX/nnLeIP+qZ3yS4r6FOQtOdQkoQEvomQUsJS2OHr5CTzKWw0wKQ=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
@@ -2976,7 +2956,7 @@
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
"shiki": ["shiki@4.2.0", "", { "dependencies": { "@shikijs/core": "4.2.0", "@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-oniguruma": "4.2.0", "@shikijs/langs": "4.2.0", "@shikijs/themes": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ=="],
"shiki": ["shiki@4.1.0", "", { "dependencies": { "@shikijs/core": "4.1.0", "@shikijs/engine-javascript": "4.1.0", "@shikijs/engine-oniguruma": "4.1.0", "@shikijs/langs": "4.1.0", "@shikijs/themes": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
@@ -3090,15 +3070,15 @@
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
"tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="],
"tinyexec": ["tinyexec@1.2.3", "", {}, "sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
"tldts": ["tldts@7.4.2", "", { "dependencies": { "tldts-core": "^7.4.2" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw=="],
"tldts": ["tldts@7.4.0", "", { "dependencies": { "tldts-core": "^7.4.0" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg=="],
"tldts-core": ["tldts-core@7.4.2", "", {}, "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA=="],
"tldts-core": ["tldts-core@7.4.1", "", {}, "sha512-sc2nGvGbixlJRHwTh/qQdPXTxJU1UDJboGPQm4d/01YUJ9r/u6aeIulQvEaxUlvKDN7hb1qCLjax+jhVAPLa/g=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -3134,13 +3114,13 @@
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="],
"type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="],
"type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.60.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.1", "@typescript-eslint/parser": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA=="],
"typescript-eslint": ["typescript-eslint@8.60.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.0", "@typescript-eslint/parser": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw=="],
"uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="],
@@ -3182,7 +3162,7 @@
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-stick-to-bottom": ["use-stick-to-bottom@1.1.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-A6vhRIbuQqqkwR9CbbMEP9oZcNaAVknjYL/GR9BnmpSUxwR8ncPx7k4O2CrJriObORKIYgvAsmVWcE+moJDmVg=="],
"use-stick-to-bottom": ["use-stick-to-bottom@1.1.4", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2w/lydkrwhWMv1vCaEhYbzMDhgbwIodHpAHPV0/xKJErRkbjDEUe1EWmvr6Fwb+qhiERjc1EWgAEZaSaF69CpA=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
@@ -3208,9 +3188,9 @@
"victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
"vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="],
"vite": ["vite@8.0.14", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw=="],
"vitest": ["vitest@4.1.8", "", { "dependencies": { "@vitest/expect": "4.1.8", "@vitest/mocker": "4.1.8", "@vitest/pretty-format": "4.1.8", "@vitest/runner": "4.1.8", "@vitest/snapshot": "4.1.8", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.8", "@vitest/browser-preview": "4.1.8", "@vitest/browser-webdriverio": "4.1.8", "@vitest/coverage-istanbul": "4.1.8", "@vitest/coverage-v8": "4.1.8", "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig=="],
"vitest": ["vitest@4.1.7", "", { "dependencies": { "@vitest/expect": "4.1.7", "@vitest/mocker": "4.1.7", "@vitest/pretty-format": "4.1.7", "@vitest/runner": "4.1.7", "@vitest/snapshot": "4.1.7", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.7", "@vitest/browser-preview": "4.1.7", "@vitest/browser-webdriverio": "4.1.7", "@vitest/coverage-istanbul": "4.1.7", "@vitest/coverage-v8": "4.1.7", "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA=="],
"voca": ["voca@1.4.1", "", {}, "sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA=="],
@@ -3220,7 +3200,7 @@
"web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
"webview": ["webview@workspace:apps/examples/vscode/src/webview"],
"webview": ["webview@workspace:sdk/apps/examples/vscode/src/webview"],
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
@@ -3286,7 +3266,7 @@
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@cline/cline-hub-webview/@types/node": ["@types/node@24.13.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg=="],
"@cline/cline-hub-webview/@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
"@cline/code/lucide-react": ["lucide-react@0.564.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg=="],
@@ -3514,7 +3494,7 @@
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
@@ -3590,7 +3570,7 @@
"jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"jsonwebtoken/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"jsonwebtoken/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
@@ -3644,7 +3624,7 @@
"react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"react-jsx-parser/@types/react": ["@types/react@18.3.30", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-3ek6mwJL5/VBewBcY4S66cqlCtK3qi4WIq37Z0m/NHw1hjhI7274Mx1qz/+ggSzyBCOEf7eHjBN6INjPAWYfYw=="],
"react-jsx-parser/@types/react": ["@types/react@18.3.29", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg=="],
"react-jsx-parser/@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
@@ -3660,7 +3640,7 @@
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"sharp/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"sharp/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="],
"slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -3680,7 +3660,7 @@
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"webview/@types/node": ["@types/node@24.13.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg=="],
"webview/@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -3690,7 +3670,7 @@
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@cline/cline-hub-webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@cline/cline-hub-webview/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
@@ -3812,7 +3792,7 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"webview/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+11 -34
View File
@@ -22,7 +22,7 @@ cline connect
| Platform | Direct Command | Required Credentials |
|----------|---------------|---------------------|
| Telegram | `cline connect telegram` | Bot token |
| Slack | `cline connect slack` | Bot token plus webhook signing secret/base URL or socket app token |
| Slack | `cline connect slack` | Bot token, signing secret, base URL |
| Discord | `cline connect discord` | Application ID, bot token, public key, base URL |
| Google Chat | `cline connect gchat` | Service account credentials JSON, base URL |
| WhatsApp | `cline connect whatsapp` | Phone number ID, access token, app secret, verify token, base URL |
@@ -54,57 +54,34 @@ cline connect
### Security
By default, anyone who finds your bot can message it and it will execute tasks on your machine. The `cline connect` wizard asks whether to restrict Telegram access and can configure this for you.
By default, anyone who finds your bot can message it and it will execute tasks on your machine. Lock it down with the `--hook-command` flag.
<Steps>
<Step title="Get your Telegram user ID">
Message [@userinfobot](https://t.me/userinfobot) on Telegram. It replies with your numeric user ID immediately.
Message [@userinfobot](https://t.me/userinfobot) on Telegram. It replies with your user ID immediately.
</Step>
<Step title="Use the wizard">
```bash
cline connect
```
Choose Telegram, enter the bot token, answer yes to access restriction, then enter your user ID.
</Step>
<Step title="Or pass the flag manually">
Replace `12345` with your Telegram user ID:
<Step title="Start with access control">
Replace `12345` with your actual Telegram user ID:
```bash
cline connect telegram -k <BOT-TOKEN> \
--allowed-user-id 12345
--hook-command 'jq -r ".payload.actor.participantKey" | grep -q "telegram:id:12345" && echo "{\"action\":\"allow\"}" || echo "{\"action\":\"deny\",\"message\":\"unauthorized\"}"'
```
</Step>
</Steps>
Use `--hook-command` only when you need custom access logic. The hook receives each incoming message with sender info via stdin. Your script returns `{"action": "allow"}` or `{"action": "deny", "message": "reason"}`. Without `--allowed-user-id` or `--hook-command`, everything is auto-approved, so restrict Telegram bots that can reach a running Cline instance.
The `--hook-command` receives each incoming message with sender info via stdin. Your script returns `{"action": "allow"}` or `{"action": "deny", "message": "reason"}`. Without `--hook-command`, everything is auto-approved.
## Slack
Slack supports webhook mode and socket mode. Each Slack thread maps to an agent session, so the agent maintains conversation context within a thread.
Webhook mode requires a bot token, signing secret, and public base URL:
Requires a bot token, signing secret, and public base URL.
```bash
cline connect slack \
--bot-token <BOT-TOKEN> \
--signing-secret <SECRET> \
--base-url <URL>
cline connect slack --token <BOT-TOKEN> --signing-secret <SECRET> --base-url <URL>
```
Configure the Slack app's event subscription and interactivity request URLs to `<URL>/api/webhooks/slack`.
Socket mode requires a bot token and an app-level token with the `connections:write` scope:
```bash
cline connect slack \
--bot-token <BOT-TOKEN> \
--app-token <APP-LEVEL-TOKEN>
```
Enable Socket Mode in the Slack app. Socket mode does not need a public request URL and is single-workspace only.
Each Slack thread maps to an agent session, so the agent maintains conversation context within a thread.
## Discord
@@ -280,7 +257,7 @@ Multiple connectors can run simultaneously. They all share the same hub:
cline connect telegram -k $TELEGRAM_TOKEN
# Terminal 2
cline connect slack --bot-token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
cline connect slack --token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
```
Connectors require the hub. Start it with `cline hub start` if it doesn't auto-start.
-213
View File
@@ -1,213 +0,0 @@
---
title: "Supply-Chain Scan Alerts"
description: "Schedule the Cline CLI to scan your machine for compromised packages with Bumblebee and text you on Telegram when it finds one."
---
npm worms like Shai-Hulud spread through install scripts: the moment you run `npm install`, a `preinstall` hook executes and steals your npm, GitHub, AWS, and SSH credentials. New campaigns are reported almost every week.
This guide wires three pieces together so your machine checks itself automatically and pings your phone only when it matters:
- [Bumblebee](https://github.com/perplexityai/bumblebee), Perplexity's open-source, read-only supply-chain scanner. It maintains catalogs of recent campaigns and checks whether any compromised package or version is present on disk.
- The Cline CLI scheduler, which runs an agent on a cron schedule.
- The Cline CLI Telegram connector, which delivers the result to a chat.
The end result: every morning, a Cline agent pulls the latest threat intelligence, runs a read-only scan, and texts you a green check if you are clean or a red alert with details if you are exposed.
```mermaid
flowchart TD
cron["cline schedule (daily)"] --> agent["Cline agent"]
agent --> pull["git pull (latest catalogs)"]
agent --> scan["bumblebee scan (read-only)"]
scan --> q{"any findings?"}
q -- "no" --> clean["✅ Clean"]
q -- "yes" --> alert["🚨 Compromise detected"]
clean --> tg["Telegram on your phone"]
alert --> tg
```
## How Bumblebee works
Bumblebee answers one narrow question fast: when an advisory names a package and version, is it present on this machine right now?
The important design choice is that it is read-only. A scanner that runs `npm`, `pnpm`, or `pip` to enumerate your dependencies would trigger the very install-script payload it is looking for. Bumblebee never does that. It only reads metadata files directly:
| Surface | What it reads |
|---|---|
| npm / pnpm / yarn / bun | lockfiles and installed `package.json` metadata |
| PyPI | `*.dist-info/METADATA`, `*.egg-info/PKG-INFO` |
| Go modules | `go.sum`, `go.mod` |
| RubyGems | `Gemfile.lock`, installed gemspecs |
| Composer | `composer.lock`, `vendor/composer/installed.json` |
| MCP servers | `mcp.json`, `claude_desktop_config.json`, and similar configs |
| Editor extensions | VS Code-family extension manifests |
| Browser extensions | Chromium-family and Firefox extension manifests |
It never runs package managers, never executes install scripts or lifecycle hooks, and never reads your application source. It ships no bundled threat intelligence either: you point it at an exposure catalog, and it reports exact `(ecosystem, name, version)` matches.
The catalogs live in the repo under `threat_intel/`, maintained by Perplexity and updated via pull requests as new campaigns are reported. That is why this automation simply pulls the latest before each scan: a `git pull` is all it takes to stay current.
Read the announcement: [Perplexity is open-sourcing Bumblebee](https://www.perplexity.ai/hub/blog/perplexity-is-open-sourcing-bumblebee).
## Prerequisites
- Node.js 22 or newer (for the Cline CLI).
- Go 1.22 or newer (to build Bumblebee).
- A Telegram account.
- An AI provider key, or a Cline account.
## 1. Install the Cline CLI
```bash
npm install -g cline
cline # run once to configure inference provider and model
```
## 2. Clone and build Bumblebee
Clone the repository somewhere stable. The clone is both the scanner and the catalog source, so the scheduled job will run from inside it.
```bash
mkdir -p ~/tools
git clone https://github.com/perplexityai/bumblebee.git ~/tools/bumblebee
cd ~/tools/bumblebee
go build -o bumblebee ./cmd/bumblebee
```
Confirm it works with the built-in self test, which runs embedded fixtures and makes no network calls:
```bash
./bumblebee selftest
# selftest OK (2 findings in 1ms)
```
## 3. Run a scan manually
Point `--exposure-catalog` at the whole `threat_intel/` directory to use every maintained catalog at once. The `--findings-only` flag suppresses the full inventory so you only get matches.
```bash
cd ~/tools/bumblebee
./bumblebee scan --profile deep --root "$HOME" \
--exposure-catalog ./threat_intel/ \
--findings-only
```
Output is NDJSON, one JSON object per line. A match looks like this:
```json
{ "record_type": "finding", "severity": "critical", "ecosystem": "npm",
"package_name": "example-pkg", "version": "1.2.3",
"source_file": "/Users/you/code/app/pnpm-lock.yaml",
"evidence": "exact name+version match (version=1.2.3)" }
```
If you are clean, you get no `finding` records. Exit code is `0` on a successful run, `1` if the scan hit errors, `2` for bad arguments.
<Note>
Scan profiles control where Bumblebee looks. `baseline` checks standard global tool, editor, and browser locations. `project` scans your development directories (pass `--root ~/code`). `deep` walks whatever roots you give it, typically your whole home directory. Use `deep` for the most thorough "am I exposed anywhere" check, or `project` for a faster daily scan of your repos.
</Note>
## 4. Create a Telegram bot and start the connector
<Steps>
<Step title="Create a bot">
Open Telegram, start a chat with [@BotFather](https://t.me/BotFather), send `/newbot`, and follow the prompts. Copy the bot token it gives you (it looks like `7123456789:AAH...`). Treat it like a password.
</Step>
<Step title="Start the connector">
Run the connector and point its working directory at your Bumblebee clone, so the scheduled agent runs there:
```bash
cline connect telegram -k "<BOT-TOKEN>" --cwd ~/tools/bumblebee
```
Leave this process running. It polls Telegram and delivers scheduled results, so it must stay alive.
</Step>
<Step title="Open the chat">
In Telegram, search for your bot's username and send it any message (for example `/whereami`). This creates the thread binding that delivery needs.
</Step>
</Steps>
<Warning>
By default, anyone who finds your bot can message it and it will run tasks on your machine. Lock it down before leaving the connector running. The `cline connect` wizard can guide you through Telegram user ID setup, or you can message [@userinfobot](https://t.me/userinfobot) and restart the connector with your allowed user ID:
```bash
cline connect telegram -k "<BOT-TOKEN>" --cwd ~/tools/bumblebee \
--allowed-user-id 12345
```
Replace `12345` with your Telegram user ID.
</Warning>
## 5. Schedule the scan
There are two ways to create the scheduled scan. Both run the same agent and deliver the result to Telegram, so pick whichever you prefer.
### Option A: From the Telegram chat
Creating the schedule from the chat automatically targets that thread for delivery, so results come straight back to you. Send this to your bot as a single message:
```text
/schedule create "supply-chain-watch" --cron "0 8 * * *" --prompt "Pull the latest Bumblebee catalogs and scan this machine for compromised packages. Run: git pull --quiet && go build -o bumblebee ./cmd/bumblebee && ./bumblebee scan --profile deep --root $HOME --exposure-catalog ./threat_intel/ --findings-only. Read the NDJSON output. If any line has record_type set to finding, reply starting with '🚨 COMPROMISE DETECTED' and list each package name, version, ecosystem, and source_file. If there are no findings, reply with exactly '✅ Clean: no compromised packages found.'"
```
The bot replies with the new schedule, including its id.
### Option B: From your terminal
Create the schedule with the Cline CLI on the same machine and pass the delivery method explicitly. The running Telegram connector delivers the result to its chat:
```bash
cline schedule create "supply-chain-watch" \
--cron "0 8 * * *" \
--workspace ~/tools/bumblebee \
--delivery-adapter telegram \
--delivery-bot <bot-username> \
--prompt "Pull the latest Bumblebee catalogs and scan this machine for compromised packages. Run: git pull --quiet && go build -o bumblebee ./cmd/bumblebee && ./bumblebee scan --profile deep --root \$HOME --exposure-catalog ./threat_intel/ --findings-only. Read the NDJSON output. If any line has record_type set to finding, reply starting with '🚨 COMPROMISE DETECTED' and list each package name, version, ecosystem, and source_file. If there are no findings, reply with exactly '✅ Clean: no compromised packages found.'"
```
Either way, this schedules a daily scan at 8am.
## Why the green check matters
Scheduled delivery always sends the run's final reply, so the prompt is written to make that reply meaningful either way:
- Clean run: one line, `✅ Clean: no compromised packages found.` You get a daily heartbeat confirming the scan actually ran.
- Exposure: `🚨 COMPROMISE DETECTED` followed by the package, version, and the file where it was found, so you can act immediately (rotate credentials, remove the package, pin a safe version).
## Test it
Trigger the scan now instead of waiting for 8am.
First find your schedule id. The create step returns it (the Telegram bot's reply shows `id=...`), or list your schedules at any time:
```bash
cline schedule list
```
Then trigger it with that id. From the terminal:
```bash
cline schedule trigger <schedule-id>
```
Or from Telegram: `/schedule trigger <schedule-id>`. Within a few seconds you should get the result in your chat.
To see a real alert, add a package and version that matches a catalog entry to a throwaway project's lockfile and run the scan against it. Bumblebee reports the match, and the agent texts you the red alert.
## Keep it running
- The connector process (`cline connect telegram`) must stay running for delivery to work. Run it under a process manager (systemd, launchd, `pm2`, or a `tmux`/`screen` session) so it survives reboots.
- The hub runs the schedule and starts automatically when you create one. If it is not running, start it with `cline hub start`.
- Manage schedules anytime with `cline schedule list`, `cline schedule pause <id>`, `cline schedule resume <id>`, and `cline schedule delete <id>`.
## Customize
- Cadence: change the cron expression. `0 */6 * * *` scans every six hours; `0 8 * * MON-FRI` runs on weekdays only.
- Scope: swap `--profile deep --root $HOME` for `--profile project --root ~/code` for a faster scan of just your repos, or `--profile baseline` for global tools, editors, and browser extensions.
- Channels: the same delivery pattern works for Slack, Discord, WhatsApp, and Google Chat. See [Connectors](/cli/connectors).
- Fleet use: Bumblebee can `POST` NDJSON to an ingest endpoint with `--output http --http-url <url>` if you want to centralize findings across many machines.
## Credits
Bumblebee is built and open-sourced by Perplexity. See the [announcement](https://www.perplexity.ai/hub/blog/perplexity-is-open-sourcing-bumblebee) and the [repository](https://github.com/perplexityai/bumblebee).
+1 -2
View File
@@ -114,8 +114,7 @@
"cli/samples/github-issue-rca",
"cli/samples/github-integration",
"cli/samples/github-pr-review",
"cli/samples/model-orchestration",
"cli/samples/supply-chain-alerts"
"cli/samples/model-orchestration"
]
},
"cli/acp-editor-integrations"
+1 -1
View File
@@ -82,7 +82,7 @@ agent.subscribe((event) => {
})
```
For a complete working example of streaming agent events to a browser via SSE, see the [multi-agent example](https://github.com/cline/cline/tree/main/apps/examples/multi-agent). It spawns multiple agents in parallel and streams each agent's events to separate UI cards.
For a complete working example of streaming agent events to a browser via SSE, see the [multi-agent example](https://github.com/cline/cline/tree/main/sdk/apps/examples/multi-agent). It spawns multiple agents in parallel and streams each agent's events to separate UI cards.
## Usage Tracking Pattern
+6 -6
View File
@@ -4,12 +4,12 @@ sidebarTitle: "Examples"
description: "Explore complete, runnable SDK examples organized by difficulty."
---
Working examples are available in the [SDK repository](https://github.com/cline/cline/tree/main/apps/examples), organized by difficulty:
Working examples are available in the [SDK repository](https://github.com/cline/cline/tree/main/sdk/apps/examples), organized by difficulty:
| Example | Difficulty | Description |
|---------|------------|-------------|
| [quickstart](https://github.com/cline/cline/tree/main/apps/examples/quickstart) | Beginner | Send one prompt, stream the response (~15 lines) |
| [cli-agent](https://github.com/cline/cline/tree/main/apps/examples/cli-agent) | Beginner | Interactive terminal chat with a shell tool |
| [code-review-bot](https://github.com/cline/cline/tree/main/apps/examples/code-review-bot) | Intermediate | AI code reviewer with custom tools and structured output |
| [multi-agent](https://github.com/cline/cline/tree/main/apps/examples/multi-agent) | Advanced | Parallel agents with streaming web UI |
| [desktop-app](https://github.com/cline/cline/tree/main/apps/examples/desktop-app) | Advanced | Full Tauri + Next.js desktop app |
| [quickstart](https://github.com/cline/cline/tree/main/sdk/apps/examples/quickstart) | Beginner | Send one prompt, stream the response (~15 lines) |
| [cli-agent](https://github.com/cline/cline/tree/main/sdk/apps/examples/cli-agent) | Beginner | Interactive terminal chat with a shell tool |
| [code-review-bot](https://github.com/cline/cline/tree/main/sdk/apps/examples/code-review-bot) | Intermediate | AI code reviewer with custom tools and structured output |
| [multi-agent](https://github.com/cline/cline/tree/main/sdk/apps/examples/multi-agent) | Advanced | Parallel agents with streaming web UI |
| [desktop-app](https://github.com/cline/cline/tree/main/sdk/apps/examples/desktop-app) | Advanced | Full Tauri + Next.js desktop app |
+5 -5
View File
@@ -4,7 +4,7 @@ sidebarTitle: "Building an Agent"
description: "Walk through a complete code review bot that reads diffs, analyzes code, and produces structured feedback."
---
This tutorial walks through the [code-review-bot example](https://github.com/cline/cline/tree/main/apps/examples/code-review-bot) from the SDK repository. By the end, you'll understand how to combine custom tools, system prompts, completion lifecycle, and event streaming into a real application.
This tutorial walks through the [code-review-bot example](https://github.com/cline/cline/tree/main/sdk/apps/examples/code-review-bot) from the SDK repository. By the end, you'll understand how to combine custom tools, system prompts, completion lifecycle, and event streaming into a real application.
## What It Builds
@@ -24,11 +24,11 @@ A code review agent that:
```bash
git clone https://github.com/cline/cline.git
cd cline/apps/examples/code-review-bot
cd cline/sdk/apps/examples/code-review-bot
bun install
```
Or read along with the [source on GitHub](https://github.com/cline/cline/blob/main/apps/examples/code-review-bot/src/index.ts).
Or read along with the [source on GitHub](https://github.com/cline/cline/blob/main/sdk/apps/examples/code-review-bot/src/index.ts).
## How It Works
@@ -153,10 +153,10 @@ From here, you could:
## More Examples
<CardGroup cols={2}>
<Card title="CLI Agent" icon="terminal" href="https://github.com/cline/cline/tree/main/apps/examples/cli-agent">
<Card title="CLI Agent" icon="terminal" href="https://github.com/cline/cline/tree/main/sdk/apps/examples/cli-agent">
Interactive terminal chat with tools and multi-turn conversation.
</Card>
<Card title="Multi-Agent" icon="users" href="https://github.com/cline/cline/tree/main/apps/examples/multi-agent">
<Card title="Multi-Agent" icon="users" href="https://github.com/cline/cline/tree/main/sdk/apps/examples/multi-agent">
Parallel agents streaming to a web UI.
</Card>
</CardGroup>
+2 -2
View File
@@ -34,7 +34,7 @@ const getCurrentTime = createTool({
The SDK converts the zod schema to JSON Schema automatically. Input is fully typed in the `execute` function.
For working examples of tools in real agents, see the [cli-agent](https://github.com/cline/cline/tree/main/apps/examples/cli-agent) (shell tool with zod) and [code-review-bot](https://github.com/cline/cline/tree/main/apps/examples/code-review-bot) (multiple tools with completion lifecycle).
For working examples of tools in real agents, see the [cli-agent](https://github.com/cline/cline/tree/main/sdk/apps/examples/cli-agent) (shell tool with zod) and [code-review-bot](https://github.com/cline/cline/tree/main/sdk/apps/examples/code-review-bot) (multiple tools with completion lifecycle).
## Anatomy of a Tool
@@ -166,7 +166,7 @@ const submitResult = createTool({
})
```
See the [code-review-bot example](https://github.com/cline/cline/tree/main/apps/examples/code-review-bot) for this pattern in a complete application.
See the [code-review-bot example](https://github.com/cline/cline/tree/main/sdk/apps/examples/code-review-bot) for this pattern in a complete application.
## Testing Tools
-15
View File
@@ -284,21 +284,6 @@ Users can also install package plugins from git, npm, or a local path:
cline plugin install https://github.com/your-org/cline-github-plugin.git
```
## Bundling Skills
Package plugins can include skills by adding a top-level `skills/` directory next to `package.json`:
```txt
cline-github-plugin/
package.json
github-plugin.ts
skills/
triage/
SKILL.md
```
Each bundled skill follows the same directory format as any other [Cline skill](/customization/skills). When the plugin is installed or loaded through `pluginPaths`, Cline discovers those skills automatically.
See [Plugins](/customization/plugins) for the full manifest format, directory layout, and the [typescript-lsp-plugin](https://github.com/cline/typescript-lsp-plugin) for a complete working example.
## Plugin Design Guidelines
+1 -1
View File
@@ -50,7 +50,7 @@ const result = await agent.run("Explain what an SDK is in two sentences.")
```
<Note>
Here is a complete [quickstart example](https://github.com/cline/cline/tree/main/apps/examples/quickstart). Clone it and run `bun dev` to try it.
Here is a complete [quickstart example](https://github.com/cline/cline/tree/main/sdk/apps/examples/quickstart). Clone it and run `bun dev` to try it.
</Note>
## Packages
+1 -1
View File
@@ -72,7 +72,7 @@ const searchDatabase = createTool({
})
```
For complete examples of tools in action, see the [cli-agent](https://github.com/cline/cline/tree/main/apps/examples/cli-agent) (shell tool) and [code-review-bot](https://github.com/cline/cline/tree/main/apps/examples/code-review-bot) (multiple tools with completion lifecycle).
For complete examples of tools in action, see the [cli-agent](https://github.com/cline/cline/tree/main/sdk/apps/examples/cli-agent) (shell tool) and [code-review-bot](https://github.com/cline/cline/tree/main/sdk/apps/examples/code-review-bot) (multiple tools with completion lifecycle).
For a full tutorial, see [Creating Custom Tools](/sdk/guides/creating-custom-tools). For exact types, see [Tools API](/sdk/reference/tools-api).
+1 -1
View File
@@ -235,7 +235,7 @@ npx tsx evals/smoke-tests/run-smoke-tests.ts --trials 1
### Re-enabling CI
The old workflow built the legacy CLI from `cli/`. Re-enable it only after replacing that build step with the SDK CLI under `apps/cli`.
The old workflow built the legacy CLI from `cli/`. Re-enable it only after replacing that build step with the SDK CLI under `sdk/apps/cli`.
## Contract Tests (Layer 1)
+1 -1
View File
@@ -419,7 +419,7 @@ async function main() {
console.error("ERROR: cline CLI not found in PATH")
console.error("")
console.error("For local development:")
console.error(" Install cline from npm or link the SDK CLI from apps/cli")
console.error(" Install cline from npm or link the SDK CLI from sdk/apps/cli")
console.error("")
console.error("For CI:")
console.error(" Ensure a cline binary is available on PATH")
+11 -16
View File
@@ -3,11 +3,10 @@
"private": true,
"workspaces": [
"sdk/packages/*",
"apps/*",
"!apps/vscode",
"apps/cline-hub/src/webview",
"apps/examples/*",
"apps/examples/vscode/src/webview",
"sdk/apps/*",
"sdk/apps/cline-hub/src/webview",
"sdk/apps/examples/*",
"sdk/apps/examples/vscode/src/webview",
"sdk/examples",
"sdk/examples/plugins/*"
],
@@ -15,24 +14,24 @@
"prepare": "husky",
"build": "bun run clean && bun install && bun run build:sdk && bun -F @cline/cli build",
"build:sdk": "bun --production -F './sdk/packages/*' build",
"build:apps": "bun -F './apps/**' --production build",
"build:apps": "bun -F './sdk/apps/**' --production build",
"build:models": "bun -F @cline/llms generate:models && bun format --write",
"dev": "bun --conditions=development run build:sdk && bun run cli && bun run cli hub stop",
"cli": "bun --conditions=development --cwd apps/cli dev",
"cli": "bun --conditions=development --cwd sdk/apps/cli dev",
"code": "bun --conditions=development -F @cline/code dev",
"clean": "bun run sdk/scripts/clean.ts",
"types": "bun --parallel -F '*' typecheck",
"test": "bun --parallel -F './sdk/packages/**' -F @cline/cli test",
"test": "bun --parallel -F './sdk/packages/**' -F './sdk/apps/cli' test",
"test:unit": "bash -lc 'set -euo pipefail; bun -F @cline/agents test & p1=$!; bun -F @cline/llms test & p2=$!; bun -F @cline/core test:unit & p3=$!; bun -F @cline/cli test:unit & p4=$!; wait $p1; wait $p2; wait $p3; wait $p4'",
"test:e2e": "bun -F @cline/core test:e2e && bun -F @cline/cli test:e2e",
"test:e2e:interactive": "bun -F @cline/cli test:e2e:interactive",
"verify:routines": "zsh -lc 'cd sdk/packages/core && bunx vitest run src/cron/schedule-service.test.ts --config vitest.config.ts'",
"verify:workos-device-auth": "bun sdk/scripts/verify-workos-device-auth.ts",
"biome": "bunx --bun @biomejs/biome",
"format": "bun biome format sdk/ apps/cli/ apps/cline-hub/ apps/examples/",
"lint": "bun biome lint sdk/ apps/cli/ apps/cline-hub/ apps/examples/",
"fix": "bun biome check --write --unsafe --diagnostic-level=error sdk/ apps/cli/ apps/cline-hub/ apps/examples/",
"check": "bun biome check --diagnostic-level=error sdk/ apps/cli/ apps/cline-hub/ apps/examples/ && bun run build:sdk && bun run -F @cline/cli build && bun --parallel -F './sdk/packages/**' -F @cline/cli typecheck && bun sdk/scripts/check-publish.ts",
"format": "bun biome format sdk/",
"lint": "bun biome lint sdk/",
"fix": "bun biome check --write --unsafe --diagnostic-level=error sdk/",
"check": "bun biome check --diagnostic-level=error sdk/ && bun run build:sdk && bun run -F @cline/cli build && bun --parallel -F './sdk/packages/**' -F @cline/cli typecheck && bun sdk/scripts/check-publish.ts",
"version": "bun run types && bun sdk/scripts/version.ts",
"release": "bun sdk/scripts/release.ts"
},
@@ -40,10 +39,6 @@
"sdk/**": [
"sh -c 'bun run types'",
"bun biome check --no-errors-on-unmatched --files-ignore-unknown=true"
],
"apps/{cli,cline-hub,examples}/**": [
"sh -c 'bun run types'",
"bun biome check --no-errors-on-unmatched --files-ignore-unknown=true"
]
},
"module": "index.ts",
+3
View File
@@ -17,6 +17,9 @@ pnpm-lock.yaml
.actrc
CLAUDE.local.md
apps/vscode/webview-ui/src/**/*.js
apps/vscode/webview-ui/src/**/*.js.map
# Ignore coverage directories and files
coverage
coverage-unit
@@ -6,18 +6,15 @@
{
"id": "sdk-tool-handler-telemetry",
"rule": "Any new tool handler added to packages/agents/src or packages/core/src that performs a user-visible action (writes files, executes commands, modifies state, calls external APIs) must include a call to captureToolUsage() from packages/core/src/services/telemetry/core-events.ts, or emit a task.tool_used event via telemetry.capture(). Pure read-only helpers and getters are exempt. When in doubt, prefer instrumentation.",
"scope": [
"sdk/packages/agents/src/**",
"sdk/packages/core/src/**"
],
"scope": ["packages/agents/src/**", "packages/core/src/**"],
"severity": "high"
},
{
"id": "sdk-session-lifecycle-telemetry",
"rule": "New session start, end, or state-transition code paths in packages/core/src must call the appropriate typed helper from packages/core/src/services/telemetry/core-events.ts (captureTaskCreated, captureTaskCompleted, captureConversationTurnEvent, captureTokenUsage, etc.). Do not inline raw telemetry.capture() calls for session lifecycle events — always use the typed helper, which guarantees a consistent payload shape.",
"scope": [
"sdk/packages/core/src/cline-core/**",
"sdk/packages/core/src/runtime/**"
"packages/core/src/cline-core/**",
"packages/core/src/runtime/**"
],
"severity": "high"
},
@@ -25,8 +22,8 @@
"id": "sdk-no-raw-event-strings",
"rule": "All telemetry event name strings must be sourced from CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts. If a PR introduces a string literal in a telemetry.capture(), telemetry.captureRequired(), or recordCounter()/recordHistogram()/recordGauge() call that does not reference CORE_TELEMETRY_EVENTS, flag it. New events must be added to CORE_TELEMETRY_EVENTS first, with a typed capture helper created alongside them.",
"scope": [
"sdk/packages/core/src/**",
"sdk/packages/agents/src/**",
"packages/core/src/**",
"packages/agents/src/**",
"apps/cli/src/**",
"apps/vscode/src/**"
],
@@ -35,17 +32,13 @@
{
"id": "sdk-auth-telemetry-completeness",
"rule": "Any new OAuth or authentication provider added under packages/core/src/auth must emit all four lifecycle events using the typed helpers from core-events.ts: captureAuthStarted (at flow entry), captureAuthSucceeded + identifyAccount (on token success), captureAuthFailed (on error), and captureAuthLoggedOut (on token invalidation or explicit logout). Flag PRs that introduce a new auth flow file without all four. Cross-reference packages/core/src/auth/cline.ts and packages/core/src/auth/codex.ts as canonical examples.",
"scope": [
"sdk/packages/core/src/auth/**"
],
"scope": ["packages/core/src/auth/**"],
"severity": "high"
},
{
"id": "sdk-telemetry-doc-update",
"rule": "Any PR that adds new event constants to CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts, adds new capture* helper functions, or changes the payload shape of an existing event must update the Event Catalog section in DOC.md. Flag PRs that modify core-events.ts without a corresponding change to DOC.md.",
"scope": [
"sdk/packages/core/src/services/telemetry/core-events.ts"
],
"scope": ["packages/core/src/services/telemetry/core-events.ts"],
"severity": "medium"
}
]
@@ -1,19 +1,19 @@
{
"files": [
{
"path": "sdk/packages/core/src/services/telemetry/core-events.ts",
"path": "packages/core/src/services/telemetry/core-events.ts",
"description": "Single source of truth for all telemetry event names (CORE_TELEMETRY_EVENTS) and their typed capture helper functions. Every PR touching telemetry must be evaluated against this catalog. New events must be defined here first."
},
{
"path": "sdk/packages/shared/src/services/telemetry.ts",
"path": "packages/shared/src/services/telemetry.ts",
"description": "ITelemetryService interface definition. Defines the contract all telemetry implementations must satisfy (capture, captureRequired, recordCounter, recordHistogram, recordGauge, flush, dispose)."
},
{
"path": "sdk/packages/core/src/services/telemetry/TelemetryService.ts",
"path": "packages/core/src/services/telemetry/TelemetryService.ts",
"description": "Reference implementation of ITelemetryService used by all hosts. Multi-adapter fan-out service that forwards events to OpenTelemetry."
},
{
"path": "sdk/packages/core/src/services/telemetry/OpenTelemetryProvider.ts",
"path": "packages/core/src/services/telemetry/OpenTelemetryProvider.ts",
"description": "OpenTelemetry-backed provider that wires logs/metrics/traces exporters. Contains createConfiguredTelemetryService and createConfiguredTelemetryHandle, the canonical factories every host should use."
},
{
@@ -21,11 +21,11 @@
"description": "Public API and event documentation. The Event Catalog and 'Activation funnel' sections must be kept in sync with core-events.ts. Host integration rules (CLI dir ordering, hub daemon metadata forwarding) are documented here."
},
{
"path": "sdk/ARCHITECTURE.md",
"path": "ARCHITECTURE.md",
"description": "Architecture reference. Telemetry design decisions, completion semantics (submit_and_exit anchoring), and hub-daemon telemetry forwarding are documented here. Use as ground truth for design intent."
},
{
"path": "sdk/AGENTS.md",
"path": "AGENTS.md",
"description": "Package boundary rules. Telemetry runtime services live in @cline/core; @cline/agents must not own stateful telemetry. Use to evaluate whether a telemetry change is being made in the correct package."
}
]

Some files were not shown because too many files have changed in this diff Show More