mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:10:44 +00:00
fix(acp): pass Codex ACP model thinking overrides
Fix ACP Codex model/thinking override propagation.\n\nThanks @91wan.
This commit is contained in:
@@ -83,6 +83,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Anthropic/Bedrock: preserve stripped thinking-only assistant replay
|
||||
turns with non-empty omitted-reasoning text so provider adapters keep strict
|
||||
user/assistant turn shape. Thanks @wujiaming88.
|
||||
- ACP/Codex: pass `sessions_spawn(runtime="acp")` model and thinking overrides into Codex ACP startup, normalize `openai-codex/*` refs and slash reasoning suffixes, and recognize managed Codex ACP wrapper commands without blocking current `gpt-5.5` sessions. Fixes #40393. (#71643) Thanks @91wan.
|
||||
- Browser/CDP: make readiness diagnostics use the same discovery-first fallback as reachability for bare `ws://` Browserless and Browserbase CDP URLs. Fixes #69532.
|
||||
- Browser/CDP: explain that loopback Browserless or other externally managed CDP services need `attachOnly: true` and matching Browserless `EXTERNAL` endpoint when reporting local port ownership conflicts, and fall back to the configured bare WebSocket root when a discovered Browserless endpoint rejects CDP. Fixes #49815.
|
||||
- Gateway/reload: preserve indefinite `gateway.reload.deferralTimeoutMs: 0` semantics for channel hot reload deferrals so active agent runs are not interrupted by a forced channel restart. (#71637) Thanks @Poo-Squirry.
|
||||
|
||||
@@ -329,7 +329,8 @@ Interface details:
|
||||
- `resumeSessionId` (optional): resume an existing ACP session instead of creating a new one. The agent replays its conversation history via `session/load`. Requires `runtime: "acp"`.
|
||||
- `streamTo` (optional): `"parent"` streams initial ACP run progress summaries back to the requester session as system events.
|
||||
- When available, accepted responses include `streamLogPath` pointing to a session-scoped JSONL log (`<sessionId>.acp-stream.jsonl`) you can tail for full relay history.
|
||||
- `model` (optional): explicit model override for the ACP child session. Honored for `runtime: "acp"` so the child uses the requested model instead of silently falling back to the target agent default.
|
||||
- `model` (optional): explicit model override for the ACP child session. Honored for `runtime: "acp"` so the child uses the requested model instead of silently falling back to the target agent default. Codex ACP spawns normalize OpenClaw Codex refs such as `openai-codex/gpt-5.4` to Codex ACP startup config before `session/new`; slash forms such as `openai-codex/gpt-5.4/high` also set Codex ACP reasoning effort.
|
||||
- `thinking` (optional): explicit thinking/reasoning effort for the ACP child session. For Codex ACP, `minimal` maps to low effort, `low`/`medium`/`high`/`xhigh` map directly, and `off` omits the reasoning-effort startup override.
|
||||
|
||||
## Delivery model
|
||||
|
||||
@@ -522,7 +523,8 @@ Notes:
|
||||
|
||||
Equivalent operations:
|
||||
|
||||
- `/acp model <id>` maps to runtime config key `model`.
|
||||
- `/acp model <id>` maps to runtime config key `model`. For Codex ACP, OpenClaw normalizes `openai-codex/<model>` to the adapter model id and maps slash reasoning suffixes such as `openai-codex/gpt-5.4/high` to Codex ACP `reasoning_effort`.
|
||||
- `/acp set thinking <level>` maps to runtime config key `thinking`. For Codex ACP, OpenClaw sends the corresponding `reasoning_effort` where the adapter supports one.
|
||||
- `/acp permissions <profile>` maps to runtime config key `approval_policy`.
|
||||
- `/acp timeout <seconds>` maps to runtime config key `timeout`.
|
||||
- `/acp cwd <path>` updates runtime cwd override directly.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AcpRuntime } from "../runtime-api.js";
|
||||
import { AcpxRuntime } from "./runtime.js";
|
||||
import { AcpxRuntime, __testing } from "./runtime.js";
|
||||
|
||||
type TestSessionStore = {
|
||||
load(sessionId: string): Promise<Record<string, unknown> | undefined>;
|
||||
@@ -9,6 +9,8 @@ type TestSessionStore = {
|
||||
|
||||
const DOCUMENTED_OPENCLAW_BRIDGE_COMMAND =
|
||||
"env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 openclaw acp --url ws://127.0.0.1:18789 --token-file ~/.openclaw/gateway.token --session agent:main:main";
|
||||
const CODEX_ACP_COMMAND = "npx @zed-industries/codex-acp@^0.11.1";
|
||||
const CODEX_ACP_WRAPPER_COMMAND = `node "/tmp/openclaw/acpx/codex-acp-wrapper.mjs"`;
|
||||
|
||||
function makeRuntime(
|
||||
baseStore: TestSessionStore,
|
||||
@@ -20,6 +22,7 @@ function makeRuntime(
|
||||
close: AcpRuntime["close"];
|
||||
ensureSession: AcpRuntime["ensureSession"];
|
||||
getStatus: NonNullable<AcpRuntime["getStatus"]>;
|
||||
setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
|
||||
isHealthy(): boolean;
|
||||
probeAvailability(): Promise<void>;
|
||||
};
|
||||
@@ -27,6 +30,7 @@ function makeRuntime(
|
||||
close: AcpRuntime["close"];
|
||||
ensureSession: AcpRuntime["ensureSession"];
|
||||
getStatus: NonNullable<AcpRuntime["getStatus"]>;
|
||||
setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
|
||||
isHealthy(): boolean;
|
||||
probeAvailability(): Promise<void>;
|
||||
};
|
||||
@@ -55,6 +59,7 @@ function makeRuntime(
|
||||
close: AcpRuntime["close"];
|
||||
ensureSession: AcpRuntime["ensureSession"];
|
||||
getStatus: NonNullable<AcpRuntime["getStatus"]>;
|
||||
setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
|
||||
isHealthy(): boolean;
|
||||
probeAvailability(): Promise<void>;
|
||||
};
|
||||
@@ -66,6 +71,7 @@ function makeRuntime(
|
||||
close: AcpRuntime["close"];
|
||||
ensureSession: AcpRuntime["ensureSession"];
|
||||
getStatus: NonNullable<AcpRuntime["getStatus"]>;
|
||||
setConfigOption: NonNullable<AcpRuntime["setConfigOption"]>;
|
||||
isHealthy(): boolean;
|
||||
probeAvailability(): Promise<void>;
|
||||
};
|
||||
@@ -79,6 +85,274 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("normalizes OpenClaw Codex model ids for ACP startup", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => undefined),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore, {
|
||||
agentRegistry: {
|
||||
resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
|
||||
list: () => ["codex", "openclaw"],
|
||||
},
|
||||
});
|
||||
const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "codex",
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
});
|
||||
|
||||
expect(ensure).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "gpt-5.4",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves Codex ACP startup defaults alone when no model or thinking is provided", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => undefined),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore, {
|
||||
agentRegistry: {
|
||||
resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
|
||||
list: () => ["codex", "openclaw"],
|
||||
},
|
||||
});
|
||||
const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "codex",
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
});
|
||||
|
||||
expect(ensure).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agent: "codex",
|
||||
}),
|
||||
);
|
||||
expect(ensure.mock.calls[0]?.[0]).not.toHaveProperty("model");
|
||||
expect(ensure.mock.calls[0]?.[0]).not.toHaveProperty("thinking");
|
||||
});
|
||||
|
||||
it("does not normalize model startup for non-Codex ACP agents", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => undefined),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore, {
|
||||
agentRegistry: {
|
||||
resolve: (agentName: string) => (agentName === "main" ? CODEX_ACP_COMMAND : agentName),
|
||||
list: () => ["main", "codex", "openclaw"],
|
||||
},
|
||||
});
|
||||
const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
|
||||
sessionKey: "agent:main:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "main",
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:main:acp:test",
|
||||
agent: "main",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
});
|
||||
|
||||
expect(ensure).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agent: "main",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("injects Codex ACP startup config into the scoped registry", () => {
|
||||
expect(__testing.isCodexAcpCommand(CODEX_ACP_COMMAND)).toBe(true);
|
||||
expect(__testing.isCodexAcpCommand(CODEX_ACP_WRAPPER_COMMAND)).toBe(true);
|
||||
expect(
|
||||
__testing.appendCodexAcpConfigOverrides(CODEX_ACP_COMMAND, {
|
||||
model: "gpt-5.4",
|
||||
reasoningEffort: "medium",
|
||||
}),
|
||||
).toBe(
|
||||
"npx @zed-industries/codex-acp@^0.11.1 -c model=gpt-5.4 -c model_reasoning_effort=medium",
|
||||
);
|
||||
expect(__testing.isCodexAcpCommand("openclaw acp")).toBe(false);
|
||||
});
|
||||
|
||||
it("passes gpt-5.5 Codex ACP startup through instead of blocking it", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => undefined),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore, {
|
||||
agentRegistry: {
|
||||
resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
|
||||
list: () => ["codex", "openclaw"],
|
||||
},
|
||||
});
|
||||
const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "codex",
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
});
|
||||
|
||||
expect(ensure).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "gpt-5.5",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("maps explicit Codex ACP thinking to startup reasoning effort", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => undefined),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore, {
|
||||
agentRegistry: {
|
||||
resolve: (agentName: string) => (agentName === "codex" ? CODEX_ACP_COMMAND : agentName),
|
||||
list: () => ["codex", "openclaw"],
|
||||
},
|
||||
});
|
||||
const ensure = vi.spyOn(delegate, "ensureSession").mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "codex",
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "x-high",
|
||||
});
|
||||
|
||||
expect(ensure).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "gpt-5.4/xhigh",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes Codex ACP model config controls to adapter ids", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => ({
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
agentCommand: CODEX_ACP_COMMAND,
|
||||
})),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore);
|
||||
const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
|
||||
const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "agent:codex:acp:test",
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
};
|
||||
|
||||
await runtime.setConfigOption({
|
||||
handle,
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4",
|
||||
});
|
||||
|
||||
expect(setConfigOption).toHaveBeenNthCalledWith(1, {
|
||||
handle,
|
||||
key: "model",
|
||||
value: "gpt-5.4",
|
||||
});
|
||||
expect(setConfigOption).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("normalizes Codex ACP slash reasoning suffixes to config controls", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => ({
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
agentCommand: CODEX_ACP_COMMAND,
|
||||
})),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore);
|
||||
const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
|
||||
const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "agent:codex:acp:test",
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
};
|
||||
|
||||
await runtime.setConfigOption({
|
||||
handle,
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4/high",
|
||||
});
|
||||
|
||||
expect(setConfigOption).toHaveBeenNthCalledWith(1, {
|
||||
handle,
|
||||
key: "model",
|
||||
value: "gpt-5.4",
|
||||
});
|
||||
expect(setConfigOption).toHaveBeenNthCalledWith(2, {
|
||||
handle,
|
||||
key: "reasoning_effort",
|
||||
value: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes Codex ACP thinking config controls to reasoning effort", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => ({
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
agentCommand: CODEX_ACP_COMMAND,
|
||||
})),
|
||||
save: vi.fn(async () => {}),
|
||||
};
|
||||
const { runtime, delegate } = makeRuntime(baseStore);
|
||||
const setConfigOption = vi.spyOn(delegate, "setConfigOption").mockResolvedValue(undefined);
|
||||
const handle: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0]["handle"] = {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
backend: "acpx",
|
||||
runtimeSessionName: "agent:codex:acp:test",
|
||||
acpxRecordId: "agent:codex:acp:test",
|
||||
};
|
||||
|
||||
await runtime.setConfigOption({
|
||||
handle,
|
||||
key: "thinking",
|
||||
value: "minimal",
|
||||
});
|
||||
|
||||
expect(setConfigOption).toHaveBeenCalledWith({
|
||||
handle,
|
||||
key: "reasoning_effort",
|
||||
value: "low",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps stale persistent loads hidden until a fresh record is saved", async () => {
|
||||
const baseStore: TestSessionStore = {
|
||||
load: vi.fn(async () => ({ acpxRecordId: "stale" }) as never),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import {
|
||||
ACPX_BACKEND_ID,
|
||||
AcpxRuntime as BaseAcpxRuntime,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
type AcpRuntimeOptions,
|
||||
type AcpRuntimeStatus,
|
||||
} from "acpx/runtime";
|
||||
import type { AcpRuntime } from "../runtime-api.js";
|
||||
import { AcpRuntimeError, type AcpRuntime } from "../runtime-api.js";
|
||||
|
||||
type AcpSessionStore = AcpRuntimeOptions["sessionStore"];
|
||||
type AcpSessionRecord = Parameters<AcpSessionStore["save"]>[0];
|
||||
@@ -60,6 +61,27 @@ function createResetAwareSessionStore(baseStore: AcpSessionStore): ResetAwareSes
|
||||
|
||||
const OPENCLAW_BRIDGE_EXECUTABLE = "openclaw";
|
||||
const OPENCLAW_BRIDGE_SUBCOMMAND = "acp";
|
||||
const CODEX_ACP_AGENT_ID = "codex";
|
||||
const CODEX_ACP_OPENCLAW_PREFIX = "openai-codex/";
|
||||
const CODEX_ACP_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh"]);
|
||||
const CODEX_ACP_THINKING_ALIASES = new Map<string, string | undefined>([
|
||||
["off", undefined],
|
||||
["minimal", "low"],
|
||||
["low", "low"],
|
||||
["medium", "medium"],
|
||||
["high", "high"],
|
||||
["x-high", "xhigh"],
|
||||
["x_high", "xhigh"],
|
||||
["extra-high", "xhigh"],
|
||||
["extra_high", "xhigh"],
|
||||
["extra high", "xhigh"],
|
||||
["xhigh", "xhigh"],
|
||||
]);
|
||||
|
||||
type CodexAcpModelOverride = {
|
||||
model?: string;
|
||||
reasoningEffort?: string;
|
||||
};
|
||||
|
||||
function normalizeAgentName(value: string | undefined): string | undefined {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
@@ -175,6 +197,149 @@ function isOpenClawBridgeCommand(command: string | undefined): boolean {
|
||||
return /^openclaw(?:\.[cm]?js)?$/i.test(scriptName) && parts[2] === OPENCLAW_BRIDGE_SUBCOMMAND;
|
||||
}
|
||||
|
||||
function isCodexAcpPackageSpec(value: string): boolean {
|
||||
return /^@zed-industries\/codex-acp(?:@.+)?$/i.test(value.trim());
|
||||
}
|
||||
|
||||
function isCodexAcpCommand(command: string | undefined): boolean {
|
||||
if (!command) {
|
||||
return false;
|
||||
}
|
||||
const parts = unwrapEnvCommand(splitCommandParts(command.trim()));
|
||||
if (!parts.length) {
|
||||
return false;
|
||||
}
|
||||
if (parts.some(isCodexAcpPackageSpec)) {
|
||||
return true;
|
||||
}
|
||||
const commandName = basename(parts[0] ?? "");
|
||||
if (/^codex-acp(?:\.exe)?$/i.test(commandName)) {
|
||||
return true;
|
||||
}
|
||||
if (commandName !== "node") {
|
||||
return false;
|
||||
}
|
||||
const scriptName = basename(parts[1] ?? "");
|
||||
return /^codex-acp(?:-wrapper)?(?:\.[cm]?js)?$/i.test(scriptName);
|
||||
}
|
||||
|
||||
function failUnsupportedCodexAcpModel(rawModel: string, detail?: string): never {
|
||||
throw new AcpRuntimeError(
|
||||
"ACP_INVALID_RUNTIME_OPTION",
|
||||
detail ??
|
||||
`Codex ACP model "${rawModel}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
|
||||
function failUnsupportedCodexAcpThinking(rawThinking: string): never {
|
||||
throw new AcpRuntimeError(
|
||||
"ACP_INVALID_RUNTIME_OPTION",
|
||||
`Codex ACP thinking level "${rawThinking}" is not supported. Use off, minimal, low, medium, high, or xhigh.`,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCodexAcpReasoningEffort(rawThinking: string | undefined): string | undefined {
|
||||
const normalized = rawThinking?.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (!CODEX_ACP_THINKING_ALIASES.has(normalized)) {
|
||||
failUnsupportedCodexAcpThinking(rawThinking ?? "");
|
||||
}
|
||||
return CODEX_ACP_THINKING_ALIASES.get(normalized);
|
||||
}
|
||||
|
||||
function normalizeCodexAcpModelOverride(
|
||||
rawModel: string | undefined,
|
||||
rawThinking?: string,
|
||||
): CodexAcpModelOverride | undefined {
|
||||
const raw = rawModel?.trim();
|
||||
const thinkingReasoningEffort = normalizeCodexAcpReasoningEffort(rawThinking);
|
||||
|
||||
if (!raw) {
|
||||
return thinkingReasoningEffort ? { reasoningEffort: thinkingReasoningEffort } : undefined;
|
||||
}
|
||||
|
||||
let value = raw;
|
||||
if (value.toLowerCase().startsWith(CODEX_ACP_OPENCLAW_PREFIX)) {
|
||||
value = value.slice(CODEX_ACP_OPENCLAW_PREFIX.length);
|
||||
}
|
||||
const parts = value.split("/");
|
||||
if (parts.length > 2) {
|
||||
failUnsupportedCodexAcpModel(
|
||||
raw,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
const model = (parts[0] ?? "").trim();
|
||||
const modelReasoningEffort = normalizeCodexAcpReasoningEffort(parts[1]);
|
||||
if (!model) {
|
||||
failUnsupportedCodexAcpModel(
|
||||
raw,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
const reasoningEffort = thinkingReasoningEffort ?? modelReasoningEffort;
|
||||
if (reasoningEffort && !CODEX_ACP_REASONING_EFFORTS.has(reasoningEffort)) {
|
||||
failUnsupportedCodexAcpThinking(reasoningEffort);
|
||||
}
|
||||
return {
|
||||
model,
|
||||
...(reasoningEffort ? { reasoningEffort } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function codexAcpSessionModelId(override: CodexAcpModelOverride): string {
|
||||
if (!override.model) {
|
||||
return "";
|
||||
}
|
||||
return override.reasoningEffort
|
||||
? `${override.model}/${override.reasoningEffort}`
|
||||
: override.model;
|
||||
}
|
||||
|
||||
function quoteShellArg(value: string): string {
|
||||
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value.replace(/'/g, "'\\''")}'`;
|
||||
}
|
||||
|
||||
function appendCodexAcpConfigOverrides(command: string, override: CodexAcpModelOverride): string {
|
||||
const configArgs = override.model ? [`model=${override.model}`] : [];
|
||||
if (override.reasoningEffort) {
|
||||
configArgs.push(`model_reasoning_effort=${override.reasoningEffort}`);
|
||||
}
|
||||
if (configArgs.length === 0) {
|
||||
return command;
|
||||
}
|
||||
return `${command} ${configArgs.map((arg) => `-c ${quoteShellArg(arg)}`).join(" ")}`;
|
||||
}
|
||||
|
||||
function createModelScopedAgentRegistry(params: {
|
||||
agentRegistry: AcpAgentRegistry;
|
||||
scope: AsyncLocalStorage<CodexAcpModelOverride | undefined>;
|
||||
}): AcpAgentRegistry {
|
||||
return {
|
||||
resolve(agentName: string): string | undefined {
|
||||
const command = params.agentRegistry.resolve(agentName);
|
||||
const override = params.scope.getStore();
|
||||
if (
|
||||
!override ||
|
||||
normalizeAgentName(agentName) !== CODEX_ACP_AGENT_ID ||
|
||||
typeof command !== "string" ||
|
||||
!isCodexAcpCommand(command)
|
||||
) {
|
||||
return command;
|
||||
}
|
||||
return appendCodexAcpConfigOverrides(command, override);
|
||||
},
|
||||
list(): string[] {
|
||||
return params.agentRegistry.list();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function resolveAgentCommand(params: {
|
||||
agentName: string | undefined;
|
||||
agentRegistry: AcpAgentRegistry;
|
||||
@@ -211,6 +376,10 @@ function shouldUseDistinctBridgeDelegate(options: AcpRuntimeOptions): boolean {
|
||||
export class AcpxRuntime implements AcpRuntime {
|
||||
private readonly sessionStore: ResetAwareSessionStore;
|
||||
private readonly agentRegistry: AcpAgentRegistry;
|
||||
private readonly scopedAgentRegistry: AcpAgentRegistry;
|
||||
private readonly codexAcpModelOverrideScope = new AsyncLocalStorage<
|
||||
CodexAcpModelOverride | undefined
|
||||
>();
|
||||
private readonly delegate: BaseAcpxRuntime;
|
||||
private readonly bridgeSafeDelegate: BaseAcpxRuntime;
|
||||
private readonly probeDelegate: BaseAcpxRuntime;
|
||||
@@ -221,9 +390,14 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
) {
|
||||
this.sessionStore = createResetAwareSessionStore(options.sessionStore);
|
||||
this.agentRegistry = options.agentRegistry;
|
||||
this.scopedAgentRegistry = createModelScopedAgentRegistry({
|
||||
agentRegistry: this.agentRegistry,
|
||||
scope: this.codexAcpModelOverrideScope,
|
||||
});
|
||||
const sharedOptions = {
|
||||
...options,
|
||||
sessionStore: this.sessionStore,
|
||||
agentRegistry: this.scopedAgentRegistry,
|
||||
};
|
||||
this.delegate = new BaseAcpxRuntime(sharedOptions, testOptions);
|
||||
this.bridgeSafeDelegate = shouldUseDistinctBridgeDelegate(options)
|
||||
@@ -259,6 +433,18 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
return this.resolveDelegateForAgent(readAgentFromHandle(handle));
|
||||
}
|
||||
|
||||
private async resolveCommandForHandle(handle: AcpRuntimeHandle): Promise<string | undefined> {
|
||||
const record = await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
|
||||
const recordCommand = readAgentCommandFromRecord(record);
|
||||
if (recordCommand) {
|
||||
return recordCommand;
|
||||
}
|
||||
return resolveAgentCommandForName({
|
||||
agentName: readAgentFromHandle(handle),
|
||||
agentRegistry: this.agentRegistry,
|
||||
});
|
||||
}
|
||||
|
||||
isHealthy(): boolean {
|
||||
return this.probeDelegate.isHealthy();
|
||||
}
|
||||
@@ -271,8 +457,32 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
return this.probeDelegate.doctor();
|
||||
}
|
||||
|
||||
ensureSession(input: Parameters<AcpRuntime["ensureSession"]>[0]): Promise<AcpRuntimeHandle> {
|
||||
return this.resolveDelegateForAgent(input.agent).ensureSession(input);
|
||||
async ensureSession(
|
||||
input: Parameters<AcpRuntime["ensureSession"]>[0],
|
||||
): Promise<AcpRuntimeHandle> {
|
||||
const command = resolveAgentCommandForName({
|
||||
agentName: input.agent,
|
||||
agentRegistry: this.agentRegistry,
|
||||
});
|
||||
const delegate = this.resolveDelegateForCommand(command);
|
||||
const codexModelOverride =
|
||||
normalizeAgentName(input.agent) === CODEX_ACP_AGENT_ID && isCodexAcpCommand(command)
|
||||
? normalizeCodexAcpModelOverride(input.model, input.thinking)
|
||||
: undefined;
|
||||
|
||||
if (!codexModelOverride) {
|
||||
return delegate.ensureSession(input);
|
||||
}
|
||||
|
||||
const normalizedInput = {
|
||||
...input,
|
||||
...(codexAcpSessionModelId(codexModelOverride)
|
||||
? { model: codexAcpSessionModelId(codexModelOverride) }
|
||||
: {}),
|
||||
};
|
||||
return this.codexAcpModelOverrideScope.run(codexModelOverride, () =>
|
||||
delegate.ensureSession(normalizedInput),
|
||||
);
|
||||
}
|
||||
|
||||
async *runTurn(input: Parameters<AcpRuntime["runTurn"]>[0]): AsyncIterable<AcpRuntimeEvent> {
|
||||
@@ -299,6 +509,39 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
input: Parameters<NonNullable<AcpRuntime["setConfigOption"]>>[0],
|
||||
): Promise<void> {
|
||||
const delegate = await this.resolveDelegateForHandle(input.handle);
|
||||
const command = await this.resolveCommandForHandle(input.handle);
|
||||
if (
|
||||
(input.key === "model" ||
|
||||
input.key === "thinking" ||
|
||||
input.key === "thought_level" ||
|
||||
input.key === "reasoning_effort") &&
|
||||
isCodexAcpCommand(command)
|
||||
) {
|
||||
const override =
|
||||
input.key === "model"
|
||||
? normalizeCodexAcpModelOverride(input.value)
|
||||
: normalizeCodexAcpModelOverride(undefined, input.value);
|
||||
if (!override && input.key !== "model") {
|
||||
return;
|
||||
}
|
||||
if (override) {
|
||||
if (override.model) {
|
||||
await delegate.setConfigOption({
|
||||
...input,
|
||||
key: "model",
|
||||
value: override.model,
|
||||
});
|
||||
}
|
||||
if (override.reasoningEffort) {
|
||||
await delegate.setConfigOption({
|
||||
...input,
|
||||
key: "reasoning_effort",
|
||||
value: override.reasoningEffort,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
await delegate.setConfigOption(input);
|
||||
}
|
||||
|
||||
@@ -334,4 +577,11 @@ export {
|
||||
encodeAcpxRuntimeHandleState,
|
||||
};
|
||||
|
||||
export const __testing = {
|
||||
appendCodexAcpConfigOverrides,
|
||||
codexAcpSessionModelId,
|
||||
isCodexAcpCommand,
|
||||
normalizeCodexAcpModelOverride,
|
||||
};
|
||||
|
||||
export type { AcpAgentRegistry, AcpRuntimeOptions, AcpSessionRecord, AcpSessionStore };
|
||||
|
||||
@@ -316,6 +316,8 @@ export class AcpSessionManager {
|
||||
...(input.cwd !== undefined ? { cwd: input.cwd } : {}),
|
||||
});
|
||||
const requestedCwd = initialRuntimeOptions.cwd;
|
||||
const requestedModel = initialRuntimeOptions.model;
|
||||
const requestedThinking = initialRuntimeOptions.thinking;
|
||||
this.enforceConcurrentSessionLimit({
|
||||
cfg: input.cfg,
|
||||
sessionKey,
|
||||
@@ -327,6 +329,8 @@ export class AcpSessionManager {
|
||||
agent,
|
||||
mode: input.mode,
|
||||
resumeSessionId: input.resumeSessionId,
|
||||
...(requestedModel ? { model: requestedModel } : {}),
|
||||
...(requestedThinking ? { thinking: requestedThinking } : {}),
|
||||
cwd: requestedCwd,
|
||||
}),
|
||||
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||
@@ -1378,6 +1382,8 @@ export class AcpSessionManager {
|
||||
const mode = params.meta.mode;
|
||||
const runtimeOptions = resolveRuntimeOptionsFromMeta(params.meta);
|
||||
const cwd = runtimeOptions.cwd ?? normalizeText(params.meta.cwd);
|
||||
const model = normalizeText(runtimeOptions.model);
|
||||
const thinking = normalizeText(runtimeOptions.thinking);
|
||||
const configuredBackend = (params.meta.backend || params.cfg.acp?.backend || "").trim();
|
||||
const cached = this.getCachedRuntimeState(params.sessionKey);
|
||||
if (cached) {
|
||||
@@ -1434,6 +1440,8 @@ export class AcpSessionManager {
|
||||
agent,
|
||||
mode,
|
||||
...(resumeSessionId ? { resumeSessionId } : {}),
|
||||
...(model ? { model } : {}),
|
||||
...(thinking ? { thinking } : {}),
|
||||
cwd,
|
||||
}),
|
||||
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
||||
|
||||
@@ -104,7 +104,15 @@ function createRuntime(): {
|
||||
setConfigOption: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
const ensureSession = vi.fn(
|
||||
async (input: { sessionKey: string; agent: string; mode: "persistent" | "oneshot" }) => ({
|
||||
async (input: {
|
||||
sessionKey: string;
|
||||
agent: string;
|
||||
mode: "persistent" | "oneshot";
|
||||
model?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
resumeSessionId?: string;
|
||||
}) => ({
|
||||
sessionKey: input.sessionKey,
|
||||
backend: "acpx",
|
||||
runtimeSessionName: `${input.sessionKey}:${input.mode}:runtime`,
|
||||
@@ -1064,6 +1072,82 @@ describe("AcpSessionManager", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes persisted model runtime options into ensureSession after restart", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
id: "acpx",
|
||||
runtime: runtimeState.runtime,
|
||||
});
|
||||
const sessionKey = "agent:codex:acp:binding:demo-binding:default:model-restart";
|
||||
hoisted.readAcpSessionEntryMock.mockImplementation((paramsUnknown: unknown) => {
|
||||
const key = (paramsUnknown as { sessionKey?: string }).sessionKey ?? sessionKey;
|
||||
return {
|
||||
sessionKey: key,
|
||||
storeSessionKey: key,
|
||||
acp: {
|
||||
...readySessionMeta(),
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey,
|
||||
text: "after restart",
|
||||
mode: "prompt",
|
||||
requestId: "r-binding-restart-model",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
model: "openai-codex/gpt-5.4",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes persisted thinking runtime options into ensureSession after restart", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
id: "acpx",
|
||||
runtime: runtimeState.runtime,
|
||||
});
|
||||
const sessionKey = "agent:codex:acp:binding:demo-binding:default:thinking-restart";
|
||||
hoisted.readAcpSessionEntryMock.mockImplementation((paramsUnknown: unknown) => {
|
||||
const key = (paramsUnknown as { sessionKey?: string }).sessionKey ?? sessionKey;
|
||||
return {
|
||||
sessionKey: key,
|
||||
storeSessionKey: key,
|
||||
acp: {
|
||||
...readySessionMeta(),
|
||||
runtimeOptions: {
|
||||
thinking: "high",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await manager.runTurn({
|
||||
cfg: baseCfg,
|
||||
sessionKey,
|
||||
text: "after restart",
|
||||
mode: "prompt",
|
||||
requestId: "r-binding-restart-thinking",
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey,
|
||||
thinking: "high",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not resume persisted ACP identity for oneshot sessions after restart", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
@@ -1308,6 +1392,7 @@ describe("AcpSessionManager", () => {
|
||||
acp: readySessionMeta({
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1320,12 +1405,21 @@ describe("AcpSessionManager", () => {
|
||||
mode: "persistent",
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
},
|
||||
});
|
||||
|
||||
expect(extractRuntimeOptionsFromUpserts()).toContainEqual({
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
});
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves runtimeOptions cwd when initializeSession cwd is omitted", async () => {
|
||||
@@ -2601,6 +2695,7 @@ describe("AcpSessionManager", () => {
|
||||
runtimeOptions: {
|
||||
runtimeMode: "plan",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
permissionProfile: "strict",
|
||||
timeoutSeconds: 120,
|
||||
},
|
||||
@@ -2627,6 +2722,12 @@ describe("AcpSessionManager", () => {
|
||||
value: "openai-codex/gpt-5.4",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "thinking",
|
||||
value: "high",
|
||||
}),
|
||||
);
|
||||
expect(runtimeState.setConfigOption).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: "approval_policy",
|
||||
|
||||
@@ -8,6 +8,7 @@ export { normalizeText } from "../normalize-text.js";
|
||||
|
||||
const MAX_RUNTIME_MODE_LENGTH = 64;
|
||||
const MAX_MODEL_LENGTH = 200;
|
||||
const MAX_THINKING_LENGTH = 32;
|
||||
const MAX_PERMISSION_PROFILE_LENGTH = 80;
|
||||
const MAX_CWD_LENGTH = 4096;
|
||||
const MIN_TIMEOUT_SECONDS = 1;
|
||||
@@ -81,6 +82,14 @@ export function validateRuntimeModelInput(rawModel: unknown): string {
|
||||
});
|
||||
}
|
||||
|
||||
export function validateRuntimeThinkingInput(rawThinking: unknown): string {
|
||||
return validateBoundedText({
|
||||
value: rawThinking,
|
||||
field: "Thinking level",
|
||||
maxLength: MAX_THINKING_LENGTH,
|
||||
});
|
||||
}
|
||||
|
||||
export function validateRuntimePermissionProfileInput(rawProfile: unknown): string {
|
||||
return validateBoundedText({
|
||||
value: rawProfile,
|
||||
@@ -145,6 +154,7 @@ export function validateRuntimeOptionPatch(
|
||||
const allowedKeys = new Set([
|
||||
"runtimeMode",
|
||||
"model",
|
||||
"thinking",
|
||||
"cwd",
|
||||
"permissionProfile",
|
||||
"timeoutSeconds",
|
||||
@@ -171,6 +181,13 @@ export function validateRuntimeOptionPatch(
|
||||
next.model = validateRuntimeModelInput(rawPatch.model);
|
||||
}
|
||||
}
|
||||
if (Object.hasOwn(rawPatch, "thinking")) {
|
||||
if (rawPatch.thinking === undefined) {
|
||||
next.thinking = undefined;
|
||||
} else {
|
||||
next.thinking = validateRuntimeThinkingInput(rawPatch.thinking);
|
||||
}
|
||||
}
|
||||
if (Object.hasOwn(rawPatch, "cwd")) {
|
||||
if (rawPatch.cwd === undefined) {
|
||||
next.cwd = undefined;
|
||||
@@ -220,6 +237,7 @@ export function normalizeRuntimeOptions(
|
||||
): AcpSessionRuntimeOptions {
|
||||
const runtimeMode = normalizeText(options?.runtimeMode);
|
||||
const model = normalizeText(options?.model);
|
||||
const thinking = normalizeText(options?.thinking);
|
||||
const cwd = normalizeText(options?.cwd);
|
||||
const permissionProfile = normalizeText(options?.permissionProfile);
|
||||
let timeoutSeconds: number | undefined;
|
||||
@@ -237,6 +255,7 @@ export function normalizeRuntimeOptions(
|
||||
return {
|
||||
...(runtimeMode ? { runtimeMode } : {}),
|
||||
...(model ? { model } : {}),
|
||||
...(thinking ? { thinking } : {}),
|
||||
...(cwd ? { cwd } : {}),
|
||||
...(permissionProfile ? { permissionProfile } : {}),
|
||||
...(typeof timeoutSeconds === "number" ? { timeoutSeconds } : {}),
|
||||
@@ -287,6 +306,7 @@ export function buildRuntimeControlSignature(options: AcpSessionRuntimeOptions):
|
||||
return JSON.stringify({
|
||||
runtimeMode: normalized.runtimeMode ?? null,
|
||||
model: normalized.model ?? null,
|
||||
thinking: normalized.thinking ?? null,
|
||||
permissionProfile: normalized.permissionProfile ?? null,
|
||||
timeoutSeconds: normalized.timeoutSeconds ?? null,
|
||||
backendExtras: extras,
|
||||
@@ -301,6 +321,9 @@ export function buildRuntimeConfigOptionPairs(
|
||||
if (normalized.model) {
|
||||
pairs.set("model", normalized.model);
|
||||
}
|
||||
if (normalized.thinking) {
|
||||
pairs.set("thinking", normalized.thinking);
|
||||
}
|
||||
if (normalized.permissionProfile) {
|
||||
pairs.set("approval_policy", normalized.permissionProfile);
|
||||
}
|
||||
@@ -324,6 +347,13 @@ export function inferRuntimeOptionPatchFromConfigOption(
|
||||
if (normalizedKey === "model") {
|
||||
return { model: validateRuntimeModelInput(validated.value) };
|
||||
}
|
||||
if (
|
||||
normalizedKey === "thinking" ||
|
||||
normalizedKey === "thought_level" ||
|
||||
normalizedKey === "reasoning_effort"
|
||||
) {
|
||||
return { thinking: validateRuntimeThinkingInput(validated.value) };
|
||||
}
|
||||
if (
|
||||
normalizedKey === "approval_policy" ||
|
||||
normalizedKey === "permission_profile" ||
|
||||
|
||||
@@ -36,6 +36,10 @@ export type AcpRuntimeEnsureInput = {
|
||||
agent: string;
|
||||
mode: AcpRuntimeSessionMode;
|
||||
resumeSessionId?: string;
|
||||
/** Optional runtime model override that must be available during session creation. */
|
||||
model?: string;
|
||||
/** Optional runtime thinking/reasoning override that must be available during session creation. */
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
|
||||
@@ -716,12 +716,13 @@ describe("spawnAcpDirect", () => {
|
||||
expect(transcriptCalls[1]?.threadId).toBe("child-thread");
|
||||
});
|
||||
|
||||
it("passes model override into ACP session initialization", async () => {
|
||||
it("passes model and thinking overrides into ACP session initialization", async () => {
|
||||
const result = await spawnAcpDirect(
|
||||
{
|
||||
task: "Investigate flaky tests",
|
||||
agentId: "codex",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
},
|
||||
{
|
||||
agentSessionKey: "agent:main:main",
|
||||
@@ -735,6 +736,7 @@ describe("spawnAcpDirect", () => {
|
||||
agent: "codex",
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
thinking: "high",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -98,6 +98,7 @@ export type SpawnAcpParams = {
|
||||
agentId?: string;
|
||||
resumeSessionId?: string;
|
||||
model?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
mode?: SpawnAcpMode;
|
||||
thread?: boolean;
|
||||
@@ -826,6 +827,7 @@ async function initializeAcpSpawnRuntime(params: {
|
||||
runtimeMode: AcpRuntimeSessionMode;
|
||||
resumeSessionId?: string;
|
||||
model?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
}): Promise<AcpSpawnInitializedRuntime> {
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.targetAgentId });
|
||||
@@ -850,7 +852,13 @@ async function initializeAcpSpawnRuntime(params: {
|
||||
agent: params.targetAgentId,
|
||||
mode: params.runtimeMode,
|
||||
resumeSessionId: params.resumeSessionId,
|
||||
runtimeOptions: params.model ? { model: params.model } : undefined,
|
||||
runtimeOptions:
|
||||
params.model || params.thinking
|
||||
? {
|
||||
...(params.model ? { model: params.model } : {}),
|
||||
...(params.thinking ? { thinking: params.thinking } : {}),
|
||||
}
|
||||
: undefined,
|
||||
cwd: params.cwd,
|
||||
backendId: params.cfg.acp?.backend,
|
||||
});
|
||||
@@ -1191,6 +1199,7 @@ export async function spawnAcpDirect(
|
||||
runtimeMode,
|
||||
resumeSessionId: params.resumeSessionId,
|
||||
model: params.model,
|
||||
thinking: params.thinking,
|
||||
cwd: runtimeCwd,
|
||||
});
|
||||
initializedRuntime = initializedSession.runtimeCloseHandle;
|
||||
|
||||
@@ -259,6 +259,7 @@ export function createSessionsSpawnTool(
|
||||
agentId: requestedAgentId,
|
||||
resumeSessionId,
|
||||
model: modelOverride,
|
||||
thinking: thinkingOverrideRaw,
|
||||
cwd,
|
||||
mode: mode === "run" || mode === "session" ? mode : undefined,
|
||||
thread,
|
||||
|
||||
@@ -58,6 +58,8 @@ export type AcpSessionRuntimeOptions = {
|
||||
runtimeMode?: string;
|
||||
/** ACP runtime config option: model id. */
|
||||
model?: string;
|
||||
/** ACP runtime config option: thinking/reasoning effort. */
|
||||
thinking?: string;
|
||||
/** Working directory override for ACP session turns. */
|
||||
cwd?: string;
|
||||
/** ACP runtime config option: permission profile id. */
|
||||
|
||||
Reference in New Issue
Block a user