docs: document embedded agent helpers

This commit is contained in:
Peter Steinberger
2026-06-04 09:22:40 -04:00
parent 550f707565
commit dbfe5a252c
15 changed files with 63 additions and 2 deletions
@@ -1,3 +1,6 @@
/**
* Splits streamed embedded-agent replies into Markdown-safe message chunks.
*/
import type { FenceSpan } from "../../packages/markdown-core/src/fences.js";
import {
findFenceSpanAt,
@@ -116,6 +119,7 @@ export class EmbeddedBlockChunker {
this.#chunking = chunking;
}
/** Add streamed text to the pending chunk buffer. */
append(text: string) {
if (!text) {
return;
@@ -123,18 +127,22 @@ export class EmbeddedBlockChunker {
this.#buffer += text;
}
/** Clear any buffered reply text without emitting it. */
reset() {
this.#buffer = "";
}
/** Return the currently buffered text for tests and flush logic. */
get bufferedText() {
return this.#buffer;
}
/** Return true when there is pending text to drain. */
hasBuffered(): boolean {
return this.#buffer.length > 0;
}
/** Emit safe chunks according to size and Markdown fence constraints. */
drain(params: { force: boolean; emit: (chunk: string) => void }) {
// KNOWN: We cannot split inside fenced code blocks (Markdown breaks + UI glitches).
// When forced (maxChars), we close + reopen the fence to keep Markdown valid.
@@ -1,3 +1,6 @@
/**
* Builds structured observations for embedded-agent API/text failures.
*/
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { readLoggingConfig } from "../logging/config.js";
import { redactIdentifier } from "../logging/redact-identifier.js";
@@ -1,3 +1,6 @@
/**
* Builds and sanitizes bootstrap context inserted into embedded-agent sessions.
*/
import fs from "node:fs/promises";
import path from "node:path";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
@@ -1,3 +1,6 @@
/**
* Classifies provider/runtime failures and formats assistant-facing error text.
*/
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
@@ -75,6 +78,7 @@ export const GENERIC_ASSISTANT_ERROR_TEXT = "LLM request failed.";
const PROVIDER_SCHEMA_REJECTION_USER_TEXT =
"LLM request failed: provider rejected the request schema or tool payload.";
/** Detect provider errors that require reasoning to stay enabled. */
export function isReasoningConstraintErrorMessage(raw: string): boolean {
if (!raw) {
return false;
@@ -93,6 +97,7 @@ function hasRateLimitTpmHint(raw: string): boolean {
return /\btpm\b/i.test(lower) || lower.includes("tokens per minute");
}
/** Detect explicit context-window overflow without confusing TPM rate limits. */
export function isContextOverflowError(errorMessage?: string): boolean {
if (!errorMessage) {
return false;
@@ -1,3 +1,6 @@
/**
* Shared text-pattern matchers for failover, auth, billing, and rate-limit errors.
*/
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
type ErrorPattern = RegExp | string;
@@ -1,3 +1,6 @@
/**
* Google/Gemini-specific embedded-agent runtime helpers.
*/
import { isGemma4ModelId } from "../../shared/google-models.js";
import { sanitizeGoogleTurnOrdering } from "./bootstrap.js";
@@ -1,3 +1,6 @@
/**
* Sanitizes historical embedded-agent message images and empty content blocks.
*/
import type { ImageSanitizationLimits } from "../image-sanitization.js";
import type { AgentMessage, AgentToolResult } from "../runtime/index.js";
import type { ToolCallIdMode } from "../tool-call-id.js";
@@ -28,6 +31,7 @@ function ensureNonEmptyContent<T>(content: T[]): T[] {
return [{ type: "text", text: EMPTY_CONTENT_PLACEHOLDER }] as T[];
}
/** Return true when an assistant turn contains no usable content blocks. */
export function isEmptyAssistantMessageContent(
message: Extract<AgentMessage, { role: "assistant" }>,
): boolean {
@@ -50,6 +54,7 @@ export function isEmptyAssistantMessageContent(
});
}
/** Resize/remove unsafe image payloads while keeping transcript turns valid. */
export async function sanitizeSessionMessagesImages(
messages: AgentMessage[],
label: string,
@@ -1,3 +1,6 @@
/**
* Normalizes outbound message text to suppress duplicate send actions.
*/
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
const MIN_DUPLICATE_TEXT_LENGTH = 10;
@@ -17,6 +20,7 @@ export function normalizeTextForComparison(text: string): string {
.trim();
}
/** Compare already-normalized message text against prior sends. */
export function isMessagingToolDuplicateNormalized(
normalized: string,
normalizedSentTexts: string[],
@@ -41,6 +45,7 @@ export function isMessagingToolDuplicateNormalized(
});
}
/** Return true when raw message text duplicates a prior sent message. */
export function isMessagingToolDuplicate(text: string, sentTexts: string[]): boolean {
if (sentTexts.length === 0) {
return false;
@@ -1,3 +1,6 @@
/**
* Normalizes OpenAI Responses reasoning/tool-call history for safe replay.
*/
import { createHash } from "node:crypto";
import type { AgentMessage } from "../runtime/index.js";
@@ -1,3 +1,6 @@
/**
* Converts raw provider/transport errors into concise user-facing copy.
*/
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
@@ -31,6 +34,7 @@ import {
isTimeoutErrorMessage,
} from "./failover-matches.js";
/** Format the billing failure copy with optional provider/model context. */
export function formatBillingErrorMessage(provider?: string, model?: string): string {
const providerName = provider?.trim();
const modelName = model?.trim();
@@ -1,3 +1,6 @@
/**
* Resolves fallback thinking levels for providers that require reasoning.
*/
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
import { normalizeThinkLevel, type ThinkLevel } from "../../auto-reply/thinking.js";
import { isReasoningConstraintErrorMessage } from "./errors.js";
@@ -20,6 +23,7 @@ function extractSupportedValues(raw: string): string[] {
);
}
/** Pick a configured or provider-safe reasoning level for fallback attempts. */
export function pickFallbackThinkingLevel(params: {
message?: string;
attempted: Set<ThinkLevel>;
@@ -1,3 +1,6 @@
/**
* Normalizes embedded-agent conversation turn ordering for provider contracts.
*/
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { AgentMessage } from "../runtime/index.js";
import { extractToolCallsFromAssistant, extractToolResultId } from "../tool-call-id.js";
@@ -349,6 +352,7 @@ export function validateGeminiTurns(messages: AgentMessage[]): AgentMessage[] {
});
}
/** Merge adjacent user turns into a single provider-compatible user message. */
export function mergeConsecutiveUserTurns(
previous: Extract<AgentMessage, { role: "user" }>,
current: Extract<AgentMessage, { role: "user" }>,
+4
View File
@@ -1,3 +1,6 @@
/**
* Loads bundle-provided LSP server config for embedded-agent sessions.
*/
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { BundleLspServerConfig } from "../plugins/bundle-lsp.js";
import { loadEnabledBundleLspConfig } from "../plugins/bundle-lsp.js";
@@ -7,6 +10,7 @@ type EmbeddedAgentLspConfig = {
diagnostics: Array<{ pluginId: string; message: string }>;
};
/** Resolve enabled embedded-agent LSP servers and diagnostics. */
export function loadEmbeddedAgentLspConfig(params: {
workspaceDir: string;
cfg?: OpenClawConfig;
+6
View File
@@ -1,3 +1,6 @@
/**
* Identifies messaging tools and send actions during embedded-agent runs.
*/
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js";
@@ -10,12 +13,14 @@ const MESSAGE_TOOL_SEND_ACTIONS = new Set([
"upload-file",
]);
/** Return true when a message action sends or uploads user-visible content. */
export function isMessageToolSendActionName(action: unknown): boolean {
const normalized = normalizeOptionalString(action) ?? "";
return MESSAGE_TOOL_SEND_ACTIONS.has(normalized);
}
// Provider docking: any plugin with `actions` opts into messaging tool handling.
/** Return true for core or channel-plugin messaging tool names. */
export function isMessagingTool(toolName: string): boolean {
if (CORE_MESSAGING_TOOLS.has(toolName)) {
return true;
@@ -24,6 +29,7 @@ export function isMessagingTool(toolName: string): boolean {
return Boolean(providerId && getChannelPlugin(providerId)?.actions);
}
/** Return true when the specific tool invocation is an outbound send. */
export function isMessagingToolSendAction(
toolName: string,
args: Record<string, unknown>,
+3 -2
View File
@@ -1,6 +1,8 @@
/**
* Shared messaging-tool metadata types captured from embedded-agent runs.
*/
import type { ReplyPayload } from "../auto-reply/reply-payload.js";
// Messaging tool metadata captured during embedded agent runs.
export type MessagingToolSend = {
tool: string;
provider: string;
@@ -13,7 +15,6 @@ export type MessagingToolSend = {
mediaUrls?: string[];
};
// Reply payload subset preserved for message-tool idempotency and delivery.
export type MessagingToolSourceReplyPayload = Pick<
ReplyPayload,
| "audioAsVoice"