Files
openclaw/extensions/voice-call/src/realtime-agent-context.ts
Peter Steinberger 77d9ac30bb refactor: reuse shared coercion helpers (#86419)
* refactor: share talk event metric extraction

* refactor: reuse shared coercion helpers

* refactor: reuse shared primitive guards

* refactor: reuse shared record guard

* refactor: reuse shared primitive helpers

* refactor: reuse shared string guards

* refactor: reuse shared non-empty string guard

* refactor: share plugin primitive coercion helpers

* refactor: reuse plugin coercion helpers

* refactor: reuse plugin coercion helpers in more plugins

* refactor: reuse channel coercion helpers

* refactor: reuse monitor coercion helpers

* refactor: reuse provider coercion helpers

* refactor: reuse core coercion helpers

* refactor: reuse runtime coercion helpers

* refactor: reuse helper coercion in codex paths

* refactor: reuse helper coercion in runtime paths

* refactor: reuse codex app-server coercion helpers

* refactor: reuse codex record helpers

* refactor: reuse migration and qa record helpers

* refactor: reuse feishu and core helper guards

* refactor: reuse browser and policy coercion helpers

* refactor: reuse memory wiki record helper

* refactor: share boolean coercion helpers

* refactor: reuse finite number coercion

* refactor: reuse trimmed string list helpers

* refactor: reuse string list normalization

* refactor: reuse remaining string list helpers

* refactor: reuse string entry normalizer

* refactor: share sorted string helpers

* refactor: share string list normalization

* test: preserve command registry browser imports

* refactor: reuse trimmed list helpers

* refactor: reuse string dedupe helpers

* refactor: reuse local dedupe helpers

* refactor: reuse more string dedupe helpers

* refactor: reuse command string dedupe helpers

* refactor: dedupe memory path lists with helper

* refactor: expose string dedupe helpers to plugins

* refactor: reuse core string dedupe helpers

* refactor: reuse shared unique value helpers

* refactor: reuse unique helpers in agent utilities

* refactor: reuse unique helpers in config plumbing

* refactor: reuse unique helpers in extensions

* refactor: reuse unique helpers in core utilities

* refactor: reuse unique helpers in qa plugins

* refactor: reuse unique helpers in memory plugins

* refactor: reuse unique helpers in channel plugins

* refactor: reuse unique helpers in core tails

* refactor: reuse unique helper in comfy workflow

* refactor: reuse unique helpers in test utilities

* refactor: expose unique value helper to plugins

* refactor: reuse unique helpers for numeric lists

* refactor: replace index dedupe filters

* refactor: reuse string entry normalization

* refactor: reuse string normalization in plugin helpers

* refactor: reuse string normalization in extension helpers

* refactor: reuse string normalization in channel parsers

* refactor: reuse string normalization in memory search

* refactor: reuse string normalization in provider parsers

* refactor: reuse string normalization in qa helpers

* refactor: reuse string normalization in infra parsers

* refactor: reuse string normalization in messaging parsers

* refactor: reuse string normalization in core parsers

* refactor: reuse string normalization in extension parsers

* refactor: reuse string normalization in remaining parsers

* refactor: reuse string normalization in final parser spots

* refactor: reuse string normalization in qa media helpers

* refactor: reuse normalization in provider and media lists

* refactor: reuse normalization for remaining set filters

* refactor: reuse normalization in policy allowlists

* refactor: reuse normalization in session and owner lists

* refactor: centralize primitive string lists

* refactor: reuse lowercase entry helpers

* refactor: reuse sorted string helpers

* refactor: reuse unique trimmed helpers

* refactor: reuse string normalization helpers

* refactor: reuse catalog string helpers

* refactor: reuse remaining string helpers

* refactor: simplify remaining list normalization

* refactor: reuse codex auth order normalization

* chore: refresh plugin sdk api baseline

* fix: make shared string sorting deterministic

* chore: refresh plugin sdk api baseline

* fix: align host env security ordering
2026-05-25 21:20:41 +01:00

147 lines
5.0 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { buildRealtimeVoiceAgentConsultPolicyInstructions } from "openclaw/plugin-sdk/realtime-voice";
import { root } from "openclaw/plugin-sdk/security-runtime";
import { normalizeOptionalString as normalizeString } from "openclaw/plugin-sdk/string-coerce-runtime";
import type { VoiceCallConfig } from "./config.js";
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
type AgentEntryLike = {
id?: unknown;
systemPromptOverride?: unknown;
};
type VoiceIdentityLike = {
name?: unknown;
emoji?: unknown;
theme?: unknown;
creature?: unknown;
vibe?: unknown;
};
function readAgentEntries(cfg: CoreConfig): AgentEntryLike[] {
const agents = (cfg as { agents?: { list?: unknown } }).agents;
return Array.isArray(agents?.list)
? agents.list.filter((entry): entry is AgentEntryLike =>
Boolean(entry && typeof entry === "object"),
)
: [];
}
function resolveAgentSystemPromptOverride(cfg: CoreConfig, agentId: string): string | undefined {
const entries = readAgentEntries(cfg);
const entry = entries.find((candidate) => normalizeString(candidate.id) === agentId);
return (
normalizeString(entry?.systemPromptOverride) ??
normalizeString(
(cfg as { agents?: { defaults?: { systemPromptOverride?: unknown } } }).agents?.defaults
?.systemPromptOverride,
)
);
}
function limitText(text: string, maxChars: number): string {
if (text.length <= maxChars) {
return text;
}
return `${text.slice(0, Math.max(0, maxChars - 32)).trimEnd()}\n[truncated]`;
}
async function readWorkspaceVoiceContextFiles(params: {
workspaceDir: string;
files: readonly string[];
maxChars: number;
}): Promise<string[]> {
const sections: string[] = [];
let remaining = params.maxChars;
const workspaceRoot = await root(params.workspaceDir).catch(() => null);
if (!workspaceRoot) {
return sections;
}
for (const file of params.files) {
if (remaining <= 0) {
continue;
}
const content = await workspaceRoot.readText(file).catch(() => undefined);
const trimmed = content?.trim();
if (!trimmed) {
continue;
}
const body = limitText(trimmed, Math.max(0, remaining - file.length - 16));
const section = `### ${file}\n${body}`;
sections.push(section);
remaining -= section.length;
}
return sections;
}
export async function buildRealtimeVoiceInstructions(params: {
baseInstructions: string;
config: VoiceCallConfig;
coreConfig: CoreConfig;
agentRuntime: CoreAgentDeps;
}): Promise<string> {
const { config } = params;
const sections: string[] = [params.baseInstructions];
const consultGuidance = buildRealtimeVoiceAgentConsultPolicyInstructions(config.realtime);
if (consultGuidance) {
sections.push(consultGuidance);
}
const contextConfig = config.realtime.agentContext;
if (!contextConfig.enabled) {
return sections.filter(Boolean).join("\n\n");
}
const agentId = config.agentId ?? "main";
const capsule: string[] = [
"OpenClaw agent voice context:",
`- Agent id: ${agentId}`,
"- Use this context to match the OpenClaw agent's personality and standing preferences on fast voice turns.",
"- Treat this as compact context only; call openclaw_agent_consult when the caller needs the full agent brain, tools, memory, or workspace state.",
];
if (contextConfig.includeIdentity) {
const identity = params.agentRuntime.resolveAgentIdentity(
params.coreConfig as OpenClawConfig,
agentId,
) as VoiceIdentityLike | undefined;
const identityLines = [
normalizeString(identity?.name) ? `- Name: ${normalizeString(identity?.name)}` : undefined,
normalizeString(identity?.emoji) ? `- Emoji: ${normalizeString(identity?.emoji)}` : undefined,
normalizeString(identity?.vibe) ? `- Vibe: ${normalizeString(identity?.vibe)}` : undefined,
normalizeString(identity?.theme) ? `- Theme: ${normalizeString(identity?.theme)}` : undefined,
normalizeString(identity?.creature)
? `- Creature/persona: ${normalizeString(identity?.creature)}`
: undefined,
].filter(Boolean);
if (identityLines.length > 0) {
capsule.push(`Configured identity:\n${identityLines.join("\n")}`);
}
}
if (contextConfig.includeSystemPrompt) {
const systemPrompt = resolveAgentSystemPromptOverride(params.coreConfig, agentId);
if (systemPrompt) {
capsule.push(`Configured system prompt override:\n${systemPrompt}`);
}
}
if (contextConfig.includeWorkspaceFiles) {
const workspaceDir = params.agentRuntime.resolveAgentWorkspaceDir(
params.coreConfig as OpenClawConfig,
agentId,
);
const fileSections = await readWorkspaceVoiceContextFiles({
workspaceDir,
files: contextConfig.files,
maxChars: contextConfig.maxChars,
});
if (fileSections.length > 0) {
capsule.push(`Workspace voice context:\n${fileSections.join("\n\n")}`);
}
}
sections.push(limitText(capsule.join("\n\n"), contextConfig.maxChars));
return sections.filter(Boolean).join("\n\n");
}