Skip to content

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

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

Preserve large-integer precision when decoding tool arguments#528
PratikDhanave (PratikDhanave) wants to merge 5 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 (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 (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
@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.)
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added area:agent Changes files in the agent area size:medium At most 100 changed lines across at most 5 files pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added failed-auto-risk Automatic risk classification was inconclusive or failed and removed pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress and removed failed-auto-risk Automatic risk classification was inconclusive or failed labels Aug 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — ✅ No Issues

This PR changes only the unexported applySchema function in agent/format/jsonformat/encoding.go. No exported Go symbols are added, removed, or modified.

Cross-repo alignment: The fix restores correct integer semantics that .NET and Python already exhibit naturally. Both the .NET System.Text.Json and Python json libraries preserve integer types when deserializing JSON numbers into typed fields, so tool handlers in those SDKs never silently lose precision on int64/uint64 arguments. The Go change brings behavior into alignment by using json.Decoder + UseNumber() instead of json.Unmarshal (which maps every JSON number to float64).

Labels: No exported API surface changed, so public-api-change is not warranted. parity-approved is retained — the change closes a Go-only precision gap and does not introduce any new divergence from .NET or Python.

Generated by Go API Consistency Review Agent · sonnet46 · 13.3 AIC · ⌖ 4.69 AIC · ⊞ 6K ·

@github-actions github-actions Bot added risk:medium Contained production impact requiring normal review depth and removed pending-auto-risk Automatic risk classification is in progress labels Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Changes files in the agent area parity-approved Go API consistency review found no parity issues risk:medium Contained production impact requiring normal review depth size:medium At most 100 changed lines across at most 5 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants