Skip to content

feat(documents): agent tools read_workbook / read_workbook_cell / write_workbook#937

Open
mimeding wants to merge 7 commits intoosaurus-ai:mainfrom
mimeding:feat/workbook-agent-tools
Open

feat(documents): agent tools read_workbook / read_workbook_cell / write_workbook#937
mimeding wants to merge 7 commits intoosaurus-ai:mainfrom
mimeding:feat/workbook-agent-tools

Conversation

@mimeding
Copy link
Copy Markdown
Contributor

@mimeding mimeding commented Apr 24, 2026

Stacked on #936, which carries #929 and #927 until the lower document stack merges. This branch has been rebased onto current origin/main (fd2ffece, #1015). Reviewers should focus on the workbook agent tools and the small strict-lint rebase cleanup; unrelated CI/TTS/tool-timeout commits from the stale branch were intentionally dropped during rebase.

Business rationale

Reading and writing XLSX in the registry is only useful for agents once they can invoke narrow, folder-scoped tools to inspect a workbook, read a targeted cell, and write a modified workbook back to disk. These tools close the agent-facing loop for spreadsheet workflows while preserving the harness principle: local, structured, path-contained file operations that do not require the user to copy/paste business data out of the file.

Coding rationale

The tools sit in the folder tool surface because workbook reads and writes are rooted in a user-selected working directory and must reuse FolderToolHelpers.resolvePath for containment. The tools consume the Workbook model and DocumentFormatRegistry adapters/emitters rather than opening XLSX libraries directly, so read/write fidelity stays behind the document abstraction. The tool list keeps main's lean filesystem posture: this PR adds only ReadWorkbookTool, ReadWorkbookCellTool, and WriteWorkbookTool, not the older file-mutation helpers that main intentionally removed. Schema responses use field-pointed error envelopes so small local models can self-correct without receiving the whole workbook unless they ask for it.

What changed

Validation

  • git fetch origin && git rebase origin/main — completed after resolving Package.resolved and keeping main's lean folder tool list plus workbook tools only.
  • swift build --package-path Packages/OsaurusCore — passed.
  • swift build --package-path Packages/OsaurusCore -c release — passed.
  • swift test --package-path Packages/OsaurusCore — passed, 1476 tests in 199 suites, with sandbox integration tests skipped by their normal environment gate.
  • xcrun swift-format lint --strict on every touched Swift file — passed.
  • swiftlint lint --strict on every touched Swift file — passed.
  • git diff --check origin/main...HEAD — passed.
  • CLI gate skipped because this slice does not touch Packages/OsaurusCLI.

Non-scope

  • No CSV/TSV streaming tools.
  • No structured attachment UI plumbing.
  • No workbook style/formatting fidelity.
  • No legacy .xls or ODS support.
  • No extra general file mutation tools.

Residual risks

The tools intentionally expose a conservative workbook surface. Large or style-heavy spreadsheets still need follow-up work for streaming, formatting, and selective row/column operations. The lower stack is still present until #927/#929/#936 merge, so reviewers may see carried commits in the GitHub diff until the stack collapses.

@mimeding
Copy link
Copy Markdown
Contributor Author

Realignment update: I am moving the workbook tool surface to osaurus-xlsx. The summary-plus-lookup split, path containment checks, invalid-args envelopes, and writeback verification remain useful, but they should be plugin tools rather than registrations from FolderToolFactory.buildCoreTools.

@mimeding mimeding force-pushed the feat/workbook-agent-tools branch 3 times, most recently from 3e3513a to d5c9dbf Compare April 28, 2026 22:07
@mimeding mimeding force-pushed the feat/workbook-agent-tools branch 2 times, most recently from 6895f06 to 2dd797d Compare May 1, 2026 03:53
@mimeding mimeding marked this pull request as ready for review May 1, 2026 05:10
@mimeding mimeding force-pushed the feat/workbook-agent-tools branch from 4e7d2a2 to 225b933 Compare May 1, 2026 20:43
mimeding and others added 7 commits May 3, 2026 19:42
…ntParser through the registry

Migrates the three ingress paths already handled by DocumentParser onto
the adapter surface introduced in the foundations PR, without changing
any user-observable behaviour. parseAll now consults the registry first
and falls back to its existing switch for anything an adapter hasn't
claimed or has declined — specifically image-only PDFs, which continue
to render via the legacy fallback until the layout-aware PDF rework
lands.

- PlainTextAdapter wraps the existing UTF-8 / ISO-Latin-1 retry path
  and the 500K-character truncation marker so the legacy behaviour
  stays byte-identical.
- PDFAdapter wraps PDFKit text extraction; it throws emptyContent when
  there is no text layer so the shim falls through to the legacy image-
  render path rather than claiming a result it cannot produce.
- RichDocumentAdapter wraps NSAttributedString across docx/doc/rtf/html;
  a single adapter for all four because they share the framework call
  today, splitting when high-fidelity DOCX lands.
- DocumentAdaptersBootstrap registers the three on the shared registry
  from AppDelegate.applicationDidFinishLaunching exactly once so the
  shim sees adapters on the first file ingress.
- PlainTextRepresentation is the neutral text shape for adapters that
  cannot yet publish a format-native representation; replaced per-format
  by Workbook / WordDocument / etc. in later PRs.
