mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(plugins): snapshot text transforms
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Text transform registration tests cover plugin-owned replacement snapshotting.
|
||||
import {
|
||||
createPluginRegistryFixture,
|
||||
registerTestPlugin,
|
||||
} from "openclaw/plugin-sdk/plugin-test-contracts";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { applyPluginTextReplacements } from "../../agents/plugin-text-transforms.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../runtime.js";
|
||||
import { createPluginRecord } from "../status.test-helpers.js";
|
||||
import { resolveRuntimeTextTransforms } from "../text-transforms.runtime.js";
|
||||
import type { PluginTextReplacement, PluginTextTransformRegistration } from "../types.js";
|
||||
|
||||
describe("plugin text transform registration", () => {
|
||||
afterEach(() => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
});
|
||||
|
||||
it("snapshots replacement fields before runtime transform resolution", () => {
|
||||
let inputReads = 0;
|
||||
let outputReads = 0;
|
||||
let inputFromReads = 0;
|
||||
let inputToReads = 0;
|
||||
let outputFromReads = 0;
|
||||
let outputToReads = 0;
|
||||
const inputReplacement = {
|
||||
get from() {
|
||||
inputFromReads += 1;
|
||||
if (inputFromReads > 1) {
|
||||
throw new Error("input from getter re-read");
|
||||
}
|
||||
return "red";
|
||||
},
|
||||
get to() {
|
||||
inputToReads += 1;
|
||||
if (inputToReads > 1) {
|
||||
throw new Error("input to getter re-read");
|
||||
}
|
||||
return "blue";
|
||||
},
|
||||
} as PluginTextReplacement;
|
||||
const outputReplacement = {
|
||||
get from() {
|
||||
outputFromReads += 1;
|
||||
if (outputFromReads > 1) {
|
||||
throw new Error("output from getter re-read");
|
||||
}
|
||||
return /done/u;
|
||||
},
|
||||
get to() {
|
||||
outputToReads += 1;
|
||||
if (outputToReads > 1) {
|
||||
throw new Error("output to getter re-read");
|
||||
}
|
||||
return "finished";
|
||||
},
|
||||
} as PluginTextReplacement;
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "volatile-text-transform",
|
||||
name: "Volatile Text Transform",
|
||||
}),
|
||||
register(api) {
|
||||
api.registerTextTransforms({
|
||||
get input() {
|
||||
inputReads += 1;
|
||||
if (inputReads > 1) {
|
||||
throw new Error("text transform input getter re-read");
|
||||
}
|
||||
return [inputReplacement];
|
||||
},
|
||||
get output() {
|
||||
outputReads += 1;
|
||||
if (outputReads > 1) {
|
||||
throw new Error("text transform output getter re-read");
|
||||
}
|
||||
return [outputReplacement];
|
||||
},
|
||||
} as PluginTextTransformRegistration);
|
||||
},
|
||||
});
|
||||
setActivePluginRegistry(registry.registry);
|
||||
|
||||
const transforms = resolveRuntimeTextTransforms();
|
||||
expect(applyPluginTextReplacements("red prompt", transforms?.input)).toBe("blue prompt");
|
||||
expect(applyPluginTextReplacements("all done", transforms?.output)).toBe("all finished");
|
||||
expect(inputReads).toBe(1);
|
||||
expect(outputReads).toBe(1);
|
||||
expect(inputFromReads).toBe(1);
|
||||
expect(inputToReads).toBe(1);
|
||||
expect(outputFromReads).toBe(1);
|
||||
expect(outputToReads).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1172,10 +1172,11 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
record: PluginRecord,
|
||||
transforms: PluginTextTransformsRegistration["transforms"],
|
||||
) => {
|
||||
if (
|
||||
(!transforms.input || transforms.input.length === 0) &&
|
||||
(!transforms.output || transforms.output.length === 0)
|
||||
) {
|
||||
const snapshot = snapshotTextTransforms(record, transforms);
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
if (!snapshot.input && !snapshot.output) {
|
||||
pushDiagnostic({
|
||||
level: "warn",
|
||||
pluginId: record.id,
|
||||
@@ -1187,12 +1188,85 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
registry.textTransforms.push({
|
||||
pluginId: record.id,
|
||||
pluginName: record.name,
|
||||
transforms,
|
||||
transforms: snapshot,
|
||||
source: record.source,
|
||||
rootDir: record.rootDir,
|
||||
});
|
||||
};
|
||||
|
||||
const snapshotTextTransforms = (
|
||||
record: PluginRecord,
|
||||
transforms: PluginTextTransformsRegistration["transforms"],
|
||||
): PluginTextTransformsRegistration["transforms"] | undefined => {
|
||||
let input: unknown;
|
||||
let output: unknown;
|
||||
try {
|
||||
input = transforms.input;
|
||||
output = transforms.output;
|
||||
const inputReplacements = snapshotTextReplacementList(record, input, "input");
|
||||
const outputReplacements = snapshotTextReplacementList(record, output, "output");
|
||||
if (!inputReplacements.ok || !outputReplacements.ok) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...(inputReplacements.value.length > 0 ? { input: inputReplacements.value } : {}),
|
||||
...(outputReplacements.value.length > 0 ? { output: outputReplacements.value } : {}),
|
||||
};
|
||||
} catch (error) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `text transform registration has unreadable fields: ${formatErrorMessage(error)}`,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const snapshotTextReplacementList = (
|
||||
record: PluginRecord,
|
||||
value: unknown,
|
||||
direction: "input" | "output",
|
||||
):
|
||||
| {
|
||||
ok: true;
|
||||
value: NonNullable<PluginTextTransformsRegistration["transforms"][typeof direction]>;
|
||||
}
|
||||
| { ok: false } => {
|
||||
if (value === undefined) {
|
||||
return { ok: true, value: [] };
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `text transform ${direction} replacements must be an array`,
|
||||
});
|
||||
return { ok: false };
|
||||
}
|
||||
const replacements: NonNullable<
|
||||
PluginTextTransformsRegistration["transforms"][typeof direction]
|
||||
> = [];
|
||||
for (const [index, replacement] of value.entries()) {
|
||||
try {
|
||||
replacements.push({
|
||||
from: replacement.from,
|
||||
to: replacement.to,
|
||||
});
|
||||
} catch (error) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `text transform ${direction} replacement ${index + 1} has unreadable fields: ${formatErrorMessage(error)}`,
|
||||
});
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
return { ok: true, value: replacements };
|
||||
};
|
||||
|
||||
const registerEmbeddingProviderForPlugin = (
|
||||
record: PluginRecord,
|
||||
adapter: EmbeddingProviderAdapter,
|
||||
|
||||
Reference in New Issue
Block a user