mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 05:45:45 +00:00
refactor: consolidate coercion contracts (#122458)
* refactor: consolidate coercion contracts Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics. Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit. * fix: preserve standalone script coercions Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
This commit is contained in:
parent
66fe424590
commit
b080dd1e76
276 changed files with 1685 additions and 1663 deletions
|
|
@ -24,6 +24,7 @@ import type {
|
|||
SessionTranscriptTargetParams,
|
||||
TranscriptTurnAdmission,
|
||||
} from "openclaw/plugin-sdk/session-transcript-runtime";
|
||||
import { readNonBlankString as readNonEmptyString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { EmbeddedRunAttemptResult } from "./attempt-terminal.js";
|
||||
import type { CodexDynamicToolFunctionSpec, CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
||||
import { flattenCodexDynamicToolFunctions } from "./protocol.js";
|
||||
|
|
@ -502,10 +503,6 @@ function readPositiveNumber(value: unknown): number | undefined {
|
|||
: undefined;
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds OpenClaw-provided workspace prompt context for the current Codex turn.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { resolveAgentDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
||||
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isIncognitoSessionKey } from "../incognito-session.js";
|
||||
|
|
@ -963,13 +964,10 @@ function isCodexThreadNotFoundError(error: unknown): boolean {
|
|||
// compaction.rs asserts message.contains("thread not found")). So the message
|
||||
// is the authoritative positive signal here, not the generic code. This is a
|
||||
// self-heal recovery gate, not user-facing classification.
|
||||
return formatCompactionError(error).toLowerCase().includes("thread not found");
|
||||
return coerceErrorMessage(error).toLowerCase().includes("thread not found");
|
||||
}
|
||||
|
||||
function formatCompactionError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
return coerceErrorMessage(error);
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { createHmac, randomBytes } from "node:crypto";
|
|||
import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import {
|
||||
asOptionalRecord as readRecord,
|
||||
normalizeOptionalString as readNonEmptyString,
|
||||
normalizeTrimmedStringList,
|
||||
parseBooleanValue,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
|
@ -12,11 +14,7 @@ const START_OPTIONS_KEY_SECRET_SYMBOL = Symbol.for("openclaw.codexAppServerStart
|
|||
const START_OPTIONS_KEY_SECRET = getStartOptionsKeySecret();
|
||||
const PLAIN_DECIMAL_NUMBER_RE = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))$/;
|
||||
|
||||
export function readRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
export { readNonEmptyString, readRecord };
|
||||
|
||||
export function normalizeCodexServiceTier(value: unknown): CodexServiceTier | undefined {
|
||||
if (typeof value !== "string") {
|
||||
|
|
@ -108,14 +106,6 @@ export function resolveArgs(configArgs: unknown, envArgs: string | undefined): s
|
|||
return splitShellWords(envArgs ?? "");
|
||||
}
|
||||
|
||||
export function readNonEmptyString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function hashSecretForKey(value: string | undefined, label: string): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|||
import type { ImageContent, TextContent } from "openclaw/plugin-sdk/llm";
|
||||
import { normalizeOpenAIToolSchemas } from "openclaw/plugin-sdk/provider-tools";
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
asOptionalRecord,
|
||||
isRecord,
|
||||
normalizeOptionalString,
|
||||
|
|
@ -557,7 +558,7 @@ export function createCodexDynamicToolBridge(params: {
|
|||
handleToolCall: async (call, options) => {
|
||||
const toolEntry = toolMap.get(call.tool);
|
||||
if (!toolEntry) {
|
||||
const executedArguments = jsonObjectToRecord(call.arguments);
|
||||
const executedArguments = asNonArrayRecord(call.arguments);
|
||||
const message = registeredToolNames.has(call.tool)
|
||||
? `OpenClaw tool is not available for this turn: ${call.tool}`
|
||||
: `Unknown OpenClaw tool: ${call.tool}`;
|
||||
|
|
@ -582,7 +583,7 @@ export function createCodexDynamicToolBridge(params: {
|
|||
});
|
||||
}
|
||||
const { tool, name: toolName } = toolEntry;
|
||||
const args = jsonObjectToRecord(call.arguments);
|
||||
const args = asNonArrayRecord(call.arguments);
|
||||
const startedAt = Date.now();
|
||||
const signal = composeAbortSignals(params.signal, options?.signal);
|
||||
let didStartExecution = false;
|
||||
|
|
@ -1530,12 +1531,6 @@ function convertToolContent(
|
|||
},
|
||||
];
|
||||
}
|
||||
function jsonObjectToRecord(value: JsonValue | undefined): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
function readFirstString(record: Record<string, unknown>, keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import {
|
|||
formatToolAggregate,
|
||||
formatToolProgressOutput,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { readStringField as readString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asNonArrayRecord,
|
||||
readStringField as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { isJsonObject, type CodexThreadItem } from "./protocol.js";
|
||||
|
||||
|
|
@ -104,10 +107,7 @@ export function toolOutputRawEchoSignature(
|
|||
}
|
||||
|
||||
export function normalizeToolTranscriptArguments(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
return asNonArrayRecord(value);
|
||||
}
|
||||
|
||||
export function collectDynamicToolContentText(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { normalizeUsage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
asSafeIntegerInRange,
|
||||
readStringField as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { readNonNegativeInteger } from "./event-projector-values.js";
|
||||
import { isJsonObject, type JsonObject } from "./protocol.js";
|
||||
|
||||
function readTokenCount(record: JsonObject, key: string): number | undefined {
|
||||
const value = readNonNegativeInteger(record, key);
|
||||
return value !== undefined && Number.isSafeInteger(value) ? value : undefined;
|
||||
return asSafeIntegerInRange(record[key], { min: 0 });
|
||||
}
|
||||
|
||||
function readCodexThreadTokenUsage(params: JsonObject): ReturnType<typeof normalizeUsage> {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import { asFiniteNumber, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
normalizeOptionalString,
|
||||
readStringField,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isJsonObject, type CodexThreadItem, type JsonObject, type JsonValue } from "./protocol.js";
|
||||
|
||||
export function normalizeNonEmptyString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
return value.trim() || undefined;
|
||||
}
|
||||
export { normalizeOptionalString as normalizeNonEmptyString };
|
||||
|
||||
export function readNonEmptyString(record: JsonObject, key: string): string | undefined {
|
||||
return normalizeNonEmptyString(record[key]);
|
||||
return normalizeOptionalString(record[key]);
|
||||
}
|
||||
|
||||
export function readNonEmptyStringArray(record: JsonObject, key: string): string[] {
|
||||
|
|
@ -19,7 +18,7 @@ export function readNonEmptyStringArray(record: JsonObject, key: string): string
|
|||
}
|
||||
const entries: string[] = [];
|
||||
for (const entry of value) {
|
||||
const normalized = normalizeNonEmptyString(entry);
|
||||
const normalized = normalizeOptionalString(entry);
|
||||
if (normalized) {
|
||||
entries.push(normalized);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Lists and normalizes models exposed by the Codex app-server `model/list`
|
||||
* endpoint, including pagination and shared-client lease handling.
|
||||
*/
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
CodexAppServerAuthRequirement,
|
||||
resolveCodexAppServerAuthProfileIdForAgent,
|
||||
|
|
@ -145,8 +145,8 @@ export function readModelListResult(value: unknown): CodexAppServerModelListResu
|
|||
}
|
||||
|
||||
function readCodexModel(value: CodexModel): CodexAppServerModel {
|
||||
const id = readNonEmptyString(value.id);
|
||||
const model = readNonEmptyString(value.model);
|
||||
const id = normalizeOptionalString(value.id);
|
||||
const model = normalizeOptionalString(value.model);
|
||||
if (!id || !model) {
|
||||
throw new Error(
|
||||
"Invalid Codex app-server model/list response: model id and name must be non-empty strings",
|
||||
|
|
@ -155,37 +155,29 @@ function readCodexModel(value: CodexModel): CodexAppServerModel {
|
|||
return {
|
||||
id,
|
||||
model,
|
||||
...(readNonEmptyString(value.displayName)
|
||||
? { displayName: readNonEmptyString(value.displayName) }
|
||||
...(normalizeOptionalString(value.displayName)
|
||||
? { displayName: normalizeOptionalString(value.displayName) }
|
||||
: {}),
|
||||
...(readNonEmptyString(value.description)
|
||||
? { description: readNonEmptyString(value.description) }
|
||||
...(normalizeOptionalString(value.description)
|
||||
? { description: normalizeOptionalString(value.description) }
|
||||
: {}),
|
||||
hidden: value.hidden,
|
||||
isDefault: value.isDefault,
|
||||
inputModalities: value.inputModalities,
|
||||
supportedReasoningEfforts: readReasoningEfforts(value.supportedReasoningEfforts),
|
||||
...(readNonEmptyString(value.defaultReasoningEffort)
|
||||
? { defaultReasoningEffort: readNonEmptyString(value.defaultReasoningEffort) }
|
||||
...(normalizeOptionalString(value.defaultReasoningEffort)
|
||||
? { defaultReasoningEffort: normalizeOptionalString(value.defaultReasoningEffort) }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
function readReasoningEfforts(value: CodexReasoningEffortOption[]): string[] {
|
||||
const efforts = value
|
||||
.map((entry) => readNonEmptyString(entry.reasoningEffort))
|
||||
.map((entry) => normalizeOptionalString(entry.reasoningEffort))
|
||||
.filter((entry): entry is string => entry !== undefined);
|
||||
return uniqueStrings(efforts);
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function normalizeMaxPages(value: unknown): number {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 20;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -715,17 +715,11 @@ function mergeJsonObjects(left: JsonObject, right: JsonObject): JsonObject {
|
|||
for (const [key, value] of Object.entries(right)) {
|
||||
const existing = merged[key];
|
||||
merged[key] =
|
||||
isPlainJsonObject(existing) && isPlainJsonObject(value)
|
||||
? mergeJsonObjects(existing, value)
|
||||
: value;
|
||||
isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function isPlainJsonObject(value: JsonValue | undefined): value is JsonObject {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function fingerprintJson(value: JsonValue): string {
|
||||
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { CodexCommandExecParams, CodexCommandExecResponse } from "./command-exec-protocol.js";
|
||||
import type {
|
||||
CodexAppInfo,
|
||||
|
|
@ -707,7 +708,7 @@ type CodexAppServerRequestResultMap = {
|
|||
};
|
||||
|
||||
export function isJsonObject(value: unknown): value is JsonObject {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
return isRecord(value);
|
||||
}
|
||||
|
||||
export function isRpcResponse(message: RpcMessage): message is RpcResponse {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { JsonValue } from "./protocol.js";
|
||||
import { isJsonObject, type JsonValue } from "./protocol.js";
|
||||
|
||||
/** RPC error wrapper that preserves app-server error code and data. */
|
||||
export class CodexAppServerRpcError extends Error {
|
||||
|
|
@ -36,7 +36,3 @@ function readCodexAppServerRpcReloginDetail(data: JsonValue | undefined): string
|
|||
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
||||
return isRelogin && detail ? detail : undefined;
|
||||
}
|
||||
|
||||
function isJsonObject(value: unknown): value is { [key: string]: JsonValue } {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Buffer } from "node:buffer";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { JsonValue } from "./protocol.js";
|
||||
import { readUpstreamUserText } from "./upstream-prompt-provenance.js";
|
||||
|
||||
|
|
@ -18,10 +18,6 @@ type ProjectedMessageGroup = {
|
|||
bytes: number;
|
||||
};
|
||||
|
||||
function readNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
||||
function readBoundedText(
|
||||
value: unknown,
|
||||
label: string,
|
||||
|
|
@ -49,7 +45,7 @@ function responseItemBytes(item: JsonValue): number {
|
|||
}
|
||||
|
||||
function requireCallId(value: unknown): string {
|
||||
const callId = readNonEmptyString(value);
|
||||
const callId = normalizeOptionalString(value);
|
||||
if (!callId || callId.length > 256) {
|
||||
throw new Error("Codex settled-turn projection found an invalid tool call id");
|
||||
}
|
||||
|
|
@ -57,7 +53,7 @@ function requireCallId(value: unknown): string {
|
|||
}
|
||||
|
||||
function requireToolName(value: unknown): string {
|
||||
const name = readNonEmptyString(value);
|
||||
const name = normalizeOptionalString(value);
|
||||
if (!name || !TOOL_NAME_PATTERN.test(name)) {
|
||||
throw new Error("Codex settled-turn projection found an invalid tool name");
|
||||
}
|
||||
|
|
@ -207,7 +203,7 @@ function projectToolResult(message: Record<string, unknown>): {
|
|||
throw new Error("Codex settled-turn projection found malformed tool result content");
|
||||
}
|
||||
if (value.type === "image") {
|
||||
const mimeType = readNonEmptyString(value.mimeType) ?? "unknown type";
|
||||
const mimeType = normalizeOptionalString(value.mimeType) ?? "unknown type";
|
||||
// The finalizer selects by text capability. Preserve image evidence as
|
||||
// metadata without embedding an executable or oversized multimodal payload.
|
||||
parts.push(`[Image tool result: ${mimeType}]`);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
isActiveHarnessContextEngine,
|
||||
type EmbeddedRunAttemptParamsV2 as EmbeddedRunAttemptParams,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
resolveCodexContextEngineProjectionMaxChars,
|
||||
resolveCodexContextEngineProjectionReserveTokens,
|
||||
|
|
@ -88,16 +89,12 @@ function areContextEngineProjectionBindingsCompatible(
|
|||
}
|
||||
|
||||
function resolveContextEngineCitationsMode(config: unknown): JsonValue | undefined {
|
||||
const rootConfig = isUnknownRecord(config) ? config : undefined;
|
||||
const memoryConfig = isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : undefined;
|
||||
const rootConfig = isRecord(config) ? config : undefined;
|
||||
const memoryConfig = isRecord(rootConfig?.memory) ? rootConfig.memory : undefined;
|
||||
const citations = memoryConfig?.citations;
|
||||
return isJsonConfigValue(citations) ? citations : undefined;
|
||||
}
|
||||
|
||||
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function isJsonConfigValue(value: unknown): value is JsonValue {
|
||||
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
||||
return true;
|
||||
|
|
@ -108,5 +105,5 @@ function isJsonConfigValue(value: unknown): value is JsonValue {
|
|||
if (Array.isArray(value)) {
|
||||
return value.every(isJsonConfigValue);
|
||||
}
|
||||
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
||||
return isRecord(value) && Object.values(value).every(isJsonConfigValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
deleteSessionUpstreamLink,
|
||||
upsertSessionUpstreamLink,
|
||||
} from "openclaw/plugin-sdk/session-catalog";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isIncognitoSessionKey } from "../incognito-session.js";
|
||||
import type { CodexSessionCatalogControl } from "../session-catalog-types.js";
|
||||
import { codexLastTerminalTurnId, codexUpstreamBaseline } from "../session-upstream-marker.js";
|
||||
|
|
@ -32,7 +32,7 @@ function readConnectionFingerprint(ref: unknown): string | undefined {
|
|||
}
|
||||
|
||||
function normalizeTurnId(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
|
||||
export async function forkCodexUpstreamSession(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ import {
|
|||
ModelSelectionLockedError,
|
||||
} from "openclaw/plugin-sdk/model-session-runtime";
|
||||
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { asBoolean, asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asBoolean,
|
||||
asOptionalRecord,
|
||||
asSafeIntegerInRange,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { Type } from "typebox";
|
||||
import { resolveCodexBindingAppServerConnection } from "./app-server/binding-connection.js";
|
||||
import { CODEX_CONTROL_METHODS } from "./app-server/capabilities.js";
|
||||
|
|
@ -112,12 +116,6 @@ type CodexThreadsToolOptions = {
|
|||
request?: typeof codexControlRequest;
|
||||
};
|
||||
|
||||
function readLimit(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function resolveToolSession(
|
||||
context: OpenClawPluginToolContext,
|
||||
runtime: PluginRuntime,
|
||||
|
|
@ -291,7 +289,7 @@ export function createCodexThreadsTool(options: CodexThreadsToolOptions): AnyAge
|
|||
CODEX_CONTROL_METHODS.listThreads,
|
||||
{
|
||||
archived: asBoolean(params.archived) ?? false,
|
||||
limit: readLimit(params.limit) ?? 20,
|
||||
limit: asSafeIntegerInRange(params.limit, { min: 1, max: 100 }) ?? 20,
|
||||
modelProviders: [],
|
||||
sortKey: "recency_at",
|
||||
sortDirection: "desc",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { readNonEmptyStringPreservingWhitespace as normalizeTurnId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { CodexThread } from "./app-server/protocol.js";
|
||||
import { codexUpstreamBaseline } from "./session-upstream-marker.js";
|
||||
|
||||
const normalizeTurnId = (value: unknown) =>
|
||||
typeof value === "string" && value ? value : undefined;
|
||||
|
||||
describe("codexUpstreamBaseline", () => {
|
||||
it("baselines an active adoption-time turn including its current user items", () => {
|
||||
const thread = {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
wrapWebContent,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
runBoundedCodexAppServerTurn,
|
||||
type CodexBoundedTurnOptions,
|
||||
|
|
@ -66,7 +67,7 @@ function summarizeCodexWebSearchItem(item: CodexThreadItem): Record<string, unkn
|
|||
const actionType = readNonEmptyString(action, "type");
|
||||
const queries = actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
|
||||
const query =
|
||||
normalizeNonEmptyString(item.query) ??
|
||||
normalizeOptionalString(item.query) ??
|
||||
(actionType === "search" ? readNonEmptyString(action, "query") : undefined) ??
|
||||
queries[0];
|
||||
const url = readNonEmptyString(action, "url");
|
||||
|
|
@ -81,7 +82,7 @@ function summarizeCodexWebSearchItem(item: CodexThreadItem): Record<string, unkn
|
|||
}
|
||||
|
||||
function readNonEmptyString(record: JsonObject | undefined, key: string): string | undefined {
|
||||
return record ? normalizeNonEmptyString(record[key]) : undefined;
|
||||
return record ? normalizeOptionalString(record[key]) : undefined;
|
||||
}
|
||||
|
||||
function readNonEmptyStringArray(record: JsonObject | undefined, key: string): string[] {
|
||||
|
|
@ -90,11 +91,7 @@ function readNonEmptyStringArray(record: JsonObject | undefined, key: string): s
|
|||
return [];
|
||||
}
|
||||
return value.flatMap((entry) => {
|
||||
const normalized = normalizeNonEmptyString(entry);
|
||||
const normalized = normalizeOptionalString(entry);
|
||||
return normalized ? [normalized] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue