fix(providers): handle proxied DeepSeek V4 replay

This commit is contained in:
Peter Steinberger
2026-04-25 19:23:06 +01:00
parent b8a41739d5
commit 31456e3326
7 changed files with 254 additions and 2 deletions

View File

@@ -686,6 +686,128 @@ describe("skill-workshop", () => {
);
});
it("uses the configured agent default for reviewer fallback", async () => {
const workspaceDir = await makeTempDir();
const stateDir = await makeTempDir();
const runEmbeddedPiAgent = vi.fn(async () => ({
payloads: [{ text: JSON.stringify({ action: "none" }) }],
meta: {},
}));
const api = createTestPluginApi({
config: {
agents: {
defaults: {
model: { primary: "openai-codex/gpt-5.5" },
},
},
},
runtime: {
agent: {
defaults: { provider: "openai", model: "gpt-5.4" },
resolveAgentDir: () => path.join(workspaceDir, ".agent"),
runEmbeddedPiAgent,
},
state: {
resolveStateDir: () => stateDir,
},
} as never,
});
await reviewTranscriptForProposal({
api,
config: {
enabled: true,
autoCapture: true,
approvalPolicy: "pending",
reviewMode: "llm",
reviewInterval: 1,
reviewMinToolCalls: 1,
reviewTimeoutMs: 5_000,
maxPending: 50,
maxSkillBytes: 40_000,
},
ctx: { agentId: "main", workspaceDir },
messages: [{ role: "user", content: "Remember this repeatable fix." }],
});
expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai-codex",
model: "gpt-5.5",
}),
);
});
it("infers reviewer fallback provider for a bare configured model", async () => {
const workspaceDir = await makeTempDir();
const stateDir = await makeTempDir();
const runEmbeddedPiAgent = vi.fn(async () => ({
payloads: [{ text: JSON.stringify({ action: "none" }) }],
meta: {},
}));
const api = createTestPluginApi({
config: {
agents: {
defaults: {
model: { primary: "gpt-5.5" },
},
},
models: {
providers: {
"openai-codex": {
baseUrl: "https://chatgpt.com/backend-api/codex",
models: [
{
id: "gpt-5.5",
name: "GPT 5.5",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 128_000,
},
],
},
},
},
},
runtime: {
agent: {
defaults: { provider: "openai", model: "gpt-5.4" },
resolveAgentDir: () => path.join(workspaceDir, ".agent"),
runEmbeddedPiAgent,
},
state: {
resolveStateDir: () => stateDir,
},
} as never,
});
await reviewTranscriptForProposal({
api,
config: {
enabled: true,
autoCapture: true,
approvalPolicy: "pending",
reviewMode: "llm",
reviewInterval: 1,
reviewMinToolCalls: 1,
reviewTimeoutMs: 5_000,
maxPending: 50,
maxSkillBytes: 40_000,
},
ctx: { agentId: "main", workspaceDir },
messages: [{ role: "user", content: "Remember this bare-model default." }],
});
expect(runEmbeddedPiAgent).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai-codex",
model: "gpt-5.5",
}),
);
});
it("runs reviewer after threshold and queues the proposal", async () => {
const workspaceDir = await makeTempDir();
const stateDir = await makeTempDir();

View File

@@ -1,6 +1,10 @@
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import {
resolveAgentEffectiveModelPrimary,
resolveDefaultModelForAgent,
} from "openclaw/plugin-sdk/agent-runtime";
import type { OpenClawPluginApi } from "../api.js";
import type { SkillWorkshopConfig } from "./config.js";
import { normalizeSkillName } from "./skills.js";
@@ -34,6 +38,22 @@ type ReviewerJson = {
newText?: string;
};
function resolveReviewerFallbackModel(params: { api: OpenClawPluginApi; agentId: string }): {
provider: string;
model: string;
} {
if (resolveAgentEffectiveModelPrimary(params.api.config, params.agentId)) {
return resolveDefaultModelForAgent({
cfg: params.api.config,
agentId: params.agentId,
});
}
return {
provider: params.api.runtime.agent.defaults.provider,
model: params.api.runtime.agent.defaults.model,
};
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -224,6 +244,10 @@ export async function reviewTranscriptForProposal(params: {
});
const sessionId = `skill-workshop-review-${randomUUID()}`;
const stateDir = params.api.runtime.state.resolveStateDir();
const fallbackModel = resolveReviewerFallbackModel({
api: params.api,
agentId: params.ctx.agentId,
});
const result = await params.api.runtime.agent.runEmbeddedPiAgent({
sessionId,
sessionKey: params.ctx.sessionKey,
@@ -235,8 +259,8 @@ export async function reviewTranscriptForProposal(params: {
agentDir: params.api.runtime.agent.resolveAgentDir(params.api.config, params.ctx.agentId),
config: params.api.config,
prompt,
provider: params.ctx.modelProviderId ?? params.api.runtime.agent.defaults.provider,
model: params.ctx.modelId ?? params.api.runtime.agent.defaults.model,
provider: params.ctx.modelProviderId ?? fallbackModel.provider,
model: params.ctx.modelId ?? fallbackModel.model,
timeoutMs: params.config.reviewTimeoutMs,
runId: sessionId,
trigger: "manual",