mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 01:01:13 +00:00
fix: queue model switches behind busy runs
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.
|
- MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.
|
||||||
- Memory/QMD: prefer `--mask` over `--glob` when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.
|
- Memory/QMD: prefer `--mask` over `--glob` when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.
|
||||||
- Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog
|
- Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog
|
||||||
|
- Sessions/model switching: keep `/model` changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.
|
||||||
|
|
||||||
## 2026.3.31
|
## 2026.3.31
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ Notes:
|
|||||||
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
|
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
|
||||||
- On Discord, `/model` and `/models` open an interactive picker with provider and model dropdowns plus a Submit step.
|
- On Discord, `/model` and `/models` open an interactive picker with provider and model dropdowns plus a Submit step.
|
||||||
- `/model <#>` selects from that picker.
|
- `/model <#>` selects from that picker.
|
||||||
|
- `/model` updates the session selection immediately. If the agent is idle, the next run uses the new model right away. If the agent is busy, the in-flight run finishes first and queued/future work uses the new model after that.
|
||||||
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
||||||
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
|
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
|
||||||
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
|
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ Notes:
|
|||||||
- `/fast` is provider-specific: OpenAI/OpenAI Codex map it to `service_tier=priority` on native Responses endpoints, while direct public Anthropic requests, including OAuth-authenticated traffic sent to `api.anthropic.com`, map it to `service_tier=auto` or `standard_only`. See [OpenAI](/providers/openai) and [Anthropic](/providers/anthropic).
|
- `/fast` is provider-specific: OpenAI/OpenAI Codex map it to `service_tier=priority` on native Responses endpoints, while direct public Anthropic requests, including OAuth-authenticated traffic sent to `api.anthropic.com`, map it to `service_tier=auto` or `standard_only`. See [OpenAI](/providers/openai) and [Anthropic](/providers/anthropic).
|
||||||
- Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`.
|
- Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`.
|
||||||
- `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats.
|
- `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats.
|
||||||
|
- `/model` persists the new session model immediately, but it does not interrupt a busy run. The current turn finishes first, then queued or future work uses the updated model.
|
||||||
- **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model).
|
- **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model).
|
||||||
- **Group mention gating:** command-only messages from allowlisted senders bypass mention requirements.
|
- **Group mention gating:** command-only messages from allowlisted senders bypass mention requirements.
|
||||||
- **Inline shortcuts (allowlisted senders only):** certain commands also work when embedded in a normal message and are stripped before the model sees the remaining text.
|
- **Inline shortcuts (allowlisted senders only):** certain commands also work when embedded in a normal message and are stripped before the model sees the remaining text.
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
hasDifferentLiveSessionModelSelection,
|
hasDifferentLiveSessionModelSelection,
|
||||||
LiveSessionModelSwitchError,
|
LiveSessionModelSwitchError,
|
||||||
resolveLiveSessionModelSelection,
|
|
||||||
shouldTrackPersistedLiveSessionModelSelection,
|
|
||||||
consumeLiveSessionModelSwitch,
|
consumeLiveSessionModelSwitch,
|
||||||
} from "../live-model-switch.js";
|
} from "../live-model-switch.js";
|
||||||
import {
|
import {
|
||||||
@@ -238,18 +236,6 @@ export async function runEmbeddedPiAgent(
|
|||||||
authProfileId: preferredProfileId,
|
authProfileId: preferredProfileId,
|
||||||
authProfileIdSource: params.authProfileIdSource,
|
authProfileIdSource: params.authProfileIdSource,
|
||||||
});
|
});
|
||||||
const resolvePersistedLiveSelection = () =>
|
|
||||||
resolveLiveSessionModelSelection({
|
|
||||||
cfg: params.config,
|
|
||||||
sessionKey: params.sessionKey,
|
|
||||||
agentId: workspaceResolution.agentId,
|
|
||||||
defaultProvider: provider,
|
|
||||||
defaultModel: modelId,
|
|
||||||
});
|
|
||||||
const shouldTrackPersistedLiveSelection = shouldTrackPersistedLiveSessionModelSelection(
|
|
||||||
resolveCurrentLiveSelection(),
|
|
||||||
resolvePersistedLiveSelection(),
|
|
||||||
);
|
|
||||||
const {
|
const {
|
||||||
advanceAuthProfile,
|
advanceAuthProfile,
|
||||||
initializeAuthProfile,
|
initializeAuthProfile,
|
||||||
@@ -457,15 +443,6 @@ export async function runEmbeddedPiAgent(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
runLoopIterations += 1;
|
runLoopIterations += 1;
|
||||||
const nextSelection = shouldTrackPersistedLiveSelection
|
|
||||||
? resolvePersistedLiveSelection()
|
|
||||||
: null;
|
|
||||||
if (hasDifferentLiveSessionModelSelection(resolveCurrentLiveSelection(), nextSelection)) {
|
|
||||||
log.info(
|
|
||||||
`live session model switch detected before attempt for ${params.sessionId}: ${provider}/${modelId} -> ${nextSelection.provider}/${nextSelection.model}`,
|
|
||||||
);
|
|
||||||
throw new LiveSessionModelSwitchError(nextSelection);
|
|
||||||
}
|
|
||||||
const runtimeAuthRetry = authRetryPending;
|
const runtimeAuthRetry = authRetryPending;
|
||||||
authRetryPending = false;
|
authRetryPending = false;
|
||||||
attemptedThinking.add(thinkLevel);
|
attemptedThinking.add(thinkLevel);
|
||||||
@@ -614,23 +591,6 @@ export async function runEmbeddedPiAgent(
|
|||||||
);
|
);
|
||||||
throw new LiveSessionModelSwitchError(requestedSelection);
|
throw new LiveSessionModelSwitchError(requestedSelection);
|
||||||
}
|
}
|
||||||
const failedOrAbortedAttempt =
|
|
||||||
aborted || Boolean(promptError) || Boolean(assistantErrorText) || timedOut;
|
|
||||||
const persistedSelection =
|
|
||||||
failedOrAbortedAttempt && shouldTrackPersistedLiveSelection
|
|
||||||
? resolvePersistedLiveSelection()
|
|
||||||
: null;
|
|
||||||
if (
|
|
||||||
failedOrAbortedAttempt &&
|
|
||||||
canRestartForLiveSwitch &&
|
|
||||||
hasDifferentLiveSessionModelSelection(resolveCurrentLiveSelection(), persistedSelection)
|
|
||||||
) {
|
|
||||||
log.info(
|
|
||||||
`live session model switch detected after failed attempt for ${params.sessionId}: ${provider}/${modelId} -> ${persistedSelection.provider}/${persistedSelection.model}`,
|
|
||||||
);
|
|
||||||
throw new LiveSessionModelSwitchError(persistedSelection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Timeout-triggered compaction ──────────────────────────────────
|
// ── Timeout-triggered compaction ──────────────────────────────────
|
||||||
// When the LLM times out with high context usage, compact before
|
// When the LLM times out with high context usage, compact before
|
||||||
// retrying to break the death spiral of repeated timeouts.
|
// retrying to break the death spiral of repeated timeouts.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
} from "../../agents/agent-scope.js";
|
} from "../../agents/agent-scope.js";
|
||||||
import { renderExecTargetLabel, resolveExecTarget } from "../../agents/bash-tools.exec-runtime.js";
|
import { renderExecTargetLabel, resolveExecTarget } from "../../agents/bash-tools.exec-runtime.js";
|
||||||
import { resolveFastModeState } from "../../agents/fast-mode.js";
|
import { resolveFastModeState } from "../../agents/fast-mode.js";
|
||||||
import { requestLiveSessionModelSwitch } from "../../agents/live-model-switch.js";
|
|
||||||
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
||||||
import type { OpenClawConfig } from "../../config/config.js";
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
withOptions,
|
withOptions,
|
||||||
} from "./directive-handling.shared.js";
|
} from "./directive-handling.shared.js";
|
||||||
import type { ElevatedLevel, ReasoningLevel, ThinkLevel } from "./directives.js";
|
import type { ElevatedLevel, ReasoningLevel, ThinkLevel } from "./directives.js";
|
||||||
|
import { refreshQueuedFollowupSession } from "./queue.js";
|
||||||
|
|
||||||
function resolveExecDefaults(params: {
|
function resolveExecDefaults(params: {
|
||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
@@ -442,15 +442,16 @@ export async function handleDirectiveOnly(
|
|||||||
store[sessionKey] = sessionEntry;
|
store[sessionKey] = sessionEntry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (modelSelection && modelSelectionUpdated) {
|
if (modelSelection && modelSelectionUpdated && sessionKey) {
|
||||||
requestLiveSessionModelSwitch({
|
// `/model` should retarget queued/future work without interrupting the
|
||||||
sessionEntry,
|
// active run. Refresh queued followups so they pick up the persisted
|
||||||
selection: {
|
// selection once the current turn finishes.
|
||||||
provider: modelSelection.provider,
|
refreshQueuedFollowupSession({
|
||||||
model: modelSelection.model,
|
key: sessionKey,
|
||||||
authProfileId: profileOverride,
|
nextProvider: modelSelection.provider,
|
||||||
authProfileIdSource: profileOverride ? "user" : undefined,
|
nextModel: modelSelection.model,
|
||||||
},
|
nextAuthProfileId: profileOverride,
|
||||||
|
nextAuthProfileIdSource: profileOverride ? "user" : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import { persistInlineDirectives } from "./directive-handling.persist.js";
|
|||||||
const liveModelSwitchMocks = vi.hoisted(() => ({
|
const liveModelSwitchMocks = vi.hoisted(() => ({
|
||||||
requestLiveSessionModelSwitch: vi.fn(),
|
requestLiveSessionModelSwitch: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
const queueMocks = vi.hoisted(() => ({
|
||||||
|
refreshQueuedFollowupSession: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock dependencies for directive handling persistence.
|
// Mock dependencies for directive handling persistence.
|
||||||
vi.mock("../../agents/agent-scope.js", () => ({
|
vi.mock("../../agents/agent-scope.js", () => ({
|
||||||
@@ -42,6 +45,11 @@ vi.mock("../../agents/live-model-switch.js", () => ({
|
|||||||
liveModelSwitchMocks.requestLiveSessionModelSwitch(...args),
|
liveModelSwitchMocks.requestLiveSessionModelSwitch(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("./queue.js", () => ({
|
||||||
|
refreshQueuedFollowupSession: (...args: unknown[]) =>
|
||||||
|
queueMocks.refreshQueuedFollowupSession(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
const TEST_AGENT_DIR = "/tmp/agent";
|
const TEST_AGENT_DIR = "/tmp/agent";
|
||||||
const OPENAI_DATE_PROFILE_ID = "20251001";
|
const OPENAI_DATE_PROFILE_ID = "20251001";
|
||||||
|
|
||||||
@@ -75,6 +83,7 @@ beforeEach(() => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
liveModelSwitchMocks.requestLiveSessionModelSwitch.mockReset().mockReturnValue(false);
|
liveModelSwitchMocks.requestLiveSessionModelSwitch.mockReset().mockReturnValue(false);
|
||||||
|
queueMocks.refreshQueuedFollowupSession.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -507,7 +516,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
|
|||||||
expect(result?.text).not.toContain("failed");
|
expect(result?.text).not.toContain("failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requests a live restart when /model mutates an active session", async () => {
|
it("does not request a live restart when /model mutates an active session", async () => {
|
||||||
const directives = parseInlineDirectives("/model openai/gpt-4o");
|
const directives = parseInlineDirectives("/model openai/gpt-4o");
|
||||||
const sessionEntry = createSessionEntry();
|
const sessionEntry = createSessionEntry();
|
||||||
|
|
||||||
@@ -518,14 +527,26 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(liveModelSwitchMocks.requestLiveSessionModelSwitch).toHaveBeenCalledWith({
|
expect(liveModelSwitchMocks.requestLiveSessionModelSwitch).not.toHaveBeenCalled();
|
||||||
sessionEntry,
|
});
|
||||||
selection: {
|
|
||||||
provider: "openai",
|
it("retargets queued followups when /model mutates session state", async () => {
|
||||||
model: "gpt-4o",
|
const directives = parseInlineDirectives("/model openai/gpt-4o");
|
||||||
authProfileId: undefined,
|
const sessionEntry = createSessionEntry();
|
||||||
authProfileIdSource: undefined,
|
|
||||||
},
|
await handleDirectiveOnly(
|
||||||
|
createHandleParams({
|
||||||
|
directives,
|
||||||
|
sessionEntry,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queueMocks.refreshQueuedFollowupSession).toHaveBeenCalledWith({
|
||||||
|
key: sessionKey,
|
||||||
|
nextProvider: "openai",
|
||||||
|
nextModel: "gpt-4o",
|
||||||
|
nextAuthProfileId: undefined,
|
||||||
|
nextAuthProfileIdSource: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -125,25 +125,54 @@ function refreshQueuedFollowupSessionForFollowupTest(params: {
|
|||||||
previousSessionId?: string;
|
previousSessionId?: string;
|
||||||
nextSessionId?: string;
|
nextSessionId?: string;
|
||||||
nextSessionFile?: string;
|
nextSessionFile?: string;
|
||||||
|
nextProvider?: string;
|
||||||
|
nextModel?: string;
|
||||||
|
nextAuthProfileId?: string;
|
||||||
|
nextAuthProfileIdSource?: "auto" | "user";
|
||||||
}): void {
|
}): void {
|
||||||
const cleaned = params.key.trim();
|
const cleaned = params.key.trim();
|
||||||
if (!cleaned || !params.previousSessionId || !params.nextSessionId) {
|
if (!cleaned) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (params.previousSessionId === params.nextSessionId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const queue = FOLLOWUP_TEST_QUEUES.get(cleaned);
|
const queue = FOLLOWUP_TEST_QUEUES.get(cleaned);
|
||||||
if (!queue) {
|
if (!queue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const shouldRewriteSession =
|
||||||
|
Boolean(params.previousSessionId) &&
|
||||||
|
Boolean(params.nextSessionId) &&
|
||||||
|
params.previousSessionId !== params.nextSessionId;
|
||||||
|
const shouldRewriteSelection =
|
||||||
|
typeof params.nextProvider === "string" ||
|
||||||
|
typeof params.nextModel === "string" ||
|
||||||
|
Object.hasOwn(params, "nextAuthProfileId") ||
|
||||||
|
Object.hasOwn(params, "nextAuthProfileIdSource");
|
||||||
|
if (!shouldRewriteSession && !shouldRewriteSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rewrite = (run?: FollowupRun["run"]) => {
|
const rewrite = (run?: FollowupRun["run"]) => {
|
||||||
if (!run || run.sessionId !== params.previousSessionId) {
|
if (!run) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
run.sessionId = params.nextSessionId!;
|
if (shouldRewriteSession && run.sessionId === params.previousSessionId) {
|
||||||
if (params.nextSessionFile?.trim()) {
|
run.sessionId = params.nextSessionId!;
|
||||||
run.sessionFile = params.nextSessionFile;
|
if (params.nextSessionFile?.trim()) {
|
||||||
|
run.sessionFile = params.nextSessionFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRewriteSelection) {
|
||||||
|
if (typeof params.nextProvider === "string") {
|
||||||
|
run.provider = params.nextProvider;
|
||||||
|
}
|
||||||
|
if (typeof params.nextModel === "string") {
|
||||||
|
run.model = params.nextModel;
|
||||||
|
}
|
||||||
|
if (Object.hasOwn(params, "nextAuthProfileId")) {
|
||||||
|
run.authProfileId = params.nextAuthProfileId?.trim() || undefined;
|
||||||
|
}
|
||||||
|
if (Object.hasOwn(params, "nextAuthProfileIdSource")) {
|
||||||
|
run.authProfileIdSource = run.authProfileId ? params.nextAuthProfileIdSource : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
rewrite(queue.lastRun);
|
rewrite(queue.lastRun);
|
||||||
|
|||||||
62
src/auto-reply/reply/queue/state.test.ts
Normal file
62
src/auto-reply/reply/queue/state.test.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { clearFollowupQueue, getFollowupQueue, refreshQueuedFollowupSession } from "./state.js";
|
||||||
|
import type { FollowupRun } from "./types.js";
|
||||||
|
|
||||||
|
const QUEUE_KEY = "agent:main:dm:test";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clearFollowupQueue(QUEUE_KEY);
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeRun(): FollowupRun["run"] {
|
||||||
|
return {
|
||||||
|
agentId: "main",
|
||||||
|
agentDir: "/tmp/agent",
|
||||||
|
sessionId: "session-1",
|
||||||
|
sessionKey: QUEUE_KEY,
|
||||||
|
sessionFile: "/tmp/session-1.jsonl",
|
||||||
|
workspaceDir: "/tmp/workspace",
|
||||||
|
config: {} as FollowupRun["run"]["config"],
|
||||||
|
provider: "anthropic",
|
||||||
|
model: "claude-opus-4-5",
|
||||||
|
authProfileId: "profile-a",
|
||||||
|
authProfileIdSource: "user",
|
||||||
|
timeoutMs: 30_000,
|
||||||
|
blockReplyBreak: "message_end",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("refreshQueuedFollowupSession", () => {
|
||||||
|
it("retargets queued runs to the persisted selection", () => {
|
||||||
|
const queue = getFollowupQueue(QUEUE_KEY, { mode: "queue" });
|
||||||
|
const lastRun = makeRun();
|
||||||
|
const queuedRun: FollowupRun = {
|
||||||
|
prompt: "queued message",
|
||||||
|
enqueuedAt: Date.now(),
|
||||||
|
run: makeRun(),
|
||||||
|
};
|
||||||
|
queue.lastRun = lastRun;
|
||||||
|
queue.items.push(queuedRun);
|
||||||
|
|
||||||
|
refreshQueuedFollowupSession({
|
||||||
|
key: QUEUE_KEY,
|
||||||
|
nextProvider: "openai",
|
||||||
|
nextModel: "gpt-4o",
|
||||||
|
nextAuthProfileId: undefined,
|
||||||
|
nextAuthProfileIdSource: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(queue.lastRun).toMatchObject({
|
||||||
|
provider: "openai",
|
||||||
|
model: "gpt-4o",
|
||||||
|
authProfileId: undefined,
|
||||||
|
authProfileIdSource: undefined,
|
||||||
|
});
|
||||||
|
expect(queue.items[0]?.run).toMatchObject({
|
||||||
|
provider: "openai",
|
||||||
|
model: "gpt-4o",
|
||||||
|
authProfileId: undefined,
|
||||||
|
authProfileIdSource: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -91,26 +91,55 @@ export function refreshQueuedFollowupSession(params: {
|
|||||||
previousSessionId?: string;
|
previousSessionId?: string;
|
||||||
nextSessionId?: string;
|
nextSessionId?: string;
|
||||||
nextSessionFile?: string;
|
nextSessionFile?: string;
|
||||||
|
nextProvider?: string;
|
||||||
|
nextModel?: string;
|
||||||
|
nextAuthProfileId?: string;
|
||||||
|
nextAuthProfileIdSource?: "auto" | "user";
|
||||||
}): void {
|
}): void {
|
||||||
const cleaned = params.key.trim();
|
const cleaned = params.key.trim();
|
||||||
if (!cleaned || !params.previousSessionId || !params.nextSessionId) {
|
if (!cleaned) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (params.previousSessionId === params.nextSessionId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const queue = getExistingFollowupQueue(cleaned);
|
const queue = getExistingFollowupQueue(cleaned);
|
||||||
if (!queue) {
|
if (!queue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const shouldRewriteSession =
|
||||||
|
Boolean(params.previousSessionId) &&
|
||||||
|
Boolean(params.nextSessionId) &&
|
||||||
|
params.previousSessionId !== params.nextSessionId;
|
||||||
|
const shouldRewriteSelection =
|
||||||
|
typeof params.nextProvider === "string" ||
|
||||||
|
typeof params.nextModel === "string" ||
|
||||||
|
Object.hasOwn(params, "nextAuthProfileId") ||
|
||||||
|
Object.hasOwn(params, "nextAuthProfileIdSource");
|
||||||
|
if (!shouldRewriteSession && !shouldRewriteSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rewriteRun = (run?: FollowupRun["run"]) => {
|
const rewriteRun = (run?: FollowupRun["run"]) => {
|
||||||
if (!run || run.sessionId !== params.previousSessionId) {
|
if (!run) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
run.sessionId = params.nextSessionId!;
|
if (shouldRewriteSession && run.sessionId === params.previousSessionId) {
|
||||||
if (params.nextSessionFile?.trim()) {
|
run.sessionId = params.nextSessionId!;
|
||||||
run.sessionFile = params.nextSessionFile;
|
if (params.nextSessionFile?.trim()) {
|
||||||
|
run.sessionFile = params.nextSessionFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRewriteSelection) {
|
||||||
|
if (typeof params.nextProvider === "string") {
|
||||||
|
run.provider = params.nextProvider;
|
||||||
|
}
|
||||||
|
if (typeof params.nextModel === "string") {
|
||||||
|
run.model = params.nextModel;
|
||||||
|
}
|
||||||
|
if (Object.hasOwn(params, "nextAuthProfileId")) {
|
||||||
|
run.authProfileId = params.nextAuthProfileId?.trim() || undefined;
|
||||||
|
}
|
||||||
|
if (Object.hasOwn(params, "nextAuthProfileIdSource")) {
|
||||||
|
run.authProfileIdSource = run.authProfileId ? params.nextAuthProfileIdSource : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user