@@ -464,6 +464,59 @@ defmodule PtcRunner.UpstreamRuntimeTest do
464464 end
465465 end
466466
467+ test "large upstream pages are usable by the program while the in-eval tool ledger is bounded" do
468+ script = write_large_page_stdio_fixture! ( )
469+ config = stdio_fixture_config ( script )
470+
471+ { :ok , runtime } = Runtime . start_link ( config: config , catalog_snapshot_mode: :frozen )
472+
473+ program = """
474+ (reduce
475+ +
476+ 0
477+ (map
478+ (fn [i]
479+ (let [r (tool/call 'fixture/page {:page i})]
480+ (if (r :ok)
481+ (count (get (r :value) "rows"))
482+ 0)))
483+ (range 20)))
484+ """
485+
486+ try do
487+ { { :ok , step } , records } =
488+ Eval . run_lisp_with_records ( runtime , program ,
489+ max_tool_call_result_bytes: 300 ,
490+ max_heap: 350_000
491+ )
492+
493+ assert step . return == 100_000
494+
495+ assert length ( step . tool_calls ) == 20
496+ assert Enum . all? ( step . tool_calls , & ( & 1 [ :name ] == "call" ) )
497+ assert Enum . all? ( step . tool_calls , & ( & 1 [ :result_truncated ] == true ) )
498+
499+ assert Enum . all? (
500+ step . tool_calls ,
501+ & ( is_binary ( & 1 [ :result ] ) and byte_size ( & 1 [ :result ] ) <= 300 )
502+ )
503+
504+ assert length ( records ) == 20
505+
506+ assert Enum . all? (
507+ records ,
508+ & match? ( % { "server" => "fixture" , "tool" => "page" , "status" => "ok" } , & 1 )
509+ )
510+
511+ assert Enum . all? (
512+ records ,
513+ & ( get_in ( & 1 , [ "result_overview" , "shape" ] ) == "map keys=[\" rows\" ] count=1" )
514+ )
515+ after
516+ Runtime . stop ( runtime )
517+ end
518+ end
519+
467520 test "root runtime can call an MCP HTTP upstream" do
468521 { :ok , server } = start_mcp_http_fixture ( )
469522
@@ -1221,6 +1274,60 @@ defmodule PtcRunner.UpstreamRuntimeTest do
12211274 path
12221275 end
12231276
1277+ defp write_large_page_stdio_fixture! do
1278+ path =
1279+ Path . join (
1280+ System . tmp_dir! ( ) ,
1281+ "ptc_runner_mcp_large_page_fixture_#{ System . unique_integer ( [ :positive ] ) } .exs"
1282+ )
1283+
1284+ File . write! ( path , ~S'''
1285+ tools = [
1286+ %{
1287+ "name" => "page",
1288+ "description" => "Return one large numeric page",
1289+ "inputSchema" => %{
1290+ "type" => "object",
1291+ "required" => ["page"],
1292+ "properties" => %{"page" => %{"type" => "integer"}}
1293+ }
1294+ }
1295+ ]
1296+
1297+ page_rows = Enum.to_list(1..5_000)
1298+
1299+ for line <- IO.stream(:stdio, :line) do
1300+ {:ok, frame} = Jason.decode(String.trim(line))
1301+ id = frame["id"]
1302+
1303+ response =
1304+ case frame["method"] do
1305+ "initialize" ->
1306+ %{"jsonrpc" => "2.0", "id" => id, "result" => %{"capabilities" => %{}}}
1307+
1308+ "notifications/initialized" ->
1309+ nil
1310+
1311+ "tools/list" ->
1312+ %{"jsonrpc" => "2.0", "id" => id, "result" => %{"tools" => tools}}
1313+
1314+ "tools/call" ->
1315+ %{
1316+ "jsonrpc" => "2.0",
1317+ "id" => id,
1318+ "result" => %{"structuredContent" => %{"rows" => page_rows}}
1319+ }
1320+ end
1321+
1322+ if response do
1323+ IO.puts(Jason.encode!(response))
1324+ end
1325+ end
1326+ ''' )
1327+
1328+ path
1329+ end
1330+
12241331 defp stdio_fixture_config ( script ) do
12251332 # Derive Jason's ebin from the loaded module rather than hard-coding
12261333 # `_build/test/lib/jason/ebin`, so the spawned `elixir` finds Jason even
0 commit comments