feat(P060): rename REMOVE_FIELDS->DELETE_FIELDS and DELETE_PAGE->DELETE_PAGES (array)#33
Merged
feat(P060): rename REMOVE_FIELDS->DELETE_FIELDS and DELETE_PAGE->DELETE_PAGES (array)#33
Conversation
…(array)
Iframe contract:
- REMOVE_FIELDS -> DELETE_FIELDS; response field removed_count -> deleted_count
- DELETE_PAGE { page } -> DELETE_PAGES { pages: number[] } (non-empty)
Validation: empty -> invalid_page; pages.length >= visible -> event_not_allowed
(last-page guard upfront); per-element invalid_page / page_out_of_range.
Visible-page positions resolved to absolute page numbers BEFORE deletion
so multi-page batches stay consistent across mid-loop index shifts.
Copilot:
- Tool registry: remove_fields -> delete_fields, delete_page -> delete_pages
- Bridge: removeFields -> deleteFields, deletePage -> deletePages
- 23 locales: rename keys + plural-aware "Deleting pages" copy
- Drop dead `chat.toolInvocation.names.create_field` key (LLM never calls
create_field; the matching createLlmFieldBaselineMiddleware was unreachable
and is removed)
- System prompt updated to encourage batched delete_pages calls
React SDK (BREAKING):
- actions.removeFields -> actions.deleteFields
- RemoveFieldsResult.removed_count -> DeleteFieldsResult.deleted_count
- Internal postMessage type literal updated; changeset added (major bump)
embed/dev: panel rows updated; DELETE_PAGES accepts comma-separated input.
documentation/IFRAME.md: section + payload + response shape rewritten.
Drops dispatcher-side narrowing for LLM-driven bridge calls. The Zod tool schemas validate shape at the AI SDK boundary, and the iframe handler in client/lib/iframe/handlers.ts is the canonical runtime validator (it owns range checks + visiblePageCount). A third validation layer in the dispatcher just duplicates one of those and would drift over time. - IframeBridge: goTo/setFieldValue/focusField/movePage/rotatePage/ deletePages/deleteFields now accept `unknown` for LLM-supplied values. No non-dispatcher consumers exist for these methods. - Dispatcher: 7 cases collapse to one-liners (set_field_value, focus_field, go_to_page, move_page, rotate_page, delete_pages, delete_fields). - Header comment rewritten to call out the new contract.
Removes the bridge.createField method, CreateFieldArgs type, the CREATE_FIELD BridgeRequestType variant, and the matching timeout-bucket arm. The copilot demo never invoked it: create_field is not a registered LLM tool (CLIENT_TOOL_NAMES omits it) and there are no direct non-dispatcher consumers. Cleanup of dead surface — the iframe contract itself still exposes CREATE_FIELD for SDK consumers, this only trims the copilot's internal bridge.
safeDispatch's "unknown tool name" path is unreachable through the standard Vercel AI SDK pathway: the SDK validates LLM tool calls against the registered tool list before the dispatcher is invoked. Removing the wrapper collapses the indirection; the factory now narrows toolName via isClientToolName inline. The dispatcher's `default` arm + `satisfies never` keeps the compile-time exhaustiveness over ClientToolName intact.
…me narrow The factory's runtime isClientToolName check was redundant: the chat_pane.tsx caller already narrows toolName via isClientToolName at the boundary (rejecting unknowns with output-error before execute fires). Tightens execute's signature to ClientToolName so the type system enforces what the caller already guarantees, and the factory's middleware terminus collapses to a one-line dispatch call. MiddlewareContext.toolName follows suit. Net: one runtime narrow at the consumer boundary; everything downstream is typed.
The bridge is the single source of truth for the iframe contract: each
operation has a Zod schema (with description) in
embed-bridge/schemas.ts, and IframeBridge methods take z.infer<typeof X>
directly. The bridge implementation is a one-line postMessage pass-
through per method — no key conversion (input shapes mirror the wire's
snake_case payloads).
The client-tools adapter is now just two files:
- schemas.ts: enumerates the LLM tool names exposed to the model.
- tools.ts: maps each tool name to { description, inputSchema } pulled
verbatim from the bridge schema's `.describe()`. No duplicated text.
Consumers (routes/api/chat.ts and lib/byok/transport.ts) drop ~50 lines
of inline tool registration each; they now just spread LLM_STATIC_TOOLS
into withFinalisationTool. Adding a new LLM-exposed bridge operation:
- one schema in embed-bridge/schemas.ts
- one method on IframeBridge + one impl line in bridge.ts
- one tool entry in client-tools/tools.ts
- one switch arm in client-tools/factory.ts
dispatch.ts is gone (the switch lives in factory.ts directly).
The tool-name registry (CLIENT_TOOL_NAMES, ClientToolName, isClientToolName) lived next to the LLM_STATIC_TOOLS map but in a separate file for no real reason. Merging both into tools.ts puts every LLM-tool decision (which schema, which name, the runtime guard) in one place. Adding a tool now touches one file in the adapter (tools.ts) + one switch arm in factory.ts.
Each bridge method now safeParses its `unknown` input via the matching
Zod schema before posting to the iframe. Bad input surfaces as
`{ success: false, error: { code: 'bad_input', message } }` without a
postMessage round-trip. The adapter (createClientTools switch terminus)
collapses to one-line cases that just hand the LLM input to the bridge.
- IframeBridge methods take `args: unknown` (uniform external surface).
- bridge.ts: parseAndSend helper centralises the parse + sendRequest pair.
- factory.ts: switch is pure routing, no schemas imported.
- sendRequest's `data` parameter loosened to `unknown` (it JSON.stringifies
and doesn't care about the shape).
Adding a new bridge operation is now: schema in schemas.ts, method on
IframeBridge, parseAndSend line in bridge.ts, switch arm in factory.ts.
The schema is the single source of truth.
…comments - Remove unused bridge.loadDocument + LoadDocumentInput + 'LOAD_DOCUMENT' from BridgeRequestType + matching arm in getRequestTimeoutMs. The copilot demo loads documents via URL params, never via postMessage — the method had no callers. - Derive ClientToolName from `keyof typeof LLM_STATIC_TOOLS | 'submit' | 'download'`. Drop the redundant CLIENT_TOOL_NAMES array. isClientToolName uses a derived ReadonlySet for the runtime guard. CLIENT_TOOL_NAMES is no longer exported from the barrel (it was internal only). - Stale "LLM dispatcher" comment wording → "LLM tool registry" (dispatch.ts has been gone for a while).
…ool) Restores `bridge.loadDocument` + `LoadDocumentInput`. The method stays available for direct host-app consumers / SDK adapters; it is NOT in LLM_STATIC_TOOLS or ClientToolName, so the LLM cannot invoke it via the copilot tool registry. This separates the bridge surface (full iframe contract) from the LLM-tool surface (a curated subset).
Bridge-owned codes (bad_input, bridge_disposed, iframe_not_ready,
missing_result, timeout) are now literal types; iframe-forwarded codes
(bad_request:*, forbidden:*, etc.) pass through via `string & {}` so
arbitrary strings still compile. The `& {}` idiom preserves IDE
autocomplete for the bridge-owned literals — typos like
`code: 'iframe_not_redy'` show up in suggestions; bridge-emitted code
is now self-documenting at the type level. Narrowing on a specific
iframe code stays the consumer's responsibility.
Exports BridgeErrorCode from the embed-bridge barrel so future
adapter / consumer code can reference the type if it ever needs to.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Cleanup pass on the copilot bridge + LLM-tool adapter. Pairs with the matching top-level repo PR (SimplePDF/simple-pdf#196) which carries the editor / e2e changes.
The bridge now owns the iframe contract end-to-end (Zod schemas + descriptions + parsing). The adapter is a one-line-per-tool router.
Changes
Bridge owns the contract
embed-bridge/schemas.ts— single source of truth: one Zod schema per iframe operation, each with.describe(). Snake_case keys throughout (matches the wire).IframeBridgemethods acceptunknownand validate internally viaparseAndSend(Schema, 'EVENT', args). Bad input →bad_inputwithout a postMessage round-trip.BridgeResult.error.codeis now a typed union with autocomplete for bridge-owned codes.Adapter is thin
dispatch.ts,safeDispatch,client-tools/schemas.ts, and the redundantisClientToolNameruntime narrow are gone.tools.tsenumerates LLM tool names and pulls descriptions verbatim from the bridge schemas (no duplicated text).factory.tsswitch terminus is pure routing — every arm is one line;satisfies neverkeeps it exhaustive.chat.tsandtransport.tsdrop ~50 lines each: tool registration is nowtools: withFinalisationTool(LLM_STATIC_TOOLS).Dead code pruned
bridge.createField+CreateFieldArgsremoved (no LLM tool, no other consumer).chat.toolInvocation.names.create_fieldi18n key dropped from all 23 locales.createLlmFieldBaselineMiddleware+markFieldAsKnownplumbing removed (thecreate_fieldtool was never registered).Iframe contract rename (also part of this PR)
REMOVE_FIELDS→DELETE_FIELDS(responseremoved_count→deleted_count).DELETE_PAGE { page }→DELETE_PAGES { pages: number[] }.React SDK (BREAKING)
actions.removeFields→actions.deleteFields;removed_count→deleted_count. Major-version changeset added.Net:
+388 / -674lines oncopilot/. Adding a new bridge operation is now: schema inschemas.ts, method onIframeBridge,parseAndSendline inbridge.ts. Exposing it to the LLM: one entry inLLM_STATIC_TOOLS, one switch arm infactory.ts. Thesatisfies neverexhaustiveness catches drift at compile time.