mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 18:35:15 +00:00
* refactor: migrate validators to typebox * fix: preserve json schema resource refs * chore: clean schema preflight recursion * refactor: remove lobster ajv shim * fix: support schema array refs * fix: validate schema dependencies * fix: preserve schema contract checks * fix: support same-document schema refs * fix: preserve untyped map defaults * fix: preserve schema default semantics * test: avoid thenable schema literals * test: build conditional schema key * fix: defer resource id refs to typebox * fix: reject invalid schema enum metadata * fix: preserve default branch semantics * fix: resolve schema resource refs * fix: narrow conditional default fallback * fix: preserve uri format validation * fix: preserve validator compatibility * test: avoid ajv cache lint violation * fix: preserve typebox validation diagnostics * fix: validate defaulted conditional schemas * fix: normalize mcp draft schemas * fix: preserve tuple schema defaults * fix: resolve relative schema refs * fix: scope typebox format semantics * fix: align conditional format defaults * fix: decode schema pointer refs * fix: filter grouped secretref diagnostics * fix: preserve default conditional compatibility * fix: preserve nullable schema compatibility * fix: settle defaults before conditionals * fix: preserve default validation invariants * fix: validate dynamic schema refs * fix: reject malformed nullable schemas
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
readCodexModelListResponse,
|
|
readCodexTurn,
|
|
assertCodexThreadStartResponse,
|
|
assertCodexThreadResumeResponse,
|
|
} from "./protocol-validators.js";
|
|
|
|
function makeMinimalThread(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: "thread-1",
|
|
sessionId: "session-1",
|
|
cliVersion: "0.129.0",
|
|
createdAt: 1715299200,
|
|
updatedAt: 1715299200,
|
|
cwd: "/tmp",
|
|
ephemeral: false,
|
|
modelProvider: "openai",
|
|
preview: "test thread",
|
|
source: "appServer",
|
|
status: { type: "notLoaded" },
|
|
turns: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeMinimalResponse(threadOverrides: Record<string, unknown> = {}) {
|
|
return {
|
|
approvalPolicy: "never",
|
|
approvalsReviewer: "user",
|
|
cwd: "/tmp",
|
|
model: "gpt-5.4",
|
|
modelProvider: "openai",
|
|
sandbox: { type: "dangerFullAccess" },
|
|
thread: makeMinimalThread(threadOverrides),
|
|
};
|
|
}
|
|
|
|
describe("assertCodexThreadStartResponse", () => {
|
|
it("accepts response with both id and sessionId", () => {
|
|
const response = makeMinimalResponse();
|
|
const result = assertCodexThreadStartResponse(response);
|
|
expect(result.thread.id).toBe("thread-1");
|
|
expect(result.thread.sessionId).toBe("session-1");
|
|
});
|
|
|
|
it("normalizes missing sessionId from id", () => {
|
|
const response = makeMinimalResponse({ sessionId: undefined });
|
|
// Remove the sessionId key entirely
|
|
delete (response.thread as Record<string, unknown>).sessionId;
|
|
const result = assertCodexThreadStartResponse(response);
|
|
expect(result.thread.id).toBe("thread-1");
|
|
expect(result.thread.sessionId).toBe("thread-1");
|
|
});
|
|
|
|
it("normalizes missing id from sessionId", () => {
|
|
const response = makeMinimalResponse({ id: undefined, sessionId: "session-1" });
|
|
delete (response.thread as Record<string, unknown>).id;
|
|
const result = assertCodexThreadStartResponse(response);
|
|
expect(result.thread.id).toBe("session-1");
|
|
expect(result.thread.sessionId).toBe("session-1");
|
|
});
|
|
|
|
it("throws on invalid response", () => {
|
|
expect(() => assertCodexThreadStartResponse({})).toThrow("Invalid Codex app-server");
|
|
});
|
|
});
|
|
|
|
describe("assertCodexThreadResumeResponse", () => {
|
|
it("normalizes missing sessionId from id", () => {
|
|
const response = makeMinimalResponse({ sessionId: undefined });
|
|
delete (response.thread as Record<string, unknown>).sessionId;
|
|
const result = assertCodexThreadResumeResponse(response);
|
|
expect(result.thread.id).toBe("thread-1");
|
|
expect(result.thread.sessionId).toBe("thread-1");
|
|
});
|
|
});
|
|
|
|
describe("readCodexModelListResponse", () => {
|
|
it("applies defaults from generated schemas behind local refs", () => {
|
|
const response = readCodexModelListResponse({
|
|
data: [
|
|
{
|
|
id: "gpt-test",
|
|
model: "gpt-test",
|
|
displayName: "GPT Test",
|
|
description: "test model",
|
|
hidden: false,
|
|
isDefault: false,
|
|
defaultReasoningEffort: "medium",
|
|
supportedReasoningEfforts: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
const model = response?.data[0] as
|
|
| (NonNullable<ReturnType<typeof readCodexModelListResponse>>["data"][number] & {
|
|
serviceTiers?: unknown;
|
|
supportsPersonality?: unknown;
|
|
})
|
|
| undefined;
|
|
expect(model?.inputModalities).toEqual(["text", "image"]);
|
|
expect(model?.serviceTiers).toEqual([]);
|
|
expect(model?.supportsPersonality).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("readCodexTurn", () => {
|
|
it("does not merge defaults from unrelated thread item union branches", () => {
|
|
const turn = readCodexTurn({
|
|
id: "turn-1",
|
|
status: "completed",
|
|
items: [{ id: "item-1", type: "plan", text: "ship it" }],
|
|
});
|
|
|
|
expect(turn?.items[0]).toEqual({ id: "item-1", type: "plan", text: "ship it" });
|
|
});
|
|
|
|
it("accepts nullable arrays in generated dynamic tool call items", () => {
|
|
const turn = readCodexTurn({
|
|
id: "turn-1",
|
|
status: "completed",
|
|
items: [
|
|
{
|
|
arguments: {},
|
|
contentItems: null,
|
|
id: "item-1",
|
|
status: "completed",
|
|
tool: "render",
|
|
type: "dynamicToolCall",
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(turn?.items[0]).toMatchObject({
|
|
contentItems: null,
|
|
id: "item-1",
|
|
type: "dynamicToolCall",
|
|
});
|
|
});
|
|
});
|