mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
docs: document runtime helper contracts
This commit is contained in:
@@ -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<ChannelAgentTool, ChannelAgentToolMeta>();
|
||||
|
||||
/** 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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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}>`),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = "<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>";
|
||||
/** Closing delimiter for protected OpenClaw runtime context blocks. */
|
||||
export const INTERNAL_RUNTIME_CONTEXT_END = "<<<END_OPENCLAW_INTERNAL_CONTEXT>>>";
|
||||
|
||||
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 = "<<<BEGIN_UNTRUSTED_CHILD_RESULT>>>";
|
||||
const LEGACY_UNTRUSTED_RESULT_END = "<<<END_UNTRUSTED_CHILD_RESULT>>>";
|
||||
|
||||
/** 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<T>(messages: T[]): T[] {
|
||||
if (!messages.some(isOpenClawRuntimeContextCustomMessage)) {
|
||||
return messages;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<string>): Set<string> {
|
||||
if (!names) {
|
||||
return new Set();
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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<ShouldSuppressBuiltInModel>
|
||||
): ReturnType<ShouldSuppressBuiltInModel> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user