mirror of
https://github.com/cline/cline.git
synced 2026-09-01 15:11:04 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f2be67c79 |
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"source": "cline-extension-remote-config",
|
||||
"version": "v1",
|
||||
"remoteConfig": {
|
||||
"version": "v1",
|
||||
"providerSettings": {},
|
||||
"telemetryEnabled": true,
|
||||
"kanbanEnabled": true,
|
||||
"mcpMarketplaceEnabled": true,
|
||||
"yoloModeAllowed": true,
|
||||
"openTelemetryEnabled": false,
|
||||
"openTelemetryOtlpInsecure": false,
|
||||
"enterpriseTelemetry": {
|
||||
"promptUploading": {
|
||||
"enabled": true,
|
||||
"type": "s3_access_keys",
|
||||
"s3AccessSettings": {
|
||||
"bucket": "cline-tasks",
|
||||
"accessKeyId": "AKIA44Y6CGDEKMATQQE2",
|
||||
"secretAccessKey": "sbQZ9qN5DdgIDL501gsK0ErV6+/ac8Yja27SbyMa",
|
||||
"region": "us-west-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"managedInstructions": [],
|
||||
"metadata": {
|
||||
"organizationId": "org-01JZPE4CHW5XTP73W95762121Y",
|
||||
"paths": {
|
||||
"pluginName": "remote-config",
|
||||
"pluginPath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config",
|
||||
"workflowsPath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config/workflows",
|
||||
"skillsPath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config/skills",
|
||||
"bundleCachePath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config/cache/bundle.json",
|
||||
"manifestPath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config/managed.json",
|
||||
"rulesFilePath": "/Users/beatrix/dev/cline/sdk/.cline/remote-config/rules.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"source": "cline-extension-remote-config",
|
||||
"version": "v1",
|
||||
"files": []
|
||||
}
|
||||
@@ -244,6 +244,7 @@ Design implication:
|
||||
- transport-specific translation belongs inside concrete hosts, not in top-level orchestration
|
||||
- `RuntimeHost` inputs stay transport-safe, while `ClineCore.start(...)` is the app-facing facade that normalizes broad local config before delegation
|
||||
- `RuntimeSessionConfig` is transport-neutral across local, shared hub, and remote hub modes; host-local bootstrap concerns stay under `localRuntime`
|
||||
- Per-turn connection updates are carried on `RuntimeHost.runTurn(...)` as transport-safe data. Local execution applies them before the turn starts, forwards the resolved connection defaults to delegated agents and teammates, and persists provider/model changes back to the session row and manifest so history reflects the latest active model.
|
||||
- client-local runtime behaviors that must survive hub mode, such as `defaultToolExecutors`, are attached at session start and proxied through hub capability requests instead of changing host selection
|
||||
- pending prompt list/update/delete are exposed through the grouped
|
||||
`ClineCore.pendingPrompts` service. Usage summary lookup and active-session
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { DialogActions, DialogId } from "@opentui-ui/dialog/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withShownDialog } from "./loading-dialog-lifecycle";
|
||||
|
||||
type LoadingDialogCall =
|
||||
|
||||
@@ -158,42 +158,48 @@ async function runProviderChange(
|
||||
}
|
||||
if (!saved) return false;
|
||||
}
|
||||
await withLoadingDialog(dialog, `Loading ${displayName} models...`, async () => {
|
||||
await refreshProviderModelsFromSource(manager, newProviderId).catch(() => {});
|
||||
const newSettings = manager.getProviderSettings(newProviderId);
|
||||
const newApiKey =
|
||||
getPersistedProviderApiKey(newProviderId, newSettings) ?? "";
|
||||
await withLoadingDialog(
|
||||
dialog,
|
||||
`Loading ${displayName} models...`,
|
||||
async () => {
|
||||
await refreshProviderModelsFromSource(manager, newProviderId).catch(
|
||||
() => {},
|
||||
);
|
||||
const newSettings = manager.getProviderSettings(newProviderId);
|
||||
const newApiKey =
|
||||
getPersistedProviderApiKey(newProviderId, newSettings) ?? "";
|
||||
|
||||
manager.saveProviderSettings(
|
||||
{
|
||||
...(newSettings ?? {}),
|
||||
provider: newProviderId,
|
||||
},
|
||||
{ setLastUsed: true },
|
||||
);
|
||||
manager.saveProviderSettings(
|
||||
{
|
||||
...(newSettings ?? {}),
|
||||
provider: newProviderId,
|
||||
},
|
||||
{ setLastUsed: true },
|
||||
);
|
||||
|
||||
config.providerId = newProviderId;
|
||||
config.apiKey = newApiKey;
|
||||
config.providerId = newProviderId;
|
||||
config.apiKey = newApiKey;
|
||||
|
||||
const resolved = await resolveProviderConfig(
|
||||
newProviderId,
|
||||
{
|
||||
loadLatestOnInit: true,
|
||||
loadPrivateOnAuth: true,
|
||||
failOnError: false,
|
||||
},
|
||||
manager.getProviderConfig(newProviderId, { includeKnownModels: false }),
|
||||
);
|
||||
config.knownModels = resolved?.knownModels;
|
||||
const modelIds = Object.keys(resolved?.knownModels ?? {});
|
||||
if (newSettings?.model) {
|
||||
config.modelId = newSettings.model;
|
||||
} else if (modelIds[0]) {
|
||||
config.modelId = modelIds[0];
|
||||
}
|
||||
const resolved = await resolveProviderConfig(
|
||||
newProviderId,
|
||||
{
|
||||
loadLatestOnInit: true,
|
||||
loadPrivateOnAuth: true,
|
||||
failOnError: false,
|
||||
},
|
||||
manager.getProviderConfig(newProviderId, { includeKnownModels: false }),
|
||||
);
|
||||
config.knownModels = resolved?.knownModels;
|
||||
const modelIds = Object.keys(resolved?.knownModels ?? {});
|
||||
if (newSettings?.model) {
|
||||
config.modelId = newSettings.model;
|
||||
} else if (modelIds[0]) {
|
||||
config.modelId = modelIds[0];
|
||||
}
|
||||
|
||||
await onModelChange();
|
||||
});
|
||||
await onModelChange();
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -176,11 +176,11 @@ export function getModeInputPlaceholder(
|
||||
}
|
||||
|
||||
function srgbToLinear(c: number): number {
|
||||
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
||||
}
|
||||
|
||||
function linearToSrgb(c: number): number {
|
||||
const v = c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
||||
const v = c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055;
|
||||
return Math.max(0, Math.min(1, v));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
ChatMessageList,
|
||||
type TranscriptScrollHandle,
|
||||
} from "../components/chat-message-list";
|
||||
import { InputBar, type TextareaHandle } from "../components/input-bar";
|
||||
import { InlineToolResponse } from "../components/inline-tool-response";
|
||||
import { InputBar, type TextareaHandle } from "../components/input-bar";
|
||||
import { QueuedPrompts } from "../components/queued-prompts";
|
||||
import {
|
||||
resolveModelDisplayName,
|
||||
|
||||
@@ -28,13 +28,13 @@ import {
|
||||
useSearchableList,
|
||||
} from "../../components/searchable-list";
|
||||
import { palette } from "../../palette";
|
||||
import { getProviderSection } from "../../utils/provider-sections";
|
||||
import {
|
||||
getDefaultAwsRegion,
|
||||
type ProviderConfigValues,
|
||||
resolveProviderConfigAwsRegion,
|
||||
updateProviderConfigValue,
|
||||
type ProviderConfigValues,
|
||||
} from "../../utils/provider-config-values";
|
||||
import { getProviderSection } from "../../utils/provider-sections";
|
||||
import {
|
||||
isOnboardingOAuthProviderId,
|
||||
type OnboardingOAuthProviderId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Agent } from "@cline/sdk";
|
||||
import { createServer } from "node:http";
|
||||
import { Agent } from "@cline/sdk";
|
||||
|
||||
const PORT = Number(process.env.PORT || 3456);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
readHubDiscovery,
|
||||
rememberRecoverableLocalHubUrl,
|
||||
resolveSharedHubOwnerContext,
|
||||
type SendSessionConnectionUpdate,
|
||||
type ToolPolicy,
|
||||
} from "@cline/core";
|
||||
import {
|
||||
@@ -1022,13 +1023,14 @@ class CoreChatWebviewController implements vscode.Disposable {
|
||||
|
||||
this.sending = true;
|
||||
try {
|
||||
await this.ensureSession(config);
|
||||
const startConfig = await this.ensureSession(config);
|
||||
const host = await this.getSessionHost();
|
||||
const activeSessionId = this.sessionId as string;
|
||||
await host.send({
|
||||
sessionId: activeSessionId,
|
||||
prompt: trimmedPrompt,
|
||||
userImages: attachments?.userImages,
|
||||
connection: buildConnectionUpdate(startConfig),
|
||||
});
|
||||
await this.refreshSessions();
|
||||
} catch (error) {
|
||||
@@ -1147,6 +1149,12 @@ class CoreChatWebviewController implements vscode.Disposable {
|
||||
|
||||
if (this.sessionId && this.startConfig) {
|
||||
if (areStartConfigsEqual(this.startConfig, startConfig)) {
|
||||
this.startConfig = {
|
||||
...this.startConfig,
|
||||
providerId: startConfig.providerId,
|
||||
modelId: startConfig.modelId,
|
||||
thinking: startConfig.thinking,
|
||||
};
|
||||
return this.startConfig;
|
||||
}
|
||||
await this.stopExistingSession();
|
||||
@@ -1537,13 +1545,10 @@ class CoreChatWebviewController implements vscode.Disposable {
|
||||
*/
|
||||
function areStartConfigsEqual(a: StartConfig, b: StartConfig): boolean {
|
||||
return (
|
||||
a.providerId === b.providerId &&
|
||||
a.modelId === b.modelId &&
|
||||
a.cwd === b.cwd &&
|
||||
a.workspaceRoot === b.workspaceRoot &&
|
||||
a.systemPrompt === b.systemPrompt &&
|
||||
a.maxIterations === b.maxIterations &&
|
||||
a.thinking === b.thinking &&
|
||||
a.enableTools === b.enableTools &&
|
||||
a.enableSpawnAgent === b.enableSpawnAgent &&
|
||||
a.enableAgentTeams === b.enableAgentTeams &&
|
||||
@@ -1559,6 +1564,16 @@ function areStartConfigsEqual(a: StartConfig, b: StartConfig): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function buildConnectionUpdate(
|
||||
config: StartConfig,
|
||||
): SendSessionConnectionUpdate {
|
||||
return {
|
||||
providerId: config.providerId,
|
||||
modelId: config.modelId,
|
||||
thinking: config.thinking === true,
|
||||
};
|
||||
}
|
||||
|
||||
function createToolPolicies(config: StartConfig): Record<string, ToolPolicy> {
|
||||
return {
|
||||
"*": { autoApprove: config.autoApproveTools !== false },
|
||||
|
||||
@@ -146,6 +146,7 @@ export function createClineCoreAutomationRuntimeHandlers(
|
||||
userImages: request.attachments?.userImages,
|
||||
userFiles: request.attachments?.userFiles?.map((file) => file.content),
|
||||
delivery: request.delivery,
|
||||
connection: request.connection,
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("ClineCore automation runtime returned no result");
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ToolResultContent } from "@cline/llms";
|
||||
import { estimateTokens, type MessageWithMetadata } from "@cline/shared";
|
||||
|
||||
export { estimateTokens };
|
||||
|
||||
import type {
|
||||
CoreCompactionContext,
|
||||
CoreCompactionSummarizerConfig,
|
||||
|
||||
@@ -921,7 +921,20 @@ export class AgentTeamsRuntime {
|
||||
}
|
||||
|
||||
updateTeammateConnections(
|
||||
overrides: Partial<Pick<AgentConfig, "apiKey" | "baseUrl" | "headers">>,
|
||||
overrides: Partial<
|
||||
Pick<
|
||||
AgentConfig,
|
||||
| "providerId"
|
||||
| "modelId"
|
||||
| "apiKey"
|
||||
| "baseUrl"
|
||||
| "headers"
|
||||
| "providerConfig"
|
||||
| "reasoningEffort"
|
||||
| "thinking"
|
||||
| "thinkingBudgetTokens"
|
||||
>
|
||||
>,
|
||||
): void {
|
||||
for (const member of this.members.values()) {
|
||||
if (member.role !== "teammate" || !member.agent) {
|
||||
|
||||
@@ -375,6 +375,7 @@ export class HubSessionClient {
|
||||
attachments: request.attachments,
|
||||
delivery: request.delivery,
|
||||
timeoutSeconds: request.config.timeoutSeconds,
|
||||
connection: request.connection,
|
||||
},
|
||||
sessionId,
|
||||
options,
|
||||
|
||||
@@ -124,6 +124,7 @@ export function createLocalHubScheduleRuntimeHandlers(
|
||||
prompt: request.prompt,
|
||||
userImages: request.attachments?.userImages,
|
||||
userFiles: request.attachments?.userFiles?.map((file) => file.content),
|
||||
connection: request.connection,
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("local hub schedule runtime returned no turn result");
|
||||
|
||||
@@ -226,6 +226,8 @@ describe("HubRuntimeHost", () => {
|
||||
mode: "plan",
|
||||
attachments: undefined,
|
||||
delivery: "queue",
|
||||
timeoutMs: undefined,
|
||||
connection: undefined,
|
||||
},
|
||||
"sess-1",
|
||||
{ timeoutMs: null },
|
||||
@@ -1294,6 +1296,8 @@ describe("HubRuntimeHost", () => {
|
||||
userImages: ["data:image/png;base64,aGVsbG8="],
|
||||
},
|
||||
delivery: undefined,
|
||||
timeoutMs: undefined,
|
||||
connection: undefined,
|
||||
},
|
||||
"sess-1",
|
||||
{ timeoutMs: null },
|
||||
@@ -1323,6 +1327,43 @@ describe("HubRuntimeHost", () => {
|
||||
userFiles: [filePath],
|
||||
},
|
||||
delivery: undefined,
|
||||
timeoutMs: undefined,
|
||||
connection: undefined,
|
||||
},
|
||||
"sess-1",
|
||||
{ timeoutMs: null },
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards per-turn connection updates when sending a run", async () => {
|
||||
commandMock.mockResolvedValue({ ok: true, payload: { result: undefined } });
|
||||
|
||||
const { HubRuntimeHost } = await import("./hub-runtime-host");
|
||||
const host = new HubRuntimeHost({ url: "ws://127.0.0.1:25463/hub" });
|
||||
|
||||
await host.runTurn({
|
||||
sessionId: "sess-1",
|
||||
prompt: "Use a different model",
|
||||
connection: {
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
thinking: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(commandMock).toHaveBeenCalledWith(
|
||||
"run.start",
|
||||
{
|
||||
sessionId: "sess-1",
|
||||
input: "Use a different model",
|
||||
attachments: undefined,
|
||||
delivery: undefined,
|
||||
timeoutMs: undefined,
|
||||
connection: {
|
||||
providerId: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
thinking: true,
|
||||
},
|
||||
},
|
||||
"sess-1",
|
||||
{ timeoutMs: null },
|
||||
|
||||
@@ -1068,6 +1068,7 @@ export class HubRuntimeHost implements RuntimeHost {
|
||||
: undefined,
|
||||
delivery: input.delivery,
|
||||
timeoutMs: input.timeoutMs,
|
||||
connection: input.connection,
|
||||
},
|
||||
input.sessionId,
|
||||
{ timeoutMs: null },
|
||||
|
||||
@@ -908,6 +908,83 @@ describe("HubServerTransport boundaries", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards per-turn connection updates to the session host", async () => {
|
||||
const runTurn = vi.fn().mockResolvedValue(undefined);
|
||||
const transport = createTransport({
|
||||
sessionHost: {
|
||||
subscribe: vi.fn(),
|
||||
startSession: vi.fn(),
|
||||
stopSession: vi.fn(),
|
||||
runTurn,
|
||||
abort: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
getSession: vi.fn(),
|
||||
listSessions: vi.fn(),
|
||||
deleteSession: vi.fn(),
|
||||
updateSession: vi.fn(),
|
||||
dispatchHookEvent: vi.fn(),
|
||||
} as never,
|
||||
});
|
||||
|
||||
const reply = await (
|
||||
transport as unknown as {
|
||||
handleCommand: (envelope: {
|
||||
version: "v1";
|
||||
requestId: string;
|
||||
command: "run.start";
|
||||
sessionId: string;
|
||||
payload: {
|
||||
sessionId: string;
|
||||
prompt: string;
|
||||
connection: Record<string, unknown>;
|
||||
};
|
||||
}) => Promise<{ ok: boolean }>;
|
||||
}
|
||||
).handleCommand({
|
||||
version: "v1",
|
||||
requestId: "req-connection",
|
||||
command: "run.start",
|
||||
sessionId: "session-1",
|
||||
payload: {
|
||||
sessionId: "session-1",
|
||||
prompt: "Use a different model",
|
||||
connection: {
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
apiKey: "turn-key",
|
||||
baseUrl: "https://example.test/v1",
|
||||
headers: { "x-turn": "true", ignored: 1 },
|
||||
providerConfig: { providerId: "openai-native", modelId: "gpt-5.1" },
|
||||
reasoningEffort: "high",
|
||||
thinking: true,
|
||||
thinkingBudgetTokens: 2048,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(reply.ok).toBe(true);
|
||||
expect(runTurn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionId: "session-1",
|
||||
prompt: "Use a different model",
|
||||
connection: {
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
apiKey: "turn-key",
|
||||
baseUrl: "https://example.test/v1",
|
||||
headers: { "x-turn": "true" },
|
||||
providerConfig: {
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
},
|
||||
reasoningEffort: "high",
|
||||
thinking: true,
|
||||
thinkingBudgetTokens: 2048,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("publishes result error text on failed run events", async () => {
|
||||
const runTurn = vi.fn().mockResolvedValue({
|
||||
text: "Provider rejected the request",
|
||||
|
||||
@@ -5,7 +5,10 @@ import type {
|
||||
HubReplyEnvelope,
|
||||
} from "@cline/shared";
|
||||
import { parseHookEventPayload } from "../../../hooks";
|
||||
import type { SendSessionInput } from "../../../runtime/host/runtime-host";
|
||||
import type {
|
||||
SendSessionConnectionUpdate,
|
||||
SendSessionInput,
|
||||
} from "../../../runtime/host/runtime-host";
|
||||
import { logHubMessage } from "../hub-server-logging";
|
||||
import { cancelPendingApprovals } from "./approval-handlers";
|
||||
import { cancelPendingCapabilityRequests } from "./capability-handlers";
|
||||
@@ -64,6 +67,57 @@ function parseRunTimeoutMs(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseStringHeaders(
|
||||
value: unknown,
|
||||
): Record<string, string> | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const entries = Object.entries(value as Record<string, unknown>).filter(
|
||||
(entry): entry is [string, string] => typeof entry[1] === "string",
|
||||
);
|
||||
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
||||
}
|
||||
|
||||
function parseConnectionUpdate(
|
||||
value: unknown,
|
||||
): SendSessionConnectionUpdate | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const input = value as Record<string, unknown>;
|
||||
const connection: SendSessionConnectionUpdate = {};
|
||||
if (typeof input.providerId === "string") {
|
||||
connection.providerId = input.providerId;
|
||||
}
|
||||
if (typeof input.modelId === "string") {
|
||||
connection.modelId = input.modelId;
|
||||
}
|
||||
if (typeof input.apiKey === "string") {
|
||||
connection.apiKey = input.apiKey;
|
||||
}
|
||||
if (typeof input.baseUrl === "string") {
|
||||
connection.baseUrl = input.baseUrl;
|
||||
}
|
||||
const headers = parseStringHeaders(input.headers);
|
||||
if (headers) {
|
||||
connection.headers = headers;
|
||||
}
|
||||
if ("providerConfig" in input) {
|
||||
connection.providerConfig = input.providerConfig;
|
||||
}
|
||||
if (typeof input.reasoningEffort === "string") {
|
||||
connection.reasoningEffort = input.reasoningEffort as never;
|
||||
}
|
||||
if (typeof input.thinking === "boolean") {
|
||||
connection.thinking = input.thinking;
|
||||
}
|
||||
if (typeof input.thinkingBudgetTokens === "number") {
|
||||
connection.thinkingBudgetTokens = input.thinkingBudgetTokens;
|
||||
}
|
||||
return Object.keys(connection).length > 0 ? connection : undefined;
|
||||
}
|
||||
|
||||
async function runTurnWithRuntimeHealth(
|
||||
ctx: HubTransportContext,
|
||||
envelope: HubCommandEnvelope,
|
||||
@@ -212,6 +266,7 @@ export async function handleSessionInput(
|
||||
: undefined,
|
||||
userFiles,
|
||||
timeoutMs,
|
||||
connection: parseConnectionUpdate(payload.connection),
|
||||
},
|
||||
timeoutMs,
|
||||
);
|
||||
|
||||
@@ -378,6 +378,7 @@ export type {
|
||||
RuntimeHost as SessionHost,
|
||||
RuntimeHostMode,
|
||||
RuntimeHostSubscribeOptions,
|
||||
SendSessionConnectionUpdate,
|
||||
SendSessionInput,
|
||||
SessionAccumulatedUsage,
|
||||
SessionUsageSummary,
|
||||
|
||||
@@ -4055,6 +4055,117 @@ describe("LocalRuntimeHost", () => {
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("applies per-turn connection updates before executing and persists provider/model", async () => {
|
||||
const sessionId = "sess-turn-connection";
|
||||
const manifest = createManifest(sessionId);
|
||||
const sessionService = {
|
||||
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
||||
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
|
||||
manifestPath: "/tmp/manifest-turn-connection.json",
|
||||
messagesPath: "/tmp/messages-turn-connection.json",
|
||||
manifest,
|
||||
}),
|
||||
persistSessionMessages: vi.fn(),
|
||||
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
|
||||
updateSession: vi.fn().mockResolvedValue({ updated: true }),
|
||||
readSessionManifest: vi.fn().mockReturnValue(manifest),
|
||||
writeSessionManifest: vi.fn(),
|
||||
listSessions: vi.fn().mockResolvedValue([]),
|
||||
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
|
||||
};
|
||||
const updateConnectionDefaults = vi.fn();
|
||||
const updateConnection = vi.fn();
|
||||
const run = vi.fn().mockResolvedValue(createResult({ text: "first" }));
|
||||
const continueRun = vi
|
||||
.fn()
|
||||
.mockResolvedValue(createResult({ text: "second" }));
|
||||
const manager = new RuntimeHostUnderTest({
|
||||
distinctId,
|
||||
sessionService: sessionService as never,
|
||||
runtimeBuilder: {
|
||||
build: vi.fn().mockReturnValue({
|
||||
tools: [],
|
||||
delegatedAgentConfigProvider: {
|
||||
getRuntimeConfig: vi.fn(),
|
||||
getConnectionConfig: vi.fn(),
|
||||
updateConnectionDefaults,
|
||||
},
|
||||
shutdown: vi.fn(),
|
||||
}),
|
||||
},
|
||||
createAgent: () =>
|
||||
({
|
||||
run,
|
||||
continue: continueRun,
|
||||
abort: vi.fn(),
|
||||
subscribeEvents: vi.fn().mockReturnValue(() => {}),
|
||||
canStartRun: vi.fn().mockReturnValue(true),
|
||||
getAgentId: vi.fn().mockReturnValue("agent-root-1"),
|
||||
getConversationId: vi.fn().mockReturnValue("conv-root-1"),
|
||||
restore: vi.fn(),
|
||||
updateConnection,
|
||||
shutdown: vi.fn().mockResolvedValue(undefined),
|
||||
getMessages: vi.fn().mockReturnValue([]),
|
||||
messages: [],
|
||||
}) as never,
|
||||
});
|
||||
|
||||
await manager.startSession(
|
||||
normalizeStartInput({
|
||||
config: createConfig({ sessionId }),
|
||||
interactive: true,
|
||||
}),
|
||||
);
|
||||
await manager.runTurn({ sessionId, prompt: "first" });
|
||||
await manager.runTurn({
|
||||
sessionId,
|
||||
prompt: "second",
|
||||
connection: {
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
reasoningEffort: "high",
|
||||
thinking: true,
|
||||
thinkingBudgetTokens: 2048,
|
||||
},
|
||||
});
|
||||
|
||||
expect(continueRun).toHaveBeenCalledWith(
|
||||
'<user_input mode="act">second</user_input>',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(updateConnection).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
reasoningEffort: "high",
|
||||
thinking: true,
|
||||
thinkingBudgetTokens: 2048,
|
||||
}),
|
||||
);
|
||||
expect(updateConnectionDefaults).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerId: "openai-native",
|
||||
modelId: "gpt-5.1",
|
||||
reasoningEffort: "high",
|
||||
thinking: true,
|
||||
thinkingBudgetTokens: 2048,
|
||||
}),
|
||||
);
|
||||
expect(sessionService.updateSession).toHaveBeenCalledWith({
|
||||
sessionId,
|
||||
provider: "openai-native",
|
||||
model: "gpt-5.1",
|
||||
});
|
||||
expect(sessionService.writeSessionManifest).toHaveBeenCalledWith(
|
||||
"/tmp/manifest-turn-connection.json",
|
||||
expect.objectContaining({
|
||||
provider: "openai-native",
|
||||
model: "gpt-5.1",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("hydrates provider-specific config from provider settings", async () => {
|
||||
const sessionId = "sess-provider-config";
|
||||
const manifest = createManifest(sessionId);
|
||||
|
||||
@@ -20,7 +20,10 @@ import type { TeamEvent } from "../../extensions/tools/team";
|
||||
import type { HookEventPayload } from "../../hooks";
|
||||
import { buildTelemetryAgentIdentity } from "../../services/agent-events";
|
||||
import { resolveWorkspacePath } from "../../services/config";
|
||||
import { prepareLocalRuntimeBootstrap } from "../../services/local-runtime-bootstrap";
|
||||
import {
|
||||
buildProviderConfig,
|
||||
prepareLocalRuntimeBootstrap,
|
||||
} from "../../services/local-runtime-bootstrap";
|
||||
import { nowIso } from "../../services/session-artifacts";
|
||||
import {
|
||||
toSessionRecord,
|
||||
@@ -104,6 +107,7 @@ import type {
|
||||
RestoreSessionResult,
|
||||
RuntimeHost,
|
||||
RuntimeHostSubscribeOptions,
|
||||
SendSessionConnectionUpdate,
|
||||
SendSessionInput,
|
||||
SessionAccumulatedUsage,
|
||||
SessionUsageSummary,
|
||||
@@ -155,6 +159,13 @@ function parseAccumulatedUsage(
|
||||
};
|
||||
}
|
||||
|
||||
function hasConnectionUpdate(
|
||||
connection: SendSessionConnectionUpdate | undefined,
|
||||
): connection is SendSessionConnectionUpdate {
|
||||
if (!connection) return false;
|
||||
return Object.values(connection).some((value) => value !== undefined);
|
||||
}
|
||||
|
||||
function maxAccumulatedUsage(
|
||||
left: SessionAccumulatedUsage,
|
||||
right: SessionAccumulatedUsage,
|
||||
@@ -695,10 +706,12 @@ export class LocalRuntimeHost implements RuntimeHost {
|
||||
delivery,
|
||||
userImages: input.userImages,
|
||||
userFiles: input.userFiles,
|
||||
connection: input.connection,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
await this.applySessionConnectionUpdate(session, input.connection);
|
||||
const result = await this.executeTurn(session, {
|
||||
prompt: input.prompt,
|
||||
mode: input.mode,
|
||||
@@ -919,11 +932,7 @@ export class LocalRuntimeHost implements RuntimeHost {
|
||||
|
||||
async updateSessionModel(sessionId: string, modelId: string): Promise<void> {
|
||||
const session = this.getSessionOrThrow(sessionId);
|
||||
session.config.modelId = modelId;
|
||||
session.runtime.delegatedAgentConfigProvider?.updateConnectionDefaults({
|
||||
modelId,
|
||||
});
|
||||
session.agent.updateConnection({ modelId });
|
||||
await this.applySessionConnectionUpdate(session, { modelId });
|
||||
}
|
||||
|
||||
// Retained for unit tests that reach in via Reflect.
|
||||
@@ -943,6 +952,112 @@ export class LocalRuntimeHost implements RuntimeHost {
|
||||
|
||||
// ── Turn execution ──────────────────────────────────────────────────
|
||||
|
||||
private async applySessionConnectionUpdate(
|
||||
session: ActiveSession,
|
||||
connection: SendSessionConnectionUpdate | undefined,
|
||||
): Promise<void> {
|
||||
if (!hasConnectionUpdate(connection)) return;
|
||||
|
||||
const previousProviderId = session.config.providerId;
|
||||
const previousModelId = session.config.modelId;
|
||||
|
||||
if (connection.providerId !== undefined) {
|
||||
session.config.providerId = connection.providerId;
|
||||
}
|
||||
if (connection.modelId !== undefined) {
|
||||
session.config.modelId = connection.modelId;
|
||||
}
|
||||
if (connection.apiKey !== undefined) {
|
||||
session.config.apiKey = connection.apiKey;
|
||||
}
|
||||
if (connection.baseUrl !== undefined) {
|
||||
session.config.baseUrl = connection.baseUrl;
|
||||
}
|
||||
if (connection.headers !== undefined) {
|
||||
session.config.headers = connection.headers;
|
||||
}
|
||||
if (connection.providerConfig !== undefined) {
|
||||
session.config.providerConfig =
|
||||
connection.providerConfig as CoreSessionConfig["providerConfig"];
|
||||
}
|
||||
if (connection.reasoningEffort !== undefined) {
|
||||
session.config.reasoningEffort = connection.reasoningEffort;
|
||||
}
|
||||
if (connection.thinking !== undefined) {
|
||||
session.config.thinking = connection.thinking;
|
||||
}
|
||||
if (connection.thinkingBudgetTokens !== undefined) {
|
||||
session.config.thinkingBudgetTokens = connection.thinkingBudgetTokens;
|
||||
}
|
||||
|
||||
const providerConfig =
|
||||
(connection.providerConfig as CoreSessionConfig["providerConfig"]) ??
|
||||
buildProviderConfig(
|
||||
session.config,
|
||||
session.sessionId,
|
||||
this.providerSettingsManager,
|
||||
undefined,
|
||||
this.defaultFetch,
|
||||
);
|
||||
session.config.providerConfig = providerConfig;
|
||||
session.config.apiKey = connection.apiKey ?? providerConfig.apiKey;
|
||||
session.config.baseUrl = connection.baseUrl ?? providerConfig.baseUrl;
|
||||
session.config.headers = connection.headers ?? providerConfig.headers;
|
||||
|
||||
const agentConnection: SendSessionConnectionUpdate = {
|
||||
providerId: session.config.providerId,
|
||||
modelId: session.config.modelId,
|
||||
apiKey: session.config.apiKey,
|
||||
baseUrl: session.config.baseUrl,
|
||||
headers: session.config.headers,
|
||||
providerConfig,
|
||||
reasoningEffort:
|
||||
session.config.reasoningEffort ?? providerConfig.reasoningEffort,
|
||||
thinking: session.config.thinking ?? providerConfig.thinking,
|
||||
thinkingBudgetTokens:
|
||||
session.config.thinkingBudgetTokens ??
|
||||
providerConfig.thinkingBudgetTokens,
|
||||
};
|
||||
session.runtime.delegatedAgentConfigProvider?.updateConnectionDefaults(
|
||||
agentConnection,
|
||||
);
|
||||
session.agent.updateConnection(agentConnection);
|
||||
session.runtime.teamRuntime?.updateTeammateConnections(agentConnection);
|
||||
|
||||
const providerChanged = previousProviderId !== session.config.providerId;
|
||||
const modelChanged = previousModelId !== session.config.modelId;
|
||||
if (providerChanged || modelChanged) {
|
||||
await this.persistSessionConnection(session);
|
||||
}
|
||||
}
|
||||
|
||||
private async persistSessionConnection(
|
||||
session: ActiveSession,
|
||||
): Promise<void> {
|
||||
if (!session.artifacts) return;
|
||||
const result = await this.invoke<{ updated: boolean }>("updateSession", {
|
||||
sessionId: session.sessionId,
|
||||
provider: session.config.providerId,
|
||||
model: session.config.modelId,
|
||||
});
|
||||
if (!result.updated) return;
|
||||
const latestManifest =
|
||||
(await this.invokeOptionalValue<SessionManifest>(
|
||||
"readSessionManifest",
|
||||
session.sessionId,
|
||||
)) ?? session.artifacts.manifest;
|
||||
latestManifest.provider = session.config.providerId;
|
||||
latestManifest.model = session.config.modelId;
|
||||
session.artifacts.manifest = latestManifest;
|
||||
session.updatedAt = nowIso();
|
||||
await this.invoke<void>(
|
||||
"writeSessionManifest",
|
||||
session.artifacts.manifestPath,
|
||||
latestManifest,
|
||||
);
|
||||
await this.emitSessionSnapshot(session.sessionId);
|
||||
}
|
||||
|
||||
private async executeTurn(
|
||||
session: ActiveSession,
|
||||
input: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type * as LlmsProviders from "@cline/llms";
|
||||
import type {
|
||||
AgentConfig,
|
||||
AgentMode,
|
||||
AgentResult,
|
||||
RuntimeConfigExtensionKind,
|
||||
@@ -160,6 +161,18 @@ export interface StartSessionResult {
|
||||
result?: AgentResult;
|
||||
}
|
||||
|
||||
export interface SendSessionConnectionUpdate {
|
||||
providerId?: string;
|
||||
modelId?: string;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
providerConfig?: unknown;
|
||||
reasoningEffort?: AgentConfig["reasoningEffort"];
|
||||
thinking?: boolean;
|
||||
thinkingBudgetTokens?: number;
|
||||
}
|
||||
|
||||
export interface SendSessionInput {
|
||||
sessionId: string;
|
||||
prompt: string;
|
||||
@@ -168,6 +181,7 @@ export interface SendSessionInput {
|
||||
userFiles?: string[];
|
||||
delivery?: "queue" | "steer";
|
||||
timeoutMs?: number;
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
}
|
||||
|
||||
export interface SessionAccumulatedUsage {
|
||||
|
||||
@@ -456,15 +456,15 @@ export class SessionRuntime {
|
||||
if (overrides.providerId !== undefined)
|
||||
next.providerId = overrides.providerId;
|
||||
if (overrides.modelId !== undefined) next.modelId = overrides.modelId;
|
||||
if (overrides.apiKey !== undefined) next.apiKey = overrides.apiKey;
|
||||
if (overrides.baseUrl !== undefined) next.baseUrl = overrides.baseUrl;
|
||||
if (overrides.headers !== undefined) next.headers = overrides.headers;
|
||||
if (overrides.providerConfig !== undefined)
|
||||
if ("apiKey" in overrides) next.apiKey = overrides.apiKey;
|
||||
if ("baseUrl" in overrides) next.baseUrl = overrides.baseUrl;
|
||||
if ("headers" in overrides) next.headers = overrides.headers;
|
||||
if ("providerConfig" in overrides)
|
||||
next.providerConfig = overrides.providerConfig;
|
||||
if (overrides.reasoningEffort !== undefined)
|
||||
if ("reasoningEffort" in overrides)
|
||||
next.reasoningEffort = overrides.reasoningEffort;
|
||||
if (overrides.thinking !== undefined) next.thinking = overrides.thinking;
|
||||
if (overrides.thinkingBudgetTokens !== undefined)
|
||||
if ("thinking" in overrides) next.thinking = overrides.thinking;
|
||||
if ("thinkingBudgetTokens" in overrides)
|
||||
next.thinkingBudgetTokens = overrides.thinkingBudgetTokens;
|
||||
this.config = next;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
PendingPromptMutationResult,
|
||||
PendingPromptsDeleteInput,
|
||||
PendingPromptsUpdateInput,
|
||||
SendSessionConnectionUpdate,
|
||||
} from "../host/runtime-host";
|
||||
|
||||
export type PendingPromptDelivery = "queue" | "steer";
|
||||
@@ -20,6 +21,7 @@ export interface PendingPromptEntry {
|
||||
delivery: PendingPromptDelivery;
|
||||
userImages?: string[];
|
||||
userFiles?: string[];
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
}
|
||||
|
||||
export interface PendingPromptQueueState {
|
||||
@@ -35,6 +37,7 @@ export interface PendingPromptsControllerDeps {
|
||||
mode?: AgentMode;
|
||||
userImages?: string[];
|
||||
userFiles?: string[];
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
}): Promise<unknown>;
|
||||
}
|
||||
|
||||
@@ -44,6 +47,7 @@ export interface PendingPromptEnqueueInput {
|
||||
delivery: PendingPromptDelivery;
|
||||
userImages?: string[];
|
||||
userFiles?: string[];
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
}
|
||||
|
||||
export interface PendingPromptConsumeResult {
|
||||
@@ -138,7 +142,7 @@ export class PendingPromptService {
|
||||
state: PendingPromptQueueState,
|
||||
input: PendingPromptEnqueueInput,
|
||||
): SessionPendingPrompt[] {
|
||||
const { prompt, mode, delivery, userImages, userFiles } = input;
|
||||
const { prompt, mode, delivery, userImages, userFiles, connection } = input;
|
||||
const existingIndex = state.pendingPrompts.findIndex(
|
||||
(queued) => queued.prompt === prompt,
|
||||
);
|
||||
@@ -150,6 +154,7 @@ export class PendingPromptService {
|
||||
mode: mode ?? existing.mode,
|
||||
userImages: userImages ?? existing.userImages,
|
||||
userFiles: userFiles ?? existing.userFiles,
|
||||
connection: connection ?? existing.connection,
|
||||
};
|
||||
if (delivery === "steer" || existing.delivery === "steer") {
|
||||
state.pendingPrompts.unshift({ ...next, delivery: "steer" });
|
||||
@@ -164,6 +169,7 @@ export class PendingPromptService {
|
||||
delivery,
|
||||
userImages,
|
||||
userFiles,
|
||||
connection,
|
||||
};
|
||||
if (delivery === "steer") {
|
||||
state.pendingPrompts.unshift(newEntry);
|
||||
@@ -243,6 +249,7 @@ export class PendingPromptsController {
|
||||
delivery: "queue" | "steer";
|
||||
userImages?: string[];
|
||||
userFiles?: string[];
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
},
|
||||
): void {
|
||||
const session = this.deps.getSession(sessionId);
|
||||
@@ -314,6 +321,7 @@ export class PendingPromptsController {
|
||||
...(next.mode ? { mode: next.mode } : {}),
|
||||
userImages: next.userImages,
|
||||
userFiles: next.userFiles,
|
||||
connection: next.connection,
|
||||
});
|
||||
} catch {
|
||||
continueDrain = false;
|
||||
|
||||
@@ -171,7 +171,7 @@ function deriveOpenAICodexAccountId(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildProviderConfig(
|
||||
export function buildProviderConfig(
|
||||
config: CoreSessionConfig,
|
||||
sessionId: string,
|
||||
providerSettingsManager: ProviderSettingsManager,
|
||||
|
||||
@@ -169,6 +169,14 @@ class FileSessionPersistenceAdapter implements SessionPersistenceAdapter {
|
||||
input.exitCode !== undefined
|
||||
? input.exitCode
|
||||
: (existing.exitCode ?? null),
|
||||
provider:
|
||||
input.provider !== undefined
|
||||
? (input.provider ?? existing.provider)
|
||||
: existing.provider,
|
||||
model:
|
||||
input.model !== undefined
|
||||
? (input.model ?? existing.model)
|
||||
: existing.model,
|
||||
prompt:
|
||||
input.prompt !== undefined ? input.prompt : (existing.prompt ?? null),
|
||||
metadata:
|
||||
|
||||
@@ -347,6 +347,50 @@ describe("UnifiedSessionPersistenceService", () => {
|
||||
expect(manifest.metadata).toMatchObject({ title: "first user message" });
|
||||
});
|
||||
|
||||
it("updates provider and model in the session row and manifest", async () => {
|
||||
const sessionsDir = mkdtempSync(join(tmpdir(), "update-model-sessions-"));
|
||||
tempDirs.push(sessionsDir);
|
||||
|
||||
const service = new FileSessionService(sessionsDir);
|
||||
const sessionId = "model-update-session";
|
||||
const artifacts = await service.createRootSessionWithArtifacts({
|
||||
sessionId,
|
||||
source: SessionSource.CLI,
|
||||
pid: process.pid,
|
||||
interactive: true,
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-6",
|
||||
cwd: "/tmp/project",
|
||||
workspaceRoot: "/tmp/project",
|
||||
enableTools: true,
|
||||
enableSpawn: true,
|
||||
enableTeams: false,
|
||||
prompt: "first user message",
|
||||
startedAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.updateSession({
|
||||
sessionId,
|
||||
provider: "openai-native",
|
||||
model: "gpt-5.1",
|
||||
}),
|
||||
).resolves.toEqual({ updated: true });
|
||||
|
||||
const [row] = await service.listSessions(10);
|
||||
expect(row).toMatchObject({
|
||||
provider: "openai-native",
|
||||
model: "gpt-5.1",
|
||||
});
|
||||
const manifest = JSON.parse(
|
||||
readFileSync(artifacts.manifestPath, "utf8"),
|
||||
) as Record<string, unknown>;
|
||||
expect(manifest).toMatchObject({
|
||||
provider: "openai-native",
|
||||
model: "gpt-5.1",
|
||||
});
|
||||
});
|
||||
|
||||
it("derives a title from a prompt only when the session has no title yet", async () => {
|
||||
const sessionsDir = mkdtempSync(join(tmpdir(), "empty-title-sessions-"));
|
||||
tempDirs.push(sessionsDir);
|
||||
|
||||
@@ -213,6 +213,8 @@ export class UnifiedSessionPersistenceService {
|
||||
|
||||
async updateSession(input: {
|
||||
sessionId: string;
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
prompt?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
title?: string | null;
|
||||
@@ -250,6 +252,8 @@ export class UnifiedSessionPersistenceService {
|
||||
|
||||
const changed = await this.adapter.updateSession({
|
||||
sessionId: input.sessionId,
|
||||
provider: input.provider,
|
||||
model: input.model,
|
||||
prompt: input.prompt,
|
||||
metadata: hasMetadataChange
|
||||
? Object.keys(baseMeta).length > 0
|
||||
@@ -264,6 +268,12 @@ export class UnifiedSessionPersistenceService {
|
||||
const { path: manifestPath, manifest } =
|
||||
this.manifestStore.readManifestFile(input.sessionId);
|
||||
if (manifest) {
|
||||
if (input.provider !== undefined && input.provider !== null) {
|
||||
manifest.provider = input.provider;
|
||||
}
|
||||
if (input.model !== undefined && input.model !== null) {
|
||||
manifest.model = input.model;
|
||||
}
|
||||
if (input.prompt !== undefined) {
|
||||
manifest.prompt = input.prompt ?? undefined;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,14 @@ class LocalSessionPersistenceAdapter implements SessionPersistenceAdapter {
|
||||
fields.push("exit_code = ?");
|
||||
params.push(input.exitCode);
|
||||
}
|
||||
if (input.provider !== undefined) {
|
||||
fields.push("provider = ?");
|
||||
params.push(input.provider ?? null);
|
||||
}
|
||||
if (input.model !== undefined) {
|
||||
fields.push("model = ?");
|
||||
params.push(input.model ?? null);
|
||||
}
|
||||
if (input.prompt !== undefined) {
|
||||
fields.push("prompt = ?");
|
||||
params.push(input.prompt ?? null);
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface CoreModelConfig {
|
||||
* Explicit reasoning effort override for capable models.
|
||||
*/
|
||||
reasoningEffort?: ProviderConfig["reasoningEffort"];
|
||||
thinkingBudgetTokens?: AgentConfig["thinkingBudgetTokens"];
|
||||
}
|
||||
|
||||
export interface CoreRuntimeFeatures {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type * as LlmsProviders from "@cline/llms";
|
||||
import type { AgentFinishReason } from "@cline/shared";
|
||||
import type { SessionAccumulatedUsage } from "../runtime/host/runtime-host";
|
||||
import type {
|
||||
SendSessionConnectionUpdate,
|
||||
SessionAccumulatedUsage,
|
||||
} from "../runtime/host/runtime-host";
|
||||
import type { BuiltRuntime } from "../runtime/orchestration/session-runtime";
|
||||
import type { SessionRuntime } from "../runtime/orchestration/session-runtime-orchestrator";
|
||||
import type { SessionRow } from "../session/models/session-row";
|
||||
@@ -62,6 +65,7 @@ export type PendingPrompt = {
|
||||
delivery: "queue" | "steer";
|
||||
userImages?: string[];
|
||||
userFiles?: string[];
|
||||
connection?: SendSessionConnectionUpdate;
|
||||
};
|
||||
|
||||
export type TeamRunUpdate = {
|
||||
@@ -89,6 +93,8 @@ export interface PersistedSessionUpdateInput {
|
||||
status?: SessionStatus;
|
||||
endedAt?: string | null;
|
||||
exitCode?: number | null;
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
prompt?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
title?: string | null;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { estimateTokens } from "@cline/shared";
|
||||
import type {
|
||||
AgentMessage,
|
||||
AgentModel,
|
||||
@@ -13,6 +12,7 @@ import type {
|
||||
GatewayStreamRequest,
|
||||
ITelemetryService,
|
||||
} from "@cline/shared";
|
||||
import { estimateTokens } from "@cline/shared";
|
||||
import { toAsyncIterable } from "./async";
|
||||
import { BUILTIN_PROVIDER_REGISTRATIONS } from "./builtins-runtime";
|
||||
import { GatewayRegistry } from "./registry";
|
||||
|
||||
@@ -2,17 +2,17 @@ import type {
|
||||
GatewayProviderContext,
|
||||
GatewayStreamRequest,
|
||||
} from "@cline/shared";
|
||||
import {
|
||||
isAnthropicCompatibleModel,
|
||||
isQwenModel,
|
||||
resolveModelFamily,
|
||||
} from "../model-facts";
|
||||
import {
|
||||
buildAnthropicCompatibleReasoningOptions,
|
||||
resolveAnthropicReasoningRequestPolicy,
|
||||
resolveReasoningRoute,
|
||||
shouldApplyPromptCache,
|
||||
} from "./anthropic-compatible";
|
||||
import {
|
||||
isAnthropicCompatibleModel,
|
||||
isQwenModel,
|
||||
resolveModelFamily,
|
||||
} from "../model-facts";
|
||||
import type {
|
||||
AiSdkProviderOptionsTarget,
|
||||
ProviderOptionSuppression,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"scope": "https://api.anthropic.com",
|
||||
"method": "POST",
|
||||
"path": "/v1/messages",
|
||||
"status": 200,
|
||||
"response": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"ID_REDACTED\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"OK\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":4}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream; charset=utf-8",
|
||||
"requestBody": "{\"max_tokens\":128000,\"messages\":[{\"content\":[{\"cache_control\":{\"type\":\"ephemeral\"},\"text\":\"Reply with the single word OK.\",\"type\":\"text\"}],\"role\":\"user\"}],\"model\":\"claude-sonnet-4-6\",\"stream\":true,\"system\":[{\"text\":\"You are a concise assistant.\",\"type\":\"text\"}]}"
|
||||
}
|
||||
{
|
||||
"scope": "https://api.anthropic.com",
|
||||
"method": "POST",
|
||||
"path": "/v1/messages",
|
||||
"status": 200,
|
||||
"response": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"ID_REDACTED\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"OK\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":4}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream; charset=utf-8",
|
||||
"requestBody": "{\"max_tokens\":128000,\"messages\":[{\"content\":[{\"cache_control\":{\"type\":\"ephemeral\"},\"text\":\"Reply with the single word OK.\",\"type\":\"text\"}],\"role\":\"user\"}],\"model\":\"claude-sonnet-4-6\",\"stream\":true,\"system\":[{\"text\":\"You are a concise assistant.\",\"type\":\"text\"}]}"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"scope": "https://api.cline.bot",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/chat/completions",
|
||||
"status": 200,
|
||||
"response": "data: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"logprobs\":null,\"finish_reason\":null}],\"system_fingerprint\":\"REDACTED\"}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"OK\"},\"logprobs\":null,\"finish_reason\":null}],\"system_fingerprint\":\"REDACTED\"}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"provider_metadata\":{\"anthropic\":{\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":4,\"service_tier\":\"standard\",\"inference_geo\":\"global\"},\"cacheCreationInputTokens\":0,\"stopSequence\":null,\"iterations\":null,\"container\":null,\"contextManagement\":null},\"gateway\":{\"routing\":{\"originalModelId\":\"anthropic/claude-sonnet-4.6\",\"resolvedProvider\":\"anthropic\",\"fallbacksAvailable\":[\"vertexAnthropic\",\"bedrock\"],\"planningReasoning\":\"REDACTED\",\"canonicalSlug\":\"anthropic/claude-sonnet-4.6\",\"finalProvider\":\"anthropic\",\"modelAttemptCount\":1,\"modelAttempts\":[{\"canonicalSlug\":\"anthropic/claude-sonnet-4.6\",\"success\":true,\"providerAttemptCount\":1,\"providerAttempts\":[{\"provider\":\"anthropic\",\"credentialType\":\"byok\",\"success\":true,\"startTime\":0,\"endTime\":0,\"providerRequestId\":\"ID_REDACTED\",\"statusCode\":200,\"providerResponseId\":\"ID_REDACTED\"}]}],\"totalProviderAttemptCount\":1},\"cost\":\"0\",\"marketCost\":\"0.000126\",\"surchargeCost\":\"0\",\"gatewayCost\":\"0\",\"generationId\":\"ID_REDACTED\"}}},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":22,\"completion_tokens\":4,\"total_tokens\":26,\"cost\":0,\"is_byok\":true,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.000126,\"upstream_inference_prompt_cost\":0,\"upstream_inference_completions_cost\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"image_tokens\":0},\"cache_creation_input_tokens\":0,\"market_cost\":0.000126},\"system_fingerprint\":\"REDACTED\",\"generationId\":\"ID_REDACTED\"}\n\ndata: [DONE]\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream",
|
||||
"requestBody": "{\"cache_control\":{\"type\":\"ephemeral\"},\"messages\":[{\"content\":\"You are a concise assistant.\",\"role\":\"system\"},{\"cache_control\":{\"type\":\"ephemeral\"},\"content\":\"Reply with the single word OK.\",\"role\":\"user\"}],\"model\":\"anthropic/claude-sonnet-4.6\",\"stream\":true,\"stream_options\":{\"include_usage\":true}}"
|
||||
}
|
||||
{
|
||||
"scope": "https://api.cline.bot",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/chat/completions",
|
||||
"status": 200,
|
||||
"response": "data: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"logprobs\":null,\"finish_reason\":null}],\"system_fingerprint\":\"REDACTED\"}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"OK\"},\"logprobs\":null,\"finish_reason\":null}],\"system_fingerprint\":\"REDACTED\"}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"anthropic/claude-sonnet-4.6\",\"choices\":[{\"index\":0,\"delta\":{\"provider_metadata\":{\"anthropic\":{\"usage\":{\"input_tokens\":22,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":4,\"service_tier\":\"standard\",\"inference_geo\":\"global\"},\"cacheCreationInputTokens\":0,\"stopSequence\":null,\"iterations\":null,\"container\":null,\"contextManagement\":null},\"gateway\":{\"routing\":{\"originalModelId\":\"anthropic/claude-sonnet-4.6\",\"resolvedProvider\":\"anthropic\",\"fallbacksAvailable\":[\"vertexAnthropic\",\"bedrock\"],\"planningReasoning\":\"REDACTED\",\"canonicalSlug\":\"anthropic/claude-sonnet-4.6\",\"finalProvider\":\"anthropic\",\"modelAttemptCount\":1,\"modelAttempts\":[{\"canonicalSlug\":\"anthropic/claude-sonnet-4.6\",\"success\":true,\"providerAttemptCount\":1,\"providerAttempts\":[{\"provider\":\"anthropic\",\"credentialType\":\"byok\",\"success\":true,\"startTime\":0,\"endTime\":0,\"providerRequestId\":\"ID_REDACTED\",\"statusCode\":200,\"providerResponseId\":\"ID_REDACTED\"}]}],\"totalProviderAttemptCount\":1},\"cost\":\"0\",\"marketCost\":\"0.000126\",\"surchargeCost\":\"0\",\"gatewayCost\":\"0\",\"generationId\":\"ID_REDACTED\"}}},\"logprobs\":null,\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":22,\"completion_tokens\":4,\"total_tokens\":26,\"cost\":0,\"is_byok\":true,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.000126,\"upstream_inference_prompt_cost\":0,\"upstream_inference_completions_cost\":0},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"image_tokens\":0},\"cache_creation_input_tokens\":0,\"market_cost\":0.000126},\"system_fingerprint\":\"REDACTED\",\"generationId\":\"ID_REDACTED\"}\n\ndata: [DONE]\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream",
|
||||
"requestBody": "{\"cache_control\":{\"type\":\"ephemeral\"},\"messages\":[{\"content\":\"You are a concise assistant.\",\"role\":\"system\"},{\"cache_control\":{\"type\":\"ephemeral\"},\"content\":\"Reply with the single word OK.\",\"role\":\"user\"}],\"model\":\"anthropic/claude-sonnet-4.6\",\"stream\":true,\"stream_options\":{\"include_usage\":true}}"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"scope": "https://openrouter.ai",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/chat/completions",
|
||||
"status": 200,
|
||||
"response": ": OPENROUTER PROCESSING\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"We\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"We\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" are\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" are\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" asked: \\\"Reply with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" asked: \\\"Reply with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the single word OK.\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the single word OK.\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" So I just need to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" So I just need to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" output \\\"OK\\\". No\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" output \\\"OK\\\". No\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" other text.\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" other text.\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"OK\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":17,\"completion_tokens\":28,\"total_tokens\":45,\"cost\":0.00012264,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.00012264,\"upstream_inference_prompt_cost\":0.00002856,\"upstream_inference_completions_cost\":0.00009408},\"completion_tokens_details\":{\"reasoning_tokens\":25,\"image_tokens\":0,\"audio_tokens\":0}}}\n\ndata: [DONE]\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream",
|
||||
"requestBody": "{\"messages\":[{\"content\":\"You are a concise assistant.\",\"role\":\"system\"},{\"content\":\"Reply with the single word OK.\",\"role\":\"user\"}],\"model\":\"deepseek/deepseek-v4-pro\",\"stream\":true,\"stream_options\":{\"include_usage\":true}}"
|
||||
}
|
||||
{
|
||||
"scope": "https://openrouter.ai",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/chat/completions",
|
||||
"status": 200,
|
||||
"response": ": OPENROUTER PROCESSING\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"We\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"We\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" are\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" are\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" asked: \\\"Reply with\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" asked: \\\"Reply with\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" the single word OK.\\\"\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" the single word OK.\\\"\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" So I just need to\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" So I just need to\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" output \\\"OK\\\". No\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" output \\\"OK\\\". No\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" other text.\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" other text.\",\"format\":\"unknown\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"OK\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"ID_REDACTED\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"deepseek/deepseek-v4-pro-20260423\",\"provider\":\"Alibaba\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":17,\"completion_tokens\":28,\"total_tokens\":45,\"cost\":0.00012264,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.00012264,\"upstream_inference_prompt_cost\":0.00002856,\"upstream_inference_completions_cost\":0.00009408},\"completion_tokens_details\":{\"reasoning_tokens\":25,\"image_tokens\":0,\"audio_tokens\":0}}}\n\ndata: [DONE]\n\n",
|
||||
"responseIsBinary": false,
|
||||
"contentType": "text/event-stream",
|
||||
"requestBody": "{\"messages\":[{\"content\":\"You are a concise assistant.\",\"role\":\"system\"},{\"content\":\"Reply with the single word OK.\",\"role\":\"user\"}],\"model\":\"deepseek/deepseek-v4-pro\",\"stream\":true,\"stream_options\":{\"include_usage\":true}}"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import z from "zod";
|
||||
import type { AgentConfig } from "../agents/types";
|
||||
import type { HubToolExecutorName } from "../hub";
|
||||
import type {
|
||||
RuntimeConfigExtensionKind,
|
||||
@@ -61,11 +62,24 @@ export interface ChatAttachments {
|
||||
userFiles?: ChatAttachmentFile[];
|
||||
}
|
||||
|
||||
export interface ChatRunTurnConnectionUpdate {
|
||||
providerId?: string;
|
||||
modelId?: string;
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
providerConfig?: unknown;
|
||||
reasoningEffort?: AgentConfig["reasoningEffort"];
|
||||
thinking?: boolean;
|
||||
thinkingBudgetTokens?: number;
|
||||
}
|
||||
|
||||
export interface ChatRunTurnRequest {
|
||||
config: ChatStartSessionRequest;
|
||||
prompt: string;
|
||||
attachments?: ChatAttachments;
|
||||
delivery?: "queue" | "steer";
|
||||
connection?: ChatRunTurnConnectionUpdate;
|
||||
}
|
||||
|
||||
export interface ChatToolCallResult {
|
||||
|
||||
Reference in New Issue
Block a user