First real-fidelity document adapter. Reads .xlsx into a typed Workbook
representation carrying sheet names, cells with formula source strings,
merged-range references, shared strings, and cell types (number, shared
string, inline string, boolean). The text fallback renders each sheet
as a tab-separated table so callers still on the legacy Attachment.
Kind.document path see something readable.

The adapter deliberately does NOT call CoreXLSX's parseStyles() — that
entry point crashes on openpyxl-generated workbooks because the
library's PatternFill.patternType is non-optional while Excel's default
empty pattern omits the attribute. Everything we surface today is
style-independent; lifting that limitation (number formats, column
widths, dates stored as styled numbers) lives in a follow-up slice
behind a hand-rolled styles fallback.

- Package.swift: CoreXLSX 0.14.2 dependency for the core target,
  testTarget resource declaration for the xlsxwriter-produced fixture.
- Workbook / Sheet / Row / Cell / CellValue / CellRange: the typed
  intermediate that both the XLSX read path and the eventual XLSX write
  emitter round-trip through.
- XLSXAdapter: the actual CoreXLSX → Workbook translator + markdown-
  style text fallback.
- DocumentAdaptersBootstrap: registers XLSXAdapter alongside PlainText /
  PDF / RichDocument, so DocumentParser.parseAll now routes .xlsx
  through the registry instead of throwing unsupportedFormat.
- Tests/Documents/Fixtures/xlsx/sample.xlsx: 5.9 KB fixture with two
  sheets, a SUM formula, a merged range (A5:B5), shared strings, and
  explicit booleans. Exercises the parse paths for each fidelity feature.
- XLSXAdapterTests: 7 tests pinning format routing, sheet/cell
  structure, formulas, merged ranges, shared strings, booleans, text
  fallback formatting, and size-limit refusal.
- DocumentParserShimTests: expands the bootstrap assertion to include
  "xlsx" alongside the three existing adapter ids.
Pairs with XLSXAdapter so agents can ingest a workbook, modify the
typed Workbook in-process, and emit it back as a fresh .xlsx attachment.
libxlsxwriter ships a first-party Swift Package as a pure C SwiftPM
target, so no XCFramework / vendored C source is needed in osaurus
itself — it's just a dependency add.

- Package.swift: libxlsxwriter 1.2.4 dependency for the core target.
- XLSXEmitter: Workbook -> .xlsx via libxlsxwriter. Parses A1 cell
  references into 0-indexed row/col, dispatches strings / numbers /
  booleans / formulas to the right write_* function, handles merged
  ranges via worksheet_merge_range with a nil string so the top-left
  cell's already-written content is preserved. Cleans up a partial
  .xlsx on any emit error so a failed round trip never masquerades as
  a readable file.
- DocumentAdaptersBootstrap: registers XLSXEmitter alongside XLSXAdapter.
- XLSXEmitterTests: 7 tests pinning the round trip end-to-end. Builds
  a Workbook in memory, writes via XLSXEmitter, reads via XLSXAdapter,
  asserts sheet names / formulas / merged ranges / strings / numbers /
  booleans all survive.

Licensing footnote: libxlsxwriter is BSD-2-Clause, but bundles
third_party/tmpfileplus/tmpfileplus.c under MPL 2.0. Statically linking
is permitted. A follow-up to AcknowledgementsView should list both;
deliberately out of scope for this PR.
…te_workbook

Exposes the typed Workbook surface to folder-mode agents. Stacks on top
of the XLSX read (osaurus-ai#929) + write (osaurus-ai#936) PRs and completes the stage-4
round-trip goal: an agent can now ingest a spreadsheet, reason about
cells and formulas in their native types, and emit a modified workbook
— all without the model having to handroll XML.

- read_workbook: returns a compact JSON summary of every sheet
  (names, row counts, merged ranges, truncated cell sample). Capped at
  200 cells per sheet so large workbooks don't blow the context
  window; agents drop to read_workbook_cell for specific values.
- read_workbook_cell: single-cell lookup by (path, sheet, A1 ref).
  Returns value, formula source, and type in a one-line JSON payload.
- write_workbook: accepts a structured sheets array and emits the
  file via XLSXEmitter. Each cell carries its A1 ref, typed value,
  and optional formula; the schema enum guards against unknown
  types. write_workbook creates parent directories and surfaces a
  sheetCount / totalCells summary on success.
- All three plug into FolderToolFactory.buildCoreTools alongside
  file_read / file_write, so they're registered the moment a working
  folder is selected and go away when it's cleared.
- Tests: 8 tests covering sheet summary rendering, missing-file and
  out-of-root rejection, formula preservation on cell lookup, missing-
  sheet error, end-to-end write + re-parse fidelity, non-xlsx path
  refusal, and empty-sheets validation. Tests reuse the sample.xlsx
  fixture from the XLSX read PR.
Business rationale: Workbook tools expose the spreadsheet round trip to agents, and the rebased branch needs to stay CI-clean before review.

Coding rationale: This keeps cleanup scoped to lint-only shape fixes inherited from lower stacked branches while preserving behavior and main's lean folder-tool activation model.

Co-authored-by: Codex <codex@openai.com>
@mimeding mimeding force-pushed the feat/workbook-agent-tools branch from 225b933 to 09dc52e Compare May 3, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant