Show it to a client
Publish a typed view of one Session for clients.
A projection is the extension’s public view of one Session. Clients read it; they never open the extension’s tables. This page projects the saved links.
Add a projection
Section titled “Add a projection”import { Mycelium } from "@fungi.computer/mycelium";import { defineCollection, defineExtension,} from "@fungi.computer/shiitake/extensions";import * as Schema from "effect/Schema";
const Link = Schema.Struct({ url: Schema.String, title: Schema.String });const Bookmark = Schema.Struct({ ...Link.fields, sessionId: Schema.String });
/** Page 3's extension plus a projection: clients see the saved links. */export const bookmarks = defineExtension("bookmarks") .state({ bookmarks: defineCollection(Bookmark, { key: (b) => JSON.stringify([b.sessionId, b.url]), index: (b) => b.sessionId, }), }) .define({ prompt: (ctx, run) => { const saved = ctx.state.read((s) => s.bookmarks.page({ index: run.sessionId, limit: 32 }), ); const titles = saved.map((b) => b.title).join(", "); return [{ name: "saved", text: `Saved links: ${titles || "none"}` }]; }, tools: (ctx, run) => Mycelium.module({ id: "example.bookmarks", name: "bookmarks", operations: { add: Mycelium.operation({ replay: "safe", description: "Save a link; saving the same URL replaces it.", input: Link, execute: (link) => Promise.resolve( ctx.state.update((s) => { s.bookmarks.put({ ...link, sessionId: run.sessionId }); s.observe(run.sessionId); return null; }), ), }), }, }), projection: { schema: Schema.Array(Bookmark), read: (ctx, sessionId) => ctx.state.bookmarks.peekPage({ index: sessionId, limit: 32 }), }, });Two lines are new since the last page. projection declares the schema clients
decode and a read function that returns the value for one Session. It runs
while Shiitake builds a Session snapshot, so it reads with the synchronous
peekPage. An empty list is a real value: no bookmarks is useful to a client.
In the tool, s.observe(run.sessionId) tells Shiitake the Session changed.
Subscribed clients receive the new projection once the write commits.
Read it
Section titled “Read it”import { callTool, memoryHost } from "@fungi.computer/shiitake/testing";import { bookmarks } from "./extension.js";
/** The model saves a link; read the Session as a client would. */export async function projectionRun() { const host = await memoryHost({ extensions: [bookmarks], script: [ callTool("bookmarks.add", { url: "https://shiitake.shiit.app/", title: "Shiitake docs", }), "Saved.", ], }); try { await host.agent .session("reading") .send("Save the Shiitake docs.", { requestId: "save" }); return await host.settle("reading"); } finally { await host.close(); }}import { projectionRun } from "./run.js";
const snapshot = await projectionRun();console.log(snapshot.extensions.bookmarks);node --import tsx main.tssnapshot.extensions.bookmarks holds the slot a client sees: its status, the
extension’s revision and the projected value, with the saved link and its
Session.
Next: Let people act.