mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06: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:
committed by
GitHub
parent
66fe424590
commit
b080dd1e76
@@ -9,6 +9,7 @@ import { isPathInside } from "openclaw/plugin-sdk/security-runtime";
|
|||||||
import {
|
import {
|
||||||
asOptionalRecord,
|
asOptionalRecord,
|
||||||
normalizeLowercaseStringOrEmpty,
|
normalizeLowercaseStringOrEmpty,
|
||||||
|
normalizeOptionalString,
|
||||||
normalizeStringEntries,
|
normalizeStringEntries,
|
||||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
@@ -132,11 +133,6 @@ function resolveToolsAllow(params: { pluginToolsAllow: unknown; cfg?: OpenClawCo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePromptConfigText(value: unknown): string | undefined {
|
|
||||||
const text = typeof value === "string" ? value.trim() : "";
|
|
||||||
return text ? text : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasDeprecatedModelFallbackPolicy(pluginConfig: unknown): boolean {
|
function hasDeprecatedModelFallbackPolicy(pluginConfig: unknown): boolean {
|
||||||
const raw = asOptionalRecord(pluginConfig);
|
const raw = asOptionalRecord(pluginConfig);
|
||||||
return raw ? Object.hasOwn(raw, "modelFallbackPolicy") : false;
|
return raw ? Object.hasOwn(raw, "modelFallbackPolicy") : false;
|
||||||
@@ -239,8 +235,8 @@ function normalizePluginConfig(
|
|||||||
fastMode: normalizeActiveMemoryFastMode(raw.fastMode),
|
fastMode: normalizeActiveMemoryFastMode(raw.fastMode),
|
||||||
promptStyle: resolvePromptStyle(raw.promptStyle, raw.queryMode),
|
promptStyle: resolvePromptStyle(raw.promptStyle, raw.queryMode),
|
||||||
toolsAllow: resolveToolsAllow({ pluginToolsAllow: raw.toolsAllow, cfg }),
|
toolsAllow: resolveToolsAllow({ pluginToolsAllow: raw.toolsAllow, cfg }),
|
||||||
promptOverride: normalizePromptConfigText(raw.promptOverride),
|
promptOverride: normalizeOptionalString(raw.promptOverride),
|
||||||
promptAppend: normalizePromptConfigText(raw.promptAppend),
|
promptAppend: normalizeOptionalString(raw.promptAppend),
|
||||||
timeoutMs: clampInt(
|
timeoutMs: clampInt(
|
||||||
parseOptionalPositiveInt(raw.timeoutMs, DEFAULT_TIMEOUT_MS),
|
parseOptionalPositiveInt(raw.timeoutMs, DEFAULT_TIMEOUT_MS),
|
||||||
DEFAULT_TIMEOUT_MS,
|
DEFAULT_TIMEOUT_MS,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
|
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import { withSessionTranscriptWriteLock } from "openclaw/plugin-sdk/session-transcript-runtime";
|
import { withSessionTranscriptWriteLock } from "openclaw/plugin-sdk/session-transcript-runtime";
|
||||||
import { CLAUDE_CLI_BACKEND_ID } from "./cli-constants.js";
|
import { CLAUDE_CLI_BACKEND_ID } from "./cli-constants.js";
|
||||||
import type { ClaudeTranscriptItem } from "./session-catalog-transcript.js";
|
import type { ClaudeTranscriptItem } from "./session-catalog-transcript.js";
|
||||||
@@ -8,8 +9,7 @@ function importedClaudeMessage(
|
|||||||
item: ClaudeTranscriptItem,
|
item: ClaudeTranscriptItem,
|
||||||
fallbackTimestamp: number,
|
fallbackTimestamp: number,
|
||||||
): AgentMessage | undefined {
|
): AgentMessage | undefined {
|
||||||
const parsedTimestamp = item.timestamp ? Date.parse(item.timestamp) : Number.NaN;
|
const timestamp = parseDateStringTimestampMs(item.timestamp) ?? fallbackTimestamp;
|
||||||
const timestamp = Number.isFinite(parsedTimestamp) ? parsedTimestamp : fallbackTimestamp;
|
|
||||||
const importedText = item.text?.trim();
|
const importedText = item.text?.trim();
|
||||||
if (!importedText && item.type === "reasoning") {
|
if (!importedText && item.type === "reasoning") {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
SessionCatalogTranscriptItem,
|
SessionCatalogTranscriptItem,
|
||||||
} from "openclaw/plugin-sdk/session-catalog";
|
} from "openclaw/plugin-sdk/session-catalog";
|
||||||
import {
|
import {
|
||||||
|
asPositiveSafeInteger as pullRequestNumber,
|
||||||
isRecord,
|
isRecord,
|
||||||
normalizeBoundedOptionalString as readBoundedString,
|
normalizeBoundedOptionalString as readBoundedString,
|
||||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
@@ -241,10 +242,6 @@ function pullRequestState(value: unknown): SessionCatalogPullRequestSummary["sta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pullRequestNumber(value: unknown): number | undefined {
|
|
||||||
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desktop retains historical PRs in order and marks hidden ones as dismissed;
|
// Desktop retains historical PRs in order and marks hidden ones as dismissed;
|
||||||
// the top-level pair identifies the current PR whose state labels the row.
|
// the top-level pair identifies the current PR whose state labels the row.
|
||||||
function desktopPullRequestSummary(
|
function desktopPullRequestSummary(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
type SessionUpstreamActivity,
|
type SessionUpstreamActivity,
|
||||||
type SessionUpstreamProbe,
|
type SessionUpstreamProbe,
|
||||||
} from "openclaw/plugin-sdk/session-catalog";
|
} from "openclaw/plugin-sdk/session-catalog";
|
||||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asSafeIntegerInRange, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { ClaudeTranscriptItem } from "./session-catalog-transcript.js";
|
import type { ClaudeTranscriptItem } from "./session-catalog-transcript.js";
|
||||||
|
|
||||||
const MAX_CLAUDE_UPSTREAM_SCAN_BYTES = 1024 * 1024;
|
const MAX_CLAUDE_UPSTREAM_SCAN_BYTES = 1024 * 1024;
|
||||||
@@ -119,7 +119,7 @@ function readMarkerOffset(probe: SessionUpstreamProbe): number | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const offset = probe.marker.offset ?? probe.marker.size;
|
const offset = probe.marker.offset ?? probe.marker.size;
|
||||||
return Number.isSafeInteger(offset) && (offset as number) >= 0 ? (offset as number) : undefined;
|
return asSafeIntegerInRange(offset, { min: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkClaudeSessionUpstreamActivity(
|
async function checkClaudeSessionUpstreamActivity(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
parseStrictInteger,
|
parseStrictInteger,
|
||||||
resolveTimerTimeoutMs,
|
resolveTimerTimeoutMs,
|
||||||
} from "openclaw/plugin-sdk/number-runtime";
|
} from "openclaw/plugin-sdk/number-runtime";
|
||||||
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { BrowserActRequest } from "./client-actions.types.js";
|
import type { BrowserActRequest } from "./client-actions.types.js";
|
||||||
import { DEFAULT_BROWSER_ACTION_TIMEOUT_MS } from "./constants.js";
|
import { DEFAULT_BROWSER_ACTION_TIMEOUT_MS } from "./constants.js";
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ function addNavigationGraceMs(durationMs: number, count = 1): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isActionObject(value: unknown): value is BrowserActRequest {
|
function isActionObject(value: unknown): value is BrowserActRequest {
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
return isRecord(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveLeafExecutionBudgetMs(
|
function resolveLeafExecutionBudgetMs(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
type StableChannelIngressIdentityParams,
|
type StableChannelIngressIdentityParams,
|
||||||
} from "openclaw/plugin-sdk/channel-ingress-runtime";
|
} from "openclaw/plugin-sdk/channel-ingress-runtime";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
|
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import {
|
import {
|
||||||
normalizeAgentId,
|
normalizeAgentId,
|
||||||
type ResolvedAgentRoute,
|
type ResolvedAgentRoute,
|
||||||
@@ -249,6 +250,7 @@ export async function resolveClickClackInboundAccess(params: {
|
|||||||
preparedRoute,
|
preparedRoute,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const botLoopNowMs = parseDateStringTimestampMs(params.message.created_at);
|
||||||
const botLoopProtection =
|
const botLoopProtection =
|
||||||
isBotAuthor && params.message.author_id !== params.account.botUserId && params.account.botUserId
|
isBotAuthor && params.message.author_id !== params.account.botUserId && params.account.botUserId
|
||||||
? {
|
? {
|
||||||
@@ -263,9 +265,7 @@ export async function resolveClickClackInboundAccess(params: {
|
|||||||
senderId: params.message.author_id,
|
senderId: params.message.author_id,
|
||||||
receiverId: params.account.botUserId,
|
receiverId: params.account.botUserId,
|
||||||
eventId: params.message.id,
|
eventId: params.message.id,
|
||||||
...(Number.isFinite(Date.parse(params.message.created_at))
|
...(botLoopNowMs !== undefined ? { nowMs: botLoopNowMs } : {}),
|
||||||
? { nowMs: Date.parse(params.message.created_at) }
|
|
||||||
: {}),
|
|
||||||
config: effectiveBotPolicy.botLoopProtection,
|
config: effectiveBotPolicy.botLoopProtection,
|
||||||
defaultsConfig: cfg.channels?.defaults?.botLoopProtection,
|
defaultsConfig: cfg.channels?.defaults?.botLoopProtection,
|
||||||
defaultEnabled: true,
|
defaultEnabled: true,
|
||||||
|
|||||||
@@ -2,10 +2,7 @@
|
|||||||
* Codex-backed media understanding provider for bounded image description and
|
* Codex-backed media understanding provider for bounded image description and
|
||||||
* structured extraction turns.
|
* structured extraction turns.
|
||||||
*/
|
*/
|
||||||
import {
|
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
||||||
type JsonSchemaObject,
|
|
||||||
validateJsonSchemaValue,
|
|
||||||
} from "openclaw/plugin-sdk/json-schema-runtime";
|
|
||||||
import type {
|
import type {
|
||||||
ImagesDescriptionRequest,
|
ImagesDescriptionRequest,
|
||||||
ImagesDescriptionResult,
|
ImagesDescriptionResult,
|
||||||
@@ -13,6 +10,7 @@ import type {
|
|||||||
StructuredExtractionRequest,
|
StructuredExtractionRequest,
|
||||||
StructuredExtractionResult,
|
StructuredExtractionResult,
|
||||||
} from "openclaw/plugin-sdk/media-understanding";
|
} from "openclaw/plugin-sdk/media-understanding";
|
||||||
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
runBoundedCodexAppServerTurn,
|
runBoundedCodexAppServerTurn,
|
||||||
type CodexBoundedTurnOptions,
|
type CodexBoundedTurnOptions,
|
||||||
@@ -179,10 +177,6 @@ function buildStructuredExtractionPrompt(req: StructuredExtractionRequest): stri
|
|||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function isJsonSchemaObject(value: unknown): value is JsonSchemaObject {
|
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeStructuredExtractionResult(params: {
|
function normalizeStructuredExtractionResult(params: {
|
||||||
text: string;
|
text: string;
|
||||||
model: string;
|
model: string;
|
||||||
@@ -201,7 +195,7 @@ function normalizeStructuredExtractionResult(params: {
|
|||||||
} catch {
|
} catch {
|
||||||
throw new Error("Codex structured extraction returned invalid JSON.");
|
throw new Error("Codex structured extraction returned invalid JSON.");
|
||||||
}
|
}
|
||||||
if (isJsonSchemaObject(params.req.jsonSchema)) {
|
if (isRecord(params.req.jsonSchema)) {
|
||||||
const validation = validateJsonSchemaValue({
|
const validation = validateJsonSchemaValue({
|
||||||
schema: params.req.jsonSchema,
|
schema: params.req.jsonSchema,
|
||||||
cacheKey: "codex.media-understanding.extractStructured",
|
cacheKey: "codex.media-understanding.extractStructured",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
SessionTranscriptTargetParams,
|
SessionTranscriptTargetParams,
|
||||||
TranscriptTurnAdmission,
|
TranscriptTurnAdmission,
|
||||||
} from "openclaw/plugin-sdk/session-transcript-runtime";
|
} 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 { EmbeddedRunAttemptResult } from "./attempt-terminal.js";
|
||||||
import type { CodexDynamicToolFunctionSpec, CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
import type { CodexDynamicToolFunctionSpec, CodexDynamicToolSpec, JsonValue } from "./protocol.js";
|
||||||
import { flattenCodexDynamicToolFunctions } from "./protocol.js";
|
import { flattenCodexDynamicToolFunctions } from "./protocol.js";
|
||||||
@@ -502,10 +503,6 @@ function readPositiveNumber(value: unknown): number | undefined {
|
|||||||
: 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.
|
* Builds OpenClaw-provided workspace prompt context for the current Codex turn.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||||
import { resolveAgentDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
import { resolveAgentDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
||||||
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-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 { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
||||||
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { isIncognitoSessionKey } from "../incognito-session.js";
|
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
|
// compaction.rs asserts message.contains("thread not found")). So the message
|
||||||
// is the authoritative positive signal here, not the generic code. This is a
|
// is the authoritative positive signal here, not the generic code. This is a
|
||||||
// self-heal recovery gate, not user-facing classification.
|
// 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 {
|
function formatCompactionError(error: unknown): string {
|
||||||
if (error instanceof Error) {
|
return coerceErrorMessage(error);
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
return String(error);
|
|
||||||
}
|
}
|
||||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
/* 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 { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||||
import {
|
import {
|
||||||
|
asOptionalRecord as readRecord,
|
||||||
|
normalizeOptionalString as readNonEmptyString,
|
||||||
normalizeTrimmedStringList,
|
normalizeTrimmedStringList,
|
||||||
parseBooleanValue,
|
parseBooleanValue,
|
||||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
} 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 START_OPTIONS_KEY_SECRET = getStartOptionsKeySecret();
|
||||||
const PLAIN_DECIMAL_NUMBER_RE = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))$/;
|
const PLAIN_DECIMAL_NUMBER_RE = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))$/;
|
||||||
|
|
||||||
export function readRecord(value: unknown): Record<string, unknown> | undefined {
|
export { readNonEmptyString, readRecord };
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
|
||||||
? (value as Record<string, unknown>)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeCodexServiceTier(value: unknown): CodexServiceTier | undefined {
|
export function normalizeCodexServiceTier(value: unknown): CodexServiceTier | undefined {
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
@@ -108,14 +106,6 @@ export function resolveArgs(configArgs: unknown, envArgs: string | undefined): s
|
|||||||
return splitShellWords(envArgs ?? "");
|
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 {
|
export function hashSecretForKey(value: string | undefined, label: string): string | null {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|||||||
import type { ImageContent, TextContent } from "openclaw/plugin-sdk/llm";
|
import type { ImageContent, TextContent } from "openclaw/plugin-sdk/llm";
|
||||||
import { normalizeOpenAIToolSchemas } from "openclaw/plugin-sdk/provider-tools";
|
import { normalizeOpenAIToolSchemas } from "openclaw/plugin-sdk/provider-tools";
|
||||||
import {
|
import {
|
||||||
|
asNonArrayRecord,
|
||||||
asOptionalRecord,
|
asOptionalRecord,
|
||||||
isRecord,
|
isRecord,
|
||||||
normalizeOptionalString,
|
normalizeOptionalString,
|
||||||
@@ -557,7 +558,7 @@ export function createCodexDynamicToolBridge(params: {
|
|||||||
handleToolCall: async (call, options) => {
|
handleToolCall: async (call, options) => {
|
||||||
const toolEntry = toolMap.get(call.tool);
|
const toolEntry = toolMap.get(call.tool);
|
||||||
if (!toolEntry) {
|
if (!toolEntry) {
|
||||||
const executedArguments = jsonObjectToRecord(call.arguments);
|
const executedArguments = asNonArrayRecord(call.arguments);
|
||||||
const message = registeredToolNames.has(call.tool)
|
const message = registeredToolNames.has(call.tool)
|
||||||
? `OpenClaw tool is not available for this turn: ${call.tool}`
|
? `OpenClaw tool is not available for this turn: ${call.tool}`
|
||||||
: `Unknown OpenClaw tool: ${call.tool}`;
|
: `Unknown OpenClaw tool: ${call.tool}`;
|
||||||
@@ -582,7 +583,7 @@ export function createCodexDynamicToolBridge(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { tool, name: toolName } = toolEntry;
|
const { tool, name: toolName } = toolEntry;
|
||||||
const args = jsonObjectToRecord(call.arguments);
|
const args = asNonArrayRecord(call.arguments);
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const signal = composeAbortSignals(params.signal, options?.signal);
|
const signal = composeAbortSignals(params.signal, options?.signal);
|
||||||
let didStartExecution = false;
|
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 {
|
function readFirstString(record: Record<string, unknown>, keys: string[]): string | undefined {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = record[key];
|
const value = record[key];
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import {
|
|||||||
formatToolAggregate,
|
formatToolAggregate,
|
||||||
formatToolProgressOutput,
|
formatToolProgressOutput,
|
||||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
} 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 { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||||
import { isJsonObject, type CodexThreadItem } from "./protocol.js";
|
import { isJsonObject, type CodexThreadItem } from "./protocol.js";
|
||||||
|
|
||||||
@@ -104,10 +107,7 @@ export function toolOutputRawEchoSignature(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeToolTranscriptArguments(value: unknown): Record<string, unknown> {
|
export function normalizeToolTranscriptArguments(value: unknown): Record<string, unknown> {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
return asNonArrayRecord(value);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return value as Record<string, unknown>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectDynamicToolContentText(
|
export function collectDynamicToolContentText(
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { normalizeUsage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
import { normalizeUsage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||||
import {
|
import {
|
||||||
asFiniteNumber,
|
asFiniteNumber,
|
||||||
|
asSafeIntegerInRange,
|
||||||
readStringField as readString,
|
readStringField as readString,
|
||||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { readNonNegativeInteger } from "./event-projector-values.js";
|
|
||||||
import { isJsonObject, type JsonObject } from "./protocol.js";
|
import { isJsonObject, type JsonObject } from "./protocol.js";
|
||||||
|
|
||||||
function readTokenCount(record: JsonObject, key: string): number | undefined {
|
function readTokenCount(record: JsonObject, key: string): number | undefined {
|
||||||
const value = readNonNegativeInteger(record, key);
|
return asSafeIntegerInRange(record[key], { min: 0 });
|
||||||
return value !== undefined && Number.isSafeInteger(value) ? value : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCodexThreadTokenUsage(params: JsonObject): ReturnType<typeof normalizeUsage> {
|
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";
|
import { isJsonObject, type CodexThreadItem, type JsonObject, type JsonValue } from "./protocol.js";
|
||||||
|
|
||||||
export function normalizeNonEmptyString(value: unknown): string | undefined {
|
export { normalizeOptionalString as normalizeNonEmptyString };
|
||||||
if (typeof value !== "string") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return value.trim() || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readNonEmptyString(record: JsonObject, key: string): string | undefined {
|
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[] {
|
export function readNonEmptyStringArray(record: JsonObject, key: string): string[] {
|
||||||
@@ -19,7 +18,7 @@ export function readNonEmptyStringArray(record: JsonObject, key: string): string
|
|||||||
}
|
}
|
||||||
const entries: string[] = [];
|
const entries: string[] = [];
|
||||||
for (const entry of value) {
|
for (const entry of value) {
|
||||||
const normalized = normalizeNonEmptyString(entry);
|
const normalized = normalizeOptionalString(entry);
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
entries.push(normalized);
|
entries.push(normalized);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Lists and normalizes models exposed by the Codex app-server `model/list`
|
* Lists and normalizes models exposed by the Codex app-server `model/list`
|
||||||
* endpoint, including pagination and shared-client lease handling.
|
* 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 {
|
import type {
|
||||||
CodexAppServerAuthRequirement,
|
CodexAppServerAuthRequirement,
|
||||||
resolveCodexAppServerAuthProfileIdForAgent,
|
resolveCodexAppServerAuthProfileIdForAgent,
|
||||||
@@ -145,8 +145,8 @@ export function readModelListResult(value: unknown): CodexAppServerModelListResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readCodexModel(value: CodexModel): CodexAppServerModel {
|
function readCodexModel(value: CodexModel): CodexAppServerModel {
|
||||||
const id = readNonEmptyString(value.id);
|
const id = normalizeOptionalString(value.id);
|
||||||
const model = readNonEmptyString(value.model);
|
const model = normalizeOptionalString(value.model);
|
||||||
if (!id || !model) {
|
if (!id || !model) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Invalid Codex app-server model/list response: model id and name must be non-empty strings",
|
"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 {
|
return {
|
||||||
id,
|
id,
|
||||||
model,
|
model,
|
||||||
...(readNonEmptyString(value.displayName)
|
...(normalizeOptionalString(value.displayName)
|
||||||
? { displayName: readNonEmptyString(value.displayName) }
|
? { displayName: normalizeOptionalString(value.displayName) }
|
||||||
: {}),
|
: {}),
|
||||||
...(readNonEmptyString(value.description)
|
...(normalizeOptionalString(value.description)
|
||||||
? { description: readNonEmptyString(value.description) }
|
? { description: normalizeOptionalString(value.description) }
|
||||||
: {}),
|
: {}),
|
||||||
hidden: value.hidden,
|
hidden: value.hidden,
|
||||||
isDefault: value.isDefault,
|
isDefault: value.isDefault,
|
||||||
inputModalities: value.inputModalities,
|
inputModalities: value.inputModalities,
|
||||||
supportedReasoningEfforts: readReasoningEfforts(value.supportedReasoningEfforts),
|
supportedReasoningEfforts: readReasoningEfforts(value.supportedReasoningEfforts),
|
||||||
...(readNonEmptyString(value.defaultReasoningEffort)
|
...(normalizeOptionalString(value.defaultReasoningEffort)
|
||||||
? { defaultReasoningEffort: readNonEmptyString(value.defaultReasoningEffort) }
|
? { defaultReasoningEffort: normalizeOptionalString(value.defaultReasoningEffort) }
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function readReasoningEfforts(value: CodexReasoningEffortOption[]): string[] {
|
function readReasoningEfforts(value: CodexReasoningEffortOption[]): string[] {
|
||||||
const efforts = value
|
const efforts = value
|
||||||
.map((entry) => readNonEmptyString(entry.reasoningEffort))
|
.map((entry) => normalizeOptionalString(entry.reasoningEffort))
|
||||||
.filter((entry): entry is string => entry !== undefined);
|
.filter((entry): entry is string => entry !== undefined);
|
||||||
return uniqueStrings(efforts);
|
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 {
|
function normalizeMaxPages(value: unknown): number {
|
||||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 20;
|
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)) {
|
for (const [key, value] of Object.entries(right)) {
|
||||||
const existing = merged[key];
|
const existing = merged[key];
|
||||||
merged[key] =
|
merged[key] =
|
||||||
isPlainJsonObject(existing) && isPlainJsonObject(value)
|
isJsonObject(existing) && isJsonObject(value) ? mergeJsonObjects(existing, value) : value;
|
||||||
? mergeJsonObjects(existing, value)
|
|
||||||
: value;
|
|
||||||
}
|
}
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlainJsonObject(value: JsonValue | undefined): value is JsonObject {
|
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function fingerprintJson(value: JsonValue): string {
|
function fingerprintJson(value: JsonValue): string {
|
||||||
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
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 { CodexCommandExecParams, CodexCommandExecResponse } from "./command-exec-protocol.js";
|
||||||
import type {
|
import type {
|
||||||
CodexAppInfo,
|
CodexAppInfo,
|
||||||
@@ -707,7 +708,7 @@ type CodexAppServerRequestResultMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function isJsonObject(value: unknown): value is JsonObject {
|
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 {
|
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. */
|
/** RPC error wrapper that preserves app-server error code and data. */
|
||||||
export class CodexAppServerRpcError extends Error {
|
export class CodexAppServerRpcError extends Error {
|
||||||
@@ -36,7 +36,3 @@ function readCodexAppServerRpcReloginDetail(data: JsonValue | undefined): string
|
|||||||
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
||||||
return isRelogin && detail ? detail : undefined;
|
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 { Buffer } from "node:buffer";
|
||||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
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 type { JsonValue } from "./protocol.js";
|
||||||
import { readUpstreamUserText } from "./upstream-prompt-provenance.js";
|
import { readUpstreamUserText } from "./upstream-prompt-provenance.js";
|
||||||
|
|
||||||
@@ -18,10 +18,6 @@ type ProjectedMessageGroup = {
|
|||||||
bytes: number;
|
bytes: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readNonEmptyString(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readBoundedText(
|
function readBoundedText(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
label: string,
|
label: string,
|
||||||
@@ -49,7 +45,7 @@ function responseItemBytes(item: JsonValue): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function requireCallId(value: unknown): string {
|
function requireCallId(value: unknown): string {
|
||||||
const callId = readNonEmptyString(value);
|
const callId = normalizeOptionalString(value);
|
||||||
if (!callId || callId.length > 256) {
|
if (!callId || callId.length > 256) {
|
||||||
throw new Error("Codex settled-turn projection found an invalid tool call id");
|
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 {
|
function requireToolName(value: unknown): string {
|
||||||
const name = readNonEmptyString(value);
|
const name = normalizeOptionalString(value);
|
||||||
if (!name || !TOOL_NAME_PATTERN.test(name)) {
|
if (!name || !TOOL_NAME_PATTERN.test(name)) {
|
||||||
throw new Error("Codex settled-turn projection found an invalid tool 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");
|
throw new Error("Codex settled-turn projection found malformed tool result content");
|
||||||
}
|
}
|
||||||
if (value.type === "image") {
|
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
|
// The finalizer selects by text capability. Preserve image evidence as
|
||||||
// metadata without embedding an executable or oversized multimodal payload.
|
// metadata without embedding an executable or oversized multimodal payload.
|
||||||
parts.push(`[Image tool result: ${mimeType}]`);
|
parts.push(`[Image tool result: ${mimeType}]`);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
isActiveHarnessContextEngine,
|
isActiveHarnessContextEngine,
|
||||||
type EmbeddedRunAttemptParamsV2 as EmbeddedRunAttemptParams,
|
type EmbeddedRunAttemptParamsV2 as EmbeddedRunAttemptParams,
|
||||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||||
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
resolveCodexContextEngineProjectionMaxChars,
|
resolveCodexContextEngineProjectionMaxChars,
|
||||||
resolveCodexContextEngineProjectionReserveTokens,
|
resolveCodexContextEngineProjectionReserveTokens,
|
||||||
@@ -88,16 +89,12 @@ function areContextEngineProjectionBindingsCompatible(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveContextEngineCitationsMode(config: unknown): JsonValue | undefined {
|
function resolveContextEngineCitationsMode(config: unknown): JsonValue | undefined {
|
||||||
const rootConfig = isUnknownRecord(config) ? config : undefined;
|
const rootConfig = isRecord(config) ? config : undefined;
|
||||||
const memoryConfig = isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : undefined;
|
const memoryConfig = isRecord(rootConfig?.memory) ? rootConfig.memory : undefined;
|
||||||
const citations = memoryConfig?.citations;
|
const citations = memoryConfig?.citations;
|
||||||
return isJsonConfigValue(citations) ? citations : undefined;
|
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 {
|
function isJsonConfigValue(value: unknown): value is JsonValue {
|
||||||
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
||||||
return true;
|
return true;
|
||||||
@@ -108,5 +105,5 @@ function isJsonConfigValue(value: unknown): value is JsonValue {
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.every(isJsonConfigValue);
|
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,
|
deleteSessionUpstreamLink,
|
||||||
upsertSessionUpstreamLink,
|
upsertSessionUpstreamLink,
|
||||||
} from "openclaw/plugin-sdk/session-catalog";
|
} 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 { isIncognitoSessionKey } from "../incognito-session.js";
|
||||||
import type { CodexSessionCatalogControl } from "../session-catalog-types.js";
|
import type { CodexSessionCatalogControl } from "../session-catalog-types.js";
|
||||||
import { codexLastTerminalTurnId, codexUpstreamBaseline } from "../session-upstream-marker.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 {
|
function normalizeTurnId(value: unknown): string | undefined {
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
return normalizeOptionalString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function forkCodexUpstreamSession(
|
export async function forkCodexUpstreamSession(
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import {
|
|||||||
ModelSelectionLockedError,
|
ModelSelectionLockedError,
|
||||||
} from "openclaw/plugin-sdk/model-session-runtime";
|
} from "openclaw/plugin-sdk/model-session-runtime";
|
||||||
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry";
|
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 { Type } from "typebox";
|
||||||
import { resolveCodexBindingAppServerConnection } from "./app-server/binding-connection.js";
|
import { resolveCodexBindingAppServerConnection } from "./app-server/binding-connection.js";
|
||||||
import { CODEX_CONTROL_METHODS } from "./app-server/capabilities.js";
|
import { CODEX_CONTROL_METHODS } from "./app-server/capabilities.js";
|
||||||
@@ -112,12 +116,6 @@ type CodexThreadsToolOptions = {
|
|||||||
request?: typeof codexControlRequest;
|
request?: typeof codexControlRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readLimit(value: unknown): number | undefined {
|
|
||||||
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100
|
|
||||||
? value
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveToolSession(
|
function resolveToolSession(
|
||||||
context: OpenClawPluginToolContext,
|
context: OpenClawPluginToolContext,
|
||||||
runtime: PluginRuntime,
|
runtime: PluginRuntime,
|
||||||
@@ -291,7 +289,7 @@ export function createCodexThreadsTool(options: CodexThreadsToolOptions): AnyAge
|
|||||||
CODEX_CONTROL_METHODS.listThreads,
|
CODEX_CONTROL_METHODS.listThreads,
|
||||||
{
|
{
|
||||||
archived: asBoolean(params.archived) ?? false,
|
archived: asBoolean(params.archived) ?? false,
|
||||||
limit: readLimit(params.limit) ?? 20,
|
limit: asSafeIntegerInRange(params.limit, { min: 1, max: 100 }) ?? 20,
|
||||||
modelProviders: [],
|
modelProviders: [],
|
||||||
sortKey: "recency_at",
|
sortKey: "recency_at",
|
||||||
sortDirection: "desc",
|
sortDirection: "desc",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
|
import { readNonEmptyStringPreservingWhitespace as normalizeTurnId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { CodexThread } from "./app-server/protocol.js";
|
import type { CodexThread } from "./app-server/protocol.js";
|
||||||
import { codexUpstreamBaseline } from "./session-upstream-marker.js";
|
import { codexUpstreamBaseline } from "./session-upstream-marker.js";
|
||||||
|
|
||||||
const normalizeTurnId = (value: unknown) =>
|
|
||||||
typeof value === "string" && value ? value : undefined;
|
|
||||||
|
|
||||||
describe("codexUpstreamBaseline", () => {
|
describe("codexUpstreamBaseline", () => {
|
||||||
it("baselines an active adoption-time turn including its current user items", () => {
|
it("baselines an active adoption-time turn including its current user items", () => {
|
||||||
const thread = {
|
const thread = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
wrapWebContent,
|
wrapWebContent,
|
||||||
} from "openclaw/plugin-sdk/provider-web-search";
|
} from "openclaw/plugin-sdk/provider-web-search";
|
||||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||||
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
runBoundedCodexAppServerTurn,
|
runBoundedCodexAppServerTurn,
|
||||||
type CodexBoundedTurnOptions,
|
type CodexBoundedTurnOptions,
|
||||||
@@ -66,7 +67,7 @@ function summarizeCodexWebSearchItem(item: CodexThreadItem): Record<string, unkn
|
|||||||
const actionType = readNonEmptyString(action, "type");
|
const actionType = readNonEmptyString(action, "type");
|
||||||
const queries = actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
|
const queries = actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
|
||||||
const query =
|
const query =
|
||||||
normalizeNonEmptyString(item.query) ??
|
normalizeOptionalString(item.query) ??
|
||||||
(actionType === "search" ? readNonEmptyString(action, "query") : undefined) ??
|
(actionType === "search" ? readNonEmptyString(action, "query") : undefined) ??
|
||||||
queries[0];
|
queries[0];
|
||||||
const url = readNonEmptyString(action, "url");
|
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 {
|
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[] {
|
function readNonEmptyStringArray(record: JsonObject | undefined, key: string): string[] {
|
||||||
@@ -90,11 +91,7 @@ function readNonEmptyStringArray(record: JsonObject | undefined, key: string): s
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return value.flatMap((entry) => {
|
return value.flatMap((entry) => {
|
||||||
const normalized = normalizeNonEmptyString(entry);
|
const normalized = normalizeOptionalString(entry);
|
||||||
return normalized ? [normalized] : [];
|
return normalized ? [normalized] : [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeNonEmptyString(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
// - `src/agents/pi-embedded-runner/run/types.ts` —
|
// - `src/agents/pi-embedded-runner/run/types.ts` —
|
||||||
// `AgentHarnessAttemptResult.replayMetadata` field requirement.
|
// `AgentHarnessAttemptResult.replayMetadata` field requirement.
|
||||||
|
|
||||||
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
type ReplayDecision =
|
type ReplayDecision =
|
||||||
| {
|
| {
|
||||||
readonly action: "resume";
|
readonly action: "resume";
|
||||||
@@ -38,11 +40,7 @@ interface ReplayShimInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSdkSessionId(value: unknown): string | undefined {
|
function normalizeSdkSessionId(value: unknown): string | undefined {
|
||||||
if (typeof value !== "string") {
|
return normalizeOptionalString(value);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed.length > 0 ? trimmed : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
normalizeDiagnosticValue,
|
normalizeDiagnosticValue,
|
||||||
normalizeDiagnosticLane,
|
normalizeDiagnosticLane,
|
||||||
} from "openclaw/plugin-sdk/diagnostic-runtime";
|
} from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||||
|
import { asNonNegativeFiniteNumber as numericValue } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||||
import type {
|
import type {
|
||||||
DiagnosticEventMetadata,
|
DiagnosticEventMetadata,
|
||||||
@@ -56,10 +57,6 @@ const RATIO_BUCKETS = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 1, 2, 4, 8, 16];
|
|||||||
const MAX_PROMETHEUS_SERIES = 2048;
|
const MAX_PROMETHEUS_SERIES = 2048;
|
||||||
const DROPPED_SERIES_COUNTER_NAME = "openclaw_prometheus_series_dropped_total";
|
const DROPPED_SERIES_COUNTER_NAME = "openclaw_prometheus_series_dropped_total";
|
||||||
|
|
||||||
function numericValue(value: number | undefined): number | undefined {
|
|
||||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function seconds(ms: number | undefined): number | undefined {
|
function seconds(ms: number | undefined): number | undefined {
|
||||||
const value = numericValue(ms);
|
const value = numericValue(ms);
|
||||||
return value === undefined ? undefined : value / 1000;
|
return value === undefined ? undefined : value / 1000;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "openclaw/plugin-sdk/proxy-capture";
|
} from "openclaw/plugin-sdk/proxy-capture";
|
||||||
import { danger, warn } from "openclaw/plugin-sdk/runtime-env";
|
import { danger, warn } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||||
|
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||||
import * as ws from "ws";
|
import * as ws from "ws";
|
||||||
import * as discordGateway from "../internal/gateway.js";
|
import * as discordGateway from "../internal/gateway.js";
|
||||||
@@ -78,8 +79,7 @@ function readStringProperty(value: object, key: string): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readNumberProperty(value: object, key: string): number | undefined {
|
function readNumberProperty(value: object, key: string): number | undefined {
|
||||||
const property = (value as Record<string, unknown>)[key];
|
return asFiniteNumber((value as Record<string, unknown>)[key]);
|
||||||
return typeof property === "number" && Number.isFinite(property) ? property : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function describeDiscordGatewayTransportError(error: Error): DiscordGatewayTransportErrorDetails {
|
function describeDiscordGatewayTransportError(error: Error): DiscordGatewayTransportErrorDetails {
|
||||||
|
|||||||
@@ -33,17 +33,6 @@ vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
|||||||
danger: (value: string) => value,
|
danger: (value: string) => value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", () => {
|
|
||||||
const normalizeMockOptionalString = (value: string | null | undefined) => {
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const normalized = value.trim();
|
|
||||||
return normalized.length > 0 ? normalized : undefined;
|
|
||||||
};
|
|
||||||
return { normalizeOptionalString: normalizeMockOptionalString };
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock("../proxy-request-client.js", () => ({
|
vi.mock("../proxy-request-client.js", () => ({
|
||||||
DISCORD_REST_TIMEOUT_MS: 15_000,
|
DISCORD_REST_TIMEOUT_MS: 15_000,
|
||||||
createDiscordRequestClient: vi.fn(() => ({
|
createDiscordRequestClient: vi.fn(() => ({
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
// Feishu plugin module implements conversation id behavior.
|
// Feishu plugin module implements conversation id behavior.
|
||||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
normalizeLowercaseStringOrEmpty,
|
||||||
|
normalizeOptionalString as normalizeText,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
export type FeishuGroupSessionScope =
|
export type FeishuGroupSessionScope =
|
||||||
| "group"
|
| "group"
|
||||||
@@ -26,14 +29,6 @@ export function resolveConfiguredFeishuGroupSessionScope(params: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeText(value: unknown): string | undefined {
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFeishuConversationId(params: {
|
export function buildFeishuConversationId(params: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
scope: FeishuGroupSessionScope;
|
scope: FeishuGroupSessionScope;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
|
|||||||
import * as http from "node:http";
|
import * as http from "node:http";
|
||||||
import * as Lark from "@larksuiteoapi/node-sdk";
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
||||||
import { channelBlockedPatch, channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
import { channelBlockedPatch, channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
||||||
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||||
import { waitForAbortableDelay } from "./async.js";
|
import { waitForAbortableDelay } from "./async.js";
|
||||||
import { createFeishuWSClient } from "./client.js";
|
import { createFeishuWSClient } from "./client.js";
|
||||||
@@ -56,7 +57,7 @@ const FEISHU_WS_AUTORECONNECT_DISABLED_ERROR =
|
|||||||
"WebSocket connect failed and autoReconnect is disabled";
|
"WebSocket connect failed and autoReconnect is disabled";
|
||||||
|
|
||||||
function isFeishuWebhookPayload(value: unknown): value is Record<string, unknown> {
|
function isFeishuWebhookPayload(value: unknown): value is Record<string, unknown> {
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
return isRecord(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCKED_FEISHU_WEBHOOK_PAYLOAD_KEYS = new Set([
|
const BLOCKED_FEISHU_WEBHOOK_PAYLOAD_KEYS = new Set([
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import path from "node:path";
|
|||||||
import { minimatch } from "minimatch";
|
import { minimatch } from "minimatch";
|
||||||
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
||||||
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||||
|
import { asNullableRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
export type FilePolicyKind = "read" | "write";
|
export type FilePolicyKind = "read" | "write";
|
||||||
type FilePolicyAskMode = "off" | "on-miss" | "always";
|
type FilePolicyAskMode = "off" | "on-miss" | "always";
|
||||||
@@ -85,10 +86,7 @@ type NodeFilePolicyConfig = {
|
|||||||
type FilePolicyConfig = Record<string, NodeFilePolicyConfig>;
|
type FilePolicyConfig = Record<string, NodeFilePolicyConfig>;
|
||||||
|
|
||||||
function asFilePolicyConfig(value: unknown): FilePolicyConfig | null {
|
function asFilePolicyConfig(value: unknown): FilePolicyConfig | null {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
return asNullableRecord(value) as FilePolicyConfig | null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value as FilePolicyConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readFilePolicyConfigFromPluginConfig(pluginConfig: unknown): FilePolicyConfig | null {
|
function readFilePolicyConfigFromPluginConfig(pluginConfig: unknown): FilePolicyConfig | null {
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
resolveSpeechProviderApiKey,
|
resolveSpeechProviderApiKey,
|
||||||
trimToUndefined,
|
trimToUndefined,
|
||||||
} from "openclaw/plugin-sdk/speech-core";
|
} from "openclaw/plugin-sdk/speech-core";
|
||||||
import { asFiniteNumberInRange, asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
asFiniteNumberInRange,
|
||||||
|
asOptionalRecord,
|
||||||
|
parseBooleanValue,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
FISH_AUDIO_STREAM_MAX_BYTES,
|
FISH_AUDIO_STREAM_MAX_BYTES,
|
||||||
type FishAudioFormat,
|
type FishAudioFormat,
|
||||||
@@ -207,12 +211,9 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
|||||||
if (!ctx.policy.allowNormalization) {
|
if (!ctx.policy.allowNormalization) {
|
||||||
return { handled: true };
|
return { handled: true };
|
||||||
}
|
}
|
||||||
const value = ctx.value.trim().toLowerCase();
|
const normalize = parseBooleanValue(ctx.value);
|
||||||
if (["true", "1", "yes", "on"].includes(value)) {
|
if (normalize !== undefined) {
|
||||||
return { handled: true, overrides: { ...ctx.currentOverrides, normalize: true } };
|
return { handled: true, overrides: { ...ctx.currentOverrides, normalize } };
|
||||||
}
|
|
||||||
if (["false", "0", "no", "off"].includes(value)) {
|
|
||||||
return { handled: true, overrides: { ...ctx.currentOverrides, normalize: false } };
|
|
||||||
}
|
}
|
||||||
return { handled: true, warnings: [`invalid Fish Audio normalize "${ctx.value}"`] };
|
return { handled: true, warnings: [`invalid Fish Audio normalize "${ctx.value}"`] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ const coerceSecretRefMock = vi.hoisted(() => vi.fn());
|
|||||||
const resolveConfiguredSecretInputWithFallbackMock = vi.hoisted(() => vi.fn());
|
const resolveConfiguredSecretInputWithFallbackMock = vi.hoisted(() => vi.fn());
|
||||||
const resolveRequiredConfiguredSecretRefInputStringMock = vi.hoisted(() => vi.fn());
|
const resolveRequiredConfiguredSecretRefInputStringMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
|
vi.mock("openclaw/plugin-sdk/provider-auth", async () => {
|
||||||
coerceSecretRef: coerceSecretRefMock,
|
const { normalizeOptionalString } = await import("openclaw/plugin-sdk/string-coerce-runtime");
|
||||||
ensureAuthProfileStore: ensureAuthProfileStoreMock,
|
return {
|
||||||
listProfilesForProvider: listProfilesForProviderMock,
|
coerceSecretRef: coerceSecretRefMock,
|
||||||
normalizeOptionalSecretInput: (value: unknown) =>
|
ensureAuthProfileStore: ensureAuthProfileStoreMock,
|
||||||
typeof value === "string" && value.trim() ? value.trim() : undefined,
|
listProfilesForProvider: listProfilesForProviderMock,
|
||||||
}));
|
normalizeOptionalSecretInput: normalizeOptionalString,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/secret-input-runtime", () => ({
|
vi.mock("openclaw/plugin-sdk/secret-input-runtime", () => ({
|
||||||
resolveConfiguredSecretInputWithFallback: resolveConfiguredSecretInputWithFallbackMock,
|
resolveConfiguredSecretInputWithFallback: resolveConfiguredSecretInputWithFallbackMock,
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { normalizeAccountId, type OpenClawConfig } from "openclaw/plugin-sdk/acc
|
|||||||
// Imessage plugin module implements accounts behavior.
|
// Imessage plugin module implements accounts behavior.
|
||||||
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
||||||
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
||||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
asOptionalRecord,
|
||||||
|
normalizeOptionalString,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { IMessageAccountConfig } from "./account-types.js";
|
import type { IMessageAccountConfig } from "./account-types.js";
|
||||||
import {
|
import {
|
||||||
expandIMessageUserPath,
|
expandIMessageUserPath,
|
||||||
@@ -45,9 +48,7 @@ function resolveIMessageAccountConfig(
|
|||||||
type IMessageStreamingConfig = NonNullable<IMessageAccountConfig["streaming"]>;
|
type IMessageStreamingConfig = NonNullable<IMessageAccountConfig["streaming"]>;
|
||||||
|
|
||||||
function asStreamingConfigObject(value: unknown): IMessageStreamingConfig | undefined {
|
function asStreamingConfigObject(value: unknown): IMessageStreamingConfig | undefined {
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
return asOptionalRecord(value) as IMessageStreamingConfig | undefined;
|
||||||
? (value as IMessageStreamingConfig)
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeIMessageStreamingConfig(
|
function mergeIMessageStreamingConfig(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
parseStrictInteger,
|
parseStrictInteger,
|
||||||
resolveExpiresAtMsFromDurationMs,
|
resolveExpiresAtMsFromDurationMs,
|
||||||
} from "openclaw/plugin-sdk/number-runtime";
|
} from "openclaw/plugin-sdk/number-runtime";
|
||||||
|
import { normalizeOptionalString as stringFromUnknown } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { IMessageActionTransportOptions } from "./actions-rpc.js";
|
import type { IMessageActionTransportOptions } from "./actions-rpc.js";
|
||||||
import { normalizeDirectChatIdentifier } from "./chat-context.js";
|
import { normalizeDirectChatIdentifier } from "./chat-context.js";
|
||||||
import { createIMessageRpcClient } from "./client.js";
|
import { createIMessageRpcClient } from "./client.js";
|
||||||
@@ -41,10 +42,6 @@ function numberFromUnknown(value: unknown): number | undefined {
|
|||||||
return typeof value === "number" && Number.isFinite(value) ? value : parseStrictInteger(value);
|
return typeof value === "number" && Number.isFinite(value) ? value : parseStrictInteger(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringFromUnknown(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function chatListCacheKey(options: IMessageActionTransportOptions): string {
|
function chatListCacheKey(options: IMessageActionTransportOptions): string {
|
||||||
return `${options.cliPath}\0${options.dbPath ?? ""}\0${options.remoteHost ?? ""}`;
|
return `${options.cliPath}\0${options.dbPath ?? ""}\0${options.remoteHost ?? ""}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import {
|
import {
|
||||||
asDateTimestampMs,
|
asDateTimestampMs,
|
||||||
|
asPositiveFiniteNumber,
|
||||||
resolveExpiresAtMsFromDurationMs,
|
resolveExpiresAtMsFromDurationMs,
|
||||||
} from "openclaw/plugin-sdk/number-runtime";
|
} from "openclaw/plugin-sdk/number-runtime";
|
||||||
import type { IMessageApprovalGatewayRuntime } from "./approval-gateway-types.js";
|
import type { IMessageApprovalGatewayRuntime } from "./approval-gateway-types.js";
|
||||||
@@ -38,7 +39,7 @@ type HistoryMessage = IMessagePayload & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeChatId(value: unknown): number | null {
|
function normalizeChatId(value: unknown): number | null {
|
||||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
return asPositiveFiniteNumber(value) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listTargetChatIds(
|
function listTargetChatIds(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
formatInboundEnvelope,
|
formatInboundEnvelope,
|
||||||
type resolveEnvelopeFormatOptions,
|
type resolveEnvelopeFormatOptions,
|
||||||
} from "openclaw/plugin-sdk/channel-inbound";
|
} from "openclaw/plugin-sdk/channel-inbound";
|
||||||
|
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { IMessageRpcClient } from "../client.js";
|
import type { IMessageRpcClient } from "../client.js";
|
||||||
import { normalizeIMessageHandle } from "../targets.js";
|
import { normalizeIMessageHandle } from "../targets.js";
|
||||||
@@ -87,8 +88,7 @@ function historyEntryFromMessage(message: IMessagePayload, fallbackSender: strin
|
|||||||
if (!body) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const timestamp =
|
const timestamp = parseDateStringTimestampMs(message.created_at);
|
||||||
typeof message.created_at === "string" ? Date.parse(message.created_at) : Number.NaN;
|
|
||||||
return {
|
return {
|
||||||
sender:
|
sender:
|
||||||
message.is_from_me === true
|
message.is_from_me === true
|
||||||
@@ -96,7 +96,7 @@ function historyEntryFromMessage(message: IMessagePayload, fallbackSender: strin
|
|||||||
: normalizeIMessageHandle(normalizeOptionalString(message.sender) ?? fallbackSender) ||
|
: normalizeIMessageHandle(normalizeOptionalString(message.sender) ?? fallbackSender) ||
|
||||||
fallbackSender,
|
fallbackSender,
|
||||||
body,
|
body,
|
||||||
...(Number.isFinite(timestamp) ? { timestamp } : {}),
|
...(timestamp !== undefined ? { timestamp } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|||||||
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||||
|
import { asSafeIntegerInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { getIMessageRuntime } from "../runtime.js";
|
import { getIMessageRuntime } from "../runtime.js";
|
||||||
import { parseIMessageNotification } from "./parse-notification.js";
|
import { parseIMessageNotification } from "./parse-notification.js";
|
||||||
import type { IMessagePayload } from "./types.js";
|
import type { IMessagePayload } from "./types.js";
|
||||||
@@ -62,8 +63,7 @@ function rawMessageRecord(raw: unknown): Record<string, unknown> | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rawRowid(raw: unknown): number | null {
|
function rawRowid(raw: unknown): number | null {
|
||||||
const rowid = rawMessageRecord(raw)?.id;
|
return asSafeIntegerInRange(rawMessageRecord(raw)?.id, { min: 0 }) ?? null;
|
||||||
return typeof rowid === "number" && Number.isSafeInteger(rowid) && rowid >= 0 ? rowid : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read only stable transport metadata; payload normalization waits for dispatch. */
|
/** Read only stable transport metadata; payload normalization waits for dispatch. */
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||||
import { sleep as delay } from "openclaw/plugin-sdk/runtime-env";
|
import { sleep as delay } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
|
import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
|
||||||
|
import { normalizeOptionalString as stringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
|
import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
|
||||||
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
||||||
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
||||||
@@ -518,10 +519,6 @@ async function runIMessageCliJson(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringValue(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resultService(value: unknown): Exclude<IMessageService, "auto"> | undefined {
|
function resultService(value: unknown): Exclude<IMessageService, "auto"> | undefined {
|
||||||
const normalized = stringValue(value)?.toLowerCase();
|
const normalized = stringValue(value)?.toLowerCase();
|
||||||
return normalized === "imessage" || normalized === "sms" ? normalized : undefined;
|
return normalized === "imessage" || normalized === "sms" ? normalized : undefined;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { ChannelLegacyStateMigrationPlan } from "openclaw/plugin-sdk/channe
|
|||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import { fileExists } from "openclaw/plugin-sdk/security-runtime";
|
import { fileExists } from "openclaw/plugin-sdk/security-runtime";
|
||||||
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
||||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asFiniteNumber, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
listIMessageAccountIds,
|
listIMessageAccountIds,
|
||||||
resolveDefaultIMessageAccountId,
|
resolveDefaultIMessageAccountId,
|
||||||
@@ -131,7 +131,7 @@ function readReplyCounterValue(value: unknown): number | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const counter = (value as { counter?: unknown }).counter;
|
const counter = (value as { counter?: unknown }).counter;
|
||||||
return typeof counter === "number" && Number.isFinite(counter) ? counter : null;
|
return asFiniteNumber(counter) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldReplaceReplyCounter(existingValue: unknown, incomingValue: unknown): boolean {
|
function shouldReplaceReplyCounter(existingValue: unknown, incomingValue: unknown): boolean {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
|
|||||||
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
||||||
|
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||||
|
|
||||||
type LogbookSnapshotParams = {
|
type LogbookSnapshotParams = {
|
||||||
@@ -22,11 +23,11 @@ function readParams(value: unknown): LogbookSnapshotParams {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const record = value as Record<string, unknown>;
|
const record = value as Record<string, unknown>;
|
||||||
const num = (key: string) => {
|
return {
|
||||||
const candidate = record[key];
|
screenIndex: asFiniteNumber(record.screenIndex),
|
||||||
return typeof candidate === "number" && Number.isFinite(candidate) ? candidate : undefined;
|
maxWidth: asFiniteNumber(record.maxWidth),
|
||||||
|
quality: asFiniteNumber(record.quality),
|
||||||
};
|
};
|
||||||
return { screenIndex: num("screenIndex"), maxWidth: num("maxWidth"), quality: num("quality") };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleLogbookSnapshot(rawParams: unknown): Promise<LogbookSnapshotPayload> {
|
export async function handleLogbookSnapshot(rawParams: unknown): Promise<LogbookSnapshotPayload> {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function readPackageJson(packageRoot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLowercaseStringOrEmpty(value) {
|
function lowercaseStringOrEmptyWithoutTrim(value) {
|
||||||
return typeof value === "string" ? value.toLowerCase() : "";
|
return typeof value === "string" ? value.toLowerCase() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ function hasTrustedOpenClawRootIndicator(packageRoot, packageJson) {
|
|||||||
const hasCliEntryExport = Object.hasOwn(packageExports, "./cli-entry");
|
const hasCliEntryExport = Object.hasOwn(packageExports, "./cli-entry");
|
||||||
const hasOpenClawBin =
|
const hasOpenClawBin =
|
||||||
(typeof packageJson?.bin === "string" &&
|
(typeof packageJson?.bin === "string" &&
|
||||||
normalizeLowercaseStringOrEmpty(packageJson.bin).includes("openclaw")) ||
|
lowercaseStringOrEmptyWithoutTrim(packageJson.bin).includes("openclaw")) ||
|
||||||
(typeof packageJson?.bin === "object" &&
|
(typeof packageJson?.bin === "object" &&
|
||||||
packageJson.bin !== null &&
|
packageJson.bin !== null &&
|
||||||
typeof packageJson.bin.openclaw === "string");
|
typeof packageJson.bin.openclaw === "string");
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
// Mattermost plugin module implements gateway auth bypass behavior.
|
// Mattermost plugin module implements gateway auth bypass behavior.
|
||||||
|
import {
|
||||||
|
asOptionalRecord,
|
||||||
|
normalizeOptionalString as readTrimmedString,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
const DEFAULT_SLASH_CALLBACK_PATH = "/api/channels/mattermost/command";
|
const DEFAULT_SLASH_CALLBACK_PATH = "/api/channels/mattermost/command";
|
||||||
|
|
||||||
type MattermostSlashCommandConfigInput = {
|
type MattermostSlashCommandConfigInput = {
|
||||||
@@ -14,10 +19,6 @@ type MattermostConfigInput = MattermostAccountConfigInput & {
|
|||||||
accounts?: Record<string, unknown>;
|
accounts?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readTrimmedString(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeCallbackPath(value: unknown): string {
|
function normalizeCallbackPath(value: unknown): string {
|
||||||
const trimmed = readTrimmedString(value);
|
const trimmed = readTrimmedString(value);
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@@ -27,9 +28,7 @@ function normalizeCallbackPath(value: unknown): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readMattermostCommands(value: unknown): MattermostSlashCommandConfigInput | undefined {
|
function readMattermostCommands(value: unknown): MattermostSlashCommandConfigInput | undefined {
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
return asOptionalRecord(value) as MattermostSlashCommandConfigInput | undefined;
|
||||||
? (value as MattermostSlashCommandConfigInput)
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMattermostBypassPath(path: string): boolean {
|
function isMattermostBypassPath(path: string): boolean {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Memory Core tests cover embeddings plugin behavior.
|
// Memory Core tests cover embeddings plugin behavior.
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import type { EmbeddingProviderAdapter } from "openclaw/plugin-sdk/embedding-providers";
|
import type { EmbeddingProviderAdapter } from "openclaw/plugin-sdk/embedding-providers";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import type { MemoryEmbeddingProviderAdapter } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
import type { MemoryEmbeddingProviderAdapter } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
@@ -105,7 +106,7 @@ function createMissingCredentialsAdapter(
|
|||||||
id: "bedrock",
|
id: "bedrock",
|
||||||
transport: "remote",
|
transport: "remote",
|
||||||
autoSelectPriority: 60,
|
autoSelectPriority: 60,
|
||||||
formatSetupError: (err) => (err instanceof Error ? err.message : String(err)),
|
formatSetupError: coerceErrorMessage,
|
||||||
shouldContinueAutoSelection: (err) =>
|
shouldContinueAutoSelection: (err) =>
|
||||||
err instanceof Error && err.message.includes("No API key found for provider"),
|
err instanceof Error && err.message.includes("No API key found for provider"),
|
||||||
create: async () => {
|
create: async () => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Msteams plugin module implements bot framework behavior.
|
// Msteams plugin module implements bot framework behavior.
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime";
|
import { parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime";
|
||||||
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
||||||
import {
|
import {
|
||||||
@@ -108,7 +109,7 @@ async function fetchBotFrameworkAttachmentInfo(params: {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", {
|
params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,7 @@ async function fetchBotFrameworkAttachmentInfo(params: {
|
|||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", {
|
params.logger?.warn?.("msteams botFramework attachmentInfo parse failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,7 @@ async function saveBotFrameworkAttachmentView(params: {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams botFramework attachmentView fetch failed", {
|
params.logger?.warn?.("msteams botFramework attachmentView fetch failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -185,7 +186,7 @@ async function saveBotFrameworkAttachmentView(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await response.body?.cancel().catch(() => undefined);
|
await response.body?.cancel().catch(() => undefined);
|
||||||
params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", {
|
params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -204,7 +205,7 @@ async function saveBotFrameworkAttachmentView(params: {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams botFramework attachmentView save failed", {
|
params.logger?.warn?.("msteams botFramework attachmentView save failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -256,7 +257,7 @@ async function downloadMSTeamsBotFrameworkAttachment(params: {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams botFramework token acquisition failed", {
|
params.logger?.warn?.("msteams botFramework token acquisition failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -401,7 +402,7 @@ export async function downloadMSTeamsBotFrameworkAttachments(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
media.push({ kind: "document", sourceId: attachmentId });
|
media.push({ kind: "document", sourceId: attachmentId });
|
||||||
params.logger?.warn?.("msteams botFramework attachment download failed", {
|
params.logger?.warn?.("msteams botFramework attachment download failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
attachmentId,
|
attachmentId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Msteams plugin module implements download behavior.
|
// Msteams plugin module implements download behavior.
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import {
|
import {
|
||||||
normalizeLowercaseStringOrEmpty,
|
normalizeLowercaseStringOrEmpty,
|
||||||
normalizeOptionalLowercaseString,
|
normalizeOptionalLowercaseString,
|
||||||
@@ -334,7 +335,7 @@ export async function downloadMSTeamsAttachments(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
||||||
params.logger?.warn?.("msteams inline attachment decode failed", {
|
params.logger?.warn?.("msteams inline attachment decode failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -370,7 +371,7 @@ export async function downloadMSTeamsAttachments(params: {
|
|||||||
out.push(withSourceId(media, candidate.sourceId));
|
out.push(withSourceId(media, candidate.sourceId));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
out.push(withSourceId({ kind: candidate.mediaKind }, candidate.sourceId));
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = coerceErrorMessage(err);
|
||||||
params.logger?.warn?.(
|
params.logger?.warn?.(
|
||||||
`msteams attachment download failed host=${safeHostForLog(candidate.url)} error=${msg}`,
|
`msteams attachment download failed host=${safeHostForLog(candidate.url)} error=${msg}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
// Mock shared.js to avoid transitive runtime-api imports that pull in uninstalled packages.
|
// Mock shared.js to avoid transitive runtime-api imports that pull in uninstalled packages.
|
||||||
vi.mock("./shared.js", async (importOriginal) => {
|
vi.mock("./shared.js", async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import("./shared.js")>();
|
const actual = await importOriginal<typeof import("./shared.js")>();
|
||||||
const isMockRecord = (value: unknown) =>
|
const { isRecord } = await import("openclaw/plugin-sdk/string-coerce-runtime");
|
||||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
applyAuthorizationHeaderForUrl: vi.fn(),
|
applyAuthorizationHeaderForUrl: vi.fn(),
|
||||||
@@ -13,7 +12,7 @@ vi.mock("./shared.js", async (importOriginal) => {
|
|||||||
resolveMSTeamsMediaKind: vi.fn(({ contentType }: { contentType?: string }) =>
|
resolveMSTeamsMediaKind: vi.fn(({ contentType }: { contentType?: string }) =>
|
||||||
contentType?.startsWith("image/") ? "image" : "document",
|
contentType?.startsWith("image/") ? "image" : "document",
|
||||||
),
|
),
|
||||||
isRecord: isMockRecord,
|
isRecord,
|
||||||
isUrlAllowed: vi.fn(() => true),
|
isUrlAllowed: vi.fn(() => true),
|
||||||
normalizeContentType: vi.fn((ct: string | null | undefined) => ct ?? undefined),
|
normalizeContentType: vi.fn((ct: string | null | undefined) => ct ?? undefined),
|
||||||
resolveMediaSsrfPolicy: vi.fn(() => undefined),
|
resolveMediaSsrfPolicy: vi.fn(() => undefined),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Msteams plugin module implements graph behavior.
|
// Msteams plugin module implements graph behavior.
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import {
|
import {
|
||||||
readProviderJsonArrayFieldResponse,
|
readProviderJsonArrayFieldResponse,
|
||||||
readProviderJsonResponse,
|
readProviderJsonResponse,
|
||||||
@@ -188,7 +189,7 @@ async function downloadGraphHostedContent(params: {
|
|||||||
})) as { status: number; items: GraphHostedContent[] };
|
})) as { status: number; items: GraphHostedContent[] };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams graph hostedContents fetch failed", {
|
params.logger?.warn?.("msteams graph hostedContents fetch failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
return { media: [], count: 0 };
|
return { media: [], count: 0 };
|
||||||
}
|
}
|
||||||
@@ -240,7 +241,7 @@ async function downloadGraphHostedContent(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
out.push(createGraphHostedContentFact(item));
|
out.push(createGraphHostedContentFact(item));
|
||||||
params.logger?.warn?.("msteams graph hostedContent value fetch failed", {
|
params.logger?.warn?.("msteams graph hostedContent value fetch failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -324,10 +325,10 @@ export async function downloadMSTeamsGraphMedia(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.debug?.("graph media message parse failed", {
|
params.logger?.debug?.("graph media message parse failed", {
|
||||||
messageUrl,
|
messageUrl,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
params.logger?.warn?.("msteams graph message parse failed", {
|
params.logger?.warn?.("msteams graph message parse failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
messageUrl,
|
messageUrl,
|
||||||
});
|
});
|
||||||
msgData = {};
|
msgData = {};
|
||||||
@@ -349,10 +350,10 @@ export async function downloadMSTeamsGraphMedia(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.debug?.("graph media message fetch failed", {
|
params.logger?.debug?.("graph media message fetch failed", {
|
||||||
messageUrl,
|
messageUrl,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
params.logger?.warn?.("msteams graph message fetch failed", {
|
params.logger?.warn?.("msteams graph message fetch failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,7 +424,7 @@ export async function downloadMSTeamsGraphMedia(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
sharePointMedia.push(unavailableMedia);
|
sharePointMedia.push(unavailableMedia);
|
||||||
params.logger?.warn?.("msteams SharePoint reference download failed", {
|
params.logger?.warn?.("msteams SharePoint reference download failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -472,7 +473,7 @@ export async function downloadMSTeamsGraphMedia(params: {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
params.logger?.warn?.("msteams graph attachment download failed", {
|
params.logger?.warn?.("msteams graph attachment download failed", {
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: coerceErrorMessage(err),
|
||||||
messageUrl,
|
messageUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,20 +45,22 @@ const keepHttpServerTaskAliveMock = vi.hoisted(() =>
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
vi.mock("../runtime-api.js", () => ({
|
vi.mock("../runtime-api.js", async () => {
|
||||||
DEFAULT_WEBHOOK_MAX_BODY_BYTES: 1024 * 1024,
|
const { normalizeOptionalString } = await import("openclaw/plugin-sdk/string-coerce-runtime");
|
||||||
isDangerousNameMatchingEnabled,
|
return {
|
||||||
normalizeSecretInputString: (value: unknown) =>
|
DEFAULT_WEBHOOK_MAX_BODY_BYTES: 1024 * 1024,
|
||||||
typeof value === "string" && value.trim() ? value.trim() : undefined,
|
isDangerousNameMatchingEnabled,
|
||||||
hasConfiguredSecretInput: (value: unknown) =>
|
normalizeSecretInputString: normalizeOptionalString,
|
||||||
typeof value === "string" && value.trim().length > 0,
|
hasConfiguredSecretInput: (value: unknown) =>
|
||||||
normalizeResolvedSecretInputString: (params: { value?: unknown }) =>
|
typeof value === "string" && value.trim().length > 0,
|
||||||
typeof params?.value === "string" && params.value.trim() ? params.value.trim() : undefined,
|
normalizeResolvedSecretInputString: (params: { value?: unknown }) =>
|
||||||
keepHttpServerTaskAlive: keepHttpServerTaskAliveMock,
|
typeof params?.value === "string" && params.value.trim() ? params.value.trim() : undefined,
|
||||||
mergeAllowlist: (params: { existing?: string[]; additions: string[] }) =>
|
keepHttpServerTaskAlive: keepHttpServerTaskAliveMock,
|
||||||
Array.from(new Set([...(params.existing ?? []), ...params.additions])),
|
mergeAllowlist: (params: { existing?: string[]; additions: string[] }) =>
|
||||||
summarizeMapping: vi.fn(),
|
Array.from(new Set([...(params.existing ?? []), ...params.additions])),
|
||||||
}));
|
summarizeMapping: vi.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("express", () => ({
|
vi.mock("express", () => ({
|
||||||
default: () => {
|
default: () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|||||||
import { once } from "node:events";
|
import { once } from "node:events";
|
||||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||||
import type { AddressInfo } from "node:net";
|
import type { AddressInfo } from "node:net";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
|
|
||||||
type MSTeamsQaOutboundActivity = {
|
type MSTeamsQaOutboundActivity = {
|
||||||
activity: Record<string, unknown>;
|
activity: Record<string, unknown>;
|
||||||
@@ -74,7 +75,7 @@ export async function startMSTeamsQaBotFrameworkServer(options: ServerOptions) {
|
|||||||
sendJson(response, 200, { id: activityId });
|
sendJson(response, 200, { id: activityId });
|
||||||
})().catch((error: unknown) => {
|
})().catch((error: unknown) => {
|
||||||
sendJson(response, 500, {
|
sendJson(response, 500, {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: coerceErrorMessage(error),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Msteams plugin module implements sdk behavior.
|
// Msteams plugin module implements sdk behavior.
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||||
import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
|
import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
|
||||||
import { normalizeBotFrameworkServiceUrl } from "./bot-framework-service-url.js";
|
import { normalizeBotFrameworkServiceUrl } from "./bot-framework-service-url.js";
|
||||||
@@ -333,7 +334,7 @@ async function createFederatedApp(
|
|||||||
try {
|
try {
|
||||||
privateKey = await readSecretFile(creds.certificatePath, "Microsoft Teams certificate");
|
privateKey = await readSecretFile(creds.certificatePath, "Microsoft Teams certificate");
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = coerceErrorMessage(err);
|
||||||
throw new Error(`Failed to read certificate file at '${creds.certificatePath}': ${msg}`, {
|
throw new Error(`Failed to read certificate file at '${creds.certificatePath}': ${msg}`, {
|
||||||
cause: err,
|
cause: err,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,13 +26,15 @@ vi.mock("./oauth.token.js", () => ({
|
|||||||
refreshMSTeamsDelegatedTokens: oauthTokenMocks.refreshMSTeamsDelegatedTokens,
|
refreshMSTeamsDelegatedTokens: oauthTokenMocks.refreshMSTeamsDelegatedTokens,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./secret-input.js", () => ({
|
vi.mock("./secret-input.js", async () => {
|
||||||
normalizeSecretInputString: (v: unknown) =>
|
const { normalizeOptionalString } = await import("openclaw/plugin-sdk/string-coerce-runtime");
|
||||||
typeof v === "string" && v.trim() ? v.trim() : undefined,
|
return {
|
||||||
normalizeResolvedSecretInputString: (opts: { value: unknown; path: string }) =>
|
normalizeSecretInputString: normalizeOptionalString,
|
||||||
typeof opts.value === "string" && opts.value.trim() ? opts.value.trim() : undefined,
|
normalizeResolvedSecretInputString: (opts: { value: unknown; path: string }) =>
|
||||||
hasConfiguredSecretInput: (v: unknown) => typeof v === "string" && v.trim().length > 0,
|
typeof opts.value === "string" && opts.value.trim() ? opts.value.trim() : undefined,
|
||||||
}));
|
hasConfiguredSecretInput: (v: unknown) => typeof v === "string" && v.trim().length > 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const ENV_KEYS = [
|
const ENV_KEYS = [
|
||||||
"MSTEAMS_APP_ID",
|
"MSTEAMS_APP_ID",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
archiveLegacyStateSource,
|
archiveLegacyStateSource,
|
||||||
type PluginDoctorStateMigration,
|
type PluginDoctorStateMigration,
|
||||||
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
||||||
|
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { normalizeNostrStateAccountId } from "./src/state-account-id.js";
|
import { normalizeNostrStateAccountId } from "./src/state-account-id.js";
|
||||||
|
|
||||||
type NostrBusState = {
|
type NostrBusState = {
|
||||||
@@ -26,10 +27,6 @@ const BUS_STATE_NAMESPACE = "bus-state";
|
|||||||
const PROFILE_STATE_NAMESPACE = "profile-state";
|
const PROFILE_STATE_NAMESPACE = "profile-state";
|
||||||
const MAX_NOSTR_STATE_ENTRIES = 256;
|
const MAX_NOSTR_STATE_ENTRIES = 256;
|
||||||
|
|
||||||
function finiteNumberOrNull(value: unknown): number | null {
|
|
||||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseBusState(value: unknown): NostrBusState | null {
|
function parseBusState(value: unknown): NostrBusState | null {
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -40,8 +37,8 @@ function parseBusState(value: unknown): NostrBusState | null {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
version: 2,
|
version: 2,
|
||||||
lastProcessedAt: finiteNumberOrNull(parsed.lastProcessedAt),
|
lastProcessedAt: asFiniteNumber(parsed.lastProcessedAt) ?? null,
|
||||||
gatewayStartedAt: finiteNumberOrNull(parsed.gatewayStartedAt),
|
gatewayStartedAt: asFiniteNumber(parsed.gatewayStartedAt) ?? null,
|
||||||
recentEventIds:
|
recentEventIds:
|
||||||
parsed.version === 2 && Array.isArray(parsed.recentEventIds)
|
parsed.version === 2 && Array.isArray(parsed.recentEventIds)
|
||||||
? parsed.recentEventIds.filter((entry): entry is string => typeof entry === "string")
|
? parsed.recentEventIds.filter((entry): entry is string => typeof entry === "string")
|
||||||
@@ -68,7 +65,7 @@ function parseProfileState(value: unknown): NostrProfileState | null {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
lastPublishedAt: finiteNumberOrNull(parsed.lastPublishedAt),
|
lastPublishedAt: asFiniteNumber(parsed.lastPublishedAt) ?? null,
|
||||||
lastPublishedEventId:
|
lastPublishedEventId:
|
||||||
typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
|
typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
|
||||||
lastPublishResults:
|
lastPublishResults:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
// Ollama tests cover embedding provider plugin behavior.
|
// Ollama tests cover embedding provider plugin behavior.
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-auth";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-auth";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
@@ -14,7 +15,7 @@ const { fetchConfiguredLocalOriginWithSsrFGuardMock } = vi.hoisted(() => ({
|
|||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||||
fetchWithSsrFGuard: vi.fn(),
|
fetchWithSsrFGuard: vi.fn(),
|
||||||
formatErrorMessage: (error: unknown) => (error instanceof Error ? error.message : String(error)),
|
formatErrorMessage: coerceErrorMessage,
|
||||||
ssrfPolicyFromHttpBaseUrlAllowedOrigin: (baseUrl: string) => {
|
ssrfPolicyFromHttpBaseUrlAllowedOrigin: (baseUrl: string) => {
|
||||||
const parsed = new URL(baseUrl);
|
const parsed = new URL(baseUrl);
|
||||||
return { allowedOrigins: [parsed.origin] };
|
return { allowedOrigins: [parsed.origin] };
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
readResponseTextLimited,
|
readResponseTextLimited,
|
||||||
} from "openclaw/plugin-sdk/provider-http";
|
} from "openclaw/plugin-sdk/provider-http";
|
||||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||||
import { asNullableRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asFiniteNumber, asNullableRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
|
import { OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_INFERENCE_TIMEOUT_MS,
|
DEFAULT_INFERENCE_TIMEOUT_MS,
|
||||||
@@ -101,10 +101,6 @@ function durationMs(value: unknown): number | undefined {
|
|||||||
return Math.round((value / 1_000_000) * 100) / 100;
|
return Math.round((value / 1_000_000) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
function optionalNumber(value: unknown): number | undefined {
|
|
||||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestOllamaJson<T>(params: {
|
async function requestOllamaJson<T>(params: {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
path: string;
|
path: string;
|
||||||
@@ -300,8 +296,8 @@ async function runOllamaNodeChat(params: {
|
|||||||
`Ollama stopped after reaching maxTokens (${params.maxTokens}); retry with a larger maxTokens value`,
|
`Ollama stopped after reaching maxTokens (${params.maxTokens}); retry with a larger maxTokens value`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const promptTokens = optionalNumber(data.prompt_eval_count);
|
const promptTokens = asFiniteNumber(data.prompt_eval_count);
|
||||||
const completionTokens = optionalNumber(data.eval_count);
|
const completionTokens = asFiniteNumber(data.eval_count);
|
||||||
const loadMs = durationMs(data.load_duration);
|
const loadMs = durationMs(data.load_duration);
|
||||||
const totalMs = durationMs(data.total_duration);
|
const totalMs = durationMs(data.total_duration);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|||||||
import { createPlainTextToolCallCompatWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
|
import { createPlainTextToolCallCompatWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||||
import { isRecord, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
isRecord,
|
||||||
|
normalizeOptionalString,
|
||||||
|
readStringValue,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { estimateStringChars, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
import { estimateStringChars, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||||
import { OLLAMA_CLOUD_BASE_URL, OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
|
import { OLLAMA_CLOUD_BASE_URL, OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
|
||||||
import { normalizeOllamaWireModelId } from "./model-id.js";
|
import { normalizeOllamaWireModelId } from "./model-id.js";
|
||||||
@@ -663,7 +667,7 @@ type OllamaAssistantMessageBuildOptions = OllamaToolCallNameOptions & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function readOllamaToolCallId(value: unknown): string | undefined {
|
function readOllamaToolCallId(value: unknown): string | undefined {
|
||||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
return normalizeOptionalString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractToolCalls(
|
function extractToolCalls(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import OpenAI from "openai";
|
|||||||
import type { ResolvedTtsConfig } from "openclaw/plugin-sdk/agent-runtime";
|
import type { ResolvedTtsConfig } from "openclaw/plugin-sdk/agent-runtime";
|
||||||
import { AuthStorage, ModelRegistry } from "openclaw/plugin-sdk/agent-sessions";
|
import { AuthStorage, ModelRegistry } from "openclaw/plugin-sdk/agent-sessions";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
|
import { coerceErrorMessage as formatLiveOpenAIError } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { encodePngRgba, fillPixel } from "openclaw/plugin-sdk/media-runtime";
|
import { encodePngRgba, fillPixel } from "openclaw/plugin-sdk/media-runtime";
|
||||||
import {
|
import {
|
||||||
registerProviderPlugin,
|
registerProviderPlugin,
|
||||||
@@ -81,10 +82,6 @@ function createReferencePng(): Buffer {
|
|||||||
return encodePngRgba(buf, width, height);
|
return encodePngRgba(buf, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatLiveOpenAIError(error: unknown): string {
|
|
||||||
return error instanceof Error ? error.message : String(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveLiveOpenAISkipReason(error: unknown): string | null {
|
function resolveLiveOpenAISkipReason(error: unknown): string | null {
|
||||||
const message = formatLiveOpenAIError(error);
|
const message = formatLiveOpenAIError(error);
|
||||||
if (isTimeoutErrorMessage(message) || /timed out|operation was aborted/i.test(message)) {
|
if (isTimeoutErrorMessage(message) || /timed out|operation was aborted/i.test(message)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
ProviderResponseModelEquivalenceContext,
|
ProviderResponseModelEquivalenceContext,
|
||||||
ProviderResolveModelRoutesContext,
|
ProviderResolveModelRoutesContext,
|
||||||
} from "openclaw/plugin-sdk/provider-model-types";
|
} from "openclaw/plugin-sdk/provider-model-types";
|
||||||
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
classifyOpenAIBaseUrl,
|
classifyOpenAIBaseUrl,
|
||||||
isOpenAICodexBaseUrl,
|
isOpenAICodexBaseUrl,
|
||||||
@@ -44,11 +45,7 @@ type OpenAIResolveSingleModelRouteContext = Omit<
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeOptionalRouteApi(value: ModelApi | null | undefined): ModelApi | undefined {
|
function normalizeOptionalRouteApi(value: ModelApi | null | undefined): ModelApi | undefined {
|
||||||
return typeof value === "string" && value.trim() ? (value.trim() as ModelApi) : undefined;
|
return normalizeOptionalString(value) as ModelApi | undefined;
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeOptionalRouteBaseUrl(value: unknown): string | undefined {
|
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Canonical logical id for OpenAI catalog projection. */
|
/** Canonical logical id for OpenAI catalog projection. */
|
||||||
@@ -138,7 +135,7 @@ function firstRouteBaseUrl(...values: unknown[]): unknown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function concreteBaseUrl(value: unknown, fallback: string): string {
|
function concreteBaseUrl(value: unknown, fallback: string): string {
|
||||||
return normalizeOptionalRouteBaseUrl(value) ?? fallback;
|
return normalizeOptionalString(value) ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOpenAIEnvironmentBaseUrl(
|
function resolveOpenAIEnvironmentBaseUrl(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Openai tests cover speech provider plugin behavior.
|
// Openai tests cover speech provider plugin behavior.
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { buildOpenAISpeechProvider } from "./speech-provider.js";
|
import { buildOpenAISpeechProvider } from "./speech-provider.js";
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ function isSpeechRequestBody(value: unknown): value is {
|
|||||||
speed?: number;
|
speed?: number;
|
||||||
response_format?: string;
|
response_format?: string;
|
||||||
} {
|
} {
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
return isRecord(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRequestBody(init: RequestInit | undefined): {
|
function parseRequestBody(init: RequestInit | undefined): {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
HealthRepairResult,
|
HealthRepairResult,
|
||||||
OpenClawConfig,
|
OpenClawConfig,
|
||||||
} from "openclaw/plugin-sdk/health";
|
} from "openclaw/plugin-sdk/health";
|
||||||
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { CHECK_IDS, type POLICY_CHECK_IDS } from "./check-ids.js";
|
import { CHECK_IDS, type POLICY_CHECK_IDS } from "./check-ids.js";
|
||||||
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
|
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
|
||||||
|
|
||||||
@@ -447,7 +448,3 @@ function ensureRecord(parent: ConfigRecord, key: string): ConfigRecord {
|
|||||||
parent[key] = next;
|
parent[key] = next;
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
function uniqueStrings(values: readonly string[]): readonly string[] {
|
|
||||||
return [...new Set(values)];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
HealthRepairEffect,
|
HealthRepairEffect,
|
||||||
HealthRepairResult,
|
HealthRepairResult,
|
||||||
} from "openclaw/plugin-sdk/health";
|
} from "openclaw/plugin-sdk/health";
|
||||||
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { CHECK_IDS, type POLICY_CHECK_IDS } from "./check-ids.js";
|
import { CHECK_IDS, type POLICY_CHECK_IDS } from "./check-ids.js";
|
||||||
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
|
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
|
||||||
|
|
||||||
@@ -121,10 +122,6 @@ function previewGatewayNodeDenyCommand(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function uniqueStrings(values: readonly string[]): readonly string[] {
|
|
||||||
return [...new Set(values)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function uniqueEffects(values: readonly HealthRepairEffect[]): readonly HealthRepairEffect[] {
|
function uniqueEffects(values: readonly HealthRepairEffect[]): readonly HealthRepairEffect[] {
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
return values.filter((value) => {
|
return values.filter((value) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "@openclaw/crabline";
|
} from "@openclaw/crabline";
|
||||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { parseBooleanValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
buildQaAgenticParityComparison,
|
buildQaAgenticParityComparison,
|
||||||
buildQaRuntimeParityReport,
|
buildQaRuntimeParityReport,
|
||||||
@@ -238,20 +238,11 @@ function parseQaModelThinkingOverrides(entries: readonly string[] | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseQaBooleanModelOption(label: string, value: string) {
|
function parseQaBooleanModelOption(label: string, value: string) {
|
||||||
switch (value.trim().toLowerCase()) {
|
const parsed = parseBooleanValue(value);
|
||||||
case "1":
|
if (parsed === undefined) {
|
||||||
case "on":
|
throw new Error(`${label} fast must be one of true, false, on, off, yes, no, 1, 0`);
|
||||||
case "true":
|
|
||||||
case "yes":
|
|
||||||
return true;
|
|
||||||
case "0":
|
|
||||||
case "false":
|
|
||||||
case "no":
|
|
||||||
case "off":
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
throw new Error(`${label} fast must be one of true, false, on, off, yes, no, 1, 0`);
|
|
||||||
}
|
}
|
||||||
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseQaPositiveIntegerOption(label: string, value: number | undefined) {
|
function parseQaPositiveIntegerOption(label: string, value: number | undefined) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
// Qa Lab plugin module implements coverage report behavior.
|
// Qa Lab plugin module implements coverage report behavior.
|
||||||
import { normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
normalizeOptionalString as stringifyConfigValue,
|
||||||
|
normalizeStringEntriesLower,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { QaSeedScenarioWithSource } from "./scenario-catalog.js";
|
import type { QaSeedScenarioWithSource } from "./scenario-catalog.js";
|
||||||
import {
|
import {
|
||||||
readQaScorecardTaxonomyReport,
|
readQaScorecardTaxonomyReport,
|
||||||
@@ -134,10 +137,6 @@ function scenarioSearchText(scenario: QaSeedScenarioWithSource) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyConfigValue(value: unknown) {
|
|
||||||
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function summarizeScenarioSearchMatch(scenario: QaSeedScenarioWithSource): QaScenarioSearchMatch {
|
function summarizeScenarioSearchMatch(scenario: QaSeedScenarioWithSource): QaScenarioSearchMatch {
|
||||||
const config = scenario.execution.config ?? {};
|
const config = scenario.execution.config ?? {};
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ function resolveMatrixQaStreamingMode(
|
|||||||
function isMatrixQaStreamingConfig(
|
function isMatrixQaStreamingConfig(
|
||||||
value: MatrixQaConfigOverrides["streaming"],
|
value: MatrixQaConfigOverrides["streaming"],
|
||||||
): value is MatrixQaStreamingConfig {
|
): value is MatrixQaStreamingConfig {
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
return isRecord(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMatrixQaStreamingPreviewToolProgress(
|
function resolveMatrixQaStreamingPreviewToolProgress(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// QA Lab Slack credentials, instrumentation, and channel config.
|
// QA Lab Slack credentials, instrumentation, and channel config.
|
||||||
import type { WebClient } from "@slack/web-api";
|
import type { WebClient } from "@slack/web-api";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asNonArrayRecord, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
type SlackQaRuntimeEnv,
|
type SlackQaRuntimeEnv,
|
||||||
type SlackQaConfigOverrides,
|
type SlackQaConfigOverrides,
|
||||||
@@ -52,9 +52,7 @@ export function parseSlackQaCredentialPayload(payload: unknown): SlackQaRuntimeE
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function asPlainRecord(value: unknown): Record<string, unknown> {
|
export function asPlainRecord(value: unknown): Record<string, unknown> {
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
return asNonArrayRecord(value);
|
||||||
? (value as Record<string, unknown>)
|
|
||||||
: {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlackQaPostMessageAttempt = {
|
type SlackQaPostMessageAttempt = {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||||
@@ -168,8 +169,7 @@ async function runSessionMemoryRankingFlow(params: {
|
|||||||
seedQaSessionTranscript: async () => undefined,
|
seedQaSessionTranscript: async () => undefined,
|
||||||
forceMemoryIndex,
|
forceMemoryIndex,
|
||||||
runAgentPrompt,
|
runAgentPrompt,
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
fetchJson,
|
fetchJson,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||||
@@ -19,8 +20,7 @@ async function runMemoryRecallScenario(recallReply?: string) {
|
|||||||
fs: { rm: async () => undefined },
|
fs: { rm: async () => undefined },
|
||||||
path,
|
path,
|
||||||
formatMemoryDreamingDay: () => "2026-08-05",
|
formatMemoryDreamingDay: () => "2026-08-05",
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
runAgentPrompt: async (_env: unknown, params: { message: string }) => {
|
runAgentPrompt: async (_env: unknown, params: { message: string }) => {
|
||||||
turnCount += 1;
|
turnCount += 1;
|
||||||
state.addInboundMessage({
|
state.addInboundMessage({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
import { runLoadedScenarioFlow } from "./scenario-flow-runner.test-support.js";
|
||||||
@@ -118,8 +119,7 @@ async function runFollowUp(params?: {
|
|||||||
runAgentPrompt,
|
runAgentPrompt,
|
||||||
splitModelRef,
|
splitModelRef,
|
||||||
normalizeModelRef,
|
normalizeModelRef,
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { hasModelSwitchContinuitySignal } from "./model-switch-eval.js";
|
import { hasModelSwitchContinuitySignal } from "./model-switch-eval.js";
|
||||||
@@ -103,8 +104,7 @@ async function runToolContinuity(
|
|||||||
},
|
},
|
||||||
splitModelRef,
|
splitModelRef,
|
||||||
normalizeModelRef,
|
normalizeModelRef,
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||||
hasModelSwitchContinuitySignal,
|
hasModelSwitchContinuitySignal,
|
||||||
runAgentPrompt,
|
runAgentPrompt,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { readQaScenarioById } from "./scenario-catalog.js";
|
import { readQaScenarioById } from "./scenario-catalog.js";
|
||||||
@@ -44,8 +45,7 @@ function createCharacterScenarioApi(
|
|||||||
writeFile: async () => undefined,
|
writeFile: async () => undefined,
|
||||||
},
|
},
|
||||||
path: { join },
|
path: { join },
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
resolveQaLiveTurnTimeoutMs: () => 10,
|
resolveQaLiveTurnTimeoutMs: () => 10,
|
||||||
waitForOutboundMessage: async (
|
waitForOutboundMessage: async (
|
||||||
state: ReturnType<typeof createQaBusState>,
|
state: ReturnType<typeof createQaBusState>,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Qa Lab tests cover scenario flow runner plugin behavior.
|
// Qa Lab tests cover scenario flow runner plugin behavior.
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { createQaBusState } from "./bus-state.js";
|
import { createQaBusState } from "./bus-state.js";
|
||||||
import { QaSuiteScenarioSkipError } from "./errors.js";
|
import { QaSuiteScenarioSkipError } from "./errors.js";
|
||||||
@@ -60,10 +62,8 @@ async function runWebchatTranscriptWait(
|
|||||||
}
|
}
|
||||||
throw new Error("test condition was not met");
|
throw new Error("test condition was not met");
|
||||||
},
|
},
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
formatErrorMessage: coerceErrorMessage,
|
||||||
formatErrorMessage: (error: unknown) =>
|
|
||||||
error instanceof Error ? error.message : String(error),
|
|
||||||
liveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
liveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -284,8 +284,7 @@ function runPlanningEvidenceFixture(
|
|||||||
return summary;
|
return summary;
|
||||||
},
|
},
|
||||||
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
resolveQaLiveTurnTimeoutMs: (_env: unknown, timeoutMs: number) => timeoutMs,
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
runAgentPrompt: async () => ({ started: { runId: "current-run" }, waited: { status: "ok" } }),
|
runAgentPrompt: async () => ({ started: { runId: "current-run" }, waited: { status: "ok" } }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -397,8 +396,7 @@ describe("scenario-flow-runner", () => {
|
|||||||
throw new Error("goal artifact has not been written");
|
throw new Error("goal artifact has not been written");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
},
|
},
|
||||||
onWaitForOutboundMessage: ({ waitCount, state: currentState }) => {
|
onWaitForOutboundMessage: ({ waitCount, state: currentState }) => {
|
||||||
const currentInbound = currentState
|
const currentInbound = currentState
|
||||||
@@ -562,8 +560,7 @@ describe("scenario-flow-runner", () => {
|
|||||||
runLoadedScenarioFlow(id, {
|
runLoadedScenarioFlow(id, {
|
||||||
state,
|
state,
|
||||||
api: {
|
api: {
|
||||||
normalizeLowercaseStringOrEmpty: (value: unknown) =>
|
normalizeLowercaseStringOrEmpty,
|
||||||
typeof value === "string" ? value.trim().toLowerCase() : "",
|
|
||||||
runAgentPrompt: async () => {
|
runAgentPrompt: async () => {
|
||||||
turnCount += 1;
|
turnCount += 1;
|
||||||
state.addOutboundMessage({
|
state.addOutboundMessage({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Qa Lab plugin module implements suite summary behavior.
|
// Qa Lab plugin module implements suite summary behavior.
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asSafeIntegerInRange, isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { QaSuiteArtifactError } from "./errors.js";
|
import { QaSuiteArtifactError } from "./errors.js";
|
||||||
import type { QaEvidenceSummaryJson, QaEvidenceTiming } from "./evidence-summary.js";
|
import type { QaEvidenceSummaryJson, QaEvidenceTiming } from "./evidence-summary.js";
|
||||||
import type { QaProviderMode } from "./model-selection.js";
|
import type { QaProviderMode } from "./model-selection.js";
|
||||||
@@ -110,7 +110,7 @@ async function readQaSuiteSummaryFile(summaryPath: string): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readNonNegativeCount(value: unknown): number | null {
|
function readNonNegativeCount(value: unknown): number | null {
|
||||||
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
return asSafeIntegerInRange(value, { min: 0 }) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertQaSuiteSummaryHasExecutedScenarios(
|
function assertQaSuiteSummaryHasExecutedScenarios(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
normalizeOpenAICompatibleReasoningReplay,
|
normalizeOpenAICompatibleReasoningReplay,
|
||||||
setQwenChatTemplateThinking,
|
setQwenChatTemplateThinking,
|
||||||
} from "openclaw/plugin-sdk/provider-stream-shared";
|
} from "openclaw/plugin-sdk/provider-stream-shared";
|
||||||
|
import { asOptionalRecord as asPayloadRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import {
|
import {
|
||||||
isQwenTokenPlanDeepSeekV4ModelId,
|
isQwenTokenPlanDeepSeekV4ModelId,
|
||||||
isQwenTokenPlanGlmModelId,
|
isQwenTokenPlanGlmModelId,
|
||||||
@@ -26,12 +27,6 @@ type QwenTokenPlanThinkingContract =
|
|||||||
| { family: "kimi" }
|
| { family: "kimi" }
|
||||||
| { family: "glm"; supportsMax: boolean };
|
| { family: "glm"; supportsMax: boolean };
|
||||||
|
|
||||||
function asPayloadRecord(value: unknown): Record<string, unknown> | undefined {
|
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
|
||||||
? (value as Record<string, unknown>)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveQwenThinkingLevel(
|
function resolveQwenThinkingLevel(
|
||||||
thinkingLevel: QwenThinkingLevel,
|
thinkingLevel: QwenThinkingLevel,
|
||||||
options: Parameters<StreamFn>[2],
|
options: Parameters<StreamFn>[2],
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import {
|
|||||||
type ChannelIngressMonitorLifecycle,
|
type ChannelIngressMonitorLifecycle,
|
||||||
} from "openclaw/plugin-sdk/channel-outbound";
|
} from "openclaw/plugin-sdk/channel-outbound";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
import { normalizeNullableString as normalizeRawString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
asPositiveSafeInteger,
|
||||||
|
isRecord,
|
||||||
|
normalizeNullableString as normalizeRawString,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { SignalSseEvent } from "./client-adapter.js";
|
import type { SignalSseEvent } from "./client-adapter.js";
|
||||||
import { getOptionalSignalRuntime } from "./runtime.js";
|
import { getOptionalSignalRuntime } from "./runtime.js";
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ const SignalIngressPermanentError = createChannelIngressError<
|
|||||||
>("SignalIngressPermanentError", { withReason: true });
|
>("SignalIngressPermanentError", { withReason: true });
|
||||||
|
|
||||||
function normalizeTimestamp(value: unknown): number | null {
|
function normalizeTimestamp(value: unknown): number | null {
|
||||||
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
return asPositiveSafeInteger(value) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseReceiveEnvelope(event: SignalSseEvent): SignalIngressEnvelope | null {
|
function parseReceiveEnvelope(event: SignalSseEvent): SignalIngressEnvelope | null {
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import {
|
|||||||
type ChannelDmPolicy,
|
type ChannelDmPolicy,
|
||||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||||
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
||||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import {
|
||||||
|
asOptionalRecord,
|
||||||
|
normalizeOptionalString,
|
||||||
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { SlackAccountSurfaceFields } from "./account-surface-fields.js";
|
import type { SlackAccountSurfaceFields } from "./account-surface-fields.js";
|
||||||
import type { SlackAccountConfig } from "./runtime-api.js";
|
import type { SlackAccountConfig } from "./runtime-api.js";
|
||||||
import { resolveSlackAppToken, resolveSlackBotToken, resolveSlackUserToken } from "./token.js";
|
import { resolveSlackAppToken, resolveSlackBotToken, resolveSlackUserToken } from "./token.js";
|
||||||
@@ -123,9 +126,7 @@ type SlackStreamingConfig = NonNullable<SlackAccountConfig["streaming"]>;
|
|||||||
type SlackStreamingConfigValue = SlackStreamingConfig | boolean | string;
|
type SlackStreamingConfigValue = SlackStreamingConfig | boolean | string;
|
||||||
|
|
||||||
function asStreamingConfigObject(value: unknown): SlackStreamingConfig | undefined {
|
function asStreamingConfigObject(value: unknown): SlackStreamingConfig | undefined {
|
||||||
return value && typeof value === "object" && !Array.isArray(value)
|
return asOptionalRecord(value) as SlackStreamingConfig | undefined;
|
||||||
? (value as SlackStreamingConfig)
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function asLegacyStreamingScalar(value: unknown): boolean | string | undefined {
|
function asLegacyStreamingScalar(value: unknown): boolean | string | undefined {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
timestampMsToIsoString,
|
timestampMsToIsoString,
|
||||||
} from "openclaw/plugin-sdk/number-runtime";
|
} from "openclaw/plugin-sdk/number-runtime";
|
||||||
import {
|
import {
|
||||||
|
asOptionalRecord,
|
||||||
normalizeOptionalString,
|
normalizeOptionalString,
|
||||||
normalizeUniqueTrimmedStringList,
|
normalizeUniqueTrimmedStringList,
|
||||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
@@ -189,13 +190,6 @@ function summarizeRichTextPreview(value: unknown): string | undefined {
|
|||||||
return joined.length <= max ? joined : truncateSlackText(joined, max);
|
return joined.length <= max ? joined : truncateSlackText(joined, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInteractionAction(raw: unknown) {
|
|
||||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return raw as Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function summarizeAction(action: Record<string, unknown>): SlackActionSummary {
|
export function summarizeAction(action: Record<string, unknown>): SlackActionSummary {
|
||||||
const typed = action as {
|
const typed = action as {
|
||||||
type?: string;
|
type?: string;
|
||||||
@@ -431,7 +425,7 @@ function parseSlackBlockAction(params: {
|
|||||||
log?: (message: string) => void;
|
log?: (message: string) => void;
|
||||||
}): ParsedSlackBlockAction | null {
|
}): ParsedSlackBlockAction | null {
|
||||||
const typedBody = params.body as SlackBlockActionBody;
|
const typedBody = params.body as SlackBlockActionBody;
|
||||||
const typedAction = readInteractionAction(params.action);
|
const typedAction = asOptionalRecord(params.action);
|
||||||
if (!typedAction) {
|
if (!typedAction) {
|
||||||
params.log?.(
|
params.log?.(
|
||||||
`slack:interaction malformed action payload channel=${typedBody.channel?.id ?? typedBody.container?.channel_id ?? "unknown"} user=${
|
`slack:interaction malformed action payload channel=${typedBody.channel?.id ?? typedBody.container?.channel_id ?? "unknown"} user=${
|
||||||
|
|||||||
@@ -871,13 +871,14 @@ vi.mock("openclaw/plugin-sdk/security-runtime", () => ({
|
|||||||
resolvePinnedMainDmOwnerFromAllowlist: () => mockedPinnedMainDmOwner,
|
resolvePinnedMainDmOwnerFromAllowlist: () => mockedPinnedMainDmOwner,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", () => {
|
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", async (importOriginal) => {
|
||||||
const isMockRecord = (value: unknown): value is Record<string, unknown> =>
|
const { asOptionalRecord, isRecord } =
|
||||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
await importOriginal<typeof import("openclaw/plugin-sdk/string-coerce-runtime")>();
|
||||||
const normalizeMockLowercaseString = (value?: string) => value?.toLowerCase();
|
const normalizeMockLowercaseString = (value?: string) => value?.toLowerCase();
|
||||||
const readMockOptionalString = (value?: string) => value;
|
const readMockOptionalString = (value?: string) => value;
|
||||||
return {
|
return {
|
||||||
isRecord: isMockRecord,
|
asOptionalRecord,
|
||||||
|
isRecord,
|
||||||
normalizeOptionalLowercaseString: normalizeMockLowercaseString,
|
normalizeOptionalLowercaseString: normalizeMockLowercaseString,
|
||||||
normalizeOptionalString: readMockOptionalString,
|
normalizeOptionalString: readMockOptionalString,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import type { ClientRequest, IncomingMessage, RequestOptions } from "node:http";
|
import type { ClientRequest, IncomingMessage, RequestOptions } from "node:http";
|
||||||
import { PassThrough } from "node:stream";
|
import { PassThrough } from "node:stream";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
const ssrfMocks = {
|
const ssrfMocks = {
|
||||||
@@ -26,7 +27,7 @@ vi.mock("node:http", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||||
formatErrorMessage: (err: unknown) => (err instanceof Error ? err.message : String(err)),
|
formatErrorMessage: coerceErrorMessage,
|
||||||
resolvePinnedHostnameWithPolicy: ssrfMocks.resolvePinnedHostnameWithPolicy,
|
resolvePinnedHostnameWithPolicy: ssrfMocks.resolvePinnedHostnameWithPolicy,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { MeetingPlatformAdapter } from "openclaw/plugin-sdk/meeting-runtime";
|
import { MeetingPlatformAdapter } from "openclaw/plugin-sdk/meeting-runtime";
|
||||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||||
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { teamsMeetingsConfig } from "./src/config.js";
|
import { teamsMeetingsConfig } from "./src/config.js";
|
||||||
import { TeamsMeetingsInvalidRequestError, teamsMeetingsInvalidRequest } from "./src/errors.js";
|
import { TeamsMeetingsInvalidRequestError, teamsMeetingsInvalidRequest } from "./src/errors.js";
|
||||||
@@ -26,8 +27,7 @@ export default MeetingPlatformAdapter.createPluginShellEntry({
|
|||||||
message: Type.Optional(Type.String({ description: "Instructions to speak" })),
|
message: Type.Optional(Type.String({ description: "Instructions to speak" })),
|
||||||
}),
|
}),
|
||||||
resolveGatewayTimeoutMs: teamsMeetingsConfig.resolveGatewayOperationTimeoutMs,
|
resolveGatewayTimeoutMs: teamsMeetingsConfig.resolveGatewayOperationTimeoutMs,
|
||||||
normalizeRequesterSessionKey: (value) =>
|
normalizeRequesterSessionKey: normalizeOptionalString,
|
||||||
typeof value === "string" && value.trim() ? value.trim() : undefined,
|
|
||||||
normalizeToolAgentId: (agentId) => (agentId ? normalizeAgentId(agentId) : undefined),
|
normalizeToolAgentId: (agentId) => (agentId ? normalizeAgentId(agentId) : undefined),
|
||||||
resolveToolRuntime: async (api, agentId) => {
|
resolveToolRuntime: async (api, agentId) => {
|
||||||
const trustedRouting = Boolean(agentId && agentId !== "main");
|
const trustedRouting = Boolean(agentId && agentId !== "main");
|
||||||
|
|||||||
@@ -11,19 +11,14 @@ vi.mock("openclaw/plugin-sdk/text-utility-runtime", () => ({
|
|||||||
fetchWithTimeout: fetchWithTimeoutMock,
|
fetchWithTimeout: fetchWithTimeoutMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", () => {
|
vi.mock("openclaw/plugin-sdk/string-coerce-runtime", async (importOriginal) => {
|
||||||
|
const { normalizeOptionalString } =
|
||||||
|
await importOriginal<typeof import("openclaw/plugin-sdk/string-coerce-runtime")>();
|
||||||
const isMockRecord = (value: unknown): value is Record<string, unknown> =>
|
const isMockRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
typeof value === "object" && value !== null;
|
typeof value === "object" && value !== null;
|
||||||
const normalizeMockOptionalString = (value: unknown) => {
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed ? trimmed : undefined;
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
isRecord: isMockRecord,
|
isRecord: isMockRecord,
|
||||||
normalizeOptionalString: normalizeMockOptionalString,
|
normalizeOptionalString,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
} from "openclaw/plugin-sdk/conversation-runtime";
|
||||||
import { isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
|
import { isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { logVerbose, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
import { logVerbose, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||||
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { TelegramApprovalCallback } from "./approval-callback-data.js";
|
import type { TelegramApprovalCallback } from "./approval-callback-data.js";
|
||||||
import {
|
import {
|
||||||
buildTelegramCanonicalApprovalTerminalText,
|
buildTelegramCanonicalApprovalTerminalText,
|
||||||
@@ -420,11 +421,7 @@ const updateMultiSelectKeyboard = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const resolvePluginCallbackSubmitText = (submitText: unknown): string | undefined => {
|
const resolvePluginCallbackSubmitText = (submitText: unknown): string | undefined => {
|
||||||
if (typeof submitText !== "string") {
|
return normalizeOptionalString(submitText);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const trimmed = submitText.trim();
|
|
||||||
return trimmed ? trimmed : undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isReplySessionInitConflictError = (err: unknown): boolean =>
|
const isReplySessionInitConflictError = (err: unknown): boolean =>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
resolveAmbientTranscriptWatermarkKey,
|
resolveAmbientTranscriptWatermarkKey,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||||
|
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
||||||
import { resolveDefaultModelForAgent } from "./bot-handlers.agent.runtime.js";
|
import { resolveDefaultModelForAgent } from "./bot-handlers.agent.runtime.js";
|
||||||
import type { RegisterTelegramHandlerParams } from "./bot-handlers.types.js";
|
import type { RegisterTelegramHandlerParams } from "./bot-handlers.types.js";
|
||||||
@@ -98,7 +99,7 @@ export type ResolvePromptContextAmbientWatermarkParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const normalizePromptContextMinTimestampMs = (timestampMs?: number) =>
|
export const normalizePromptContextMinTimestampMs = (timestampMs?: number) =>
|
||||||
typeof timestampMs === "number" && Number.isFinite(timestampMs) ? timestampMs : undefined;
|
asFiniteNumber(timestampMs);
|
||||||
|
|
||||||
export function promptContextBoundaryOptions(
|
export function promptContextBoundaryOptions(
|
||||||
timestampMs?: number,
|
timestampMs?: number,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Telegram tests cover bot.create telegram bot.media group skip warning plugin behavior.
|
// Telegram tests cover bot.create telegram bot.media group skip warning plugin behavior.
|
||||||
import { setTimeout as delay } from "node:timers/promises";
|
import { setTimeout as delay } from "node:timers/promises";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { telegramBotInfoForTest } from "./bot.create-telegram-bot.test-support.js";
|
import { telegramBotInfoForTest } from "./bot.create-telegram-bot.test-support.js";
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ vi.mock("./bot/delivery.resolve-media.runtime.js", async () => {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
||||||
formatErrorMessage: (err: unknown) => (err instanceof Error ? err.message : String(err)),
|
formatErrorMessage: coerceErrorMessage,
|
||||||
logVerbose: () => {},
|
logVerbose: () => {},
|
||||||
MediaFetchError: actual.MediaFetchError,
|
MediaFetchError: actual.MediaFetchError,
|
||||||
resolveTelegramApiBase: (apiRoot?: string) =>
|
resolveTelegramApiBase: (apiRoot?: string) =>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { GrammyError } from "grammy";
|
import { GrammyError } from "grammy";
|
||||||
import type { Message } from "grammy/types";
|
import type { Message } from "grammy/types";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||||
// Telegram tests cover delivery.resolve media retry plugin behavior.
|
// Telegram tests cover delivery.resolve media retry plugin behavior.
|
||||||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||||
@@ -56,7 +57,7 @@ vi.mock("./delivery.resolve-media.runtime.js", () => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
||||||
formatErrorMessage: (err: unknown) => (err instanceof Error ? err.message : String(err)),
|
formatErrorMessage: coerceErrorMessage,
|
||||||
logVerbose: () => {},
|
logVerbose: () => {},
|
||||||
MediaFetchError,
|
MediaFetchError,
|
||||||
resolveTelegramApiBase: (apiRoot?: string) =>
|
resolveTelegramApiBase: (apiRoot?: string) =>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Message } from "grammy/types";
|
import type { Message } from "grammy/types";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||||
// Telegram tests cover delivery.resolve media retry plugin behavior.
|
// Telegram tests cover delivery.resolve media retry plugin behavior.
|
||||||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||||
@@ -55,7 +56,7 @@ vi.mock("./delivery.resolve-media.runtime.js", () => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
readRemoteMediaBuffer: (...args: unknown[]) => readRemoteMediaBuffer(...args),
|
||||||
formatErrorMessage: (err: unknown) => (err instanceof Error ? err.message : String(err)),
|
formatErrorMessage: coerceErrorMessage,
|
||||||
logVerbose: () => {},
|
logVerbose: () => {},
|
||||||
MediaFetchError,
|
MediaFetchError,
|
||||||
resolveTelegramApiBase: (apiRoot?: string) =>
|
resolveTelegramApiBase: (apiRoot?: string) =>
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => ({
|
||||||
isTruthyEnvValue: (value: string | undefined) =>
|
...(await importOriginal<typeof import("openclaw/plugin-sdk/runtime-env")>()),
|
||||||
typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim()),
|
|
||||||
isWSL2Sync: vi.fn(() => false),
|
isWSL2Sync: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { GrammyError } from "grammy";
|
import { GrammyError } from "grammy";
|
||||||
import type { MessageEntity } from "grammy/types";
|
import type { MessageEntity } from "grammy/types";
|
||||||
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||||
|
import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js";
|
import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js";
|
||||||
import { normalizeTelegramReplyToMessageId } from "./outbound-params.js";
|
import { normalizeTelegramReplyToMessageId } from "./outbound-params.js";
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ export function getTelegramNativeQuoteReplyMessageId(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const messageId = (replyParameters as { message_id?: unknown }).message_id;
|
const messageId = (replyParameters as { message_id?: unknown }).message_id;
|
||||||
return typeof messageId === "number" && Number.isFinite(messageId) ? messageId : undefined;
|
return asFiniteNumber(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTelegramQuoteParamError(err: unknown): boolean {
|
export function isTelegramQuoteParamError(err: unknown): boolean {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
resolveEnabledConfiguredAccountId,
|
resolveEnabledConfiguredAccountId,
|
||||||
type AccountStatusSnapshot,
|
type AccountStatusSnapshot,
|
||||||
} from "openclaw/plugin-sdk/status-helpers";
|
} from "openclaw/plugin-sdk/status-helpers";
|
||||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
const TELEGRAM_POLLING_CONNECT_GRACE_MS = 120_000;
|
const TELEGRAM_POLLING_CONNECT_GRACE_MS = 120_000;
|
||||||
const TELEGRAM_POLLING_STALE_TRANSPORT_MS = 30 * 60_000;
|
const TELEGRAM_POLLING_STALE_TRANSPORT_MS = 30 * 60_000;
|
||||||
@@ -41,10 +41,6 @@ type TelegramGroupMembershipAuditSummary = {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function asFiniteNumberOrNull(value: unknown): number | null {
|
|
||||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendTelegramRuntimeError(message: string, lastError: unknown): string {
|
function appendTelegramRuntimeError(message: string, lastError: unknown): string {
|
||||||
const error = normalizeOptionalString(lastError);
|
const error = normalizeOptionalString(lastError);
|
||||||
return error ? `${message}: ${error}` : message;
|
return error ? `${message}: ${error}` : message;
|
||||||
@@ -69,8 +65,8 @@ function collectTelegramPollingRuntimeIssues(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastStartAt = asFiniteNumberOrNull(account.lastStartAt);
|
const lastStartAt = asFiniteNumber(account.lastStartAt) ?? null;
|
||||||
const lastTransportActivityAt = asFiniteNumberOrNull(account.lastTransportActivityAt);
|
const lastTransportActivityAt = asFiniteNumber(account.lastTransportActivityAt) ?? null;
|
||||||
const fix = `Run: ${formatCliCommand("openclaw channels status --probe")} (or restart the gateway). Check the bot token, proxy/network settings, and logs if it persists.`;
|
const fix = `Run: ${formatCliCommand("openclaw channels status --probe")} (or restart the gateway). Check the bot token, proxy/network settings, and logs if it persists.`;
|
||||||
|
|
||||||
if (account.connected === false) {
|
if (account.connected === false) {
|
||||||
@@ -129,7 +125,7 @@ function collectTelegramWebhookRuntimeIssues(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastStartAt = asFiniteNumberOrNull(account.lastStartAt);
|
const lastStartAt = asFiniteNumber(account.lastStartAt) ?? null;
|
||||||
const withinStartupGrace =
|
const withinStartupGrace =
|
||||||
lastStartAt != null && now - lastStartAt < TELEGRAM_WEBHOOK_CONNECT_GRACE_MS;
|
lastStartAt != null && now - lastStartAt < TELEGRAM_WEBHOOK_CONNECT_GRACE_MS;
|
||||||
if (withinStartupGrace) {
|
if (withinStartupGrace) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
sleepWithAbort,
|
sleepWithAbort,
|
||||||
type BackoffPolicy,
|
type BackoffPolicy,
|
||||||
} from "openclaw/plugin-sdk/runtime-env";
|
} from "openclaw/plugin-sdk/runtime-env";
|
||||||
|
import { asSafeIntegerInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
|
|
||||||
const OFFSET_PERSIST_RETRY_POLICY: BackoffPolicy = {
|
const OFFSET_PERSIST_RETRY_POLICY: BackoffPolicy = {
|
||||||
initialMs: 250,
|
initialMs: 250,
|
||||||
@@ -21,10 +22,7 @@ type TelegramUpdateOffsetPersistenceOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function normalizeTelegramUpdateId(value: number | null): number | null {
|
export function normalizeTelegramUpdateId(value: number | null): number | null {
|
||||||
if (value === null || !Number.isSafeInteger(value) || value < 0) {
|
return asSafeIntegerInRange(value, { min: 0 }) ?? null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTelegramUpdateOffsetPersistence(
|
export function createTelegramUpdateOffsetPersistence(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Tlon plugin module implements discovery behavior.
|
// Tlon plugin module implements discovery behavior.
|
||||||
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||||
import { asNullableRecord as asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asNullableRecord as asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import type { Foreigns } from "../urbit/foreigns.js";
|
import type { Foreigns } from "../urbit/foreigns.js";
|
||||||
import { formatErrorMessage } from "./utils.js";
|
|
||||||
|
|
||||||
interface InitData {
|
interface InitData {
|
||||||
channels: string[];
|
channels: string[];
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// Tlon plugin module implements history behavior.
|
// Tlon plugin module implements history behavior.
|
||||||
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||||
import { asNullableRecord as asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asNullableRecord as asRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
import { extractMessageText, formatErrorMessage } from "./utils.js";
|
import { extractMessageText } from "./utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a number as @ud (with dots every 3 digits from the right)
|
* Format a number as @ud (with dots every 3 digits from the right)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
bindIngressLifecycleToReplyOptions,
|
bindIngressLifecycleToReplyOptions,
|
||||||
waitUntilAbort,
|
waitUntilAbort,
|
||||||
} from "openclaw/plugin-sdk/channel-outbound";
|
} from "openclaw/plugin-sdk/channel-outbound";
|
||||||
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import type { GetReplyOptions, ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
import type { GetReplyOptions, ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||||
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
||||||
@@ -44,7 +45,6 @@ import {
|
|||||||
shouldMigrateTlonSetting,
|
shouldMigrateTlonSetting,
|
||||||
} from "./settings-helpers.js";
|
} from "./settings-helpers.js";
|
||||||
import { createActiveSnapshotTracker, createParticipatedThreadTracker } from "./tracking.js";
|
import { createActiveSnapshotTracker, createParticipatedThreadTracker } from "./tracking.js";
|
||||||
import { formatErrorMessage } from "./utils.js";
|
|
||||||
import {
|
import {
|
||||||
extractMessageText,
|
extractMessageText,
|
||||||
formatModelName,
|
formatModelName,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
type StableChannelIngressIdentityParams,
|
type StableChannelIngressIdentityParams,
|
||||||
} from "openclaw/plugin-sdk/channel-ingress-runtime";
|
} from "openclaw/plugin-sdk/channel-ingress-runtime";
|
||||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||||
import { formatErrorMessage as sharedFormatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
||||||
// Tlon helper module supports utils behavior.
|
// Tlon helper module supports utils behavior.
|
||||||
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
||||||
import { asNullableRecord, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
import { asNullableRecord, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||||
@@ -243,8 +242,6 @@ export async function resolveAuthorizedMessageText(params: {
|
|||||||
return citedContent + rawText;
|
return citedContent + rawText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatErrorMessage = sharedFormatErrorMessage;
|
|
||||||
|
|
||||||
// Helper to recursively extract text from inline content
|
// Helper to recursively extract text from inline content
|
||||||
function renderInlineItem(
|
function renderInlineItem(
|
||||||
item: unknown,
|
item: unknown,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Together tests cover together plugin behavior.
|
// Together tests cover together plugin behavior.
|
||||||
import { completeSimple, type Model } from "openclaw/plugin-sdk/llm";
|
import { completeSimple, type Model } from "openclaw/plugin-sdk/llm";
|
||||||
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./models.js";
|
import { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./models.js";
|
||||||
|
|
||||||
const TOGETHER_KEY = process.env.TOGETHER_API_KEY ?? "";
|
const TOGETHER_KEY = process.env.TOGETHER_API_KEY ?? "";
|
||||||
const LIVE = ["LIVE", "OPENCLAW_LIVE_TEST", "TOGETHER_LIVE_TEST"].some((name) => {
|
const LIVE = ["LIVE", "OPENCLAW_LIVE_TEST", "TOGETHER_LIVE_TEST"].some((name) =>
|
||||||
const value = process.env[name]?.trim().toLowerCase();
|
isTruthyEnvValue(process.env[name]),
|
||||||
return value === "1" || value === "true" || value === "yes" || value === "on";
|
);
|
||||||
});
|
|
||||||
const TOGETHER_LIVE_TIMEOUT_MS = 45_000;
|
const TOGETHER_LIVE_TIMEOUT_MS = 45_000;
|
||||||
|
|
||||||
const describeLive = LIVE && TOGETHER_KEY ? describe : describe.skip;
|
const describeLive = LIVE && TOGETHER_KEY ? describe : describe.skip;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { EventEmitter } from "node:events";
|
|||||||
import fsSync from "node:fs";
|
import fsSync from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { createChannelIngressQueueForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
import { createChannelIngressQueueForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
||||||
import { resetLogger, setLoggerOverride } from "openclaw/plugin-sdk/runtime-env";
|
import { resetLogger, setLoggerOverride } from "openclaw/plugin-sdk/runtime-env";
|
||||||
import { afterEach, beforeEach, expect, vi } from "vitest";
|
import { afterEach, beforeEach, expect, vi } from "vitest";
|
||||||
@@ -238,7 +239,7 @@ vi.mock("./session.js", async () => {
|
|||||||
}),
|
}),
|
||||||
waitForWaConnection: vi.fn().mockResolvedValue(undefined),
|
waitForWaConnection: vi.fn().mockResolvedValue(undefined),
|
||||||
getStatusCode: vi.fn(() => 500),
|
getStatusCode: vi.fn(() => 500),
|
||||||
formatError: (err: unknown) => (err instanceof Error ? err.message : String(err)),
|
formatError: coerceErrorMessage,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Whatsapp tests cover qa driver plugin behavior.
|
// Whatsapp tests cover qa driver plugin behavior.
|
||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import type { proto, WAMessage } from "baileys";
|
import type { proto, WAMessage } from "baileys";
|
||||||
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { startWhatsAppQaDriverSession, type WhatsAppQaDriverSession } from "./qa-driver.runtime.js";
|
import { startWhatsAppQaDriverSession, type WhatsAppQaDriverSession } from "./qa-driver.runtime.js";
|
||||||
import { DEFAULT_WHATSAPP_SOCKET_TIMING } from "./socket-timing.js";
|
import { DEFAULT_WHATSAPP_SOCKET_TIMING } from "./socket-timing.js";
|
||||||
@@ -22,7 +23,7 @@ const mocks = vi.hoisted(() => ({
|
|||||||
|
|
||||||
vi.mock("./session.js", () => ({
|
vi.mock("./session.js", () => ({
|
||||||
createWaSocket: mocks.createWaSocket,
|
createWaSocket: mocks.createWaSocket,
|
||||||
formatError: (error: unknown) => (error instanceof Error ? error.message : String(error)),
|
formatError: coerceErrorMessage,
|
||||||
getStatusCode: (error: unknown) =>
|
getStatusCode: (error: unknown) =>
|
||||||
(error as { output?: { statusCode?: number } } | undefined)?.output?.statusCode,
|
(error as { output?: { statusCode?: number } } | undefined)?.output?.statusCode,
|
||||||
waitForWaConnection: mocks.waitForWaConnection,
|
waitForWaConnection: mocks.waitForWaConnection,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// ACP Core module implements meta behavior.
|
// ACP Core module implements meta behavior.
|
||||||
|
import { asFiniteNumber, asSafeIntegerInRange } from "@openclaw/normalization-core/number-coercion";
|
||||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||||
|
|
||||||
function readMetaValue<T>(
|
function readMetaValue<T>(
|
||||||
@@ -39,9 +40,7 @@ export function readMetadataNumber(
|
|||||||
meta: Record<string, unknown> | null | undefined,
|
meta: Record<string, unknown> | null | undefined,
|
||||||
keys: string[],
|
keys: string[],
|
||||||
): number | undefined {
|
): number | undefined {
|
||||||
return readMetaValue(meta, keys, (value) =>
|
return readMetaValue(meta, keys, asFiniteNumber);
|
||||||
typeof value === "number" && Number.isFinite(value) ? value : undefined,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reads the first safe non-negative integer metadata value, preserving zero. */
|
/** Reads the first safe non-negative integer metadata value, preserving zero. */
|
||||||
@@ -49,7 +48,5 @@ export function readNonNegativeInteger(
|
|||||||
meta: Record<string, unknown> | null | undefined,
|
meta: Record<string, unknown> | null | undefined,
|
||||||
keys: string[],
|
keys: string[],
|
||||||
): number | undefined {
|
): number | undefined {
|
||||||
return readMetaValue(meta, keys, (value) =>
|
return readMetaValue(meta, keys, (value) => asSafeIntegerInRange(value, { min: 0 }));
|
||||||
typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
ToolResultMessage,
|
ToolResultMessage,
|
||||||
} from "@openclaw/llm-core";
|
} from "@openclaw/llm-core";
|
||||||
import type { EventStream as SourceEventStream } from "@openclaw/llm-core";
|
import type { EventStream as SourceEventStream } from "@openclaw/llm-core";
|
||||||
|
import { coerceErrorMessage } from "@openclaw/normalization-core/error-coercion";
|
||||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||||
import { TranscriptNotContinuableError } from "./errors.js";
|
import { TranscriptNotContinuableError } from "./errors.js";
|
||||||
import { uuidv7 } from "./harness/session/uuid.js";
|
import { uuidv7 } from "./harness/session/uuid.js";
|
||||||
@@ -1332,7 +1333,7 @@ async function prepareToolCall(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
result: createErrorToolResult(coerceErrorMessage(error)),
|
||||||
isError: true,
|
isError: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1360,11 +1361,7 @@ async function validateToolCallForBatchAdmission(
|
|||||||
outcome: {
|
outcome: {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
result: createErrorToolResult(
|
result: createErrorToolResult(
|
||||||
signal?.aborted
|
signal?.aborted ? "Operation aborted" : coerceErrorMessage(resolution.error),
|
||||||
? "Operation aborted"
|
|
||||||
: resolution.error instanceof Error
|
|
||||||
? resolution.error.message
|
|
||||||
: String(resolution.error),
|
|
||||||
),
|
),
|
||||||
isError: true,
|
isError: true,
|
||||||
},
|
},
|
||||||
@@ -1390,7 +1387,7 @@ async function validateToolCallForBatchAdmission(
|
|||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
outcome: {
|
outcome: {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
result: createErrorToolResult(coerceErrorMessage(error)),
|
||||||
isError: true,
|
isError: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1404,7 +1401,7 @@ async function validateToolCallForBatchAdmission(
|
|||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
outcome: {
|
outcome: {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
result: createErrorToolResult(coerceErrorMessage(error)),
|
||||||
isError: true,
|
isError: true,
|
||||||
errorKind: "argument-validation",
|
errorKind: "argument-validation",
|
||||||
},
|
},
|
||||||
@@ -1452,7 +1449,7 @@ async function prepareToolCallExecution(
|
|||||||
return {
|
return {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
outcome: {
|
outcome: {
|
||||||
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
result: createErrorToolResult(coerceErrorMessage(error)),
|
||||||
isError: true,
|
isError: true,
|
||||||
executionStarted: false,
|
executionStarted: false,
|
||||||
},
|
},
|
||||||
@@ -1508,7 +1505,7 @@ async function prepareToolCallExecution(
|
|||||||
throw implementationStartError.error;
|
throw implementationStartError.error;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
result: createErrorToolResult(coerceErrorMessage(error)),
|
||||||
isError: true,
|
isError: true,
|
||||||
executionStarted,
|
executionStarted,
|
||||||
...(executionStarted && signal?.aborted && error === signal.reason
|
...(executionStarted && signal?.aborted && error === signal.reason
|
||||||
@@ -1570,11 +1567,7 @@ async function prepareToolCallExecution(
|
|||||||
return {
|
return {
|
||||||
kind: "immediate",
|
kind: "immediate",
|
||||||
outcome: {
|
outcome: {
|
||||||
result: createErrorToolResult(
|
result: createErrorToolResult(coerceErrorMessage(internalPreparation.outcome.error)),
|
||||||
internalPreparation.outcome.error instanceof Error
|
|
||||||
? internalPreparation.outcome.error.message
|
|
||||||
: String(internalPreparation.outcome.error),
|
|
||||||
),
|
|
||||||
isError: true,
|
isError: true,
|
||||||
executionStarted: false,
|
executionStarted: false,
|
||||||
},
|
},
|
||||||
@@ -1627,7 +1620,7 @@ async function finalizeExecutedToolCall(
|
|||||||
isError = afterResult.isError ?? isError;
|
isError = afterResult.isError ?? isError;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
result = createErrorToolResult(error instanceof Error ? error.message : String(error));
|
result = createErrorToolResult(coerceErrorMessage(error));
|
||||||
isError = true;
|
isError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1692,9 +1685,7 @@ async function finalizeToolCallOutcome(
|
|||||||
isError: afterResult.isError ?? finalized.isError,
|
isError: afterResult.isError ?? finalized.isError,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorResult = createErrorToolResult(
|
const errorResult = createErrorToolResult(coerceErrorMessage(error));
|
||||||
error instanceof Error ? error.message : String(error),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...finalized,
|
...finalized,
|
||||||
result: {
|
result: {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { asNonNegativeFiniteNumber } from "@openclaw/normalization-core/number-coercion";
|
||||||
import type { Usage } from "../types.js";
|
import type { Usage } from "../types.js";
|
||||||
|
|
||||||
type AnthropicUsagePayload = {
|
type AnthropicUsagePayload = {
|
||||||
@@ -31,7 +32,7 @@ export type AnthropicIterationUsageResult =
|
|||||||
| { state: "valid"; usage: AnthropicIterationUsageSnapshot };
|
| { state: "valid"; usage: AnthropicIterationUsageSnapshot };
|
||||||
|
|
||||||
export function readAnthropicUsageTokenCount(value: unknown): number | undefined {
|
export function readAnthropicUsageTokenCount(value: unknown): number | undefined {
|
||||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
return asNonNegativeFiniteNumber(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readAnthropicCacheWriteUsage(
|
export function readAnthropicCacheWriteUsage(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
* package and managed transports from drifting on token buckets, service-tier pricing, or future
|
* package and managed transports from drifting on token buckets, service-tier pricing, or future
|
||||||
* terminal-event semantics.
|
* terminal-event semantics.
|
||||||
*/
|
*/
|
||||||
|
import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
|
||||||
import type OpenAI from "openai";
|
import type OpenAI from "openai";
|
||||||
import type { StopReason, Usage } from "../types.js";
|
import type { StopReason, Usage } from "../types.js";
|
||||||
|
|
||||||
@@ -53,10 +54,7 @@ export function mapResponsesTerminalUsage(
|
|||||||
export function readResponsesReasoningTokens(
|
export function readResponsesReasoningTokens(
|
||||||
usage: ResponsesTerminalUsagePayload | undefined | null,
|
usage: ResponsesTerminalUsagePayload | undefined | null,
|
||||||
): number | undefined {
|
): number | undefined {
|
||||||
const reasoningTokens = usage?.output_tokens_details?.reasoning_tokens;
|
return asFiniteNumber(usage?.output_tokens_details?.reasoning_tokens);
|
||||||
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens)
|
|
||||||
? reasoningTokens
|
|
||||||
: undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapResponsesTerminalStopReason(
|
function mapResponsesTerminalStopReason(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user