fix: strip opencode image reasoning none

This commit is contained in:
Peter Steinberger
2026-04-25 04:35:00 +01:00
parent 96515891a2
commit 6f72b74cec
8 changed files with 203 additions and 5 deletions

View File

@@ -1,8 +1,29 @@
import { describe, it } from "vitest";
import { describe, expect, it } from "vitest";
import { registerProviderPlugin } from "../../test/helpers/plugins/provider-registration.js";
import { expectPassthroughReplayPolicy } from "../../test/helpers/provider-replay-policy.ts";
import plugin from "./index.js";
describe("opencode provider plugin", () => {
it("registers image media understanding through the OpenCode plugin", async () => {
const { mediaProviders } = await registerProviderPlugin({
plugin,
id: "opencode",
name: "OpenCode Zen Provider",
});
expect(mediaProviders).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "opencode",
capabilities: ["image"],
defaultModels: { image: "gpt-5-nano" },
describeImage: expect.any(Function),
describeImages: expect.any(Function),
}),
]),
);
});
it("owns passthrough-gemini replay policy for Gemini-backed models", async () => {
await expectPassthroughReplayPolicy({
plugin,

View File

@@ -6,6 +6,7 @@ import {
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { applyOpencodeZenConfig, OPENCODE_ZEN_DEFAULT_MODEL } from "./api.js";
import { opencodeMediaUnderstandingProvider } from "./media-understanding-provider.js";
const PROVIDER_ID = "opencode";
const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m2.7"] as const;
@@ -49,5 +50,6 @@ export default definePluginEntry({
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId),
});
api.registerMediaUnderstandingProvider(opencodeMediaUnderstandingProvider);
},
});

View File

@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import {
opencodeMediaUnderstandingProvider,
stripOpencodeDisabledResponsesReasoningPayload,
} from "./media-understanding-provider.js";
describe("opencode media understanding provider", () => {
it("strips disabled Responses reasoning payloads", () => {
const payload = {
reasoning: { effort: "none" },
include: ["reasoning.encrypted_content"],
store: false,
};
stripOpencodeDisabledResponsesReasoningPayload(payload);
expect(payload).toEqual({
include: ["reasoning.encrypted_content"],
store: false,
});
});
it("keeps supported Responses reasoning payloads", () => {
const payload = {
reasoning: { effort: "low" },
store: false,
};
stripOpencodeDisabledResponsesReasoningPayload(payload);
expect(payload).toEqual({
reasoning: { effort: "low" },
store: false,
});
});
it("declares OpenCode image understanding support", () => {
expect(opencodeMediaUnderstandingProvider).toEqual(
expect.objectContaining({
id: "opencode",
capabilities: ["image"],
defaultModels: { image: "gpt-5-nano" },
describeImage: expect.any(Function),
describeImages: expect.any(Function),
}),
);
});
});

View File

@@ -0,0 +1,42 @@
import type { ProviderStreamOptions } from "@mariozechner/pi-ai";
import {
describeImageWithModelPayloadTransform,
describeImagesWithModelPayloadTransform,
type MediaUnderstandingProvider,
} from "openclaw/plugin-sdk/media-understanding";
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
export function stripOpencodeDisabledResponsesReasoningPayload(payload: unknown): void {
if (!isRecord(payload)) {
return;
}
const reasoning = payload.reasoning;
if (reasoning === "none") {
delete payload.reasoning;
return;
}
if (!isRecord(reasoning) || reasoning.effort !== "none") {
return;
}
delete payload.reasoning;
}
const stripDisabledResponsesReasoning: ProviderStreamOptions["onPayload"] = (payload) => {
stripOpencodeDisabledResponsesReasoningPayload(payload);
return undefined;
};
export const opencodeMediaUnderstandingProvider: MediaUnderstandingProvider = {
id: "opencode",
capabilities: ["image"],
defaultModels: {
image: "gpt-5-nano",
},
describeImage: (request) =>
describeImageWithModelPayloadTransform(request, stripDisabledResponsesReasoning),
describeImages: (request) =>
describeImagesWithModelPayloadTransform(request, stripDisabledResponsesReasoning),
};