From 7f75100da2994c8a27e523cc7c284a2991a4f156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Barreiro?= <52393857+BarreiroT@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:40:44 -0300 Subject: [PATCH] Add Client Type to Recommended Models requests (#14224) Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> --- apps/cli/src/acp/index.ts | 3 + apps/cli/src/index.ts | 2 + apps/cli/src/main.ts | 2 + apps/cli/src/utils/cline-client-identity.ts | 12 +++ apps/cli/src/utils/free-model-cost.test.ts | 26 +++++++ apps/cli/src/utils/free-model-cost.ts | 3 +- apps/examples/desktop-app/sidecar/index.ts | 4 + apps/vscode/src/common.ts | 3 + .../src/services/ClineClientIdentity.test.ts | 77 +++++++++++++++++++ .../src/services/ClineClientIdentity.ts | 24 ++++++ apps/vscode/vitest.config.ts | 1 + sdk/packages/core/src/index.ts | 1 + .../llms/cline-recommended-models.test.ts | 23 +++++- .../services/llms/cline-recommended-models.ts | 5 +- .../src/catalog/catalog-cline-recommended.ts | 3 +- .../llms/src/catalog/catalog-live.test.ts | 3 + sdk/packages/llms/src/index.browser.ts | 1 + sdk/packages/llms/src/index.ts | 1 + .../providers/cline-client-headers.test.ts | 57 ++++++++++++++ .../src/providers/cline-client-headers.ts | 28 +++++++ .../llms/src/providers/request-headers.ts | 2 +- sdk/packages/shared/src/index.browser.ts | 5 ++ sdk/packages/shared/src/index.ts | 9 ++- .../src/runtime/cline-client-identity.ts | 24 ++++++ 24 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 apps/cli/src/utils/cline-client-identity.ts create mode 100644 apps/vscode/src/services/ClineClientIdentity.test.ts create mode 100644 apps/vscode/src/services/ClineClientIdentity.ts create mode 100644 sdk/packages/llms/src/providers/cline-client-headers.test.ts create mode 100644 sdk/packages/llms/src/providers/cline-client-headers.ts create mode 100644 sdk/packages/shared/src/runtime/cline-client-identity.ts diff --git a/apps/cli/src/acp/index.ts b/apps/cli/src/acp/index.ts index a82106b93f..76b47386ab 100644 --- a/apps/cli/src/acp/index.ts +++ b/apps/cli/src/acp/index.ts @@ -1,4 +1,5 @@ import { Readable, Writable } from "node:stream"; +import { registerClineClientIdentity } from "../utils/cline-client-identity"; import { writeDiagnostic } from "../utils/output"; export interface AcpModeOptions { @@ -11,6 +12,8 @@ export async function runAcpMode(options?: AcpModeOptions): Promise { ); const { AcpAgent } = await import("./acpAgent"); + registerClineClientIdentity("cline-acp"); + writeDiagnostic("[acp] starting ACP mode over stdio…"); const stream = ndJsonStream( diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 3f2afc16a6..2b1dc1fbf8 100755 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -15,6 +15,7 @@ import { cleanupActiveRuntime, isAbortInProgress, } from "./runtime/active-runtime"; +import { registerClineClientIdentity } from "./utils/cline-client-identity"; import { resolveCliLaunchSpec } from "./utils/internal-launch"; import { writeErr } from "./utils/output"; @@ -32,6 +33,7 @@ if (!isMainThread) { // daemon-hosted session spawns do not inherit it and try to become daemons. // The hub daemon owns its process-level abort handling. Installing the CLI's // fatal rejection handler first would make expected abort rejections exit it. + registerClineClientIdentity("cline-cli"); void import("@cline/core/hub/daemon-entry"); } else { // Same reasoning as the daemon sentinel above: consume the supervised-connector diff --git a/apps/cli/src/main.ts b/apps/cli/src/main.ts index b0cb9232de..fa69522d31 100644 --- a/apps/cli/src/main.ts +++ b/apps/cli/src/main.ts @@ -18,6 +18,7 @@ import { import { CLI_DEFAULT_CHECKPOINT_CONFIG } from "./runtime/defaults"; import type { TuiStartupTarget } from "./tui/types"; import { filterChatModels } from "./utils/chat-models"; +import { registerClineClientIdentity } from "./utils/cline-client-identity"; import { getCliBuildInfo } from "./utils/common"; import { buildCliCompactionConfig, @@ -146,6 +147,7 @@ function startupTargetTakesPrecedenceOverMigrationNotice( } export async function runCli(): Promise { + registerClineClientIdentity("cline-cli"); installStreamErrorGuards(); autoUpdateOnStartup(); diff --git a/apps/cli/src/utils/cline-client-identity.ts b/apps/cli/src/utils/cline-client-identity.ts new file mode 100644 index 0000000000..0d9c9a5124 --- /dev/null +++ b/apps/cli/src/utils/cline-client-identity.ts @@ -0,0 +1,12 @@ +import { setClineClientIdentity } from "@cline/shared"; +import { getCliBuildInfo } from "./common"; + +export function registerClineClientIdentity(name: string): void { + const { version } = getCliBuildInfo(); + setClineClientIdentity({ + name, + version, + platform: "cli", + platformVersion: version, + }); +} diff --git a/apps/cli/src/utils/free-model-cost.test.ts b/apps/cli/src/utils/free-model-cost.test.ts index 08f033f5da..081951cb00 100644 --- a/apps/cli/src/utils/free-model-cost.test.ts +++ b/apps/cli/src/utils/free-model-cost.test.ts @@ -1,5 +1,7 @@ import type { AgentEvent } from "@cline/core"; +import { setClineClientIdentity } from "@cline/shared"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { registerClineClientIdentity } from "./cline-client-identity"; import { clearClineFreeModelCostCache, shouldZeroClineFreeModelCost, @@ -9,6 +11,7 @@ import { afterEach(() => { clearClineFreeModelCostCache(); + setClineClientIdentity(undefined); vi.unstubAllGlobals(); }); @@ -39,6 +42,29 @@ describe("shouldZeroClineFreeModelCost", () => { ); }); + it("identifies the CLI client on the free model request", async () => { + const fetchMock = vi.fn( + async (_input: Parameters[0], _init?: RequestInit) => { + return new Response(JSON.stringify({ free: [] }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }, + ); + vi.stubGlobal("fetch", fetchMock); + registerClineClientIdentity("cline-cli"); + + await shouldZeroClineFreeModelCost({ + providerId: "cline", + modelId: "deepseek/deepseek-v4-flash", + baseUrl: "https://cline.test/api/v1", + }); + + expect(fetchMock.mock.calls[0]?.[1]?.headers).toMatchObject({ + "X-CLIENT-TYPE": "cline-cli", + }); + }); + it("matches cline-free model ids from the free endpoint bucket exactly", async () => { vi.stubGlobal( "fetch", diff --git a/apps/cli/src/utils/free-model-cost.ts b/apps/cli/src/utils/free-model-cost.ts index 0ec93fb32a..5ca882ae88 100644 --- a/apps/cli/src/utils/free-model-cost.ts +++ b/apps/cli/src/utils/free-model-cost.ts @@ -1,4 +1,4 @@ -import type { AgentEvent } from "@cline/core"; +import { type AgentEvent, buildClineClientHeaders } from "@cline/core"; import { getClineEnvironmentConfig } from "@cline/shared"; import type { Config } from "./types"; @@ -37,6 +37,7 @@ async function fetchClineFreeModelIds( ); try { const response = await fetch(resolveClineRecommendedModelsUrl(baseUrl), { + headers: buildClineClientHeaders(), signal: controller.signal, }); if (!response.ok) return undefined; diff --git a/apps/examples/desktop-app/sidecar/index.ts b/apps/examples/desktop-app/sidecar/index.ts index bdbc54f5ad..87c9faaf0b 100644 --- a/apps/examples/desktop-app/sidecar/index.ts +++ b/apps/examples/desktop-app/sidecar/index.ts @@ -12,8 +12,10 @@ import { runRemoteHelperEntrypoint } from "@cline/core/remote/helper"; import { captureSdkError, disableCurrentDirectoryExecutableSearch, + setClineClientIdentity, } from "@cline/shared"; import { prewarmWorkspaceMetadata } from "./chat-session"; +import { DESKTOP_CLIENT_CONTEXT } from "./client-context"; import { configureConnectorCliLaunch } from "./connectors"; import { broadcastEvent, @@ -236,6 +238,8 @@ async function runEntrypoint(): Promise { runTelemetrySelfcheck(); return; } + setClineClientIdentity(DESKTOP_CLIENT_CONTEXT); + disableCurrentDirectoryExecutableSearch(); if (await runRemoteHelperEntrypoint()) { return; diff --git a/apps/vscode/src/common.ts b/apps/vscode/src/common.ts index fd9c4e73fc..8b86c8f372 100644 --- a/apps/vscode/src/common.ts +++ b/apps/vscode/src/common.ts @@ -12,6 +12,7 @@ import { StateManager } from "./core/storage/StateManager" import { AgentConfigLoader } from "./core/task/tools/subagent/AgentConfigLoader" import { ExtensionRegistryInfo } from "./registry" import { registerVsCodeLmHandler } from "./sdk/vscode-lm/register-vscode-lm" +import { registerClineClientIdentity } from "./services/ClineClientIdentity" import { ErrorService } from "./services/error" import { featureFlagsService } from "./services/feature-flags" import { getDistinctId } from "./services/logging/distinctId" @@ -63,6 +64,8 @@ export async function initialize(storageContext: StorageContext): Promise ({ + setClineClientIdentity: vi.fn(), +})) + +const hostState = vi.hoisted(() => ({ + hostVersion: {} as { + platform?: string + version?: string + clineType?: string + clineVersion?: string + }, + hostVersionError: undefined as Error | undefined, +})) + +vi.mock("@cline/shared", () => ({ + setClineClientIdentity: identityMocks.setClineClientIdentity, +})) + +vi.mock("@/hosts/host-provider", () => ({ + HostProvider: { + env: { + getHostVersion: vi.fn(async () => { + if (hostState.hostVersionError) { + throw hostState.hostVersionError + } + return hostState.hostVersion + }), + }, + }, +})) + +vi.mock("@/shared/services/Logger", () => ({ + Logger: { log: vi.fn(), error: vi.fn(), debug: vi.fn() }, +})) + +vi.mock("@/registry", () => ({ + ExtensionRegistryInfo: { version: "9.9.9" }, +})) + +import { registerClineClientIdentity } from "./ClineClientIdentity" + +describe("registerClineClientIdentity", () => { + beforeEach(() => { + identityMocks.setClineClientIdentity.mockClear() + hostState.hostVersionError = undefined + hostState.hostVersion = { + platform: "Visual Studio Code", + version: "1.103.0", + clineType: "VSCode Extension", + clineVersion: "3.40.0", + } + }) + + it("publishes the host-reported client identity", async () => { + await registerClineClientIdentity() + + expect(identityMocks.setClineClientIdentity).toHaveBeenCalledWith({ + name: "VSCode Extension", + version: "3.40.0", + platform: "Visual Studio Code", + platformVersion: "1.103.0", + }) + }) + + it("falls back to the extension's own identity when the host bridge fails", async () => { + hostState.hostVersionError = new Error("host bridge unavailable") + + await registerClineClientIdentity() + + expect(identityMocks.setClineClientIdentity).toHaveBeenCalledWith({ + name: "VSCode Extension", + version: "9.9.9", + }) + }) +}) diff --git a/apps/vscode/src/services/ClineClientIdentity.ts b/apps/vscode/src/services/ClineClientIdentity.ts new file mode 100644 index 0000000000..5f443fd310 --- /dev/null +++ b/apps/vscode/src/services/ClineClientIdentity.ts @@ -0,0 +1,24 @@ +import { setClineClientIdentity } from "@cline/shared" +import { HostProvider } from "@/hosts/host-provider" +import { ExtensionRegistryInfo } from "@/registry" +import { ClineClient } from "@/shared/cline" +import { EmptyRequest } from "@/shared/proto/cline/common" +import { Logger } from "@/shared/services/Logger" + +export async function registerClineClientIdentity(): Promise { + try { + const host = await HostProvider.env.getHostVersion(EmptyRequest.create({})) + setClineClientIdentity({ + name: host.clineType || ClineClient.VSCode, + version: host.clineVersion || ExtensionRegistryInfo.version, + platform: host.platform || undefined, + platformVersion: host.version || undefined, + }) + } catch (error) { + Logger.log("Failed to get IDE/platform info via HostBridge EnvService.getHostVersion", error) + setClineClientIdentity({ + name: ClineClient.VSCode, + version: ExtensionRegistryInfo.version, + }) + } +} diff --git a/apps/vscode/vitest.config.ts b/apps/vscode/vitest.config.ts index 8760e7ac77..9a213cf4b2 100644 --- a/apps/vscode/vitest.config.ts +++ b/apps/vscode/vitest.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ "src/core/controller/remoteConfig/**/*.test.ts", "src/core/controller/state/**/*.test.ts", "src/core/controller/slash/**/*.test.ts", + "src/services/ClineClientIdentity.test.ts", "src/services/mcp/__tests__/settingsLock.test.ts", "src/shared/model-catalog/provider-helpers.test.ts", "src/core/controller/models/__tests__/providerCatalogHandlers.test.ts", diff --git a/sdk/packages/core/src/index.ts b/sdk/packages/core/src/index.ts index 3472e52e47..0599b85c92 100644 --- a/sdk/packages/core/src/index.ts +++ b/sdk/packages/core/src/index.ts @@ -6,6 +6,7 @@ export * as Llms from "@cline/llms"; export { + buildClineClientHeaders, ClineFreeModelLimitError, ClineNotSubscribedError, ClineOrgIndividualInferenceSubscriptionError, diff --git a/sdk/packages/core/src/services/llms/cline-recommended-models.test.ts b/sdk/packages/core/src/services/llms/cline-recommended-models.test.ts index 772397609b..838efcca76 100644 --- a/sdk/packages/core/src/services/llms/cline-recommended-models.test.ts +++ b/sdk/packages/core/src/services/llms/cline-recommended-models.test.ts @@ -2,7 +2,8 @@ import { GENERATED_CLINE_RECOMMENDED_MODELS, getGeneratedProviderModels, } from "@cline/llms"; -import { describe, expect, it } from "vitest"; +import { setClineClientIdentity } from "@cline/shared"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { applyClineFeaturedModels, type ClineRecommendedModelsData, @@ -94,6 +95,10 @@ function namesOf(data: ClineRecommendedModelsData) { } describe("fetchClineRecommendedModels", () => { + afterEach(() => { + setClineClientIdentity(undefined); + }); + it("resolves display names from the models catalog", async () => { const data = await fetchClineRecommendedModels({ baseUrl: BASE_URL, @@ -118,6 +123,22 @@ describe("fetchClineRecommendedModels", () => { ]); }); + it("identifies the client on the feed request", async () => { + setClineClientIdentity({ name: "VSCode Extension", version: "3.40.0" }); + const fetchImpl = vi.fn(jsonResponse(ENDPOINT_PAYLOAD)); + + await fetchClineRecommendedModels({ + baseUrl: BASE_URL, + fetchImpl: fetchImpl as unknown as typeof fetch, + catalogLoader: async () => CATALOG, + }); + + expect(fetchImpl.mock.calls[0]?.[1]?.headers).toMatchObject({ + "X-CLIENT-TYPE": "VSCode Extension", + "X-CLIENT-VERSION": "3.40.0", + }); + }); + it("degrades to endpoint names and id slugs when the catalog is unavailable", async () => { const data = await fetchClineRecommendedModels({ baseUrl: BASE_URL, diff --git a/sdk/packages/core/src/services/llms/cline-recommended-models.ts b/sdk/packages/core/src/services/llms/cline-recommended-models.ts index b5abebf13c..a3256af544 100644 --- a/sdk/packages/core/src/services/llms/cline-recommended-models.ts +++ b/sdk/packages/core/src/services/llms/cline-recommended-models.ts @@ -1,4 +1,5 @@ import { + buildClineClientHeaders, GENERATED_CLINE_RECOMMENDED_MODELS, getGeneratedProviderModels, VERCEL_OPENROUTER_MODEL_ID_ALIAS_RULES, @@ -133,11 +134,12 @@ async function fetchWithTimeout( fetchImpl: typeof fetch, input: string, timeoutMs: number, + headers: Record, ): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { - return await fetchImpl(input, { signal: controller.signal }); + return await fetchImpl(input, { headers, signal: controller.signal }); } finally { clearTimeout(timer); } @@ -265,6 +267,7 @@ export async function fetchClineRecommendedModels( fetchImpl, `${base}/api/v1/ai/cline/recommended-models`, timeoutMs, + buildClineClientHeaders(), ); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const json: unknown = await resp.json(); diff --git a/sdk/packages/llms/src/catalog/catalog-cline-recommended.ts b/sdk/packages/llms/src/catalog/catalog-cline-recommended.ts index f6d99307b6..e92f2b8f01 100644 --- a/sdk/packages/llms/src/catalog/catalog-cline-recommended.ts +++ b/sdk/packages/llms/src/catalog/catalog-cline-recommended.ts @@ -1,4 +1,5 @@ import { getClineEnvironmentConfig } from "@cline/shared"; +import { buildClineClientHeaders } from "../providers/cline-client-headers"; import type { ModelInfo } from "./types"; export interface ClineRecommendedModelEntry { @@ -159,7 +160,7 @@ export async function fetchClineRecommendedModelsPayload( fetcher: typeof fetch = fetch, ): Promise { const url = `${getClineEnvironmentConfig().apiBaseUrl}/api/v1/ai/cline/recommended-models`; - const response = await fetcher(url); + const response = await fetcher(url, { headers: buildClineClientHeaders() }); if (!response.ok) { throw new Error( `Failed to load Cline recommended models from ${url}: HTTP ${response.status}`, diff --git a/sdk/packages/llms/src/catalog/catalog-live.test.ts b/sdk/packages/llms/src/catalog/catalog-live.test.ts index 242459154b..7fd357cd51 100644 --- a/sdk/packages/llms/src/catalog/catalog-live.test.ts +++ b/sdk/packages/llms/src/catalog/catalog-live.test.ts @@ -1143,6 +1143,9 @@ describe("models-dev-catalog", () => { expect(fetcher).toHaveBeenCalledWith("https://models.dev/api.json"); expect(fetcher).toHaveBeenCalledWith( "https://api.cline.bot/api/v1/ai/cline/recommended-models", + expect.objectContaining({ + headers: expect.objectContaining({ "X-CLIENT-TYPE": "cline-sdk" }), + }), ); expect(result.openrouter).toHaveProperty("vendor/live-base-model"); expect(result["cline-pass"]?.["cline-pass/live-base-model"]).toMatchObject({ diff --git a/sdk/packages/llms/src/index.browser.ts b/sdk/packages/llms/src/index.browser.ts index 5157a4c88f..4af5d532d4 100644 --- a/sdk/packages/llms/src/index.browser.ts +++ b/sdk/packages/llms/src/index.browser.ts @@ -37,6 +37,7 @@ export { resolveProviderUsageCostDisplay, shouldShowProviderUsageCost, } from "./providers/billing"; +export { buildClineClientHeaders } from "./providers/cline-client-headers"; export { type ProviderLocalCli, resolveProviderLocalCli, diff --git a/sdk/packages/llms/src/index.ts b/sdk/packages/llms/src/index.ts index a51d9af7ea..029d1cbe04 100644 --- a/sdk/packages/llms/src/index.ts +++ b/sdk/packages/llms/src/index.ts @@ -106,6 +106,7 @@ export { resolveProviderUsageCostDisplay, shouldShowProviderUsageCost, } from "./providers/billing"; +export { buildClineClientHeaders } from "./providers/cline-client-headers"; export type * from "./providers/gateway"; export { createGateway, DefaultGateway } from "./providers/gateway"; export { diff --git a/sdk/packages/llms/src/providers/cline-client-headers.test.ts b/sdk/packages/llms/src/providers/cline-client-headers.test.ts new file mode 100644 index 0000000000..1a0adc508c --- /dev/null +++ b/sdk/packages/llms/src/providers/cline-client-headers.test.ts @@ -0,0 +1,57 @@ +import { getClineClientIdentity, setClineClientIdentity } from "@cline/shared"; +import { afterEach, describe, expect, it } from "vitest"; +import { buildClineClientHeaders } from "./cline-client-headers"; + +afterEach(() => { + setClineClientIdentity(undefined); +}); + +describe("buildClineClientHeaders", () => { + it("falls back to the SDK client type when no identity is registered", () => { + expect(getClineClientIdentity()).toBeUndefined(); + expect(buildClineClientHeaders()).toEqual({ + "HTTP-Referer": "https://cline.bot", + "X-Title": "Cline", + "X-IS-MULTIROOT": "false", + "User-Agent": "Cline/unknown", + "X-CLIENT-TYPE": "cline-sdk", + "X-CLIENT-VERSION": "unknown", + "X-PLATFORM": "cline-sdk", + "X-PLATFORM-VERSION": "unknown", + }); + }); + + it("stamps the registered client identity", () => { + setClineClientIdentity({ + name: "VSCode Extension", + version: "3.40.0", + platform: "Visual Studio Code", + platformVersion: "1.100.0", + }); + + expect(buildClineClientHeaders()).toMatchObject({ + "User-Agent": "Cline/3.40.0", + "X-CLIENT-TYPE": "VSCode Extension", + "X-CLIENT-VERSION": "3.40.0", + "X-PLATFORM": "Visual Studio Code", + "X-PLATFORM-VERSION": "1.100.0", + }); + }); + + it("ignores blank identity fields and honors an explicit override", () => { + setClineClientIdentity({ name: " ", version: "9.9.9" }); + + expect(buildClineClientHeaders()).toMatchObject({ + "X-CLIENT-TYPE": "cline-sdk", + "X-CLIENT-VERSION": "9.9.9", + "X-PLATFORM": "cline-sdk", + "X-PLATFORM-VERSION": "9.9.9", + }); + expect( + buildClineClientHeaders({ name: "cline-cli", version: "1.2.3" }), + ).toMatchObject({ + "X-CLIENT-TYPE": "cline-cli", + "X-CLIENT-VERSION": "1.2.3", + }); + }); +}); diff --git a/sdk/packages/llms/src/providers/cline-client-headers.ts b/sdk/packages/llms/src/providers/cline-client-headers.ts new file mode 100644 index 0000000000..8c4ff7b707 --- /dev/null +++ b/sdk/packages/llms/src/providers/cline-client-headers.ts @@ -0,0 +1,28 @@ +import { + type ClineClientIdentity, + getClineClientIdentity, +} from "@cline/shared"; +import { DEFAULT_CLINE_REQUEST_HEADERS } from "./request-headers"; + +function trimNonEmpty(value: string | undefined): string | undefined { + const trimmed = value?.trim(); + return trimmed && trimmed.length > 0 ? trimmed : undefined; +} + +export function buildClineClientHeaders( + identity: ClineClientIdentity | undefined = getClineClientIdentity(), +): Record { + const clientType = + trimNonEmpty(identity?.name) ?? + DEFAULT_CLINE_REQUEST_HEADERS["X-CLIENT-TYPE"]; + const clientVersion = trimNonEmpty(identity?.version) ?? "unknown"; + return { + ...DEFAULT_CLINE_REQUEST_HEADERS, + "User-Agent": `Cline/${clientVersion}`, + "X-CLIENT-TYPE": clientType, + "X-CLIENT-VERSION": clientVersion, + "X-PLATFORM": trimNonEmpty(identity?.platform) ?? clientType, + "X-PLATFORM-VERSION": + trimNonEmpty(identity?.platformVersion) ?? clientVersion, + }; +} diff --git a/sdk/packages/llms/src/providers/request-headers.ts b/sdk/packages/llms/src/providers/request-headers.ts index 6a892c1fa7..f176abfb46 100644 --- a/sdk/packages/llms/src/providers/request-headers.ts +++ b/sdk/packages/llms/src/providers/request-headers.ts @@ -32,7 +32,7 @@ export interface ResolveProviderRequestHeadersInput { headers?: ProviderRequestHeaderLayers; } -const DEFAULT_CLINE_REQUEST_HEADERS: Record = { +export const DEFAULT_CLINE_REQUEST_HEADERS: Record = { "HTTP-Referer": "https://cline.bot", "X-Title": "Cline", "X-IS-MULTIROOT": "false", diff --git a/sdk/packages/shared/src/index.browser.ts b/sdk/packages/shared/src/index.browser.ts index 04afbb5d2a..4e40120716 100644 --- a/sdk/packages/shared/src/index.browser.ts +++ b/sdk/packages/shared/src/index.browser.ts @@ -422,6 +422,11 @@ export { TEAM_LIFECYCLE_EVENT_TYPE, TEAM_PROGRESS_EVENT_TYPE, } from "./rpc/team-progress"; +export type { ClineClientIdentity } from "./runtime/cline-client-identity"; +export { + getClineClientIdentity, + setClineClientIdentity, +} from "./runtime/cline-client-identity"; export type { ClineEnvironment, ClineEnvironmentConfig, diff --git a/sdk/packages/shared/src/index.ts b/sdk/packages/shared/src/index.ts index f6151d433f..913bcdca80 100644 --- a/sdk/packages/shared/src/index.ts +++ b/sdk/packages/shared/src/index.ts @@ -484,6 +484,11 @@ export { resolveClineBuildEnv, withResolvedClineBuildEnv, } from "./runtime/build-env"; +export type { ClineClientIdentity } from "./runtime/cline-client-identity"; +export { + getClineClientIdentity, + setClineClientIdentity, +} from "./runtime/cline-client-identity"; export type { ClineEnvironment, ClineEnvironmentConfig, @@ -523,6 +528,7 @@ export type { CaptureAgentUnexpectedReasoningTokensInput, CaptureSdkErrorInput, CaptureTaskLifecycleEventInput, + CoreSpawnReason, ITelemetryService, OpenTelemetryClientConfig, SdkTelemetryErrorComponent, @@ -533,11 +539,11 @@ export type { TelemetryPrimitive, TelemetryProperties, TelemetryValue, - CoreSpawnReason, } from "./services/telemetry"; export { AGENT_UNEXPECTED_REASONING_TOKENS_EVENT, buildSdkErrorProperties, + CORE_SPAWN_REASONS, captureAgentUnexpectedReasoningTokens, captureSdkError, captureTaskLifecycleEvent, @@ -552,7 +558,6 @@ export { TASK_PROVIDER_REQUEST_STARTED_EVENT, TASK_PROVIDER_STREAM_FAILED_EVENT, TASK_PROVIDER_STREAM_STARTED_EVENT, - CORE_SPAWN_REASONS, } from "./services/telemetry"; export type { ClineTelemetryServiceConfig } from "./services/telemetry-config"; export { diff --git a/sdk/packages/shared/src/runtime/cline-client-identity.ts b/sdk/packages/shared/src/runtime/cline-client-identity.ts new file mode 100644 index 0000000000..f7e97ec6e8 --- /dev/null +++ b/sdk/packages/shared/src/runtime/cline-client-identity.ts @@ -0,0 +1,24 @@ +export interface ClineClientIdentity { + name?: string; + version?: string; + platform?: string; + platformVersion?: string; +} + +interface ClineClientIdentityGlobal { + __clineClientIdentity?: ClineClientIdentity; +} + +function identityHolder(): ClineClientIdentityGlobal { + return globalThis as ClineClientIdentityGlobal; +} + +export function setClineClientIdentity( + identity: ClineClientIdentity | undefined, +): void { + identityHolder().__clineClientIdentity = identity; +} + +export function getClineClientIdentity(): ClineClientIdentity | undefined { + return identityHolder().__clineClientIdentity; +}