










@@ -306,6 +306,92 @@ describe("runtime.llm.complete", () => {
306306expect(hoisted.prepareSimpleCompletionModelForAgent).not.toHaveBeenCalled();
307307});
308308309+it("matches allowlist entries for provider-qualified model ids without doubling the provider prefix", async () => {
310+const runtimeContext = resolveContextEngineCapabilities({
311+config: {
312+ ...cfg,
313+plugins: {
314+entries: {
315+"lossless-claw": {
316+llm: {
317+allowModelOverride: true,
318+allowedModels: ["openrouter/gpt-5.4-mini"],
319+},
320+},
321+},
322+},
323+},
324+sessionKey: "agent:main:session:abc",
325+contextEnginePluginId: "lossless-claw",
326+purpose: "context-engine.compaction",
327+});
328+329+hoisted.prepareSimpleCompletionModelForAgent.mockResolvedValue(
330+createPreparedModel("openrouter/gpt-5.4-mini"),
331+);
332+hoisted.resolveSimpleCompletionSelectionForAgent.mockImplementation(
333+(params: { agentId: string }) => ({
334+provider: "openrouter",
335+modelId: "openrouter/gpt-5.4-mini",
336+agentDir: `/tmp/${params.agentId}`,
337+}),
338+);
339+340+await runtimeContext.llm!.complete({
341+agentId: "main",
342+model: "openrouter/gpt-5.4-mini",
343+messages: [{ role: "user", content: "summarize" }],
344+});
345+346+expectSingleCallFirstArg(hoisted.prepareSimpleCompletionModelForAgent, {
347+agentId: "main",
348+modelRef: "openrouter/gpt-5.4-mini",
349+});
350+});
351+352+it("reports denials for provider-qualified model ids without doubling the provider prefix", async () => {
353+const runtimeContext = resolveContextEngineCapabilities({
354+config: {
355+ ...cfg,
356+plugins: {
357+entries: {
358+"lossless-claw": {
359+llm: {
360+allowModelOverride: true,
361+allowedModels: ["openrouter/gpt-5.4-mini"],
362+},
363+},
364+},
365+},
366+},
367+sessionKey: "agent:main:session:abc",
368+contextEnginePluginId: "lossless-claw",
369+purpose: "context-engine.compaction",
370+});
371+372+hoisted.resolveSimpleCompletionSelectionForAgent.mockImplementation(
373+(params: { agentId: string }) => ({
374+provider: "openrouter",
375+modelId: "openrouter/gpt-5.5",
376+agentDir: `/tmp/${params.agentId}`,
377+}),
378+);
379+380+let caught: unknown;
381+try {
382+await runtimeContext.llm!.complete({
383+model: "openrouter/gpt-5.5",
384+messages: [{ role: "user", content: "summarize" }],
385+});
386+} catch (error) {
387+caught = error;
388+}
389+const message = caught instanceof Error ? caught.message : String(caught);
390+expect(message).toContain('"openrouter/gpt-5.5"');
391+expect(message).not.toContain("openrouter/openrouter/");
392+expect(hoisted.prepareSimpleCompletionModelForAgent).not.toHaveBeenCalled();
393+});
394+309395it("keeps context-engine attribution and host-derived policy inside plugin runtime scope", async () => {
310396const runtimeContext = resolveContextEngineCapabilities({
311397config: {
이 콘텐츠는 인셔셔RSS(RSS 리더)가 자동으로 집계한 것으로 읽기 참고용입니다. 원문 출처 — 저작권은 원저작자에게 있습니다.