Skip to content

fix: infer JSON serialization for agent RPCs - #379

Open
posva wants to merge 7 commits into
mainfrom
fix/implicit-agent-json-serialization
Open

fix: infer JSON serialization for agent RPCs#379
posva wants to merge 7 commits into
mainfrom
fix/implicit-agent-json-serialization

Conversation

@posva

@posva posva commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

I found this to be weird: having to always manually set jsonSerializable when defining agent info. I switched the error to throw when it's set to false.
I also renamed locally a var to something more explicit

Copilot AI lite review requested due to automatic review settings September 10, 2026 09:15
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 10, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +1 new · 🟠 ~4 changed · 🔴 -0 removed · 2 flows · 7 files · commit 355b449


Architecture

Architecture diagram for devframes/devframe at 355b449

5 components touched across 3 lanes.

Open the interactive canvas


Inside the changed components — 2 views

Component view — Agent serialization & collection

RPC registration and dump collection automatically validate and infer JSON serialization for agent-exposed functions.

Architecture view of Component view — Agent serialization & collection in devframes/devframe

Component view — RPC type contracts

Dedicated schema and schema-less interfaces simplify generated declaration snapshots.

Architecture view of Component view — RPC type contracts in devframes/devframe

Data flow

Data flow diagram for devframes/devframe at 355b449

Registering an agent-exposed RPC function · Collecting static RPC dumps

Open the interactive canvas


The other flows — 1 sequence

Collecting static RPC dumps

Sequence diagram of Collecting static RPC dumps in devframes/devframe

View

  • Architecture lens
  • Data flow lens
  • Expand every detail

Tip

Click the link under each diagram to open it on a canvas you can zoom, pan and step through.

🪧 More tips
  • Run npx skills add coldteadotai/pr-lens, then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Run npx @coldtea/pr-lens-cli analyze --base origin/main on a branch, then npx @coldtea/pr-lens-cli render .pr-lens/graph.json. Same lenses, your own model key, before the pull request exists.
  • Untick Architecture lens or Data flow lens under View to hide a diagram, or tick Expand every detail to open every section. The comment redraws in a few seconds.
  • The diagrams are links. Click one to open it on the canvas, then press W or click play to walk through the change.
  • Open a diagram on the canvas, then press W or click play to walk through the change one step at a time.
  • The CLI's render reads .github/pr-lens.yml and applies your renames, exclusions and lane pins at draw time.
  • Set github.comment.collapsed: true in .github/pr-lens.yml to fold the comment behind one View architecture and data flow row. Drawing still runs on every push.
  • Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and your model provider's key as its api-key to run PR Lens from your own CI. Any /chat/completions endpoint works.
  • Push a commit and the comment redraws for the new head. A slow older run never overwrites a newer one.
  • Switch GitHub to dark mode and the diagrams follow. The moving dots are this pull request's data in motion.

Thanks for using PR Lens! It's built by Coldtea, free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
devframe Ready Ready Preview Sep 10, 2026 1:20pm UTC

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

packages/devframe/src/rpc/types.ts now references incorrect/nonexistent diagnostic codes in the updated jsonSerializable documentation, which should be corrected before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR makes agent-exposed RPC functions implicitly opt into strict JSON serialization (jsonSerializable: true), so MCP-exposed tools always use JSON-safe payloads without requiring an explicit flag, while continuing to reject an explicit jsonSerializable: false.

Changes:

  • Normalize RPC registrations/updates so agent implies jsonSerializable: true, and throw DF0019 only when agent is combined with jsonSerializable: false.
  • Update DF0019 diagnostics text and docs to reflect the new “implicit JSON” behavior.
  • Adjust collector tests and RPC guide examples to validate/teach the inferred behavior.
File summaries
File Description
packages/devframe/src/rpc/types.ts Updates API docs for jsonSerializable to reflect inferred behavior for agent-exposed functions.
packages/devframe/src/rpc/diagnostics.ts Refines DF0019 message/fix text for the new gating rule (jsonSerializable: false).
packages/devframe/src/rpc/collector.ts Implements inference/normalization of jsonSerializable when agent is present.
packages/devframe/src/rpc/collector.test.ts Updates tests to assert inference on register/update and rejection only on explicit false.
docs/content/6.errors/DF0019.md Updates the error reference page to match new semantics and recommended fixes.
docs/content/1.guide/3.rpc.md Updates the guide to show agent implying strict JSON serialization (removes explicit flag in example).
Review details

Suppressed comments (2)

packages/devframe/src/rpc/types.ts:254

  • This docblock mentions DF0019/DF0018, but strict JSON serialization failures throw DF0020, and the agent + jsonSerializable: false conflict throws DF0019 (no DF0018 exists).
         * - `true`: wire and dump use strict `JSON.stringify`; misshapen
         *   values throw `DF0019` at the call site. Inferred for `agent`.
         * - `false` (default): `structured-clone-es` round-trips fancy
         *   types. Cannot be `agent`-exposed (registration throws `DF0018`).

packages/devframe/src/rpc/types.ts:300

  • Same diagnostic-code mismatch here: strict JSON serialization failures are DF0020, and the agent + jsonSerializable: false conflict is DF0019 (there is no DF0018).
         * - `true`: wire and dump use strict `JSON.stringify`; misshapen
         *   values throw `DF0019` at the call site. Inferred for `agent`.
         * - `false` (default): `structured-clone-es` round-trips fancy
         *   types. Cannot be `agent`-exposed (registration throws `DF0018`).
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/devframe/src/rpc/types.ts Outdated
Copilot AI review requested due to automatic review settings September 10, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The updated jsonSerializable docs in packages/devframe/src/rpc/types.ts still reference incorrect diagnostic codes/behavior, which would leave the public API documentation inconsistent with the runtime.

