refactor: tighten plugin runtime sdk boundaries

This commit is contained in:
Peter Steinberger
2026-04-27 14:15:39 +01:00
parent b181930c23
commit 67a447c175
31 changed files with 234 additions and 63 deletions

View File

@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Plugins/runtime: expose provider-backed thinking policy and normalization through `api.runtime.agent`, letting tool plugins validate thinking overrides without duplicating provider/model level lists. Thanks @openclaw.
- Providers: add Cerebras as a bundled plugin with onboarding, static model catalog, docs, and manifest-owned endpoint metadata.
- Memory/OpenAI-compatible: add optional `memorySearch.inputType`, `queryInputType`, and `documentInputType` config for asymmetric embedding endpoints, including direct query embeddings and provider batch indexing. Carries forward #63313 and #60727. Thanks @HOYALIM and @prospect1314521.
- Ollama/memory: add model-specific retrieval query prefixes for `nomic-embed-text`, `qwen3-embedding`, and `mxbai-embed-large` memory-search queries while leaving document batches unchanged. Carries forward #45013. Thanks @laolin5564.

View File

@@ -1,2 +1,2 @@
491267e919c6bf426f673a9066e703811c7779a32de87edd0ce493147fd4438e plugin-sdk-api-baseline.json
590d21aeb520f34b5bf23abb7b17602b204f170547c772d60b604bb34a3940bb plugin-sdk-api-baseline.jsonl
b81647828ee6599cdd1d76d96ea02c92ccdebb8c1b3b443cefe10ca8bd2ddbfe plugin-sdk-api-baseline.json
ca9f3569352522621857b51872f30b3c31881505fd9eff2451b1b46d77670726 plugin-sdk-api-baseline.jsonl

View File

@@ -62,7 +62,18 @@ Internal OpenClaw runtime code has the same direction: load config once at the C
const identity = api.runtime.agent.resolveAgentIdentity(cfg);
// Get default thinking level
const thinking = api.runtime.agent.resolveThinkingDefault(cfg, provider, model);
const thinking = api.runtime.agent.resolveThinkingDefault({
cfg,
provider,
model,
});
// Validate a user-provided thinking level against the active provider profile
const policy = api.runtime.agent.resolveThinkingPolicy({ provider, model });
const level = api.runtime.agent.normalizeThinkingLevel("extra high");
if (level && policy.levels.some((entry) => entry.id === level)) {
// pass level to an embedded run
}
// Get agent timeout
const timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);
@@ -86,6 +97,10 @@ Internal OpenClaw runtime code has the same direction: load config once at the C
`runEmbeddedPiAgent(...)` remains as a compatibility alias.
`resolveThinkingPolicy(...)` returns the provider/model's supported thinking levels and optional default. Provider plugins own the model-specific profile through their thinking hooks, so tool plugins should call this runtime helper instead of importing or duplicating provider lists.
`normalizeThinkingLevel(...)` converts user text such as `on`, `x-high`, or `extra high` to the canonical stored level before checking it against the resolved policy.
**Session store helpers** are under `api.runtime.agent.session`:
```typescript

View File

@@ -138,7 +138,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `plugin-sdk/allow-from` | `formatAllowFromLowercase` |
| `plugin-sdk/channel-secret-runtime` | Narrow secret-contract collection helpers for channel/plugin secret surfaces |
| `plugin-sdk/secret-ref-runtime` | Narrow `coerceSecretRef` and SecretRef typing helpers for secret-contract/config parsing |
| `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, constant-time secret comparison, and secret-collection helpers |
| `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, sensitive text redaction, constant-time secret comparison, and secret-collection helpers |
| `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers |
| `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface |
| `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, SSRF error, and SSRF policy helpers |
@@ -201,7 +201,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `plugin-sdk/provider-zai-endpoint` | Z.AI endpoint detection helpers |
| `plugin-sdk/infra-runtime` | System event/heartbeat helpers |
| `plugin-sdk/collection-runtime` | Small bounded cache helpers |
| `plugin-sdk/diagnostic-runtime` | Diagnostic flag and event helpers |
| `plugin-sdk/diagnostic-runtime` | Diagnostic flag, event, and trace-context helpers |
| `plugin-sdk/error-runtime` | Error graph, formatting, shared error classification helpers, `isApprovalNotFoundError` |
| `plugin-sdk/fetch-runtime` | Wrapped fetch, proxy, and pinned lookup helpers |
| `plugin-sdk/runtime-fetch` | Dispatcher-aware runtime fetch without proxy/guarded-fetch imports |

