mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(agents): respect compat.supportsStore in WebSocket stream path (#39113)
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
This commit is contained in:
@@ -261,6 +261,7 @@ Docs: https://docs.openclaw.ai
|
||||
- UI/Control chat tool streaming: render tool events live in webchat without requiring refresh by enabling `tool-events` capability, fixing stream/event correlation, and resetting/reloading stream state around tool results and terminal events. (#39104) Thanks @jakepresent.
|
||||
- Models/provider apiKey persistence hardening: when a provider `apiKey` value equals a known provider env var value, persist the canonical env var name into `models.json` instead of resolved plaintext secrets. (#38889) Thanks @gambletan.
|
||||
- Discord/model picker persistence check: add a short post-dispatch settle delay before reading back session model state so picker confirmations stop reporting false mismatch warnings after successful model switches. (#39105) Thanks @akropp.
|
||||
- Agents/OpenAI WS compat store flag: omit `store` from `response.create` payloads when model compat sets `supportsStore: false`, preventing strict OpenAI-compatible providers from rejecting websocket requests with unknown-field errors. (#39113) Thanks @scoootscooob.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
||||
@@ -634,6 +634,8 @@ describe("createOpenAIWebSocketStreamFn", () => {
|
||||
releaseWsSession("sess-incremental");
|
||||
releaseWsSession("sess-full");
|
||||
releaseWsSession("sess-tools");
|
||||
releaseWsSession("sess-store-default");
|
||||
releaseWsSession("sess-store-compat");
|
||||
});
|
||||
|
||||
it("connects to the WebSocket on first call", async () => {
|
||||
@@ -691,6 +693,73 @@ describe("createOpenAIWebSocketStreamFn", () => {
|
||||
expect(Array.isArray(sent.input)).toBe(true);
|
||||
});
|
||||
|
||||
it("includes store:false by default", async () => {
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-store-default");
|
||||
const stream = streamFn(
|
||||
modelStub as Parameters<typeof streamFn>[0],
|
||||
contextStub as Parameters<typeof streamFn>[1],
|
||||
);
|
||||
|
||||
const completed = new Promise<void>((res, rej) => {
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
await new Promise((r) => setImmediate(r));
|
||||
const manager = MockManager.lastInstance!;
|
||||
manager.simulateEvent({
|
||||
type: "response.completed",
|
||||
response: makeResponseObject("resp_store_default", "ok"),
|
||||
});
|
||||
for await (const _ of await resolveStream(stream)) {
|
||||
// consume
|
||||
}
|
||||
res();
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
await completed;
|
||||
|
||||
const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
|
||||
expect(sent.store).toBe(false);
|
||||
});
|
||||
|
||||
it("omits store when compat.supportsStore is false (#39086)", async () => {
|
||||
releaseWsSession("sess-store-compat");
|
||||
const noStoreModel = {
|
||||
...modelStub,
|
||||
compat: { supportsStore: false },
|
||||
};
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-store-compat");
|
||||
const stream = streamFn(
|
||||
noStoreModel as Parameters<typeof streamFn>[0],
|
||||
contextStub as Parameters<typeof streamFn>[1],
|
||||
);
|
||||
|
||||
const completed = new Promise<void>((res, rej) => {
|
||||
queueMicrotask(async () => {
|
||||
try {
|
||||
await new Promise((r) => setImmediate(r));
|
||||
const manager = MockManager.lastInstance!;
|
||||
manager.simulateEvent({
|
||||
type: "response.completed",
|
||||
response: makeResponseObject("resp_no_store", "ok"),
|
||||
});
|
||||
for await (const _ of await resolveStream(stream)) {
|
||||
// consume
|
||||
}
|
||||
res();
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
await completed;
|
||||
|
||||
const sent = MockManager.lastInstance!.sentEvents[0] as Record<string, unknown>;
|
||||
expect(sent).not.toHaveProperty("store");
|
||||
});
|
||||
|
||||
it("emits an AssistantMessage on response.completed", async () => {
|
||||
const streamFn = createOpenAIWebSocketStreamFn("sk-test", "sess-2");
|
||||
const stream = streamFn(
|
||||
|
||||
@@ -589,10 +589,15 @@ export function createOpenAIWebSocketStreamFn(
|
||||
extraParams.reasoning = reasoning;
|
||||
}
|
||||
|
||||
// Respect compat.supportsStore — providers like Gemini reject unknown
|
||||
// fields such as `store` with a 400 error. Fixes #39086.
|
||||
const supportsStore = (model as { compat?: { supportsStore?: boolean } }).compat
|
||||
?.supportsStore;
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
type: "response.create",
|
||||
model: model.id,
|
||||
store: false,
|
||||
...(supportsStore !== false ? { store: false } : {}),
|
||||
input: inputItems,
|
||||
instructions: context.systemPrompt ?? undefined,
|
||||
tools: tools.length > 0 ? tools : undefined,
|
||||
|
||||
Reference in New Issue
Block a user