openclaw / src /plugins /runtime /runtime-llm.runtime.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4c76b0d verified
Raw
History Blame Contribute Delete
32.1 kB
// Runtime LLM tests cover plugin provider hooks inside the model runtime adapter.
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { resolveContextEngineCapabilities } from "../../agents/embedded-agent-runner/context-engine-capabilities.js";
import { runContextEngineMaintenance } from "../../agents/embedded-agent-runner/context-engine-maintenance.js";
import { buildAfterTurnRuntimeContext } from "../../agents/embedded-agent-runner/run/attempt-prompt-helpers.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { withPluginRuntimePluginScope } from "./gateway-request-scope.js";
import { createRuntimeLlm } from "./runtime-llm.runtime.js";
import type { RuntimeLogger } from "./types-core.js";
const hoisted = vi.hoisted(() => ({
acquireSimpleCompletionModelForAgent:
vi.fn<
typeof import("../../agents/simple-completion-runtime.js").acquireSimpleCompletionModelForAgent
>(),
completeWithPreparedSimpleCompletionModel: vi.fn(),
resolveSimpleCompletionSelectionForAgent: vi.fn(),
}));
vi.mock("../../agents/simple-completion-runtime.js", () => ({
acquireSimpleCompletionModelForAgent: hoisted.acquireSimpleCompletionModelForAgent,
completeWithPreparedSimpleCompletionModel: hoisted.completeWithPreparedSimpleCompletionModel,
resolveSimpleCompletionSelectionForAgent: hoisted.resolveSimpleCompletionSelectionForAgent,
}));
const cfg = {
agents: {
defaults: {
model: "openai/gpt-5.5",
},
},
} satisfies OpenClawConfig;
function createPreparedModel(
modelId = "gpt-5.5",
): Extract<
Awaited<ReturnType<typeof hoisted.acquireSimpleCompletionModelForAgent>>,
{ model: unknown }
> {
return {
async [Symbol.asyncDispose]() {},
selection: {
provider: "openai",
modelId,
agentDir: "/tmp/openclaw-agent",
},
model: {
provider: "openai",
id: modelId,
name: modelId,
api: "openai",
baseUrl: "https://fixture.invalid/v1",
input: ["text"],
reasoning: false,
contextWindow: 128_000,
maxTokens: 4096,
cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 0.2 },
},
auth: {
apiKey: "test-api-key",
source: "test",
mode: "api-key",
},
};
}
function createLogger(): RuntimeLogger {
return {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
}
type MockCalls = {
mock: { calls: unknown[][] };
};
const requireRecord = createRequireRecord("object", "expected-label");
function requireArray(value: unknown, label: string): unknown[] {
expect(Array.isArray(value), label).toBe(true);
return value as unknown[];
}
function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
for (const [key, value] of Object.entries(expected)) {
expect(record[key], key).toEqual(value);
}
}
function expectSingleCallFirstArg(
mock: MockCalls,
expected: Record<string, unknown>,
label = "mock first argument",
): Record<string, unknown> {
expect(mock.mock.calls).toHaveLength(1);
const [firstArg] = mock.mock.calls[0] ?? [];
const record = requireRecord(firstArg, label);
expectFields(record, expected);
return record;
}
function expectSingleLogPayload(
loggerMethod: MockCalls,
message: string,
expected: Record<string, unknown>,
): Record<string, unknown> {
expect(loggerMethod.mock.calls).toHaveLength(1);
const [actualMessage, payload] = loggerMethod.mock.calls[0] ?? [];
expect(actualMessage).toBe(message);
const payloadRecord = requireRecord(payload, "log payload");
expectFields(payloadRecord, expected);
return payloadRecord;
}
function primeCompletionMocks() {
hoisted.acquireSimpleCompletionModelForAgent.mockResolvedValue(createPreparedModel());
hoisted.resolveSimpleCompletionSelectionForAgent.mockImplementation(
(params: { modelRef?: string; agentId: string }) => {
if (!params.modelRef) {
return {
provider: "openai",
modelId: "gpt-5.5",
agentDir: `/tmp/${params.agentId}`,
};
}
const slash = params.modelRef.indexOf("/");
return {
provider: slash > 0 ? params.modelRef.slice(0, slash) : "openai",
modelId: slash > 0 ? params.modelRef.slice(slash + 1) : params.modelRef,
agentDir: `/tmp/${params.agentId}`,
};
},
);
hoisted.completeWithPreparedSimpleCompletionModel.mockResolvedValue({
content: [{ type: "text", text: "done" }],
responseModel: "gpt-5.5-2026-08-01",
stopReason: "stop",
usage: {
input: 11,
output: 7,
cacheRead: 5,
cacheWrite: 2,
total: 25,
cost: { total: 0.0042 },
},
});
}
describe("runtime.llm.complete", () => {
beforeEach(() => {
hoisted.acquireSimpleCompletionModelForAgent.mockReset();
hoisted.completeWithPreparedSimpleCompletionModel.mockReset();
hoisted.resolveSimpleCompletionSelectionForAgent.mockReset();
primeCompletionMocks();
});
it("binds context-engine completions to the active session agent", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "agent:ada:session:abc",
purpose: "context-engine.after-turn",
});
const result = await runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
purpose: "memory-maintenance",
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
cfg,
agentId: "ada",
allowBundledStaticCatalogFallback: true,
allowMissingApiKeyModes: ["aws-sdk"],
skipAgentDiscovery: true,
});
expect(result.agentId).toBe("ada");
expectFields(requireRecord(result.audit, "audit"), {
caller: { kind: "context-engine", id: "context-engine.after-turn" },
purpose: "memory-maintenance",
sessionKey: "agent:ada:session:abc",
});
});
it("passes the active auth profile to context-engine completions", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "agent:ada:session:abc",
authProfileId: "openai:claude@martian.engineering",
purpose: "context-engine.compaction",
});
await runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
cfg,
agentId: "ada",
preferredProfile: "openai:claude@martian.engineering",
allowBundledStaticCatalogFallback: true,
allowMissingApiKeyModes: ["aws-sdk"],
skipAgentDiscovery: true,
});
});
it("uses trusted context-engine attribution inside plugin runtime scope", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "agent:ada:session:abc",
purpose: "context-engine.after-turn",
});
const result = await withPluginRuntimePluginScope({ pluginId: "memory-core" }, () =>
runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
purpose: "memory-maintenance",
}),
);
expect(result.audit.caller).toEqual({
kind: "context-engine",
id: "context-engine.after-turn",
});
expect(result.agentId).toBe("ada");
});
it("does not fall back to the default agent for unbound active-session hooks", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "legacy-session",
purpose: "context-engine.after-turn",
});
await expect(
runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("not bound to an active session agent");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("does not trust a fallback-derived agent in the after-turn caller", async () => {
const attempt = {
sessionKey: "legacy-session",
config: cfg,
skillsSnapshot: undefined,
provider: "openai",
modelId: "gpt-5.5",
thinkLevel: "off" as const,
} satisfies Parameters<typeof buildAfterTurnRuntimeContext>[0]["attempt"];
const runtimeContext = buildAfterTurnRuntimeContext({
attempt,
workspaceDir: "/tmp/workspace",
agentDir: "/tmp/agent",
activeAgentId: "main",
});
await expect(
runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("not bound to an active session agent");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
const maintain = vi.fn(async (params: { runtimeContext?: typeof runtimeContext }) => {
await params.runtimeContext?.llm?.complete({
messages: [{ role: "user", content: "maintain" }],
});
return { changed: false, bytesFreed: 0, rewrittenEntries: 0 };
});
const maintenanceResult = await runContextEngineMaintenance({
contextEngine: {
info: { id: "test", name: "Test" },
maintain,
} as never,
sessionId: "legacy-session",
sessionKey: "legacy-session",
sessionFile: "legacy-session",
reason: "turn",
config: cfg,
agentId: "main",
runtimeContext,
});
expect(maintain).toHaveBeenCalledOnce();
expect(maintenanceResult).toBeUndefined();
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("accepts an explicitly trusted pre-fallback agent", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "legacy-session",
explicitAgentId: "ada",
purpose: "context-engine.after-turn",
});
const result = await runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
});
expect(result.agentId).toBe("ada");
});
it("fails closed for context-engine completions without any session agent", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
purpose: "context-engine.after-turn",
});
await expect(
runtimeContext.llm!.complete({
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("not bound to an active session agent");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("denies context-engine model overrides without owning plugin llm policy", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
await expect(
runtimeContext.llm!.complete({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("cannot override the target model");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("allows context-engine model overrides through the owning plugin llm policy", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: {
...cfg,
plugins: {
entries: {
"lossless-claw": {
llm: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4-mini", "minimax/MiniMax-M2.7"],
},
},
},
},
},
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
const result = await runtimeContext.llm!.complete({
agentId: "main",
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "summarize" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
agentId: "main",
modelRef: "openai/gpt-5.4-mini",
});
expectFields(requireRecord(result.audit, "audit"), {
caller: { kind: "context-engine", id: "context-engine.compaction" },
sessionKey: "agent:main:session:abc",
});
});
it("denies context-engine model overrides outside the owning plugin allowlist", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: {
...cfg,
plugins: {
entries: {
"lossless-claw": {
llm: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4-mini"],
},
},
},
},
},
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
await expect(
runtimeContext.llm!.complete({
model: "openai/gpt-5.5",
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow(
'model override "openai/gpt-5.5" is not allowlisted for plugin "lossless-claw"',
);
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("matches allowlist entries for provider-qualified model ids without doubling the provider prefix", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: {
...cfg,
plugins: {
entries: {
"lossless-claw": {
llm: {
allowModelOverride: true,
allowedModels: ["openrouter/gpt-5.4-mini"],
},
},
},
},
},
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
hoisted.acquireSimpleCompletionModelForAgent.mockResolvedValue(
createPreparedModel("openrouter/gpt-5.4-mini"),
);
hoisted.resolveSimpleCompletionSelectionForAgent.mockImplementation(
(params: { agentId: string }) => ({
provider: "openrouter",
modelId: "openrouter/gpt-5.4-mini",
agentDir: `/tmp/${params.agentId}`,
}),
);
await runtimeContext.llm!.complete({
agentId: "main",
model: "openrouter/gpt-5.4-mini",
messages: [{ role: "user", content: "summarize" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
agentId: "main",
modelRef: "openrouter/gpt-5.4-mini",
});
});
it("reports denials for provider-qualified model ids without doubling the provider prefix", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: {
...cfg,
plugins: {
entries: {
"lossless-claw": {
llm: {
allowModelOverride: true,
allowedModels: ["openrouter/gpt-5.4-mini"],
},
},
},
},
},
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
hoisted.resolveSimpleCompletionSelectionForAgent.mockImplementation(
(params: { agentId: string }) => ({
provider: "openrouter",
modelId: "openrouter/gpt-5.5",
agentDir: `/tmp/${params.agentId}`,
}),
);
let caught: unknown;
try {
await runtimeContext.llm!.complete({
model: "openrouter/gpt-5.5",
messages: [{ role: "user", content: "summarize" }],
});
} catch (error) {
caught = error;
}
const message = caught instanceof Error ? caught.message : String(caught);
expect(message).toContain('"openrouter/gpt-5.5"');
expect(message).not.toContain("openrouter/openrouter/");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("keeps context-engine attribution and host-derived policy inside plugin runtime scope", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: {
...cfg,
plugins: {
entries: {
"lossless-claw": {
llm: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4-mini"],
},
},
},
},
},
sessionKey: "agent:main:session:abc",
contextEnginePluginId: "lossless-claw",
purpose: "context-engine.compaction",
});
const result = await withPluginRuntimePluginScope({ pluginId: "spoofed-plugin" }, () =>
runtimeContext.llm!.complete({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "summarize" }],
caller: { kind: "plugin", id: "spoofed-plugin" },
} as Parameters<NonNullable<typeof runtimeContext.llm>["complete"]>[0] & {
caller: unknown;
}),
);
expect(result.audit.caller).toEqual({
kind: "context-engine",
id: "context-engine.compaction",
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
modelRef: "openai/gpt-5.4-mini",
});
});
it("allows the bound context-engine agent and denies cross-agent overrides", async () => {
const runtimeContext = resolveContextEngineCapabilities({
config: cfg,
sessionKey: "main",
purpose: "context-engine.compaction",
});
await runtimeContext.llm!.complete({
agentId: "main",
messages: [{ role: "user", content: "summarize" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
agentId: "main",
});
await expect(
runtimeContext.llm!.complete({
agentId: "worker",
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("cannot override the active session agent");
});
it("allows explicit agentId for non-session plugin calls", async () => {
const logger = createLogger();
const llm = createRuntimeLlm({
getConfig: () => cfg,
logger,
authority: {
allowAgentIdOverride: true,
allowModelOverride: true,
allowComplete: true,
},
});
await llm.complete({
agentId: "worker",
messages: [{ role: "user", content: "draft" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
cfg,
agentId: "worker",
});
});
it("ignores request auth profile preferences without a trusted authority binding", async () => {
const llm = createRuntimeLlm({
getConfig: () => cfg,
authority: {
allowComplete: true,
},
});
await llm.complete({
authProfileId: "openai:work",
messages: [{ role: "user", content: "draft" }],
} as Parameters<typeof llm.complete>[0] & { authProfileId: string });
const call = expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
cfg,
agentId: "main",
});
expect(call.preferredProfile).toBeUndefined();
});
it("allows host model overrides only when explicit authority allowlists the model", async () => {
const llm = createRuntimeLlm({
getConfig: () => cfg,
authority: {
caller: { kind: "host", id: "runtime-test" },
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4"],
allowComplete: true,
},
});
await llm.complete({
model: "openai/gpt-5.4",
messages: [{ role: "user", content: "Ping" }],
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
modelRef: "openai/gpt-5.4",
});
await expect(
llm.complete({
model: "openai/gpt-5.5",
messages: [{ role: "user", content: "Ping" }],
}),
).rejects.toThrow('model override "openai/gpt-5.5" is not allowlisted');
});
it("requires model overrides to satisfy host and plugin allowlists", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": {
llm: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4"],
},
},
},
},
}),
authority: {
allowComplete: true,
allowModelOverride: true,
allowedModels: ["openai/gpt-5.5"],
},
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({
model: "openai/gpt-5.5",
messages: [{ role: "user", content: "Ping" }],
}),
),
).rejects.toThrow(
'model override "openai/gpt-5.5" is not allowlisted for plugin "restricted-plugin"',
);
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("uses runtime-scoped config and the host preparation/dispatch path", async () => {
const logger = createLogger();
const llm = createRuntimeLlm({
getConfig: () => cfg,
logger,
authority: {
caller: { kind: "host", id: "runtime-test" },
allowComplete: true,
},
});
const result = await llm.complete({
messages: [
{ role: "system", content: "Be terse." },
{ role: "user", content: "Ping" },
],
temperature: 0.2,
maxTokens: 64,
reasoning: "ultra",
responseFormat: {
type: "json_schema",
json_schema: { name: "test_result", strict: true, schema: { type: "object" } },
},
purpose: "test-purpose",
});
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
cfg,
agentId: "main",
});
const completionArg = expectSingleCallFirstArg(
hoisted.completeWithPreparedSimpleCompletionModel,
{
cfg,
},
);
const context = requireRecord(completionArg.context, "completion context");
expect(context.systemPrompt).toBe("Be terse.");
const [message] = requireArray(context.messages, "completion messages");
expectFields(requireRecord(message, "completion message"), {
role: "user",
content: "Ping",
});
expectFields(requireRecord(completionArg.options, "completion options"), {
maxTokens: 64,
temperature: 0.2,
reasoning: "ultra",
responseFormat: {
type: "json_schema",
json_schema: { name: "test_result", strict: true, schema: { type: "object" } },
},
});
expectFields(requireRecord(result, "completion result"), {
text: "done",
provider: "openai",
model: "gpt-5.5",
responseModel: "gpt-5.5-2026-08-01",
stopReason: "stop",
});
expectFields(requireRecord(result.usage, "completion usage"), {
inputTokens: 11,
outputTokens: 7,
cacheReadTokens: 5,
cacheWriteTokens: 2,
totalTokens: 25,
costUsd: 0.0042,
});
const logPayload = expectSingleLogPayload(
logger.info as unknown as MockCalls,
"plugin llm completion",
{
caller: { kind: "host", id: "runtime-test" },
purpose: "test-purpose",
},
);
expectFields(requireRecord(logPayload.usage, "log usage"), { costUsd: 0.0042 });
});
it("preserves the completion options shape when reasoning is omitted", async () => {
const llm = createRuntimeLlm({
getConfig: () => cfg,
authority: { allowComplete: true },
});
await llm.complete({
messages: [{ role: "user", content: "Ping" }],
});
const completionArg = expectSingleCallFirstArg(
hoisted.completeWithPreparedSimpleCompletionModel,
{ cfg },
);
expect(requireRecord(completionArg.options, "completion options")).not.toHaveProperty(
"reasoning",
);
});
it("uses scoped plugin identity and ignores caller-shaped spoofing input", async () => {
const logger = createLogger();
const llm = createRuntimeLlm({
getConfig: () => cfg,
logger,
authority: {
caller: { kind: "host", id: "ignored-host" },
allowComplete: true,
},
});
const result = await withPluginRuntimePluginScope({ pluginId: "trusted-plugin" }, () =>
llm.complete({
messages: [{ role: "user", content: "Ping" }],
purpose: "identity-test",
caller: { kind: "plugin", id: "spoofed-plugin" },
} as Parameters<typeof llm.complete>[0] & { caller: unknown }),
);
expect(result.audit.caller).toEqual({ kind: "plugin", id: "trusted-plugin" });
expectSingleLogPayload(logger.info as unknown as MockCalls, "plugin llm completion", {
caller: { kind: "plugin", id: "trusted-plugin" },
purpose: "identity-test",
});
});
it("denies plugin model overrides by default", async () => {
const llm = createRuntimeLlm({
getConfig: () => cfg,
authority: {
allowComplete: true,
},
});
await expect(
withPluginRuntimePluginScope({ pluginId: "plain-plugin" }, () =>
llm.complete({
model: "openai/gpt-5.4",
messages: [{ role: "user", content: "Ping" }],
}),
),
).rejects.toThrow("cannot override the target model");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("denies plugin agent overrides by default and allows them only when configured", async () => {
const denied = createRuntimeLlm({
getConfig: () => cfg,
authority: {
allowComplete: true,
},
});
await expect(
withPluginRuntimePluginScope({ pluginId: "plain-plugin" }, () =>
denied.complete({
agentId: "worker",
messages: [{ role: "user", content: "Ping" }],
}),
),
).rejects.toThrow("cannot override the target agent");
const allowed = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"trusted-plugin": {
llm: {
allowAgentIdOverride: true,
},
},
},
},
}),
authority: {
allowComplete: true,
},
});
await withPluginRuntimePluginScope({ pluginId: "trusted-plugin" }, () =>
allowed.complete({
agentId: "worker",
messages: [{ role: "user", content: "Ping" }],
}),
);
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
agentId: "worker",
});
});
it("allows plugin model overrides only when configured and allowlisted", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"trusted-plugin": {
llm: {
allowModelOverride: true,
allowedModels: ["openai/gpt-5.4"],
},
},
},
},
}),
authority: {
allowComplete: true,
},
});
await withPluginRuntimePluginScope({ pluginId: "trusted-plugin" }, () =>
llm.complete({
model: "openai/gpt-5.4",
messages: [{ role: "user", content: "Ping" }],
}),
);
expectSingleCallFirstArg(hoisted.acquireSimpleCompletionModelForAgent, {
agentId: "main",
modelRef: "openai/gpt-5.4",
});
await expect(
withPluginRuntimePluginScope({ pluginId: "trusted-plugin" }, () =>
llm.complete({
model: "openai/gpt-5.6",
messages: [{ role: "user", content: "Ping" }],
}),
),
).rejects.toThrow('model override "openai/gpt-5.6" is not allowlisted');
});
it("keeps the shipped model allowlist scoped to explicit overrides", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": {
llm: { allowedModels: ["anthropic/claude-haiku-4-5"] },
},
},
},
}),
authority: { allowComplete: true },
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({ messages: [{ role: "user", content: "Ping" }] }),
),
).resolves.toMatchObject({ text: "done" });
});
it("applies a completion model allowlist to the host-resolved default", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": {
llm: { allowedCompletionModels: ["anthropic/claude-haiku-4-5"] },
},
},
},
}),
authority: { allowComplete: true },
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({ messages: [{ role: "user", content: "Ping" }] }),
),
).rejects.toThrow('model "openai/gpt-5.5" is not allowlisted for completions');
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it("applies the completion model allowlist to explicit overrides too", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": {
llm: {
allowModelOverride: true,
allowedModels: ["*"],
allowedCompletionModels: ["openai/gpt-5.4"],
},
},
},
},
}),
authority: { allowComplete: true },
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({
model: "openai/gpt-5.6",
messages: [{ role: "user", content: "Ping" }],
}),
),
).rejects.toThrow('model "openai/gpt-5.6" is not allowlisted for completions');
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
});
it.each([[[]], [["not-a-canonical-model-ref"]]])(
"fails closed for an unusable completion allowlist %j",
async (allowedCompletionModels) => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": { llm: { allowedCompletionModels } },
},
},
}),
authority: { allowComplete: true },
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({ messages: [{ role: "user", content: "Ping" }] }),
),
).rejects.toThrow("completion model allowlist has no valid models");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
},
);
it("accepts an explicit wildcard completion allowlist", async () => {
const llm = createRuntimeLlm({
getConfig: () => ({
...cfg,
plugins: {
entries: {
"restricted-plugin": { llm: { allowedCompletionModels: ["*"] } },
},
},
}),
authority: { allowComplete: true },
});
await expect(
withPluginRuntimePluginScope({ pluginId: "restricted-plugin" }, () =>
llm.complete({ messages: [{ role: "user", content: "Ping" }] }),
),
).resolves.toMatchObject({ text: "done" });
});
it("denies completions when runtime authority disables the capability", async () => {
const logger = createLogger();
const llm = createRuntimeLlm({
getConfig: () => cfg,
logger,
authority: {
allowComplete: false,
denyReason: "not trusted",
},
});
await expect(
llm.complete({
messages: [{ role: "user", content: "Ping" }],
}),
).rejects.toThrow("Plugin LLM completion denied: not trusted");
expect(hoisted.acquireSimpleCompletionModelForAgent).not.toHaveBeenCalled();
expectSingleLogPayload(logger.warn as unknown as MockCalls, "plugin llm completion denied", {
reason: "not trusted",
});
});
});