This page is for agent builders who already have an AI SDK, LangChain, Mastra, LlamaIndex.TS, LangGraph.js, CopilotKit, or Cloudflare Agents loop and need a spreadsheet tool that can do more than return a screenshot.
@bilig/headless gives the agent a WorkPaper object: sheets, addresses,
formulas, computed readback, and JSON persistence. The framework wrapper should
stay thin. Keep the workbook behavior in ordinary Node functions, then expose
those functions through the tool shape your agent framework expects.
generateText() SmokeRun this first if you use the Vercel AI SDK and want to see the complete
generateText() path execute WorkPaper tools:
git clone https://github.com/proompteng/bilig.git
cd bilig/examples/headless-workpaper
npm install
npm run agent:ai-sdk-generate-text
The smoke test uses the real ai package: generateText(), tool(),
stepCountIs(), and MockLanguageModelV3 from ai/test. The mock model keeps
the example provider-free. The WorkPaper tool execution is real TypeScript:
readWorkPaperSummary reads Summary!A1:B5, then setWorkPaperInputCell
writes Inputs!B3 = 0.4, recalculates dependent formulas, serializes the
document, restores it, and returns structured readback.
Passing output includes:
{
"apiShape": "AI SDK generateText -> tool -> execute",
"modelCallCount": 2,
"toolNames": ["readWorkPaperSummary", "setWorkPaperInputCell"],
"writeResult": {
"editedCell": "Inputs!B3",
"before": { "expectedArr": 60000, "targetGap": -34000 },
"after": { "expectedArr": 96000, "targetGap": 5600 },
"checks": {
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true
}
}
}
Inspect the runnable file here:
examples/headless-workpaper/ai-sdk-generate-text-tool-smoke.ts.
streamText() SmokeUse this command when your agent path streams model output:
git clone https://github.com/proompteng/bilig.git
cd bilig/examples/headless-workpaper
npm install
npm run agent:ai-sdk-stream-text
The smoke test uses the real streamText() API, tool() wrappers, and
simulateReadableStream() from ai. The deterministic MockLanguageModelV3
streams two WorkPaper tool calls, the AI SDK executes those tools, and the
second model step streams the final answer. The WorkPaper behavior is the same
as the generateText() smoke: read Summary!A1:B5, write
Inputs!B3 = 0.4, recalculate, serialize, restore, and verify the restored
summary.
Passing output includes:
{
"apiShape": "AI SDK streamText -> tool -> execute",
"modelStreamCallCount": 2,
"streamChunkTypes": ["tool-call", "tool-result", "tool-call", "tool-result", "text-delta", "text-delta"],
"writeResult": {
"editedCell": "Inputs!B3",
"before": { "expectedArr": 60000, "targetGap": -34000 },
"after": { "expectedArr": 96000, "targetGap": 5600 },
"checks": {
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true
}
}
}
Inspect the runnable file here:
examples/headless-workpaper/ai-sdk-stream-text-tool-smoke.ts.
Run the dependency-free adapter example from a clean checkout:
git clone https://github.com/proompteng/bilig.git
cd bilig/examples/headless-workpaper
npm install
npm run agent:framework-adapters
The script builds the same workbook once per adapter family and exposes the same operations in the shapes those frameworks expect:
readWorkPaperSummary and setWorkPaperInputCell for an AI SDK-style tool
map with inputSchema and executeread_workpaper_summary and set_workpaper_input_cell for a
LangChain-style tool list with schema and invokecreateTool({ id, inputSchema, outputSchema, execute })tool(fn, { parameters }) / FunctionTool style functionsToolNode-style dispatch over LangChain toolsuseCopilotAction({ parameters, handler }) action objectsAIChatAgent / streamText({ tools }) style toolsThe example installs zod for real schemas, but it does not install the agent
frameworks. That is deliberate. It keeps the WorkPaper contract visible and
avoids hiding workbook logic behind framework setup.
The mutating tool edits Inputs!B3 and then verifies the dependent summary
formulas:
{
"editedCell": "Inputs!B3",
"before": {
"expectedCustomers": 5,
"expectedArr": 60000,
"expansionArr": 66000,
"targetGap": -34000
},
"after": {
"expectedCustomers": 8,
"expectedArr": 96000,
"expansionArr": 105600,
"targetGap": 5600
},
"checks": {
"previousValue": 0.25,
"newValue": 0.4,
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true
}
}
That is the useful part for agents. The tool result names the exact edited cell, returns before and after computed values, preserves formula contracts, serializes the workbook, restores it, and proves the restored output still matches the post-write state.
The runnable adapter includes a LangChain-shaped smoke that keeps the tool output structured. The model can decide to call the tools, but the WorkPaper functions return JSON evidence instead of prose:
const tools = createLangChainTools(createWorkPaperTools(workbook))
const readResult = requireTool(tools, 'read_workpaper_summary').invoke({
range: 'Summary!A1:B5',
})
const writeResult = requireTool(tools, 'set_workpaper_input_cell').invoke({
sheetName: 'Inputs',
address: 'B3',
value: 0.4,
})
return {
readResult,
writeResult: {
editedCell: writeResult.editedCell,
before: writeResult.before,
after: writeResult.after,
checks: writeResult.checks,
},
}
Those four fields are the important LangChain.js contract: editedCell says
what changed, before and after prove the dependent formulas recalculated,
and checks records persistence and restored-readback assertions. Keep that
shape as the tool return value; do not collapse it into a sentence like
“spreadsheet updated successfully.”
Keep the adapter boring:
The framework wrapper can then expose those functions with the local tool shape:
inputSchema and execute, schema and invoke, parameters and handler,
or a ToolNode-style dispatch wrapper. The workbook behavior should not care
which framework called it.
Official docs for the framework shapes:
tool reference:
https://ai-sdk.dev/docs/reference/ai-sdk-core/toolstreamText reference:
https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-textcreateTool():
https://mastra.ai/reference/tools/create-toolToolNode:
https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.prebuilt.ToolNode.htmluseCopilotAction:
https://docs.copilotkit.ai/reference/hooks/useCopilotActionFramework-specific WorkPaper pages:
examples/headless-workpaper/agent-framework-adapters.tsexamples/headless-workpaper/README.md#agent-framework-adaptersdocs/agent-workpaper-tool-calling-recipe.mdexamples/headless-workpaper/agent-writeback-verification.tsUse this pattern when the agent needs to change a forecast, pricing model, pipeline summary, budget check, or workbook-backed business rule and then prove the formulas reacted. If the tool only says “I updated the spreadsheet” without computed readback, it is not enough for production workflows.
Start with the adapter command above. If it saves you an agent-tooling spike, star the repository so the next person searching for spreadsheet tools can find it: https://github.com/proompteng/bilig/stargazers.
If it almost matches but a gap blocks adoption, use the adoption blocker form: https://github.com/proompteng/bilig/discussions/new?category=general.