Compare commits

...

42 Commits

Author SHA1 Message Date
Saoud Rizwan 9da3c59553 fix(vscode): update Claude Sonnet 5 pricing (#12009)
* fix(vscode): update Claude Sonnet 5 pricing

* fix(vscode): restore Claude Sonnet 4.6 pricing
2026-06-30 12:48:11 -07:00
Saoud Rizwan 0e01b99e76 chore(vscode): bump to 4.0.5 for legacy release 2026-06-30 12:28:05 -07:00
Saoud Rizwan ba5f22f58c feat(vscode): add Claude Sonnet 5 support (#12007) 2026-06-30 12:26:30 -07:00
Saoud Rizwan 1cbfc7197e chore(vscode): bump to 4.0.4 for legacy release 2026-06-29 13:15:03 -07:00
Tomás Barreiro 4d8f9ea1a3 Remove feature flag from the extension (#11987)
* Remove feature flag from the extension

* fix(vscode): preserve ClinePass provider metadata

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-29 13:07:42 -07:00
Saoud Rizwan 40b0b0c236 chore(vscode): bump to 4.0.3 for legacy release 2026-06-29 10:20:41 -07:00
Tomás Barreiro 8c5e1289bf Remove handler flag (#11978) 2026-06-29 10:15:45 -07:00
Saoud Rizwan 4cddde58ed chore(vscode): bump to 4.0.2 for legacy release 2026-06-28 20:54:04 -07:00
Saoud Rizwan 9d45accc3b fix: default focus chain settings in webview state (#11960)
* fix: default focus chain settings in webview state

* fix: default missing global settings from state manager

* Revert "fix: default missing global settings from state manager"

This reverts commit 76a628689e.
2026-06-28 20:52:52 -07:00
Saoud Rizwan 57760c1e20 fix: polish ClinePass and Z AI model metadata (#11958) 2026-06-28 20:11:11 -07:00
Tomás Barreiro fc33e8dbd9 Fix webview env replacing (#11955) 2026-06-28 18:37:02 -07:00
Saoud Rizwan b1dcaf576a fix(vscode): align ClinePass model resolution and reasoning controls (#11954)
* fix(vscode): show reasoning effort for ClinePass models

* fix(vscode): centralize reasoning effort support

* fix(vscode): normalize Cline recommended Z.ai ids

* fix(vscode): preserve Claude budget reasoning include flag

* fix(vscode): simplify ClinePass reasoning support

* fix(vscode): simplify reasoning effort model detection

* chore(vscode): remove unused ClinePass helper

* chore(vscode): avoid mutating ClinePass model info
2026-06-28 18:17:15 -07:00
Saoud Rizwan 419f2cea73 feat(vscode): improve ClinePass provider UX (#11950)
* feat(vscode): improve ClinePass provider UX

* fix(vscode): handle ClinePass action fallback
2026-06-28 16:33:44 -07:00
Saoud Rizwan 54d8c695a3 fix(vscode): prefer canonical Cline Z.ai model ids (#11951)
* fix(vscode): prefer canonical Cline Z.ai model ids

* fix(vscode): avoid redundant Cline model alias filtering
2026-06-28 16:13:04 -07:00
Saoud Rizwan 5a62ab8564 feat(deepseek): support reasoning effort (incl. xhigh) for thinking models (#11938)
* feat(deepseek): support reasoning effort (incl. xhigh) for thinking models

* fix(deepseek): don't forward 'none' reasoning effort to API

* fix(deepseek): scope reasoning effort selector
2026-06-28 10:55:26 -07:00
Saoud Rizwan f81afb51b5 chore(vscode): bump to 4.0.1 for legacy rollback release 2026-06-27 19:09:05 -07:00
John Choi 864419d8b0 Fix ClinePass onboarding model grouping (#11768) 2026-06-23 17:38:30 -07:00
Tomás Barreiro 218db38544 Add to the status bar [ENG-2216] (#11766)
* Add  to the status bar

* Update tests
2026-06-24 01:24:46 +02:00
Bee a3b3295461 fix(llms): includes session id for OpenRouter caching (#11744)
* Improve OpenRouter sticky session routing

OpenRouter prompt caching was getting weaker cache hit rates because requests did not include a stable session_id. Without that identifier, OpenRouter can route consecutive turns away from the same upstream cache context even when cache_control is present, causing more fresh input billing.

Propagate explicit runtime sessionId metadata from agents into model requests without synthesizing fallback session or conversation ids. Add provider metadata for sticky sessions so OpenRouter maps metadata.sessionId to the JSON body session_id field, while keeping the mechanism extensible for header-based providers.

Apply sticky-session metadata in the AI SDK provider fetch wrapper, preserving explicit wire values when callers already set them. Move trimNonEmpty and omitUndefinedValues into @cline/shared for reuse, and cover the JSON-body, header, and no-fallback paths with tests.

* Tighten sticky session fetch body handling

Treat init.body null as an explicit no-body override so the sticky-session wrapper does not inspect or parse a Request body that the caller has intentionally overridden.

Model fetch body text by source, which removes the unreachable JSON-body injection branch and makes the Request rewrite path explicit. Also type the provider fetch mocks so sticky-session assertions compile cleanly in editors.

* merges model-handle default metadata with per-request metadata
2026-06-23 12:07:17 -07:00
Max d09270940f fix(hub): validate dashboard websocket host and origin (#11724)
* fix(hub): validate browser websocket origins

* test(hub): cover websocket host and origin gates

* fix(hub): allow explicit bind host for dashboard socket

* fix(hub): default protect browser routes

* fix(hub): normalize default port origins

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-06-23 10:14:25 -07:00
Saoud Rizwan 23c9bf1c05 fix: prevent 'ERROR: EMPTY CONTENT' message when error occurs (#11726)
* fix: prevent empty assistant history replay

* refactor: move tool call filtering after empty guard
2026-06-23 09:33:10 -07:00
Bee 52531d935a refactor: add connector configure path and share catalog to shared pkg (#11730)
* feat(hub): add connector configure path and share catalog via @cline/shared

Add connector.channels/configure/delete_config Hub commands that persist
connector settings to disk without starting connector processes or calling
provider auth APIs. This means settings (including tokens) are saved as-is
without verification; callers are responsible for supplying valid values.

Shared catalog and platform definitions:
- Move ConnectorCatalogEntry, CONNECTOR_CATALOG, listConnectorCatalog, and
  all ConnectorPlatformDef/FieldDef/SecurityDef types + CONNECTOR_PLATFORMS
  into sdk/packages/shared/src/connectors/platforms.ts
- Export everything from @cline/shared index so CLI and Hub use the same
  definitions without duplication
- Reduce apps/cli/src/connectors/catalog.ts and
  apps/cli/src/wizards/connect/platforms.ts to thin re-export shims that
  preserve existing CLI import paths

New Hub connector handlers (sdk/packages/core):
- connector-handlers.ts: handles connector.channels (list available/active/
  configured), connector.configure (validate fields and write settings.json
  under ~/.cline/data/connectors/), connector.delete_config (remove entry
  and clean up empty file)
- Settings are stored as ConnectorSettingsFile (version 1) at
  ~/.cline/data/connectors/settings.json; reads are lenient/defensive
- Wire handlers into hub-server-transport.ts dispatch switch
- connector-handlers.test.ts: unit tests for configure, channels, and
  delete_config covering field validation, conditional fields, security
  constraints, and settings round-trips

Hub WebSocket auth helpers (hub-websocket-server.ts):
- Extract isLocalHubHostName / isLocalHubOrigin as named, tested exports
- Allow unauthenticated WebSocket upgrades from local origins (localhost,
  127.0.0.1, ::1) so the Hub UI can connect without an auth token
- hub-websocket-server.test.ts: extend tests to cover new local-origin logic

Add connector.channels, connector.configure, connector.delete_config to
HubCommandName union in sdk/packages/shared/src/hub.ts

* apply feedback

* export connector settings json path

* apply feedback from robin
2026-06-22 22:25:49 -07:00
Tomás Barreiro 19d4248381 Add Organization error messages to the Cline CLI (#11737)
* Add Organization error messages to the Cline CLI

* Fix tests

* Fix tests

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-22 17:18:29 -07:00
Tomás Barreiro b2b29678a2 Add a button to unselect an org on ClinePass error (#11738)
* Add a button to unselect an org on ClinePass error

* Fix comments
2026-06-23 01:47:15 +02:00
Bee ad30714608 fix(hub-ui): Use development mode conditionally for connector (#11728)
* fix(hub-ui): Use development mode conditionally for connector

Refactor connector CLI launch logic to conditionally apply Bun-specific
flags (`--conditions=development`) only when running under Bun or Node
runtimes with access to the source entrypoint. When running from a
compiled binary (e.g., packaged Cline app), use the execPath directly
without Bun flags.

- Extract `buildCliConnectCommand` function to encapsulate launcher
  and args resolution logic based on runtime detection
- Use `withResolvedClineBuildEnv` for environment variable setup
- Export `__test__` object to enable unit testing of internal logic
- Add tests covering Bun source entrypoint and compiled binary cases

* address feedback
2026-06-22 16:41:47 -07:00
Bee 9eebc44f24 chore: Optimize Cline Hub webview bundle (#11731)
* Optimize Cline Hub webview bundle

Issue:

The Cline Hub webview build emitted Vite's large chunk warning. The main entry bundle was over 2 MB minified, and the build output included many Shiki language/theme chunks plus eagerly bundled Streamdown math/Mermaid support that the hub did not need on startup.

Changes:

- Split heavyweight Cline Hub views behind React.lazy/Suspense so settings, customization, and chat code do not all land in the initial app shell.

- Replace root Shiki highlighter usage with shiki/core and lazily loaded, explicitly supported languages/themes.

- Route Streamdown through a local HubStreamdown wrapper that uses the local CodeBlock renderer and keeps Mermaid as a lazy diagram plugin.

- Remove unused Streamdown math/code/Mermaid packages, add direct lazy Mermaid support, and add targeted Rolldown chunk groups for Mermaid parser/layout/markup assets.

Before/after bundle measurements:

| Metric | Before | After |

| --- | ---: | ---: |

| Total generated JS | 24,356 KiB | 5,800 KiB |

| Total generated JS gzip | 4,805.5 KiB | 1,434.6 KiB |

| Main entry chunk | 2,044.02 kB | 373.88 kB |

| Main entry gzip | 617.25 kB | 117.83 kB |

Verification:

- bun -F @cline/cline-hub build:webview

- bun biome check apps/cline-hub/src/webview/vite.config.ts apps/cline-hub/src/webview/src/App.tsx apps/cline-hub/src/webview/src/components/ai-elements/code-block.tsx apps/cline-hub/src/webview/src/components/ai-elements/message.tsx apps/cline-hub/src/webview/src/components/ai-elements/reasoning.tsx apps/cline-hub/src/webview/src/components/ai-elements/streamdown.tsx apps/cline-hub/src/webview/package.json

* address feedback
2026-06-22 16:40:01 -07:00
Tomás Barreiro 6ddf48d227 Forcefully send users to their personal dashboard when going to subscribe (#11725)
* Forcefully send users to their personal dashboard when going to subscribe

* fix trailing slash
2026-06-22 22:27:31 +02:00
Saoud Rizwan ee59f81706 chore(cli): release v3.0.29 2026-06-20 00:30:51 -07:00
Saoud Rizwan 92f6e28f13 fix(cli): hide costs for Cline free models (#11686)
* fix(cli): hide Cline free model costs

* fix(cli): harden free model cost lookup
2026-06-20 00:20:07 -07:00
Saoud Rizwan 27a78f0248 chore(sdk): release v0.0.51 2026-06-19 19:15:00 -07:00
Saoud Rizwan 2c4980f42a fix(sdk): resolve Cline Z.ai model metadata aliases (#11685)
* fix(sdk): resolve Cline Z.ai model metadata aliases

* fix(sdk): preserve Cline model alias overrides

* test(sdk): update Cline provider model list expectation
2026-06-19 19:05:47 -07:00
Saoud Rizwan 64c5e48edb Revert "fix(cli): resolve Cline model display names by just model name (#11668)" (#11684)
This reverts commit c497698beb.
2026-06-19 18:27:29 -07:00
Saoud Rizwan 3c23f80a94 fix(sdk): deflake run_commands timeout telemetry tests 2026-06-19 14:59:46 -07:00
Saoud Rizwan 4be362892f chore(cli): release v3.0.28 2026-06-19 14:52:50 -07:00
Saoud Rizwan cdff084652 chore(sdk): release v0.0.50 2026-06-19 14:11:25 -07:00
Robin Newhouse 299a4a9520 fix(sdk): harden parallel tool guidance (#11598)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-19 12:25:20 -07:00
Saoud Rizwan c497698beb fix(cli): resolve Cline model display names by just model name (#11668)
* fix(cli): resolve Cline model display names by slug

* fix(cli): type model display metadata

* test(cli): cover model display fallback paths
2026-06-19 12:21:57 -07:00
Ara 292133989b fix(sdk): cap assistant text in provider messages (#11478) 2026-06-19 11:41:04 -07:00
Ara f5aa035a6a fix(sdk): default-on tool result truncation, tightened limits + budget accounting in MessageBuilder (#11475)
* fix(sdk): default-on tool result truncation, name fallback, tool_use budget accounting

- Truncate every tool result (MCP/custom tools included), not just an allowlist
- Resolve tool names from tool_result.name when the paired tool_use is gone
- Count tool_use.input strings toward the aggregate provider request budget
- Protect any binary carrier block ({type, data}) from truncation, not just images
- Don't let failSession cleanup errors mask the original turn error

* fix(sdk): tighten MessageBuilder limits with named options and env overrides

Folds #11474 into the default-on truncation branch and addresses review
feedback from both PRs:

- per-result tool cap drops to 8k chars; MessageBuilderOptions constructor
  with CLINE_MESSAGE_BUILDER_* env overrides for A/B testing
- env parsing is positive-only so '=0' cannot silently disable a limit
  (greptile on #11474)
- aggregate budget stays at 6MB: budget truncation rewrites mid-transcript
  bytes and breaks provider prefix caching, so it must stay a rare overflow
  valve rather than the steady state (johnwschoi on #11474)
- user file attachments get a dedicated 50k cap instead of inheriting the
  aggressive tool-result cap (codex on #11474)
- isBinaryContentLike restricted to known binary block types so textual
  {type, data} payloads can no longer dodge every cap (codex/greptile)
- tool_use.input strings become last-resort budget truncation candidates,
  making the aggregate budget reclaimable when oversized model-generated
  arguments carry the overflow (robinnewhouse/greptile)

* fix(sdk): constrain binary tool result truncation

* style(sdk): remove disallowed comment formatting

* Revert "style(sdk): remove disallowed comment formatting"

This reverts commit 9d8b66726d.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-19 11:15:42 -07:00
Saoud Rizwan bbcae25542 fix(cli): refine marketplace primitive rows (#11659) 2026-06-19 10:57:37 -07:00
Tomás Barreiro 9b914912ac Rename Cline Pass to ClinePass (#11657) 2026-06-19 18:36:07 +02:00
Saoud Rizwan 8c369eeed9 docs(vscode): remove marketplace readme language links (#11658) 2026-06-18 18:09:02 -07:00
151 changed files with 8653 additions and 2098 deletions
+38
View File
@@ -1,5 +1,43 @@
# Changelog
## [4.0.5]
### Added
- Add support for Claude Sonnet 5 across the Anthropic, Bedrock, Vertex, Claude Code, SAP AI Core, OpenRouter, and Vercel AI Gateway providers, including model picker and recommended-model updates.
## [4.0.4]
### Changed
- Fully remove the ClinePass feature flag so ClinePass is available everywhere in the UI — onboarding, settings, the welcome promo banner, and the credit-limit "Switch to ClinePass" action.
## [4.0.3]
### Changed
- Enable the ClinePass provider for all users by removing the feature-flag gate that previously fell back to the standard Cline provider.
## [4.0.2]
### Added
- Add reasoning effort support (including `xhigh`) for DeepSeek thinking models.
- Improve the ClinePass provider experience with clearer reasoning controls and model selection.
### Fixed
- Show reasoning effort controls for ClinePass models and align ClinePass model resolution with the rest of the provider.
- Prefer canonical Cline Z.ai model ids and polish ClinePass and Z.ai model metadata.
- Fix environment variable replacement in the webview.
- Default focus chain settings in webview state so the toggle reflects the correct value on load.
## [4.0.1]
### Changed
- Roll the stable VS Code extension back to the pre-SDK-migration codebase to resolve regressions reported in 4.0.0. This release ships the 3.89.2 extension code under a higher version number so existing 4.0.0 users receive the update. SDK-migration work continues separately on `main`.
## [3.89.2]
### Fixed
+16
View File
@@ -1,5 +1,21 @@
# Cline CLI Changelog
## 3.0.29
- Costs are now hidden for Cline free models
- Fixed Z.ai model metadata resolution for Z.ai models accessed through the Cline provider
- Reverted the model-name-only display change from v3.0.28; the model picker, selector, and status bar return to their previous display behavior
## 3.0.28
- Added a ClinePass onboarding flow with selectable ClinePass models, plus improved ClinePass error handling
- Added hub primitive catalogs and refreshed the hub dashboard design with a dedicated customizations breakout
- Auto-approve toggles now apply immediately when changed
- Feature flags now resolve using your user ID on startup
- Fixed Cline model display names so they resolve by model name
- Truncate large tool results by default (including MCP and custom tool output) to keep requests within context budget
- Hardened parallel tool-call guidance for faster, more reliable multi-tool execution
## 3.0.27
- Added a `cline skill` command to install and manage skills, matching `cline plugin install` and `cline mcp` (installs default to the Cline agent directory)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/cli",
"displayName": "cline",
"version": "3.0.27",
"version": "3.0.29",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"type": "module",
"publishConfig": {
+2 -36
View File
@@ -1,36 +1,2 @@
export type ConnectorCatalogEntry = {
name: string;
description: string;
};
export const CONNECTOR_CATALOG: ConnectorCatalogEntry[] = [
{
name: "discord",
description:
"Discord interactions and gateway bridge backed by RPC runtime sessions",
},
{
name: "gchat",
description: "Google Chat webhook bridge backed by RPC runtime sessions",
},
{
name: "linear",
description: "Linear webhook bridge backed by RPC runtime sessions",
},
{
name: "slack",
description: "Slack webhook/socket bridge backed by RPC runtime sessions",
},
{
name: "telegram",
description: "Bridge Telegram bot messages into RPC chat sessions",
},
{
name: "whatsapp",
description: "Bridge WhatsApp webhook messages into RPC chat sessions",
},
];
export function listConnectorCatalog(): ConnectorCatalogEntry[] {
return CONNECTOR_CATALOG.map((entry) => ({ ...entry }));
}
export type { ConnectorCatalogEntry } from "@cline/shared";
export { CONNECTOR_CATALOG, listConnectorCatalog } from "@cline/shared";
+145 -2
View File
@@ -27,11 +27,25 @@ const outputMocks = vi.hoisted(() => ({
c: { dim: "", reset: "" },
}));
const sessionEventsMocks = vi.hoisted(() => ({
listener: undefined as ((event: unknown) => void) | undefined,
subscribeToAgentEvents: vi.fn(
(_: unknown, listener: (event: unknown) => void) => {
sessionEventsMocks.listener = listener;
return () => {};
},
),
}));
const CLINE_PASS_SUBSCRIPTION_URL =
"https://app.cline.bot/dashboard/subscription/";
"https://app.cline.bot/dashboard/subscription?personal=true";
const CLINE_PASS_SUBSCRIPTION_MESSAGE = `No access to ClinePass subscription models yet. Subscribe to ClinePass, the low cost open weights model coding plan: ${CLINE_PASS_SUBSCRIPTION_URL}`;
const CLINE_ORG_INDIVIDUAL_INFERENCE_SUBSCRIPTION_MESSAGE =
"Organization accounts cannot use ClinePass subscriptions. Go to /account -> change account to switch to your personal account for ClinePass";
vi.mock("@cline/core", () => ({
getClineOrgIndividualInferenceSubscriptionMessage: () =>
CLINE_ORG_INDIVIDUAL_INFERENCE_SUBSCRIPTION_MESSAGE,
getClinePassSubscriptionUrl: () => CLINE_PASS_SUBSCRIPTION_URL,
isClineNotSubscribedError: (error: unknown) =>
error instanceof Error && error.name === "ClineNotSubscribedError",
@@ -39,6 +53,15 @@ vi.mock("@cline/core", () => ({
text
.toLowerCase()
.includes("the user is not subscribed to required model plan"),
isClineOrgIndividualInferenceSubscriptionError: (error: unknown) =>
error instanceof Error &&
error.name === "ClineOrgIndividualInferenceSubscriptionError",
isClineOrgIndividualInferenceSubscriptionMessage: (text: string) =>
text
.toLowerCase()
.includes(
"organization accounts cannot use individual model inference subscriptions",
),
prewarmFileIndex: vi.fn(async () => undefined),
SessionSource: {
CLI: "cli",
@@ -88,7 +111,7 @@ vi.mock("./prompt", () => ({
}));
vi.mock("./session-events", () => ({
subscribeToAgentEvents: vi.fn(() => () => {}),
subscribeToAgentEvents: sessionEventsMocks.subscribeToAgentEvents,
}));
describe("runAgent", () => {
@@ -112,6 +135,9 @@ describe("runAgent", () => {
outputMocks.writeln.mockReset();
outputMocks.emitJsonLine.mockReset();
outputMocks.setActiveCliSession.mockReset();
sessionEventsMocks.listener = undefined;
sessionEventsMocks.subscribeToAgentEvents.mockClear();
vi.unstubAllGlobals();
});
afterEach(() => {
@@ -938,4 +964,121 @@ describe("runAgent", () => {
expect.stringContaining("est. cost"),
);
});
it("zeros Cline free model costs in JSON results and agent events", async () => {
const startedAt = new Date("2026-03-22T00:00:00.000Z");
const endedAt = new Date("2026-03-22T00:00:01.000Z");
vi.stubGlobal(
"fetch",
vi.fn(async () => {
return new Response(
JSON.stringify({
free: [{ id: "deepseek/deepseek-v4-flash" }],
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
}),
);
sessionManagerMocks.start.mockResolvedValue({
sessionId: "session-1",
manifestPath: "/tmp/manifest.json",
messagesPath: "/tmp/messages.json",
manifest: {
session_id: "session-1",
},
result: {
text: "completed text",
usage: {
inputTokens: 1,
outputTokens: 1,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0.25,
},
messages: [],
toolCalls: [],
iterations: 1,
finishReason: "completed",
model: {
id: "deepseek/deepseek-v4-flash",
provider: "cline",
info: {},
},
startedAt,
endedAt,
durationMs: 1000,
},
});
sessionManagerMocks.getAccumulatedUsage.mockResolvedValue({
usage: {
inputTokens: 1,
outputTokens: 1,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0.25,
},
aggregateUsage: {
inputTokens: 1,
outputTokens: 1,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0.25,
},
});
const { runAgent } = await import("./run-agent");
const { handleEvent } = await import("../utils/events");
await expect(
runAgent("test prompt", {
baseUrl: "https://cline.test/api/v1",
cwd: process.cwd(),
enableAgentTeams: false,
enableSpawnAgent: false,
enableTools: [],
execution: {
maxConsecutiveMistakes: 3,
},
logger: undefined,
mode: "yolo",
modelId: "deepseek/deepseek-v4-flash",
outputMode: "json",
providerId: "cline",
systemPrompt: "system",
thinking: false,
toolPolicies: { "*": { autoApprove: true } },
verbose: false,
workspaceRoot: process.cwd(),
} as never),
).resolves.toBeUndefined();
const runResult = outputMocks.emitJsonLine.mock.calls.find(
([, payload]) =>
(payload as { type?: string } | undefined)?.type === "run_result",
)?.[1] as
| {
usage?: { totalCost?: number };
aggregateUsage?: { totalCost?: number };
}
| undefined;
expect(runResult?.usage?.totalCost).toBe(0);
expect(runResult?.aggregateUsage?.totalCost).toBe(0);
sessionEventsMocks.listener?.({
type: "usage",
inputTokens: 1,
outputTokens: 1,
cost: 0.25,
totalCost: 0.25,
});
expect(handleEvent).toHaveBeenLastCalledWith(
expect.objectContaining({
type: "usage",
cost: 0,
totalCost: 0,
}),
expect.any(Object),
);
});
});
+16 -3
View File
@@ -18,6 +18,11 @@ import {
} from "../utils/approval";
import { formatCliErrorMessage } from "../utils/cline-pass-errors";
import { handleEvent, handleTeamEvent } from "../utils/events";
import {
shouldZeroClineFreeModelCost,
zeroCliAgentEventCost,
zeroCliUsageCost,
} from "../utils/free-model-cost";
import { createRuntimeHooks } from "../utils/hooks";
import {
c,
@@ -184,8 +189,10 @@ export async function runAgent(
let reasoningChunkCount = 0;
let redactedReasoningChunkCount = 0;
const displayedErrorMessages = new Set<string>();
const shouldZeroCost = await shouldZeroClineFreeModelCost(config);
const onAgentEvent = (event: AgentEvent): void => {
const onAgentEvent = (rawEvent: AgentEvent): void => {
const event = zeroCliAgentEventCost(rawEvent, shouldZeroCost);
if (event.type === "content_start" && event.contentType === "reasoning") {
reasoningChunkCount += 1;
if (event.redacted) {
@@ -339,8 +346,14 @@ export async function runAgent(
const usageSummary = await sessionManager.getAccumulatedUsage(
started.sessionId,
);
const aggregateUsage = usageSummary?.aggregateUsage;
const usage = aggregateUsage ?? usageSummary?.usage ?? result.usage;
const aggregateUsage = zeroCliUsageCost(
usageSummary?.aggregateUsage,
shouldZeroCost,
);
const usage = zeroCliUsageCost(
aggregateUsage ?? usageSummary?.usage ?? result.usage,
shouldZeroCost,
);
if (config.outputMode === "json") {
emitJsonLine("stdout", {
+18 -4
View File
@@ -24,6 +24,11 @@ import { disableOpenTuiGraphicsProbe } from "../tui/opentui-env";
import type { QueuedPromptItem } from "../tui/types";
import { type ChatCommandState, chatCommandHost } from "../utils/chat-commands";
import { applyCliCompactionMode } from "../utils/compaction-mode";
import {
shouldZeroClineFreeModelCost,
zeroCliAgentEventCost,
zeroCliUsageCost,
} from "../utils/free-model-cost";
import {
prepareTerminalForPostTuiOutput,
writeErr,
@@ -153,6 +158,7 @@ export async function runInteractive(
askQuestionRef: tuiAskQuestion,
});
const providerSettingsManager = new ProviderSettingsManager();
let zeroCurrentTurnCost = false;
const sessionRuntime = createInteractiveSessionRuntime({
config,
@@ -166,7 +172,7 @@ export async function runInteractive(
resolveMistakeLimitDecision,
switchToActModeTool,
onAgentEvent: (event) => {
uiEvents.emit("agent", event);
uiEvents.emit("agent", zeroCliAgentEventCost(event, zeroCurrentTurnCost));
},
onTeamEvent: (event) => {
uiEvents.emit("team", event);
@@ -432,6 +438,7 @@ export async function runInteractive(
},
onSubmit: async (input, mode, delivery, attachments, onCommandOutput) => {
let commandOutput: string | undefined;
let zeroTurnCost = false;
try {
await sessionRuntime.ensureReady();
await waitForSubmittedMode(mode);
@@ -478,6 +485,8 @@ export async function runInteractive(
}
input = chatCommandResult.input;
commandOutput = chatCommandResult.commandOutput;
zeroTurnCost = await shouldZeroClineFreeModelCost(config);
zeroCurrentTurnCost = zeroTurnCost;
const {
prompt: userInput,
userImages,
@@ -519,8 +528,9 @@ export async function runInteractive(
}
if (result.finishReason !== "completed") {
if (result.finishReason === "aborted" || isAbortInProgress()) {
const usage = await sessionRuntime.getAccumulatedUsage(
result.usage,
const usage = zeroCliUsageCost(
await sessionRuntime.getAccumulatedUsage(result.usage),
zeroTurnCost,
);
return {
usage,
@@ -535,7 +545,10 @@ export async function runInteractive(
errorText || `Turn finished with ${result.finishReason}`,
);
}
const usage = await sessionRuntime.getAccumulatedUsage(result.usage);
const usage = zeroCliUsageCost(
await sessionRuntime.getAccumulatedUsage(result.usage),
zeroTurnCost,
);
return {
usage,
currentContextSize: getCurrentContextSize(result.messages),
@@ -559,6 +572,7 @@ export async function runInteractive(
});
throw error;
} finally {
zeroCurrentTurnCost = false;
if (!delivery) {
isRunning = false;
clearAbortInProgress();
@@ -3,7 +3,9 @@ import type React from "react";
import { useState } from "react";
import "opentui-spinner/react";
import {
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassSubscriptionUrl,
isClineOrgIndividualInferenceSubscriptionErrorMessage,
isClinePassSubscriptionError,
} from "../../utils/cline-pass-errors";
import {
@@ -329,6 +331,30 @@ function ClinePassSubscriptionErrorView(props: { defaultFg?: string }) {
);
}
function ClineOrgIndividualInferenceSubscriptionErrorView(props: {
defaultFg?: string;
}) {
return (
<box flexDirection="row">
<text fg="yellow" content="* " />
<box
flexDirection="column"
border
borderStyle="rounded"
borderColor="yellow"
paddingX={1}
>
<text fg="yellow">Personal ClinePass required</text>
<text
fg={props.defaultFg}
selectable
content={getClineOrgIndividualInferenceSubscriptionMessage()}
/>
</box>
</box>
);
}
export function ChatEntryView(props: {
entry: ChatEntry;
accent?: string;
@@ -427,6 +453,11 @@ export function ChatEntryView(props: {
if (isClineAccountCreditsErrorMessage(entry.text)) {
return <ClineCreditsErrorView defaultFg={defaultFg} />;
}
if (isClineOrgIndividualInferenceSubscriptionErrorMessage(entry.text)) {
return (
<ClineOrgIndividualInferenceSubscriptionErrorView defaultFg={defaultFg} />
);
}
if (isClinePassSubscriptionError(entry.text)) {
return <ClinePassSubscriptionErrorView defaultFg={defaultFg} />;
}
@@ -57,7 +57,7 @@ describe("formatStatusBarUsageText", () => {
totalCost: 0.123,
showCost: true,
}),
).toBe("(12,345) $0.12");
).toBe("(12,345 tokens) $0.12");
});
it("omits cost when usage cost is hidden", () => {
@@ -67,6 +67,6 @@ describe("formatStatusBarUsageText", () => {
totalCost: 0.123,
showCost: false,
}),
).toBe("(12,345)");
).toBe("(12,345 tokens)");
});
});
+1 -1
View File
@@ -51,7 +51,7 @@ export function formatStatusBarUsageText(input: {
totalCost: number;
showCost: boolean;
}): string {
const tokens = `(${input.totalTokens.toLocaleString()})`;
const tokens = `(${input.totalTokens.toLocaleString()} tokens)`;
if (!input.showCost) return tokens;
return `${tokens} ${formatCost(input.totalCost)}`;
}
+19 -1
View File
@@ -1,7 +1,9 @@
import { describe, expect, it } from "vitest";
import {
formatCliErrorMessage,
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassSubscriptionUrl,
isClineOrgIndividualInferenceSubscriptionErrorMessage,
isClinePassSubscriptionError,
} from "./cline-pass-errors";
@@ -20,7 +22,23 @@ describe("cline-pass-errors", () => {
it("formats the ClinePass subscription URL", () => {
expect(getClinePassSubscriptionUrl()).toBe(
"https://app.cline.bot/dashboard/subscription/",
"https://app.cline.bot/dashboard/subscription?personal=true",
);
});
it("recognizes and formats organization account individual subscription errors", () => {
const raw =
"403 Error 403: organization accounts cannot use individual model inference subscriptions";
const formatted = getClineOrgIndividualInferenceSubscriptionMessage();
expect(isClineOrgIndividualInferenceSubscriptionErrorMessage(raw)).toBe(
true,
);
expect(
isClineOrgIndividualInferenceSubscriptionErrorMessage(
new Error(formatted),
),
).toBe(true);
expect(formatCliErrorMessage(new Error(raw))).toBe(formatted);
});
});
+30 -1
View File
@@ -1,10 +1,16 @@
import {
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassSubscriptionUrl,
isClineNotSubscribedError,
isClineNotSubscribedMessage,
isClineOrgIndividualInferenceSubscriptionError,
isClineOrgIndividualInferenceSubscriptionMessage,
} from "@cline/core";
export { getClinePassSubscriptionUrl };
export {
getClineOrgIndividualInferenceSubscriptionMessage,
getClinePassSubscriptionUrl,
};
function isFormattedClinePassSubscriptionMessage(message: string): boolean {
const normalized = message.trim().toLowerCase();
@@ -32,7 +38,30 @@ export function isClinePassSubscriptionError(error: unknown): boolean {
);
}
export function isClineOrgIndividualInferenceSubscriptionErrorMessage(
error: unknown,
): boolean {
if (isClineOrgIndividualInferenceSubscriptionError(error)) {
return true;
}
if (error instanceof Error) {
return (
error.name === "ClineOrgIndividualInferenceSubscriptionError" ||
isClineOrgIndividualInferenceSubscriptionMessage(error.message) ||
error.message === getClineOrgIndividualInferenceSubscriptionMessage()
);
}
return (
typeof error === "string" &&
(isClineOrgIndividualInferenceSubscriptionMessage(error) ||
error === getClineOrgIndividualInferenceSubscriptionMessage())
);
}
export function formatCliErrorMessage(error: unknown): string {
if (isClineOrgIndividualInferenceSubscriptionErrorMessage(error)) {
return getClineOrgIndividualInferenceSubscriptionMessage();
}
if (error instanceof Error) {
return error.message;
}
+162
View File
@@ -0,0 +1,162 @@
import type { AgentEvent } from "@cline/core";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
clearClineFreeModelCostCache,
shouldZeroClineFreeModelCost,
zeroCliAgentEventCost,
zeroCliUsageCost,
} from "./free-model-cost";
afterEach(() => {
clearClineFreeModelCostCache();
vi.unstubAllGlobals();
});
describe("shouldZeroClineFreeModelCost", () => {
it("uses the Cline free model list", async () => {
const fetchMock = vi.fn(
async (_input: Parameters<typeof fetch>[0], _init?: RequestInit) => {
return new Response(
JSON.stringify({
free: [{ id: "deepseek/deepseek-v4-flash" }],
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
},
);
vi.stubGlobal("fetch", fetchMock);
await expect(
shouldZeroClineFreeModelCost({
providerId: "cline",
modelId: "deepseek/deepseek-v4-flash",
baseUrl: "https://cline.test/api/v1",
}),
).resolves.toBe(true);
expect(fetchMock.mock.calls[0]?.[0]).toBe(
"https://cline.test/api/v1/ai/cline/recommended-models",
);
});
it("does not zero non-Cline providers", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
await expect(
shouldZeroClineFreeModelCost({
providerId: "openrouter",
modelId: "deepseek/deepseek-v4-flash",
baseUrl: "https://cline.test/api/v1",
}),
).resolves.toBe(false);
expect(fetchMock).not.toHaveBeenCalled();
});
it("does not match a paid model by only the final path segment", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => {
return new Response(
JSON.stringify({
free: [{ id: "deepseek/deepseek-v4-flash" }],
}),
{ status: 200, headers: { "content-type": "application/json" } },
);
}),
);
await expect(
shouldZeroClineFreeModelCost({
providerId: "cline",
modelId: "acme/deepseek-v4-flash",
baseUrl: "https://cline.test/api/v1",
}),
).resolves.toBe(false);
});
it("retries after a failed free model list fetch", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response("unavailable", { status: 503 }))
.mockResolvedValueOnce(
new Response(
JSON.stringify({
free: [{ id: "deepseek/deepseek-v4-flash" }],
}),
{ status: 200, headers: { "content-type": "application/json" } },
),
);
vi.stubGlobal("fetch", fetchMock);
await expect(
shouldZeroClineFreeModelCost({
providerId: "cline",
modelId: "deepseek/deepseek-v4-flash",
baseUrl: "https://cline.test/api/v1",
}),
).resolves.toBe(false);
await expect(
shouldZeroClineFreeModelCost({
providerId: "cline",
modelId: "deepseek/deepseek-v4-flash",
baseUrl: "https://cline.test/api/v1",
}),
).resolves.toBe(true);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
});
describe("zeroCliUsageCost", () => {
it("zeros total cost while preserving token usage", () => {
expect(
zeroCliUsageCost(
{
inputTokens: 10,
outputTokens: 5,
totalCost: 0.001,
},
true,
),
).toEqual({
inputTokens: 10,
outputTokens: 5,
totalCost: 0,
});
});
});
describe("zeroCliAgentEventCost", () => {
it("zeros usage event cost fields", () => {
const event = {
type: "usage",
inputTokens: 10,
outputTokens: 5,
cost: 0.001,
totalCost: 0.001,
} as AgentEvent;
expect(zeroCliAgentEventCost(event, true)).toMatchObject({
cost: 0,
totalCost: 0,
});
});
it("zeros done event usage cost", () => {
const event = {
type: "done",
reason: "completed",
text: "ok",
iterations: 1,
usage: {
inputTokens: 10,
outputTokens: 5,
totalCost: 0.001,
},
} as AgentEvent;
expect(zeroCliAgentEventCost(event, true)).toMatchObject({
usage: { totalCost: 0 },
});
});
});
+123
View File
@@ -0,0 +1,123 @@
import type { AgentEvent } from "@cline/core";
import { getClineEnvironmentConfig } from "@cline/shared";
import type { Config } from "./types";
const CLINE_RECOMMENDED_MODELS_TIMEOUT_MS = 5_000;
const freeModelIdsByBaseUrl = new Map<
string,
Promise<readonly string[] | undefined>
>();
function normalizeModelId(modelId: string | undefined): string {
return modelId?.trim().toLowerCase() ?? "";
}
function modelIdsMatch(selectedModelId: string, freeModelId: string): boolean {
const selected = normalizeModelId(selectedModelId);
const free = normalizeModelId(freeModelId);
if (!selected || !free) return false;
return selected === free;
}
function resolveClineRecommendedModelsUrl(baseUrl: string): string {
const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, "");
const apiBaseUrl = normalizedBaseUrl.endsWith("/api/v1")
? normalizedBaseUrl.slice(0, -"/api/v1".length)
: normalizedBaseUrl;
return `${apiBaseUrl}/api/v1/ai/cline/recommended-models`;
}
async function fetchClineFreeModelIds(
baseUrl: string,
): Promise<readonly string[] | undefined> {
const controller = new AbortController();
const timeout = setTimeout(
() => controller.abort(),
CLINE_RECOMMENDED_MODELS_TIMEOUT_MS,
);
try {
const response = await fetch(resolveClineRecommendedModelsUrl(baseUrl), {
signal: controller.signal,
});
if (!response.ok) return undefined;
const json = (await response.json()) as { free?: unknown };
return Array.isArray(json.free)
? json.free
.map((model) =>
model && typeof model === "object"
? (model as Record<string, unknown>).id
: undefined,
)
.filter((id): id is string => typeof id === "string" && id.length > 0)
: [];
} catch {
return undefined;
} finally {
clearTimeout(timeout);
}
}
function getClineFreeModelIds(baseUrl: string): Promise<readonly string[]> {
const cacheKey = baseUrl.trim();
let cached = freeModelIdsByBaseUrl.get(cacheKey);
if (!cached) {
cached = fetchClineFreeModelIds(cacheKey).then((ids) => {
if (!ids) freeModelIdsByBaseUrl.delete(cacheKey);
return ids;
});
freeModelIdsByBaseUrl.set(cacheKey, cached);
}
return cached.then((ids) => ids ?? []);
}
export async function shouldZeroClineFreeModelCost(
config: Pick<Config, "providerId" | "modelId" | "baseUrl">,
): Promise<boolean> {
if (config.providerId !== "cline") return false;
const modelId = normalizeModelId(config.modelId);
if (!modelId) return false;
const baseUrl =
config.baseUrl?.trim() || getClineEnvironmentConfig().apiBaseUrl;
const freeModelIds = await getClineFreeModelIds(baseUrl);
return freeModelIds.some((freeModelId) =>
modelIdsMatch(modelId, freeModelId),
);
}
export function zeroCliUsageCost<T extends { totalCost?: number } | undefined>(
usage: T,
shouldZeroCost: boolean,
): T {
if (
!shouldZeroCost ||
!usage ||
typeof usage.totalCost !== "number" ||
usage.totalCost === 0
) {
return usage;
}
return { ...usage, totalCost: 0 } as T;
}
export function zeroCliAgentEventCost(
event: AgentEvent,
shouldZeroCost: boolean,
): AgentEvent {
if (!shouldZeroCost) return event;
if (event.type === "done" && event.usage) {
return {
...event,
usage: zeroCliUsageCost(event.usage, true),
};
}
if (event.type !== "usage") return event;
const next = { ...event } as Record<string, unknown>;
if (typeof next.cost === "number") next.cost = 0;
if (typeof next.totalCost === "number") next.totalCost = 0;
return next as unknown as AgentEvent;
}
export function clearClineFreeModelCostCache(): void {
freeModelIdsByBaseUrl.clear();
}
+14 -323
View File
@@ -1,325 +1,16 @@
export interface PlatformDef {
id: string;
name: string;
type: "polling" | "webhook" | "hybrid";
hint: string;
fields: FieldDef[];
security?: SecurityDef;
}
import {
CONNECTOR_PLATFORMS,
shouldIncludeConnectorField,
} from "@cline/shared";
export interface FieldDef {
flag: string;
label: string;
placeholder?: string;
required?: boolean;
help?: string[];
initialValue?: string;
options?: Array<{ value: string; label: string; hint?: string }>;
includeWhen?: FieldCondition;
}
export type {
ConnectorFieldCondition as FieldCondition,
ConnectorFieldDef as FieldDef,
ConnectorPlatformDef as PlatformDef,
ConnectorSecurityDef as SecurityDef,
ConnectorSecurityFieldDef as SecurityFieldDef,
} from "@cline/shared";
export { CONNECTOR_PLATFORMS, shouldIncludeConnectorField };
export type FieldCondition = {
flag: string;
equals?: string;
notEquals?: string;
};
export interface SecurityFieldDef {
key: string;
label: string;
placeholder?: string;
help?: string[];
requiredMessage: string;
validate?: (value: string) => string | undefined;
}
export interface SecurityDef {
prompt: string;
fields: SecurityFieldDef[];
buildArgs: (values: Record<string, string>) => string[];
}
export function shouldIncludeField(
field: FieldDef,
values: Record<string, string>,
): boolean {
const condition = field.includeWhen;
if (!condition) {
return true;
}
const value = values[condition.flag] ?? "";
if (condition.equals !== undefined && value !== condition.equals) {
return false;
}
if (condition.notEquals !== undefined && value === condition.notEquals) {
return false;
}
return true;
}
function validateTelegramUserId(value: string): string | undefined {
return /^\d+$/.test(value)
? undefined
: "Telegram user ID must contain digits only";
}
function validateSlackTeamId(value: string): string | undefined {
return /^T[A-Z0-9]+$/.test(value)
? undefined
: "Slack workspace ID must start with T and contain uppercase letters or digits only";
}
function validateSlackUserId(value: string): string | undefined {
return /^[UW][A-Z0-9]+$/.test(value)
? undefined
: "Slack member ID must start with U or W and contain uppercase letters or digits only";
}
export const PLATFORMS: PlatformDef[] = [
{
id: "telegram",
name: "Telegram",
type: "polling",
hint: "Easiest to set up. No public URL needed.",
fields: [
{
flag: "-k",
label: "Bot token",
placeholder: "7123456789:AAH...",
required: true,
help: [
"Open Telegram and start a chat with @BotFather",
"Send /newbot and follow the prompts",
"BotFather gives you this after creating the bot",
"It looks like 7123456789:AAHxxx...",
],
},
],
security: {
prompt:
"By default, anyone who finds your bot can message it and run tasks on your machine. Restrict access to your Telegram user ID?",
fields: [
{
key: "userId",
label: "Your Telegram user ID",
placeholder: "123456789",
help: [
"Message @userinfobot on Telegram",
"It will reply with your numeric user ID",
],
requiredMessage: "User ID is required to restrict access",
validate: validateTelegramUserId,
},
],
buildArgs: ({ userId }) => ["--allowed-user-id", userId ?? ""],
},
},
{
id: "slack",
name: "Slack",
type: "hybrid",
hint: "Public URL for webhook mode; leave blank for socket mode.",
fields: [
{
flag: "--bot-token",
label: "Bot token",
placeholder: "xoxb-...",
required: true,
help: [
"Go to api.slack.com/apps and create a new app",
"Add Bot Token Scopes: chat:write, app_mentions:read, channels:history, channels:read, im:history, im:read, im:write, users:read",
"Install to workspace and copy the Bot Token",
],
},
{
flag: "--base-url",
label: "Public base URL",
placeholder: "leave blank for socket mode",
help: [
"Enter a publicly accessible URL for webhook mode",
"Leave blank to use Slack socket mode instead",
],
},
{
flag: "--signing-secret",
label: "Signing secret",
required: true,
help: ["Found in your app's Basic Information page"],
includeWhen: { flag: "--base-url", notEquals: "" },
},
{
flag: "--app-token",
label: "App-level token",
placeholder: "xapp-...",
required: true,
help: [
"Enable Socket Mode in the Slack app",
"Generate an app-level token with the connections:write scope",
],
includeWhen: { flag: "--base-url", equals: "" },
},
],
security: {
prompt: "Restrict which Slack users can interact with the bot?",
fields: [
{
key: "teamId",
label: "Allowed Slack workspace ID",
placeholder: "T01ABC123",
help: [
"Open your Slack workspace URL in a browser",
"The workspace ID is the segment after /client/, for example T01ABC123",
],
requiredMessage: "Workspace ID is required to restrict access",
validate: validateSlackTeamId,
},
{
key: "userId",
label: "Allowed Slack member ID",
placeholder: "U01ABC123",
help: [
"Click a user's name in Slack, then View full profile",
"Click ... and Copy member ID",
],
requiredMessage: "Member ID is required to restrict access",
validate: validateSlackUserId,
},
],
buildArgs: ({ teamId, userId }) => [
"--hook-command",
`jq -r ".payload.actor.participantKey" | grep -qx "slack:team:${teamId}:user:${userId}" && echo '{"action":"allow"}' || echo '{"action":"deny"}'`,
],
},
},
{
id: "discord",
name: "Discord",
type: "webhook",
hint: "Requires a Discord app and public URL.",
fields: [
{
flag: "--application-id",
label: "Application ID",
required: true,
help: [
"Go to discord.com/developers/applications",
"Create a new app, copy the Application ID",
],
},
{
flag: "--bot-token",
label: "Bot token",
required: true,
help: ["Go to Bot section, create a bot, copy the token"],
},
{
flag: "--public-key",
label: "Public key",
required: true,
help: ["Found in General Information of your app"],
},
{
flag: "--base-url",
label: "Public base URL",
placeholder: "https://example.com",
required: true,
help: [
"Base URL for the connector",
"For Discord, set the Interactions Endpoint URL to <base-url>/api/webhooks/discord",
],
},
],
},
{
id: "whatsapp",
name: "WhatsApp",
type: "webhook",
hint: "Requires Meta developer account and public URL.",
fields: [
{
flag: "--phone-number-id",
label: "Phone number ID",
required: true,
help: ["From your WhatsApp Business account in Meta Developer portal"],
},
{
flag: "--access-token",
label: "Access token",
required: true,
help: ["Generate a permanent token in Meta Developer portal"],
},
{
flag: "--app-secret",
label: "App secret",
required: true,
help: ["Found in App Settings > Basic"],
},
{
flag: "--verify-token",
label: "Webhook verify token",
placeholder: "my-verify-token",
required: true,
help: ["Any string you choose, used to verify webhook setup"],
},
{
flag: "--base-url",
label: "Public base URL",
placeholder: "https://example.com",
required: true,
},
],
},
{
id: "gchat",
name: "Google Chat",
type: "webhook",
hint: "Requires Google Cloud project and public URL.",
fields: [
{
flag: "--credentials-json",
label: "Service account credentials JSON",
required: true,
help: [
"Create a service account in Google Cloud Console",
"Download the credentials JSON file",
"Paste the JSON content here",
],
},
{
flag: "--base-url",
label: "Public base URL",
placeholder: "https://example.com",
required: true,
},
],
},
{
id: "linear",
name: "Linear",
type: "webhook",
hint: "React to Linear issues and comments.",
fields: [
{
flag: "--api-key",
label: "API key",
required: true,
help: ["Go to Linear Settings > API > Personal API keys"],
},
{
flag: "--webhook-secret",
label: "Webhook signing secret",
required: true,
help: [
"Go to Settings > API > Webhooks, create one",
"Copy the signing secret",
],
},
{
flag: "--base-url",
label: "Public base URL",
placeholder: "https://example.com",
required: true,
},
],
},
];
export const PLATFORMS = CONNECTOR_PLATFORMS;
export const shouldIncludeField = shouldIncludeConnectorField;
+21 -2
View File
@@ -1,3 +1,5 @@
import { isIP } from "node:net";
export interface ClineHubServerOptions {
host: string;
port: number;
@@ -47,6 +49,9 @@ function normalizePublicUrl(
`PUBLIC_URL must use http: or https:, got ${parsed.protocol}`,
);
}
if (shouldAddDashboardPortToPublicUrl(parsed, port)) {
parsed.port = String(port);
}
parsed.hash = "";
return parsed.toString().replace(/\/$/, "");
}
@@ -85,12 +90,26 @@ export function resolveClineHubServerOptions(
};
}
function isDefaultProtocolPort(url: URL, port: number): boolean {
return (
(url.protocol === "http:" && port === 80) ||
(url.protocol === "https:" && port === 443)
);
}
function shouldAddDashboardPortToPublicUrl(url: URL, port: number): boolean {
if (url.port || isDefaultProtocolPort(url, port)) return false;
const hostname = url.hostname.replace(/^\[|\]$/g, "");
return hostname === "localhost" || isIP(hostname) !== 0;
}
export function buildInviteUrl(
publicUrl: string,
roomSecret: string | undefined,
): string {
if (!roomSecret) return publicUrl;
const url = new URL(publicUrl);
url.searchParams.set("roomSecret", roomSecret);
if (roomSecret) {
url.searchParams.set("roomSecret", roomSecret);
}
return url.toString();
}
+42 -9
View File
@@ -4,6 +4,7 @@ import {
handleToolApprovalResponse,
rejectOrphanedApprovals,
} from "./server/approvals";
import { isAuthorizedBrowserToDesktopRequest } from "./server/browser-auth";
import {
browserConfig,
host,
@@ -14,7 +15,11 @@ import {
webviewDistDir,
} from "./server/deps";
import { handleDesktopCommand } from "./server/desktop-commands";
import { createJsonResponse, WebviewAssets } from "./server/http";
import {
createJsonResponse,
isWebviewRoute,
WebviewAssets,
} from "./server/http";
import {
attachHub,
detachHub,
@@ -53,17 +58,33 @@ export interface ClineHubDashboardServer {
stop: () => Promise<void>;
}
const PUBLIC_BROWSER_PATHS = new Set([
"/version",
"/health",
"/config.json",
"/api/marketplace/catalog",
"/icon.png",
"/icon.svg",
"/icon.ico",
"/32x32.png",
"/cline-logo-filled.svg",
"/favicon.svg",
]);
function isPublicStaticAssetPath(pathname: string): boolean {
return pathname.startsWith("/assets/") || PUBLIC_BROWSER_PATHS.has(pathname);
}
function isPublicBrowserRoute(_req: Request, url: URL): boolean {
return isWebviewRoute(url.pathname) || isPublicStaticAssetPath(url.pathname);
}
export async function startClineHubDashboardServer(): Promise<ClineHubDashboardServer> {
const ctx = new HubContext();
const assets = new WebviewAssets(webviewDistDir);
const syncClientsAndSessions = () => syncHubClientsAndSessions(ctx);
let stopped = false;
function isAuthorizedBrowserRequest(url: URL): boolean {
if (!roomSecret) return true;
return url.searchParams.get("roomSecret") === roomSecret;
}
await attachHub(ctx);
const healthInterval = setInterval(() => {
void (async () => {
@@ -77,6 +98,21 @@ export async function startClineHubDashboardServer(): Promise<ClineHubDashboardS
hostname: host,
async fetch(req, server) {
const url = new URL(req.url);
if (
!isAuthorizedBrowserToDesktopRequest(
req,
url,
{
bindHost: host,
port,
publicUrl,
roomSecret,
},
isPublicBrowserRoute,
)
) {
return createJsonResponse({ error: "unauthorized_browser" }, 403);
}
if (url.pathname === "/version") {
return createJsonResponse({ coreVersion: CORE_BUILD_VERSION });
}
@@ -85,9 +121,6 @@ export async function startClineHubDashboardServer(): Promise<ClineHubDashboardS
return createJsonResponse(hubStatusPayload(ctx));
}
if (url.pathname === "/browser") {
if (!isAuthorizedBrowserRequest(url)) {
return createJsonResponse({ error: "invalid_room_secret" }, 401);
}
const displayName = `Browser ${Math.random().toString(36).slice(2, 6)}`;
const data = {
socket: undefined as never,
@@ -0,0 +1,359 @@
import { describe, expect, it } from "vitest";
import {
allowedBrowserHosts,
allowedBrowserOrigins,
isAuthorizedBrowserRequest,
isAuthorizedBrowserToDesktopRequest,
requiresBrowserRequestAuth,
} from "./browser-auth";
const defaultOptions = {
bindHost: "127.0.0.1",
port: 8787,
publicUrl: "http://127.0.0.1:8787",
};
const publicRoute = (_req: Request, url: URL) => url.pathname === "/public";
function browserRequest(
origin?: string,
init?: Omit<RequestInit, "headers"> & {
headers?: Record<string, string>;
},
): Request {
return new Request("http://127.0.0.1:8787/browser", {
...init,
headers: {
host: "127.0.0.1:8787",
...(origin === undefined ? {} : { origin }),
...(init?.headers ?? {}),
},
});
}
describe("allowedBrowserOrigins", () => {
it("allows the configured public URL origin and local aliases for local binds", () => {
expect([...allowedBrowserOrigins(defaultOptions)].sort()).toEqual([
"http://127.0.0.1:8787",
"http://[::1]:8787",
"http://localhost:8787",
]);
});
it("uses the configured public URL scheme for local aliases", () => {
expect(
[
...allowedBrowserOrigins({
...defaultOptions,
publicUrl: "https://127.0.0.1:8787",
}),
].sort(),
).toEqual([
"https://127.0.0.1:8787",
"https://[::1]:8787",
"https://localhost:8787",
]);
});
it("omits default protocol ports for local alias origins", () => {
expect(
[
...allowedBrowserOrigins({
bindHost: "127.0.0.1",
port: 80,
publicUrl: "http://localhost",
}),
].sort(),
).toEqual(["http://127.0.0.1", "http://[::1]", "http://localhost"]);
expect(
[
...allowedBrowserOrigins({
bindHost: "127.0.0.1",
port: 443,
publicUrl: "https://localhost",
}),
].sort(),
).toEqual(["https://127.0.0.1", "https://[::1]", "https://localhost"]);
});
it("allows the configured public URL origin and explicit bind origin for non-local binds", () => {
expect(
[
...allowedBrowserOrigins({
bindHost: "0.0.0.0",
port: 8787,
publicUrl: "https://example.ngrok-free.app",
roomSecret: "secret",
}),
].sort(),
).toEqual(["https://0.0.0.0:8787", "https://example.ngrok-free.app"]);
});
});
describe("allowedBrowserHosts", () => {
it("allows the configured public URL host and local aliases for local binds", () => {
expect([...allowedBrowserHosts(defaultOptions)].sort()).toEqual([
"127.0.0.1:8787",
"[::1]:8787",
"localhost:8787",
]);
});
it("omits default protocol ports for local alias hosts", () => {
expect(
[
...allowedBrowserHosts({
bindHost: "127.0.0.1",
port: 80,
publicUrl: "http://localhost",
}),
].sort(),
).toEqual(["127.0.0.1", "[::1]", "localhost"]);
expect(
[
...allowedBrowserHosts({
bindHost: "127.0.0.1",
port: 443,
publicUrl: "https://localhost",
}),
].sort(),
).toEqual(["127.0.0.1", "[::1]", "localhost"]);
});
it("allows the configured public URL host and explicit bind host for non-local binds", () => {
expect(
[
...allowedBrowserHosts({
bindHost: "0.0.0.0",
port: 8787,
publicUrl: "https://example.ngrok-free.app",
roomSecret: "secret",
}),
].sort(),
).toEqual(["0.0.0.0:8787", "example.ngrok-free.app"]);
});
});
describe("requiresBrowserRequestAuth", () => {
it("does not require browser auth for public GET routes", () => {
expect(
requiresBrowserRequestAuth(
new Request("http://127.0.0.1:8787/public"),
new URL("http://127.0.0.1:8787/public"),
publicRoute,
),
).toBe(false);
});
it("requires browser auth for unknown paths even when they use GET", () => {
expect(
requiresBrowserRequestAuth(
new Request("http://127.0.0.1:8787/future-api"),
new URL("http://127.0.0.1:8787/future-api"),
publicRoute,
),
).toBe(true);
});
it("requires browser auth for privileged paths even when they use GET", () => {
expect(
requiresBrowserRequestAuth(
new Request("http://127.0.0.1:8787/browser"),
new URL("http://127.0.0.1:8787/browser"),
publicRoute,
),
).toBe(true);
});
it("requires browser auth for every WebSocket upgrade path", () => {
expect(
requiresBrowserRequestAuth(
new Request("http://127.0.0.1:8787/future-socket", {
headers: { upgrade: "websocket" },
}),
new URL("http://127.0.0.1:8787/future-socket"),
publicRoute,
),
).toBe(true);
});
it("requires browser auth for every unsafe HTTP method", () => {
expect(
requiresBrowserRequestAuth(
new Request("http://127.0.0.1:8787/future-api", { method: "POST" }),
new URL("http://127.0.0.1:8787/future-api"),
publicRoute,
),
).toBe(true);
});
});
describe("isAuthorizedBrowserRequest", () => {
it.each([
"http://127.0.0.1:8787",
"http://localhost:8787",
"http://[::1]:8787",
])("accepts local dashboard origin %s without a room secret", (origin) => {
expect(
isAuthorizedBrowserRequest(
browserRequest(origin),
new URL("http://127.0.0.1:8787/browser"),
defaultOptions,
),
).toBe(true);
});
it.each([
undefined,
"",
"null",
"not a url",
"http://evil.attacker.example.com",
"http://127.0.0.1:9999",
"https://127.0.0.1:8787",
])("rejects untrusted origin %s", (origin) => {
expect(
isAuthorizedBrowserRequest(
browserRequest(origin),
new URL("http://127.0.0.1:8787/browser"),
defaultOptions,
),
).toBe(false);
});
it.each([
undefined,
"",
"evil.attacker.example.com",
"127.0.0.1:9999",
"localhost:9999",
])("rejects untrusted host %s", (host) => {
expect(
isAuthorizedBrowserRequest(
browserRequest("http://127.0.0.1:8787", {
headers: host === undefined ? { host: "" } : { host },
}),
new URL("http://127.0.0.1:8787/browser"),
defaultOptions,
),
).toBe(false);
});
it("allows explicit wildcard bind host and origin when a room secret is configured", () => {
expect(
isAuthorizedBrowserRequest(
browserRequest("http://0.0.0.0:8787", {
headers: { host: "0.0.0.0:8787" },
}),
new URL("http://0.0.0.0:8787/browser?roomSecret=invite-123"),
{
bindHost: "0.0.0.0",
port: 8787,
publicUrl: "http://127.0.0.1:8787",
roomSecret: "invite-123",
},
),
).toBe(true);
});
it("requires trusted origin, trusted host, and room secret when a room secret is configured", () => {
const options = { ...defaultOptions, roomSecret: "invite-123" };
expect(
isAuthorizedBrowserRequest(
browserRequest("http://127.0.0.1:8787"),
new URL("http://127.0.0.1:8787/browser?roomSecret=invite-123"),
options,
),
).toBe(true);
expect(
isAuthorizedBrowserRequest(
browserRequest("http://127.0.0.1:8787"),
new URL("http://127.0.0.1:8787/browser"),
options,
),
).toBe(false);
expect(
isAuthorizedBrowserRequest(
browserRequest("http://evil.attacker.example.com"),
new URL("http://127.0.0.1:8787/browser?roomSecret=invite-123"),
options,
),
).toBe(false);
expect(
isAuthorizedBrowserRequest(
browserRequest("http://127.0.0.1:8787", {
headers: { host: "evil.attacker.example.com" },
}),
new URL("http://127.0.0.1:8787/browser?roomSecret=invite-123"),
options,
),
).toBe(false);
});
});
describe("isAuthorizedBrowserToDesktopRequest", () => {
it("allows safe public GET routes without an origin", () => {
expect(
isAuthorizedBrowserToDesktopRequest(
new Request("http://127.0.0.1:8787/public"),
new URL("http://127.0.0.1:8787/public"),
defaultOptions,
publicRoute,
),
).toBe(true);
});
it("rejects future WebSocket paths from untrusted origins by default", () => {
expect(
isAuthorizedBrowserToDesktopRequest(
new Request("http://127.0.0.1:8787/future-socket", {
headers: {
host: "127.0.0.1:8787",
origin: "http://evil.attacker.example.com",
upgrade: "websocket",
},
}),
new URL("http://127.0.0.1:8787/future-socket"),
defaultOptions,
publicRoute,
),
).toBe(false);
});
it("rejects future unsafe HTTP routes from untrusted origins by default", () => {
expect(
isAuthorizedBrowserToDesktopRequest(
new Request("http://127.0.0.1:8787/future-api", {
method: "POST",
headers: {
host: "127.0.0.1:8787",
origin: "http://evil.attacker.example.com",
},
}),
new URL("http://127.0.0.1:8787/future-api"),
defaultOptions,
publicRoute,
),
).toBe(false);
});
it("allows future unsafe HTTP routes from trusted origins", () => {
expect(
isAuthorizedBrowserToDesktopRequest(
new Request("http://127.0.0.1:8787/future-api", {
method: "POST",
headers: {
host: "127.0.0.1:8787",
origin: "http://127.0.0.1:8787",
},
}),
new URL("http://127.0.0.1:8787/future-api"),
defaultOptions,
publicRoute,
),
).toBe(true);
});
});
+134
View File
@@ -0,0 +1,134 @@
import { isNonLocalBindHost } from "../options";
export interface BrowserRequestAuthOptions {
bindHost: string;
port: number;
publicUrl: string;
roomSecret?: string;
}
const SAFE_HTTP_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
export type PublicBrowserRoutePredicate = (req: Request, url: URL) => boolean;
function isWebSocketUpgrade(req: Request): boolean {
return req.headers.get("upgrade")?.toLowerCase() === "websocket";
}
function parseOrigin(value: string | null): string | undefined {
const origin = parseHeader(value);
try {
return new URL(origin ?? "").origin;
} catch {
return undefined;
}
}
function parseHeader(value: string | null): string | undefined {
const host = value?.trim().toLowerCase();
return host || undefined;
}
function formatHostForOrigin(host: string): string {
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
}
function isDefaultProtocolPort(protocol: string, port: number): boolean {
return (
(protocol === "http:" && port === 80) ||
(protocol === "https:" && port === 443)
);
}
function originForHost(protocol: string, host: string, port: number): string {
return new URL(`${protocol}//${formatHostForOrigin(host)}:${port}`).origin;
}
function hostHeaderForHost(
protocol: string,
host: string,
port: number,
): string {
const formattedHost = formatHostForOrigin(host).toLowerCase();
return isDefaultProtocolPort(protocol, port)
? formattedHost
: `${formattedHost}:${port}`;
}
export function allowedBrowserOrigins({
bindHost,
port,
publicUrl,
}: BrowserRequestAuthOptions): Set<string> {
const publicUrlParts = new URL(publicUrl);
const origins = new Set<string>();
origins.add(publicUrlParts.origin);
origins.add(originForHost(publicUrlParts.protocol, bindHost, port));
if (!isNonLocalBindHost(bindHost)) {
for (const hostname of ["127.0.0.1", "localhost", "[::1]"]) {
origins.add(originForHost(publicUrlParts.protocol, hostname, port));
}
}
return origins;
}
export function allowedBrowserHosts({
bindHost,
port,
publicUrl,
}: BrowserRequestAuthOptions): Set<string> {
const publicUrlParts = new URL(publicUrl);
const hosts = new Set<string>();
const publicHost = publicUrlParts.host.toLowerCase();
hosts.add(publicHost);
hosts.add(hostHeaderForHost(publicUrlParts.protocol, bindHost, port));
if (!isNonLocalBindHost(bindHost)) {
for (const hostname of ["127.0.0.1", "localhost", "[::1]"]) {
hosts.add(hostHeaderForHost(publicUrlParts.protocol, hostname, port));
}
}
return hosts;
}
export function requiresBrowserRequestAuth(
req: Request,
url: URL,
isPublicBrowserRoute: PublicBrowserRoutePredicate,
): boolean {
if (isWebSocketUpgrade(req)) return true;
if (!SAFE_HTTP_METHODS.has(req.method.toUpperCase())) return true;
return !isPublicBrowserRoute(req, url);
}
export function isAuthorizedBrowserRequest(
req: Request,
url: URL,
options: BrowserRequestAuthOptions,
): boolean {
const host = parseHeader(req.headers.get("host"));
if (!host || !allowedBrowserHosts(options).has(host)) return false;
const origin = parseOrigin(req.headers.get("origin"));
if (!origin || !allowedBrowserOrigins(options).has(origin)) return false;
if (!options.roomSecret) return true;
return url.searchParams.get("roomSecret") === options.roomSecret;
}
export function isAuthorizedBrowserToDesktopRequest(
req: Request,
url: URL,
options: BrowserRequestAuthOptions,
isPublicBrowserRoute: PublicBrowserRoutePredicate,
): boolean {
return (
!requiresBrowserRequestAuth(req, url, isPublicBrowserRoute) ||
isAuthorizedBrowserRequest(req, url, options)
);
}
@@ -0,0 +1,97 @@
import { describe, expect, it } from "vitest";
import { __test__ } from "./connectors";
describe("connector launch command", () => {
it("uses Bun conditions when launching the source CLI from Bun", () => {
expect(
__test__.buildCliConnectCommand(["telegram", "--bot-token", "token"], {
execPath: "/Users/test/.bun/bin/bun",
cliPath: "/repo/apps/cli/src/index.ts",
exists: () => true,
}),
).toEqual({
launcher: "/Users/test/.bun/bin/bun",
childArgs: [
"--conditions=development",
"/repo/apps/cli/src/index.ts",
"connect",
"telegram",
"--bot-token",
"token",
],
});
});
it("uses compiled CLI subcommands without Bun flags", () => {
expect(
__test__.buildCliConnectCommand(["telegram", "--bot-token", "token"], {
execPath: "/Applications/Cline/bin/cline",
cliPath: "/repo/apps/cli/src/index.ts",
exists: () => true,
}),
).toEqual({
launcher: "/Applications/Cline/bin/cline",
childArgs: ["connect", "telegram", "--bot-token", "token"],
});
});
it("uses Bun conditions when launching the source CLI from Node", () => {
expect(
__test__.buildCliConnectCommand(["telegram", "--bot-token", "token"], {
execPath: "/usr/local/bin/node",
cliPath: "/repo/apps/cli/src/index.ts",
exists: () => true,
}),
).toEqual({
launcher: "bun",
childArgs: [
"--conditions=development",
"/repo/apps/cli/src/index.ts",
"connect",
"telegram",
"--bot-token",
"token",
],
});
});
it("detects Windows Node when launching the source CLI", () => {
expect(
__test__.buildCliConnectCommand(["telegram", "--bot-token", "token"], {
execPath: "node.exe",
cliPath: "C:\\repo\\apps\\cli\\src\\index.ts",
exists: () => true,
}),
).toEqual({
launcher: "bun",
childArgs: [
"--conditions=development",
"C:\\repo\\apps\\cli\\src\\index.ts",
"connect",
"telegram",
"--bot-token",
"token",
],
});
});
it("strips terminal color codes from connector command failures", () => {
expect(
__test__.normalizeConnectorError(
"\u001B[31merror:\u001B[0m error: unknown option '--conditions=development'",
"connector start failed",
),
).toBe("unknown option '--conditions=development'");
});
it("turns Telegram unauthorized responses into a token validation message", () => {
expect(
__test__.normalizeConnectorError(
"\u001B[31merror:\u001B[0m Telegram getMe failed (401 Unauthorized): Unauthorized",
"connector start failed",
),
).toBe(
"Telegram rejected this bot token. Copy the token from @BotFather and try again.",
);
});
});
+86 -23
View File
@@ -1,5 +1,8 @@
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { basename } from "node:path";
import process from "node:process";
import { withResolvedClineBuildEnv } from "@cline/shared";
import { listConnectorCatalog } from "../../../cli/src/connectors/catalog";
import { listActiveConnectors } from "../../../cli/src/connectors/status";
import {
@@ -13,6 +16,68 @@ import type {
import { cliIndexPath, workspaceRoot } from "./deps";
import { asRecord, asString } from "./utils";
type CliConnectCommand = {
launcher: string;
childArgs: string[];
};
const ANSI_ESCAPE_PATTERN = new RegExp(
[
"[\\u001B\\u009B][[\\]()#;?]*",
"(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)",
"|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
].join(""),
"g",
);
function stripAnsi(value: string): string {
return value.replace(ANSI_ESCAPE_PATTERN, "");
}
function normalizeConnectorError(rawMessage: string, fallback: string): string {
const message =
stripAnsi(rawMessage)
.replace(/\r\n/g, "\n")
.trim()
.replace(/^(?:error:\s*)+/i, "")
.trim() || fallback;
if (
/^Telegram getMe failed \(401 Unauthorized\): Unauthorized$/i.test(message)
) {
return "Telegram rejected this bot token. Copy the token from @BotFather and try again.";
}
return message.slice(0, 2_000);
}
function buildCliConnectCommand(
args: string[],
options: {
execPath?: string;
cliPath?: string;
exists?: (path: string) => boolean;
} = {},
): CliConnectCommand {
const execPath = options.execPath ?? process.execPath;
const cliPath = options.cliPath ?? cliIndexPath;
const exists = options.exists ?? existsSync;
const runtimeName = basename(execPath).toLowerCase();
const isBunRuntime = runtimeName.includes("bun");
const isNodeRuntime = runtimeName === "node" || runtimeName === "node.exe";
const useBunSourceEntrypoint =
(isBunRuntime || isNodeRuntime) && exists(cliPath);
const launcher = isBunRuntime
? execPath
: useBunSourceEntrypoint
? "bun"
: execPath;
const childArgs = useBunSourceEntrypoint
? ["--conditions=development", cliPath, "connect", ...args]
: ["connect", ...args];
return { launcher, childArgs };
}
export function connectorChannelsPayload(): WebviewConnectorChannelsResponse {
const supported = new Set(
listConnectorCatalog().map((connector) => connector.name),
@@ -55,23 +120,14 @@ async function runCliConnectCommand(args: string[]): Promise<{
stdout: string;
stderr: string;
}> {
const launcher = (process.versions as Record<string, string | undefined>).bun
? process.execPath
: "bun";
const child = spawn(
launcher,
["--conditions=development", cliIndexPath, "connect", ...args],
{
cwd: workspaceRoot,
env: {
...process.env,
CLINE_BUILD_ENV: process.env.CLINE_BUILD_ENV ?? "development",
},
stdio: ["ignore", "pipe", "pipe"],
// Prevent a console window from flashing on Windows.
windowsHide: true,
},
);
const { launcher, childArgs } = buildCliConnectCommand(args);
const child = spawn(launcher, childArgs, {
cwd: workspaceRoot,
env: withResolvedClineBuildEnv(process.env),
stdio: ["ignore", "pipe", "pipe"],
// Prevent a console window from flashing on Windows.
windowsHide: true,
});
let stdout = "";
let stderr = "";
child.stdout?.setEncoding("utf8");
@@ -157,9 +213,10 @@ export async function startConnectorChannel(
const result = await runCliConnectCommand(cliArgs);
if (result.code !== 0) {
throw new Error(
(result.stderr.trim() || result.stdout.trim() || "connector start failed")
.trim()
.slice(0, 2_000),
normalizeConnectorError(
result.stderr || result.stdout,
"connector start failed",
),
);
}
await waitForConnectorState(() =>
@@ -168,6 +225,11 @@ export async function startConnectorChannel(
return connectorChannelsPayload();
}
export const __test__ = {
buildCliConnectCommand,
normalizeConnectorError,
};
export async function stopConnectorChannel(
args?: Record<string, unknown>,
): Promise<WebviewConnectorChannelsResponse> {
@@ -182,9 +244,10 @@ export async function stopConnectorChannel(
const result = await runCliConnectCommand([channel, "--stop"]);
if (result.code !== 0) {
throw new Error(
(result.stderr.trim() || result.stdout.trim() || "connector stop failed")
.trim()
.slice(0, 2_000),
normalizeConnectorError(
result.stderr || result.stdout,
"connector stop failed",
),
);
}
await waitForConnectorState(
+17
View File
@@ -41,6 +41,23 @@ expectEqual(
"invite URL",
);
const tailscale = resolveClineHubServerOptions({
HOST: "0.0.0.0",
CLINE_HUB_DASHBOARD_PORT: "8787",
PUBLIC_URL: "http://100.82.5.118",
ROOM_SECRET: "invite-123",
});
expectEqual(
tailscale.publicUrl,
"http://100.82.5.118:8787",
"direct IP public URL gets dashboard port",
);
expectEqual(
buildInviteUrl(tailscale.publicUrl, tailscale.roomSecret),
"http://100.82.5.118:8787/?roomSecret=invite-123",
"invite URL for direct IP public URL",
);
expectThrows(
() => resolveClineHubServerOptions({ HOST: "0.0.0.0" }),
"non-local bind without ROOM_SECRET",
+3 -3
View File
@@ -13,10 +13,9 @@
"@fontsource-variable/schibsted-grotesk": "^5.2.8",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@rive-app/react-webgl2": "^4.27.2",
"@shikijs/langs": "^4.2.0",
"@shikijs/themes": "^4.2.0",
"@streamdown/cjk": "^1.0.3",
"@streamdown/code": "^1.1.1",
"@streamdown/math": "^1.0.2",
"@streamdown/mermaid": "^1.0.2",
"@tailwindcss/vite": "^4.2.1",
"@xyflow/react": "^12.10.1",
"ai": "^6.0.116",
@@ -27,6 +26,7 @@
"embla-carousel-react": "^8.6.0",
"lucide-react": "^0.577.0",
"media-chrome": "^4.18.1",
"mermaid": "^11.15.0",
"motion": "^12.38.0",
"nanoid": "^5.1.7",
"next-themes": "^0.4.6",
+31 -11
View File
@@ -22,7 +22,14 @@ import {
WrenchIcon,
} from "lucide-react";
import type { ReactNode } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
lazy,
Suspense,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -53,19 +60,24 @@ import type {
WebviewOutboundMessage,
WebviewSessionSummary,
} from "../../webview-protocol";
import Chat from "./Chat";
import { PageFrame, PageHeader } from "./components/views/page-layout";
import {
type CustomizationSection,
CustomizationSectionView,
} from "./components/views/settings/extensions-view";
import {
type SettingsSection,
SettingsView,
} from "./components/views/settings/settings-view";
import type { CustomizationSection } from "./components/views/settings/extensions-view";
import type { SettingsSection } from "./components/views/settings/settings-view";
import { syncHubTheme } from "./lib/theme";
import { postToHost } from "./vscode";
const Chat = lazy(() => import("./Chat"));
const SettingsView = lazy(() =>
import("./components/views/settings/settings-view").then((module) => ({
default: module.SettingsView,
})),
);
const CustomizationSectionView = lazy(() =>
import("./components/views/settings/extensions-view").then((module) => ({
default: module.CustomizationSectionView,
})),
);
type View =
| "home"
| "sessions"
@@ -238,6 +250,14 @@ function currentPathWithSearch(): string {
return `${window.location.pathname}${window.location.search}`;
}
function ViewLoading() {
return (
<PageFrame>
<p className="text-sm text-muted-foreground">Loading...</p>
</PageFrame>
);
}
function formatRelativeTime(timestamp?: number): string {
if (!timestamp) return "unknown";
const elapsed = Math.max(0, Date.now() - timestamp);
@@ -1333,7 +1353,7 @@ function App() {
return (
<Shell onNavigate={navigate} version={hubState.coreVersion} view={view}>
{content}
<Suspense fallback={<ViewLoading />}>{content}</Suspense>
</Shell>
);
}
@@ -11,12 +11,13 @@ import {
useState,
} from "react";
import type {
BundledLanguage,
BundledTheme,
HighlighterGeneric,
HighlighterCore,
LanguageRegistration,
ThemedToken,
} from "shiki";
import { createHighlighter } from "shiki";
ThemeRegistration,
} from "shiki/core";
import { createHighlighterCore } from "shiki/core";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
import { Button } from "@/components/ui/button";
import {
Select,
@@ -36,6 +37,78 @@ const isUnderline = (fontStyle: number | undefined) =>
// oxlint-disable-next-line eslint(no-bitwise)
fontStyle && fontStyle & 4;
const SUPPORTED_LANGUAGES = [
"bash",
"css",
"diff",
"html",
"javascript",
"json",
"jsonc",
"jsx",
"markdown",
"python",
"shellscript",
"tsx",
"typescript",
"yaml",
] as const;
export type SupportedCodeLanguage = (typeof SUPPORTED_LANGUAGES)[number];
const SUPPORTED_LANGUAGE_SET = new Set<string>(SUPPORTED_LANGUAGES);
const LANGUAGE_LOADERS: Record<
SupportedCodeLanguage,
() => Promise<LanguageRegistration[]>
> = {
bash: () => import("@shikijs/langs/bash").then((module) => module.default),
css: () => import("@shikijs/langs/css").then((module) => module.default),
diff: () => import("@shikijs/langs/diff").then((module) => module.default),
html: () => import("@shikijs/langs/html").then((module) => module.default),
javascript: () =>
import("@shikijs/langs/javascript").then((module) => module.default),
json: () => import("@shikijs/langs/json").then((module) => module.default),
jsonc: () => import("@shikijs/langs/jsonc").then((module) => module.default),
jsx: () => import("@shikijs/langs/jsx").then((module) => module.default),
markdown: () =>
import("@shikijs/langs/markdown").then((module) => module.default),
python: () =>
import("@shikijs/langs/python").then((module) => module.default),
shellscript: () =>
import("@shikijs/langs/shellscript").then((module) => module.default),
tsx: () => import("@shikijs/langs/tsx").then((module) => module.default),
typescript: () =>
import("@shikijs/langs/typescript").then((module) => module.default),
yaml: () => import("@shikijs/langs/yaml").then((module) => module.default),
};
const LANGUAGE_ALIASES: Record<string, SupportedCodeLanguage> = {
console: "shellscript",
cjs: "javascript",
htm: "html",
js: "javascript",
json5: "jsonc",
md: "markdown",
mjs: "javascript",
py: "python",
sh: "shellscript",
shell: "shellscript",
ts: "typescript",
yml: "yaml",
};
const normalizeLanguage = (
language: string,
): SupportedCodeLanguage | "text" => {
const normalized = language.trim().toLowerCase();
if (!normalized) {
return "text";
}
const aliased = LANGUAGE_ALIASES[normalized] ?? normalized;
return SUPPORTED_LANGUAGE_SET.has(aliased) ? aliased : "text";
};
// Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint
interface KeyedToken {
token: ThemedToken;
@@ -108,7 +181,7 @@ const LineSpan = ({
// Types
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
code: string;
language: BundledLanguage;
language: string;
showLineNumbers?: boolean;
};
@@ -128,10 +201,9 @@ const CodeBlockContext = createContext<CodeBlockContextType>({
});
// Highlighter cache (singleton per language)
const highlighterCache = new Map<
string,
Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
>();
let highlighterPromise: Promise<HighlighterCore> | undefined;
let themesPromise: Promise<void> | undefined;
const languagePromises = new Map<SupportedCodeLanguage, Promise<void>>();
// Token cache
const tokensCache = new Map<string, TokenizedCode>();
@@ -139,27 +211,44 @@ const tokensCache = new Map<string, TokenizedCode>();
// Subscribers for async token updates
const subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();
const getTokensCacheKey = (code: string, language: BundledLanguage) => {
const getTokensCacheKey = (code: string, language: string) => {
const start = code.slice(0, 100);
const end = code.length > 100 ? code.slice(-100) : "";
return `${language}:${code.length}:${start}:${end}`;
};
const getHighlighter = (
language: BundledLanguage,
): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {
const cached = highlighterCache.get(language);
const getHighlighter = (): Promise<HighlighterCore> => {
if (!highlighterPromise) {
highlighterPromise = createHighlighterCore({
engine: createJavaScriptRegexEngine({ forgiving: true }),
});
}
return highlighterPromise;
};
const ensureThemes = (highlighter: HighlighterCore): Promise<void> => {
if (!themesPromise) {
themesPromise = Promise.all([
import("@shikijs/themes/github-light").then((module) => module.default),
import("@shikijs/themes/github-dark").then((module) => module.default),
]).then((themes: ThemeRegistration[]) => highlighter.loadTheme(...themes));
}
return themesPromise;
};
const ensureLanguage = (
highlighter: HighlighterCore,
language: SupportedCodeLanguage,
): Promise<void> => {
const cached = languagePromises.get(language);
if (cached) {
return cached;
}
const highlighterPromise = createHighlighter({
langs: [language],
themes: ["github-light", "github-dark"],
});
highlighterCache.set(language, highlighterPromise);
return highlighterPromise;
const languagePromise = LANGUAGE_LOADERS[language]().then((registrations) =>
highlighter.loadLanguage(...registrations),
);
languagePromises.set(language, languagePromise);
return languagePromise;
};
// Create raw tokens for immediate display while highlighting loads
@@ -181,11 +270,16 @@ const createRawTokens = (code: string): TokenizedCode => ({
// Synchronous highlight with callback for async results
export const highlightCode = (
code: string,
language: BundledLanguage,
language: string,
// oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)
callback?: (result: TokenizedCode) => void,
): TokenizedCode | null => {
const tokensCacheKey = getTokensCacheKey(code, language);
const langToUse = normalizeLanguage(language);
if (langToUse === "text") {
return createRawTokens(code);
}
const tokensCacheKey = getTokensCacheKey(code, langToUse);
// Return cached result if available
const cached = tokensCache.get(tokensCacheKey);
@@ -202,11 +296,11 @@ export const highlightCode = (
}
// Start highlighting in background - fire-and-forget async pattern
getHighlighter(language)
getHighlighter()
// oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then)
.then((highlighter) => {
const availableLangs = highlighter.getLoadedLanguages();
const langToUse = availableLangs.includes(language) ? language : "text";
.then(async (highlighter) => {
await ensureThemes(highlighter);
await ensureLanguage(highlighter, langToUse);
const result = highlighter.codeToTokens(code, {
lang: langToUse,
@@ -376,7 +470,7 @@ export const CodeBlockContent = ({
showLineNumbers = false,
}: {
code: string;
language: BundledLanguage;
language: string;
showLineNumbers?: boolean;
}) => {
// Memoized raw tokens for immediate display
@@ -1,9 +1,5 @@
"use client";
import { cjk } from "@streamdown/cjk";
import { code } from "@streamdown/code";
import { math } from "@streamdown/math";
import { mermaid } from "@streamdown/mermaid";
import type { UIMessage } from "ai";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
@@ -16,7 +12,6 @@ import {
useMemo,
useState,
} from "react";
import { Streamdown } from "streamdown";
import { Button } from "@/components/ui/button";
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
import {
@@ -26,6 +21,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { HubStreamdown } from "./streamdown";
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
from: UIMessage["role"];
@@ -316,24 +312,18 @@ export const MessageBranchPage = ({
);
};
export type MessageResponseProps = ComponentProps<typeof Streamdown>;
const streamdownPlugins = { cjk, code, math, mermaid };
export type MessageResponseProps = ComponentProps<typeof HubStreamdown>;
export const MessageResponse = memo(
({ className, ...props }: MessageResponseProps) => (
<Streamdown
<HubStreamdown
className={cn(
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
className,
)}
plugins={streamdownPlugins}
{...props}
/>
),
(prevProps, nextProps) =>
prevProps.children === nextProps.children &&
nextProps.isAnimating === prevProps.isAnimating,
);
MessageResponse.displayName = "MessageResponse";
@@ -1,10 +1,6 @@
"use client";
import { useControllableState } from "@radix-ui/react-use-controllable-state";
import { cjk } from "@streamdown/cjk";
import { code } from "@streamdown/code";
import { math } from "@streamdown/math";
import { mermaid } from "@streamdown/mermaid";
import { BrainIcon, ChevronDownIcon } from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
import {
@@ -17,7 +13,6 @@ import {
useRef,
useState,
} from "react";
import { Streamdown } from "streamdown";
import {
Collapsible,
CollapsibleContent,
@@ -26,6 +21,7 @@ import {
import { cn } from "@/lib/utils";
import { Shimmer } from "./shimmer";
import { HubStreamdown } from "./streamdown";
interface ReasoningContextValue {
isStreaming: boolean;
@@ -204,8 +200,6 @@ export type ReasoningContentProps = ComponentProps<
children: string;
};
const streamdownPlugins = { cjk, code, math, mermaid };
export const ReasoningContent = memo(
({ className, children, ...props }: ReasoningContentProps) => (
<CollapsibleContent
@@ -216,7 +210,7 @@ export const ReasoningContent = memo(
)}
{...props}
>
<Streamdown plugins={streamdownPlugins}>{children}</Streamdown>
<HubStreamdown>{children}</HubStreamdown>
</CollapsibleContent>
),
);
@@ -0,0 +1,170 @@
import { cjk } from "@streamdown/cjk";
import type { MermaidConfig } from "mermaid";
import type { ComponentProps, ReactNode } from "react";
import { isValidElement, memo } from "react";
import {
type Components,
type DiagramPlugin,
Streamdown,
type StreamdownProps,
} from "streamdown";
import {
CodeBlock,
CodeBlockActions,
CodeBlockCopyButton,
CodeBlockFilename,
CodeBlockHeader,
CodeBlockTitle,
} from "@/components/ai-elements/code-block";
import { cn } from "@/lib/utils";
type MarkdownCodeProps = ComponentProps<"code"> & {
"data-block"?: boolean | string;
node?: {
properties?: {
metastring?: string;
};
};
};
const LANGUAGE_CLASS_PATTERN = /(?:^|\s)language-([^\s]+)/;
const START_LINE_PATTERN = /startLine=(\d+)/;
const NO_LINE_NUMBERS_PATTERN = /\bnoLineNumbers\b/;
function codeText(children: ReactNode): string {
if (typeof children === "string" || typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(codeText).join("");
}
if (isValidElement<{ children?: ReactNode }>(children)) {
return codeText(children.props.children);
}
return "";
}
const MarkdownCode = ({
children,
className,
node,
"data-block": dataBlock,
...props
}: MarkdownCodeProps) => {
const language = className?.match(LANGUAGE_CLASS_PATTERN)?.[1] ?? "text";
if (!dataBlock) {
return (
<code
className={cn(
"rounded bg-muted px-1.5 py-0.5 font-mono text-sm",
className,
)}
{...props}
>
{children}
</code>
);
}
const meta = node?.properties?.metastring;
const startLineMatch = meta?.match(START_LINE_PATTERN);
const startLine = startLineMatch ? Number.parseInt(startLineMatch[1], 10) : 1;
const showLineNumbers = meta ? !NO_LINE_NUMBERS_PATTERN.test(meta) : true;
return (
<CodeBlock
code={codeText(children)}
data-start-line={startLine > 1 ? startLine : undefined}
language={language}
showLineNumbers={showLineNumbers}
>
<CodeBlockHeader>
<CodeBlockTitle>
<CodeBlockFilename>{language}</CodeBlockFilename>
</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton />
</CodeBlockActions>
</CodeBlockHeader>
</CodeBlock>
);
};
const markdownComponents = {
code: MarkdownCode,
} satisfies Components;
const DEFAULT_MERMAID_CONFIG = {
fontFamily: "monospace",
securityLevel: "strict",
startOnLoad: false,
suppressErrorRendering: true,
theme: "default",
} satisfies MermaidConfig;
interface LazyMermaidInstance {
initialize: (config: MermaidConfig) => void;
render: (
id: string,
source: string,
) => Promise<{
svg: string;
}>;
}
function createLazyMermaidPlugin(): DiagramPlugin {
let config: MermaidConfig = DEFAULT_MERMAID_CONFIG;
let initialized = false;
const instance: LazyMermaidInstance = {
initialize(nextConfig: MermaidConfig) {
config = { ...DEFAULT_MERMAID_CONFIG, ...config, ...nextConfig };
initialized = false;
},
async render(id: string, source: string) {
const mermaidModule = await import("mermaid");
const mermaid = mermaidModule.default;
if (!initialized) {
mermaid.initialize(config);
initialized = true;
}
return mermaid.render(id, source);
},
};
return {
getMermaid(nextConfig?: MermaidConfig) {
if (nextConfig) {
instance.initialize(nextConfig);
}
return instance;
},
language: "mermaid",
name: "mermaid",
type: "diagram",
};
}
const streamdownPlugins = { cjk, mermaid: createLazyMermaidPlugin() };
export type HubStreamdownProps = StreamdownProps;
export const HubStreamdown = memo(
({ className, components, ...props }: HubStreamdownProps) => {
const mergedComponents = components
? { ...markdownComponents, ...components }
: markdownComponents;
return (
<Streamdown
className={className}
components={mergedComponents}
plugins={streamdownPlugins}
{...props}
/>
);
},
);
HubStreamdown.displayName = "HubStreamdown";
@@ -3,6 +3,7 @@ import {
Puzzle,
Search,
Server,
Star,
Trash2,
Zap,
} from "lucide-react";
@@ -26,6 +27,7 @@ import {
type MarketplacePrimitiveType,
type MarketplaceTag,
} from "@/lib/marketplace";
import { cn } from "@/lib/utils";
import { CommandBadge, PageFrame, PageHeader } from "./page-layout";
type EntryActionState =
@@ -114,6 +116,13 @@ function entryKey(entry: Pick<MarketplaceEntry, "id" | "type">): string {
return `${entry.type}:${entry.id}`;
}
function compareFeaturedEntries(
left: MarketplaceEntry,
right: MarketplaceEntry,
): number {
return Number(Boolean(right.featured)) - Number(Boolean(left.featured));
}
function normalizeMatchValue(value: string): string {
return value.trim().toLowerCase();
}
@@ -257,6 +266,8 @@ function MarketplaceEntryCard({
onToggleExpanded,
onUninstall,
matchedLocalItems = [],
showFeatured = true,
showTags = true,
sourceLabel,
tagLabels,
}: {
@@ -269,6 +280,8 @@ function MarketplaceEntryCard({
onToggleExpanded: (entry: MarketplaceEntry) => void;
onUninstall: (entry: MarketplaceEntry) => void;
matchedLocalItems?: MarketplaceLocalInstalledItem[];
showFeatured?: boolean;
showTags?: boolean;
sourceLabel?: string;
tagLabels: Map<string, string>;
}) {
@@ -299,15 +312,63 @@ function MarketplaceEntryCard({
: installed
? "Uninstall"
: "Install";
const statusMessage = inlineMessage ? (
<output
className={cn(
"text-xs",
actionState?.status === "failed"
? "text-destructive"
: "text-muted-foreground",
)}
>
{inlineMessage}
</output>
) : setupNeeded ? (
<span className="text-xs text-amber-700 dark:text-amber-300">
Requires setup after install
</span>
) : null;
const actionButton = (
<Button
disabled={!installedStatusReady || busy}
onClick={handleActionClick}
size="sm"
type="button"
variant={installed ? "destructive" : "default"}
>
{busy || !installedStatusReady ? <Spinner /> : null}
{installed && !busy ? <Trash2 className="size-4" /> : null}
{actionLabel}
</Button>
);
const content = (
<>
{!installed ? (
<div
className="absolute top-4 right-4"
data-marketplace-entry-interactive
>
{actionButton}
</div>
) : null}
<div className="min-w-0">
<div className="flex min-w-0 items-start justify-between gap-2">
<div className="flex min-w-0 flex-1 items-center gap-3">
<div
className={cn(
"flex min-w-0 flex-1 items-center gap-2",
!installed && "pr-24",
)}
>
<EntryIcon className="h-4 w-4 shrink-0 text-primary" />
<h2 className="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">
<h2 className="min-w-0 truncate text-sm font-semibold text-foreground">
{entry.name}
</h2>
{showFeatured && entry.featured ? (
<Badge className="border border-violet-500/20 bg-violet-500/10 text-violet-700 dark:text-violet-300">
<Star className="fill-current" />
Featured
</Badge>
) : null}
</div>
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
{sourceLabel ? (
@@ -349,23 +410,34 @@ function MarketplaceEntryCard({
) : null}
</div>
<p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
{entry.description}
</p>
<div className="flex flex-col gap-3 sm:flex-row sm:items-stretch sm:justify-between">
<div className="grid min-w-0 flex-1 gap-2">
{showTags && entry.tags.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{entry.tags.slice(0, 5).map((tag) => (
<Badge
key={tag}
variant="outline"
className="max-w-full text-muted-foreground"
>
<span className="truncate">{tagLabels.get(tag) ?? tag}</span>
</Badge>
))}
</div>
) : null}
{entry.tags.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{entry.tags.slice(0, 5).map((tag) => (
<Badge
key={tag}
variant="outline"
className="max-w-full text-muted-foreground"
>
<span className="truncate">{tagLabels.get(tag) ?? tag}</span>
</Badge>
))}
<p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
{entry.description}
</p>
{statusMessage}
</div>
) : null}
{installed ? (
<div className="flex shrink-0 flex-col items-start gap-1 sm:items-end sm:justify-end">
{actionButton}
</div>
) : null}
</div>
{matchedLocalItems.some((item) => item.renderMatchedDetails) ? (
<div className="grid gap-2" data-marketplace-entry-details>
@@ -377,36 +449,6 @@ function MarketplaceEntryCard({
</div>
) : null}
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="min-h-5 text-xs text-muted-foreground">
{inlineMessage ? (
<output
className={
actionState?.status === "failed"
? "text-destructive"
: "text-muted-foreground"
}
>
{inlineMessage}
</output>
) : setupNeeded ? (
<span className="text-amber-700 dark:text-amber-300">
Requires setup after install
</span>
) : null}
</div>
<Button
disabled={!installedStatusReady || busy}
onClick={handleActionClick}
type="button"
variant={installed ? "destructive" : "default"}
>
{busy || !installedStatusReady ? <Spinner /> : null}
{installed && !busy ? <Trash2 className="size-4" /> : null}
{actionLabel}
</Button>
</div>
{expanded && hasExpandableDetails ? (
<EntryDetails actionState={actionState} entry={entry} />
) : null}
@@ -415,7 +457,7 @@ function MarketplaceEntryCard({
if (!hasExpandableDetails) {
return (
<div className="grid gap-3 rounded-lg border bg-card p-4 text-left transition-colors hover:bg-accent/20">
<div className="relative grid gap-2 rounded-lg border bg-card p-4 text-left transition-colors hover:bg-accent/20">
{content}
</div>
);
@@ -426,7 +468,7 @@ function MarketplaceEntryCard({
<div
aria-expanded={expanded}
aria-label={`${expanded ? "Collapse" : "Expand"} ${entry.name}`}
className="grid cursor-pointer gap-3 rounded-lg border bg-card p-4 text-left transition-colors hover:bg-accent/20 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
className="relative grid cursor-pointer gap-2 rounded-lg border bg-card p-4 text-left transition-colors hover:bg-accent/20 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
onClick={(event) => {
if (
event.target instanceof HTMLElement &&
@@ -487,6 +529,7 @@ function MarketplaceSection({
emptyMessage,
entries,
expandedEntryKey,
headerContent,
installedEntryKeys,
installedStatusReady,
localOnlyInstalledItems = [],
@@ -494,6 +537,8 @@ function MarketplaceSection({
onInstall,
onToggleExpanded,
onUninstall,
showFeaturedBadges = true,
showEntryTags = true,
sourceLabel,
tagLabels,
title,
@@ -502,6 +547,7 @@ function MarketplaceSection({
emptyMessage: string;
entries: MarketplaceEntry[];
expandedEntryKey: string | null;
headerContent?: ReactNode;
installedEntryKeys: Set<string>;
installedStatusReady: boolean;
localOnlyInstalledItems?: MarketplaceLocalInstalledItem[];
@@ -509,6 +555,8 @@ function MarketplaceSection({
onInstall: (entry: MarketplaceEntry) => void;
onToggleExpanded: (entry: MarketplaceEntry) => void;
onUninstall: (entry: MarketplaceEntry) => void;
showFeaturedBadges?: boolean;
showEntryTags?: boolean;
sourceLabel?: string;
tagLabels: Map<string, string>;
title: string;
@@ -520,6 +568,7 @@ function MarketplaceSection({
<h2 className="text-base font-semibold text-foreground">{title}</h2>
<span className="text-sm text-muted-foreground">{totalCount}</span>
</div>
{headerContent}
{totalCount > 0 ? (
<div className="grid gap-3">
{localOnlyInstalledItems.map((item) => item.render())}
@@ -537,6 +586,8 @@ function MarketplaceSection({
onToggleExpanded={onToggleExpanded}
onUninstall={onUninstall}
matchedLocalItems={matchedLocalItemsByEntryKey?.get(key) ?? []}
showFeatured={showFeaturedBadges}
showTags={showEntryTags}
sourceLabel={sourceLabel}
tagLabels={tagLabels}
/>
@@ -626,19 +677,48 @@ export function MarketplaceView({
);
const primitiveEntries = useMemo(
() => catalog?.entries.filter((entry) => entry.type === primitive) ?? [],
() =>
(catalog?.entries.filter((entry) => entry.type === primitive) ?? []).sort(
compareFeaturedEntries,
),
[catalog?.entries, primitive],
);
const queryFilteredEntries = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return primitiveEntries.filter((entry) => {
return (
normalizedQuery.length === 0 ||
entrySearchText(entry, tagLabels).includes(normalizedQuery)
);
});
}, [primitiveEntries, query, tagLabels]);
const installedEntries = useMemo(
() =>
queryFilteredEntries.filter((entry) =>
installedEntryKeys.has(entryKey(entry)),
),
[queryFilteredEntries, installedEntryKeys],
);
const marketplaceEntriesBeforeTag = useMemo(
() =>
queryFilteredEntries.filter(
(entry) => !installedEntryKeys.has(entryKey(entry)),
),
[queryFilteredEntries, installedEntryKeys],
);
const tagCounts = useMemo(() => {
const counts = new Map<string, number>();
for (const entry of primitiveEntries) {
for (const entry of marketplaceEntriesBeforeTag) {
for (const tag of entry.tags) {
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
}
return counts;
}, [primitiveEntries]);
}, [marketplaceEntriesBeforeTag]);
const primitiveTags = useMemo(
() =>
@@ -646,26 +726,20 @@ export function MarketplaceView({
[catalog?.tags, tagCounts],
);
const filteredEntries = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return primitiveEntries.filter((entry) => {
const matchesTag = !selectedTag || entry.tags.includes(selectedTag);
const matchesQuery =
normalizedQuery.length === 0 ||
entrySearchText(entry, tagLabels).includes(normalizedQuery);
return matchesTag && matchesQuery;
});
}, [primitiveEntries, query, selectedTag, tagLabels]);
const catalogEntries = useMemo(
() =>
marketplaceEntriesBeforeTag.filter(
(entry) => !selectedTag || entry.tags.includes(selectedTag),
),
[marketplaceEntriesBeforeTag, selectedTag],
);
const matchedLocalItemsByEntryKey = useMemo(() => {
const matched = new Map<string, MarketplaceLocalInstalledItem[]>();
for (const item of installedItems ?? []) {
for (const entry of filteredEntries) {
for (const entry of installedEntries) {
const key = entryKey(entry);
if (
!installedEntryKeys.has(key) ||
!entryMatchesLocalItem(entry, item)
) {
if (!entryMatchesLocalItem(entry, item)) {
continue;
}
const items = matched.get(key) ?? [];
@@ -674,49 +748,78 @@ export function MarketplaceView({
}
}
return matched;
}, [filteredEntries, installedEntryKeys, installedItems]);
}, [installedEntries, installedItems]);
const matchedLocalItemKeys = useMemo(
() =>
new Set(
[...matchedLocalItemsByEntryKey.values()].flatMap((items) =>
items.map((item) => item.key),
),
),
[matchedLocalItemsByEntryKey],
);
const matchedLocalItemKeys = useMemo(() => {
const matched = new Set<string>();
const installedMarketplaceEntries = primitiveEntries.filter((entry) =>
installedEntryKeys.has(entryKey(entry)),
);
for (const item of installedItems ?? []) {
if (
installedMarketplaceEntries.some((entry) =>
entryMatchesLocalItem(entry, item),
)
) {
matched.add(item.key);
}
}
return matched;
}, [installedEntryKeys, installedItems, primitiveEntries]);
const localOnlyInstalledItems = useMemo(
() =>
(installedItems ?? []).filter(
(item) => !matchedLocalItemKeys.has(item.key),
),
[installedItems, matchedLocalItemKeys],
);
const localOnlyInstalledItems = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return (installedItems ?? []).filter((item) => {
if (matchedLocalItemKeys.has(item.key)) {
return false;
}
return (
normalizedQuery.length === 0 ||
item.matchValues
.map(normalizeMatchValue)
.some((value) => value.includes(normalizedQuery))
);
});
}, [installedItems, matchedLocalItemKeys, query]);
const installedEntries = useMemo(
() =>
filteredEntries.filter((entry) =>
installedEntryKeys.has(entryKey(entry)),
),
[filteredEntries, installedEntryKeys],
);
const catalogEntries = useMemo(
() =>
filteredEntries.filter(
(entry) => !installedEntryKeys.has(entryKey(entry)),
),
[filteredEntries, installedEntryKeys],
);
const activeFilters = query.trim().length > 0 || selectedTag !== null;
const installedStatusReady = installedStatusState === "ready";
const clearFilters = () => {
setQuery("");
setSelectedTag(null);
};
const marketplaceTagFilters =
primitiveTags.length > 0 ? (
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div className="flex gap-2 overflow-x-auto pb-1">
{primitiveTags.map((tag) => (
<TagButton
active={selectedTag === tag.id}
count={tagCounts.get(tag.id) ?? 0}
key={tag.id}
onClick={() =>
setSelectedTag((current) =>
current === tag.id ? null : tag.id,
)
}
tag={tag}
/>
))}
</div>
<div className="flex min-h-8 shrink-0 items-center gap-2 text-sm text-muted-foreground">
<span className="font-medium text-foreground">
{catalogEntries.length}
</span>
<span>{catalogEntries.length === 1 ? "result" : "results"}</span>
{selectedTag ? (
<Button
onClick={() => setSelectedTag(null)}
size="sm"
type="button"
variant="ghost"
>
Clear filters
</Button>
) : null}
</div>
</div>
) : null;
const setEntryState = (entry: MarketplaceEntry, state: EntryActionState) => {
const key = entryKey(entry);
@@ -863,43 +966,7 @@ export function MarketplaceView({
value={query}
/>
</div>
<div className="flex min-h-8 items-center gap-2 text-sm text-muted-foreground">
<span className="font-medium text-foreground">
{filteredEntries.length}
</span>
<span>
{filteredEntries.length === 1 ? "result" : "results"}
</span>
{activeFilters ? (
<Button
onClick={clearFilters}
size="sm"
type="button"
variant="ghost"
>
Clear filters
</Button>
) : null}
</div>
</div>
{primitiveTags.length > 0 ? (
<div className="flex gap-2 overflow-x-auto pb-1">
{primitiveTags.map((tag) => (
<TagButton
active={selectedTag === tag.id}
count={tagCounts.get(tag.id) ?? 0}
key={tag.id}
onClick={() =>
setSelectedTag((current) =>
current === tag.id ? null : tag.id,
)
}
tag={tag}
/>
))}
</div>
) : null}
</div>
<MarketplaceSection
@@ -914,6 +981,8 @@ export function MarketplaceView({
onInstall={installEntry}
onToggleExpanded={toggleExpanded}
onUninstall={uninstallEntry}
showFeaturedBadges={false}
showEntryTags={false}
sourceLabel="Marketplace"
tagLabels={tagLabels}
title="Installed"
@@ -924,6 +993,7 @@ export function MarketplaceView({
emptyMessage={pageDetails.emptyCatalog}
entries={catalogEntries}
expandedEntryKey={expandedEntryKey}
headerContent={marketplaceTagFilters}
installedEntryKeys={installedEntryKeys}
installedStatusReady={installedStatusReady}
onInstall={installEntry}
@@ -39,10 +39,10 @@ export type CustomizationSection =
const sectionDescriptions: Record<CustomizationSection, string> = {
Rules: "Review project and global rule files that shape Cline behavior.",
Hooks: "Inspect hook configuration and recent execution status.",
MCP: "Manage installed MCP servers and add new servers from the catalog.",
Skills: "Manage installed skills and add new skills from the catalog.",
MCP: "Manage installed MCP servers and add new servers from the marketplace.",
Skills: "Manage installed skills and add new skills from the marketplace.",
Agents: "Review configured agents discovered from local settings.",
Plugins: "Manage installed plugins and add new plugins from the catalog.",
Plugins: "Manage installed plugins and add new plugins from the marketplace.",
Tools: "Inspect built-in tools and tools contributed by plugins.",
};
@@ -17,6 +17,7 @@ export type MarketplaceEntry = {
id: string;
type: MarketplacePrimitiveType;
name: string;
featured?: boolean;
tagline: string;
description: string;
tags: string[];
@@ -100,7 +101,7 @@ export async function fetchMarketplaceCatalog(): Promise<MarketplaceCatalog> {
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error(`Failed to fetch marketplace catalog: ${response.status}`);
throw new Error(`Failed to fetch marketplace: ${response.status}`);
}
const data = await response.json();
const baseUrl = typeof data?.baseUrl === "string" ? data.baseUrl : undefined;
@@ -152,6 +153,10 @@ export async function fetchMarketplaceCatalog(): Promise<MarketplaceCatalog> {
id: candidate.id,
type: candidate.type,
name: candidate.name,
featured:
typeof candidate.featured === "boolean"
? candidate.featured
: undefined,
tagline: candidate.tagline,
description: candidate.description,
tags: toStringArray(candidate.tags),
+29
View File
@@ -3,6 +3,27 @@ import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
const mermaidChunkGroups = [
{
name: "mermaid-parser",
maxSize: 450_000,
test: /node_modules[\\/](?:\.bun[\\/])?@mermaid-js[+]parser/,
},
{
name: "mermaid-langium",
test: /node_modules[\\/](?:\.bun[\\/])?langium/,
},
{
name: "mermaid-layout",
maxSize: 450_000,
test: /node_modules[\\/](?:\.bun[\\/])?(?:cytoscape|cytoscape-cose-bilkent|dagre|elkjs)/,
},
{
name: "mermaid-markup",
test: /node_modules[\\/](?:\.bun[\\/])?(?:katex|dompurify)/,
},
];
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
@@ -25,5 +46,13 @@ export default defineConfig({
outDir: "../../dist/webview",
emptyOutDir: true,
cssMinify: "esbuild",
chunkSizeWarningLimit: 600,
rolldownOptions: {
output: {
codeSplitting: {
groups: mermaidChunkGroups,
},
},
},
},
});
-4
View File
@@ -1,7 +1,3 @@
<div align="center"><sub>
English | <a href="https://github.com/cline/cline/blob/main/locales/es/README.md" target="_blank">Español</a> | <a href="https://github.com/cline/cline/blob/main/locales/de/README.md" target="_blank">Deutsch</a> | <a href="https://github.com/cline/cline/blob/main/locales/ja/README.md" target="_blank">日本語</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-cn/README.md" target="_blank">简体中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-tw/README.md" target="_blank">繁體中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/ko/README.md" target="_blank">한국어</a>
</sub></div>
# Cline
<div align="center">
<table>
+1 -1
View File
@@ -2,7 +2,7 @@
"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": "4.0.5",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
+16 -8
View File
@@ -1,8 +1,13 @@
import { ApiConfiguration, clinePassDefaultModelId, ModelInfo, QwenApiRegions, resolveClinePassModelInfo } from "@shared/api"
import {
ApiConfiguration,
buildModelInfoNameMap,
clinePassDefaultModelId,
ModelInfo,
QwenApiRegions,
resolveClinePassModelInfo,
} from "@shared/api"
import { Mode } from "@shared/storage/types"
import { featureFlagsService } from "@/services/feature-flags"
import { ClineStorageMessage } from "@/shared/messages/content"
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
import { Logger } from "@/shared/services/Logger"
import { ClineTool } from "@/shared/tools"
import { AIhubmixHandler } from "./providers/aihubmix"
@@ -80,10 +85,7 @@ function createHandlerForProvider(
options: Omit<ApiConfiguration, "apiProvider">,
mode: Mode,
): ApiHandler {
const effectiveApiProvider =
apiProvider === "cline-pass" && !featureFlagsService.getBooleanFlagEnabled(FeatureFlag.CLINE_PASS) ? "cline" : apiProvider
switch (effectiveApiProvider) {
switch (apiProvider) {
case "anthropic":
return new AnthropicHandler({
onRetryAttempt: options.onRetryAttempt,
@@ -204,6 +206,7 @@ function createHandlerForProvider(
onRetryAttempt: options.onRetryAttempt,
deepSeekApiKey: options.deepSeekApiKey,
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
})
case "requesty":
return new RequestyHandler({
@@ -292,7 +295,12 @@ function createHandlerForProvider(
const clineModelId = configuredClinePassModelId?.startsWith("cline-pass/")
? configuredClinePassModelId
: clinePassDefaultModelId
const clineModelInfo = configuredClinePassModelInfo || resolveClinePassModelInfo(clineModelId)
const clineModelInfo = resolveClinePassModelInfo(
clineModelId,
configuredClinePassModelInfo
? buildModelInfoNameMap({ [clineModelId]: configuredClinePassModelInfo })
: undefined,
)
return new ClineHandler({
onRetryAttempt: options.onRetryAttempt,
clineAccountId: options.clineAccountId,
@@ -220,6 +220,11 @@ describe("AwsBedrockHandler", () => {
bedrockModels["anthropic.claude-fable-5:1m"].supportsGlobalEndpoint.should.equal(true)
})
it("should mark Bedrock Sonnet 5 variants as global-endpoint capable", () => {
bedrockModels["anthropic.claude-sonnet-5"].supportsGlobalEndpoint.should.equal(true)
bedrockModels["anthropic.claude-sonnet-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)
@@ -990,6 +995,19 @@ describe("AwsBedrockHandler", () => {
modelId.should.equal("jp.anthropic.claude-sonnet-4-6")
})
it("should apply JP cross-region prefix for sonnet 5", async () => {
const jpOptions: AwsBedrockHandlerOptions = {
...mockOptions,
awsUseCrossRegionInference: true,
apiModelId: "anthropic.claude-sonnet-5",
awsRegion: "ap-northeast-1",
}
const jpHandler = new AwsBedrockHandler(jpOptions)
const modelId = await jpHandler.getModelId()
modelId.should.equal("jp.anthropic.claude-sonnet-5")
})
it("should apply global cross-region prefix for supported models", async () => {
const globalOptions: AwsBedrockHandlerOptions = {
...mockOptions,
@@ -426,6 +426,26 @@ describe("ClaudeCodeHandler", () => {
model.info.contextWindow.should.equal(200_000)
})
it("should support Sonnet 5 model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "claude-sonnet-5",
})
const model = handler.getModel()
model.id.should.equal("claude-sonnet-5")
model.info.contextWindow.should.equal(200_000)
})
it("should support Sonnet 5 1m model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "claude-sonnet-5[1m]",
})
const model = handler.getModel()
model.id.should.equal("claude-sonnet-5[1m]")
model.info.contextWindow.should.equal(1_000_000)
})
it("should support Fable 5 1m model id", () => {
const handler = new ClaudeCodeHandler({
apiModelId: "claude-fable-5[1m]",
@@ -69,6 +69,7 @@ describe("SapAiCoreHandler", () => {
it("should support different Claude model variants", () => {
const modelVariants = [
"anthropic--claude-sonnet-5",
"anthropic--claude-4.6-sonnet",
"anthropic--claude-4-sonnet",
"anthropic--claude-4-opus",
@@ -134,9 +134,11 @@ interface ProviderChainOptions {
profile?: string
}
// a special jp inference profile was created for sonnet 4.6, opus 4.6, sonnet 4.5 & haiku 4.5
// a special jp inference profile was created for newer Claude CRIS models
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
const JP_SUPPORTED_CRIS_MODELS = [
"anthropic.claude-sonnet-5",
"anthropic.claude-sonnet-5:1m",
"anthropic.claude-sonnet-4-6",
"anthropic.claude-sonnet-4-6:1m",
"anthropic.claude-opus-4-6-v1",
+10 -1
View File
@@ -1,7 +1,10 @@
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
import { calculateApiCostOpenAI } from "@utils/cost"
import OpenAI from "openai"
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
import type {
ChatCompletionReasoningEffort,
ChatCompletionTool as OpenAITool,
} from "openai/resources/chat/completions"
import { buildExternalBasicHeaders } from "@/services/EnvUtils"
import { ClineStorageMessage } from "@/shared/messages/content"
import { fetch } from "@/shared/net"
@@ -15,6 +18,7 @@ import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-p
interface DeepSeekHandlerOptions extends CommonApiHandlerOptions {
deepSeekApiKey?: string
apiModelId?: string
reasoningEffort?: string
}
export class DeepSeekHandler implements ApiHandler {
@@ -98,6 +102,11 @@ export class DeepSeekHandler implements ApiHandler {
stream_options: { include_usage: true },
// Only set temperature for non-thinking models
...(isDeepSeekThinkingModel ? {} : { temperature: 0 }),
// DeepSeek thinking models accept reasoning effort (low/medium map to high, xhigh maps to max).
// "none" isn't a valid DeepSeek value, so omit it and let the API use its default.
...(isDeepSeekThinkingModel && this.options.reasoningEffort && this.options.reasoningEffort !== "none"
? { reasoning_effort: this.options.reasoningEffort as ChatCompletionReasoningEffort }
: {}),
...getOpenAIToolParams(tools),
})
@@ -672,6 +672,7 @@ export class SapAiCoreHandler implements ApiHandler {
const anthropicModels = [
"anthropic--claude-4.5-haiku",
"anthropic--claude-sonnet-5",
"anthropic--claude-4.7-opus",
"anthropic--claude-4.6-opus",
"anthropic--claude-4.5-opus",
@@ -725,6 +726,7 @@ export class SapAiCoreHandler implements ApiHandler {
"amazon--nova-micro",
];
const converseStreamModels = [
"anthropic--claude-sonnet-5",
"anthropic--claude-4.7-opus",
"anthropic--claude-4.6-opus",
"anthropic--claude-4.5-opus",
@@ -78,6 +78,19 @@ describe("createOpenRouterStream", () => {
payload.should.not.have.property("max_tokens")
})
it("strips the custom Sonnet 5 1m suffix and pins Anthropic/Vertex routing", async () => {
const { client, create } = createClient()
await createOpenRouterStream(client as any, "system prompt", [{ role: "user", content: "hello" }] as any, {
id: "anthropic/claude-sonnet-5:1m",
info: createModelInfo(128_000),
})
const payload = create.firstCall.args[0] as any
payload.should.have.property("model", "anthropic/claude-sonnet-5")
payload.provider.should.deepEqual({ order: ["anthropic", "google-vertex/global"], allow_fallbacks: false })
})
it("adds cache_control blocks for Qwen models that require explicit OpenRouter caching", async () => {
for (const modelId of ["qwen/qwen3.6-plus", "qwen/qwen3.7-max"]) {
const { client, create } = createClient()
@@ -114,4 +127,87 @@ describe("createOpenRouterStream", () => {
should(payload.temperature).equal(undefined)
should(payload.top_p).equal(undefined)
})
it("includes reasoning for Claude budget-based reasoning models", async () => {
const { client, create } = createClient()
await createOpenRouterStream(
client as any,
"system prompt",
[{ role: "user", content: "hello" }] as any,
{
id: "anthropic/claude-sonnet-4.5",
info: createModelInfo(64_000),
},
undefined,
16_384,
)
const payload = create.firstCall.args[0] as any
payload.should.have.property("include_reasoning", true)
payload.reasoning.should.deepEqual({ max_tokens: 16_384 })
should(payload.temperature).equal(undefined)
})
it("sends reasoning effort instead of token budgets for supported OpenRouter/Cline model families", async () => {
for (const modelId of [
"zai/glm-5.2",
"z-ai/glm-5.2",
"moonshotai/kimi-k2-thinking",
"accounts/fireworks/models/minimax-m3",
"provider/mimo-vl",
"qwen/qwen3.7-max",
"deepseek/deepseek-r1",
]) {
const { client, create } = createClient()
await createOpenRouterStream(
client as any,
"system prompt",
[{ role: "user", content: "hello" }] as any,
{
id: modelId,
info: { ...createModelInfo(131_072), thinkingConfig: { maxBudget: 16_384 } },
},
"high",
16_384,
)
const payload = create.firstCall.args[0] as any
payload.should.have.property("include_reasoning", true)
payload.reasoning.should.deepEqual({ effort: "high" })
}
})
it("does not send reasoning effort for ClinePass requests when unset", async () => {
const { client, create } = createClient()
await createOpenRouterStream(client as any, "system prompt", [{ role: "user", content: "hello" }] as any, {
id: "cline-pass/glm-5.2",
info: createModelInfo(131_072),
})
const payload = create.firstCall.args[0] as any
payload.should.have.property("include_reasoning", true)
payload.should.not.have.property("reasoning")
})
it("sends the selected reasoning effort for ClinePass requests", async () => {
const { client, create } = createClient()
await createOpenRouterStream(
client as any,
"system prompt",
[{ role: "user", content: "hello" }] as any,
{
id: "cline-pass/glm-5.1",
info: createModelInfo(131_072),
},
"high",
)
const payload = create.firstCall.args[0] as any
payload.should.have.property("include_reasoning", true)
payload.reasoning.should.deepEqual({ effort: "high" })
})
})
@@ -10,6 +10,7 @@ import {
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeSonnet461mModelId,
openRouterClaudeSonnet51mModelId,
} from "@shared/api"
import { normalizeOpenaiReasoningEffort } from "@shared/storage/types"
import { isClaudeOpusAdaptiveThinkingModel, resolveClaudeOpusAdaptiveThinking } from "@shared/utils/reasoning-support"
@@ -62,6 +63,7 @@ export async function createOpenRouterStream(
model.id === openRouterClaudeSonnet41mModelId ||
model.id === openRouterClaudeSonnet451mModelId ||
model.id === openRouterClaudeSonnet461mModelId ||
model.id === openRouterClaudeSonnet51mModelId ||
model.id === openRouterClaudeOpus461mModelId ||
model.id === openRouterClaudeOpus471mModelId ||
model.id === openRouterClaudeOpus481mModelId ||
@@ -147,6 +149,8 @@ export async function createOpenRouterStream(
switch (model.id) {
case "anthropic/claude-haiku-4.5":
case "anthropic/claude-4.5-haiku":
case "anthropic/claude-sonnet-5":
case "anthropic/claude-5-sonnet":
case "anthropic/claude-sonnet-4.6":
case "anthropic/claude-4.6-sonnet":
case "anthropic/claude-sonnet-4.5":
@@ -9,6 +9,7 @@ import {
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeSonnet461mModelId,
openRouterClaudeSonnet51mModelId,
} from "@shared/api"
import { normalizeOpenaiReasoningEffort } from "@shared/storage/types"
import { isClaudeOpusAdaptiveThinkingModel, resolveClaudeOpusAdaptiveThinking } from "@shared/utils/reasoning-support"
@@ -38,6 +39,7 @@ export async function createVercelAIGatewayStream(
model.id === openRouterClaudeSonnet41mModelId ||
model.id === openRouterClaudeSonnet451mModelId ||
model.id === openRouterClaudeSonnet461mModelId ||
model.id === openRouterClaudeSonnet51mModelId ||
model.id === openRouterClaudeOpus461mModelId ||
model.id === openRouterClaudeOpus471mModelId ||
model.id === openRouterClaudeOpus481mModelId ||
+3 -3
View File
@@ -11,6 +11,7 @@ import { McpHub } from "@services/mcp/McpHub"
import type { ApiProvider, ModelInfo } from "@shared/api"
import type { ChatContent } from "@shared/ChatContent"
import type { ExtensionState, Platform } from "@shared/ExtensionMessage"
import { DEFAULT_FOCUS_CHAIN_SETTINGS } from "@shared/FocusChainSettings"
import type { HistoryItem } from "@shared/HistoryItem"
import type { McpMarketplaceCatalog, McpMarketplaceItem } from "@shared/mcp"
import { type Settings } from "@shared/storage/state-keys"
@@ -523,8 +524,7 @@ export class Controller {
// On login we route the user to the managed "cline" provider, but preserve a
// "cline-pass" selection made during onboarding (otherwise it would be clobbered).
// A "cline-pass" provider can only be set when the ext-cline-pass flag is on, so
// non-ClinePass logins are unaffected.
// Non-ClinePass logins are unaffected because they do not persist "cline-pass".
const planProvider: ApiProvider =
currentApiConfiguration.planModeApiProvider === "cline-pass" ? "cline-pass" : "cline"
const actProvider: ApiProvider = currentApiConfiguration.actModeApiProvider === "cline-pass" ? "cline-pass" : "cline"
@@ -856,7 +856,7 @@ export class Controller {
const taskHistory = this.stateManager.getGlobalStateKey("taskHistory")
const autoApprovalSettings = this.stateManager.getGlobalSettingsKey("autoApprovalSettings")
const browserSettings = this.stateManager.getGlobalSettingsKey("browserSettings")
const focusChainSettings = this.stateManager.getGlobalSettingsKey("focusChainSettings")
const focusChainSettings = this.stateManager.getGlobalSettingsKey("focusChainSettings") ?? DEFAULT_FOCUS_CHAIN_SETTINGS
const preferredLanguage = this.stateManager.getGlobalSettingsKey("preferredLanguage")
const mode = this.stateManager.getGlobalSettingsKey("mode")
const strictPlanModeEnabled = this.stateManager.getGlobalSettingsKey("strictPlanModeEnabled")
@@ -25,7 +25,7 @@ describe("clearOrganizationForClinePassProviderSelection", () => {
} as unknown as Controller;
}
it("does nothing when Cline Pass is not selected", async () => {
it("does nothing when ClinePass is not selected", async () => {
await clearOrganizationForClinePassProviderSelection(createController(), {
planModeApiProvider: "cline",
actModeApiProvider: "openrouter",
@@ -34,7 +34,7 @@ describe("clearOrganizationForClinePassProviderSelection", () => {
assert.strictEqual(switchAccount.callCount, 0);
});
it("switches to the personal account when Cline Pass is selected", async () => {
it("switches to the personal account when ClinePass is selected", async () => {
await clearOrganizationForClinePassProviderSelection(createController(), {
planModeApiProvider: "cline-pass",
actModeApiProvider: "openrouter",
@@ -135,4 +135,72 @@ describe("refreshClineModels", () => {
expect(fable1m.contextWindow).to.equal(1_000_000)
expect(fable1m.tiers).to.not.equal(undefined)
})
it("prefers Vercel-style Z.ai IDs when the Cline model list includes OpenRouter aliases", 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: "z-ai/glm-5.2",
name: "OpenRouter GLM 5.2",
description: "OpenRouter alias",
context_length: 128_000,
top_provider: {
max_completion_tokens: 8_192,
context_length: 128_000,
is_moderated: false,
},
architecture: {
modality: "text->text",
},
pricing: {
prompt: "0.00000098",
completion: "0.00000308",
},
supported_parameters: ["include_reasoning", "reasoning"],
},
{
id: "zai/glm-5.2",
name: "GLM 5.2",
description: "Vercel canonical ID",
context_length: 1_000_000,
top_provider: {
max_completion_tokens: 131_072,
context_length: 1_000_000,
is_moderated: false,
},
architecture: {
modality: "text->text",
},
pricing: {
prompt: "0.0000015",
completion: "0.0000045",
},
supported_parameters: ["include_reasoning", "reasoning"],
},
],
},
})
const models = await refreshClineModels({} as Controller)
expect(models["zai/glm-5.2"]).to.not.equal(undefined)
expect(models["zai/glm-5.2"].contextWindow).to.equal(1_000_000)
expect(models["z-ai/glm-5.2"]).to.equal(undefined)
})
})
@@ -39,7 +39,7 @@ describe("refreshClineRecommendedModels", () => {
data: {
recommended: [
{
id: "anthropic/claude-sonnet-4.6",
id: "anthropic/claude-sonnet-5",
description: "Remote recommended",
tags: ["NEW"],
},
@@ -48,7 +48,7 @@ describe("refreshClineRecommendedModels", () => {
clinePass: [
{
id: "cline-pass/glm-5",
description: "Remote Cline Pass",
description: "Remote ClinePass",
tags: ["CLINE_PASS"],
},
],
@@ -61,8 +61,8 @@ describe("refreshClineRecommendedModels", () => {
expect(result).to.deep.equal({
recommended: [
{
id: "anthropic/claude-sonnet-4.6",
name: "anthropic/claude-sonnet-4.6",
id: "anthropic/claude-sonnet-5",
name: "anthropic/claude-sonnet-5",
description: "Remote recommended",
tags: ["NEW"],
},
@@ -79,7 +79,7 @@ describe("refreshClineRecommendedModels", () => {
{
id: "cline-pass/glm-5",
name: "cline-pass/glm-5",
description: "Remote Cline Pass",
description: "Remote ClinePass",
tags: ["CLINE_PASS"],
},
],
@@ -114,7 +114,7 @@ describe("refreshClineRecommendedModels", () => {
clinePass: [
{
id: "cline-pass/glm-5",
description: "Remote Cline Pass",
description: "Remote ClinePass",
tags: ["CLINE_PASS"],
},
],
@@ -127,4 +127,99 @@ describe("refreshClineRecommendedModels", () => {
expect(axiosGetStub.calledOnce).to.equal(true);
expect(secondResult).to.deep.equal(firstResult);
});
it("normalizes Cline provider Z.ai recommended IDs to the Cline API alias", async () => {
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(disk, "ensureCacheDirectoryExists").resolves("/tmp");
sandbox.stub(fs, "writeFile").resolves();
sandbox.stub(axios, "get").resolves({
data: {
recommended: [
{
id: "zai/glm-5.2",
name: "zai/glm-5.2",
description: "Recommended GLM",
},
],
free: [
{
id: "zai/free-glm",
description: "Free GLM",
},
],
},
});
const result = await refreshClineRecommendedModels();
expect(result.recommended[0]).to.include({
id: "z-ai/glm-5.2",
name: "z-ai/glm-5.2",
});
expect(result.free[0]).to.include({
id: "z-ai/free-glm",
name: "z-ai/free-glm",
});
});
it("normalizes cached Cline provider Z.ai recommended IDs", async () => {
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(disk, "ensureCacheDirectoryExists").resolves("/tmp");
sandbox.stub(axios, "get").rejects(new Error("network unavailable"));
sandbox.stub(fs, "access").resolves();
sandbox.stub(fs, "readFile").resolves(
JSON.stringify({
recommended: [
{
id: "zai/glm-5.2",
name: "zai/glm-5.2",
},
],
}),
);
const result = await refreshClineRecommendedModels();
expect(result.recommended.map((model) => model.id)).to.deep.equal(["z-ai/glm-5.2"]);
expect(result.recommended.map((model) => model.name)).to.deep.equal(["z-ai/glm-5.2"]);
});
it("prefers canonical ClinePass Z.ai IDs when aliases are also present", async () => {
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(disk, "ensureCacheDirectoryExists").resolves("/tmp");
sandbox.stub(fs, "writeFile").resolves();
sandbox.stub(axios, "get").resolves({
data: {
clinePass: [
{
id: "cline-pass/z-ai/glm-5.2",
description: "OpenRouter alias",
},
{
id: "cline-pass/zai/glm-5.2",
description: "Canonical ID",
},
],
},
});
const result = await refreshClineRecommendedModels();
expect(result.clinePass.map((model) => model.id)).to.deep.equal(["cline-pass/zai/glm-5.2"]);
});
});
@@ -5,7 +5,7 @@ import type { Controller } from "../index"
export const CLINE_PASS_PROVIDER_ID = "cline-pass"
/**
* Cline Pass always uses the user's personal Cline account balance.
* ClinePass always uses the user's personal Cline account balance.
*
* This is intentionally best-effort: selecting the provider should still be
* saved even if the account switch fails.
@@ -24,6 +24,6 @@ export async function clearOrganizationForClinePassProviderSelection(
try {
await controller.accountService.switchAccount(null)
} catch (error) {
Logger.debug("Failed to switch Cline Pass to personal account", { error })
Logger.debug("Failed to switch ClinePass to personal account", { error })
}
}
@@ -21,6 +21,7 @@ import {
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeSonnet461mModelId,
openRouterClaudeSonnet51mModelId,
} from "@/shared/api"
import { getAxiosSettings } from "@/shared/net"
import { FeatureFlag } from "@/shared/services/feature-flags/feature-flags"
@@ -87,6 +88,37 @@ interface ClineRawModelInfo {
// Track pending refresh promise to prevent duplicate concurrent fetches
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
interface ModelIdAliasRule {
canonicalPrefix: string
aliasPrefix: string
}
// Mirrors @cline/llms VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES.
const VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES = [
{ canonicalPrefix: "zai/", aliasPrefix: "z-ai/" },
] as const satisfies readonly ModelIdAliasRule[]
function preferCanonicalModelIds<T>(models: Record<string, T>, rules: readonly ModelIdAliasRule[]): Record<string, T> {
return Object.fromEntries(
Object.entries(models).filter(([modelId]) => {
for (const rule of rules) {
if (!modelId.startsWith(rule.aliasPrefix)) {
continue
}
const canonicalModelId = `${rule.canonicalPrefix}${modelId.slice(rule.aliasPrefix.length)}`
if (canonicalModelId in models) {
return false
}
}
return true
}),
)
}
function preferClineCanonicalModelIds(models: Record<string, ModelInfo>): Record<string, ModelInfo> {
return preferCanonicalModelIds(models, VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES)
}
async function fetchRawClineModels(): Promise<ClineRawModelInfo[]> {
const apiBaseUrl = ClineEnv.config().apiBaseUrl
const response = await axios.get(`${apiBaseUrl}/api/v1/ai/cline/models`, getAxiosSettings())
@@ -107,7 +139,7 @@ async function fetchRawClineModels(): Promise<ClineRawModelInfo[]> {
export async function refreshClineModels(controller: Controller): Promise<Record<string, ModelInfo>> {
const shouldUseClineEndpointSource = featureFlagsService.getBooleanFlagEnabled(FeatureFlag.EXTENSION_CLINE_MODELS_ENDPOINT)
if (!shouldUseClineEndpointSource) {
return refreshOpenRouterModels(controller)
return preferClineCanonicalModelIds(await refreshOpenRouterModels(controller))
}
// Check in-memory cache first
@@ -181,6 +213,15 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
// Apply model-specific overrides for known models
switch (rawModel.id) {
case "anthropic/claude-sonnet-5":
case "anthropic/claude-5-sonnet":
modelInfo.contextWindow = 200_000
modelInfo.supportsPromptCache = true
modelInfo.inputPrice = 2.0
modelInfo.outputPrice = 10.0
modelInfo.cacheWritesPrice = 2.5
modelInfo.cacheReadsPrice = 0.2
break
case "anthropic/claude-sonnet-4.6":
case "anthropic/claude-4.6-sonnet":
case "anthropic/claude-sonnet-4.5":
@@ -273,12 +314,17 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
rawModel.id === "anthropic/claude-sonnet-4" ||
rawModel.id === "anthropic/claude-sonnet-4.5" ||
rawModel.id === "anthropic/claude-sonnet-4.6" ||
rawModel.id === "anthropic/claude-4.6-sonnet"
rawModel.id === "anthropic/claude-4.6-sonnet" ||
rawModel.id === "anthropic/claude-sonnet-5" ||
rawModel.id === "anthropic/claude-5-sonnet"
) {
const claudeSonnet1mModelInfo = cloneDeep(modelInfo)
claudeSonnet1mModelInfo.contextWindow = 1_000_000
claudeSonnet1mModelInfo.tiers = CLAUDE_SONNET_1M_TIERS
if (rawModel.id === "anthropic/claude-sonnet-5" || rawModel.id === "anthropic/claude-5-sonnet") {
models[openRouterClaudeSonnet51mModelId] = claudeSonnet1mModelInfo
}
if (rawModel.id === "anthropic/claude-sonnet-4") {
models[openRouterClaudeSonnet41mModelId] = claudeSonnet1mModelInfo
}
@@ -319,8 +365,9 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
if (Object.keys(models).length === 0) {
throw new Error("No Cline models returned from API")
}
// Save models and cache them in memory
await fs.writeFile(clineModelsFilePath, JSON.stringify(models))
await fs.writeFile(clineModelsFilePath, JSON.stringify(preferClineCanonicalModelIds(models)))
Logger.log("Cline models fetched and saved")
} catch (error) {
Logger.error("Error fetching Cline models:", error)
@@ -340,6 +387,7 @@ async function fetchAndCacheClineModels(): Promise<Record<string, ModelInfo>> {
// Avoid poisoning in-memory cache with an empty model map after transient failures.
if (Object.keys(models).length > 0) {
models = preferClineCanonicalModelIds(models)
StateManager.get().setModelsCache("cline", models)
}
@@ -23,6 +23,31 @@ export interface ClineRecommendedModelsData {
}
const RECOMMENDED_MODELS_CACHE_TTL_MS = 60 * 60 * 1000;
const CLINE_PASS_MODEL_ID_ALIAS_RULES = [
{ canonicalPrefix: "cline-pass/zai/", aliasPrefix: "cline-pass/z-ai/" },
] as const;
function normalizeClineProviderRecommendedModelId(modelId: string): string {
const zaiPrefix = "zai/";
return modelId.startsWith(zaiPrefix) ? `z-ai/${modelId.slice(zaiPrefix.length)}` : modelId;
}
function normalizeClineProviderRecommendedModels(
models: ClineRecommendedModelData[],
): ClineRecommendedModelData[] {
return models.map((model) => {
const id = normalizeClineProviderRecommendedModelId(model.id);
if (id === model.id) {
return model;
}
return {
...model,
id,
name: model.name === model.id ? id : model.name,
};
});
}
let pendingRefresh: Promise<ClineRecommendedModelsData> | null = null;
let inMemoryCache: {
@@ -30,6 +55,26 @@ let inMemoryCache: {
timestamp: number;
} | null = null;
function preferCanonicalRecommendedModels(
models: ClineRecommendedModelData[],
): ClineRecommendedModelData[] {
const modelIds = new Set(models.map((model) => model.id));
return models.filter((model) => {
for (const rule of CLINE_PASS_MODEL_ID_ALIAS_RULES) {
if (!model.id.startsWith(rule.aliasPrefix)) {
continue;
}
const canonicalModelId = `${rule.canonicalPrefix}${model.id.slice(rule.aliasPrefix.length)}`;
if (modelIds.has(canonicalModelId)) {
return false;
}
}
return true;
});
}
function normalizeRecommendedModel(
raw: unknown,
): ClineRecommendedModelData | null {
@@ -89,7 +134,11 @@ function normalizeRecommendedModelsResponse(
.map((model) => normalizeRecommendedModel(model))
.filter((model): model is ClineRecommendedModelData => model !== null);
return { recommended, free, clinePass };
return {
recommended: normalizeClineProviderRecommendedModels(recommended),
free: normalizeClineProviderRecommendedModels(free),
clinePass: preferCanonicalRecommendedModels(clinePass),
};
}
export async function refreshClineRecommendedModels(): Promise<ClineRecommendedModelsData> {
@@ -161,14 +210,9 @@ async function fetchAndCacheClineRecommendedModels(): Promise<ClineRecommendedMo
"utf8",
);
const parsed = JSON.parse(fileContents);
if (parsed) {
result = {
recommended: Array.isArray(parsed.recommended)
? parsed.recommended
: [],
free: Array.isArray(parsed.free) ? parsed.free : [],
clinePass: Array.isArray(parsed.clinePass) ? parsed.clinePass : [],
};
const normalized = normalizeRecommendedModelsResponse(parsed);
if (normalized) {
result = normalized;
Logger.log("Loaded Cline recommended models from cache");
}
}
@@ -18,6 +18,7 @@ import {
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeSonnet461mModelId,
openRouterClaudeSonnet51mModelId,
} from "@/shared/api"
import { getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
@@ -150,6 +151,16 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
}
switch (rawModel.id) {
case "anthropic/claude-sonnet-5":
case "anthropic/claude-5-sonnet":
// NOTE: we artificially restrict the context window to 200k to keep costs low for users, and have a :1m model variant created below for users that want to use the full 1m.
modelInfo.contextWindow = 200_000
modelInfo.supportsPromptCache = true
modelInfo.inputPrice = 2.0
modelInfo.outputPrice = 10.0
modelInfo.cacheWritesPrice = 2.5
modelInfo.cacheReadsPrice = 0.2
break
case "anthropic/claude-sonnet-4.6":
case "anthropic/claude-4.6-sonnet":
case "anthropic/claude-sonnet-4.5":
@@ -294,11 +305,17 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
rawModel.id === "anthropic/claude-sonnet-4.5" ||
rawModel.id === "anthropic/claude-4.5-sonnet" ||
rawModel.id === "anthropic/claude-sonnet-4.6" ||
rawModel.id === "anthropic/claude-4.6-sonnet"
rawModel.id === "anthropic/claude-4.6-sonnet" ||
rawModel.id === "anthropic/claude-sonnet-5" ||
rawModel.id === "anthropic/claude-5-sonnet"
) {
const claudeSonnet1mModelInfo = cloneDeep(modelInfo)
claudeSonnet1mModelInfo.contextWindow = 1_000_000 // limiting providers to those that support 1m context window
claudeSonnet1mModelInfo.tiers = CLAUDE_SONNET_1M_TIERS
// sonnet 5
if (rawModel.id === "anthropic/claude-sonnet-5" || rawModel.id === "anthropic/claude-5-sonnet") {
models[openRouterClaudeSonnet51mModelId] = claudeSonnet1mModelInfo
}
// sonnet 4
if (rawModel.id === "anthropic/claude-sonnet-4") {
models[openRouterClaudeSonnet41mModelId] = claudeSonnet1mModelInfo
@@ -60,7 +60,7 @@ describe("fetchRemoteConfig", () => {
sandbox.restore();
});
it("clears remote config and skips discovery when Cline Pass is selected", async () => {
it("clears remote config and skips discovery when ClinePass is selected", async () => {
const controller = createController({
stateManager: {
getApiConfiguration: sandbox.stub().returns({
+7 -7
View File
@@ -87,7 +87,6 @@ import {
} from "@shared/Languages";
import { USER_CONTENT_TAGS } from "@shared/messages/constants";
import { convertClineMessageToProto } from "@shared/proto-conversions/cline-message";
import { FeatureFlag } from "@shared/services/feature-flags/feature-flags";
import { type ClineDefaultTool, READ_ONLY_TOOLS } from "@shared/tools";
import type { ClineAskResponse } from "@shared/WebviewMessage";
import {
@@ -2039,11 +2038,7 @@ export class Task {
? apiConfig.planModeApiProvider
: apiConfig.actModeApiProvider
) as string;
const providerId =
configuredProviderId === "cline-pass" &&
!featureFlagsService.getBooleanFlagEnabled(FeatureFlag.CLINE_PASS)
? "cline"
: configuredProviderId;
const providerId = configuredProviderId;
const customPrompt = this.stateManager.getGlobalSettingsKey("customPrompt");
return { model, providerId, customPrompt, mode };
}
@@ -2469,6 +2464,9 @@ export class Task {
const isEntitlementError = clineError.isErrorType(
ClineErrorType.Entitlement,
);
const isOrgClinePassRestrictionError = clineError.isErrorType(
ClineErrorType.OrgClinePassRestriction,
);
// Check if this is a Cline provider insufficient credits error - don't auto-retry these
const isClineProviderInsufficientCredits = (() => {
@@ -2495,6 +2493,7 @@ export class Task {
!isSpendLimitError &&
!quotaExceeded &&
!isEntitlementError &&
!isOrgClinePassRestrictionError &&
this.taskState.autoRetryAttempts < 3;
if (shouldRetry) {
// Auto-retry enabled with max 3 attempts: automatically approve the retry
@@ -2557,7 +2556,8 @@ export class Task {
!isAuthError &&
!isSpendLimitError &&
!quotaExceeded &&
!isEntitlementError;
!isEntitlementError &&
!isOrgClinePassRestrictionError;
if (showRetry) {
await this.say(
"error_retry",
File diff suppressed because it is too large Load Diff
+105 -53
View File
@@ -1,5 +1,5 @@
import { serializeError } from "serialize-error"
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "../../shared/ClineAccount"
import { serializeError } from "serialize-error";
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "../../shared/ClineAccount";
export enum ClineErrorType {
Auth = "auth",
@@ -9,46 +9,57 @@ export enum ClineErrorType {
SpendLimit = "spendLimit",
QuotaExceeded = "quotaExceeded",
Entitlement = "entitlement",
OrgClinePassRestriction = "orgClinePassRestriction",
}
interface ErrorDetails {
/**
* The HTTP status code of the error, if applicable.
*/
status?: number
status?: number;
/**
* The request ID associated with the error, if available.
* This can be useful for debugging and support.
*/
request_id?: string
request_id?: string;
/**
* Specific error code provided by the API or service.
*/
code?: string
code?: string;
/**
* The model ID associated with the error, if applicable.
* This is useful for identifying which model the error relates to.
*/
modelId?: string
modelId?: string;
/**
* The provider ID associated with the error, if applicable.
* This is useful for identifying which provider the error relates to.
*/
providerId?: string
providerId?: string;
/**
* The error message associated with the error, if applicable.
*/
message?: string
message?: string;
// Additional details that might be present in the error
// This can include things like current balance, error messages, etc.
details?: any
details?: any;
}
const RATE_LIMIT_PATTERNS = [/status code 429/i, /rate limit/i, /too many requests/i, /quota exceeded/i, /resource exhausted/i]
const RATE_LIMIT_PATTERNS = [
/status code 429/i,
/rate limit/i,
/too many requests/i,
/quota exceeded/i,
/resource exhausted/i,
];
const ORG_CLINE_PASS_RESTRICTION_MESSAGE =
"organization accounts cannot use individual model inference subscriptions";
const ORG_CLINE_PASS_RESTRICTION_USER_MESSAGE =
"organization accounts cannot use clinepass subscriptions";
export class ClineError extends Error {
readonly title = "ClineError"
readonly _error: ErrorDetails
readonly title = "ClineError";
readonly _error: ErrorDetails;
// Error details per providers:
// Cline: error?.error
@@ -59,15 +70,19 @@ export class ClineError extends Error {
public readonly modelId?: string,
public readonly providerId?: string,
) {
const error = serializeError(raw)
const error = serializeError(raw);
const message = error.message || error?.response?.message || String(error) || error?.cause?.means
super(message)
const message =
error.message ||
error?.response?.message ||
String(error) ||
error?.cause?.means;
super(message);
// Extract status from multiple possible locations
const status = error.status || error.statusCode || error.response?.status
this.modelId = modelId || error.modelId
this.providerId = providerId || error.providerId
const status = error.status || error.statusCode || error.response?.status;
this.modelId = modelId || error.modelId;
this.providerId = providerId || error.providerId;
// Construct the error details object to includes relevant information
// And ensure it has a consistent structure
@@ -85,7 +100,7 @@ export class ClineError extends Error {
providerId: this.providerId,
details: error.details || error.error, // Additional details provided by the server
stack: undefined, // Avoid serializing stack trace to keep the error object clean
}
};
}
/**
@@ -101,7 +116,7 @@ export class ClineError extends Error {
modelId: this.modelId,
providerId: this.providerId,
details: this._error.details,
})
});
}
/**
@@ -109,29 +124,33 @@ export class ClineError extends Error {
*/
static parse(errorStr?: string, modelId?: string): ClineError | undefined {
if (!errorStr || typeof errorStr !== "string") {
return undefined
return undefined;
}
return ClineError.transform(errorStr, modelId)
return ClineError.transform(errorStr, modelId);
}
/**
* Transforms any object into a ClineError instance.
* Always returns a ClineError, even if the input is not a valid error object.
*/
static transform(error: any, modelId?: string, providerId?: string): ClineError {
static transform(
error: any,
modelId?: string,
providerId?: string,
): ClineError {
try {
// If already a ClineError, return it directly to prevent infinite recursion
if (error instanceof ClineError) {
return error
return error;
}
return new ClineError(JSON.parse(error), modelId, providerId)
return new ClineError(JSON.parse(error), modelId, providerId);
} catch {
return new ClineError(error, modelId, providerId)
return new ClineError(error, modelId, providerId);
}
}
public isErrorType(type: ClineErrorType): boolean {
return ClineError.getErrorType(this) === type
return ClineError.getErrorType(this) === type;
}
/**
@@ -139,53 +158,86 @@ export class ClineError extends Error {
* This is useful for determining how to handle the error in the UI or logic.
*/
static getErrorType(err: ClineError): ClineErrorType | undefined {
const { code, status, details } = err._error
const message = (err._error?.message || err.message || JSON.stringify(err._error))?.toLowerCase()
const { code, status, details } = err._error;
const message = (
err._error?.message ||
err.message ||
JSON.stringify(err._error)
)?.toLowerCase();
// Check balance error first (most specific)
if (code === "insufficient_credits" && typeof details?.current_balance === "number") {
return ClineErrorType.Balance
if (
code === "insufficient_credits" &&
typeof details?.current_balance === "number"
) {
return ClineErrorType.Balance;
}
// Check spend limit exceeded (org-enforced budget cap, 429 SPEND_LIMIT_EXCEEDED)
// Must be checked before the generic rate-limit check since both use 429
if (code === "SPEND_LIMIT_EXCEEDED" || details?.code === "SPEND_LIMIT_EXCEEDED") {
return ClineErrorType.SpendLimit
if (
code === "SPEND_LIMIT_EXCEEDED" ||
details?.code === "SPEND_LIMIT_EXCEEDED"
) {
return ClineErrorType.SpendLimit;
}
// Scoped to the individual "not subscribed" case; other ENTITLEMENT_ERROR variants (e.g. org
// accounts) fall through. Checked before the generic auth check since these are returned as 403.
const isEntitlementCode = code === "ENTITLEMENT_ERROR" || details?.code === "ENTITLEMENT_ERROR"
const entitlementText = `${message ?? ""} ${details?.message ?? ""}`.toLowerCase()
if (isEntitlementCode && entitlementText.includes("not subscribed to required model plan")) {
return ClineErrorType.Entitlement
// ClinePass entitlement errors are user-actionable and should not fall through to generic 403 auth.
// The organization-account variant gets separate copy because subscribing is not the right action.
const isEntitlementCode =
code === "ENTITLEMENT_ERROR" || details?.code === "ENTITLEMENT_ERROR";
const entitlementText =
`${message ?? ""} ${details?.message ?? ""}`.toLowerCase();
if (
isEntitlementCode &&
(entitlementText.includes(ORG_CLINE_PASS_RESTRICTION_MESSAGE) ||
entitlementText.includes(ORG_CLINE_PASS_RESTRICTION_USER_MESSAGE))
) {
return ClineErrorType.OrgClinePassRestriction;
}
if (
isEntitlementCode &&
entitlementText.includes("not subscribed to required model plan")
) {
return ClineErrorType.Entitlement;
}
// Check auth errors
const isAuthStatus = status !== undefined && status > 400 && status < 429
if (code === "ERR_BAD_REQUEST" || err instanceof AuthInvalidTokenError || isAuthStatus) {
return ClineErrorType.Auth
const isAuthStatus = status !== undefined && status > 400 && status < 429;
if (
code === "ERR_BAD_REQUEST" ||
err instanceof AuthInvalidTokenError ||
isAuthStatus
) {
return ClineErrorType.Auth;
}
if (code === "INFERENCE_CAP_ERROR") {
return ClineErrorType.QuotaExceeded
return ClineErrorType.QuotaExceeded;
}
if (message) {
// Check for specific error codes/messages if applicable
const authErrorRegex = [/(?:in)?valid[-_ ]?(?:api )?(?:token|key)/i, /authentication[-_ ]?failed/i, /unauthorized/i]
if (message?.includes(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE) || authErrorRegex.some((regex) => regex.test(message))) {
return ClineErrorType.Auth
const authErrorRegex = [
/(?:in)?valid[-_ ]?(?:api )?(?:token|key)/i,
/authentication[-_ ]?failed/i,
/unauthorized/i,
];
if (
message?.includes(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE) ||
authErrorRegex.some((regex) => regex.test(message))
) {
return ClineErrorType.Auth;
}
// Check rate limit patterns
const lowerMessage = message.toLowerCase()
const lowerMessage = message.toLowerCase();
if (RATE_LIMIT_PATTERNS.some((pattern) => pattern.test(lowerMessage))) {
return ClineErrorType.RateLimit
return ClineErrorType.RateLimit;
}
}
return undefined
return undefined;
}
}
@@ -194,14 +246,14 @@ export class AuthNetworkError extends Error {
message: string,
override readonly cause?: Error,
) {
super(message)
this.name = ClineErrorType.Network
super(message);
this.name = ClineErrorType.Network;
}
}
export class AuthInvalidTokenError extends Error {
constructor(message: string) {
super(message)
this.name = ClineErrorType.Auth
super(message);
this.name = ClineErrorType.Auth;
}
}
@@ -1,42 +1,52 @@
import { describe, it } from "mocha"
import "should"
import { ClineError, ClineErrorType } from "../ClineError"
import { describe, it } from "mocha";
import "should";
import { ClineError, ClineErrorType } from "../ClineError";
describe("ClineError", () => {
describe("getErrorType", () => {
it("should return QuotaExceeded when code is INFERENCE_CAP_ERROR", () => {
const err = new ClineError({ message: "Inference cap reached", code: "INFERENCE_CAP_ERROR" })
ClineError.getErrorType(err)!.should.equal(ClineErrorType.QuotaExceeded)
})
const err = new ClineError({
message: "Inference cap reached",
code: "INFERENCE_CAP_ERROR",
});
ClineError.getErrorType(err)!.should.equal(ClineErrorType.QuotaExceeded);
});
it("should return Entitlement when code is ENTITLEMENT_ERROR", () => {
const err = new ClineError({
message: "403 Error 403: the user is not subscribed to required model plan",
message:
"403 Error 403: the user is not subscribed to required model plan",
code: "ENTITLEMENT_ERROR",
status: 403,
})
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement)
})
});
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement);
});
it("should return Entitlement when details.code is ENTITLEMENT_ERROR", () => {
const err = new ClineError({
message: "403 Error 403: the user is not subscribed to required model plan",
message:
"403 Error 403: the user is not subscribed to required model plan",
status: 403,
details: { code: "ENTITLEMENT_ERROR", message: "Error 403: the user is not subscribed to required model plan" },
})
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement)
})
details: {
code: "ENTITLEMENT_ERROR",
message:
"Error 403: the user is not subscribed to required model plan",
},
});
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement);
});
it("should prefer Entitlement over Auth for 403 ENTITLEMENT_ERROR", () => {
// status 403 would otherwise be classified as Auth; the entitlement code must win.
const err = new ClineError({
message: "403 Error 403: the user is not subscribed to required model plan",
message:
"403 Error 403: the user is not subscribed to required model plan",
code: "ENTITLEMENT_ERROR",
status: 403,
})
ClineError.getErrorType(err)!.should.not.equal(ClineErrorType.Auth)
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement)
})
});
ClineError.getErrorType(err)!.should.not.equal(ClineErrorType.Auth);
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement);
});
it("should return Entitlement for the real Cline 403 provider error shape (nested error object)", () => {
// ClineError maps `error.error` into `details`, so `details.code` drives classification.
@@ -45,25 +55,39 @@ describe("ClineError", () => {
status: 403,
error: {
code: "ENTITLEMENT_ERROR",
message: "Error 403: the user is not subscribed to required model plan",
message:
"Error 403: the user is not subscribed to required model plan",
},
},
"cline-pass/glm-5.1",
"cline-pass",
)
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement)
})
);
ClineError.getErrorType(err)!.should.equal(ClineErrorType.Entitlement);
});
it("should NOT classify the organization ENTITLEMENT_ERROR variant as Entitlement", () => {
// Org accounts can't use individual subs; this case is intentionally out of scope and
// falls through to generic handling rather than showing the ClinePass card.
it("should classify the organization ENTITLEMENT_ERROR variant separately from the ClinePass subscription card", () => {
// Org accounts can't use individual subs; this case should not show the personal ClinePass
// subscription card, but it should still get dedicated user-actionable copy.
const err = new ClineError({
message: "403 Error 403: organization accounts cannot use individual model inference subscriptions",
message:
"403 Error 403: organization accounts cannot use individual model inference subscriptions",
code: "ENTITLEMENT_ERROR",
status: 403,
})
const result = ClineError.getErrorType(err)
;(result !== ClineErrorType.Entitlement).should.be.true()
})
})
})
});
const result = ClineError.getErrorType(err);
result!.should.equal(ClineErrorType.OrgClinePassRestriction);
(result !== ClineErrorType.Entitlement).should.be.true();
});
it("should not classify organization restriction text without ENTITLEMENT_ERROR as OrgClinePassRestriction", () => {
const err = new ClineError({
message:
"Network error: organization accounts cannot use individual model inference subscriptions",
code: "ERR_NETWORK",
});
const result = ClineError.getErrorType(err);
(result !== ClineErrorType.OrgClinePassRestriction).should.be.true();
});
});
});
@@ -0,0 +1,65 @@
import { expect } from "chai"
import {
buildModelInfoNameMap,
internationalZAiModels,
mainlandZAiModels,
type ModelInfo,
resolveClinePassModelInfo,
} from "../api"
describe("ClinePass model info", () => {
const createModelInfo = (name: string, contextWindow: number): ModelInfo => ({
name,
contextWindow,
maxTokens: 8_192,
supportsPromptCache: false,
supportsReasoning: false,
thinkingConfig: { maxBudget: 16_384 },
})
it("prefers dynamic model metadata for ClinePass GLM aliases", () => {
const modelInfo = resolveClinePassModelInfo(
"cline-pass/z-ai/glm-5.2",
buildModelInfoNameMap({
"z-ai/glm-5.2": createModelInfo("OpenRouter GLM 5.2", 1_000_000),
}),
)
expect(modelInfo.name).to.equal("OpenRouter GLM 5.2")
expect(modelInfo.contextWindow).to.equal(1_000_000)
expect(modelInfo.thinkingConfig).to.deep.equal({ maxBudget: 16_384 })
})
it("falls back to static ClinePass metadata when dynamic metadata is unavailable", () => {
const modelInfo = resolveClinePassModelInfo("cline-pass/glm-5.2")
expect(modelInfo.contextWindow).to.equal(202_752)
expect(modelInfo.supportsReasoning).to.equal(true)
expect(modelInfo.thinkingConfig).to.equal(undefined)
})
it("preserves dynamic ClinePass model info when no static alias exists", () => {
const modelInfo = resolveClinePassModelInfo(
"cline-pass/new-model",
buildModelInfoNameMap({
"zai/new-model": createModelInfo("New model", 1_000_000),
}),
)
expect(modelInfo.name).to.equal("New model")
expect(modelInfo.supportsReasoning).to.equal(false)
expect(modelInfo.thinkingConfig).to.deep.equal({ maxBudget: 16_384 })
})
})
describe("Z AI model info", () => {
it("includes GLM 5.2 for both direct Z AI entrypoints", () => {
for (const models of [internationalZAiModels, mainlandZAiModels]) {
expect(models["glm-5.2"].contextWindow).to.equal(1_000_000)
expect(models["glm-5.2"].maxTokens).to.equal(128_000)
expect(models["glm-5.2"].inputPrice).to.equal(1.4)
expect(models["glm-5.2"].outputPrice).to.equal(4.4)
expect(models["glm-5.2"].cacheReadsPrice).to.equal(0.26)
}
})
})
+145 -21
View File
@@ -179,10 +179,33 @@ export const hicapModelInfoSaneDefaults: HicapCompatibleModelInfo = {
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
export type AnthropicModelId = keyof typeof anthropicModels
export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-5-20250929"
export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-5"
export const ANTHROPIC_MIN_THINKING_BUDGET = 1_024
export const ANTHROPIC_MAX_THINKING_BUDGET = 6_000
export const anthropicModels = {
"claude-sonnet-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
},
"claude-sonnet-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
tiers: CLAUDE_SONNET_1M_TIERS,
},
"claude-sonnet-4-6": {
maxTokens: 64_000,
contextWindow: 200_000,
@@ -471,15 +494,15 @@ export const anthropicModels = {
// Claude Code
export type ClaudeCodeModelId = keyof typeof claudeCodeModels
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-4-5-20250929"
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-5"
export const claudeCodeModels = {
sonnet: {
...anthropicModels["claude-sonnet-4-5-20250929"],
...anthropicModels["claude-sonnet-5"],
supportsImages: false,
supportsPromptCache: false,
},
"sonnet[1m]": {
...anthropicModels["claude-sonnet-4-5-20250929:1m"],
...anthropicModels["claude-sonnet-5:1m"],
supportsImages: false,
supportsPromptCache: false,
},
@@ -499,6 +522,16 @@ export const claudeCodeModels = {
supportsImages: false,
supportsPromptCache: false,
},
"claude-sonnet-5": {
...anthropicModels["claude-sonnet-5"],
supportsImages: false,
supportsPromptCache: false,
},
"claude-sonnet-5[1m]": {
...anthropicModels["claude-sonnet-5:1m"],
supportsImages: false,
supportsPromptCache: false,
},
"claude-sonnet-4-6": {
...anthropicModels["claude-sonnet-4-6"],
supportsImages: false,
@@ -597,8 +630,33 @@ export const claudeCodeModels = {
// AWS Bedrock
// https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html
export type BedrockModelId = keyof typeof bedrockModels
export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"
export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-sonnet-5"
export const bedrockModels = {
"anthropic.claude-sonnet-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
supportsGlobalEndpoint: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
},
"anthropic.claude-sonnet-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
supportsReasoning: true,
supportsGlobalEndpoint: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
tiers: CLAUDE_SONNET_1M_TIERS,
},
"anthropic.claude-sonnet-4-6": {
maxTokens: 64_000,
contextWindow: 200_000,
@@ -991,25 +1049,26 @@ export const bedrockModels = {
// OpenRouter
// https://openrouter.ai/models?order=newest&supported_parameters=tools
export const openRouterDefaultModelId = "anthropic/claude-sonnet-4.5" // will always exist in openRouterModels
export const openRouterDefaultModelId = "anthropic/claude-sonnet-5" // will always exist in openRouterModels
export const openRouterClaudeSonnet41mModelId = `anthropic/claude-sonnet-4${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeSonnet451mModelId = `anthropic/claude-sonnet-4.5${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeSonnet461mModelId = `anthropic/claude-sonnet-4.6${CLAUDE_SONNET_1M_SUFFIX}`
export const openRouterClaudeSonnet51mModelId = `anthropic/claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`
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,
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
description:
"Claude Sonnet 4.5 delivers superior intelligence across coding, agentic search, and AI agent capabilities. It's a powerful choice for agentic coding, and can complete tasks across the entire software development lifecycle, from initial planning to bug fixes, maintenance to large refactors. It offers strong performance in both planning and solving for complex coding tasks, making it an ideal choice to power end-to-end software development processes.\n\nRead more in the [blog post here](https://www.anthropic.com/claude/sonnet)",
"Claude Sonnet 5 is Anthropic's latest Sonnet model for coding, agents, and professional work. It supports adaptive thinking, prompt caching, image inputs, and long-context workflows.",
}
// Cline custom model - Devstral
@@ -1025,7 +1084,7 @@ export const clineDevstralModelInfo: ModelInfo = {
}
export type ClinePassModelId = keyof typeof clinePassModels
export const clinePassDefaultModelId = "cline-pass/glm-5.1"
export const clinePassDefaultModelId = "cline-pass/glm-5.2"
export const clinePassModelInfoSaneDefaults: ModelInfo = {
maxTokens: 8_192,
contextWindow: 128_000,
@@ -1039,6 +1098,19 @@ export const clinePassModelInfoSaneDefaults: ModelInfo = {
description: "",
}
export const clinePassModels = {
"cline-pass/glm-5.2": {
name: "cline-pass/glm-5.2",
maxTokens: 131_072,
contextWindow: 202_752,
supportsImages: false,
supportsPromptCache: true,
supportsReasoning: true,
inputPrice: 0.98,
outputPrice: 3.08,
cacheReadsPrice: 0.182,
cacheWritesPrice: 0,
description: "",
},
"cline-pass/glm-5.1": {
name: "cline-pass/glm-5.1",
maxTokens: 131_072,
@@ -1069,9 +1141,12 @@ export function buildModelInfoNameMap(models: Record<string, ModelInfo>): Record
}
export function resolveClinePassModelInfo(modelId: string, modelInfoByName?: Record<string, ModelInfo>): ModelInfo {
const modelSlug = getModelSlug(modelId)
const clinePassSlugModelId = `cline-pass/${modelSlug}`
return (
modelInfoByName?.[modelSlug] ??
clinePassModels[modelId as keyof typeof clinePassModels] ??
modelInfoByName?.[getModelSlug(modelId)] ??
clinePassModels[clinePassSlugModelId as keyof typeof clinePassModels] ??
clinePassModelInfoSaneDefaults
)
}
@@ -1230,6 +1305,29 @@ export const vertexModels = {
supportsThinkingLevel: true,
},
},
"claude-sonnet-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
supportsReasoning: true,
},
"claude-sonnet-5:1m": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
supportsReasoning: true,
tiers: CLAUDE_SONNET_1M_TIERS,
},
"claude-sonnet-4-6": {
maxTokens: 64_000,
contextWindow: 200_000,
@@ -4360,18 +4458,18 @@ export const groqModels = {
// Requesty
// https://requesty.ai/models
export const requestyDefaultModelId = "anthropic/claude-3-7-sonnet-latest"
export const requestyDefaultModelId = "anthropic/claude-sonnet-5"
export const requestyDefaultModelInfo: ModelInfo = {
maxTokens: 8192,
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
description: "Anthropic's most intelligent model. Highest level of intelligence and capability.",
inputPrice: 2.0,
outputPrice: 10.0,
cacheWritesPrice: 2.5,
cacheReadsPrice: 0.2,
description: "Anthropic's latest Sonnet model for coding, agents, and professional work.",
}
// SAP AI Core
@@ -4380,6 +4478,13 @@ export const sapAiCoreDefaultModelId: SapAiCoreModelId = "anthropic--claude-3.5-
// Pricing is calculated using Capacity Units, not directly in USD
const sapAiCoreModelDescription = "Pricing is calculated using SAP's Capacity Units rather than direct USD pricing."
export const sapAiCoreModels = {
"anthropic--claude-sonnet-5": {
maxTokens: 128_000,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
description: sapAiCoreModelDescription,
},
"anthropic--claude-4.5-haiku": {
maxTokens: 64000,
contextWindow: 200_000,
@@ -4983,12 +5088,22 @@ export type BasetenModelId = keyof typeof basetenModels
export const basetenDefaultModelId = "zai-org/GLM-4.6" satisfies BasetenModelId
// Z AI
// https://docs.z.ai/guides/llm/glm-5.2
// https://docs.z.ai/guides/llm/glm-5.1
// https://docs.z.ai/guides/llm/glm-5
// https://docs.z.ai/guides/overview/pricing
export type internationalZAiModelId = keyof typeof internationalZAiModels
export const internationalZAiDefaultModelId: internationalZAiModelId = "glm-5.1"
export const internationalZAiModels = {
"glm-5.2": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
cacheReadsPrice: 0.26,
inputPrice: 1.4,
outputPrice: 4.4,
},
"glm-5.1": {
maxTokens: 128_000,
contextWindow: 200_000,
@@ -5054,6 +5169,15 @@ export const internationalZAiModels = {
export type mainlandZAiModelId = keyof typeof mainlandZAiModels
export const mainlandZAiDefaultModelId: mainlandZAiModelId = "glm-5.1"
export const mainlandZAiModels = {
"glm-5.2": {
maxTokens: 128_000,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
cacheReadsPrice: 0.26,
inputPrice: 1.4,
outputPrice: 4.4,
},
"glm-5.1": {
maxTokens: 128_000,
contextWindow: 200_000,
+4 -4
View File
@@ -55,8 +55,8 @@ export const CLINE_ONBOARDING_MODELS: OnboardingModel[] = [
},
{
group: "frontier",
id: "anthropic/claude-sonnet-4.5",
name: "Anthropic: Claude Sonnet 4.5",
id: "anthropic/claude-sonnet-5",
name: "Anthropic: Claude Sonnet 5",
badge: "Best",
score: 97,
latency: 3,
@@ -64,8 +64,8 @@ export const CLINE_ONBOARDING_MODELS: OnboardingModel[] = [
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
inputPrice: 2.0,
outputPrice: 10.0,
tiers: [],
},
},
@@ -22,8 +22,8 @@ export const CLINE_RECOMMENDED_MODELS_FALLBACK: ClineRecommendedModelsData = {
tags: ["NEW"],
},
{
id: "anthropic/claude-sonnet-4.6",
name: "Anthropic Claude Sonnet 4.6",
id: "anthropic/claude-sonnet-5",
name: "Anthropic Claude Sonnet 5",
description: "Latest Sonnet release with strong coding and agent performance",
tags: ["NEW"],
},
@@ -6,7 +6,7 @@
},
{
"value": "cline-pass",
"label": "Cline Pass"
"label": "ClinePass"
},
{
"value": "openai-codex",
@@ -15,7 +15,7 @@ export enum FeatureFlag {
// Rollout flag for Cline provider model sourcing:
// off => OpenRouter model list, on => Cline endpoint model list.
EXTENSION_CLINE_MODELS_ENDPOINT = "extension_cline_models_endpoint",
// Enables Cline Pass provider/model list exposure.
// Enables ClinePass provider/model list exposure.
CLINE_PASS = "ext-cline-pass",
// Use the websocket mode for OpenAI native Responses API format
OPENAI_RESPONSES_WEBSOCKET_MODE = "openai-responses-websocket-mode",
@@ -23,7 +23,7 @@ describe("Provider key mapping", () => {
expect(getProviderModelIdKey("cline", "plan")).to.equal("planModeClineModelId")
})
it("uses separate model keys for Cline Pass", () => {
it("uses separate model keys for ClinePass", () => {
expect(getProviderModelIdKey("cline-pass", "act")).to.equal("actModeClinePassModelId")
expect(getProviderModelIdKey("cline-pass", "plan")).to.equal("planModeClinePassModelId")
})
@@ -1,11 +1,13 @@
import { normalizeOpenaiReasoningEffort, type OpenaiReasoningEffort } from "../storage/types"
export interface ClaudeOpusAdaptiveThinkingSettings {
export interface ClaudeAdaptiveThinkingSettings {
enabled: boolean
effort?: OpenaiReasoningEffort
}
export function isClaudeOpusAdaptiveThinkingModel(modelId?: string): boolean {
export type ClaudeOpusAdaptiveThinkingSettings = ClaudeAdaptiveThinkingSettings
export function isClaudeAdaptiveThinkingModel(modelId?: string): boolean {
if (!modelId) {
return false
}
@@ -14,14 +16,18 @@ export function isClaudeOpusAdaptiveThinkingModel(modelId?: string): boolean {
const adaptiveVersions = ["4-6", "4.6", "4-7", "4.7", "4-8", "4.8"]
return (
id.includes("claude-fable-5") ||
id.includes("claude-sonnet-5") ||
id.includes("claude-5-sonnet") ||
adaptiveVersions.some((version) => id.includes(`claude-opus-${version}`) || id.includes(`claude-${version}-opus`))
)
}
export function resolveClaudeOpusAdaptiveThinking(
export const isClaudeOpusAdaptiveThinkingModel = isClaudeAdaptiveThinkingModel
export function resolveClaudeAdaptiveThinking(
reasoningEffort?: string,
legacyThinkingBudgetTokens?: number,
): ClaudeOpusAdaptiveThinkingSettings {
): ClaudeAdaptiveThinkingSettings {
if (reasoningEffort) {
const effort = normalizeOpenaiReasoningEffort(reasoningEffort)
return effort === "none" ? { enabled: false } : { enabled: true, effort }
@@ -30,6 +36,8 @@ export function resolveClaudeOpusAdaptiveThinking(
return legacyThinkingBudgetTokens && legacyThinkingBudgetTokens > 0 ? { enabled: true, effort: "high" } : { enabled: false }
}
export const resolveClaudeOpusAdaptiveThinking = resolveClaudeAdaptiveThinking
export function supportsReasoningEffortForModel(modelId?: string): boolean {
if (!modelId) {
return false
@@ -37,11 +45,20 @@ export function supportsReasoningEffortForModel(modelId?: string): boolean {
const id = modelId.toLowerCase()
return (
id.includes("deepseek") ||
id.includes("gemini") ||
id.includes("glm") ||
id.includes("gpt") ||
id.includes("kimi") ||
id.includes("mimo") ||
id.includes("minimax") ||
id.includes("moonshot") ||
id.startsWith("openai/o") ||
id.includes("/o") ||
id.startsWith("o") ||
id.includes("qwen") ||
id.includes("z-ai") ||
id.includes("zai") ||
id.includes("grok")
)
}
@@ -94,8 +94,8 @@ export const E2E_MOCK_CLINE_RECOMMENDED_MODELS = {
],
recommended: [
{
id: "anthropic/claude-sonnet-4.6",
name: "anthropic/claude-sonnet-4.6",
id: "anthropic/claude-sonnet-5",
name: "anthropic/claude-sonnet-5",
description: "Recommended model for e2e onboarding",
tags: ["BEST"],
},
@@ -123,12 +123,12 @@ export const E2E_MOCK_CLINE_MODELS = [
supported_parameters: [],
},
{
id: "anthropic/claude-sonnet-4.6",
name: "anthropic/claude-sonnet-4.6",
id: "anthropic/claude-sonnet-5",
name: "anthropic/claude-sonnet-5",
description: "Recommended model for e2e onboarding",
context_length: 200_000,
top_provider: {
max_completion_tokens: 64_000,
max_completion_tokens: 128_000,
context_length: 200_000,
is_moderated: false,
},
@@ -136,10 +136,10 @@ export const E2E_MOCK_CLINE_MODELS = [
modality: "text->text",
},
pricing: {
prompt: "0.000003",
completion: "0.000015",
input_cache_read: "0.0000003",
input_cache_write: "0.00000375",
prompt: "0.000002",
completion: "0.00001",
input_cache_read: "0.0000002",
input_cache_write: "0.0000025",
},
supported_parameters: ["include_reasoning"],
},
@@ -1,6 +1,7 @@
import { describe, it } from "mocha"
import "should"
import type { ApiHandlerModel, ApiProviderInfo } from "@core/api"
import { isClaudeAdaptiveThinkingModel } from "@shared/utils/reasoning-support"
import {
GEMINI_FLASH_MAX_OUTPUT_TOKENS,
isClaude4PlusModelFamily,
@@ -13,6 +14,7 @@ import {
isPoolsideModelFamily,
modelDoesntSupportWebp,
shouldSkipReasoningForModel,
supportsReasoningEffortForModel,
} from "../model-utils"
// Minimal helper — modelDoesntSupportWebp only reads apiHandlerModel.id
@@ -37,6 +39,7 @@ describe("shouldSkipReasoningForModel", () => {
shouldSkipReasoningForModel("claude-3-sonnet").should.equal(false)
shouldSkipReasoningForModel("gpt-4").should.equal(false)
shouldSkipReasoningForModel("gemini-pro").should.equal(false)
shouldSkipReasoningForModel("zai/glm-5.2").should.equal(false)
})
it("should return false for undefined or empty model IDs", () => {
@@ -50,6 +53,31 @@ describe("shouldSkipReasoningForModel", () => {
})
})
describe("supportsReasoningEffortForModel", () => {
it("should return true for OpenRouter/Cline model families that use reasoning effort", () => {
for (const modelId of [
"zai/glm-5.2",
"z-ai/glm-5.2",
"cline-pass/glm-5.2",
"moonshotai/kimi-k2-thinking",
"kimi-k2-thinking",
"accounts/fireworks/models/kimi-k2p6",
"accounts/fireworks/models/minimax-m3",
"minimax/MiniMax-M2.7",
"provider/mimo-vl",
"qwen/qwen3.7-max",
"deepseek/deepseek-r1",
]) {
supportsReasoningEffortForModel(modelId).should.equal(true)
}
})
it("should return false for undefined and unrelated model IDs", () => {
supportsReasoningEffortForModel(undefined).should.equal(false)
supportsReasoningEffortForModel("anthropic/claude-sonnet-4.5").should.equal(false)
})
})
describe("isClaude4PlusModelFamily", () => {
it("should return true for Claude 4+ model IDs with version numbers", () => {
isClaude4PlusModelFamily("claude-sonnet-4-5-20250929").should.equal(true)
@@ -77,6 +105,18 @@ describe("isClaude4PlusModelFamily", () => {
})
})
describe("isClaudeAdaptiveThinkingModel", () => {
it("should return true for Claude Sonnet 5 IDs across provider naming variants", () => {
for (const modelId of [
"claude-sonnet-5",
"anthropic/claude-sonnet-5:1m",
"anthropic/claude-5-sonnet",
]) {
isClaudeAdaptiveThinkingModel(modelId).should.equal(true)
}
})
})
describe("isGPT5ModelFamily", () => {
it("should return true for GPT-5 model IDs with hyphen", () => {
isGPT5ModelFamily("gpt-5").should.equal(true)
+1 -1
View File
@@ -46,7 +46,7 @@ export function shouldSkipReasoningForModel(modelId?: string): boolean {
if (!modelId) {
return false
}
return modelId.includes("grok-4") || modelId.includes("devstral") || modelId.includes("glm")
return modelId.includes("grok-4") || modelId.includes("devstral")
}
export function isAnthropicModelId(modelId: string): modelId is AnthropicModelId {
@@ -1106,6 +1106,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
switch (selectedProvider) {
case "cline":
return `${selectedProvider}:${selectedModelId}`
case "cline-pass":
return `${selectedProvider}:${selectedModelId.replace(/^cline-pass\//, "")}`
case "openai":
return `openai-compat:${selectedModelId}`
case "vscode-lm":
@@ -3,7 +3,9 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import React, { useEffect, useMemo, useState } from "react"
import VSCodeButtonLink from "@/components/common/VSCodeButtonLink"
import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient, TaskServiceClient } from "@/services/grpc-client"
import { useApiConfigurationHandlers } from "../settings/utils/useApiConfigurationHandlers"
interface CreditLimitErrorProps {
currentBalance: number
@@ -26,7 +28,11 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
totalSpent,
}) => {
const { activeOrganization } = useClineAuth()
const { mode, navigateToSettings } = useExtensionState()
const { handleModeFieldChange } = useApiConfigurationHandlers()
const [fullBuyCreditsUrl, setFullBuyCreditsUrl] = useState<string>("")
const [isSwitchingToClinePass, setIsSwitchingToClinePass] = useState(false)
const [didSwitchToClinePass, setDidSwitchToClinePass] = useState(false)
const dashboardUrl = useMemo(() => {
return buyCreditsUrl ?? (activeOrganization?.organizationId ? DEFAULT_BUY_CREDITS_URL.ORG : DEFAULT_BUY_CREDITS_URL.USER)
@@ -48,6 +54,19 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
fetchCallbackUrl()
}, [dashboardUrl])
const handleSwitchToClinePass = async () => {
setIsSwitchingToClinePass(true)
try {
await handleModeFieldChange({ plan: "planModeApiProvider", act: "actModeApiProvider" }, "cline-pass", mode)
setDidSwitchToClinePass(true)
navigateToSettings("api-config")
} catch (error) {
console.error("Failed to switch to ClinePass:", error)
} finally {
setIsSwitchingToClinePass(false)
}
}
// We have to divide because the balance is stored in microcredits
return (
<div className="p-2 border-none rounded-md mb-2 bg-(--vscode-textBlockQuote-background)">
@@ -66,6 +85,24 @@ const CreditLimitError: React.FC<CreditLimitErrorProps> = ({
</div>
</div>
<div className="mb-2">
<div className="text-(--vscode-descriptionForeground) text-xs mb-2">
Trying to use ClinePass instead of credits?
</div>
<VSCodeButton
appearance="secondary"
className="w-full"
disabled={isSwitchingToClinePass || didSwitchToClinePass}
onClick={handleSwitchToClinePass}>
<span className="codicon codicon-arrow-swap mr-1.5" />
{isSwitchingToClinePass
? "Switching..."
: didSwitchToClinePass
? "Switched to ClinePass"
: "Switch to ClinePass"}
</VSCodeButton>
</div>
<VSCodeButtonLink className="w-full mb-2" href={fullBuyCreditsUrl}>
<span className="codicon codicon-credit-card mr-[6px] text-[14px]" />
Buy Credits
@@ -48,14 +48,14 @@ describe("EntitlementError", () => {
it("builds the subscribe link from the authenticated user's app base URL", () => {
mockAuth.clineUser = { appBaseUrl: "https://staging-app.cline.bot" }
const { unmount } = render(<EntitlementError />)
expect(getSubscribeHref()).toBe("https://staging-app.cline.bot/dashboard/subscription")
expect(getSubscribeHref()).toBe("https://staging-app.cline.bot/dashboard/subscription?personal=true")
unmount()
mockAuth.clineUser = {
appBaseUrl: "https://proxy.enterprise.com/cline/app",
}
render(<EntitlementError />)
expect(getSubscribeHref()).toBe("https://proxy.enterprise.com/cline/app/dashboard/subscription")
expect(getSubscribeHref()).toBe("https://proxy.enterprise.com/cline/app/dashboard/subscription?personal=true")
})
it("sends a yesButtonClicked askResponse when Retry Request is clicked", () => {
@@ -20,7 +20,9 @@ function buildSubscribeUrl(appBaseUrl?: string): string | undefined {
}
try {
const base = appBaseUrl.endsWith("/") ? appBaseUrl : `${appBaseUrl}/`
return new URL(CLINE_PASS_SUBSCRIBE_PATH, base).toString()
const url = new URL(CLINE_PASS_SUBSCRIBE_PATH, base)
url.searchParams.set("personal", "true")
return url.toString()
} catch {
// Malformed appBaseUrl: omit the link rather than crashing the error card.
return undefined
@@ -1,7 +1,9 @@
import type { ClineMessage } from "@shared/ExtensionMessage"
import { render, screen } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"
import ErrorRow from "./ErrorRow"
import type { ClineMessage } from "@shared/ExtensionMessage";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import ErrorRow from "./ErrorRow";
const mockSetUserOrganization = vi.hoisted(() => vi.fn());
// Mock the auth context
vi.mock("@/context/ClineAuthContext", () => ({
@@ -12,17 +14,27 @@ vi.mock("@/context/ClineAuthContext", () => ({
isLoginLoading: false,
}),
handleSignOut: vi.fn(),
}))
}));
// Mock CreditLimitError component
vi.mock("@/components/chat/CreditLimitError", () => ({
default: ({ message }: { message: string }) => <div data-testid="credit-limit-error">{message}</div>,
}))
default: ({ message }: { message: string }) => (
<div data-testid="credit-limit-error">{message}</div>
),
}));
// Mock EntitlementError component
vi.mock("@/components/chat/EntitlementError", () => ({
default: ({ message }: { message: string }) => <div data-testid="entitlement-error">{message}</div>,
}))
default: ({ message }: { message: string }) => (
<div data-testid="entitlement-error">{message}</div>
),
}));
vi.mock("@/services/grpc-client", () => ({
AccountServiceClient: {
setUserOrganization: mockSetUserOrganization,
},
}));
// Mock ClineError
vi.mock("../../../../src/services/error/ClineError", () => ({
@@ -34,8 +46,9 @@ vi.mock("../../../../src/services/error/ClineError", () => ({
RateLimit: "rateLimit",
Auth: "auth",
Entitlement: "entitlement",
OrgClinePassRestriction: "orgClinePassRestriction",
},
}))
}));
describe("ErrorRow", () => {
const mockMessage: ClineMessage = {
@@ -43,40 +56,47 @@ describe("ErrorRow", () => {
type: "say",
say: "error",
text: "Test error message",
}
};
beforeEach(() => {
vi.clearAllMocks()
})
vi.clearAllMocks();
mockSetUserOrganization.mockResolvedValue({});
});
it("renders basic error message", () => {
render(<ErrorRow errorType="error" message={mockMessage} />)
render(<ErrorRow errorType="error" message={mockMessage} />);
expect(screen.getByText("Test error message")).toBeInTheDocument()
})
expect(screen.getByText("Test error message")).toBeInTheDocument();
});
it("renders mistake limit reached error", () => {
const mistakeMessage = { ...mockMessage, text: "Mistake limit reached" }
render(<ErrorRow errorType="mistake_limit_reached" message={mistakeMessage} />)
const mistakeMessage = { ...mockMessage, text: "Mistake limit reached" };
render(
<ErrorRow errorType="mistake_limit_reached" message={mistakeMessage} />,
);
expect(screen.getByText("Mistake limit reached")).toBeInTheDocument()
})
expect(screen.getByText("Mistake limit reached")).toBeInTheDocument();
});
it("renders diff error", () => {
render(<ErrorRow errorType="diff_error" message={mockMessage} />)
render(<ErrorRow errorType="diff_error" message={mockMessage} />);
expect(
screen.getByText("The model used search patterns that don't match anything in the file. Retrying..."),
).toBeInTheDocument()
})
screen.getByText(
"The model used search patterns that don't match anything in the file. Retrying...",
),
).toBeInTheDocument();
});
it("renders clineignore error", () => {
const clineignoreMessage = { ...mockMessage, text: "/path/to/file.txt" }
render(<ErrorRow errorType="clineignore_error" message={clineignoreMessage} />)
const clineignoreMessage = { ...mockMessage, text: "/path/to/file.txt" };
render(
<ErrorRow errorType="clineignore_error" message={clineignoreMessage} />,
);
expect(screen.getByText(/Cline tried to access/)).toBeInTheDocument()
expect(screen.getByText("/path/to/file.txt")).toBeInTheDocument()
})
expect(screen.getByText(/Cline tried to access/)).toBeInTheDocument();
expect(screen.getByText("/path/to/file.txt")).toBeInTheDocument();
});
describe("API error handling", () => {
it("renders credit limit error when balance error is detected", async () => {
@@ -92,16 +112,26 @@ describe("ErrorRow", () => {
buy_credits_url: "https://app.cline.bot/dashboard",
},
},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(<ErrorRow apiRequestFailedMessage="Insufficient credits error" errorType="error" message={mockMessage} />)
render(
<ErrorRow
apiRequestFailedMessage="Insufficient credits error"
errorType="error"
message={mockMessage}
/>,
);
expect(screen.getByTestId("credit-limit-error")).toBeInTheDocument()
expect(screen.getByText("You have run out of credits.")).toBeInTheDocument()
})
expect(screen.getByTestId("credit-limit-error")).toBeInTheDocument();
expect(
screen.getByText("You have run out of credits."),
).toBeInTheDocument();
});
it("renders rate limit error with request ID", async () => {
const mockClineError = {
@@ -110,46 +140,66 @@ describe("ErrorRow", () => {
_error: {
request_id: "req_123456",
},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(<ErrorRow apiRequestFailedMessage="Rate limit exceeded" errorType="error" message={mockMessage} />)
render(
<ErrorRow
apiRequestFailedMessage="Rate limit exceeded"
errorType="error"
message={mockMessage}
/>,
);
expect(screen.getByText("Rate limit exceeded")).toBeInTheDocument()
expect(screen.getByText("Request ID: req_123456")).toBeInTheDocument()
})
expect(screen.getByText("Rate limit exceeded")).toBeInTheDocument();
expect(screen.getByText("Request ID: req_123456")).toBeInTheDocument();
});
it("renders quota exceeded error", async () => {
const mockClineError = {
message: "Inference cap reached",
isErrorType: vi.fn((type) => type === "quotaexceeded"),
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(<ErrorRow apiRequestFailedMessage="The message" errorType="error" message="" />)
expect(screen.getByText("Inference cap reached")).toBeInTheDocument()
})
render(
<ErrorRow
apiRequestFailedMessage="The message"
errorType="error"
message=""
/>,
);
expect(screen.getByText("Inference cap reached")).toBeInTheDocument();
});
it("renders entitlement error with the detail message instead of a raw JSON blob", async () => {
const mockClineError = {
message: "403 Error 403: the user is not subscribed to required model plan",
message:
"403 Error 403: the user is not subscribed to required model plan",
isErrorType: vi.fn((type) => type === "entitlement"),
providerId: "cline-pass",
_error: {
code: "ENTITLEMENT_ERROR",
details: {
code: "ENTITLEMENT_ERROR",
message: "Error 403: the user is not subscribed to required model plan",
message:
"Error 403: the user is not subscribed to required model plan",
},
},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(
<ErrorRow
@@ -157,14 +207,64 @@ describe("ErrorRow", () => {
errorType="error"
message={mockMessage}
/>,
)
);
// Renders the friendly EntitlementError component with the human-readable detail message...
expect(screen.getByTestId("entitlement-error")).toBeInTheDocument()
expect(screen.getByText("Error 403: the user is not subscribed to required model plan")).toBeInTheDocument()
expect(screen.getByTestId("entitlement-error")).toBeInTheDocument();
expect(
screen.getByText(
"Error 403: the user is not subscribed to required model plan",
),
).toBeInTheDocument();
// ...and does not dump the raw JSON blob or the [CLINE-PASS] ENTITLEMENT_ERROR header.
expect(screen.queryByText(/ENTITLEMENT_ERROR/)).not.toBeInTheDocument()
})
expect(screen.queryByText(/ENTITLEMENT_ERROR/)).not.toBeInTheDocument();
});
it("renders organization account ClinePass restriction with friendly account switching copy", async () => {
const rawMessage =
"403 Error 403: organization accounts cannot use individual model inference subscriptions";
const mockClineError = {
message: rawMessage,
isErrorType: vi.fn((type) => type === "orgClinePassRestriction"),
providerId: "cline",
_error: {
code: "ENTITLEMENT_ERROR",
message: rawMessage,
},
};
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(
<ErrorRow
apiRequestFailedMessage={rawMessage}
errorType="error"
message={mockMessage}
/>,
);
expect(
screen.getByTestId("org-cline-pass-restriction-error"),
).toBeInTheDocument();
expect(
screen.getByText(
/Organization accounts cannot use ClinePass subscriptions/,
),
).toBeInTheDocument();
expect(screen.queryByText(rawMessage)).not.toBeInTheDocument();
fireEvent.click(screen.getByText("Switch to personal account"));
await waitFor(() =>
expect(mockSetUserOrganization).toHaveBeenCalledWith({}),
);
expect(
screen.getByText("Switched to personal account"),
).toBeInTheDocument();
});
it("renders friendly logged-out message and sign in button when user is not signed in", async () => {
const mockClineError = {
@@ -172,27 +272,42 @@ describe("ErrorRow", () => {
isErrorType: vi.fn((type) => type === "auth"),
providerId: "cline",
_error: {},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(<ErrorRow apiRequestFailedMessage="Authentication failed" errorType="error" message={mockMessage} />)
render(
<ErrorRow
apiRequestFailedMessage="Authentication failed"
errorType="error"
message={mockMessage}
/>,
);
expect(screen.queryByText("Authentication failed")).not.toBeInTheDocument()
expect(screen.getByText(/Whoops looks like you're logged out/)).toBeInTheDocument()
expect(screen.getByText("Sign in to Cline")).toBeInTheDocument()
})
expect(
screen.queryByText("Authentication failed"),
).not.toBeInTheDocument();
expect(
screen.getByText(/Whoops looks like you're logged out/),
).toBeInTheDocument();
expect(screen.getByText("Sign in to Cline")).toBeInTheDocument();
});
it("renders PowerShell troubleshooting link when error mentions PowerShell", async () => {
const mockClineError = {
message: "PowerShell is not recognized as an internal or external command",
message:
"PowerShell is not recognized as an internal or external command",
isErrorType: vi.fn(() => false),
_error: {},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(
<ErrorRow
@@ -200,46 +315,66 @@ describe("ErrorRow", () => {
errorType="error"
message={mockMessage}
/>,
)
);
expect(screen.getByText(/PowerShell is not recognized/)).toBeInTheDocument()
expect(screen.getByText("troubleshooting guide")).toBeInTheDocument()
expect(screen.getByRole("link", { name: "troubleshooting guide" })).toHaveAttribute(
expect(
screen.getByText(/PowerShell is not recognized/),
).toBeInTheDocument();
expect(screen.getByText("troubleshooting guide")).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "troubleshooting guide" }),
).toHaveAttribute(
"href",
"https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22",
)
})
);
});
it("handles apiReqStreamingFailedMessage instead of apiRequestFailedMessage", async () => {
const mockClineError = {
message: "Streaming failed",
isErrorType: vi.fn(() => false),
_error: {},
}
};
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(mockClineError as any);
render(<ErrorRow apiReqStreamingFailedMessage="Streaming failed" errorType="error" message={mockMessage} />)
render(
<ErrorRow
apiReqStreamingFailedMessage="Streaming failed"
errorType="error"
message={mockMessage}
/>,
);
expect(screen.getByText("Streaming failed")).toBeInTheDocument()
})
expect(screen.getByText("Streaming failed")).toBeInTheDocument();
});
it("falls back to regular error message when ClineError.parse returns null", async () => {
const { ClineError } = await import("../../../../src/services/error/ClineError")
vi.mocked(ClineError.parse).mockReturnValue(undefined)
const { ClineError } = await import(
"../../../../src/services/error/ClineError"
);
vi.mocked(ClineError.parse).mockReturnValue(undefined);
render(<ErrorRow apiRequestFailedMessage="Some API error" errorType="error" message={mockMessage} />)
render(
<ErrorRow
apiRequestFailedMessage="Some API error"
errorType="error"
message={mockMessage}
/>,
);
// When ClineError.parse returns null, we display the raw error message for non-Cline providers
// Since clineError is undefined, isClineProvider is false, so we show the raw apiRequestFailedMessage
expect(screen.getByText("Some API error")).toBeInTheDocument()
})
expect(screen.getByText("Some API error")).toBeInTheDocument();
});
it("renders regular error message when no API error messages are provided", () => {
render(<ErrorRow errorType="error" message={mockMessage} />)
render(<ErrorRow errorType="error" message={mockMessage} />);
expect(screen.getByText("Test error message")).toBeInTheDocument()
})
})
})
expect(screen.getByText("Test error message")).toBeInTheDocument();
});
});
});
@@ -1,176 +1,228 @@
import { ClineMessage } from "@shared/ExtensionMessage"
import { isClineProvider } from "@shared/utils/cline"
import { memo } from "react"
import CreditLimitError from "@/components/chat/CreditLimitError"
import EntitlementError from "@/components/chat/EntitlementError"
import SpendLimitError from "@/components/chat/SpendLimitError"
import { Button } from "@/components/ui/button"
import { useClineAuth, useClineSignIn } from "@/context/ClineAuthContext"
import { ClineError, ClineErrorType } from "../../../../src/services/error/ClineError"
import type { ClineMessage } from "@shared/ExtensionMessage";
import { isClineProvider } from "@shared/utils/cline";
import { memo } from "react";
import CreditLimitError from "@/components/chat/CreditLimitError";
import EntitlementError from "@/components/chat/EntitlementError";
import OrgClinePassRestrictionError from "@/components/chat/OrgClinePassRestrictionError";
import SpendLimitError from "@/components/chat/SpendLimitError";
import { Button } from "@/components/ui/button";
import { useClineAuth, useClineSignIn } from "@/context/ClineAuthContext";
import {
ClineError,
ClineErrorType,
} from "../../../../src/services/error/ClineError";
const _errorColor = "var(--vscode-errorForeground)"
const _errorColor = "var(--vscode-errorForeground)";
interface ErrorRowProps {
message: ClineMessage
errorType: "error" | "mistake_limit_reached" | "diff_error" | "clineignore_error"
apiRequestFailedMessage?: string
apiReqStreamingFailedMessage?: string
message: ClineMessage;
errorType:
| "error"
| "mistake_limit_reached"
| "diff_error"
| "clineignore_error";
apiRequestFailedMessage?: string;
apiReqStreamingFailedMessage?: string;
}
const ErrorRow = memo(({ message, errorType, apiRequestFailedMessage, apiReqStreamingFailedMessage }: ErrorRowProps) => {
const { clineUser } = useClineAuth()
const rawApiError = apiRequestFailedMessage || apiReqStreamingFailedMessage
const ErrorRow = memo(
({
message,
errorType,
apiRequestFailedMessage,
apiReqStreamingFailedMessage,
}: ErrorRowProps) => {
const { clineUser } = useClineAuth();
const rawApiError = apiRequestFailedMessage || apiReqStreamingFailedMessage;
const { isLoginLoading, handleSignIn } = useClineSignIn()
const { isLoginLoading, handleSignIn } = useClineSignIn();
const renderErrorContent = () => {
switch (errorType) {
case "error":
case "mistake_limit_reached":
// Handle API request errors with special error parsing
if (rawApiError) {
// FIXME: ClineError parsing should not be applied to non-Cline providers, but it seems we're using clineErrorMessage below in the default error display
const clineError = ClineError.parse(rawApiError)
const errorMessage = clineError?._error?.message || clineError?.message || rawApiError
const requestId = clineError?._error?.request_id
const providerId = clineError?.providerId || clineError?._error?.providerId
const errorCode = clineError?._error?.code
const renderErrorContent = () => {
switch (errorType) {
case "error":
case "mistake_limit_reached":
// Handle API request errors with special error parsing
if (rawApiError) {
// FIXME: ClineError parsing should not be applied to non-Cline providers, but it seems we're using clineErrorMessage below in the default error display
const clineError = ClineError.parse(rawApiError);
const errorMessage =
clineError?._error?.message || clineError?.message || rawApiError;
const requestId = clineError?._error?.request_id;
const providerId =
clineError?.providerId || clineError?._error?.providerId;
const errorCode = clineError?._error?.code;
if (clineError?.isErrorType(ClineErrorType.Balance)) {
const errorDetails = clineError._error?.details
return (
<CreditLimitError
buyCreditsUrl={errorDetails?.buy_credits_url}
currentBalance={errorDetails?.current_balance}
message={errorDetails?.message}
totalPromotions={errorDetails?.total_promotions}
totalSpent={errorDetails?.total_spent}
/>
)
}
if (clineError?.isErrorType(ClineErrorType.Balance)) {
const errorDetails = clineError._error?.details;
return (
<CreditLimitError
buyCreditsUrl={errorDetails?.buy_credits_url}
currentBalance={errorDetails?.current_balance}
message={errorDetails?.message}
totalPromotions={errorDetails?.total_promotions}
totalSpent={errorDetails?.total_spent}
/>
);
}
if (clineError?.isErrorType(ClineErrorType.SpendLimit)) {
const d = clineError._error?.details
return (
<SpendLimitError
budgetPeriod={d?.budget_period}
limitUsd={d?.limit_usd}
message={d?.message || errorMessage}
resetsAt={d?.resets_at}
spentUsd={d?.spent_usd}
/>
)
}
if (clineError?.isErrorType(ClineErrorType.SpendLimit)) {
const d = clineError._error?.details;
return (
<SpendLimitError
budgetPeriod={d?.budget_period}
limitUsd={d?.limit_usd}
message={d?.message || errorMessage}
resetsAt={d?.resets_at}
spentUsd={d?.spent_usd}
/>
);
}
if (clineError?.isErrorType(ClineErrorType.Entitlement)) {
const detailMessage = clineError?._error?.details?.message || errorMessage
return <EntitlementError message={detailMessage} />
}
if (clineError?.isErrorType(ClineErrorType.Entitlement)) {
const detailMessage =
clineError?._error?.details?.message || errorMessage;
return <EntitlementError message={detailMessage} />;
}
if (clineError?.isErrorType(ClineErrorType.RateLimit)) {
return (
<p className="m-0 whitespace-pre-wrap text-error wrap-anywhere">
{errorMessage}
{requestId && <div>Request ID: {requestId}</div>}
</p>
)
}
if (
clineError?.isErrorType(ClineErrorType.OrgClinePassRestriction)
) {
return <OrgClinePassRestrictionError />;
}
if (clineError?.isErrorType(ClineErrorType.QuotaExceeded)) {
const detailMessage = clineError?._error?.details?.message || errorMessage
return <p className="m-0 whitespace-pre-wrap text-error wrap-anywhere">{detailMessage}</p>
}
if (clineError?.isErrorType(ClineErrorType.RateLimit)) {
return (
<p className="m-0 whitespace-pre-wrap text-error wrap-anywhere">
{errorMessage}
{requestId && <div>Request ID: {requestId}</div>}
</p>
);
}
if (clineError?.isErrorType(ClineErrorType.Auth) && isClineProvider(providerId)) {
return !clineUser ? (
// User is using Cline provider and is not logged in
<div className="flex flex-col gap-3">
<div className="flex items-center justify-center rounded border border-neutral-500/30 bg-vscode-editor-background p-6 text-center text-vscode-foreground">
Whoops looks like you're logged out click below to sign in
if (clineError?.isErrorType(ClineErrorType.QuotaExceeded)) {
const detailMessage =
clineError?._error?.details?.message || errorMessage;
return (
<p className="m-0 whitespace-pre-wrap text-error wrap-anywhere">
{detailMessage}
</p>
);
}
if (
clineError?.isErrorType(ClineErrorType.Auth) &&
isClineProvider(providerId)
) {
return !clineUser ? (
// User is using Cline provider and is not logged in
<div className="flex flex-col gap-3">
<div className="flex items-center justify-center rounded border border-neutral-500/30 bg-vscode-editor-background p-6 text-center text-vscode-foreground">
Whoops looks like you're logged out click below to sign in
</div>
<Button
className="w-full"
disabled={isLoginLoading}
onClick={handleSignIn}
>
Sign in to Cline
{isLoginLoading && (
<span className="ml-1 animate-spin">
<span className="codicon codicon-refresh" />
</span>
)}
</Button>
</div>
<Button className="w-full" disabled={isLoginLoading} onClick={handleSignIn}>
Sign in to Cline
{isLoginLoading && (
<span className="ml-1 animate-spin">
<span className="codicon codicon-refresh" />
</span>
) : (
// Don't show sign in button after the user has logged in, just ask them to retry
<div className="mt-4">
<span className="text-description">
(Click "Retry" below)
</span>
</div>
);
}
return (
<p className="m-0 whitespace-pre-wrap text-error wrap-anywhere flex flex-col gap-3">
{/* Display the well-formatted error extracted from the ClineError instance */}
<header>
{providerId && (
<span className="uppercase">[{providerId}] </span>
)}
</Button>
</div>
) : (
// Don't show sign in button after the user has logged in, just ask them to retry
<div className="mt-4">
<span className="text-description">(Click "Retry" below)</span>
</div>
)
{errorCode && <span>{errorCode}</span>}
{errorMessage}
{requestId && <div>Request ID: {requestId}</div>}
</header>
{/* Windows Powershell Issue */}
{errorMessage?.toLowerCase()?.includes("powershell") && (
<div>
It seems like you're having Windows PowerShell issues,
please see this{" "}
<a
className="underline text-inherit"
href="https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22"
>
troubleshooting guide
</a>
.
</div>
)}
{/* Display raw API error if different from parsed error message */}
{errorMessage !== rawApiError && <div>{rawApiError}</div>}
<div className="mt-4">
<span className="text-description">
(Click "Retry" below)
</span>
</div>
</p>
);
}
// Regular error message
return (
<p className="m-0 whitespace-pre-wrap text-error wrap-anywhere flex flex-col gap-3">
{/* Display the well-formatted error extracted from the ClineError instance */}
<header>
{providerId && <span className="uppercase">[{providerId}] </span>}
{errorCode && <span>{errorCode}</span>}
{errorMessage}
{requestId && <div>Request ID: {requestId}</div>}
</header>
{/* Windows Powershell Issue */}
{errorMessage?.toLowerCase()?.includes("powershell") && (
<div>
It seems like you're having Windows PowerShell issues, please see this{" "}
<a
className="underline text-inherit"
href="https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22">
troubleshooting guide
</a>
.
</div>
)}
{/* Display raw API error if different from parsed error message */}
{errorMessage !== rawApiError && <div>{rawApiError}</div>}
<div className="mt-4">
<span className="text-description">(Click "Retry" below)</span>
</div>
<p className="m-0 mt-0 whitespace-pre-wrap text-error wrap-anywhere">
{message.text}
</p>
)
}
);
// Regular error message
return <p className="m-0 mt-0 whitespace-pre-wrap text-error wrap-anywhere">{message.text}</p>
case "diff_error":
return (
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-quote text-foreground">
<div>The model used search patterns that don't match anything in the file. Retrying...</div>
</div>
)
case "clineignore_error":
return (
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-quote text-foreground">
<div>
Cline tried to access <code>{message.text}</code> which is blocked by the <code>.clineignore</code>
file.
case "diff_error":
return (
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-quote text-foreground">
<div>
The model used search patterns that don't match anything in the
file. Retrying...
</div>
</div>
</div>
)
);
default:
return null
case "clineignore_error":
return (
<div className="flex flex-col p-2 rounded text-xs opacity-80 bg-quote text-foreground">
<div>
Cline tried to access <code>{message.text}</code> which is
blocked by the <code>.clineignore</code>
file.
</div>
</div>
);
default:
return null;
}
};
// For diff_error and clineignore_error, we don't show the header separately
if (errorType === "diff_error" || errorType === "clineignore_error") {
return renderErrorContent();
}
}
// For diff_error and clineignore_error, we don't show the header separately
if (errorType === "diff_error" || errorType === "clineignore_error") {
return renderErrorContent()
}
// For other error types, show header + content
return renderErrorContent();
},
);
// For other error types, show header + content
return renderErrorContent()
})
export default ErrorRow
export default ErrorRow;
@@ -0,0 +1,62 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
import { useState } from "react";
import { AccountServiceClient } from "@/services/grpc-client";
const ORG_CLINE_PASS_RESTRICTION_MESSAGE =
"Organization accounts cannot use ClinePass subscriptions.";
const OrgClinePassRestrictionError = () => {
const [isSwitching, setIsSwitching] = useState(false);
const [didSwitch, setDidSwitch] = useState(false);
const [error, setError] = useState<string | undefined>();
const handleSwitchToPersonalAccount = async () => {
setIsSwitching(true);
setError(undefined);
try {
await AccountServiceClient.setUserOrganization({});
setDidSwitch(true);
} catch (error) {
console.error("Failed to switch to personal Cline account:", error);
setError(
"Failed to switch account. Use /accounts to switch to your personal account.",
);
} finally {
setIsSwitching(false);
}
};
return (
<div
className="p-2 border-none rounded-md mb-2 bg-(--vscode-textBlockQuote-background)"
data-testid="org-cline-pass-restriction-error"
>
<div className="text-error mb-2">
Organization account cannot use ClinePass
</div>
<div className="text-(--vscode-descriptionForeground) text-xs wrap-anywhere">
{ORG_CLINE_PASS_RESTRICTION_MESSAGE}
</div>
<VSCodeButton
className="w-full mt-3"
disabled={isSwitching || didSwitch}
onClick={handleSwitchToPersonalAccount}
>
{isSwitching
? "Switching..."
: didSwitch
? "Switched to personal account"
: "Switch to personal account"}
</VSCodeButton>
{didSwitch && (
<div className="text-(--vscode-descriptionForeground) text-xs mt-2">
Retry the request after switching.
</div>
)}
{error && <div className="text-error text-xs mt-2">{error}</div>}
</div>
);
};
export { ORG_CLINE_PASS_RESTRICTION_MESSAGE };
export default OrgClinePassRestrictionError;
@@ -2,12 +2,13 @@ import { BANNER_DATA, BannerAction, BannerActionType, BannerCardData } from "@sh
import { EmptyRequest } from "@shared/proto/cline/common"
import type { Worktree } from "@shared/proto/cline/worktree"
import { TrackWorktreeViewOpenedRequest } from "@shared/proto/cline/worktree"
import { GitBranch } from "lucide-react"
import { GitBranch, Sparkles } from "lucide-react"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import BannerCarousel from "@/components/common/BannerCarousel"
import BannerCarousel, { type BannerData } from "@/components/common/BannerCarousel"
import WhatsNewModal from "@/components/common/WhatsNewModal"
import HistoryPreview from "@/components/history/HistoryPreview"
import { useApiConfigurationHandlers } from "@/components/settings/utils/useApiConfigurationHandlers"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import HomeHeader from "@/components/welcome/HomeHeader"
import { SuggestedTasks } from "@/components/welcome/SuggestedTasks"
@@ -16,9 +17,12 @@ import { useClineAuth } from "@/context/ClineAuthContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient, StateServiceClient, UiServiceClient, WorktreeServiceClient } from "@/services/grpc-client"
import { convertBannerData } from "@/utils/bannerUtils"
import { buildClinePassSubscriptionUrl } from "@/utils/clinePassSubscription"
import { getCurrentPlatform } from "@/utils/platformUtils"
import { WelcomeSectionProps } from "../../types/chatTypes"
const CLINE_PASS_PROMO_BANNER_ID = "cline-pass-home-promo-v2"
/**
* Welcome section shown when there's no active task
* Includes info banner, announcements, home header, and history preview
@@ -68,6 +72,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
welcomeBanners,
} = useExtensionState()
const { handleFieldsChange } = useApiConfigurationHandlers()
const [dismissedLocalBanners, setDismissedLocalBanners] = useState<Set<string>>(() => new Set())
// Open modal once we have welcome banners
useEffect(() => {
@@ -160,7 +165,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
break
case BannerActionType.SetModel: {
const modelId = action.arg || "anthropic/claude-sonnet-4.5"
const modelId = action.arg || "anthropic/claude-sonnet-5"
const initialModelTab = action.tab || "recommended"
handleFieldsChange({
planModeOpenRouterModelId: modelId,
@@ -210,6 +215,8 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
* Dismissal handler - updates version tracking
*/
const handleBannerDismiss = useCallback((bannerId: string) => {
setDismissedLocalBanners((previous) => new Set(previous).add(bannerId))
// !! Do not continue use these version numbers or add new banners that don't have unique IDs. !!
// Banner versions are **deprecated**. Going forward, we are tracking which banners have
// been dismissed using the **banner ID**.
@@ -225,6 +232,65 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
}
}, [])
const clinePassPromoBanner = useMemo((): BannerData | undefined => {
if (
isBannerDismissed(CLINE_PASS_PROMO_BANNER_ID) ||
dismissedLocalBanners.has(CLINE_PASS_PROMO_BANNER_ID)
) {
return undefined
}
const dismissPromoBanner = () => handleBannerDismiss(CLINE_PASS_PROMO_BANNER_ID)
const subscriptionUrl = buildClinePassSubscriptionUrl(clineUser?.appBaseUrl)
return {
id: CLINE_PASS_PROMO_BANNER_ID,
icon: <Sparkles className="size-4 text-[var(--vscode-charts-yellow)]" />,
title: "Try ClinePass",
description: (
<div className="flex flex-col gap-2">
<p className="m-0">
A $9.99/month subscription for the latest open-weights models, at much lower cost than
paying for direct API access.
</p>
<div>
<Button
onClick={() => {
UiServiceClient.openUrl({ value: subscriptionUrl }).catch(console.error)
}}
size="sm">
Get ClinePass
</Button>
</div>
<button
className="w-fit cursor-pointer border-0 bg-transparent p-0 text-left text-xs text-[var(--vscode-textLink-foreground)] underline hover:cursor-pointer hover:text-[var(--vscode-textLink-activeForeground,var(--vscode-textLink-foreground))]"
onClick={async () => {
try {
await handleFieldsChange({
planModeApiProvider: "cline-pass",
actModeApiProvider: "cline-pass",
})
navigateToSettings("api-config")
} catch (error) {
console.error("Failed to switch to ClinePass:", error)
}
}}
type="button">
Switch to ClinePass provider to access subscription.
</button>
</div>
),
onDismiss: dismissPromoBanner,
}
}, [
clineUser?.appBaseUrl,
dismissedLocalBanners,
handleBannerDismiss,
handleFieldsChange,
isBannerDismissed,
navigateToSettings,
])
/**
* Build array of active banners for carousel
* Combines hardcoded banners (bannerConfig) with dynamic banners from extension state
@@ -247,8 +313,9 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
)
// Combine both sources: extension state banners first, then hardcoded banners
return [...extensionStateBanners, ...hardcodedBanners]
}, [bannerConfig, banners, clineUser, handleBannerAction, handleBannerDismiss])
const carouselBanners = [...extensionStateBanners, ...hardcodedBanners]
return clinePassPromoBanner ? [clinePassPromoBanner, ...carouselBanners] : carouselBanners
}, [bannerConfig, banners, clinePassPromoBanner, handleBannerAction, handleBannerDismiss])
return (
<div className="flex flex-col flex-1 w-full h-full p-0 m-0">
@@ -260,10 +327,10 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
welcomeBanners={welcomeBanners}
/>
<div className="overflow-y-auto flex flex-col pb-2.5">
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
{!showWhatsNewModal && (
<>
<BannerCarousel banners={activeBanners} />
<HomeHeader shouldShowQuickWins={shouldShowQuickWins} />
{!shouldShowQuickWins && taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
{/* Quick launch worktree button */}
{isGitRepo && worktreesEnabled?.featureFlag && worktreesEnabled?.user && (
@@ -313,6 +380,7 @@ export const WelcomeSection: React.FC<WelcomeSectionProps> = ({
)}
</>
)}
{showWhatsNewModal && <HomeHeader shouldShowQuickWins={shouldShowQuickWins} />}
</div>
<SuggestedTasks shouldShowQuickWins={shouldShowQuickWins} />
@@ -49,17 +49,22 @@ const BannerCardContent: React.FC<BannerCardContentProps> = ({ banner, isActive,
}}>
{/* Title with optional icon */}
<h3
className={cn("font-semibold mb-2 flex items-center gap-2 text-base pr-0", {
className={cn("font-semibold mb-2 flex items-center text-base pr-0", {
"gap-2": banner.icon,
"pr-6": showDismissButton,
})}>
<span className="shrink-0">{banner.icon}</span>
{banner.icon && <span className="shrink-0">{banner.icon}</span>}
{banner.title}
</h3>
{/* Description */}
<div className="text-sm text-description leading-relaxed [&>*:last-child]:mb-0 [&_a]:hover:underline">
{markdownContent}
</div>
{typeof banner.description === "string" ? (
<div className="text-sm text-description leading-relaxed [&>*:last-child]:mb-0 [&_a]:hover:underline">
{markdownContent}
</div>
) : (
<div className="text-sm text-description leading-relaxed">{banner.description}</div>
)}
{/* Action buttons */}
{banner.actions?.length ? (
@@ -7,15 +7,13 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Item, ItemContent, ItemDescription, ItemHeader, ItemMedia, ItemTitle } from "@/components/ui/item"
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
import { cn } from "@/lib/utils"
import { AccountServiceClient, StateServiceClient } from "@/services/grpc-client"
import { setPendingClinePassSubscribe } from "./clinePassSubscribe"
import ApiConfigurationSection from "../settings/sections/ApiConfigurationSection"
import { useApiConfigurationHandlers } from "../settings/utils/useApiConfigurationHandlers"
import WelcomeView from "../welcome/WelcomeView"
import { setPendingClinePassSubscribe } from "./clinePassSubscribe"
import {
getCapabilities,
getClineUIOnboardingGroups,
@@ -301,8 +299,7 @@ const OnboardingStepContent = ({
const OnboardingViewContent = ({ onboardingModels }: { onboardingModels: OnboardingModelGroup }) => {
const { handleFieldsChange } = useApiConfigurationHandlers()
const { openRouterModels, hideSettings, hideAccount, setShowWelcome } = useExtensionState()
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
const userTypeSelections = useMemo(() => getUserTypeSelections(isClinePassEnabled), [isClinePassEnabled])
const userTypeSelections = useMemo(() => getUserTypeSelections(true), [])
const [stepNumber, setStepNumber] = useState(0)
const [isActionLoading, setIsActionLoading] = useState(false)
@@ -1,6 +1,6 @@
import type { OnboardingModel, OnboardingModelGroup } from "@shared/proto/cline/state"
import { describe, expect, it } from "vitest"
import { getClineUIOnboardingGroups, getRecommendedModelsData } from "../data-models"
import { CLINEPASS_GROUP, getClineUIOnboardingGroups, getRecommendedModelsData } from "../data-models"
function model(id: string, group: string): OnboardingModel {
return {
@@ -22,7 +22,7 @@ describe("getClineUIOnboardingGroups", () => {
it("buckets ClinePass models into the clinePass group", () => {
const result = getClineUIOnboardingGroups(
groupOf([
model("cline-pass/glm-5.1", "clinepass"),
model("cline-pass/glm-5.1", CLINEPASS_GROUP),
model("free-model", "free"),
model("anthropic/claude", "frontier"),
model("z-ai/glm", "open source"),
@@ -30,12 +30,18 @@ describe("getClineUIOnboardingGroups", () => {
)
expect(result.clinePass).toHaveLength(1)
expect(result.clinePass[0].group).toBe("clinepass")
expect(result.clinePass[0].group).toBe(CLINEPASS_GROUP)
expect(result.clinePass[0].models.map((m) => m.id)).toEqual(["cline-pass/glm-5.1"])
expect(result.free[0].models.map((m) => m.id)).toEqual(["free-model"])
expect(result.power.flatMap((g) => g.models.map((m) => m.id))).toEqual(["anthropic/claude", "z-ai/glm"])
})
it("does not bucket cline-pass ids without a ClinePass group label", () => {
const result = getClineUIOnboardingGroups(groupOf([model("cline-pass/glm-5.2", "frontier")]))
expect(result.clinePass).toEqual([])
})
it("returns an empty clinePass group when no ClinePass models are present", () => {
const result = getClineUIOnboardingGroups(groupOf([model("free-model", "free")]))
expect(result.clinePass).toEqual([])
@@ -1,6 +1,8 @@
import type { ClineRecommendedModel, OpenRouterModelInfo } from "@shared/proto/cline/models"
import type { OnboardingModel, OnboardingModelGroup } from "@shared/proto/cline/state"
export const CLINEPASS_GROUP = "cline-pass"
export interface RecommendedModelsData {
recommended: ClineRecommendedModel[]
free: ClineRecommendedModel[]
@@ -39,16 +41,20 @@ interface ModelGroup {
models: OnboardingModel[]
}
function isClinePassOnboardingModel(model: OnboardingModel): boolean {
return model.group === CLINEPASS_GROUP
}
export function getClineUIOnboardingGroups(groupedModels: OnboardingModelGroup): OnboardingModelsByGroup {
const { models } = groupedModels
const clinePassModels = models.filter((m) => m.group === "clinepass")
const clinePassModels = models.filter(isClinePassOnboardingModel)
const freeModels = models.filter((m) => m.group === "free")
const frontierModels = models.filter((m) => m.group === "frontier")
const openSourceModels = models.filter((m) => m.group === "open source")
return {
clinePass: clinePassModels.length > 0 ? [{ group: "clinepass", models: clinePassModels }] : [],
clinePass: clinePassModels.length > 0 ? [{ group: CLINEPASS_GROUP, models: clinePassModels }] : [],
free: freeModels.length > 0 ? [{ group: "free", models: freeModels }] : [],
power: [
...(frontierModels.length > 0 ? [{ group: "frontier", models: frontierModels }] : []),
@@ -4,11 +4,9 @@ import { EmptyRequest } from "@shared/proto/cline/common"
import type { ClineRecommendedModel } from "@shared/proto/cline/models"
import type { OnboardingModel, OnboardingModelGroup } from "@shared/proto/cline/state"
import { useEffect, useMemo, useState } from "react"
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
import { ModelsServiceClient } from "@/services/grpc-client"
import { getRecommendedModelsData, type RecommendedModelsData } from "./data-models"
import { CLINEPASS_GROUP, getRecommendedModelsData, type RecommendedModelsData } from "./data-models"
export type OnboardingModelsStatus = "loading" | "success" | "empty"
@@ -51,7 +49,6 @@ type FetchState = { status: "loading" } | { status: "success"; data: Recommended
export function useOnboardingModels(): UseOnboardingModelsResult {
const { openRouterModels, clineModels, refreshClineModels } = useExtensionState()
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
const [fetchState, setFetchState] = useState<FetchState>({ status: "loading" })
useEffect(() => {
@@ -61,7 +58,7 @@ export function useOnboardingModels(): UseOnboardingModelsResult {
try {
const response = await ModelsServiceClient.refreshClineRecommendedModelsRpc(EmptyRequest.create({}))
if (!cancelled) {
const data = getRecommendedModelsData(response, isClinePassEnabled)
const data = getRecommendedModelsData(response, true)
if (!data) {
setFetchState({ status: "empty" })
} else {
@@ -80,7 +77,7 @@ export function useOnboardingModels(): UseOnboardingModelsResult {
return () => {
cancelled = true
}
}, [isClinePassEnabled])
}, [])
useEffect(() => {
refreshClineModels()
@@ -107,7 +104,7 @@ export function useOnboardingModels(): UseOnboardingModelsResult {
const clinePassCatalog = Object.fromEntries(
data.clinePass.map((rec) => [rec.id, resolveClinePassModelInfo(rec.id, openRouterModelsByName)]),
)
const clinePassModels = data.clinePass.map((rec) => toOnboardingModel(rec, "clinepass", "", clinePassCatalog))
const clinePassModels = data.clinePass.map((rec) => toOnboardingModel(rec, CLINEPASS_GROUP, "", clinePassCatalog))
return { status: "success", models: { models: [...clinePassModels, ...freeModels, ...frontierModels] } }
}, [fetchState, modelCatalog, openRouterModelsByName])
@@ -9,9 +9,7 @@ import styled from "styled-components"
import { normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { PLATFORM_CONFIG, PlatformType } from "@/config/platform.config"
import { CLINE_PASS_FEATURE_FLAG } from "@/constants/featureFlags"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useHasFeatureFlag } from "@/hooks/useFeatureFlag"
import { ModelsServiceClient } from "@/services/grpc-client"
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
import { AIhubmixProvider } from "./providers/AihubmixProvider"
@@ -21,7 +19,6 @@ import { BasetenProvider } from "./providers/BasetenProvider"
import { BedrockProvider } from "./providers/BedrockProvider"
import { CerebrasProvider } from "./providers/CerebrasProvider"
import { ClaudeCodeProvider } from "./providers/ClaudeCodeProvider"
import { ClinePassProvider } from "./providers/ClinePassProvider"
import { ClineProvider } from "./providers/ClineProvider"
import { DeepSeekProvider } from "./providers/DeepSeekProvider"
import { DifyProvider } from "./providers/DifyProvider"
@@ -102,9 +99,8 @@ const ApiOptions = ({
}: ApiOptionsProps) => {
// Use full context state for immediate save payload
const { apiConfiguration, remoteConfigSettings } = useExtensionState()
const isClinePassEnabled = useHasFeatureFlag(CLINE_PASS_FEATURE_FLAG)
const { selectedProvider } = normalizeApiConfiguration(apiConfiguration, currentMode, { isClinePassEnabled })
const { selectedProvider } = normalizeApiConfiguration(apiConfiguration, currentMode, { isClinePassEnabled: true })
const { handleModeFieldChange } = useApiConfigurationHandlers()
@@ -145,9 +141,6 @@ const ApiOptions = ({
const providerOptions = useMemo(() => {
let providers = PROVIDERS.list
if (!isClinePassEnabled) {
providers = providers.filter((option) => option.value !== "cline-pass")
}
// Filter by platform
if (PLATFORM_CONFIG.type !== PlatformType.VSCODE) {
// Don't include VS Code LM API for non-VSCode platforms
@@ -157,15 +150,24 @@ const ApiOptions = ({
// Filter by remote config if remoteConfiguredProviders is set
const remoteProviders: string[] = remoteConfigSettings?.remoteConfiguredProviders || []
if (remoteProviders.length > 0) {
providers = providers.filter((option) => remoteProviders.includes(option.value))
const effectiveRemoteProviders =
remoteProviders.includes("cline-pass") && !remoteProviders.includes("cline")
? [...remoteProviders, "cline"]
: remoteProviders
providers = providers.filter((option) => effectiveRemoteProviders.includes(option.value))
}
return providers
}, [isClinePassEnabled, remoteConfigSettings])
}, [remoteConfigSettings])
const getProviderDisplayLabel = useCallback((option: (typeof PROVIDERS.list)[number]) => {
return option.value === "cline" ? "Cline Usage-Billing" : option.label
}, [])
const currentProviderLabel = useMemo(() => {
return providerOptions.find((option) => option.value === selectedProvider)?.label || selectedProvider
}, [providerOptions, selectedProvider])
const selectedOption = providerOptions.find((option) => option.value === selectedProvider)
return selectedOption ? getProviderDisplayLabel(selectedOption) : selectedProvider
}, [getProviderDisplayLabel, providerOptions, selectedProvider])
// Sync search term with current provider when not searching
useEffect(() => {
@@ -177,13 +179,19 @@ const ApiOptions = ({
const searchableItems = useMemo(() => {
return providerOptions.map((option) => ({
value: option.value,
html: option.label,
html: getProviderDisplayLabel(option),
searchText:
option.value === "cline"
? "Cline Usage Billing usage based pay as you go"
: option.value === "cline-pass"
? "ClinePass subscription included models"
: option.label,
}))
}, [providerOptions])
}, [getProviderDisplayLabel, providerOptions])
const fuse = useMemo(() => {
return new Fuse(searchableItems, {
keys: ["html"],
keys: ["html", "searchText"],
threshold: 0.3,
shouldSort: true,
isCaseSensitive: false,
@@ -366,20 +374,17 @@ const ApiOptions = ({
<HicapProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
)}
{apiConfiguration && selectedProvider === "cline" && (
{apiConfiguration && (selectedProvider === "cline" || selectedProvider === "cline-pass") && (
<ClineProvider
currentMode={currentMode}
initialModelTab={initialModelTab}
isClinePassEnabled={isClinePassEnabled}
isClinePassEnabled={true}
isPopup={isPopup}
selectedProvider={selectedProvider}
showModelOptions={showModelOptions}
/>
)}
{apiConfiguration && isClinePassEnabled && selectedProvider === "cline-pass" && (
<ClinePassProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
)}
{apiConfiguration && selectedProvider === "asksage" && (
<AskSageProvider currentMode={currentMode} isPopup={isPopup} showModelOptions={showModelOptions} />
)}
@@ -29,7 +29,7 @@ export const ClineAccountInfoCard = () => {
<div className="max-w-[600px]">
{user ? (
<VSCodeButton appearance="secondary" onClick={handleShowAccount}>
View Billing & Usage
View Billing History
</VSCodeButton>
) : (
<div>
@@ -37,7 +37,7 @@ export const ClineAccountInfoCard = () => {
Sign Up with Cline
{isLoading && (
<span className="ml-1 animate-spin">
<span className="codicon codicon-refresh"></span>
<span className="codicon codicon-refresh" />
</span>
)}
</VSCodeButton>
@@ -414,6 +414,8 @@ const ClineModelPicker: React.FC<ClineModelPickerProps> = ({
Object.entries(resolvedModels ?? {})?.some(([id, m]) => id === selectedModelId && m.thinkingConfig) ||
selectedModelIdLower.includes("claude-haiku-4.5") ||
selectedModelIdLower.includes("claude-4.5-haiku") ||
selectedModelIdLower.includes("claude-sonnet-5") ||
selectedModelIdLower.includes("claude-5-sonnet") ||
selectedModelIdLower.includes("claude-sonnet-4.6") ||
selectedModelIdLower.includes("claude-sonnet-4-6") ||
selectedModelIdLower.includes("claude-4.6-sonnet") ||
@@ -570,6 +572,14 @@ const ClineModelPicker: React.FC<ClineModelPickerProps> = ({
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Sonnet 5 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`}
base200kModelId="anthropic/claude-sonnet-5"
onModelChange={handleModelChange}
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Opus 4.8 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-opus-4.8${CLAUDE_SONNET_1M_SUFFIX}`}
@@ -655,7 +665,7 @@ const ClineModelPicker: React.FC<ClineModelPickerProps> = ({
color: "var(--vscode-descriptionForeground)",
}}>
The extension automatically fetches the latest Cline model list. If you're unsure which model to choose, Cline
works best with <strong>anthropic/claude-sonnet-4.5</strong>.
works best with <strong>anthropic/claude-sonnet-5</strong>.
</p>
)}
</div>
@@ -221,6 +221,8 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
Object.entries(openRouterModels)?.some(([id, m]) => id === selectedModelId && m.thinkingConfig) ||
selectedModelIdLower.includes("claude-haiku-4.5") ||
selectedModelIdLower.includes("claude-4.5-haiku") ||
selectedModelIdLower.includes("claude-sonnet-5") ||
selectedModelIdLower.includes("claude-5-sonnet") ||
selectedModelIdLower.includes("claude-sonnet-4.6") ||
selectedModelIdLower.includes("claude-sonnet-4-6") ||
selectedModelIdLower.includes("claude-4.6-sonnet") ||
@@ -331,6 +333,14 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Sonnet 5 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`}
base200kModelId="anthropic/claude-sonnet-5"
onModelChange={handleModelChange}
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Opus 4.8 */}
<ContextWindowSwitcher
base1mModelId={`anthropic/claude-opus-4.8${CLAUDE_SONNET_1M_SUFFIX}`}
@@ -421,9 +431,9 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
</VSCodeLink>
If you're unsure which model to choose, Cline works best with{" "}
<VSCodeLink
onClick={() => handleModelChange("anthropic/claude-sonnet-4.6")}
onClick={() => handleModelChange("anthropic/claude-sonnet-5")}
style={{ display: "inline", fontSize: "inherit" }}>
anthropic/claude-sonnet-4.6.
anthropic/claude-sonnet-5.
</VSCodeLink>
You can also try searching "free" for no-cost options currently available.
</p>
@@ -179,6 +179,8 @@ const VercelModelPicker: React.FC<VercelModelPickerProps> = ({ isPopup, currentM
return (
selectedModelIdLower.includes("claude-haiku-4.5") ||
selectedModelIdLower.includes("claude-4.5-haiku") ||
selectedModelIdLower.includes("claude-sonnet-5") ||
selectedModelIdLower.includes("claude-5-sonnet") ||
selectedModelIdLower.includes("claude-sonnet-4.6") ||
selectedModelIdLower.includes("claude-sonnet-4-6") ||
selectedModelIdLower.includes("claude-4.6-sonnet") ||
@@ -189,8 +189,9 @@ export const ModelInfoView = ({
const [advancedExpanded, setAdvancedExpanded] = useState(false)
const isGemini = Object.keys(geminiModels).includes(selectedModelId)
const hidePricing = selectedModelId.trim().toLowerCase().startsWith("cline-pass/")
const hasThinkingConfig = hasThinkingBudget(modelInfo)
const hasTiers = !!modelInfo.tiers && modelInfo.tiers.length > 0
const hasTiers = !hidePricing && !!modelInfo.tiers && modelInfo.tiers.length > 0
// Capability checks
const hasImages = supportsImages(modelInfo)
@@ -198,7 +199,8 @@ export const ModelInfoView = ({
const hasCaching = !isGemini && supportsPromptCache(modelInfo)
// Check if we have cache pricing to show in Advanced section
const hasCachePricing = modelInfo.supportsPromptCache && (modelInfo.cacheWritesPrice || modelInfo.cacheReadsPrice)
const hasCachePricing =
!hidePricing && modelInfo.supportsPromptCache && (modelInfo.cacheWritesPrice || modelInfo.cacheReadsPrice)
return (
<div style={{ marginTop: 4 }}>
@@ -215,13 +217,13 @@ export const ModelInfoView = ({
<InfoValue>{formatCompactContext(modelInfo.contextWindow)}</InfoValue>
</InfoItem>
)}
{modelInfo.inputPrice !== undefined && (
{!hidePricing && modelInfo.inputPrice !== undefined && (
<InfoItem>
<InfoLabel>Input: </InfoLabel>
<InfoValue>{formatCompactPrice(modelInfo.inputPrice)}</InfoValue>
</InfoItem>
)}
{modelInfo.outputPrice !== undefined && (
{!hidePricing && modelInfo.outputPrice !== undefined && (
<InfoItem>
<InfoLabel>Output: </InfoLabel>
<InfoValue>
@@ -15,6 +15,8 @@ import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandler
// Anthropic models that support thinking/reasoning mode
export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
"claude-sonnet-5",
`claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`,
"claude-sonnet-4-6",
`claude-sonnet-4-6${CLAUDE_SONNET_1M_SUFFIX}`,
"claude-3-7-sonnet-20250219",
@@ -90,6 +92,14 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Sonnet 5 */}
<ContextWindowSwitcher
base1mModelId={`claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`}
base200kModelId="claude-sonnet-5"
onModelChange={handleModelChange}
selectedModelId={selectedModelId}
/>
{/* Context window switcher for Claude Opus 4.6 */}
<ContextWindowSwitcher
base1mModelId={`claude-opus-4-6${CLAUDE_SONNET_1M_SUFFIX}`}
@@ -24,6 +24,8 @@ import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/provi
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
export const SUPPORTED_BEDROCK_THINKING_MODELS = [
"anthropic.claude-sonnet-5",
`anthropic.claude-sonnet-5${CLAUDE_SONNET_1M_SUFFIX}`,
"anthropic.claude-sonnet-4-6",
`anthropic.claude-sonnet-4-6${CLAUDE_SONNET_1M_SUFFIX}`,
"anthropic.claude-3-7-sonnet-20250219-v1:0",
@@ -13,6 +13,7 @@ const SUPPORTED_CLAUDE_CODE_THINKING_MODELS = [
...SUPPORTED_ANTHROPIC_THINKING_MODELS,
"sonnet",
"sonnet[1m]",
"claude-sonnet-5[1m]",
"claude-fable-5[1m]",
"claude-opus-4-8[1m]",
"claude-opus-4-7[1m]",
@@ -6,14 +6,26 @@ import {
resolveClinePassModelInfo,
} from "@shared/api"
import { EmptyRequest } from "@shared/proto/cline/common"
import type { Mode } from "@shared/storage/types"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ClineAccountInfoCard } from "../ClineAccountInfoCard"
import ClineModelPicker from "../ClineModelPicker"
import { ClineProvider } from "./ClineProvider"
export const ClinePassProvider: typeof ClineProvider = (props) => {
interface ClinePassProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
showAccountCard?: boolean
}
export const ClinePassProvider = ({
showModelOptions,
isPopup,
currentMode,
showAccountCard = true,
}: ClinePassProviderProps) => {
const { openRouterModels } = useExtensionState()
const openRouterModelsByName = useMemo(() => buildModelInfoNameMap(openRouterModels), [openRouterModels])
const [clinePassRecommendedModels, setClinePassRecommendedModels] = useState<Record<string, ModelInfo> | undefined>(undefined)
@@ -25,7 +37,7 @@ export const ClinePassProvider: typeof ClineProvider = (props) => {
(response.clinePass ?? [])
.filter((model) => model.id)
.map((model) => {
// Cline Pass model IDs omit the upstream lab, so look up capabilities using
// ClinePass model IDs omit the upstream lab, so look up capabilities using
// the model slug (for example, glm-5.1 instead of cline-pass/glm-5.1).
// If the model is not in OpenRouter yet, use conservative generic defaults
// instead of copying GLM-5.1-specific context/max-token values.
@@ -42,7 +54,7 @@ export const ClinePassProvider: typeof ClineProvider = (props) => {
)
setClinePassRecommendedModels(Object.keys(models).length > 0 ? models : undefined)
} catch (error) {
console.error("Failed to refresh Cline Pass models:", error)
console.error("Failed to refresh ClinePass models:", error)
}
}, [openRouterModelsByName])
@@ -63,18 +75,23 @@ export const ClinePassProvider: typeof ClineProvider = (props) => {
return (
<div>
<div style={{ marginBottom: 14, marginTop: 4 }}>
<ClineAccountInfoCard />
</div>
{showAccountCard && (
<div style={{ marginBottom: 14, marginTop: 4 }}>
<ClineAccountInfoCard />
</div>
)}
<ClineModelPicker
{...props}
defaultModelId={clinePassDefaultModel}
modelIdFieldPair={{ plan: "planModeClinePassModelId", act: "actModeClinePassModelId" }}
modelInfoFieldPair={{ plan: "planModeClinePassModelInfo", act: "actModeClinePassModelInfo" }}
models={clinePassModelOptions}
showFeaturedModels={false}
/>
{showModelOptions && (
<ClineModelPicker
currentMode={currentMode}
defaultModelId={clinePassDefaultModel}
isPopup={isPopup}
modelIdFieldPair={{ plan: "planModeClinePassModelId", act: "actModeClinePassModelId" }}
modelInfoFieldPair={{ plan: "planModeClinePassModelInfo", act: "actModeClinePassModelInfo" }}
models={clinePassModelOptions}
showFeaturedModels={false}
/>
)}
</div>
)
}
@@ -1,18 +1,28 @@
import { Mode } from "@shared/storage/types"
import type { ApiProvider } from "@shared/api"
import type { Mode } from "@shared/storage/types"
import styled from "styled-components"
import VSCodeButtonLink from "@/components/common/VSCodeButtonLink"
import { useClineAuth } from "@/context/ClineAuthContext"
import { buildClinePassSubscriptionUrl } from "@/utils/clinePassSubscription"
import { ClineAccountInfoCard } from "../ClineAccountInfoCard"
import ClineModelPicker from "../ClineModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { ClinePassProvider } from "./ClinePassProvider"
/**
* Props for the ClineProvider component
*/
interface ClineProviderProps {
export interface ClineProviderProps {
showModelOptions: boolean
isPopup?: boolean
currentMode: Mode
initialModelTab?: "recommended" | "free"
isClinePassEnabled?: boolean
selectedProvider?: ApiProvider
}
type ClineBillingRoute = "cline" | "cline-pass"
/**
* The Cline provider configuration component
*/
@@ -22,23 +32,128 @@ export const ClineProvider = ({
currentMode,
initialModelTab,
isClinePassEnabled,
selectedProvider,
}: ClineProviderProps) => {
const { handleModeFieldChange } = useApiConfigurationHandlers()
const { clineUser } = useClineAuth()
const activeRoute: ClineBillingRoute = selectedProvider === "cline-pass" && isClinePassEnabled ? "cline-pass" : "cline"
const clinePassSubscribeUrl = clineUser ? buildClinePassSubscriptionUrl(clineUser.appBaseUrl) : undefined
const handleRouteChange = async (route: ClineBillingRoute) => {
if (route === activeRoute) {
return
}
await handleModeFieldChange({ plan: "planModeApiProvider", act: "actModeApiProvider" }, route, currentMode)
}
return (
<div>
{/* Cline Account Info Card */}
<div style={{ marginBottom: 14, marginTop: 4 }}>
<ClineAccountInfoCard />
</div>
{isClinePassEnabled && (
<RouteContainer>
<RouteStatus>
{activeRoute === "cline-pass" ? (
<>
ClinePass lets you use the best open weights models.{" "}
<RouteLink
href="#"
onClick={(event) => {
event.preventDefault()
handleRouteChange("cline").catch((error) => console.error("Failed to switch to Cline:", error))
}}>
Switch to Cline Usage-Billing for other models.
</RouteLink>
</>
) : (
<>
Usage-Billing models bill to your Cline account balance.{" "}
<RouteLink
href="#"
onClick={(event) => {
event.preventDefault()
handleRouteChange("cline-pass").catch((error) =>
console.error("Failed to switch to ClinePass:", error),
)
}}>
Switch to ClinePass provider to access subscription.
</RouteLink>
</>
)}
</RouteStatus>
<RouteActions>
{activeRoute === "cline" && <ClineAccountInfoCard />}
{activeRoute === "cline-pass" && (
clinePassSubscribeUrl ? (
<VSCodeButtonLink appearance="secondary" href={clinePassSubscribeUrl}>
Manage ClinePass or See Usage
</VSCodeButtonLink>
) : (
<ClineAccountInfoCard />
)
)}
</RouteActions>
</RouteContainer>
)}
{!isClinePassEnabled && (
<StandaloneRouteActions>
<ClineAccountInfoCard />
</StandaloneRouteActions>
)}
{showModelOptions && (
<ClineModelPicker
currentMode={currentMode}
initialTab={initialModelTab}
isClinePassEnabled={isClinePassEnabled}
isPopup={isPopup}
showProviderRouting={true}
/>
activeRoute === "cline-pass" ? (
<ClinePassProvider
currentMode={currentMode}
isPopup={isPopup}
showAccountCard={false}
showModelOptions={showModelOptions}
/>
) : (
<ClineModelPicker
currentMode={currentMode}
initialTab={initialModelTab}
isClinePassEnabled={isClinePassEnabled}
isPopup={isPopup}
showProviderRouting={true}
/>
)
)}
</div>
)
}
const RouteContainer = styled.div`
margin-bottom: 10px;
`
const RouteStatus = styled.div`
font-size: 11px;
line-height: 1.35;
color: var(--vscode-descriptionForeground);
`
const RouteLink = styled.a`
color: var(--vscode-textLink-foreground);
font: inherit;
line-height: inherit;
cursor: pointer !important;
text-decoration: none;
&:hover {
color: var(--vscode-textLink-activeForeground, var(--vscode-textLink-foreground));
cursor: pointer !important;
text-decoration: underline;
}
`
const RouteActions = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
margin-top: 8px;
`
const StandaloneRouteActions = styled(RouteActions)`
margin: 4px 0 14px;
`
@@ -4,9 +4,16 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import ReasoningEffortSelector from "../ReasoningEffortSelector"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
const DEEPSEEK_REASONING_EFFORT_MODELS = new Set([
"deepseek-v4-flash",
"deepseek-v4-pro",
"deepseek-reasoner",
])
/**
* Props for the DeepSeekProvider component
*/
@@ -25,6 +32,7 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
const showReasoningEffort = DEEPSEEK_REASONING_EFFORT_MODELS.has(selectedModelId)
return (
<div>
@@ -50,6 +58,8 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
selectedModelId={selectedModelId}
/>
{showReasoningEffort && <ReasoningEffortSelector currentMode={currentMode} />}
<ModelInfoView isPopup={isPopup} modelInfo={selectedModelInfo} selectedModelId={selectedModelId} />
</>
)}
@@ -25,6 +25,8 @@ interface VertexProviderProps {
// Vertex models that support thinking
const SUPPORTED_THINKING_MODELS = [
"claude-sonnet-5",
"claude-sonnet-5:1m",
"claude-sonnet-4-6",
"claude-sonnet-4-6:1m",
"claude-haiku-4-5@20251001",
@@ -9,6 +9,7 @@ import {
basetenModels,
bedrockDefaultModelId,
bedrockModels,
buildModelInfoNameMap,
cerebrasDefaultModelId,
cerebrasModels,
claudeCodeDefaultModelId,
@@ -360,17 +361,20 @@ export function normalizeApiConfiguration(
? configuredClinePassModelId
: clinePassDefaultModelId;
const clinePassModelInfo =
(currentMode === "plan"
currentMode === "plan"
? apiConfiguration?.planModeClinePassModelInfo
: apiConfiguration?.actModeClinePassModelInfo) ||
resolveClinePassModelInfo(
clinePassModelId,
options.clinePassModelInfoByName,
);
: apiConfiguration?.actModeClinePassModelInfo;
const clinePassModelInfoByName = clinePassModelInfo
? buildModelInfoNameMap({ [clinePassModelId]: clinePassModelInfo })
: options.clinePassModelInfoByName;
const resolvedClinePassModelInfo = resolveClinePassModelInfo(
clinePassModelId,
clinePassModelInfoByName,
);
return {
selectedProvider: provider,
selectedModelId: clinePassModelId,
selectedModelInfo: clinePassModelInfo,
selectedModelInfo: resolvedClinePassModelInfo,
};
}
case "openai": {
@@ -1,3 +0,0 @@
// Webview copies of feature-flag strings. Must match the extension's FeatureFlag
// enum, which can't be imported here (it pulls in Node-only deps).
export const CLINE_PASS_FEATURE_FLAG = "ext-cline-pass"
@@ -0,0 +1,14 @@
const DEFAULT_CLINE_APP_BASE_URL = "https://app.cline.bot"
const CLINE_PASS_SUBSCRIPTION_PATH = "dashboard/subscription"
export function buildClinePassSubscriptionUrl(appBaseUrl?: string): string {
try {
const baseUrl = appBaseUrl || DEFAULT_CLINE_APP_BASE_URL
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`
const url = new URL(CLINE_PASS_SUBSCRIPTION_PATH, base)
url.searchParams.set("personal", "true")
return url.toString()
} catch {
return `${DEFAULT_CLINE_APP_BASE_URL}/${CLINE_PASS_SUBSCRIPTION_PATH}?personal=true`
}
}
+150 -119
View File
@@ -1,10 +1,10 @@
/// <reference types="vitest/config" />
import { writeFileSync } from "node:fs"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react-swc"
import { resolve } from "path"
import { defineConfig, type Plugin, ViteDevServer } from "vite"
import { writeFileSync } from "node:fs";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
import { resolve } from "path";
import { defineConfig, loadEnv, type Plugin, type ViteDevServer } from "vite";
// Custom plugin to write the server port to a file
const writePortToFile = (): Plugin => {
@@ -12,135 +12,166 @@ const writePortToFile = (): Plugin => {
name: "write-port-to-file",
configureServer(server: ViteDevServer) {
server.httpServer?.once("listening", () => {
const address = server.httpServer?.address()
const port = typeof address === "object" && address ? address.port : null
const address = server.httpServer?.address();
const port =
typeof address === "object" && address ? address.port : null;
if (port) {
const portFilePath = resolve(__dirname, ".vite-port")
writeFileSync(portFilePath, port.toString())
const portFilePath = resolve(__dirname, ".vite-port");
writeFileSync(portFilePath, port.toString());
} else {
console.warn("[writePortToFile] Could not determine server port")
console.warn("[writePortToFile] Could not determine server port");
}
})
});
},
}
}
};
};
const isDevBuild = process.argv.includes("--dev-build")
const isDevBuild = process.argv.includes("--dev-build");
// Valid platforms, these should the keys in platform-configs.json
const VALID_PLATFORMS = ["vscode", "standalone"]
const platform = process.env.PLATFORM || "vscode" // Default to vscode
const VALID_PLATFORMS = ["vscode", "standalone"];
if (!VALID_PLATFORMS.includes(platform)) {
throw new Error(`Invalid PLATFORM "${platform}". Must be one of: ${VALID_PLATFORMS.join(", ")}`)
}
console.log("Building webview for", platform)
export default defineConfig(({ mode }) => {
const env = {
...loadEnv(mode, __dirname, ""),
...process.env,
};
export default defineConfig({
base: "./",
optimizeDeps: {
force: true, // Forces re-optimization
},
plugins: [react(), tailwindcss(), writePortToFile()],
test: {
environment: "jsdom",
globals: true,
setupFiles: ["./src/setupTests.ts"],
coverage: {
provider: "v8",
reportOnFailure: true,
reporter: ["html", "lcov", "text"],
reportsDirectory: "./coverage",
exclude: [
"**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}",
const platform = env.PLATFORM || "vscode"; // Default to vscode
"**/*.d.ts",
"**/vite-env.d.ts",
"**/*.{config,setup}.{js,ts,mjs,cjs}",
if (!VALID_PLATFORMS.includes(platform)) {
throw new Error(
`Invalid PLATFORM "${platform}". Must be one of: ${VALID_PLATFORMS.join(", ")}`,
);
}
console.log("Building webview for", platform);
"**/*.{css,scss,sass,less,styl}",
"**/*.{svg,png,jpg,jpeg,gif,ico}",
"**/*.{json,yaml,yml}",
"**/__mocks__/**",
"node_modules/**",
"build/**",
"coverage/**",
"dist/**",
"public/**",
"src/services/grpc-client.ts",
],
return {
base: "./",
optimizeDeps: {
force: true, // Forces re-optimization
},
},
build: {
outDir: "build",
reportCompressedSize: false,
// Only minify in production build
minify: !isDevBuild,
// Enable inline source maps for dev build
sourcemap: isDevBuild ? "inline" : false,
rollupOptions: {
output: {
inlineDynamicImports: true,
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`,
// Disable compact output for dev build
compact: !isDevBuild,
// Add generous formatting for dev build
...(isDevBuild && {
generatedCode: {
constBindings: false,
objectShorthand: false,
arrowFunctions: false,
},
}),
plugins: [react(), tailwindcss(), writePortToFile()],
test: {
environment: "jsdom",
globals: true,
setupFiles: ["./src/setupTests.ts"],
coverage: {
provider: "v8",
reportOnFailure: true,
reporter: ["html", "lcov", "text"],
reportsDirectory: "./coverage",
exclude: [
"**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}",
"**/*.d.ts",
"**/vite-env.d.ts",
"**/*.{config,setup}.{js,ts,mjs,cjs}",
"**/*.{css,scss,sass,less,styl}",
"**/*.{svg,png,jpg,jpeg,gif,ico}",
"**/*.{json,yaml,yml}",
"**/__mocks__/**",
"node_modules/**",
"build/**",
"coverage/**",
"dist/**",
"public/**",
"src/services/grpc-client.ts",
],
},
},
chunkSizeWarningLimit: 100000,
},
server: {
port: 25463,
hmr: {
host: "localhost",
protocol: "ws",
build: {
outDir: "build",
reportCompressedSize: false,
// Only minify in production build
minify: !isDevBuild,
// Enable inline source maps for dev build
sourcemap: isDevBuild ? "inline" : false,
rollupOptions: {
output: {
inlineDynamicImports: true,
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`,
// Disable compact output for dev build
compact: !isDevBuild,
// Add generous formatting for dev build
...(isDevBuild && {
generatedCode: {
constBindings: false,
objectShorthand: false,
arrowFunctions: false,
},
}),
},
},
chunkSizeWarningLimit: 100000,
},
cors: {
origin: "*",
methods: "*",
allowedHeaders: "*",
server: {
port: 25463,
hmr: {
host: "localhost",
protocol: "ws",
},
cors: {
origin: "*",
methods: "*",
allowedHeaders: "*",
},
},
},
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),
},
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
"@components": resolve(__dirname, "./src/components"),
"@context": resolve(__dirname, "./src/context"),
"@shared": resolve(__dirname, "../src/shared"),
"@utils": resolve(__dirname, "./src/utils"),
define: {
__PLATFORM__: JSON.stringify(platform),
__NODE_PLATFORM__: JSON.stringify(process.platform),
"process.env.CLINE_ENVIRONMENT": JSON.stringify(
env.CLINE_ENVIRONMENT ?? "production",
),
"process.env.IS_DEV": JSON.stringify(env.IS_DEV),
"process.env.IS_TEST": JSON.stringify(env.IS_TEST),
"process.env.CI": JSON.stringify(env.CI),
// PostHog environment variables
"process.env.TELEMETRY_SERVICE_API_KEY": JSON.stringify(
env.TELEMETRY_SERVICE_API_KEY,
),
"process.env.ERROR_SERVICE_API_KEY": JSON.stringify(
env.ERROR_SERVICE_API_KEY,
),
"process.env.ENABLE_ERROR_AUTOCAPTURE": JSON.stringify(
env.ENABLE_ERROR_AUTOCAPTURE,
),
// OpenTelemetry environment variables
"process.env.OTEL_TELEMETRY_ENABLED": JSON.stringify(
env.OTEL_TELEMETRY_ENABLED,
),
"process.env.OTEL_METRICS_EXPORTER": JSON.stringify(
env.OTEL_METRICS_EXPORTER,
),
"process.env.OTEL_LOGS_EXPORTER": JSON.stringify(env.OTEL_LOGS_EXPORTER),
"process.env.OTEL_EXPORTER_OTLP_PROTOCOL": JSON.stringify(
env.OTEL_EXPORTER_OTLP_PROTOCOL,
),
"process.env.OTEL_EXPORTER_OTLP_ENDPOINT": JSON.stringify(
env.OTEL_EXPORTER_OTLP_ENDPOINT,
),
"process.env.OTEL_EXPORTER_OTLP_HEADERS": JSON.stringify(
env.OTEL_EXPORTER_OTLP_HEADERS,
),
"process.env.OTEL_METRIC_EXPORT_INTERVAL": JSON.stringify(
env.OTEL_METRIC_EXPORT_INTERVAL,
),
},
},
})
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
"@components": resolve(__dirname, "./src/components"),
"@context": resolve(__dirname, "./src/context"),
"@shared": resolve(__dirname, "../src/shared"),
"@utils": resolve(__dirname, "./src/utils"),
},
},
};
});
+117 -111
View File
@@ -19,7 +19,7 @@
},
"apps/cli": {
"name": "@cline/cli",
"version": "3.0.27",
"version": "3.0.29",
"bin": {
"cline": "src/index.ts",
},
@@ -78,10 +78,9 @@
"@fontsource-variable/schibsted-grotesk": "^5.2.8",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@rive-app/react-webgl2": "^4.27.2",
"@shikijs/langs": "^4.2.0",
"@shikijs/themes": "^4.2.0",
"@streamdown/cjk": "^1.0.3",
"@streamdown/code": "^1.1.1",
"@streamdown/math": "^1.0.2",
"@streamdown/mermaid": "^1.0.2",
"@tailwindcss/vite": "^4.2.1",
"@xyflow/react": "^12.10.1",
"ai": "^6.0.116",
@@ -92,6 +91,7 @@
"embla-carousel-react": "^8.6.0",
"lucide-react": "^0.577.0",
"media-chrome": "^4.18.1",
"mermaid": "^11.15.0",
"motion": "^12.38.0",
"nanoid": "^5.1.7",
"next-themes": "^0.4.6",
@@ -372,7 +372,7 @@
},
"sdk/packages/agents": {
"name": "@cline/agents",
"version": "0.0.49",
"version": "0.0.51",
"dependencies": {
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
@@ -381,7 +381,7 @@
},
"sdk/packages/core": {
"name": "@cline/core",
"version": "0.0.49",
"version": "0.0.51",
"dependencies": {
"@cline/agents": "workspace:*",
"@cline/llms": "workspace:*",
@@ -419,7 +419,7 @@
},
"sdk/packages/llms": {
"name": "@cline/llms",
"version": "0.0.49",
"version": "0.0.51",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.89",
"@ai-sdk/anthropic": "^3.0.68",
@@ -453,14 +453,14 @@
},
"sdk/packages/sdk": {
"name": "@cline/sdk",
"version": "0.0.49",
"version": "0.0.51",
"dependencies": {
"@cline/core": "workspace:*",
},
},
"sdk/packages/shared": {
"name": "@cline/shared",
"version": "0.0.49",
"version": "0.0.51",
"dependencies": {
"aws4fetch": "^1.0.20",
"jsonrepair": "^3.13.2",
@@ -472,51 +472,51 @@
"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.114", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.69", "@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-s3ajs4hlAdb+5A58O423OY0zEcNfsjgGLv+GkbwZegjcvvfESyoH2CzZnG321Fjzr79nKpo7PQsVFxMZcWwFaQ=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.117", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.84", "@ai-sdk/openai": "3.0.71", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "@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-MebXAEsdvNdzKZCbVxFK0668KhYrs6W3mMH5fWT5Yc0TMSSTbwPI9fIw0sOzxLWd9TE9cJz73vVPjQjtG3vj/Q=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BIDaHmCHs6Sr5VUsEkTbbVlAN4GWjg97X9x/IfXyviLtzsXvffui9XIcZugkAi1Ri6FnvI5T5qDGh5YLnSuzRg=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.127", "", { "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-Obmw5hmE5x+ccRrMp/Djx5r0rpFVX87YqE6OY06g5fwYlRI30dA84ARfTzX45ivCvkW4eCnBpOVXVWQ/pjH85w=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.130", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qenRdpoYM+2y8Ibj3Y7XngvfcG4NIpejaM+YqAKWXi3/N1qZYeIelrm19jxhIwQW0W/g7WUz0L2Agl7+FnwrQA=="],
"@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": ["@ai-sdk/google@3.0.82", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-md+M92ZJuPIMU2p4v1rGLpJJWTmTh/vpJPkMnQbEdcLaPTZxRaroIKSnmL/9UGJV0BORJlHNDJegkcnhVpTmDA=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.143", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@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-ue7NXgSX8F7ASv5y/+HTaAdz+QyZQB0uReeujestsnqCWk5LT52lkIon9ukXC/GbqyF+vvcojLoMWeuLQxDnbQ=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.145", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.84", "@ai-sdk/google": "3.0.82", "@ai-sdk/openai-compatible": "2.0.50", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-48wlju7ksjARn6aa1vUZtPFDp+PXadHDpOsE8YHvi4wKVUf7Sxma+WYZNkIDts1b9Alv0BBZlkn5azLNciHD6g=="],
"@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=="],
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.39", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-drPs9K3a7A8LKvISzbXDoKlRD00CqeacKSSrjFDswCfCr1xS/4/GYUyJDt7UKykHW//iiiElSFWoea95yRQchg=="],
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-T/J3ED6ERDsine+crkXHfraisSG20k6BkBdgjvXCvySYnnJ19IaLIOsQPYfvXdOsKrl0LVNghooTV/nKCA7SmQ=="],
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-j6eBAa5oHFZ4U5CxpIV3T4zXNM/BviodNCZCL1qHkA4aqkwK9iQ18TWYz2DZcXpw4BO5pikKzqpXORxb1EnZGA=="],
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="],
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HyuxddF2Yv5G8qxK/0uksAINjQ4h6TpwOqHuqzsCM0u78/JWAW2OXcIplQeB44PIAORgPjbMzrw9DhnPYHMskA=="],
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@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/provider-utils": ["@ai-sdk/provider-utils@4.0.29", "", { "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-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.201", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.199", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-sDYdkPB96aHPrF5WKP0BdjRjymgZOdIMzNt1SlYPCoWWvLnV+UMwnIqvqEG+OAa9n4Sq7J3A3N/JD7rozow+CA=="],
"@ai-sdk/react": ["@ai-sdk/react@3.0.206", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.29", "ai": "6.0.204", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-26gbPT876BZVXJlSNjqkCDOFiqfxAAhYn+a/ryV6ce5TWo6EfWQZeEiLrZOS6bxaOi7xxkB5uPZaZKFaIJndjQ=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.141", "", { "dependencies": { "@anthropic-ai/sdk": "^0.93.0", "@modelcontextprotocol/sdk": "^1.29.0" }, "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.141", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.141", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.141", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.141", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.141", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.141", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.141", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.141" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-AIBacMWGcZIUcXlUoObqjwJ6pmJI3BayAqPAFXuvSq3DHJXdiuZVs7l/zTB5l3nRhRv5cqSrI2XbiDeHgZWizw=="],
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.177", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.177", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.177", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.177", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.177", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.177", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.177", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.177", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.177" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-CBzXnzR661q3AlfZzBjmIFQx0cxr36iJV3PExTYmPyGQX32qxtiFQgnxTcF8wB4hcSVf2hnoy/gprVJdkNx7cw=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.2.141", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9HZ0ot6+FwOfQ1aeMqQLH4IJGMm/DcP08SysDxscVjBm6l2JjqleHohxi3zid0DurfGweqT+4x9GScJffwg55g=="],
"@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.177", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u9Ty+KPllm2nw0RatdPF0zcPRquNZjVptmyLG0DqduGbgZDLQpfPFMF5hffFIRnVaXhx7+jkUmEdw0jrda0UcA=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.2.141", "", { "os": "darwin", "cpu": "x64" }, "sha512-4iAdarJaQ+2R58s6QJswZCzUdz2WQmL5lYG7Y+FLzWbRSROFfcH0QYpmOqSaPXd2KRQhIJwEacqecDZd/Q1XKQ=="],
"@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.177", "", { "os": "darwin", "cpu": "x64" }, "sha512-ona6Jv54XFwBTqOj3MzLWfKtc2m7Rdh58wOAX9Hnue/6FcWfyeuz/UDcidVTXQ7Xytz//Tb0JJgFtiQjO7FbIA=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.2.141", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jdf0ZEwJzOP8sE6rPqdJN+SxMb0/L8sxJg4twCv/7S+Qzk0hJtls+wxSi+0Tjh6EEMaNxJqEGc7S3fx99Wi99Q=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.177", "", { "os": "linux", "cpu": "arm64" }, "sha512-wBCbklkaDb483Ab4DUFfmJjZJKXz58YXPv+CiGsyjq1St19mbKEma5KKz3Ya9mlV8aLyh4zmLK2mEHPnF//Ipg=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.2.141", "", { "os": "linux", "cpu": "arm64" }, "sha512-6H1AJ/AVaWNnV22kubUPkOTRzZFH0+qP9k7WlhriHMN9gtgZcVAsITMddDeGjQsQJMCAdhXFd6sgi7TM1LdeOQ=="],
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.177", "", { "os": "linux", "cpu": "arm64" }, "sha512-v6PMDD3h2erLuTK5S2ZvExqdL3v44OyC70XpKhyqIUnyPaGR9YAMjh//EKdWC+mNvt6mIbRZpaDcHtbASVW8Rw=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.2.141", "", { "os": "linux", "cpu": "x64" }, "sha512-DVjp72f3HmrRYpbneWZZWIqkUht5kTZXS7wXGFiwzLz6eNYEgjjh+GcsnhIi8UOwZUtNiKUrjZnoP38ovFqV8A=="],
"@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.177", "", { "os": "linux", "cpu": "x64" }, "sha512-WDP6puwPHscggNAfvIxyUSHSAjfUEkGRfnMXEPBHOqO+qjX2KGxeE13/ih3EVioeBVOUIqPui6VB05vXLa6T9Q=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.2.141", "", { "os": "linux", "cpu": "x64" }, "sha512-fTI1YuM4cxOa4nSgsyMAdB5ELizkWp+w5Ispo4JnnYtcczMAL4D9GBNjWPW0sUzKvjsJOUVim68SmWLWhUOpXQ=="],
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.177", "", { "os": "linux", "cpu": "x64" }, "sha512-1cdEO06WoEsl1JnnLCPIlg/8x37GtBsTuj65gIpSjdrImNBjgIuMWVyceP4qaXhFjtQgYK1nx3TpaxEFVDOrDg=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.2.141", "", { "os": "win32", "cpu": "arm64" }, "sha512-Wm10J6kfbufbPGFELokiJ/7Y5Oqug4Uag3HXFsV8g7TWCpaItx/oqVaJoiGptuAtXQB7xGLQVTuk082wER+Y5w=="],
"@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.177", "", { "os": "win32", "cpu": "arm64" }, "sha512-xLgDWnZaYohtFrkgEIkGZdP+rp4sXxAMbbpEvEp0LK1vAYmJam/ztT2yoK6gfI58IbToJq1WGUEX2HVnE65yOg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.2.141", "", { "os": "win32", "cpu": "x64" }, "sha512-IXuP29YJuWbR5Q6xOHrjFVGG54V2s1FC61UVNwEN5fpxL09MwPnbwtQL6fqgzt/U1MP7vWAwpXZriYAklkH/mg=="],
"@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.177", "", { "os": "win32", "cpu": "x64" }, "sha512-SIdQLbtF//rYK4KDBNpUPyjyui7NwCFPZ2/3vyW3TR8R8xynkNq3cLis90FnPSgxaSshi38vkJYWh+9kGDB7zg=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.93.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA=="],
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.104.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-gGACa/+IaiXzRRmF96aOhamoBgapKRBiFWbmmTFP8aMkpaEcuStF+Q61bjo4vPxBM7gqWJNZqsngslRdnLHv0Q=="],
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
@@ -528,35 +528,35 @@
"@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.1065.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-node": "^3.972.54", "@aws-sdk/types": "^3.973.12", "@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-dGKFCiGbX5IrK+1NcqNgHKqWZtF3f5K2tDWJKDvGBurOO2q1SHu/XntEJydLWqEzYzJTimOZHaDzp/fqwYzu+g=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1068.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-node": "^3.972.55", "@aws-sdk/types": "^3.973.12", "@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-by2Qj3f9BI9X4cY0n160R3uzkMpI6k9PmGA8QLAuzr8HzkiNrYFygMEPhIEdqBFHQPD/8AXNUs45HYjEDsQ33g=="],
"@aws-sdk/core": ["@aws-sdk/core@3.974.20", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@aws-sdk/xml-builder": "^3.972.29", "@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-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.44", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-OjovzYsHOTZmOdrSQI2/CjGNgHO0c0ZHTFIkt3bBfrhgNFPTLnbZDvmGdMst7U5c1dqq4gzpUwZC6X9HA12MDQ=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.45", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-4L/REssieLON1hVsTzZucP1p2u5jmPNejyh/9BCGZXr93IalBbhRPCrtKIKwMTu9yRGr/bcKzhrQocByKLSzLQ=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/types": "^3.973.12", "@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-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-login": "^3.972.51", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.51", "@aws-sdk/credential-provider-web-identity": "^3.972.51", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-szg1nnebqC+Svv6Vfsdf6P/QK8x5g/ghG2CKa/1WkHifRnq0BBmDELj2Qnqk9nPsUvEu/OEcYic97CPLpKqF9g=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.53", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-login": "^3.972.52", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.52", "@aws-sdk/credential-provider-web-identity": "^3.972.52", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZfdhIOR41q8TcWEnUac+gCOb+O2LBWdHLmjedXpXz4IEFW2ppNuFcm6p0sMTavpM+zD5TYfpH5Gp7guRyqSgsQ=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.51", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-csHFsH+/VjnI40oqm1l1OqMY4B4kza36DbfcbHcgcbobgjebasqUbTU34xvwUkvtoNGGizbfyMSlMzJWUPv3dQ=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-9hu2oR0qH7Fst5Tzdx+UWxm+w5zCXtErTLtOOW5hwwQc170CLwOeniRxyFY6s9mHfGEfC5zFukNBdKBwJR8mhQ=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.54", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-ini": "^3.972.52", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.51", "@aws-sdk/credential-provider-web-identity": "^3.972.51", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-vinTSQtziNHxi2nqXF+76jr2sO44q88Ind1qFFVaotNgBaC1rcWDjBug8yoE8n0ov33s21xks9WY5XDHH9SENw=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.55", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-ini": "^3.972.53", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.52", "@aws-sdk/credential-provider-web-identity": "^3.972.52", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-zMGLa/dhESVqmCD7mmIFFKSwSFrJGScvCXcjvBZEVOOMauFS5JRQvLTMukFpMEFWiV6dTAlsen2ATDBulLPtbg=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.51", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/token-providers": "3.1065.0", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-60qhpQcSDIKIr0AuBlmJezKX0b5nbJPCINiR49N9yJXrEI5tTRwsXVBr0IdSvvsNJyqgiINyoBd++Ed0yvggbw=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/token-providers": "3.1066.0", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-nb2/n4o/HQf+FVpVbZe9vCTFngmuDoIsltMgLAtjixaKzvzhB4J8WSDFyWgnErgLHk55ctWH+I4PU+LIHhyffg=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.51", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-0X5eWsUIp8ItRJeJBBrhQAPzc9AQelDetRTVTsycCAISCCzM17R4hs/vFAPeQ0o0B35sciLiqe/Pwmml909cZA=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-lKj6aRSGbqLmpYmM24bY7a1Xmfcq2vkE3hv8CSPYfc1yCu0BPu/XEJ1L4Fm61MsU6ULLNSG8UGsffNoFUBjESA=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1065.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1065.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-cognito-identity": "^3.972.44", "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-ini": "^3.972.52", "@aws-sdk/credential-provider-login": "^3.972.51", "@aws-sdk/credential-provider-node": "^3.972.54", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.51", "@aws-sdk/credential-provider-web-identity": "^3.972.51", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-g5FGZvk9UbAD7HeGFCp+fQGJiNAneJJabpAZY13SSmVeI2Yw/V66U3na1StnqvZCYfXvGybJvSGtq2IdoCrK4g=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1068.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1068.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/credential-provider-cognito-identity": "^3.972.45", "@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-ini": "^3.972.53", "@aws-sdk/credential-provider-login": "^3.972.52", "@aws-sdk/credential-provider-node": "^3.972.55", "@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-sso": "^3.972.52", "@aws-sdk/credential-provider-web-identity": "^3.972.52", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-DN7UewD/XKGfNGWXZsTECWRj3IsULkE7aG+fPwqPf13CnX4UmNvj80g1xieJ0lVdMQ2h0L3gZpn2ClIY06+/vQ=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.19", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/signature-v4-multi-region": "^3.996.33", "@aws-sdk/types": "^3.973.12", "@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-P2Otgf15GBJMKzG6j5Ddf7w+Kz6z2jvesMy874TD3jlMfDWNK7clJeUd7hgigdeVOotjoUP4emcTWVdS9sfZDw=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.20", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.20", "@aws-sdk/signature-v4-multi-region": "^3.996.34", "@aws-sdk/types": "^3.973.12", "@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-IYJuLpXp2DEILVQpQOy0PMpkftv0AHEOCn52o0atyOaumA0CdWQ3klPyXdViGYLbNpESsVFMVybvHUeZAuiGxA=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.33", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.34", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-mx1L5qlumSOt/nKM3BFaHE2HVkWwz0i4Bw0pyYO42FfX/FeLlo8YI6csC0gSPprEk6fTIqI+CZN9RwUwKd5krQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1065.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-qdHQntq82gMqG6Tf8xrgmhJxacaYkxW4PEeDg/ISMVJ84EWe7iD6JyCTgbyox3uNDH6vqEJ8GUiTaXCq307zVw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1066.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.20", "@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-UqEUJq7dqa44hneLDUcX7UJy95cg8YqEWyakRpvIPnrNS3Mq+UlQHgCDGu5pvwAPtlIW4qcYbvW6reG6++FyvA=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.12", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA=="],
@@ -720,7 +720,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.2", "", { "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-Xj9T3Wr+Bo4ILKf9PZJBYJ4SJiZGC/pqIdzOMbX9jgAFb0oGuKkusLleYHN/N6zanZixNvmuMVWYR1T3YJuVTA=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.3", "", { "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-WSmox5aD+XxJEUEOTk7gKLpd5+Iz9Nik89Zpbu5DijMln6LsFiv3xpNKBMc/b9sSkUlKvAblzrhik2TqKFE7NA=="],
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
@@ -982,7 +982,7 @@
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="],
@@ -1008,7 +1008,7 @@
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="],
"@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -1030,13 +1030,13 @@
"@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.17.0", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-a4Fw6lEZqXhjkyTH0LNInntrdLaWcz9M1+eqQgSVqI6PAlcqC/Jo069k8VdGcU3u4qbETqmURec8+VfhS06Sqg=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.17.4", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-OdHkBoNIQOjQsPnFtkcxAp9HsLAKn/MQiq7wIQURkRARmw8yhFqGiMPYbNm+UfLn6bMkx97rdPkWQARejMyiXQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.7.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ=="],
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.8.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw=="],
"@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="],
@@ -1050,15 +1050,15 @@
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="],
"@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="],
"@opentelemetry/resources": ["@opentelemetry/resources@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg=="],
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ=="],
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg=="],
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw=="],
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.8.0", "", { "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ=="],
"@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.7.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.7.1", "@opentelemetry/core": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg=="],
"@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.8.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg=="],
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="],
@@ -1086,9 +1086,9 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@posthog/core": ["@posthog/core@1.30.14", "", { "dependencies": { "@posthog/types": "1.383.3" } }, "sha512-cC0che/17kP6qMIMgdmxsoz3h8Jar8knQfDM8WqQwVacSeWXkrwkemoV7S5tCGmgTuRTTsdigirs9HiBXHQ/dA=="],
"@posthog/core": ["@posthog/core@1.32.3", "", { "dependencies": { "@posthog/types": "1.386.3" } }, "sha512-vwOEMfZvGv5XxNWV7p9I52NSmvFNMhyW2IHpIoUHW5jLkgUrknzJW1H/qxVGSIrNNVQkfsoaDFzDhJdg10pgrA=="],
"@posthog/types": ["@posthog/types@1.383.3", "", {}, "sha512-N4jtmLaJxzjQ/C0UHnF0igQPSwUqwScPDv9ePGjKCfDomIEUcO3+c6pBrjTp7woxuMQ49BmyM/pV4/SOBPpe0Q=="],
"@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=="],
@@ -1102,8 +1102,6 @@
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="],
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
@@ -1230,9 +1228,9 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@rive-app/react-webgl2": ["@rive-app/react-webgl2@4.29.0", "", { "dependencies": { "@rive-app/webgl2": "2.38.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0" } }, "sha512-w2EjPDhU33ucc+dokl1ADGnEFWguL9f6DZTNbYQuMO9T33lGsT54Y4cdqiMRnJJizuKyGYUQVZgfWqFbx3k1/w=="],
"@rive-app/react-webgl2": ["@rive-app/react-webgl2@4.29.1", "", { "dependencies": { "@rive-app/webgl2": "2.38.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0" } }, "sha512-m80p2dXWdQR5P0Mi6dP2fOCUwSx6MYxd051LTtG/7h/ryNCBmjceK1zFJwBzlmuWrfGQCXTPvCjOFHmU+uYcpw=="],
"@rive-app/webgl2": ["@rive-app/webgl2@2.38.0", "", {}, "sha512-icwQjMP7cJccK37lgGG4L1XagSeMObKylFTTd6Ob4nAEvCajSGZvQV2pE/tcXYd17eR9laIM3lI0y8zbI1eiiw=="],
"@rive-app/webgl2": ["@rive-app/webgl2@2.38.1", "", {}, "sha512-dotNsRlrTaHNxTiJFxHHycB1PQhnJyIvrMTVpyzG6GYgjztnM27r07e8GJ2AuDQB/Vkmdz0Y6NRG5NVJ1OWDxQ=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="],
@@ -1328,30 +1326,32 @@
"@slack/types": ["@slack/types@2.21.1", "", {}, "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ=="],
"@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=="],
"@slack/web-api": ["@slack/web-api@7.17.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-jejr34a8B4L5AS713wOAx1LAqNkW16HVMDEa6sYBvFDc/llUBl8hXaiI4BwF+Al+Sug19Vn2O7iokTVIhVvZ1Q=="],
"@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.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.4", "tslib": "^2.6.2" } }, "sha512-KoUi4M1f3BG6kzN1FnCwL7oyFptTbyBJKjR6yhSib+JHRdUmM1o+VwsFtJ66NZCkCzVfJMWRHJNo0R0jznp0Pg=="],
"@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.9", "", { "dependencies": { "@smithy/core": "^3.24.7", "@smithy/types": "^4.14.4", "tslib": "^2.6.2" } }, "sha512-ZlfJ/4Fa3jYb+3eaohPfG9utX9HmdhFNcFtpoGAhUhdynAOmGXtmigbi7eEiONKM+ykHw8RwKuDEb85Lx7t7fA=="],
"@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.7", "", { "dependencies": { "@smithy/core": "^3.24.7", "tslib": "^2.6.2" } }, "sha512-VPH5RUHtlYQHQqD3k3zvoCNiXflRc24rLUK7AUi79jLmZFLzzlWmZo6awslPVmMkpoJBRNAKQE5QTQvnGENY2w=="],
"@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.7", "", { "dependencies": { "@smithy/core": "^3.24.7", "@smithy/types": "^4.14.4", "tslib": "^2.6.2" } }, "sha512-NslaM2ir0N2hisDmzXLstPaVINZheh8SokyOC++kzFPloZucL2R7Y7bS57mSzx/1Fc/fqmn7twjkeezTTrV0EA=="],
"@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.8", "", { "dependencies": { "@smithy/core": "^3.24.7", "@smithy/types": "^4.14.4", "tslib": "^2.6.2" } }, "sha512-f+DbsWUwSbtMu1a/j8Y93KiU1SRg9nyzfjereqn1BJ33QOTUXxdlYvVXMhAYl1vuR1Kmna5aIJe09KSIfyFNYw=="],
"@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.7", "", { "dependencies": { "@smithy/core": "^3.24.7", "@smithy/types": "^4.14.4", "tslib": "^2.6.2" } }, "sha512-LwQZazFayImv+IOm0S0enoLeUJwmAlhGC5O6YCcLWezyu08dF46GOxPOq35OpBIHkgd7OvNvBStIFwVNyrvoBw=="],
"@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="],
"@smithy/types": ["@smithy/types@4.14.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-B2S9+UGm1+/pHkcx3ZoLVX1a+pmSk8rqxRR+ZsNqZaJ5q9FWX9AFGQVM4qG5+OBeQUZVy99HY8HqW8gK/wgXzQ=="],
"@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.7", "", { "dependencies": { "@smithy/core": "^3.24.7", "tslib": "^2.6.2" } }, "sha512-U2XPgO73I8ef8FP3jRQO4Iiol7chWuv4TD6LxNLvRo/pYwBvbelxLcODkpAA9ek+k23NMgc9NoiVzp8INwogMA=="],
"@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="],
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@streamdown/cjk": ["@streamdown/cjk@1.0.3", "", { "dependencies": { "remark-cjk-friendly": "^2.0.1", "remark-cjk-friendly-gfm-strikethrough": "^2.0.1", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-WRg8HR/gHbBoTgsMd91OKFUClIoDcEFVofJvluvEAyjx3KpU0aGgD9tGDqHkHj14ShoMSkX0IYetWGegTcwIJw=="],
@@ -1396,37 +1396,37 @@
"@swc/types": ["@swc/types@0.1.26", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.3.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "5.21.6", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.1" } }, "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.1", "@tailwindcss/oxide-darwin-arm64": "4.3.1", "@tailwindcss/oxide-darwin-x64": "4.3.1", "@tailwindcss/oxide-freebsd-x64": "4.3.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", "@tailwindcss/oxide-linux-x64-musl": "4.3.1", "@tailwindcss/oxide-wasm32-wasi": "4.3.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0", "", { "os": "linux", "cpu": "arm" }, "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.0", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.1", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.0", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "postcss": "^8.5.10", "tailwindcss": "4.3.0" } }, "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "postcss": "8.5.15", "tailwindcss": "4.3.1" } }, "sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.0", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.1", "", { "dependencies": { "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "tailwindcss": "4.3.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ=="],
"@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="],
@@ -1560,7 +1560,7 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
"@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
@@ -1648,19 +1648,19 @@
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ai": ["ai@6.0.199", "", { "dependencies": { "@ai-sdk/gateway": "3.0.127", "@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-6H9RPEjzBQECM+eU1JxAh6jHcZPU/6q5QZ8D8QV8agubf0Mm/kcBlwqrFcFtup6RQzmEvMkVaQOoLCZ8bQ13lA=="],
"ai": ["ai@6.0.204", "", { "dependencies": { "@ai-sdk/gateway": "3.0.130", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SudB8rUwaVhpWF8+qTJcxUptXPIdN9rWMknzTT3WbKa2QwiGRshyepFKNkDILWm882LgqlEyRZgKhNT14j0jpQ=="],
"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=="],
"ai-sdk-provider-claude-code": ["ai-sdk-provider-claude-code@3.5.0", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "@anthropic-ai/claude-agent-sdk": "^0.3.170" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-7SZrTGkuR4G4zeNrjnJgDzYkrKZqzq7GUQJcFBKCxFH5dJpS9lbs+g9BCt7bwT1gDm4SAUmOQ0T5rKqcC2HkVQ=="],
"ai-sdk-provider-codex-cli": ["ai-sdk-provider-codex-cli@1.2.1", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "jsonc-parser": "^3.3.1" }, "optionalDependencies": { "@openai/codex": "^0.130.0" }, "peerDependencies": { "zod": "^3.0.0 || ^4.0.0" } }, "sha512-YBOycHYrdIXUcqOXS/BPDTjABjNl2ZBUtcy+PQmuRNUHaZohedZdWNxVAcIw65RZHzcasDojcD+qgLr0QvJX0Q=="],
"ai-sdk-provider-codex-cli": ["ai-sdk-provider-codex-cli@1.2.2", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.1", "jsonc-parser": "^3.3.1" }, "optionalDependencies": { "@openai/codex": "^0.130.0" }, "peerDependencies": { "zod": "^3.0.0 || ^4.0.0" } }, "sha512-hlIWo9KP7/hJaEjXZbxgjVO/FvMn3I+5RSht0PBp3GOBKVFkKeKlrIDvaA8Wi/nAYiAePESPem+fi1NVNfNQJA=="],
"ai-sdk-provider-opencode-sdk": ["ai-sdk-provider-opencode-sdk@3.0.3", "", { "dependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.15", "@opencode-ai/sdk": "^1.2.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XxO5eNny2p0waqlPfSc+gNg8FVTdzsqQH0gLH88Vy2kDFPFCtGzccfQcXXOeunMtqKbQT3W6d3KtUC6gtxXvoA=="],
"ai-sdk-provider-opencode-sdk": ["ai-sdk-provider-opencode-sdk@3.0.6", "", { "dependencies": { "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.15", "@opencode-ai/sdk": "^1.2.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CZ2I5z96HUdEKAT1ExhdxLE1bjD8efzT7I5GjBevWgwNGgYZjMPE7ZfQCJFVAzWqODwdTdxp7KSZjnmcd4h1Bw=="],
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
@@ -1716,7 +1716,7 @@
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.35", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.37", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
@@ -1760,7 +1760,7 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="],
"caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
@@ -1972,7 +1972,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.10", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
@@ -1986,7 +1986,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.371", "", {}, "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w=="],
"electron-to-chromium": ["electron-to-chromium@1.5.372", "", {}, "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA=="],
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
@@ -2000,7 +2000,7 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"enhanced-resolve": ["enhanced-resolve@5.23.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA=="],
"enhanced-resolve": ["enhanced-resolve@5.21.6", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ=="],
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
@@ -2022,7 +2022,7 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"es-toolkit": ["es-toolkit@1.47.0", "", {}, "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw=="],
"es-toolkit": ["es-toolkit@1.47.1", "", {}, "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q=="],
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
@@ -2100,6 +2100,8 @@
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
@@ -2142,7 +2144,7 @@
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
@@ -2518,7 +2520,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.2", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-4ai1ITN8wBhwugQcRgqe3tN0z6OSKGOXqHLNrS04MgKFfsLqu6Dm8MPq02pI9Y9ZKoXtFjIl85jOryIW9es3BA=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -2658,7 +2660,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.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="],
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
@@ -2764,11 +2766,11 @@
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Wjvt4scRFouioIInHf51IFNP4ltJ2EngJM+cZPGiqbKetBfmP3vpdPV8ID2S6JS6/jdo74N8+aEYH9lQr2C6sA=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"posthog-node": ["posthog-node@5.36.8", "", { "dependencies": { "@posthog/core": "1.30.14" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-xBdJ3Y5zcveN1QINN39dIiZiCbEhfLeh/4qBiICoLLQOTYfat6zLlwfBmFPcr4hdyQ1nBXh8sIQ9KzSG/zcxpA=="],
"posthog-node": ["posthog-node@5.37.0", "", { "dependencies": { "@posthog/core": "^1.32.3" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-wFwWGcqAqZ1WJRlNNYc92veV83d1lOQcP4Lq0q7Kar9GdZLPpiFYHeudyybYJnjZjkI9v06vLvY/Og5CZIfByg=="],
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
@@ -2790,7 +2792,7 @@
"property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
"protobufjs": ["protobufjs@7.6.3", "", { "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-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw=="],
"protobufjs": ["protobufjs@7.6.4", "", { "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/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw=="],
"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=="],
@@ -2984,6 +2986,8 @@
"stage-js": ["stage-js@1.0.2", "", {}, "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ=="],
"standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="],
@@ -3032,7 +3036,7 @@
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
"tailwindcss": ["tailwindcss@4.3.0", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="],
"tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="],
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
@@ -3074,7 +3078,7 @@
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
"ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
@@ -3230,7 +3234,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.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
"@cline/cline-hub-webview/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="],
"@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=="],
@@ -3270,6 +3274,8 @@
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
@@ -3326,15 +3332,15 @@
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],
"@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="],
"@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="],
"@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
"@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="],
"@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
"@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="],
"@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
"@opentui/core/marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
@@ -3552,13 +3558,13 @@
"@streamdown/code/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
@@ -3600,7 +3606,7 @@
"dify-ai-provider/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="],
"dify-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CvsRu+32Y8a167s+lrIBtsybvgTHp8j9y+6BeTvLeoW3Q+okw/b4CnNUFOLIXsRaKHQKAH+IHNJPYWywfpw0LA=="],
"dify-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dNciNI4knep6z3cqDNng7yORCcBnEDBHZYj8rJLcLn9pLzEtNVkf6WLg9HR6AnVDDRxHsUeGAEqyF8M+FAtRgg=="],
"dify-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3808,7 +3814,7 @@
"vaul/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw=="],
"webview/@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
"webview/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+14
View File
@@ -1,5 +1,19 @@
# Cline SDK Changelog
## 0.0.51
- Fixed Z.ai model metadata not resolving correctly when using Z.ai models through the Cline provider; aliases now map to the right model metadata and user overrides are preserved
## 0.0.50
- Truncate every tool result by default (including MCP and custom tool output), with tightened `MessageBuilder` limits and tunable `CLINE_MESSAGE_BUILDER_*` env overrides, to keep provider requests within budget
- Cap assistant text in provider messages and count `tool_use` input toward the request budget; protect binary carrier blocks (not just images) from truncation
- Resolve tool names from `tool_result` when the paired `tool_use` is gone
- Add ClinePass provider support (built-in provider, error handling, format compatibility)
- Apply auto-approve toggles immediately in the agent runtime
- Harden parallel tool-call guidance in the system prompt and tool definitions
- Refresh the generated model catalog
## 0.0.49
- Reverted ClinePass recommended-models support, removing the `clinePass` field from the recommended models data

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