Review details

Suppressed comments (3)

packages/devframe/src/rpc/types.ts:181

  • The jsonSerializable docs reference DF0019 for strict-JSON serialization failures and DF0018 for agent exposure, but the strict JSON serializer throws DF0020 (see strictJsonStringify), and agent exposure only errors when jsonSerializable: false is explicitly set (DF0019). Updating these codes/behavior here will keep the docs accurate.
   * - `true`: args and return are encoded with strict `JSON.stringify`
   *   on the wire and on disk. Misshapen values throw `DF0019` at the
   *   sender, surfacing the bug *during the offending call* rather than
   *   silently coercing to `{}` later. Inferred for `agent` exposure.

packages/devframe/src/rpc/types.ts:254

  • This JSDoc block still says strict JSON failures throw DF0019 and that agent exposure fails with DF0018, but strict JSON validation throws DF0020 and the agent conflict now throws DF0019 only when jsonSerializable: false is explicitly set.
         * - `true`: wire and dump use strict `JSON.stringify`; misshapen
         *   values throw `DF0019` at the call site. Inferred for `agent`.
         * - `false` (default): `structured-clone-es` round-trips fancy
         *   types. Cannot be `agent`-exposed (registration throws `DF0018`).

packages/devframe/src/rpc/types.ts:300

  • Same as above: this block references DF0019/DF0018 for serialization/agent gating, but strict JSON validation throws DF0020 and the agent + jsonSerializable: false conflict throws DF0019.
         * - `true`: wire and dump use strict `JSON.stringify`; misshapen
         *   values throw `DF0019` at the call site. Inferred for `agent`.
         * - `false` (default): `structured-clone-es` round-trips fancy
         *   types. Cannot be `agent`-exposed (registration throws `DF0018`).
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 10, 2026 10:06
* silently coercing to `{}` later. Required for `agent` exposure.
* - `false` (default): payloads use `structured-clone-es`, which
* round-trips Maps/Sets/cycles. Functions in this mode cannot be
* exposed via the `agent` field; registration throws `DF0018`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified the docs, they mention error codes, but that's an implementation detail and it shouldn't appear

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new agent serialization inference currently mutates caller-provided RPC definitions and relies on truthiness checks, which can cause surprising runtime behavior and should be tightened for robustness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread packages/devframe/src/rpc/collector.ts Outdated
Comment thread packages/devframe/src/rpc/types.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 10, 2026 12:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new “agent implies JSON” contract is only enforced during collector registration/update, which can leave other definition consumers (e.g. dump/static paths) behaving inconsistently with the updated docs/types.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/9 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread packages/devframe/src/rpc/collector.ts Outdated
Comment on lines 104 to 111
function ensureAgentJsonSerializable(
fnDef: RpcFunctionDefinition<string, any, any, any, any, any, any>,
): void {
if (fn.agent && fn.jsonSerializable !== true)
throw diagnostics.DF0019({ name: fn.name })
if (fnDef.agent && fnDef.jsonSerializable === false)
throw diagnostics.DF0019({ name: fnDef.name })
if (fnDef.agent && !fnDef.jsonSerializable)
fnDef.jsonSerializable = true
}
Comment on lines 179 to +185
/**
* Declares whether this function's args/return are JSON-serializable,
* i.e. no `Map`, `Set`, `Date`, `BigInt`, class instances, circular
* references, `undefined` leaves, `Symbol`, or `Function` values.
* Selects the serialization format for arguments and return values.
*
* - `true`: args and return are encoded with strict `JSON.stringify`
* on the wire and on disk. Misshapen values throw `DF0019` at the
* sender, surfacing the bug *during the offending call* rather than
* silently coercing to `{}` later. Required for `agent` exposure.
* - `false` (default): payloads use `structured-clone-es`, which
* round-trips Maps/Sets/cycles. Functions in this mode cannot be
* exposed via the `agent` field; registration throws `DF0018`.
* - `true`: uses strict JSON encoding (default when `agent` is set).
* - `false` (default otherwise): uses structured-clone encoding and supports values
* such as `Map`, `Set`, `Date`, and cycles. Functions using this mode
* cannot be agent-exposed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The behavioral change is covered by updated unit tests and the docs/diagnostics/snapshots are consistent with the new implicit JSON-serialization contract for agent-exposed RPCs.

Review details
  • Files reviewed: 7/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 10, 2026 12:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are small but important follow-ups needed (notably a missing DF0019-on-update negative test) to fully lock in the new agent/jsonSerializable gating behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/16 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +44 to 48
it('also infers jsonSerializable: true on update()', () => {
const collector = new RpcFunctionsCollectorBase({})
collector.register({ name: 'plugin:fn', handler: () => 0 } as any)
expect(() => collector.update({
collector.update({
name: 'plugin:fn',
Comment thread packages/devframe/src/rpc/collector.ts Outdated
Comment on lines +97 to +103
/**
* Prevents registering an agent function that is explicitly marked as
* non-serializable, and ensures that agent functions are marked as
* serializable by default.
*
* @internal
*/

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

ensureAgentJsonSerializable() doesn’t fully normalize agent-exposed definitions to jsonSerializable: true in the presence of non-boolean truthy values, which can contradict the intended MCP JSON contract.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 10/19 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +11 to +16
export function ensureAgentJsonSerializable(fnDef: RpcFunctionDefinitionAny): void {
if (fnDef.agent && fnDef.jsonSerializable === false)
throw diagnostics.DF0019({ name: fnDef.name })
if (fnDef.agent && !fnDef.jsonSerializable)
fnDef.jsonSerializable = true
}
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.

2 participants