2026-05-19 10:13:52 +09:00
|
|
|
import { describe, test, expect } from "bun:test"
|
2026-05-22 00:06:56 +09:00
|
|
|
import { createAgentToolAllowlist } from "../shared/permission-compat"
|
|
|
|
|
import { READ_ENABLED } from "../tools/look-at/look-at-prompt"
|
2026-05-19 10:13:52 +09:00
|
|
|
import { createMultimodalLookerAgent } from "./multimodal-looker"
|
|
|
|
|
|
2026-05-22 00:06:56 +09:00
|
|
|
function extractAvailableToolClaims(prompt: string): readonly string[] {
|
|
|
|
|
const availableToolsLine = prompt
|
|
|
|
|
.split("\n")
|
|
|
|
|
.find((line) => line.toLowerCase().includes("available tools"))
|
|
|
|
|
if (availableToolsLine === undefined) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tools: string[] = []
|
|
|
|
|
for (const match of availableToolsLine.matchAll(/['`]([^'`]+)['`]/g)) {
|
|
|
|
|
const toolName = match[1]
|
|
|
|
|
if (toolName !== undefined) {
|
|
|
|
|
tools.push(toolName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [...new Set(tools)].sort()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function allowedToolNames(
|
|
|
|
|
toolAllowlist: ReturnType<typeof createAgentToolAllowlist>
|
|
|
|
|
): readonly string[] {
|
|
|
|
|
return Object.entries(toolAllowlist.permission)
|
|
|
|
|
.filter(([toolName, permission]) => toolName !== "*" && permission === "allow")
|
|
|
|
|
.map(([toolName]) => toolName)
|
|
|
|
|
.sort()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createLookAtRuntimeToolAllowlist(): ReturnType<typeof createAgentToolAllowlist> {
|
|
|
|
|
return createAgentToolAllowlist(READ_ENABLED ? ["read"] : [])
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 10:13:52 +09:00
|
|
|
describe("createMultimodalLookerAgent", () => {
|
2026-05-22 00:06:56 +09:00
|
|
|
test("prompt available tool claims match the look_at runtime allowlist", () => {
|
|
|
|
|
// given
|
|
|
|
|
const agent = createMultimodalLookerAgent("openai/gpt-5-nano")
|
|
|
|
|
const runtimeToolAllowlist = createLookAtRuntimeToolAllowlist()
|
|
|
|
|
|
|
|
|
|
// when
|
|
|
|
|
const prompt = typeof agent.prompt === "string" ? agent.prompt : ""
|
|
|
|
|
const promptToolClaims = extractAvailableToolClaims(prompt)
|
|
|
|
|
const runtimeToolNames = allowedToolNames(runtimeToolAllowlist)
|
|
|
|
|
|
|
|
|
|
// then
|
|
|
|
|
expect(promptToolClaims).toEqual(runtimeToolNames)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("prompt denies tool use to prevent death loop on small VL models", () => {
|
2026-05-19 10:13:52 +09:00
|
|
|
// given
|
|
|
|
|
const agent = createMultimodalLookerAgent("openai/gpt-5-nano")
|
|
|
|
|
|
|
|
|
|
// when
|
|
|
|
|
const prompt = typeof agent.prompt === "string" ? agent.prompt : ""
|
2026-05-22 00:06:56 +09:00
|
|
|
const normalizedPrompt = prompt.toLowerCase()
|
2026-05-19 10:13:52 +09:00
|
|
|
|
|
|
|
|
// then
|
2026-05-22 00:06:56 +09:00
|
|
|
expect(normalizedPrompt).toContain("never")
|
|
|
|
|
expect(normalizedPrompt).toContain("tools")
|
|
|
|
|
expect(extractAvailableToolClaims(prompt)).toEqual([])
|
2026-05-19 10:13:52 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("prompt instructs the agent never to call other tools", () => {
|
|
|
|
|
// given
|
|
|
|
|
const agent = createMultimodalLookerAgent("openai/gpt-5-nano")
|
|
|
|
|
|
|
|
|
|
// when
|
|
|
|
|
const prompt = typeof agent.prompt === "string" ? agent.prompt : ""
|
|
|
|
|
|
|
|
|
|
// then
|
|
|
|
|
expect(prompt.toLowerCase()).toContain("never")
|
|
|
|
|
})
|
|
|
|
|
})
|