Skip to content

Preserve large-integer precision when decoding tool arguments - #528

Open
PratikDhanave wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fix-jsonformat-int-precision
Open

Preserve large-integer precision when decoding tool arguments#528
PratikDhanave wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:fix-jsonformat-int-precision

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Summary

applySchema (agent/format/jsonformat/encoding.go), reached via Format.Unmarshal, decodes incoming JSON arguments into an interface{} with json.Unmarshal, which turns every JSON number into a float64. Integer arguments beyond 2^53 are therefore silently truncated before being re-marshalled and handed to the typed handler:

// handler takes struct{ N int64 `json:"n"` }
tool.Call(ctx, `{"n":9007199254740993}`)   // 2^53 + 1
// handler receives N == 9007199254740992  (off by one) — no error

Every functool call with an int64/uint64 argument is affected. (The output / Normalize path is not — it operates on the already-typed value and doesn't round-trip through this number-losing step.)

Fix

Decode with json.Decoder + UseNumber(), so numbers are preserved as json.Number and round-trip exactly through the re-marshal.

Public API

No exported symbols change.

Tests

Adds TestFormat_Unmarshal_PreservesLargeIntegerPrecision (black-box): a 2^53+1 argument. It comes back as 9007199254740992 before this change and exactly 9007199254740993 after. Full ./agent/format/jsonformat and ./tool/functool suites pass (no regression).

@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 17, 2026 18:48
Copilot AI review requested due to automatic review settings July 17, 2026 18:48

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.

Pull request overview

This PR fixes loss of integer precision (> 2^53) when decoding tool-call JSON arguments by switching the schema-application step to decode numbers as json.Number instead of float64, ensuring large int64/uint64 arguments round-trip correctly into typed handlers.

Changes:

  • Update applySchema to decode JSON with json.Decoder + UseNumber() to preserve large integer precision.
  • Add a black-box regression test proving Format.Unmarshal preserves a 2^53+1 integer.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
agent/format/jsonformat/encoding.go Switch argument decoding to UseNumber() in the schema/defaults path to avoid float64 truncation.
agent/format/jsonformat/encoding_test.go Add regression test ensuring Unmarshal preserves large int64 values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +73 to 80
// Decode with UseNumber so integers beyond 2^53 are not silently
// truncated by being decoded into float64 and re-marshalled.
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return nil, fmt.Errorf("unmarshaling arguments: %w", err)
}
}

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.

Addressed — after Decode the code checks for io.EOF and rejects trailing data, restoring json.Unmarshal's strictness.

Comment on lines 5 to 9
import (
"bytes"
"encoding/json"
"fmt"

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.

Done — added the io import and a strict trailing-data check (dec.Token() must return io.EOF). Note: validate() can't also use UseNumber — json.Number is a string type the jsonschema validator rejects as a non-integer, which broke every test; left as a comment in the PR.

Comment on lines +73 to +77
// Decode with UseNumber so integers beyond 2^53 are not silently
// truncated by being decoded into float64 and re-marshalled.
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {

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.

The UseNumber decode preserves large-integer precision in the result value the tool receives. validate() re-decodes separately only for schema checking (pre-existing behavior) — json.Number is a string kind the jsonschema validator rejects as non-integer, so it can't be used there. The precision fix applies where it matters (the decoded result) without altering validation.

@PratikDhanave
PratikDhanave force-pushed the fix-jsonformat-int-precision branch 2 times, most recently from 22af497 to f7805b6 Compare July 23, 2026 15:44
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 23, 2026
@PratikDhanave
PratikDhanave force-pushed the fix-jsonformat-int-precision branch from f7805b6 to 0496f60 Compare July 24, 2026 01:43
@github-actions

This comment has been minimized.

applySchema decoded incoming JSON arguments into an interface{} via
json.Unmarshal, which turns every JSON number into a float64. Integer
arguments beyond 2^53 (int64/uint64) were therefore silently truncated
before being re-marshalled and handed to the typed handler — e.g. a tool
called with {"n":9007199254740993} received 9007199254740992.

Decode with json.Decoder.UseNumber() so numbers are preserved as json.Number
and round-trip exactly. Only the input path (Format.Unmarshal) was affected;
the output/Normalize path already operates on the typed value.

Adds a black-box test asserting a 2^53+1 argument round-trips exactly.
Address review feedback: switching to json.Decoder for UseNumber made
applySchema tolerate trailing data after the first JSON value, unlike the
json.Unmarshal it replaced. Restore strict single-value parsing by
requiring io.EOF after the decoded value, and add a regression test.

(The related suggestion to also decode with UseNumber inside validate()
is not applied: json.Number is a string type that the jsonschema
validator rejects as a non-integer, so it cannot be used there.)
@PratikDhanave
PratikDhanave force-pushed the fix-jsonformat-int-precision branch from 0496f60 to 081b78e Compare July 24, 2026 09:38
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — No Issues Found

This PR fixes a silent precision bug in the unexported applySchema function (agent/format/jsonformat/encoding.go) by switching from json.Unmarshal to json.Decoder with UseNumber(), and adds a follow-up commit to restore strict single-value parsing.

Scope: Both changed files (encoding.go, encoding_test.go) are internal implementation. No exported symbols were added, removed, or modified — confirmed by the PR description and diff.

Cross-repo parity: This is a correctness fix for Go-specific number handling (float64 precision loss beyond 2^53). The upstream .NET and Python implementations use their own native JSON deserialization stacks (System.Text.Json / json module) which do not share this code path, so no equivalent change is needed or expected there. The fix aligns Go behavior with the semantically correct outcome users of the other SDKs already get.

Labels: public-api-change is not warranted (no exported API surface changed). Retaining the existing parity-approved label.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 26.1 AIC · ⌖ 3.95 AIC · ⊞ 5.9K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants