-- Self-check for panto-web. Run with: panto lua _selfcheck.lua local registered = {} local json = require("panto").ext.json package.loaded["panto"] = { ext = { register_tool = function(tool) registered[#registered + 1] = tool end, json = json, }, } local here = debug.getinfo(1, "S").source:match("^@(.*)/[^/]+$") or "." package.path = here .. "/panto-web/?.lua;" .. here .. "/panto-web/?/init.lua;" .. package.path local web = require("init") assert(web.name == "web", "entry name") web.activate() assert(registered[1].name == "web.fetch", "fetch tool registered") assert(registered[2].name == "web.search", "search tool registered") assert(web.on_fetch({}):find("non-empty", 1, true), "fetch rejects missing URL") assert(web.on_fetch({ url = "" }):find("non-empty", 1, true), "fetch rejects empty URL") assert(web.on_search({ query = "" }):find("non-empty", 1, true), "search rejects empty query") assert(web.on_search({ query = "ok", num_results = 0 }):find("integer", 1, true), "search rejects count") local markdown = web.build_request(7, "tools/call", web.tool_params("markdown", { url = "https://example.com" })) assert(markdown.id == 7 and markdown.params.name == "markdown", "markdown MCP request") local evaluate = web.build_request(8, "tools/call", web.tool_params("evaluate", { script = "1 + 1" })) assert(evaluate.id == 8 and evaluate.params.name == "evaluate", "evaluate MCP request") -- Replies must resume only the coroutine waiting for their id. local resumed = {} local fake = { pending = {}, buf = "" } for _, id in ipairs({ 1, 2 }) do local co = coroutine.create(function() coroutine.yield() resumed[#resumed + 1] = id end) assert(coroutine.resume(co)) fake.pending[id] = { co = co } end assert(not web.feed(fake, json.encode({ jsonrpc = "2.0", id = 2, result = {} }) .. "\n")) assert(#resumed == 1 and resumed[1] == 2 and fake.pending[1], "response routed by id") assert(not web.feed(fake, json.encode({ jsonrpc = "2.0", id = 1, result = {} }) .. "\n")) assert(#resumed == 2 and resumed[2] == 1, "second response routed by id") local body = json.decode(web.exa_body("Lightpanda", 3)) assert(body.query == "Lightpanda" and body.numResults == 3 and body.type == "auto", "Exa request fields") assert(body.contents.highlights == true, "Exa highlights requested") local mcp_body = json.decode(web.exa_mcp_body("Lightpanda", 3)) assert(mcp_body.params.name == "web_search_exa", "keyless Exa MCP tool") assert(mcp_body.params.arguments.query == "Lightpanda" and mcp_body.params.arguments.numResults == 3, "keyless Exa MCP arguments") local mcp_payload = json.encode({ jsonrpc = "2.0", id = 1, result = { content = { { type = "text", text = "Title: Result" } }, isError = false, } }) assert(web.exa_mcp_result("event: message\ndata: " .. mcp_payload .. "\n") == "Title: Result", "keyless Exa SSE result") local fake_key = "fake-secret-key" assert(not web.exa_body("Lightpanda", 3):find(fake_key, 1, true), "key absent from Exa body") assert(not web.scrub("failed with " .. fake_key, fake_key):find(fake_key, 1, true), "key redacted from output") local oversized = web.truncate(string.rep("x", 101 * 1024)) assert(#oversized < 101 * 1024 and oversized:find("truncated", 1, true), "output capped") local text = assert(web.result_text({ content = { { type = "text", text = "first" }, { type = "text", text = "second" }, } })) assert(text == "first\nsecond", "MCP text extracted") local _, tool_err = web.result_text({ isError = true, content = { { type = "text", text = "failed" } } }) assert(tool_err == "failed", "MCP tool error extracted") print("panto-web selfcheck: OK")