From e533ff4c4a11d757bb1ff16d6abdccc5a8f35540 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 06:40:26 -0400 Subject: [PATCH] docs: document runtime helper contracts --- src/agents/channel-tools.ts | 12 ++++++++++++ src/agents/deepseek-text-filter.test.ts | 4 ++++ src/agents/deepseek-text-filter.ts | 7 +++++-- src/agents/internal-runtime-context.test.ts | 4 ++++ src/agents/internal-runtime-context.ts | 16 ++++++++++++++++ src/agents/local-model-lean.test.ts | 4 ++++ src/agents/local-model-lean.ts | 9 +++++---- src/agents/mcp-stdio.ts | 8 ++++++-- src/agents/model-suppression.runtime.ts | 8 ++++++-- src/agents/model-transport-url.test.ts | 4 ++++ src/agents/model-transport-url.ts | 6 ++++-- src/agents/subagent-capabilities.test.ts | 4 ++++ src/agents/subagent-capabilities.ts | 11 +++++++++-- src/agents/usage.normalization.test.ts | 4 ++++ src/agents/usage.test.ts | 4 ++++ src/agents/usage.ts | 15 +++++++++++++++ 16 files changed, 106 insertions(+), 14 deletions(-) diff --git a/src/agents/channel-tools.ts b/src/agents/channel-tools.ts index 0ad3394cab08..e87b51cbc2fe 100644 --- a/src/agents/channel-tools.ts +++ b/src/agents/channel-tools.ts @@ -1,3 +1,8 @@ +/** + * Channel-owned agent tool and prompt helpers. + * Discovers channel tools, message actions, prompt capabilities, reaction + * guidance, and weakly-attached channel metadata for wrapped tools. + */ import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; import { getChannelPlugin, listChannelPlugins } from "../channels/plugins/index.js"; import { @@ -36,10 +41,12 @@ type ChannelMessageActionDiscoveryParams = { const channelAgentToolMeta = new WeakMap(); +/** Read channel metadata attached to a channel-owned agent tool. */ export function getChannelAgentToolMeta(tool: ChannelAgentTool): ChannelAgentToolMeta | undefined { return channelAgentToolMeta.get(tool); } +/** Copy channel metadata when wrapping or replacing a channel-owned tool. */ export function copyChannelAgentToolMeta(source: ChannelAgentTool, target: ChannelAgentTool): void { const meta = channelAgentToolMeta.get(source); if (meta) { @@ -96,6 +103,7 @@ export function listAllChannelSupportedActions( return Array.from(actions); } +/** List agent tools contributed by registered channel plugins. */ export function listChannelAgentTools(params: { cfg?: OpenClawConfig }): ChannelAgentTool[] { // Channel docking: aggregate channel-owned tools (login, etc.). const tools: ChannelAgentTool[] = []; @@ -115,6 +123,7 @@ export function listChannelAgentTools(params: { cfg?: OpenClawConfig }): Channel return tools; } +/** Resolve channel-specific message tool hints for system prompt assembly. */ export function resolveChannelMessageToolHints(params: { cfg?: OpenClawConfig; channel?: string | null; @@ -132,6 +141,7 @@ export function resolveChannelMessageToolHints(params: { return normalizeStringEntries(resolve({ cfg, accountId: params.accountId })); } +/** Resolve channel prompt capabilities, including native approval UI support. */ export function resolveChannelPromptCapabilities(params: { cfg?: OpenClawConfig; channel?: string | null; @@ -156,6 +166,7 @@ function normalizePromptCapabilities(capabilities?: readonly string[] | null): s return normalizeStringEntries(capabilities ?? []); } +/** Resolve optional channel reaction guidance for assistant replies. */ export function resolveChannelReactionGuidance(params: { cfg?: OpenClawConfig; channel?: string | null; @@ -180,6 +191,7 @@ export function resolveChannelReactionGuidance(params: { }; } +/** Test-only utilities for channel tool discovery state. */ export const testing = { resetLoggedListActionErrors() { messageActionTesting.resetLoggedMessageActionErrors(); diff --git a/src/agents/deepseek-text-filter.test.ts b/src/agents/deepseek-text-filter.test.ts index c92e99bfcddb..49b02758fbaa 100644 --- a/src/agents/deepseek-text-filter.test.ts +++ b/src/agents/deepseek-text-filter.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for DeepSeek DSML streamed text filtering. + * Verifies complete, split, full-width, and unterminated DSML markup handling. + */ import { describe, expect, it } from "vitest"; import { createDeepSeekTextFilter } from "./deepseek-text-filter.js"; diff --git a/src/agents/deepseek-text-filter.ts b/src/agents/deepseek-text-filter.ts index 8506c22f6d08..531c855c8e20 100644 --- a/src/agents/deepseek-text-filter.ts +++ b/src/agents/deepseek-text-filter.ts @@ -1,8 +1,11 @@ +/** + * DeepSeek DSML streaming text filter. + * Removes provider-emitted DSML tool markup while buffering split tag prefixes + * across streamed chunks. + */ const DSML_KINDS = ["tool_use_error", "tool_calls", "tool_call", "function_calls"] as const; const DSML_BARS = ["|", "|"] as const; -// Streaming filter for DeepSeek DSML tool markup. It removes complete markup -// blocks while holding enough trailing bytes to recognize split open/close tags. const DSML_OPEN_TOKENS = DSML_BARS.flatMap((bar) => DSML_KINDS.map((kind) => `<${bar}DSML${bar}${kind}>`), ); diff --git a/src/agents/internal-runtime-context.test.ts b/src/agents/internal-runtime-context.test.ts index 1725594ba867..a82cfbbda27e 100644 --- a/src/agents/internal-runtime-context.test.ts +++ b/src/agents/internal-runtime-context.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for internal runtime-context stripping and extraction. + * Verifies protected delimiters, legacy blocks, and custom-message filtering. + */ import { describe, expect, it } from "vitest"; import { escapeInternalRuntimeContextDelimiters, diff --git a/src/agents/internal-runtime-context.ts b/src/agents/internal-runtime-context.ts index adeefd7342d0..77ce62fecced 100644 --- a/src/agents/internal-runtime-context.ts +++ b/src/agents/internal-runtime-context.ts @@ -1,14 +1,25 @@ +/** + * Internal runtime-context delimiter and stripping helpers. + * Protects runtime-generated prompt blocks from user text and removes old + * context formats before replaying or comparing messages. + */ +/** Opening delimiter for protected OpenClaw runtime context blocks. */ export const INTERNAL_RUNTIME_CONTEXT_BEGIN = "<<>>"; +/** Closing delimiter for protected OpenClaw runtime context blocks. */ export const INTERNAL_RUNTIME_CONTEXT_END = "<<>>"; const ESCAPED_INTERNAL_RUNTIME_CONTEXT_BEGIN = "[[OPENCLAW_INTERNAL_CONTEXT_BEGIN]]"; const ESCAPED_INTERNAL_RUNTIME_CONTEXT_END = "[[OPENCLAW_INTERNAL_CONTEXT_END]]"; +/** Notice inserted into runtime-generated context blocks. */ export const OPENCLAW_RUNTIME_CONTEXT_NOTICE = "This context is runtime-generated, not user-authored. Keep internal details private."; +/** Header for context attached to the immediately preceding user message. */ export const OPENCLAW_NEXT_TURN_RUNTIME_CONTEXT_HEADER = "OpenClaw runtime context for the immediately preceding user message."; +/** Header for runtime events passed as prompt context. */ export const OPENCLAW_RUNTIME_EVENT_HEADER = "OpenClaw runtime event."; +/** Custom message type used for structured runtime-context messages. */ export const OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE = "openclaw.runtime-context"; const LEGACY_INTERNAL_CONTEXT_HEADER = @@ -19,6 +30,7 @@ const LEGACY_INTERNAL_EVENT_SEPARATOR = "\n\n---\n\n"; const LEGACY_UNTRUSTED_RESULT_BEGIN = "<<>>"; const LEGACY_UNTRUSTED_RESULT_END = "<<>>"; +/** Escape protected context delimiters before embedding untrusted text. */ export function escapeInternalRuntimeContextDelimiters(value: string): string { return value .replaceAll(INTERNAL_RUNTIME_CONTEXT_BEGIN, ESCAPED_INTERNAL_RUNTIME_CONTEXT_BEGIN) @@ -204,6 +216,7 @@ function stripRuntimeContextPromptPreface(text: string): string { : text; } +/** Remove protected and legacy runtime-context blocks from text. */ export function stripInternalRuntimeContext(text: string): string { if (!text) { return text; @@ -218,6 +231,7 @@ export function stripInternalRuntimeContext(text: string): string { ); } +/** Extract protected runtime-context blocks while returning remaining visible text. */ export function extractInternalRuntimeContext(text: string): { text: string; runtimeContext?: string; @@ -233,6 +247,7 @@ export function extractInternalRuntimeContext(text: string): { }; } +/** Return true when text contains current or legacy runtime-context markers. */ export function hasInternalRuntimeContext(text: string): boolean { if (!text) { return false; @@ -257,6 +272,7 @@ function isOpenClawRuntimeContextCustomMessage(message: unknown): boolean { ); } +/** Remove all structured runtime-context custom messages. */ export function stripRuntimeContextCustomMessages(messages: T[]): T[] { if (!messages.some(isOpenClawRuntimeContextCustomMessage)) { return messages; diff --git a/src/agents/local-model-lean.test.ts b/src/agents/local-model-lean.test.ts index 3b36f80a1074..08f53e620db3 100644 --- a/src/agents/local-model-lean.test.ts +++ b/src/agents/local-model-lean.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for local-model lean tool filtering. + * Verifies agent scope, default flags, preserve lists, and message-tool overrides. + */ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { AnyAgentTool } from "./agent-tools.types.js"; diff --git a/src/agents/local-model-lean.ts b/src/agents/local-model-lean.ts index a83080d1028e..b885fb059c45 100644 --- a/src/agents/local-model-lean.ts +++ b/src/agents/local-model-lean.ts @@ -1,15 +1,16 @@ +/** + * Local-model lean tool filtering. + * Removes high-latency or channel-dependent tools for local models while + * preserving explicitly required delivery tools. + */ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js"; import { resolveAgentConfig, resolveDefaultAgentId } from "./agent-scope-config.js"; import type { AnyAgentTool } from "./agent-tools.types.js"; import { expandToolGroups, normalizeToolName } from "./tool-policy.js"; -// Local-model lean mode removes high-latency or channel-dependent tools unless -// the caller explicitly preserves them for the current delivery path. const LOCAL_MODEL_LEAN_DENY_TOOL_NAMES = new Set(["browser", "cron", "message"]); -// Preserve lists accept tool groups; normalize them to concrete tool names so -// filtering stays aligned with the normal tool-policy path. function resolvePreservedLocalModelLeanToolNames(names?: Iterable): Set { if (!names) { return new Set(); diff --git a/src/agents/mcp-stdio.ts b/src/agents/mcp-stdio.ts index c83ac9190767..dc78aee7a1b6 100644 --- a/src/agents/mcp-stdio.ts +++ b/src/agents/mcp-stdio.ts @@ -1,7 +1,11 @@ +/** + * Stdio MCP launch config normalization. + * Accepts OpenClaw and upstream MCP config field names, keeping only + * command/args/env/cwd needed to spawn a stdio server. + */ import { isMcpConfigRecord, toMcpEnvRecord, toMcpStringArray } from "./mcp-config-shared.js"; -// Stdio MCP launch config normalization. Raw bundle config can use OpenClaw or -// upstream field names; this keeps only command/args/env/cwd needed to spawn. +/** Normalized stdio MCP server launch config. */ export type StdioMcpServerLaunchConfig = { command: string; args?: string[]; diff --git a/src/agents/model-suppression.runtime.ts b/src/agents/model-suppression.runtime.ts index a3c5c84e37eb..a0b33bd59ed7 100644 --- a/src/agents/model-suppression.runtime.ts +++ b/src/agents/model-suppression.runtime.ts @@ -1,15 +1,19 @@ +/** + * Runtime seam for built-in model suppression. + * Lets tests and lazy catalog paths stub suppression behavior without importing + * the full suppression implementation at module load. + */ import { buildShouldSuppressBuiltInModel as buildShouldSuppressBuiltInModelImpl, shouldSuppressBuiltInModel as shouldSuppressBuiltInModelImpl, } from "./model-suppression.js"; -// Runtime re-export seam for tests and lazy catalog paths that need to stub -// built-in model suppression without loading the full model suppression module. type ShouldSuppressBuiltInModel = typeof import("./model-suppression.js").shouldSuppressBuiltInModel; type BuildShouldSuppressBuiltInModel = typeof import("./model-suppression.js").buildShouldSuppressBuiltInModel; +/** Runtime-forwarded predicate for hiding bundled models. */ export function shouldSuppressBuiltInModel( ...args: Parameters ): ReturnType { diff --git a/src/agents/model-transport-url.test.ts b/src/agents/model-transport-url.test.ts index a33160877de9..91fac48dd71f 100644 --- a/src/agents/model-transport-url.test.ts +++ b/src/agents/model-transport-url.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for model transport debug URL formatting. + * Ensures credentials, query strings, and fragments stay out of diagnostics. + */ import { describe, expect, it } from "vitest"; import { formatModelTransportDebugBaseUrl, diff --git a/src/agents/model-transport-url.ts b/src/agents/model-transport-url.ts index b1405c82ce51..baf6c8013569 100644 --- a/src/agents/model-transport-url.ts +++ b/src/agents/model-transport-url.ts @@ -1,5 +1,7 @@ -// Debug formatting helpers for model transport endpoints. These keep logs useful -// without exposing credentials, request params, or fragments. +/** + * Debug formatting helpers for model transport endpoints. + * Keeps logs useful without exposing credentials, request params, or fragments. + */ /** Return a sanitized URL suitable for logs and diagnostics. */ export function formatModelTransportDebugUrl(rawUrl: string): string { try { diff --git a/src/agents/subagent-capabilities.test.ts b/src/agents/subagent-capabilities.test.ts index 7a8733b46f13..d5b94cb5aa88 100644 --- a/src/agents/subagent-capabilities.test.ts +++ b/src/agents/subagent-capabilities.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for depth-derived subagent capabilities. + * Verifies main/orchestrator/leaf role and control-scope decisions. + */ import { describe, expect, it } from "vitest"; import { resolveSubagentCapabilities } from "./subagent-capabilities.js"; diff --git a/src/agents/subagent-capabilities.ts b/src/agents/subagent-capabilities.ts index c05678125975..7ee5c3c156a6 100644 --- a/src/agents/subagent-capabilities.ts +++ b/src/agents/subagent-capabilities.ts @@ -1,3 +1,8 @@ +/** + * Subagent capability resolution. + * Combines session-key shape, stored envelopes, spawn depth, and inherited tool + * policy to decide role, control scope, and subagent permissions. + */ import { resolveIntegerOption, resolveNonNegativeIntegerOption, @@ -18,8 +23,7 @@ import { import { getSubagentDepthFromSessionStore } from "./subagent-depth.js"; import { normalizeSubagentSessionKey } from "./subagent-session-key.js"; -// Subagent capability resolution for live and persisted sessions. Depth derives -// defaults, while stored envelopes can override role/scope and inherited tools. +/** Resolved role for a main session, orchestrating subagent, or leaf subagent. */ export type SubagentSessionRole = "main" | "orchestrator" | "leaf"; const SUBAGENT_SESSION_ROLES: readonly SubagentSessionRole[] = [ "main", @@ -40,6 +44,7 @@ type SessionCapabilityEntry = { inheritedToolDeny?: unknown; }; +/** Minimal persisted session-store shape needed to resolve subagent capabilities. */ export type SessionCapabilityStore = Record< string, { @@ -126,6 +131,7 @@ function resolveSessionCapabilityEntry(params: { return store[params.sessionKey] ?? findEntryBySessionId(store, params.sessionKey); } +/** Resolve the session-store subset used for subagent capability lookup. */ export function resolveSubagentCapabilityStore( sessionKey: string | undefined | null, opts?: { @@ -172,6 +178,7 @@ function resolveSubagentControlScopeForRole(role: SubagentSessionRole): Subagent return role === "leaf" ? "none" : "children"; } +/** Resolve depth-derived role, scope, and spawn/control booleans. */ export function resolveSubagentCapabilities(params: { depth: number; maxSpawnDepth?: number }) { const depth = resolveNonNegativeIntegerOption(params.depth, 0); const role = resolveSubagentRoleForDepth(params); diff --git a/src/agents/usage.normalization.test.ts b/src/agents/usage.normalization.test.ts index 1bf2b0e49261..b13cb52e5630 100644 --- a/src/agents/usage.normalization.test.ts +++ b/src/agents/usage.normalization.test.ts @@ -1,3 +1,7 @@ +/** + * Focused usage-normalization tests for provider token payload variants. + * Protects cache read/write and session total prompt-token calculations. + */ import { describe, expect, it } from "vitest"; import { deriveSessionTotalTokens, hasNonzeroUsage, normalizeUsage } from "./usage.js"; diff --git a/src/agents/usage.test.ts b/src/agents/usage.test.ts index 75f29709974e..0fe285dd882b 100644 --- a/src/agents/usage.test.ts +++ b/src/agents/usage.test.ts @@ -1,3 +1,7 @@ +/** + * Regression coverage for token usage normalization. + * Verifies provider usage aliases, OpenAI-compatible output, and prompt-token derivation. + */ import { describe, expect, it } from "vitest"; import { deriveContextPromptTokens, diff --git a/src/agents/usage.ts b/src/agents/usage.ts index b0a32e6961dc..70c87369c00f 100644 --- a/src/agents/usage.ts +++ b/src/agents/usage.ts @@ -1,5 +1,11 @@ +/** + * Token usage normalization helpers. + * Converts provider-specific usage shapes into OpenClaw's normalized input, + * output, cache, reasoning, and total token accounting fields. + */ import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion"; +/** Provider/SDK usage payload variants accepted by usage normalization. */ export type UsageLike = { input?: number; output?: number; @@ -41,6 +47,7 @@ export type UsageLike = { }; }; +/** Normalized token counts used by runtime accounting. */ export type NormalizedUsage = { input?: number; output?: number; @@ -50,6 +57,7 @@ export type NormalizedUsage = { total?: number; }; +/** OpenAI chat-completions compatible usage shape. */ export type OpenAiChatCompletionsUsage = { prompt_tokens: number; completion_tokens: number; @@ -58,6 +66,7 @@ export type OpenAiChatCompletionsUsage = { completion_tokens_details?: { reasoning_tokens: number }; }; +/** Assistant usage snapshot with token counts and computed cost buckets. */ export type AssistantUsageSnapshot = { input: number; output: number; @@ -73,6 +82,7 @@ export type AssistantUsageSnapshot = { }; }; +/** Build a zeroed assistant usage snapshot. */ export function makeZeroUsageSnapshot(): AssistantUsageSnapshot { return { input: 0, @@ -90,6 +100,7 @@ export function makeZeroUsageSnapshot(): AssistantUsageSnapshot { }; } +/** Return true when any normalized usage bucket is positive. */ export function hasNonzeroUsage(usage?: NormalizedUsage | null): usage is NormalizedUsage { if (!usage) { return false; @@ -115,6 +126,7 @@ const normalizeTokenCount = (value: unknown): number | undefined => { return Math.min(Math.trunc(numeric), Number.MAX_SAFE_INTEGER); }; +/** Normalize provider-specific token usage fields into OpenClaw usage buckets. */ export function normalizeUsage(raw?: UsageLike | null): NormalizedUsage | undefined { if (!raw) { return undefined; @@ -238,6 +250,7 @@ export function toOpenAiChatCompletionsUsage( }; } +/** Derive prompt/context tokens from normalized input and cache buckets. */ export function derivePromptTokens(usage?: { input?: number; cacheRead?: number; @@ -253,6 +266,7 @@ export function derivePromptTokens(usage?: { return sum > 0 ? sum : undefined; } +/** Resolve context prompt tokens from explicit override, last call, or aggregate usage. */ export function deriveContextPromptTokens(params: { lastCallUsage?: NormalizedUsage; promptTokens?: number; @@ -266,6 +280,7 @@ export function deriveContextPromptTokens(params: { return derivePromptTokens(params.lastCallUsage) ?? derivePromptTokens(params.usage); } +/** Derive the session prompt-token snapshot stored for context display. */ export function deriveSessionTotalTokens(params: { usage?: { input?: number;