mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
This is to pave the way for introducing a new "Page" container, which will take over the page lifecycle currently burdening Session. The ultimate goal of that is to allow the Session to have multiple pages (mostly for better transitions between pages), which is hard to do now since the Session has so much state. This rename was aggressive, e.g. currentPage() -> currentFrame() so that, when the new Page container is added, you won't see "currentPage()" and wonder: "Does 'currentPage' mean the new Page container, or the Frame (which used to be called Page)".
113 lines
3.6 KiB
Zig
113 lines
3.6 KiB
Zig
const std = @import("std");
|
|
|
|
const lp = @import("lightpanda");
|
|
const log = lp.log;
|
|
|
|
const protocol = @import("protocol.zig");
|
|
const Server = @import("Server.zig");
|
|
|
|
pub const resource_list = [_]protocol.Resource{
|
|
.{
|
|
.uri = "mcp://page/html",
|
|
.name = "Page HTML",
|
|
.description = "The serialized HTML DOM of the current page",
|
|
.mimeType = "text/html",
|
|
},
|
|
.{
|
|
.uri = "mcp://page/markdown",
|
|
.name = "Page Markdown",
|
|
.description = "The token-efficient markdown representation of the current page",
|
|
.mimeType = "text/markdown",
|
|
},
|
|
};
|
|
|
|
pub fn handleList(server: *Server, req: protocol.Request) !void {
|
|
const id = req.id orelse return;
|
|
try server.sendResult(id, .{ .resources = &resource_list });
|
|
}
|
|
|
|
const ReadParams = struct {
|
|
uri: []const u8,
|
|
};
|
|
const Format = enum { html, markdown };
|
|
|
|
const ResourceStreamingResult = struct {
|
|
contents: []const struct {
|
|
uri: []const u8,
|
|
mimeType: []const u8,
|
|
text: StreamingText,
|
|
},
|
|
|
|
const StreamingText = struct {
|
|
frame: *lp.Frame,
|
|
format: Format,
|
|
|
|
pub fn jsonStringify(self: @This(), jw: *std.json.Stringify) !void {
|
|
try jw.beginWriteRaw();
|
|
try jw.writer.writeByte('"');
|
|
var escaped = protocol.JsonEscapingWriter.init(jw.writer);
|
|
switch (self.format) {
|
|
.html => lp.dump.root(self.frame.document, .{}, &escaped.writer, self.frame) catch |err| {
|
|
log.err(.mcp, "html dump failed", .{ .err = err });
|
|
return error.WriteFailed;
|
|
},
|
|
.markdown => lp.markdown.dump(self.frame.document.asNode(), .{}, &escaped.writer, self.frame) catch |err| {
|
|
log.err(.mcp, "markdown dump failed", .{ .err = err });
|
|
return error.WriteFailed;
|
|
},
|
|
}
|
|
try jw.writer.writeByte('"');
|
|
jw.endWriteRaw();
|
|
}
|
|
};
|
|
};
|
|
|
|
const ResourceUri = enum {
|
|
@"mcp://page/html",
|
|
@"mcp://page/markdown",
|
|
};
|
|
|
|
const resource_map = std.StaticStringMap(ResourceUri).initComptime(.{
|
|
.{ "mcp://page/html", .@"mcp://page/html" },
|
|
.{ "mcp://page/markdown", .@"mcp://page/markdown" },
|
|
});
|
|
|
|
pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
|
if (req.params == null or req.id == null) {
|
|
return server.sendError(req.id orelse .{ .integer = -1 }, .InvalidParams, "Missing params");
|
|
}
|
|
const req_id = req.id.?;
|
|
|
|
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{ .ignore_unknown_fields = true }) catch {
|
|
return server.sendError(req_id, .InvalidParams, "Invalid params");
|
|
};
|
|
|
|
const uri = resource_map.get(params.uri) orelse {
|
|
return server.sendError(req_id, .InvalidRequest, "Resource not found");
|
|
};
|
|
|
|
const frame = server.session.currentFrame() orelse {
|
|
return server.sendError(req_id, .FrameNotLoaded, "Page not loaded");
|
|
};
|
|
|
|
const format: Format = switch (uri) {
|
|
.@"mcp://page/html" => .html,
|
|
.@"mcp://page/markdown" => .markdown,
|
|
};
|
|
const mime_type: []const u8 = switch (uri) {
|
|
.@"mcp://page/html" => "text/html",
|
|
.@"mcp://page/markdown" => "text/markdown",
|
|
};
|
|
|
|
const result: ResourceStreamingResult = .{
|
|
.contents = &.{.{
|
|
.uri = params.uri,
|
|
.mimeType = mime_type,
|
|
.text = .{ .frame = frame, .format = format },
|
|
}},
|
|
};
|
|
server.sendResult(req_id, result) catch {
|
|
return server.sendError(req_id, .InternalError, "Failed to serialize resource content");
|
|
};
|
|
}
|