View File

@@ -124,5 +124,6 @@ Malformed local-model reasoning tags are handled conservatively. Closed `<think>
- Provider plugins can expose `resolveThinkingProfile(ctx)` to define the model's supported levels and default.
- Each profile level has a stored canonical `id` (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `adaptive`, or `max`) and may include a display `label`. Binary providers use `{ id: "low", label: "on" }`.
- Tool plugins that need to validate an explicit thinking override should use `api.runtime.agent.resolveThinkingPolicy({ provider, model })` plus `api.runtime.agent.normalizeThinkingLevel(...)`; they should not keep their own provider/model level lists.
- Published legacy hooks (`supportsXHighThinking`, `isBinaryThinking`, and `resolveDefaultThinkingLevel`) remain as compatibility adapters, but new custom level sets should use `resolveThinkingProfile`.
- Gateway rows/defaults expose `thinkingLevels`, `thinkingOptions`, and `thinkingDefault` so ACP/chat clients render the same profile ids and labels that runtime validation uses.

View File

@@ -1 +1,20 @@
export * from "openclaw/plugin-sdk/diagnostics-otel";
export {
createChildDiagnosticTraceContext,
createDiagnosticTraceContext,
emitDiagnosticEvent,
formatDiagnosticTraceparent,
isValidDiagnosticSpanId,
isValidDiagnosticTraceFlags,
isValidDiagnosticTraceId,
onDiagnosticEvent,
parseDiagnosticTraceparent,
type DiagnosticEventMetadata,
type DiagnosticEventPayload,
type DiagnosticTraceContext,
} from "openclaw/plugin-sdk/diagnostic-runtime";
export { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export type {
OpenClawPluginService,
OpenClawPluginServiceContext,
} from "openclaw/plugin-sdk/plugin-entry";
export { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";

View File

@@ -1 +1,12 @@
export * from "openclaw/plugin-sdk/diagnostics-prometheus";
export type {
DiagnosticEventMetadata,
DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
export {
emptyPluginConfigSchema,
type OpenClawPluginApi,
type OpenClawPluginHttpRouteHandler,
type OpenClawPluginService,
type OpenClawPluginServiceContext,
} from "openclaw/plugin-sdk/plugin-entry";
export { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";

View File

@@ -1 +1,10 @@
export * from "openclaw/plugin-sdk/diffs";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export {
definePluginEntry,
type AnyAgentTool,
type OpenClawPluginApi,
type OpenClawPluginConfigSchema,
type OpenClawPluginToolContext,
type PluginLogger,
} from "openclaw/plugin-sdk/plugin-entry";
export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";

View File

@@ -30,6 +30,7 @@ export {
export { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export { GoogleChatConfigSchema } from "openclaw/plugin-sdk/channel-config-schema-legacy";
export {
GROUP_POLICY_BLOCKED_LABEL,
isDangerousNameMatchingEnabled,
@@ -41,11 +42,7 @@ export { fetchRemoteMedia, resolveChannelMediaMaxBytes } from "openclaw/plugin-s
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
export { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
export {
GoogleChatConfigSchema,
type GoogleChatAccountConfig,
type GoogleChatConfig,
} from "openclaw/plugin-sdk/googlechat-runtime-shared";
export type { GoogleChatAccountConfig, GoogleChatConfig } from "openclaw/plugin-sdk/config-runtime";
export { extractToolSend } from "openclaw/plugin-sdk/tool-send";
export { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";

View File

@@ -1 +1,6 @@
export * from "openclaw/plugin-sdk/llm-task";
export * from "./src/runtime-api.js";
export {
definePluginEntry,
type AnyAgentTool,
type OpenClawPluginApi,
} from "openclaw/plugin-sdk/plugin-entry";

View File

@@ -41,7 +41,6 @@ vi.mock("../api.js", async () => {
return {
...actual,
resolvePreferredOpenClawTmpDir: () => "/tmp",
supportsXHighThinking: () => false,
};
});
@@ -52,6 +51,30 @@ const runEmbeddedPiAgent = vi.fn(async () => ({
payloads: [{ text: "{}" }],
}));
const resolveThinkingPolicy = vi.fn(() => ({
levels: [
{ id: "off", label: "off" },
{ id: "minimal", label: "minimal" },
{ id: "low", label: "low" },
{ id: "medium", label: "medium" },
{ id: "high", label: "high" },
],
}));
const normalizeThinkingLevel = vi.fn((raw?: string | null) => {
const value = raw?.trim().toLowerCase();
if (!value) {
return undefined;
}
if (value === "on") {
return "low";
}
if (["off", "minimal", "low", "medium", "high", "xhigh", "adaptive", "max"].includes(value)) {
return value;
}
return undefined;
});
function fakeApi(overrides: any = {}) {
return {
id: "llm-task",
@@ -65,6 +88,8 @@ function fakeApi(overrides: any = {}) {
version: "test",
agent: {
runEmbeddedPiAgent,
resolveThinkingPolicy,
normalizeThinkingLevel,
},
},
logger: { debug() {}, info() {}, warn() {}, error() {} },
@@ -170,6 +195,10 @@ describe("llm-task tool (json-only)", () => {
mockEmbeddedRunJson({ ok: true });
const call = await executeEmbeddedRun({ prompt: "x", thinking: "high" });
expect(call.thinkLevel).toBe("high");
expect(resolveThinkingPolicy).toHaveBeenCalledWith({
provider: "openai-codex",
model: "gpt-5.2",
});
});
it("normalizes thinking aliases", async () => {

View File

@@ -3,12 +3,7 @@ import path from "node:path";
import Ajv from "ajv";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { Type } from "typebox";
import {
formatThinkingLevels,
isThinkingLevelSupported,
normalizeThinkLevel,
resolvePreferredOpenClawTmpDir,
} from "../api.js";
import { resolvePreferredOpenClawTmpDir } from "../api.js";
import type { OpenClawPluginApi } from "../api.js";
const AjvCtor = Ajv as unknown as typeof import("ajv").default;
@@ -70,8 +65,18 @@ type LlmTaskParams = {
timeoutMs?: unknown;
};
const INVALID_THINKING_LEVELS_HINT =
"off, minimal, low, medium, high, adaptive, xhigh where supported, and max where supported";
type ThinkingPolicy = ReturnType<OpenClawPluginApi["runtime"]["agent"]["resolveThinkingPolicy"]>;
function formatThinkingPolicy(policy: ThinkingPolicy): string {
return policy.levels.map((level) => level.label).join(", ");
}
function supportsThinkingPolicyLevel(
policy: ThinkingPolicy,
level: ReturnType<OpenClawPluginApi["runtime"]["agent"]["normalizeThinkingLevel"]>,
): boolean {
return !!level && policy.levels.some((entry) => entry.id === level);
}
export function createLlmTaskTool(api: OpenClawPluginApi) {
return {
@@ -148,24 +153,22 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
const thinkingRaw =
typeof params.thinking === "string" && params.thinking.trim() ? params.thinking : undefined;
const thinkLevel = thinkingRaw ? normalizeThinkLevel(thinkingRaw) : undefined;
if (thinkingRaw && !thinkLevel) {
throw new Error(
`Invalid thinking level "${thinkingRaw}". Use one of: ${INVALID_THINKING_LEVELS_HINT}.`,
);
}
let resolvedThinkLevel = thinkLevel;
if (
thinkLevel &&
!isThinkingLevelSupported({
provider,
model,
level: thinkLevel,
})
) {
throw new Error(
`Thinking level "${thinkLevel}" is not supported for ${provider}/${model}. Use one of: ${formatThinkingLevels(provider, model)}.`,
);
let thinkLevel: ReturnType<OpenClawPluginApi["runtime"]["agent"]["normalizeThinkingLevel"]> =
undefined;
if (thinkingRaw) {
const thinkingPolicy = api.runtime.agent.resolveThinkingPolicy({ provider, model });
const thinkingLevelsHint = formatThinkingPolicy(thinkingPolicy);
thinkLevel = api.runtime.agent.normalizeThinkingLevel(thinkingRaw);
if (!thinkLevel) {
throw new Error(
`Invalid thinking level "${thinkingRaw}". Use one of: ${thinkingLevelsHint}.`,
);
}
if (!supportsThinkingPolicyLevel(thinkingPolicy, thinkLevel)) {
throw new Error(
`Thinking level "${thinkLevel}" is not supported for ${provider}/${model}. Use one of: ${thinkingLevelsHint}.`,
);
}
}
const timeoutMs =
@@ -225,7 +228,7 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
model,
authProfileId,
authProfileIdSource: authProfileId ? "user" : "auto",
thinkLevel: resolvedThinkLevel,
thinkLevel,
streamParams,
disableTools: true,
});

View File

@@ -0,0 +1 @@
export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";

View File

@@ -29,13 +29,12 @@ export { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
export type {
ChannelDirectoryEntry,
ChannelMessageActionContext,
OpenClawConfig,
PluginRuntime,
RuntimeLogger,
RuntimeEnv,
WizardPrompter,
} from "openclaw/plugin-sdk/matrix-runtime-shared";
export { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared";
} from "openclaw/plugin-sdk/channel-contract";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export { formatZonedTimestamp } from "openclaw/plugin-sdk/core";
export type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
export type { WizardPrompter } from "openclaw/plugin-sdk/setup";
export function chunkTextForOutbound(text: string, limit: number): string[] {
const chunks: string[] = [];

View File

@@ -1,7 +1,5 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime";
import type { WizardPrompter } from "openclaw/plugin-sdk/matrix-runtime-shared";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
import {
type ChannelSetupDmPolicy,
type ChannelSetupWizardAdapter,
@@ -34,6 +32,7 @@ import {
} from "./matrix/client/url-validation.js";
import { resolveMatrixConfigFieldPath, updateMatrixAccountConfig } from "./matrix/config-update.js";
import { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./matrix/deps.js";
import type { RuntimeEnv, WizardPrompter } from "./runtime-api.js";
import { moveSingleMatrixAccountConfigToNamedAccount } from "./setup-config.js";
import { resolveMatrixSetupDmAllowFrom } from "./setup-dm-policy.js";
import type { CoreConfig, MatrixConfig } from "./types.js";

View File

@@ -42,7 +42,7 @@ export type {
GroupPolicy,
} from "openclaw/plugin-sdk/config-runtime";
export type { GroupToolPolicyConfig } from "openclaw/plugin-sdk/config-runtime";
export type { WizardPrompter } from "openclaw/plugin-sdk/matrix-runtime-shared";
export type { WizardPrompter } from "openclaw/plugin-sdk/setup";
export type { SecretInput } from "openclaw/plugin-sdk/secret-input";
export {
GROUP_POLICY_BLOCKED_LABEL,
@@ -107,7 +107,7 @@ export {
formatZonedTimestamp,
type PluginRuntime,
type RuntimeLogger,
} from "openclaw/plugin-sdk/matrix-runtime-shared";
} from "openclaw/plugin-sdk/core";
export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
// resolveMatrixAccountStringValues already comes from plugin-sdk/matrix.
// Re-exporting auth-precedence here makes Jiti try to define the same export twice.

View File

@@ -1,4 +1,4 @@
export type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export type {
MemoryEmbeddingProbeResult,
MemoryProviderStatus,

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveMemoryDreamingConfig } from "openclaw/plugin-sdk/memory-core-host-status";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { asRecord } from "./dreaming-shared.js";
import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js";

View File

@@ -2,7 +2,6 @@ import { createHash } from "node:crypto";
import type { Dirent } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
import {
buildSessionEntry,
listSessionFilesForAgent,
@@ -18,6 +17,7 @@ import {
resolveMemoryLightDreamingConfig,
resolveMemoryRemDreamingConfig,
} from "openclaw/plugin-sdk/memory-core-host-status";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { writeDailyDreamingPhaseBlock } from "./dreaming-markdown.js";
import { generateAndAppendDreamNarrative, type NarrativePhaseData } from "./dreaming-narrative.js";
import { asRecord, formatErrorMessage, normalizeTrimmedString } from "./dreaming-shared.js";

View File

@@ -1,5 +1,5 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { peekSystemEventEntries } from "openclaw/plugin-sdk/infra-runtime";
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
import {
DEFAULT_MEMORY_DREAMING_FREQUENCY as DEFAULT_MEMORY_DREAMING_CRON_EXPR,
DEFAULT_MEMORY_DEEP_DREAMING_LIMIT as DEFAULT_MEMORY_DREAMING_LIMIT,
@@ -20,6 +20,7 @@ import {
resolveMemoryDeepDreamingConfig,
resolveMemoryDreamingWorkspaces,
} from "openclaw/plugin-sdk/memory-core-host-status";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { writeDeepDreamingReport } from "./dreaming-markdown.js";
import { generateAndAppendDreamNarrative, type NarrativePhaseData } from "./dreaming-narrative.js";

View File

@@ -1 +1,2 @@
export * from "openclaw/plugin-sdk/memory-lancedb";
export { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export { resolveStateDir } from "openclaw/plugin-sdk/state-paths";

View File

@@ -1 +1,7 @@
export * from "openclaw/plugin-sdk/thread-ownership";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export {
fetchWithSsrFGuard,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "openclaw/plugin-sdk/ssrf-runtime";

View File

@@ -128,6 +128,8 @@ export type {
DiscordSlashCommandConfig,
DmConfig,
DmPolicy,
GoogleChatAccountConfig,
GoogleChatConfig,
ContextVisibilityMode,
GroupPolicy,
GroupToolPolicyBySenderConfig,

View File

@@ -1,4 +1,22 @@
// Diagnostic flag/event helpers for plugins that want narrow runtime gating.
export { isDiagnosticFlagEnabled } from "../infra/diagnostic-flags.js";
export { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
export type {
DiagnosticEventMetadata,
DiagnosticEventPayload,
} from "../infra/diagnostic-events.js";
export {
emitDiagnosticEvent,
isDiagnosticsEnabled,
onDiagnosticEvent,
} from "../infra/diagnostic-events.js";
export type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
export {
createChildDiagnosticTraceContext,
createDiagnosticTraceContext,
formatDiagnosticTraceparent,
isValidDiagnosticSpanId,
isValidDiagnosticTraceFlags,
isValidDiagnosticTraceId,
parseDiagnosticTraceparent,
} from "../infra/diagnostic-trace-context.js";

View File

@@ -16,6 +16,7 @@ import type {
OpenClawPluginCommandDefinition,
OpenClawPluginConfigSchema,
OpenClawPluginDefinition,
OpenClawPluginHttpRouteHandler,
OpenClawPluginNodeHostCommand,
OpenClawPluginReloadRegistration,
OpenClawPluginSecurityAuditCollector,
@@ -104,6 +105,7 @@ export type {
PluginCommandContext,
PluginCommandResult,
OpenClawPluginConfigSchema,
OpenClawPluginHttpRouteHandler,
ProviderDiscoveryContext,
ProviderCatalogContext,
ProviderCatalogResult,

View File

@@ -9,4 +9,5 @@ export * from "../security/context-visibility.js";
export * from "../security/dm-policy-shared.js";
export * from "../security/external-content.js";
export * from "../security/safe-regex.js";
export { redactSensitiveText } from "../logging/redact.js";
export { safeEqualSecret } from "../security/secret-equal.js";

View File

@@ -67,12 +67,13 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
'export { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";',
'export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";',
'export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";',
'export { GoogleChatConfigSchema } from "openclaw/plugin-sdk/channel-config-schema-legacy";',
'export { GROUP_POLICY_BLOCKED_LABEL, isDangerousNameMatchingEnabled, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/config-runtime";',
'export { fetchRemoteMedia, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";',
'export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";',
'export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";',
'export { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";',
'export { GoogleChatConfigSchema, type GoogleChatAccountConfig, type GoogleChatConfig } from "openclaw/plugin-sdk/googlechat-runtime-shared";',
'export type { GoogleChatAccountConfig, GoogleChatConfig } from "openclaw/plugin-sdk/config-runtime";',
'export { extractToolSend } from "openclaw/plugin-sdk/tool-send";',
'export { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";',
'export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";',
@@ -125,8 +126,12 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
'export { setMatrixThreadBindingIdleTimeoutBySessionKey, setMatrixThreadBindingMaxAgeBySessionKey } from "./src/matrix/thread-bindings-shared.js";',
'export { setMatrixRuntime } from "./src/runtime.js";',
'export { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";',
'export type { ChannelDirectoryEntry, ChannelMessageActionContext, OpenClawConfig, PluginRuntime, RuntimeLogger, RuntimeEnv, WizardPrompter } from "openclaw/plugin-sdk/matrix-runtime-shared";',
'export { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared";',
'export type { ChannelDirectoryEntry, ChannelMessageActionContext } from "openclaw/plugin-sdk/channel-contract";',
'export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";',
'export { formatZonedTimestamp } from "openclaw/plugin-sdk/core";',
'export type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";',
'export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";',
'export type { WizardPrompter } from "openclaw/plugin-sdk/setup";',
'export function chunkTextForOutbound(text: string, limit: number): string[] { const chunks: string[] = []; let remaining = text; while (remaining.length > limit) { const window = remaining.slice(0, limit); const splitAt = Math.max(window.lastIndexOf("\\n"), window.lastIndexOf(" ")); const breakAt = splitAt > 0 ? splitAt : limit; chunks.push(remaining.slice(0, breakAt).trimEnd()); remaining = remaining.slice(breakAt).trimStart(); } if (remaining.length > 0 || text.length === 0) { chunks.push(remaining); } return chunks; }',
],
[bundledPluginFile({

View File

@@ -218,6 +218,8 @@ describe("plugin runtime command execution", () => {
expectFunctionKeys(runtime.agent as Record<string, unknown>, [
"runEmbeddedAgent",
"runEmbeddedPiAgent",
"normalizeThinkingLevel",
"resolveThinkingPolicy",
"resolveAgentDir",
]);
expectFunctionKeys(runtime.agent.session as Record<string, unknown>, [

View File

@@ -4,6 +4,7 @@ import { resolveAgentIdentity } from "../../agents/identity.js";
import { resolveThinkingDefault } from "../../agents/model-selection.js";
import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
import { ensureAgentWorkspace } from "../../agents/workspace.js";
import { normalizeThinkLevel, resolveThinkingProfile } from "../../auto-reply/thinking.js";
import { resolveSessionFilePath, resolveStorePath } from "../../config/sessions/paths.js";
import { loadSessionStore, saveSessionStore } from "../../config/sessions/store.js";
import { createLazyRuntimeMethod, createLazyRuntimeModule } from "../../shared/lazy-runtime.js";
@@ -24,6 +25,17 @@ export function createRuntimeAgent(): PluginRuntime["agent"] {
resolveAgentWorkspaceDir,
resolveAgentIdentity,
resolveThinkingDefault,
normalizeThinkingLevel: normalizeThinkLevel,
resolveThinkingPolicy: (params) => {
const profile = resolveThinkingProfile(params);
const policy: Omit<
ReturnType<PluginRuntime["agent"]["resolveThinkingPolicy"]>,
"defaultLevel"
> = {
levels: profile.levels.map(({ id, label }) => ({ id, label })),
};
return profile.defaultLevel ? { ...policy, defaultLevel: profile.defaultLevel } : policy;
},
resolveAgentTimeoutMs,
ensureAgentWorkspace,
} satisfies Omit<PluginRuntime["agent"], "runEmbeddedAgent" | "runEmbeddedPiAgent" | "session"> &

View File

@@ -47,6 +47,19 @@ type RuntimeReplaceConfigFileParams = {
afterWrite: RuntimeConfigAfterWrite;
writeOptions?: RuntimeWriteConfigOptions;
};
export type PluginRuntimeThinkingPolicyRequest = {
provider?: string | null;
model?: string | null;
catalog?: import("../../auto-reply/thinking.js").ThinkingCatalogEntry[];
};
export type PluginRuntimeThinkingPolicyLevel = {
id: import("../../auto-reply/thinking.js").ThinkLevel;
label: string;
};
export type PluginRuntimeThinkingPolicy = {
levels: PluginRuntimeThinkingPolicyLevel[];
defaultLevel?: import("../../auto-reply/thinking.js").ThinkLevel | null;
};
/** Structured logger surface injected into runtime-backed plugin helpers. */
export type RuntimeLogger = {
@@ -116,6 +129,12 @@ export type PluginRuntimeCore = {
model: string;
catalog?: import("../../agents/model-catalog.types.js").ModelCatalogEntry[];
}) => import("../../auto-reply/thinking.js").ThinkLevel;
normalizeThinkingLevel: (
raw?: string | null,
) => import("../../auto-reply/thinking.js").ThinkLevel | undefined;
resolveThinkingPolicy: (
params: PluginRuntimeThinkingPolicyRequest,
) => PluginRuntimeThinkingPolicy;
runEmbeddedAgent: import("../../agents/pi-embedded-runtime.types.js").RunEmbeddedAgentFn;
runEmbeddedPiAgent: import("../../agents/pi-embedded-runtime.types.js").RunEmbeddedPiAgentFn;
resolveAgentTimeoutMs: typeof import("../../agents/timeout.js").resolveAgentTimeoutMs;

View File

@@ -123,6 +123,18 @@ export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> =
resolveThinkingDefault: vi.fn(
() => "off",
) as unknown as PluginRuntime["agent"]["resolveThinkingDefault"],
normalizeThinkingLevel: vi.fn(
(raw?: string | null) => raw,
) as unknown as PluginRuntime["agent"]["normalizeThinkingLevel"],
resolveThinkingPolicy: vi.fn(() => ({
levels: [
{ id: "off", label: "off" },
{ id: "minimal", label: "minimal" },
{ id: "low", label: "low" },
{ id: "medium", label: "medium" },
{ id: "high", label: "high" },
],
})) as unknown as PluginRuntime["agent"]["resolveThinkingPolicy"],
runEmbeddedPiAgent: vi.fn().mockResolvedValue({
payloads: [],
meta: {},