mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 04:10:44 +00:00
refactor(runtime): add prepared runtime foundation (#78248)
* docs(runtime): document prepared runtime guidance * refactor(provider-runtime): thread prepared provider handles * refactor(runtime-plan): add prepared runtime foundation * refactor(outbound): add prepared channel runtime facts * refactor(models): add scoped model reference helpers * refactor(plugin-sdk): expose prepared runtime helper surfaces
This commit is contained in:
@@ -37,6 +37,11 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
|
||||
- Channels: `src/channels/**` is implementation; plugin authors get SDK seams.
|
||||
- Providers: core owns generic loop; provider plugins own auth/catalog/runtime hooks.
|
||||
- Request-time runtime resolution: when a path already knows the provider id, model ref, channel id, outbound target, capability family, or attachment class, carry that as a prepared runtime fact instead of rediscovering it later.
|
||||
- Prepared runtime facts should be small typed values produced once near startup, reply dispatch, model selection, tool planning, or channel resolution, then passed through context to consumers. Prefer `AgentRuntimePlan`, `ProviderRuntimePluginHandle`, scoped model/catalog helpers, active/runtime registries, manifest/public-artifact lookups, single-provider resolvers, and lazy registry construction.
|
||||
- Avoid broad request-time rediscovery: hot reply/tool/outbound/media paths should not call broad plugin/provider/channel/capability loaders such as `loadOpenClawPlugins`, `resolveProviderPluginsForHooks`, `resolvePluginCapabilityProviders`, `resolvePluginDiscoveryProvidersRuntime`, `getChannelPlugin`, or broad model/tool/media registry builders just to answer a question the caller already knows. Do not build multimodal/provider registries for document-only or otherwise non-participating paths.
|
||||
- Compatibility fallbacks are allowed only for startup/setup/admin/standalone/legacy callers that genuinely lack prepared facts. Keep them explicit, tested, and outside migrated hot reply/tool/outbound paths.
|
||||
- Do not fix repeated request-time discovery by adding scattered cache layers. Move the canonical fact earlier, reuse the existing prepared-runtime object, and delete duplicate lookup branches when the last migrated caller stops needing them.
|
||||
- Gateway protocol changes: additive first; incompatible needs versioning/docs/client follow-through.
|
||||
- Config contract: exported types, schema/help, metadata, baselines, docs aligned. Retired public keys stay retired; compat in raw migration/doctor.
|
||||
- Direction: manifest-first control plane; targeted runtime loaders; no hidden contract bypasses; broad mutable registries transitional.
|
||||
|
||||
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI/chat: add an agent-first filter to the chat session picker, keep chat controls/composer responsive across phone/tablet/desktop widths, keep desktop chat controls on one row, avoid duplicate avatar refreshes during initial chat load, and hide that row while scrolling down the transcript. Thanks @BunsDev.
|
||||
- Control UI/chat: collapse consecutive duplicate text messages into one bubble with a count so repeated text-only messages stay compact without hiding nearby context.
|
||||
- Control UI/chat and Sessions: label inherited thinking defaults separately from explicit overrides while preserving provider-supplied option labels. Fixes #77581. Thanks @BunsDev and @Beandon13.
|
||||
- Agents/runtime: add prepared runtime foundation contracts for carrying provider, model, tool, TTS, and outbound runtime facts through later reply-path migrations. Thanks @mcaxtr.
|
||||
- Agents/subagents: preserve every grouped child result when direct completion fallback has to bypass the requester-agent announce turn. Thanks @vincentkoc.
|
||||
- TTS/telephony: honor provider voice/model overrides in telephony synthesis providers so Google Meet agent speech logs match the backend that actually produced the audio. Thanks @vincentkoc.
|
||||
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
259c4d22d5fe84ca6b27f9d061dddca4459e4eddd516729564f250d823dbd819 plugin-sdk-api-baseline.json
|
||||
b2bf16d3c5859b06240b5f41ed5803af8e82338da3e56ace1569520ac0732a5a plugin-sdk-api-baseline.jsonl
|
||||
28e280d21693216c99cfa8da553589b41741d37c0ada956e316ee01d3d6c202c plugin-sdk-api-baseline.json
|
||||
633dae33da97f6a073c5561709c57d5c0b7ff67af0512d0261f05455c24b38de plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -101,6 +101,15 @@ export const pluginSdkDocMetadata = {
|
||||
"runtime-store": {
|
||||
category: "runtime",
|
||||
},
|
||||
"agent-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"speech-core": {
|
||||
category: "provider",
|
||||
},
|
||||
"tts-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"allow-from": {
|
||||
category: "utilities",
|
||||
},
|
||||
|
||||
51
src/agents/model-catalog-scope.ts
Normal file
51
src/agents/model-catalog-scope.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
|
||||
|
||||
function dedupeCatalogScopeRefs(values: Array<string | undefined>): string[] {
|
||||
const refs = new Set<string>();
|
||||
for (const value of values) {
|
||||
const trimmed = value?.trim();
|
||||
if (trimmed) {
|
||||
refs.add(trimmed);
|
||||
}
|
||||
}
|
||||
return [...refs];
|
||||
}
|
||||
|
||||
function providerFromModelRef(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const slash = trimmed.indexOf("/");
|
||||
if (slash <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
const provider = normalizeProviderId(trimmed.slice(0, slash));
|
||||
return provider || undefined;
|
||||
}
|
||||
|
||||
export function resolveModelCatalogScope(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
provider: string;
|
||||
model: string;
|
||||
}): { providerRefs: string[]; modelRefs: string[] } {
|
||||
const provider = params.provider.trim();
|
||||
const model = params.model.trim();
|
||||
const providerConfig = findNormalizedProviderValue(params.cfg?.models?.providers, provider);
|
||||
return {
|
||||
providerRefs: dedupeCatalogScopeRefs([provider, providerConfig?.api]),
|
||||
modelRefs: dedupeCatalogScopeRefs([provider && model ? `${provider}/${model}` : model, model]),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveProviderDiscoveryProviderIdsForCatalogScope(params: {
|
||||
providerRefs?: readonly string[];
|
||||
modelRefs?: readonly string[];
|
||||
}): string[] | undefined {
|
||||
const providerIds = dedupeCatalogScopeRefs([
|
||||
...(params.providerRefs ?? []),
|
||||
...(params.modelRefs ?? []).map(providerFromModelRef),
|
||||
]);
|
||||
return providerIds.length > 0 ? providerIds : undefined;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import {
|
||||
prepareProviderExtraParams as prepareProviderExtraParamsRuntime,
|
||||
type ProviderRuntimePluginHandle,
|
||||
resolveProviderExtraParamsForTransport as resolveProviderExtraParamsForTransportRuntime,
|
||||
wrapProviderStreamFn as wrapProviderStreamFnRuntime,
|
||||
} from "../../plugins/provider-hook-runtime.js";
|
||||
@@ -207,6 +208,7 @@ export function resolvePreparedExtraParams(params: {
|
||||
resolvedExtraParams?: Record<string, unknown>;
|
||||
model?: ProviderRuntimeModel;
|
||||
resolvedTransport?: SupportedTransport;
|
||||
providerRuntimeHandle?: ProviderRuntimePluginHandle;
|
||||
}): Record<string, unknown> {
|
||||
const resolvedExtraParams =
|
||||
params.resolvedExtraParams ??
|
||||
@@ -253,6 +255,7 @@ export function resolvePreparedExtraParams(params: {
|
||||
provider: params.provider,
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
runtimeHandle: params.providerRuntimeHandle,
|
||||
context: {
|
||||
config: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
@@ -267,6 +270,7 @@ export function resolvePreparedExtraParams(params: {
|
||||
provider: params.provider,
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
runtimeHandle: params.providerRuntimeHandle,
|
||||
context: {
|
||||
config: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
|
||||
@@ -105,6 +105,7 @@ function makeForwardedRuntimePlan(overrides: RuntimePlanOverrides = {}): AgentRu
|
||||
provider: "anthropic",
|
||||
modelId: "test-model",
|
||||
resolveSystemPromptContribution: vi.fn(),
|
||||
transformSystemPrompt: vi.fn((context) => context.systemPrompt),
|
||||
},
|
||||
transcript: {
|
||||
policy: transcriptPolicy,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import type { TSchema } from "typebox";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { ProviderRuntimePluginHandle } from "../../plugins/provider-hook-runtime.js";
|
||||
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
|
||||
import {
|
||||
inspectProviderToolSchemasWithPlugin,
|
||||
@@ -19,6 +20,7 @@ type ProviderToolSchemaParams<TSchemaType extends TSchema = TSchema, TResult = u
|
||||
modelId?: string;
|
||||
modelApi?: string | null;
|
||||
model?: ProviderRuntimeModel;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
};
|
||||
|
||||
function buildProviderToolSchemaContext<TSchemaType extends TSchema = TSchema, TResult = unknown>(
|
||||
@@ -51,6 +53,7 @@ export function normalizeProviderToolSchemas<
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
runtimeHandle: params.runtimeHandle,
|
||||
context: buildProviderToolSchemaContext(params, provider),
|
||||
});
|
||||
return Array.isArray(pluginNormalized)
|
||||
@@ -68,6 +71,7 @@ export function logProviderToolSchemaDiagnostics(params: ProviderToolSchemaParam
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
runtimeHandle: params.runtimeHandle,
|
||||
context: buildProviderToolSchemaContext(params, provider),
|
||||
});
|
||||
if (!Array.isArray(diagnostics)) {
|
||||
|
||||
@@ -1,20 +1,80 @@
|
||||
import { createParameterFreeTool } from "openclaw/plugin-sdk/agent-runtime-test-contracts";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildAgentRuntimePlan } from "./build.js";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetConfigRuntimeState, setRuntimeConfigSnapshot } from "../../config/config.js";
|
||||
import {
|
||||
resolveProviderRuntimePluginHandle,
|
||||
prepareProviderExtraParams,
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
type ProviderRuntimePluginHandle,
|
||||
} from "../../plugins/provider-hook-runtime.js";
|
||||
import { buildAgentRuntimeDeliveryPlan, buildAgentRuntimePlan } from "./build.js";
|
||||
|
||||
const manifestMocks = vi.hoisted(() => ({
|
||||
loadManifestMetadataSnapshot: vi.fn(() => ({}) as never),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/manifest-contract-eligibility.js", () => ({
|
||||
loadManifestMetadataSnapshot: manifestMocks.loadManifestMetadataSnapshot,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/provider-hook-runtime.js", () => ({
|
||||
__testing: {},
|
||||
ensureProviderRuntimePluginHandle: vi.fn(
|
||||
(params) => params.runtimeHandle ?? { provider: "openai" },
|
||||
),
|
||||
prepareProviderExtraParams: vi.fn(() => undefined),
|
||||
resolveProviderAuthProfileId: vi.fn(() => undefined),
|
||||
resolveProviderExtraParamsForTransport: vi.fn(() => undefined),
|
||||
resolveProviderFollowupFallbackRoute: vi.fn(() => undefined),
|
||||
resolveProviderHookPlugin: vi.fn(() => undefined),
|
||||
resolveProviderPluginsForHooks: vi.fn(() => []),
|
||||
resolveProviderRuntimePlugin: vi.fn(() => undefined),
|
||||
resolveProviderRuntimePluginHandle: vi.fn(() => ({ provider: "openai" })),
|
||||
wrapProviderStreamFn: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
const gpt54Model = {
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200_000,
|
||||
maxTokens: 8_192,
|
||||
} as const;
|
||||
|
||||
describe("AgentRuntimePlan", () => {
|
||||
afterEach(() => {
|
||||
resetConfigRuntimeState();
|
||||
manifestMocks.loadManifestMetadataSnapshot.mockClear();
|
||||
vi.mocked(resolveProviderRuntimePluginHandle).mockClear();
|
||||
});
|
||||
|
||||
it("defers default transport extra params until they are read", () => {
|
||||
const prepareProviderExtraParamsMock = vi.mocked(prepareProviderExtraParams);
|
||||
prepareProviderExtraParamsMock.mockClear();
|
||||
|
||||
const plan = buildAgentRuntimePlan({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
modelApi: "openai-responses",
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
model: gpt54Model,
|
||||
});
|
||||
|
||||
expect(prepareProviderExtraParamsMock).not.toHaveBeenCalled();
|
||||
expect(plan.transport.extraParams).toMatchObject({
|
||||
parallel_tool_calls: true,
|
||||
text_verbosity: "low",
|
||||
openaiWsWarmup: false,
|
||||
});
|
||||
expect(prepareProviderExtraParamsMock).toHaveBeenCalledTimes(1);
|
||||
void plan.transport.extraParams;
|
||||
expect(prepareProviderExtraParamsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("records resolved model, auth, transport, tool, delivery, and observability policy", () => {
|
||||
const plan = buildAgentRuntimePlan({
|
||||
provider: "openai",
|
||||
@@ -27,16 +87,8 @@ describe("AgentRuntimePlan", () => {
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
model: {
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
...gpt54Model,
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200_000,
|
||||
maxTokens: 8_192,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -95,16 +147,8 @@ describe("AgentRuntimePlan", () => {
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
model: {
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
...gpt54Model,
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200_000,
|
||||
maxTokens: 8_192,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -155,4 +199,178 @@ describe("AgentRuntimePlan", () => {
|
||||
forwardedAuthProfileId: "openai-codex:work",
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves follow-up routes with the prepared provider handle", () => {
|
||||
const resolveProviderFollowupFallbackRouteMock = vi.mocked(
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
);
|
||||
resolveProviderFollowupFallbackRouteMock.mockClear();
|
||||
resolveProviderFollowupFallbackRouteMock.mockReturnValueOnce({
|
||||
route: "dispatcher" as const,
|
||||
reason: "prepared-route",
|
||||
});
|
||||
const providerRuntimeHandle: ProviderRuntimePluginHandle = {
|
||||
provider: "openai",
|
||||
};
|
||||
|
||||
const plan = buildAgentRuntimePlan({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
providerRuntimeHandle,
|
||||
});
|
||||
|
||||
expect(
|
||||
plan.delivery.resolveFollowupRoute({
|
||||
payload: { text: "hello" },
|
||||
originRoutable: false,
|
||||
dispatcherAvailable: true,
|
||||
}),
|
||||
).toEqual({
|
||||
route: "dispatcher",
|
||||
reason: "prepared-route",
|
||||
});
|
||||
expect(resolveProviderFollowupFallbackRouteMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "openai",
|
||||
runtimeHandle: providerRuntimeHandle,
|
||||
context: expect.objectContaining({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
originRoutable: false,
|
||||
dispatcherAvailable: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves incomplete supplied provider handles before invoking runtime hooks", () => {
|
||||
const resolveProviderRuntimePluginHandleMock = vi.mocked(resolveProviderRuntimePluginHandle);
|
||||
const resolveProviderFollowupFallbackRouteMock = vi.mocked(
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
);
|
||||
resolveProviderRuntimePluginHandleMock.mockClear();
|
||||
resolveProviderFollowupFallbackRouteMock.mockClear();
|
||||
|
||||
const suppliedHandle = {
|
||||
provider: "openai",
|
||||
config: { plugins: { allow: ["openai"] } },
|
||||
};
|
||||
const resolvedHandle: ProviderRuntimePluginHandle = {
|
||||
...suppliedHandle,
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
env: process.env,
|
||||
plugin: {} as never,
|
||||
};
|
||||
|
||||
resolveProviderRuntimePluginHandleMock.mockReturnValueOnce(resolvedHandle);
|
||||
|
||||
const plan = buildAgentRuntimePlan({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
providerRuntimeHandle: suppliedHandle,
|
||||
});
|
||||
|
||||
expect(plan.providerRuntimeHandle).toBe(resolvedHandle);
|
||||
|
||||
plan.delivery.resolveFollowupRoute({
|
||||
payload: { text: "hello" },
|
||||
originRoutable: false,
|
||||
dispatcherAvailable: true,
|
||||
});
|
||||
|
||||
expect(resolveProviderRuntimePluginHandleMock).toHaveBeenCalledWith({
|
||||
provider: "openai",
|
||||
config: suppliedHandle.config,
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
env: process.env,
|
||||
applyAutoEnable: undefined,
|
||||
bundledProviderAllowlistCompat: undefined,
|
||||
bundledProviderVitestCompat: undefined,
|
||||
});
|
||||
expect(resolveProviderFollowupFallbackRouteMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
runtimeHandle: resolvedHandle,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves incomplete supplied delivery handles before follow-up routing", () => {
|
||||
const resolveProviderRuntimePluginHandleMock = vi.mocked(resolveProviderRuntimePluginHandle);
|
||||
const resolveProviderFollowupFallbackRouteMock = vi.mocked(
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
);
|
||||
resolveProviderRuntimePluginHandleMock.mockClear();
|
||||
resolveProviderFollowupFallbackRouteMock.mockClear();
|
||||
|
||||
const suppliedHandle = {
|
||||
provider: "openai",
|
||||
};
|
||||
const resolvedHandle: ProviderRuntimePluginHandle = {
|
||||
provider: "openai",
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
env: process.env,
|
||||
plugin: {} as never,
|
||||
};
|
||||
|
||||
resolveProviderRuntimePluginHandleMock.mockReturnValueOnce(resolvedHandle);
|
||||
|
||||
const delivery = buildAgentRuntimeDeliveryPlan({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
providerRuntimeHandle: suppliedHandle,
|
||||
});
|
||||
|
||||
delivery.resolveFollowupRoute({
|
||||
payload: { text: "hello" },
|
||||
originRoutable: false,
|
||||
dispatcherAvailable: true,
|
||||
});
|
||||
|
||||
expect(resolveProviderRuntimePluginHandleMock).toHaveBeenCalledWith({
|
||||
provider: "openai",
|
||||
config: {},
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
env: process.env,
|
||||
applyAutoEnable: undefined,
|
||||
bundledProviderAllowlistCompat: undefined,
|
||||
bundledProviderVitestCompat: undefined,
|
||||
});
|
||||
expect(resolveProviderFollowupFallbackRouteMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
runtimeHandle: resolvedHandle,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("plans tool metadata against the runtime source snapshot lazily", () => {
|
||||
const sourceConfig = { channels: { telegram: { botToken: "token" } } };
|
||||
const runtimeConfig = {
|
||||
...sourceConfig,
|
||||
plugins: { allow: ["telegram"] },
|
||||
};
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
|
||||
const plan = buildAgentRuntimePlan({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
config: runtimeConfig,
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
});
|
||||
|
||||
expect(manifestMocks.loadManifestMetadataSnapshot).not.toHaveBeenCalled();
|
||||
|
||||
plan.tools.preparedPlanning?.loadMetadataSnapshot?.();
|
||||
|
||||
expect(manifestMocks.loadManifestMetadataSnapshot).toHaveBeenCalledWith({
|
||||
config: sourceConfig,
|
||||
workspaceDir: "/tmp/openclaw-runtime-plan",
|
||||
env: process.env,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,20 @@ import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-pay
|
||||
import type { TSchema } from "typebox";
|
||||
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import { isSilentReplyPayloadText, SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js";
|
||||
import { projectConfigOntoRuntimeSourceSnapshot } from "../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { loadManifestMetadataSnapshot } from "../../plugins/manifest-contract-eligibility.js";
|
||||
import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.types.js";
|
||||
import {
|
||||
resolveProviderRuntimePluginHandle,
|
||||
type ProviderRuntimePluginHandle,
|
||||
} from "../../plugins/provider-hook-runtime.js";
|
||||
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
|
||||
import {
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
resolveProviderSystemPromptContribution,
|
||||
resolveProviderTextTransforms,
|
||||
transformProviderSystemPrompt,
|
||||
} from "../../plugins/provider-runtime.js";
|
||||
import { resolvePreparedExtraParams } from "../pi-embedded-runner/extra-params.js";
|
||||
import { classifyEmbeddedPiRunResultForModelFallback } from "../pi-embedded-runner/result-fallback-classifier.js";
|
||||
@@ -49,10 +58,46 @@ function asThinkLevel(value: BuildAgentRuntimePlanParams["thinkingLevel"]): Thin
|
||||
return value !== undefined ? (value as ThinkLevel) : undefined;
|
||||
}
|
||||
|
||||
function isProviderRuntimePluginHandle(
|
||||
value: BuildAgentRuntimePlanParams["providerRuntimeHandle"] | ProviderRuntimePluginHandle,
|
||||
): value is ProviderRuntimePluginHandle {
|
||||
return value !== undefined && "plugin" in value;
|
||||
}
|
||||
|
||||
function resolveProviderRuntimeHandleForPlugins(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
runtimeHandle?: BuildAgentRuntimePlanParams["providerRuntimeHandle"];
|
||||
resolveWhenMissing?: boolean;
|
||||
}): ProviderRuntimePluginHandle | undefined {
|
||||
if (isProviderRuntimePluginHandle(params.runtimeHandle)) {
|
||||
return params.runtimeHandle;
|
||||
}
|
||||
if (!params.runtimeHandle && !params.resolveWhenMissing) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveProviderRuntimePluginHandle({
|
||||
provider: params.runtimeHandle?.provider ?? params.provider,
|
||||
config: asOpenClawConfig(params.runtimeHandle?.config) ?? params.config,
|
||||
workspaceDir: params.runtimeHandle?.workspaceDir ?? params.workspaceDir,
|
||||
env: params.runtimeHandle?.env ?? process.env,
|
||||
applyAutoEnable: params.runtimeHandle?.applyAutoEnable,
|
||||
bundledProviderAllowlistCompat: params.runtimeHandle?.bundledProviderAllowlistCompat,
|
||||
bundledProviderVitestCompat: params.runtimeHandle?.bundledProviderVitestCompat,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildAgentRuntimeDeliveryPlan(
|
||||
params: BuildAgentRuntimeDeliveryPlanParams,
|
||||
): AgentRuntimeDeliveryPlan {
|
||||
const config = asOpenClawConfig(params.config);
|
||||
const providerRuntimeHandle = resolveProviderRuntimeHandleForPlugins({
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
runtimeHandle: params.providerRuntimeHandle,
|
||||
});
|
||||
return {
|
||||
isSilentPayload(payload): boolean {
|
||||
return isSilentReplyPayloadText(payload.text, SILENT_REPLY_TOKEN) && !hasMedia(payload);
|
||||
@@ -62,6 +107,7 @@ export function buildAgentRuntimeDeliveryPlan(
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
runtimeHandle: providerRuntimeHandle,
|
||||
context: {
|
||||
config,
|
||||
agentDir: params.agentDir,
|
||||
@@ -90,6 +136,23 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
const model = asProviderRuntimeModel(params.model);
|
||||
const modelApi = params.modelApi ?? params.model?.api ?? undefined;
|
||||
const transport = params.resolvedTransport;
|
||||
const toolPlanningConfig = config ? projectConfigOntoRuntimeSourceSnapshot(config) : undefined;
|
||||
let toolPlanningMetadataSnapshot: PluginMetadataSnapshot | undefined;
|
||||
const loadToolPlanningMetadataSnapshot = () => {
|
||||
toolPlanningMetadataSnapshot ??= loadManifestMetadataSnapshot({
|
||||
config: toolPlanningConfig,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
env: process.env,
|
||||
});
|
||||
return toolPlanningMetadataSnapshot;
|
||||
};
|
||||
const providerRuntimeHandleForPlugins = resolveProviderRuntimeHandleForPlugins({
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
runtimeHandle: params.providerRuntimeHandle,
|
||||
resolveWhenMissing: true,
|
||||
});
|
||||
const auth = buildAgentRuntimeAuthPlan({
|
||||
provider: params.provider,
|
||||
authProfileProvider: params.authProfileProvider,
|
||||
@@ -112,6 +175,7 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: process.env,
|
||||
runtimeHandle: providerRuntimeHandleForPlugins,
|
||||
modelId: params.modelId,
|
||||
modelApi,
|
||||
model,
|
||||
@@ -137,6 +201,7 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
config,
|
||||
workspaceDir: overrides?.workspaceDir ?? params.workspaceDir,
|
||||
env: process.env,
|
||||
runtimeHandle: providerRuntimeHandleForPlugins,
|
||||
modelApi: overrides?.modelApi ?? modelApi,
|
||||
model: asProviderRuntimeModel(overrides?.model) ?? model,
|
||||
});
|
||||
@@ -154,19 +219,52 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
agentId: overrides.agentId ?? params.agentId,
|
||||
model: asProviderRuntimeModel(overrides.model) ?? model,
|
||||
resolvedTransport: overrides.resolvedTransport ?? transport,
|
||||
providerRuntimeHandle: providerRuntimeHandleForPlugins,
|
||||
});
|
||||
let memoizedTranscriptPolicy: ReturnType<typeof resolveTranscriptRuntimePolicy> | undefined;
|
||||
let memoizedTransportExtraParams: ReturnType<typeof resolveTransportExtraParams> | undefined;
|
||||
const resolveDefaultTranscriptPolicy = () => {
|
||||
memoizedTranscriptPolicy ??= resolveTranscriptRuntimePolicy();
|
||||
return memoizedTranscriptPolicy;
|
||||
};
|
||||
const resolveDefaultTransportExtraParams = () => {
|
||||
memoizedTransportExtraParams ??= resolveTransportExtraParams();
|
||||
return memoizedTransportExtraParams;
|
||||
};
|
||||
const providerTextTransforms = resolveProviderTextTransforms({
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: process.env,
|
||||
runtimeHandle: providerRuntimeHandleForPlugins,
|
||||
});
|
||||
|
||||
return {
|
||||
resolvedRef,
|
||||
providerRuntimeHandle: providerRuntimeHandleForPlugins,
|
||||
auth,
|
||||
prompt: {
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
textTransforms: providerTextTransforms,
|
||||
resolveSystemPromptContribution(context) {
|
||||
return resolveProviderSystemPromptContribution({
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: context.workspaceDir ?? params.workspaceDir,
|
||||
runtimeHandle: providerRuntimeHandleForPlugins,
|
||||
context: {
|
||||
...context,
|
||||
config: asOpenClawConfig(context.config),
|
||||
},
|
||||
});
|
||||
},
|
||||
transformSystemPrompt(context) {
|
||||
return transformProviderSystemPrompt({
|
||||
provider: params.provider,
|
||||
config,
|
||||
workspaceDir: context.workspaceDir ?? params.workspaceDir,
|
||||
runtimeHandle: providerRuntimeHandleForPlugins,
|
||||
context: {
|
||||
...context,
|
||||
config: asOpenClawConfig(context.config),
|
||||
@@ -175,6 +273,9 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
preparedPlanning: {
|
||||
loadMetadataSnapshot: loadToolPlanningMetadataSnapshot,
|
||||
},
|
||||
normalize<TSchemaType extends TSchema = TSchema, TResult = unknown>(
|
||||
tools: AgentTool<TSchemaType, TResult>[],
|
||||
overrides?: {
|
||||
@@ -203,13 +304,20 @@ export function buildAgentRuntimePlan(params: BuildAgentRuntimePlanParams): Agen
|
||||
},
|
||||
},
|
||||
transcript: {
|
||||
policy: resolveTranscriptRuntimePolicy(),
|
||||
get policy() {
|
||||
return resolveDefaultTranscriptPolicy();
|
||||
},
|
||||
resolvePolicy: resolveTranscriptRuntimePolicy,
|
||||
},
|
||||
delivery: buildAgentRuntimeDeliveryPlan(params),
|
||||
delivery: buildAgentRuntimeDeliveryPlan({
|
||||
...params,
|
||||
providerRuntimeHandle: providerRuntimeHandleForPlugins,
|
||||
}),
|
||||
outcome: buildAgentRuntimeOutcomePlan(),
|
||||
transport: {
|
||||
extraParams: resolveTransportExtraParams(),
|
||||
get extraParams() {
|
||||
return resolveDefaultTransportExtraParams();
|
||||
},
|
||||
resolveExtraParams: resolveTransportExtraParams,
|
||||
},
|
||||
observability: {
|
||||
|
||||
@@ -46,7 +46,7 @@ export type AgentRuntimeModel = {
|
||||
provider?: string;
|
||||
baseUrl?: string;
|
||||
reasoning?: boolean;
|
||||
input?: string[];
|
||||
input?: readonly string[];
|
||||
cost?: {
|
||||
input: number;
|
||||
output: number;
|
||||
@@ -59,6 +59,26 @@ export type AgentRuntimeModel = {
|
||||
compat?: unknown;
|
||||
};
|
||||
|
||||
export type AgentRuntimeTextReplacement = {
|
||||
from: string | RegExp;
|
||||
to: string;
|
||||
};
|
||||
|
||||
export type AgentRuntimeTextTransforms = {
|
||||
input?: AgentRuntimeTextReplacement[];
|
||||
output?: AgentRuntimeTextReplacement[];
|
||||
};
|
||||
|
||||
export type AgentRuntimeProviderHandle = {
|
||||
provider: string;
|
||||
config?: AgentRuntimeConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
applyAutoEnable?: boolean;
|
||||
bundledProviderAllowlistCompat?: boolean;
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
};
|
||||
|
||||
export type AgentRuntimeInteractiveButtonStyle = "primary" | "secondary" | "success" | "danger";
|
||||
|
||||
export type AgentRuntimeInteractiveReplyButton = {
|
||||
@@ -251,12 +271,27 @@ export type AgentRuntimeAuthPlan = {
|
||||
export type AgentRuntimePromptPlan = {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
textTransforms?: AgentRuntimeTextTransforms;
|
||||
resolveSystemPromptContribution(
|
||||
context: AgentRuntimeSystemPromptContributionContext,
|
||||
): AgentRuntimeSystemPromptContribution | undefined;
|
||||
transformSystemPrompt(
|
||||
context: AgentRuntimeSystemPromptContributionContext & {
|
||||
systemPrompt: string;
|
||||
},
|
||||
): string;
|
||||
};
|
||||
|
||||
// Keep the leaf runtime-plan contract decoupled from plugin metadata internals.
|
||||
export type AgentRuntimePreparedMetadataSnapshot = object;
|
||||
|
||||
export type PreparedOpenClawToolPlanning = {
|
||||
metadataSnapshot?: AgentRuntimePreparedMetadataSnapshot;
|
||||
loadMetadataSnapshot?: () => AgentRuntimePreparedMetadataSnapshot;
|
||||
};
|
||||
|
||||
export type AgentRuntimeToolPlan = {
|
||||
preparedPlanning?: PreparedOpenClawToolPlanning;
|
||||
normalize<TSchemaType extends TSchema = TSchema, TResult = unknown>(
|
||||
tools: AgentTool<TSchemaType, TResult>[],
|
||||
params?: {
|
||||
@@ -306,6 +341,7 @@ export type AgentRuntimeTransportPlan = {
|
||||
|
||||
export type AgentRuntimePlan = {
|
||||
resolvedRef: AgentRuntimeResolvedRef;
|
||||
providerRuntimeHandle?: AgentRuntimeProviderHandle;
|
||||
auth: AgentRuntimeAuthPlan;
|
||||
prompt: AgentRuntimePromptPlan;
|
||||
tools: AgentRuntimeToolPlan;
|
||||
@@ -337,6 +373,7 @@ export type BuildAgentRuntimeDeliveryPlanParams = {
|
||||
agentDir?: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
providerRuntimeHandle?: AgentRuntimeProviderHandle;
|
||||
};
|
||||
|
||||
export type BuildAgentRuntimePlanParams = {
|
||||
@@ -356,4 +393,5 @@ export type BuildAgentRuntimePlanParams = {
|
||||
thinkingLevel?: AgentRuntimeThinkLevel;
|
||||
extraParamsOverride?: Record<string, unknown>;
|
||||
resolvedTransport?: AgentRuntimeTransport;
|
||||
providerRuntimeHandle?: AgentRuntimeProviderHandle;
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@ export function installEmbeddedRunnerFastRunE2eMocks(
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
resolveSystemPromptContribution: vi.fn(() => undefined),
|
||||
transformSystemPrompt: vi.fn((context) => context.systemPrompt),
|
||||
},
|
||||
tools: {
|
||||
normalize: vi.fn((tools: unknown[]) => tools),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "../plugins/plugin-control-plane-context.js";
|
||||
import type { ProviderRuntimePluginHandle } from "../plugins/provider-hook-runtime.js";
|
||||
import { resolveProviderRuntimePlugin } from "../plugins/provider-hook-runtime.js";
|
||||
import { shouldPreserveThinkingBlocks } from "../plugins/provider-replay-helpers.js";
|
||||
import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
|
||||
@@ -219,6 +220,7 @@ export function resolveTranscriptPolicy(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
model?: ProviderRuntimeModel;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
}): TranscriptPolicy {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const cacheConfig = canCacheTranscriptPolicy(params) ? params.config : undefined;
|
||||
@@ -231,14 +233,16 @@ export function resolveTranscriptPolicy(params: {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
const runtimePlugin = provider
|
||||
? resolveProviderRuntimePlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
: undefined;
|
||||
const runtimePlugin =
|
||||
params.runtimeHandle?.plugin ??
|
||||
(provider
|
||||
? resolveProviderRuntimePlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
: undefined);
|
||||
const context = {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
|
||||
export type ConfiguredModelRef = {
|
||||
@@ -115,3 +116,19 @@ export function collectConfiguredModelRefs(
|
||||
);
|
||||
return refs;
|
||||
}
|
||||
|
||||
export function collectConfiguredModelRefValues(
|
||||
config: unknown,
|
||||
options?: { includeChannelModelOverrides?: boolean },
|
||||
): string[] {
|
||||
return collectConfiguredModelRefs(config, options).map((ref) => ref.value);
|
||||
}
|
||||
|
||||
export function extractProviderFromModelRef(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
const slash = trimmed.indexOf("/");
|
||||
if (slash <= 0) {
|
||||
return null;
|
||||
}
|
||||
return normalizeProviderId(trimmed.slice(0, slash));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
import type { ChannelMessageAdapterShape } from "../../channels/message/types.js";
|
||||
import { getChannelPlugin, getLoadedChannelPlugin } from "../../channels/plugins/index.js";
|
||||
import { channelPluginHasNativeApprovalPromptUi } from "../../channels/plugins/native-approval-prompt.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
|
||||
import type {
|
||||
ChannelAgentPromptAdapter,
|
||||
ChannelAllowlistAdapter,
|
||||
ChannelCapabilities,
|
||||
ChannelCommandAdapter,
|
||||
ChannelConfigAdapter,
|
||||
ChannelConversationBindingSupport,
|
||||
ChannelDirectoryAdapter,
|
||||
ChannelGroupAdapter,
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessagingAdapter,
|
||||
ChannelOutboundAdapter,
|
||||
ChannelPairingAdapter,
|
||||
ChannelStreamingAdapter,
|
||||
ChannelThreadingAdapter,
|
||||
} from "../../channels/plugins/types.public.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { getActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import {
|
||||
@@ -13,6 +30,60 @@ import {
|
||||
resetOutboundChannelBootstrapStateForTests,
|
||||
} from "./channel-bootstrap.runtime.js";
|
||||
|
||||
type ChannelTargetResolver = NonNullable<ChannelMessagingAdapter["targetResolver"]>;
|
||||
|
||||
export type ChannelPromptRuntime = {
|
||||
messageToolHints?: ChannelAgentPromptAdapter["messageToolHints"];
|
||||
messageToolCapabilities?: ChannelAgentPromptAdapter["messageToolCapabilities"];
|
||||
reactionGuidance?: ChannelAgentPromptAdapter["reactionGuidance"];
|
||||
hasNativeApprovalPromptUi?: boolean;
|
||||
};
|
||||
|
||||
export type OutboundChannelRuntime = {
|
||||
id: string;
|
||||
label: string;
|
||||
chatTypes: NonNullable<ChannelCapabilities["chatTypes"]>;
|
||||
preferSessionLookupForAnnounceTarget?: ChannelPlugin["meta"]["preferSessionLookupForAnnounceTarget"];
|
||||
actions?: ChannelMessageActionAdapter;
|
||||
approvalCapability?: ChannelPlugin["approvalCapability"];
|
||||
conversationBindings?: ChannelConversationBindingSupport;
|
||||
allowlist?: ChannelAllowlistAdapter;
|
||||
pairing?: ChannelPairingAdapter;
|
||||
commands?: ChannelCommandAdapter;
|
||||
defaultAccountId?: ChannelConfigAdapter<unknown>["defaultAccountId"];
|
||||
directory?: ChannelDirectoryAdapter;
|
||||
promptRuntime?: ChannelPromptRuntime;
|
||||
inferTargetChatType?: ChannelMessagingAdapter["inferTargetChatType"];
|
||||
normalizeTarget?: ChannelMessagingAdapter["normalizeTarget"];
|
||||
looksLikeTargetId?: ChannelTargetResolver["looksLikeId"];
|
||||
targetResolverHint?: string;
|
||||
resolveMessagingTargetFallback?: ChannelTargetResolver["resolveTarget"];
|
||||
resolveSessionTarget?: ChannelMessagingAdapter["resolveSessionTarget"];
|
||||
formatTargetDisplay?: ChannelMessagingAdapter["formatTargetDisplay"];
|
||||
resolveOutboundSessionRoute?: ChannelMessagingAdapter["resolveOutboundSessionRoute"];
|
||||
buildCrossContextPresentation?: ChannelMessagingAdapter["buildCrossContextPresentation"];
|
||||
transformReplyPayload?: ChannelMessagingAdapter["transformReplyPayload"];
|
||||
resolveAllowFrom?: ChannelConfigAdapter<unknown>["resolveAllowFrom"];
|
||||
resolveDefaultTo?: ChannelConfigAdapter<unknown>["resolveDefaultTo"];
|
||||
formatAllowFrom?: ChannelPlugin["config"]["formatAllowFrom"];
|
||||
allowFromFallback?: NonNullable<ChannelPlugin["elevated"]>["allowFromFallback"];
|
||||
resolveGroupRequireMention?: ChannelGroupAdapter["resolveRequireMention"];
|
||||
resolveGroupToolPolicy?: ChannelGroupAdapter["resolveToolPolicy"];
|
||||
queueDebounceMs?: NonNullable<NonNullable<ChannelPlugin["defaults"]>["queue"]>["debounceMs"];
|
||||
buildThreadingToolContext?: ChannelThreadingAdapter["buildToolContext"];
|
||||
resolveAutoThreadId?: ChannelThreadingAdapter["resolveAutoThreadId"];
|
||||
resolveReplyToMode?: ChannelThreadingAdapter["resolveReplyToMode"];
|
||||
resolveReplyTransport?: ChannelThreadingAdapter["resolveReplyTransport"];
|
||||
outbound?: ChannelOutboundAdapter;
|
||||
resolveTarget?: ChannelOutboundAdapter["resolveTarget"];
|
||||
textChunkLimit?: ChannelOutboundAdapter["textChunkLimit"];
|
||||
shouldTreatDeliveredTextAsVisible?: ChannelOutboundAdapter["shouldTreatDeliveredTextAsVisible"];
|
||||
shouldTreatRoutedTextAsVisible?: ChannelOutboundAdapter["shouldTreatRoutedTextAsVisible"];
|
||||
targetsMatchForReplySuppression?: ChannelOutboundAdapter["targetsMatchForReplySuppression"];
|
||||
hasStructuredReplyPayload?: ChannelMessagingAdapter["hasStructuredReplyPayload"];
|
||||
blockStreamingCoalesceDefaults?: ChannelStreamingAdapter["blockStreamingCoalesceDefaults"];
|
||||
};
|
||||
|
||||
export function resetOutboundChannelResolutionStateForTest(): void {
|
||||
resetOutboundChannelBootstrapStateForTests();
|
||||
}
|
||||
@@ -34,9 +105,7 @@ function maybeBootstrapChannelPlugin(params: {
|
||||
bootstrapOutboundChannelPlugin(params);
|
||||
}
|
||||
|
||||
function resolveDirectFromActiveRegistry(
|
||||
channel: DeliverableMessageChannel,
|
||||
): ChannelPlugin | undefined {
|
||||
function resolveDirectFromActiveRegistry(channel: string): ChannelPlugin | undefined {
|
||||
const activeRegistry = getActivePluginRegistry();
|
||||
if (!activeRegistry) {
|
||||
return undefined;
|
||||
@@ -50,6 +119,58 @@ function resolveDirectFromActiveRegistry(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function toOutboundChannelRuntime(plugin: ChannelPlugin): OutboundChannelRuntime {
|
||||
return {
|
||||
id: plugin.id,
|
||||
label: plugin.meta.label,
|
||||
chatTypes: plugin.capabilities.chatTypes,
|
||||
preferSessionLookupForAnnounceTarget: plugin.meta.preferSessionLookupForAnnounceTarget,
|
||||
actions: plugin.actions,
|
||||
approvalCapability: plugin.approvalCapability,
|
||||
conversationBindings: plugin.conversationBindings,
|
||||
allowlist: plugin.allowlist,
|
||||
pairing: plugin.pairing,
|
||||
commands: plugin.commands,
|
||||
defaultAccountId: plugin.config.defaultAccountId,
|
||||
directory: plugin.directory,
|
||||
promptRuntime: {
|
||||
messageToolHints: plugin.agentPrompt?.messageToolHints,
|
||||
messageToolCapabilities: plugin.agentPrompt?.messageToolCapabilities,
|
||||
reactionGuidance: plugin.agentPrompt?.reactionGuidance,
|
||||
hasNativeApprovalPromptUi: channelPluginHasNativeApprovalPromptUi(plugin),
|
||||
},
|
||||
inferTargetChatType: plugin.messaging?.inferTargetChatType,
|
||||
normalizeTarget: plugin.messaging?.normalizeTarget,
|
||||
looksLikeTargetId: plugin.messaging?.targetResolver?.looksLikeId,
|
||||
targetResolverHint: plugin.messaging?.targetResolver?.hint,
|
||||
resolveMessagingTargetFallback: plugin.messaging?.targetResolver?.resolveTarget,
|
||||
resolveSessionTarget: plugin.messaging?.resolveSessionTarget,
|
||||
formatTargetDisplay: plugin.messaging?.formatTargetDisplay,
|
||||
resolveOutboundSessionRoute: plugin.messaging?.resolveOutboundSessionRoute,
|
||||
buildCrossContextPresentation: plugin.messaging?.buildCrossContextPresentation,
|
||||
transformReplyPayload: plugin.messaging?.transformReplyPayload,
|
||||
resolveAllowFrom: plugin.config?.resolveAllowFrom,
|
||||
resolveDefaultTo: plugin.config?.resolveDefaultTo,
|
||||
formatAllowFrom: plugin.config?.formatAllowFrom,
|
||||
allowFromFallback: plugin.elevated?.allowFromFallback,
|
||||
resolveGroupRequireMention: plugin.groups?.resolveRequireMention,
|
||||
resolveGroupToolPolicy: plugin.groups?.resolveToolPolicy,
|
||||
queueDebounceMs: plugin.defaults?.queue?.debounceMs,
|
||||
buildThreadingToolContext: plugin.threading?.buildToolContext,
|
||||
resolveAutoThreadId: plugin.threading?.resolveAutoThreadId,
|
||||
resolveReplyToMode: plugin.threading?.resolveReplyToMode,
|
||||
resolveReplyTransport: plugin.threading?.resolveReplyTransport,
|
||||
outbound: plugin.outbound,
|
||||
resolveTarget: plugin.outbound?.resolveTarget,
|
||||
textChunkLimit: plugin.outbound?.textChunkLimit,
|
||||
shouldTreatDeliveredTextAsVisible: plugin.outbound?.shouldTreatDeliveredTextAsVisible,
|
||||
shouldTreatRoutedTextAsVisible: plugin.outbound?.shouldTreatRoutedTextAsVisible,
|
||||
targetsMatchForReplySuppression: plugin.outbound?.targetsMatchForReplySuppression,
|
||||
hasStructuredReplyPayload: plugin.messaging?.hasStructuredReplyPayload,
|
||||
blockStreamingCoalesceDefaults: plugin.streaming?.blockStreamingCoalesceDefaults,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveOutboundChannelPlugin(params: {
|
||||
channel: string;
|
||||
cfg?: OpenClawConfig;
|
||||
@@ -86,3 +207,53 @@ export function resolveOutboundChannelMessageAdapter(params: {
|
||||
}): ChannelMessageAdapterShape | undefined {
|
||||
return resolveOutboundChannelPlugin(params)?.message;
|
||||
}
|
||||
|
||||
export function resolveOutboundChannelPluginForRead(params: {
|
||||
channel: string;
|
||||
cfg?: OpenClawConfig;
|
||||
}): ChannelPlugin | undefined {
|
||||
const normalized = normalizeMessageChannel(params.channel) ?? params.channel.trim();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
const channelId = normalized as Parameters<typeof getLoadedChannelPlugin>[0];
|
||||
const current = getLoadedChannelPlugin(channelId);
|
||||
if (current) {
|
||||
return current;
|
||||
}
|
||||
const directCurrent = resolveDirectFromActiveRegistry(normalized);
|
||||
if (directCurrent) {
|
||||
return directCurrent;
|
||||
}
|
||||
const deliverable = normalizeDeliverableOutboundChannel(normalized);
|
||||
if (deliverable) {
|
||||
maybeBootstrapChannelPlugin({ channel: deliverable, cfg: params.cfg });
|
||||
return (
|
||||
getLoadedChannelPlugin(deliverable) ??
|
||||
resolveDirectFromActiveRegistry(deliverable) ??
|
||||
getChannelPlugin(deliverable)
|
||||
);
|
||||
}
|
||||
return getChannelPlugin(channelId);
|
||||
}
|
||||
|
||||
export function resolveOutboundChannelRuntime(params: {
|
||||
channel: string;
|
||||
cfg?: OpenClawConfig;
|
||||
}): OutboundChannelRuntime | undefined {
|
||||
const plugin = resolveOutboundChannelPluginForRead(params);
|
||||
return plugin ? toOutboundChannelRuntime(plugin) : undefined;
|
||||
}
|
||||
|
||||
export function resolveLoadedOutboundChannelPluginForRead(params: {
|
||||
channel: string;
|
||||
}): ChannelPlugin | undefined {
|
||||
const normalized = normalizeMessageChannel(params.channel) ?? params.channel.trim();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
getLoadedChannelPlugin(normalized as Parameters<typeof getLoadedChannelPlugin>[0]) ??
|
||||
resolveDirectFromActiveRegistry(normalized)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from "../agents/identity.js";
|
||||
export * from "../agents/model-auth-markers.js";
|
||||
export * from "../agents/model-auth.js";
|
||||
export * from "../agents/model-catalog.js";
|
||||
export * from "../agents/model-catalog-scope.js";
|
||||
export * from "../agents/model-selection.js";
|
||||
export * from "../agents/simple-completion-runtime.js";
|
||||
export * from "../agents/pi-embedded-block-chunker.js";
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { emptyChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
import type { ChannelOutboundAdapter } from "../channels/plugins/types.adapters.js";
|
||||
import type { ChannelConfigSchema } from "../channels/plugins/types.config.js";
|
||||
import type { ChannelLegacyStateMigrationPlan } from "../channels/plugins/types.core.js";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||
@@ -52,6 +53,7 @@ type DefineBundledChannelEntryOptions<TPlugin = ChannelPlugin> = {
|
||||
description: string;
|
||||
importMetaUrl: string;
|
||||
plugin: BundledEntryModuleRef;
|
||||
outbound?: BundledEntryModuleRef;
|
||||
secrets?: BundledEntryModuleRef;
|
||||
configSchema?: ChannelEntryConfigSchema<TPlugin> | (() => ChannelEntryConfigSchema<TPlugin>);
|
||||
runtime?: BundledEntryModuleRef;
|
||||
@@ -108,6 +110,9 @@ export type BundledChannelEntryContract<TPlugin = ChannelPlugin> = {
|
||||
features?: BundledChannelEntryFeatures;
|
||||
register: (api: OpenClawPluginApi) => void;
|
||||
loadChannelPlugin: (options?: BundledEntryModuleLoadOptions) => TPlugin;
|
||||
loadChannelOutbound?: (
|
||||
options?: BundledEntryModuleLoadOptions,
|
||||
) => ChannelOutboundAdapter | undefined;
|
||||
loadChannelSecrets?: (
|
||||
options?: BundledEntryModuleLoadOptions,
|
||||
) => ChannelPlugin["secrets"] | undefined;
|
||||
@@ -435,6 +440,7 @@ export function defineBundledChannelEntry<TPlugin = ChannelPlugin>({
|
||||
description,
|
||||
importMetaUrl,
|
||||
plugin,
|
||||
outbound,
|
||||
secrets,
|
||||
configSchema,
|
||||
runtime,
|
||||
@@ -449,6 +455,14 @@ export function defineBundledChannelEntry<TPlugin = ChannelPlugin>({
|
||||
: ((configSchema ?? emptyChannelConfigSchema()) as ChannelEntryConfigSchema<TPlugin>);
|
||||
const loadChannelPlugin = (options?: BundledEntryModuleLoadOptions) =>
|
||||
loadBundledEntryExportSync<TPlugin>(importMetaUrl, plugin, options);
|
||||
const loadChannelOutbound = outbound
|
||||
? (options?: BundledEntryModuleLoadOptions) =>
|
||||
loadBundledEntryExportSync<ChannelOutboundAdapter | undefined>(
|
||||
importMetaUrl,
|
||||
outbound,
|
||||
options,
|
||||
)
|
||||
: undefined;
|
||||
const loadChannelSecrets = secrets
|
||||
? (options?: BundledEntryModuleLoadOptions) =>
|
||||
loadBundledEntryExportSync<ChannelPlugin["secrets"] | undefined>(
|
||||
@@ -511,6 +525,7 @@ export function defineBundledChannelEntry<TPlugin = ChannelPlugin>({
|
||||
profile("bundled-register:registerFull", () => registerFull?.(api));
|
||||
},
|
||||
loadChannelPlugin,
|
||||
...(loadChannelOutbound ? { loadChannelOutbound } : {}),
|
||||
...(loadChannelSecrets ? { loadChannelSecrets } : {}),
|
||||
...(loadChannelAccountInspector ? { loadChannelAccountInspector } : {}),
|
||||
...(setChannelRuntime ? { setChannelRuntime } : {}),
|
||||
|
||||
@@ -37,6 +37,7 @@ export { parseTtsDirectives } from "../tts/directives.js";
|
||||
export {
|
||||
canonicalizeSpeechProviderId,
|
||||
getSpeechProvider,
|
||||
listLoadedSpeechProviders,
|
||||
listSpeechProviders,
|
||||
normalizeSpeechProviderId,
|
||||
} from "../tts/provider-registry.js";
|
||||
|
||||
@@ -33,6 +33,10 @@ function loadFacadeModule(): FacadeModule {
|
||||
});
|
||||
}
|
||||
|
||||
export function prewarmTtsRuntimeFacade(): void {
|
||||
loadFacadeModule();
|
||||
}
|
||||
|
||||
export const _test: FacadeModule["_test"] = createLazyFacadeObjectValue(
|
||||
() => loadFacadeModule()._test,
|
||||
);
|
||||
|
||||
@@ -22,8 +22,9 @@ import type {
|
||||
} from "./types.js";
|
||||
|
||||
const providerRuntimePluginCache: ConfigScopedRuntimeCache<ProviderPlugin | null> = new WeakMap();
|
||||
const PREPARED_PROVIDER_RUNTIME_SURFACES = ["channel"] as const;
|
||||
|
||||
type ProviderRuntimePluginLookupParams = {
|
||||
export type ProviderRuntimePluginLookupParams = {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
@@ -33,6 +34,14 @@ type ProviderRuntimePluginLookupParams = {
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
};
|
||||
|
||||
export type ProviderRuntimePluginHandle = ProviderRuntimePluginLookupParams & {
|
||||
plugin?: ProviderPlugin;
|
||||
};
|
||||
|
||||
export type ProviderRuntimePluginHandleParams = ProviderRuntimePluginLookupParams & {
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
};
|
||||
|
||||
function matchesProviderId(provider: ProviderPlugin, providerId: string): boolean {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
if (!normalized) {
|
||||
@@ -68,13 +77,42 @@ function matchesProviderLiteralId(provider: ProviderPlugin, providerId: string):
|
||||
return !!normalized && normalizeLowercaseStringOrEmpty(provider.id) === normalized;
|
||||
}
|
||||
|
||||
function resolveCompatibleActiveProviderRegistry(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): PluginRegistry | undefined {
|
||||
return getLoadedRuntimePluginRegistry({
|
||||
env: params.env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
function findProviderRuntimePluginInLoadedRegistries(params: {
|
||||
lookup: ProviderRuntimePluginLookupParams;
|
||||
apiOwnerHint?: string;
|
||||
}): ProviderPlugin | undefined {
|
||||
const activeRegistry = getLoadedRuntimePluginRegistry({
|
||||
env: params.lookup.env,
|
||||
workspaceDir: params.lookup.workspaceDir,
|
||||
});
|
||||
const activePlugin = activeRegistry
|
||||
? findProviderRuntimePluginInRegistry({
|
||||
registry: activeRegistry,
|
||||
provider: params.lookup.provider,
|
||||
apiOwnerHint: params.apiOwnerHint,
|
||||
})
|
||||
: undefined;
|
||||
if (activePlugin) {
|
||||
return activePlugin;
|
||||
}
|
||||
for (const surface of PREPARED_PROVIDER_RUNTIME_SURFACES) {
|
||||
const registry = getLoadedRuntimePluginRegistry({
|
||||
env: params.lookup.env,
|
||||
workspaceDir: params.lookup.workspaceDir,
|
||||
surface,
|
||||
});
|
||||
const plugin = registry
|
||||
? findProviderRuntimePluginInRegistry({
|
||||
registry,
|
||||
provider: params.lookup.provider,
|
||||
apiOwnerHint: params.apiOwnerHint,
|
||||
})
|
||||
: undefined;
|
||||
if (plugin) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findProviderRuntimePluginInRegistry(params: {
|
||||
@@ -100,7 +138,7 @@ export function resolveProviderPluginsForHooks(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
onlyPluginIds?: string[];
|
||||
providerRefs?: string[];
|
||||
providerRefs?: readonly string[];
|
||||
applyAutoEnable?: boolean;
|
||||
bundledProviderAllowlistCompat?: boolean;
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
@@ -139,16 +177,12 @@ export function resolveProviderRuntimePlugin(
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
});
|
||||
const activeRegistry = resolveCompatibleActiveProviderRegistry(params);
|
||||
const activePlugin = activeRegistry
|
||||
? findProviderRuntimePluginInRegistry({
|
||||
registry: activeRegistry,
|
||||
provider: params.provider,
|
||||
apiOwnerHint,
|
||||
})
|
||||
: undefined;
|
||||
if (activePlugin) {
|
||||
return activePlugin;
|
||||
const loadedPlugin = findProviderRuntimePluginInLoadedRegistries({
|
||||
lookup: params,
|
||||
apiOwnerHint,
|
||||
});
|
||||
if (loadedPlugin) {
|
||||
return loadedPlugin;
|
||||
}
|
||||
const cacheConfig = params.env && params.env !== process.env ? undefined : params.config;
|
||||
const plugin = resolveConfigScopedRuntimeCacheValue({
|
||||
@@ -196,14 +230,43 @@ export function resolveProviderHookPlugin(params: {
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProviderRuntimePluginHandle(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): ProviderRuntimePluginHandle {
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
|
||||
const env = params.env;
|
||||
const runtimePlugin = resolveProviderRuntimePlugin({
|
||||
...params,
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
|
||||
return {
|
||||
...params,
|
||||
workspaceDir,
|
||||
env,
|
||||
plugin: runtimePlugin,
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureProviderRuntimePluginHandle(
|
||||
params: ProviderRuntimePluginHandleParams,
|
||||
): ProviderRuntimePluginHandle {
|
||||
return params.runtimeHandle ?? resolveProviderRuntimePluginHandle(params);
|
||||
}
|
||||
|
||||
export function prepareProviderExtraParams(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderPrepareExtraParamsContext;
|
||||
}) {
|
||||
return resolveProviderRuntimePlugin(params)?.prepareExtraParams?.(params.context) ?? undefined;
|
||||
return (
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.prepareExtraParams?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProviderExtraParamsForTransport(params: {
|
||||
@@ -211,10 +274,12 @@ export function resolveProviderExtraParamsForTransport(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderExtraParamsForTransportContext;
|
||||
}) {
|
||||
return (
|
||||
resolveProviderRuntimePlugin(params)?.extraParamsForTransport?.(params.context) ?? undefined
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.extraParamsForTransport?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,9 +288,12 @@ export function resolveProviderAuthProfileId(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderResolveAuthProfileIdContext;
|
||||
}): string | undefined {
|
||||
const resolved = resolveProviderRuntimePlugin(params)?.resolveAuthProfileId?.(params.context);
|
||||
const resolved = ensureProviderRuntimePluginHandle(params).plugin?.resolveAuthProfileId?.(
|
||||
params.context,
|
||||
);
|
||||
return typeof resolved === "string" && resolved.trim() ? resolved.trim() : undefined;
|
||||
}
|
||||
|
||||
@@ -234,9 +302,13 @@ export function resolveProviderFollowupFallbackRoute(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderFollowupFallbackRouteContext;
|
||||
}): ProviderFollowupFallbackRouteResult | undefined {
|
||||
return resolveProviderHookPlugin(params)?.followupFallbackRoute?.(params.context) ?? undefined;
|
||||
return (
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.followupFallbackRoute?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function wrapProviderStreamFn(params: {
|
||||
@@ -244,7 +316,10 @@ export function wrapProviderStreamFn(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderWrapStreamFnContext;
|
||||
}) {
|
||||
return resolveProviderRuntimePlugin(params)?.wrapStreamFn?.(params.context) ?? undefined;
|
||||
return (
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.wrapStreamFn?.(params.context) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import {
|
||||
resolveProviderAuthProfileId,
|
||||
resolveProviderExtraParamsForTransport,
|
||||
resolveProviderFollowupFallbackRoute,
|
||||
ensureProviderRuntimePluginHandle,
|
||||
resolveProviderHookPlugin,
|
||||
resolveProviderPluginsForHooks,
|
||||
resolveProviderRuntimePlugin,
|
||||
type ProviderRuntimePluginHandle,
|
||||
wrapProviderStreamFn,
|
||||
} from "./provider-hook-runtime.js";
|
||||
import { resolveBundledProviderPolicySurface } from "./provider-public-artifacts.js";
|
||||
@@ -188,9 +190,10 @@ export function resolveProviderSystemPromptContribution(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderSystemPromptContributionContext;
|
||||
}): ProviderSystemPromptContribution | undefined {
|
||||
const plugin = resolveProviderRuntimePlugin(params);
|
||||
const plugin = ensureProviderRuntimePluginHandle(params).plugin;
|
||||
const baseOverlay = resolveGpt5SystemPromptContribution({
|
||||
config: params.context.config ?? params.config,
|
||||
providerId: params.context.provider ?? params.provider,
|
||||
@@ -240,9 +243,10 @@ export function transformProviderSystemPrompt(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderTransformSystemPromptContext;
|
||||
}): string {
|
||||
const plugin = resolveProviderRuntimePlugin(params);
|
||||
const plugin = ensureProviderRuntimePluginHandle(params).plugin;
|
||||
const textTransforms = mergePluginTextTransforms(
|
||||
resolveRuntimeTextTransforms(),
|
||||
plugin?.textTransforms,
|
||||
@@ -257,10 +261,11 @@ export function resolveProviderTextTransforms(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
}): PluginTextTransforms | undefined {
|
||||
return mergePluginTextTransforms(
|
||||
resolveRuntimeTextTransforms(),
|
||||
resolveProviderRuntimePlugin(params)?.textTransforms,
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.textTransforms,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -554,9 +559,13 @@ export function normalizeProviderToolSchemasWithPlugin(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderNormalizeToolSchemasContext;
|
||||
}) {
|
||||
return resolveProviderRuntimePlugin(params)?.normalizeToolSchemas?.(params.context) ?? undefined;
|
||||
return (
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.normalizeToolSchemas?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function inspectProviderToolSchemasWithPlugin(params: {
|
||||
@@ -564,9 +573,13 @@ export function inspectProviderToolSchemasWithPlugin(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
runtimeHandle?: ProviderRuntimePluginHandle;
|
||||
context: ProviderNormalizeToolSchemasContext;
|
||||
}) {
|
||||
return resolveProviderRuntimePlugin(params)?.inspectToolSchemas?.(params.context) ?? undefined;
|
||||
return (
|
||||
ensureProviderRuntimePluginHandle(params).plugin?.inspectToolSchemas?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProviderReasoningOutputModeWithPlugin(params: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { getActiveRuntimePluginRegistry } from "../plugins/active-runtime-registry.js";
|
||||
import {
|
||||
resolvePluginCapabilityProvider,
|
||||
resolvePluginCapabilityProviders,
|
||||
@@ -17,6 +18,10 @@ function resolveSpeechProviderPluginEntries(cfg?: OpenClawConfig): SpeechProvide
|
||||
});
|
||||
}
|
||||
|
||||
function resolveLoadedSpeechProviderPluginEntries(): SpeechProviderPlugin[] {
|
||||
return (getActiveRuntimePluginRegistry()?.speechProviders ?? []).map((entry) => entry.provider);
|
||||
}
|
||||
|
||||
const defaultSpeechProviderRegistryResolver: SpeechProviderRegistryResolver = {
|
||||
getProvider: (providerId, cfg) =>
|
||||
resolvePluginCapabilityProvider({
|
||||
@@ -31,7 +36,19 @@ const defaultSpeechProviderRegistry = createSpeechProviderRegistry(
|
||||
defaultSpeechProviderRegistryResolver,
|
||||
);
|
||||
|
||||
const loadedSpeechProviderRegistry = createSpeechProviderRegistry({
|
||||
getProvider: (providerId) =>
|
||||
resolveLoadedSpeechProviderPluginEntries().find((provider) => {
|
||||
if (provider.id === providerId) {
|
||||
return true;
|
||||
}
|
||||
return provider.aliases?.includes(providerId) ?? false;
|
||||
}),
|
||||
listProviders: () => resolveLoadedSpeechProviderPluginEntries(),
|
||||
});
|
||||
|
||||
export const listSpeechProviders = defaultSpeechProviderRegistry.listSpeechProviders;
|
||||
export const listLoadedSpeechProviders = loadedSpeechProviderRegistry.listSpeechProviders;
|
||||
export const getSpeechProvider = defaultSpeechProviderRegistry.getSpeechProvider;
|
||||
export const canonicalizeSpeechProviderId =
|
||||
defaultSpeechProviderRegistry.canonicalizeSpeechProviderId;
|
||||
|
||||
Reference in New Issue
Block a user