mirror of
https://github.com/cline/cline.git
synced 2026-09-04 20:02:30 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43ddc8c846 |
+13
-9
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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"]);
|
||||
});
|
||||
});
|
||||
@@ -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.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+48
-23
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
+23
-3
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+1323
-1986
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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":
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user