mirror of
https://github.com/luxfi/mcp.git
synced 2026-09-24 00:25:04 +00:00
Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers: any, max and range over int. omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+5
-5
@@ -24,9 +24,9 @@ func (pingSurface) Tools() []mcp.Tool {
|
||||
return []mcp.Tool{{
|
||||
Name: "ping",
|
||||
Description: "returns pong",
|
||||
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
|
||||
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
|
||||
return map[string]interface{}{"pong": true}, nil, nil
|
||||
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Read: func(_ context.Context, _ map[string]any) (any, *mcp.ChainObservation, error) {
|
||||
return map[string]any{"pong": true}, nil, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func TestTransportComposesMultipleSurfaces(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch ping: %v", err)
|
||||
}
|
||||
m, ok := res.(map[string]interface{})
|
||||
m, ok := res.(map[string]any)
|
||||
if !ok || m["pong"] != true {
|
||||
t.Fatalf("ping result=%v, want {pong:true}", res)
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func TestTransportComposesMultipleSurfaces(t *testing.T) {
|
||||
// a tool whose error path is reached without a live chain read returning bad bytes:
|
||||
// param_value with a malformed argument fails at argument validation (boundary), proving
|
||||
// the governance tool was dispatched (not "unknown tool").
|
||||
_, err = srv.CallTool(context.Background(), "param_value", map[string]interface{}{})
|
||||
_, err = srv.CallTool(context.Background(), "param_value", map[string]any{})
|
||||
if err == nil {
|
||||
t.Fatal("expected param_value to fail on missing args (proving it dispatched), got nil")
|
||||
}
|
||||
|
||||
+4
-4
@@ -64,7 +64,7 @@ func NewContract(jsonABI string, addr common.Address) (*Contract, error) {
|
||||
|
||||
// Call packs `method`(args...), executes it as a read-only eth_call against the bound
|
||||
// address at the latest block, and returns the decoded output values.
|
||||
func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...interface{}) ([]interface{}, error) {
|
||||
func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...any) ([]any, error) {
|
||||
return c.CallAt(ctx, ec, nil, method, args...)
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...i
|
||||
// — a bond withdraw racing the tally — cannot produce an inconsistent snapshot. An
|
||||
// in-process test backend may ignore the block arg (it has only latest state); the
|
||||
// production *ethclient.Client honors it.
|
||||
func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method string, args ...interface{}) ([]interface{}, error) {
|
||||
func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method string, args ...any) ([]any, error) {
|
||||
m, ok := c.abi.Methods[method]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("evmread: unknown method %q", method)
|
||||
@@ -103,14 +103,14 @@ func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method
|
||||
// (Arguments.copyAtomic). So we hand it a one-field wrapper whose field is T; geth then
|
||||
// field-copies the tuple into it by index — which is exactly why T's field order MUST
|
||||
// mirror the Solidity struct.
|
||||
func CallStruct[T any](ctx context.Context, c *Contract, ec Caller, method string, args ...interface{}) (T, error) {
|
||||
func CallStruct[T any](ctx context.Context, c *Contract, ec Caller, method string, args ...any) (T, error) {
|
||||
return CallStructAt[T](ctx, c, ec, nil, method, args...)
|
||||
}
|
||||
|
||||
// CallStructAt is CallStruct pinned to a specific block (nil = latest); see CallAt for
|
||||
// why pinning matters. A quorum tally uses it to read getThought / getVerdicts at the
|
||||
// same block it reads the bonds, for a consistent settle-equivalent snapshot.
|
||||
func CallStructAt[T any](ctx context.Context, c *Contract, ec Caller, block *big.Int, method string, args ...interface{}) (T, error) {
|
||||
func CallStructAt[T any](ctx context.Context, c *Contract, ec Caller, block *big.Int, method string, args ...any) (T, error) {
|
||||
var wrap struct{ V T }
|
||||
in, err := c.abi.Pack(method, args...)
|
||||
if err != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestBoundedEnforcesCeiling(t *testing.T) {
|
||||
cc := &countingCaller{ret: common.LeftPadBytes(big.NewInt(7).Bytes(), 32)}
|
||||
b := NewBounded(cc, max)
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
for i := range max {
|
||||
if _, err := b.CallContract(context.Background(), gethereum.CallMsg{To: &common.Address{}}, nil); err != nil {
|
||||
t.Fatalf("call %d under the ceiling errored: %v", i, err)
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func TestBoundedEnforcesCeiling(t *testing.T) {
|
||||
func TestBoundedZeroMaxDisablesCeiling(t *testing.T) {
|
||||
cc := &countingCaller{ret: common.LeftPadBytes(big.NewInt(1).Bytes(), 32)}
|
||||
b := NewBounded(cc, 0)
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
if _, err := b.CallContract(context.Background(), gethereum.CallMsg{To: &common.Address{}}, nil); err != nil {
|
||||
t.Fatalf("unbounded call %d errored: %v", i, err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/luxfi/mcp
|
||||
|
||||
go 1.26.4
|
||||
go 1.27.1
|
||||
|
||||
require (
|
||||
github.com/holiman/uint256 v1.3.2
|
||||
|
||||
+14
-14
@@ -24,7 +24,7 @@ import (
|
||||
const maxArgStringLen = 4096
|
||||
|
||||
// argString returns a required string argument, rejecting absurdly long inputs.
|
||||
func argString(args map[string]interface{}, name string) (string, error) {
|
||||
func argString(args map[string]any, name string) (string, error) {
|
||||
v, ok := args[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%s: missing required argument %q", "args", name)
|
||||
@@ -40,7 +40,7 @@ func argString(args map[string]interface{}, name string) (string, error) {
|
||||
}
|
||||
|
||||
// argBytes32 parses a required 0x-hex bytes32 argument.
|
||||
func argBytes32(args map[string]interface{}, name string) ([32]byte, error) {
|
||||
func argBytes32(args map[string]any, name string) ([32]byte, error) {
|
||||
var out [32]byte
|
||||
s, err := argString(args, name)
|
||||
if err != nil {
|
||||
@@ -58,7 +58,7 @@ func argBytes32(args map[string]interface{}, name string) ([32]byte, error) {
|
||||
}
|
||||
|
||||
// argAddress parses a required 0x-hex address argument.
|
||||
func argAddress(args map[string]interface{}, name string) (common.Address, error) {
|
||||
func argAddress(args map[string]any, name string) (common.Address, error) {
|
||||
s, err := argString(args, name)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
@@ -72,7 +72,7 @@ func argAddress(args map[string]interface{}, name string) (common.Address, error
|
||||
|
||||
// argUint256 parses a required unsigned integer (used for task/round ids). Accepts a
|
||||
// JSON number, a decimal string, or a 0x-hex string. Negative values are rejected.
|
||||
func argUint256(args map[string]interface{}, name string) (*big.Int, error) {
|
||||
func argUint256(args map[string]any, name string) (*big.Int, error) {
|
||||
v, ok := args[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("args: missing required argument %q", name)
|
||||
@@ -96,7 +96,7 @@ const maxLimit = 256
|
||||
// argLimit reads an optional positive "limit" argument, falling back to def. A
|
||||
// non-positive or absent limit yields def; anything over maxLimit is clamped to maxLimit
|
||||
// so a caller cannot force an unbounded scan.
|
||||
func argLimit(args map[string]interface{}, def int) int {
|
||||
func argLimit(args map[string]any, def int) int {
|
||||
v, ok := args["limit"]
|
||||
if !ok {
|
||||
return def
|
||||
@@ -113,7 +113,7 @@ func argLimit(args map[string]interface{}, def int) int {
|
||||
|
||||
// argFromRound reads the optional "fromRound" argument; nil (use default) when
|
||||
// absent or unparseable. Clamped to <= count-1 by the caller.
|
||||
func argFromRound(args map[string]interface{}, count *big.Int) *big.Int {
|
||||
func argFromRound(args map[string]any, count *big.Int) *big.Int {
|
||||
v, ok := args["fromRound"]
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -127,7 +127,7 @@ func argFromRound(args map[string]interface{}, count *big.Int) *big.Int {
|
||||
|
||||
// toBigInt converts a decoded-JSON value (float64, json.Number, or string) to a
|
||||
// *big.Int. Floats must be integral. Strings may be decimal or 0x-hex.
|
||||
func toBigInt(v interface{}) (*big.Int, error) {
|
||||
func toBigInt(v any) (*big.Int, error) {
|
||||
switch t := v.(type) {
|
||||
case float64:
|
||||
if t != float64(int64(t)) {
|
||||
@@ -176,11 +176,11 @@ func ensure0x(s string) string {
|
||||
// JSON-schema helpers for the tool descriptors (tools/list inputSchema).
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func objSchema(props map[string]interface{}, required []string) map[string]interface{} {
|
||||
func objSchema(props map[string]any, required []string) map[string]any {
|
||||
if props == nil {
|
||||
props = map[string]interface{}{}
|
||||
props = map[string]any{}
|
||||
}
|
||||
schema := map[string]interface{}{
|
||||
schema := map[string]any{
|
||||
"type": "object",
|
||||
"properties": props,
|
||||
}
|
||||
@@ -190,10 +190,10 @@ func objSchema(props map[string]interface{}, required []string) map[string]inter
|
||||
return schema
|
||||
}
|
||||
|
||||
func strSchema(desc string) map[string]interface{} {
|
||||
return map[string]interface{}{"type": "string", "description": desc}
|
||||
func strSchema(desc string) map[string]any {
|
||||
return map[string]any{"type": "string", "description": desc}
|
||||
}
|
||||
|
||||
func intSchema(desc string) map[string]interface{} {
|
||||
return map[string]interface{}{"type": "integer", "description": desc}
|
||||
func intSchema(desc string) map[string]any {
|
||||
return map[string]any{"type": "integer", "description": desc}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestPerRequestCallCeiling(t *testing.T) {
|
||||
|
||||
// pending_operations with a large limit would normally scan many tasks. The ceiling must
|
||||
// stop it well before taskCount.
|
||||
_, err = srv.CallTool(context.Background(), toolPendingOperations, map[string]interface{}{"limit": 200})
|
||||
_, err = srv.CallTool(context.Background(), toolPendingOperations, map[string]any{"limit": 200})
|
||||
if err == nil {
|
||||
t.Fatal("expected a per-request ceiling error, got nil")
|
||||
}
|
||||
@@ -101,28 +101,28 @@ func TestPerRequestCallCeiling(t *testing.T) {
|
||||
func TestArgInputLengthCapped(t *testing.T) {
|
||||
bigStr := strings.Repeat("9", maxArgStringLen+1)
|
||||
// argString path (knobKey).
|
||||
if _, err := argString(map[string]interface{}{"knobKey": bigStr}, "knobKey"); err == nil {
|
||||
if _, err := argString(map[string]any{"knobKey": bigStr}, "knobKey"); err == nil {
|
||||
t.Fatal("expected argString to reject an over-long string")
|
||||
}
|
||||
// toBigInt string path (task/round ids).
|
||||
if _, err := argUint256(map[string]interface{}{"taskId": bigStr}, "taskId"); err == nil {
|
||||
if _, err := argUint256(map[string]any{"taskId": bigStr}, "taskId"); err == nil {
|
||||
t.Fatal("expected argUint256 to reject an over-long integer string")
|
||||
}
|
||||
// A normal value still works.
|
||||
if _, err := argString(map[string]interface{}{"knobKey": "temperature"}, "knobKey"); err != nil {
|
||||
if _, err := argString(map[string]any{"knobKey": "temperature"}, "knobKey"); err != nil {
|
||||
t.Fatalf("normal knobKey wrongly rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestArgLimitClampedToMax is the MEDIUM-4 boundary test: limit is clamped to maxLimit.
|
||||
func TestArgLimitClampedToMax(t *testing.T) {
|
||||
if got := argLimit(map[string]interface{}{"limit": float64(1_000_000)}, 16); got != maxLimit {
|
||||
if got := argLimit(map[string]any{"limit": float64(1_000_000)}, 16); got != maxLimit {
|
||||
t.Fatalf("argLimit(1e6)=%d, want clamp to %d", got, maxLimit)
|
||||
}
|
||||
if got := argLimit(map[string]interface{}{}, 16); got != 16 {
|
||||
if got := argLimit(map[string]any{}, 16); got != 16 {
|
||||
t.Fatalf("argLimit(absent)=%d, want default 16", got)
|
||||
}
|
||||
if got := argLimit(map[string]interface{}{"limit": float64(10)}, 16); got != 10 {
|
||||
if got := argLimit(map[string]any{"limit": float64(10)}, 16); got != 10 {
|
||||
t.Fatalf("argLimit(10)=%d, want 10", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ func (c *evmChain) mine() {
|
||||
|
||||
// advanceBlocks mines `n` empty blocks (advances number + clock without a tx).
|
||||
func (c *evmChain) advanceBlocks(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
for range n {
|
||||
c.mine()
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,7 @@ func (c *evmChain) headerTime(n uint64) uint64 {
|
||||
|
||||
// readStruct is the harness-side INDEPENDENT struct decode (full artifact ABI),
|
||||
// using the same single-output wrapper geth requires (see callStruct in abi.go).
|
||||
func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string, args ...interface{}) T {
|
||||
func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string, args ...any) T {
|
||||
t.Helper()
|
||||
var wrap struct{ V T }
|
||||
in, err := b.abi.Pack(method, args...)
|
||||
@@ -303,7 +303,7 @@ func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string
|
||||
}
|
||||
|
||||
// callViewValues unpacks into a []interface{} (for non-struct returns).
|
||||
func (c *evmChain) callViewValues(b boundContract, method string, args ...interface{}) []interface{} {
|
||||
func (c *evmChain) callViewValues(b boundContract, method string, args ...any) []any {
|
||||
in, err := b.abi.Pack(method, args...)
|
||||
if err != nil {
|
||||
c.t.Fatalf("pack %s: %v", method, err)
|
||||
@@ -364,7 +364,7 @@ func (e *chainEnv) deployAll() {
|
||||
}
|
||||
|
||||
// deployWithCtor packs constructor args, appends to creation bytecode, and deploys.
|
||||
func (e *chainEnv) deployWithCtor(a abi.ABI, code []byte, args ...interface{}) common.Address {
|
||||
func (e *chainEnv) deployWithCtor(a abi.ABI, code []byte, args ...any) common.Address {
|
||||
packed, err := a.Pack("", args...)
|
||||
if err != nil {
|
||||
e.t.Fatalf("pack ctor: %v", err)
|
||||
@@ -451,7 +451,7 @@ func (e *chainEnv) settleRound(roundID *big.Int) {
|
||||
e.c.sendTx(e.c.deployer, e.params.addr, in, nil)
|
||||
}
|
||||
|
||||
func (e *chainEnv) callParams(method string, args ...interface{}) []interface{} {
|
||||
func (e *chainEnv) callParams(method string, args ...any) []any {
|
||||
return e.c.callViewValues(e.params, method, args...)
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ func specHash(label string) [32]byte {
|
||||
func genKeys(t *testing.T, n int) []*ecdsa.PrivateKey {
|
||||
t.Helper()
|
||||
keys := make([]*ecdsa.PrivateKey, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
for range n {
|
||||
k, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("gen key: %v", err)
|
||||
|
||||
+15
-15
@@ -16,13 +16,13 @@ import (
|
||||
|
||||
// callTool runs a read tool and returns its result as a map (all tool results are
|
||||
// JSON objects). Fails the test on tool error or wrong shape.
|
||||
func callTool(t *testing.T, srv *mcp.Server, name string, args map[string]interface{}) map[string]interface{} {
|
||||
func callTool(t *testing.T, srv *mcp.Server, name string, args map[string]any) map[string]any {
|
||||
t.Helper()
|
||||
res, err := srv.CallTool(context.Background(), name, args)
|
||||
if err != nil {
|
||||
t.Fatalf("tool %s: %v", name, err)
|
||||
}
|
||||
m, ok := res.(map[string]interface{})
|
||||
m, ok := res.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("tool %s: result not a map: %T", name, res)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func TestLiveParamRoundSetsAIParamsValue(t *testing.T) {
|
||||
|
||||
// Before the round settles, the knob is not decided.
|
||||
specPre := specHash("zen-nano-temperature")
|
||||
pre := callTool(t, srv, toolParamValue, map[string]interface{}{
|
||||
pre := callTool(t, srv, toolParamValue, map[string]any{
|
||||
"modelSpecHash": common.BytesToHash(specPre[:]).Hex(),
|
||||
"knobKey": "temperature",
|
||||
})
|
||||
@@ -77,7 +77,7 @@ func TestLiveParamRoundSetsAIParamsValue(t *testing.T) {
|
||||
_, spec, knobKey := driveSettledRound(t, env, ops, value)
|
||||
|
||||
// MCP param_value must now reflect the decided value.
|
||||
got := callTool(t, srv, toolParamValue, map[string]interface{}{
|
||||
got := callTool(t, srv, toolParamValue, map[string]any{
|
||||
"modelSpecHash": common.BytesToHash(spec[:]).Hex(),
|
||||
"knobKey": knobKey,
|
||||
})
|
||||
@@ -109,7 +109,7 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
|
||||
|
||||
_, spec, knobKey := driveSettledRound(t, env, ops, big.NewInt(777))
|
||||
|
||||
hist := callTool(t, srv, toolParamHistory, map[string]interface{}{})
|
||||
hist := callTool(t, srv, toolParamHistory, map[string]any{})
|
||||
|
||||
// roundCount parity.
|
||||
rcOut := env.callParams("roundCount")
|
||||
@@ -118,21 +118,21 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
|
||||
t.Fatalf("param_history roundCount=%v, want %s", hist["roundCount"], wantCount)
|
||||
}
|
||||
|
||||
rounds, ok := hist["rounds"].([]interface{})
|
||||
rounds, ok := hist["rounds"].([]any)
|
||||
if !ok || len(rounds) == 0 {
|
||||
t.Fatalf("param_history returned no rounds: %v", hist["rounds"])
|
||||
}
|
||||
|
||||
// The newest round (index 0, descending) is roundId wantCount-1. Field parity
|
||||
// against the on-chain getRound.
|
||||
first := rounds[0].(map[string]interface{})
|
||||
first := rounds[0].(map[string]any)
|
||||
roundID := new(big.Int).Sub(wantCount, big.NewInt(1))
|
||||
if first["roundId"] != roundID.String() {
|
||||
t.Fatalf("first round id=%v, want %s", first["roundId"], roundID)
|
||||
}
|
||||
|
||||
onchain := readStruct[Round](t, env.c, env.params, "getRound", roundID)
|
||||
rj := first["round"].(map[string]interface{})
|
||||
rj := first["round"].(map[string]any)
|
||||
if rj["knobKey"] != onchain.KnobKey || onchain.KnobKey != knobKey {
|
||||
t.Fatalf("knobKey mismatch: mcp=%v onchain=%v want=%s", rj["knobKey"], onchain.KnobKey, knobKey)
|
||||
}
|
||||
@@ -151,12 +151,12 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
|
||||
|
||||
// Proposals parity: count and each value/operator.
|
||||
onProps := readStruct[[]Proposal](t, env.c, env.params, "getProposals", roundID)
|
||||
mcpProps := first["proposals"].([]interface{})
|
||||
mcpProps := first["proposals"].([]any)
|
||||
if len(mcpProps) != len(onProps) {
|
||||
t.Fatalf("proposals count mismatch: mcp=%d onchain=%d", len(mcpProps), len(onProps))
|
||||
}
|
||||
for i := range onProps {
|
||||
p := mcpProps[i].(map[string]interface{})
|
||||
p := mcpProps[i].(map[string]any)
|
||||
if p["value"] != onProps[i].Value.String() {
|
||||
t.Errorf("proposal[%d] value: mcp=%v onchain=%s", i, p["value"], onProps[i].Value)
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
|
||||
taskID := env.openThought(spec, knobKey, n)
|
||||
|
||||
// While Open: derived status must be "Open".
|
||||
openRes := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
openRes := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
|
||||
if openRes["status"] != "Open" {
|
||||
t.Fatalf("open thought status=%v, want Open", openRes["status"])
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
|
||||
env.c.advanceSeconds(2 * 3600) // past the 1h voting window
|
||||
env.settleThought(taskID)
|
||||
|
||||
res := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
res := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
|
||||
|
||||
onchain := readStruct[Thought](t, env.c, env.governor, "getThought", taskID)
|
||||
|
||||
@@ -235,7 +235,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
|
||||
}
|
||||
|
||||
// quorum_status cross-check: unanimous YES means votesFor==n, quorumReached.
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if got := toInt(t, q["votesFor"]); got != int(n) {
|
||||
t.Fatalf("quorum votesFor=%d, want %d", got, n)
|
||||
}
|
||||
@@ -388,7 +388,7 @@ type mockVerdict struct {
|
||||
|
||||
// toUint8 coerces a JSON-decoded numeric (uint8 from the tool, or float64 if it
|
||||
// round-tripped through JSON) to uint8.
|
||||
func toUint8(t *testing.T, v interface{}) uint8 {
|
||||
func toUint8(t *testing.T, v any) uint8 {
|
||||
t.Helper()
|
||||
switch n := v.(type) {
|
||||
case uint8:
|
||||
@@ -403,7 +403,7 @@ func toUint8(t *testing.T, v interface{}) uint8 {
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(t *testing.T, v interface{}) int {
|
||||
func toInt(t *testing.T, v any) int {
|
||||
t.Helper()
|
||||
switch n := v.(type) {
|
||||
case int:
|
||||
|
||||
@@ -35,13 +35,13 @@ func TestPendingOperationsAnnotatesDeadlineAndTruncation(t *testing.T) {
|
||||
// Task 1: open it fresh (deadline in the future) -> Open, deadlinePassed false.
|
||||
t1 := env.openThought(spec, "knob-1", 1)
|
||||
|
||||
res := callTool(t, srv, toolPendingOperations, map[string]interface{}{})
|
||||
pending := res["pending"].([]interface{})
|
||||
res := callTool(t, srv, toolPendingOperations, map[string]any{})
|
||||
pending := res["pending"].([]any)
|
||||
|
||||
// Both tasks are Open, so both appear. Index them by taskId.
|
||||
byID := map[string]map[string]interface{}{}
|
||||
byID := map[string]map[string]any{}
|
||||
for _, p := range pending {
|
||||
m := p.(map[string]interface{})
|
||||
m := p.(map[string]any)
|
||||
byID[m["taskId"].(string)] = m
|
||||
}
|
||||
p0, ok := byID[t0.String()]
|
||||
@@ -61,13 +61,13 @@ func TestPendingOperationsAnnotatesDeadlineAndTruncation(t *testing.T) {
|
||||
|
||||
// With a tiny limit and only the tail scanned, truncation must be reported honestly.
|
||||
// Open several more tasks so taskCount exceeds a limit=1 window.
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
env.openThought(spec, "knob-extra", 1)
|
||||
}
|
||||
// Pass limit as float64 to mirror JSON-decoded args (the real transport path).
|
||||
small := callTool(t, srv, toolPendingOperations, map[string]interface{}{"limit": float64(1)})
|
||||
small := callTool(t, srv, toolPendingOperations, map[string]any{"limit": float64(1)})
|
||||
// We asked for at most 1 result; with many open tasks at the tail it returns 1.
|
||||
if got := len(small["pending"].([]interface{})); got != 1 {
|
||||
if got := len(small["pending"].([]any)); got != 1 {
|
||||
t.Fatalf("limit=1 returned %d pending, want 1", got)
|
||||
}
|
||||
if small["truncated"] != true {
|
||||
@@ -94,7 +94,7 @@ func TestPendingOperationsNotTruncatedWhenAllScanned(t *testing.T) {
|
||||
env.openThought(spec, "a", 1)
|
||||
env.openThought(spec, "b", 1)
|
||||
|
||||
res := callTool(t, srv, toolPendingOperations, map[string]interface{}{})
|
||||
res := callTool(t, srv, toolPendingOperations, map[string]any{})
|
||||
if res["truncated"] != false {
|
||||
t.Fatalf("truncated=%v, want false (scan reached task 0)", res["truncated"])
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
|
||||
|
||||
// BEFORE any withdrawal, MCP must see a real quorum (sanity: the fix didn't break
|
||||
// the honest case).
|
||||
qBefore := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
qBefore := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if qBefore["quorumReached"] != true {
|
||||
t.Fatalf("pre-withdraw quorumReached=%v, want true", qBefore["quorumReached"])
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
|
||||
|
||||
// MCP quorum_status must now report quorumReached=FALSE (matching settle), and
|
||||
// account for the two dropped verdicts.
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if q["quorumReached"] != false {
|
||||
t.Fatalf("post-withdraw quorumReached=%v, want false (settle will drop unbonded verdicts)", q["quorumReached"])
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
|
||||
}
|
||||
|
||||
// And thought_status now reads NoQuorum, consistent with the prediction.
|
||||
ts := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
ts := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
|
||||
if ts["status"] != "NoQuorum" {
|
||||
t.Fatalf("thought_status=%v, want NoQuorum", ts["status"])
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
|
||||
env.submitVerdict(k, taskID, spec, voteYes, 10000)
|
||||
}
|
||||
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if q["quorumReached"] != true {
|
||||
t.Fatalf("quorumReached=%v, want true", q["quorumReached"])
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
|
||||
env.deregister(k)
|
||||
}
|
||||
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if got := toInt(t, q["verdictsCounted"]); got != 3 {
|
||||
t.Fatalf("verdictsCounted=%d, want 3 (deregistered-but-bonded verdicts still count)", got)
|
||||
}
|
||||
@@ -225,7 +225,7 @@ func TestQuorumStatusWinningVoteForNonApproval(t *testing.T) {
|
||||
env.submitVerdict(ops[1], taskID, spec, voteNo, 10000)
|
||||
env.submitVerdict(ops[2], taskID, spec, voteYes, 10000)
|
||||
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if q["quorumReached"] != true {
|
||||
t.Fatalf("quorumReached=%v, want true (NO group of 2 >= threshold 2)", q["quorumReached"])
|
||||
}
|
||||
@@ -282,7 +282,7 @@ func TestQuorumStatusDeadlineGate(t *testing.T) {
|
||||
}
|
||||
|
||||
// Mid-window: quorum reached, but the deadline has NOT passed -> not settleable.
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if q["quorumReached"] != true {
|
||||
t.Fatalf("quorumReached=%v, want true", q["quorumReached"])
|
||||
}
|
||||
@@ -295,7 +295,7 @@ func TestQuorumStatusDeadlineGate(t *testing.T) {
|
||||
|
||||
// Advance past the 1h voting window; now the quorum is settleable.
|
||||
env.c.advanceSeconds(2 * 3600)
|
||||
q2 := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
|
||||
q2 := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
|
||||
if q2["deadlinePassed"] != true {
|
||||
t.Fatalf("deadlinePassed=%v, want true after the window", q2["deadlinePassed"])
|
||||
}
|
||||
|
||||
+16
-16
@@ -48,8 +48,8 @@ func TestStdioServeRoundTrip(t *testing.T) {
|
||||
if idNum(init["id"]) != 1 {
|
||||
t.Fatalf("initialize response id=%v, want 1", init["id"])
|
||||
}
|
||||
result := init["result"].(map[string]interface{})
|
||||
si := result["serverInfo"].(map[string]interface{})
|
||||
result := init["result"].(map[string]any)
|
||||
si := result["serverInfo"].(map[string]any)
|
||||
if si["name"] != mcp.ServerName {
|
||||
t.Fatalf("serverInfo.name=%v, want %s", si["name"], mcp.ServerName)
|
||||
}
|
||||
@@ -58,18 +58,18 @@ func TestStdioServeRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
// 2) tools/list: exactly 8 tools.
|
||||
list := resps[1]["result"].(map[string]interface{})
|
||||
tools := list["tools"].([]interface{})
|
||||
list := resps[1]["result"].(map[string]any)
|
||||
tools := list["tools"].([]any)
|
||||
if len(tools) != 8 {
|
||||
t.Fatalf("tools/list returned %d tools, want 8", len(tools))
|
||||
}
|
||||
// Each tool descriptor must carry name + inputSchema (the MCP shape).
|
||||
for _, tl := range tools {
|
||||
td := tl.(map[string]interface{})
|
||||
td := tl.(map[string]any)
|
||||
if _, ok := td["name"].(string); !ok {
|
||||
t.Fatalf("tool descriptor missing name: %v", td)
|
||||
}
|
||||
if _, ok := td["inputSchema"].(map[string]interface{}); !ok {
|
||||
if _, ok := td["inputSchema"].(map[string]any); !ok {
|
||||
t.Fatalf("tool descriptor missing inputSchema: %v", td)
|
||||
}
|
||||
}
|
||||
@@ -77,20 +77,20 @@ func TestStdioServeRoundTrip(t *testing.T) {
|
||||
// 3) tools/call(chain_state): content[0].text is a JSON object carrying the tool VALUE
|
||||
// plus the verifiable observation. The chain_state fields live under "value"; the
|
||||
// observation under "observation" (the MED-8 wiring — every result is bindable).
|
||||
call := resps[2]["result"].(map[string]interface{})
|
||||
content := call["content"].([]interface{})
|
||||
call := resps[2]["result"].(map[string]any)
|
||||
content := call["content"].([]any)
|
||||
if len(content) != 1 {
|
||||
t.Fatalf("tools/call content length=%d, want 1", len(content))
|
||||
}
|
||||
block := content[0].(map[string]interface{})
|
||||
block := content[0].(map[string]any)
|
||||
if block["type"] != "text" {
|
||||
t.Fatalf("content type=%v, want text", block["type"])
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(block["text"].(string)), &payload); err != nil {
|
||||
t.Fatalf("chain_state text not JSON: %v", err)
|
||||
}
|
||||
state, ok := payload["value"].(map[string]interface{})
|
||||
state, ok := payload["value"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("tool result missing value object: %v", payload)
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func TestStdioServeRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
// The observation must be present and carry a binding hash + the tool name.
|
||||
obs, ok := payload["observation"].(map[string]interface{})
|
||||
obs, ok := payload["observation"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("tool result missing observation object: %v", payload)
|
||||
}
|
||||
@@ -141,14 +141,14 @@ func TestStdioUnknownToolIsError(t *testing.T) {
|
||||
}
|
||||
|
||||
// decodeLines parses newline-delimited JSON-RPC responses.
|
||||
func decodeLines(t *testing.T, s string) []map[string]interface{} {
|
||||
func decodeLines(t *testing.T, s string) []map[string]any {
|
||||
t.Helper()
|
||||
var out []map[string]interface{}
|
||||
var out []map[string]any
|
||||
r := bufio.NewReader(strings.NewReader(s))
|
||||
for {
|
||||
line, err := r.ReadString('\n')
|
||||
if t := strings.TrimSpace(line); t != "" {
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
if jerr := json.Unmarshal([]byte(t), &m); jerr != nil {
|
||||
panic("bad response line: " + t)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func decodeLines(t *testing.T, s string) []map[string]interface{} {
|
||||
}
|
||||
|
||||
// idNum extracts a numeric JSON-RPC id (decoded as float64).
|
||||
func idNum(v interface{}) int {
|
||||
func idNum(v any) int {
|
||||
if f, ok := v.(float64); ok {
|
||||
return int(f)
|
||||
}
|
||||
|
||||
+44
-47
@@ -85,8 +85,8 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
// run executes a body against a fresh per-request bounded caller and builds the
|
||||
// observation from the facts it returned (closing MED-8: every tool returns a
|
||||
// ChainObservation of its exact reads). The ceiling lives in the bounded caller.
|
||||
run := func(name string, body func(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error)) func(context.Context, map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
|
||||
return func(ctx context.Context, args map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
|
||||
run := func(name string, body func(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error)) func(context.Context, map[string]any) (any, *mcp.ChainObservation, error) {
|
||||
return func(ctx context.Context, args map[string]any) (any, *mcp.ChainObservation, error) {
|
||||
ec := g.bounded()
|
||||
val, reads, err := body(ctx, ec, args)
|
||||
if err != nil {
|
||||
@@ -110,7 +110,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
{
|
||||
Name: toolParamValue,
|
||||
Description: "Read a decided governance knob value from AIParams.valueOf(modelSpecHash, knobKey). Returns {value, decided}.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"modelSpecHash": strSchema("bytes32 model spec hash, 0x-hex"),
|
||||
"knobKey": strSchema("knob key string"),
|
||||
}, []string{"modelSpecHash", "knobKey"}),
|
||||
@@ -119,7 +119,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
{
|
||||
Name: toolParamHistory,
|
||||
Description: "AIParams round history (newest first): each round with its proposals. Optional limit (default 16) and fromRound.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"limit": intSchema("max rounds to return (default 16)"),
|
||||
"fromRound": intSchema("highest round id to start from (default roundCount-1)"),
|
||||
}, nil),
|
||||
@@ -128,7 +128,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
{
|
||||
Name: toolThoughtStatus,
|
||||
Description: "AIGovernor.getThought(taskId) fields plus a derived status (Open/Settled/NoQuorum) and taskCount.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"taskId": intSchema("task id"),
|
||||
}, []string{"taskId"}),
|
||||
Read: run(toolThoughtStatus, g.toolThoughtStatus),
|
||||
@@ -136,7 +136,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
{
|
||||
Name: toolReceiptLookup,
|
||||
Description: "AIThoughtRegistry receipt lookup by receiptId. Returns {exists, receipt, receiptCount}.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"receiptId": strSchema("bytes32 receipt id, 0x-hex"),
|
||||
}, []string{"receiptId"}),
|
||||
Read: run(toolReceiptLookup, g.toolReceiptLookup),
|
||||
@@ -150,7 +150,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
"winningIsApprove (winning vote == Yes), deadlinePassed and settleable " +
|
||||
"(quorum reached AND deadline passed — settle reverts before the deadline). " +
|
||||
"Also returns verdictsTotal/verdictsCounted/droppedUnbonded and observedBlock.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"taskId": intSchema("task id"),
|
||||
}, []string{"taskId"}),
|
||||
Read: run(toolQuorumStatus, g.toolQuorumStatus),
|
||||
@@ -158,7 +158,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
{
|
||||
Name: toolOperatorReputation,
|
||||
Description: "Operator standing: {isOperator, bond, weight, agreementRateBps, rep} from AIGovernor and AIReputation.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"operator": strSchema("operator address, 0x-hex"),
|
||||
}, []string{"operator"}),
|
||||
Read: run(toolOperatorReputation, g.toolOperatorReputation),
|
||||
@@ -171,7 +171,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
"inspected (range scannedFrom..taskCount-1). Each entry carries deadlinePassed " +
|
||||
"(now >= deadline) so a still-Open task whose voting window has closed is visible as " +
|
||||
"settle-ready. Also returns observedBlock.",
|
||||
InputSchema: objSchema(map[string]interface{}{
|
||||
InputSchema: objSchema(map[string]any{
|
||||
"limit": intSchema("max open thoughts to return (default 16)"),
|
||||
}, nil),
|
||||
Read: run(toolPendingOperations, g.toolPendingOperations),
|
||||
@@ -183,7 +183,7 @@ func (g *Surface) Tools() []mcp.Tool {
|
||||
// 1. chain_state
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("chain_state: chainID: %w", err)
|
||||
@@ -196,7 +196,7 @@ func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[s
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("chain_state: header: %w", err)
|
||||
}
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"chainId": chainID.String(),
|
||||
"blockNumber": bn,
|
||||
"blockHash": hdr.Hash().Hex(),
|
||||
@@ -215,7 +215,7 @@ func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[s
|
||||
// 2. param_value
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
spec, err := argBytes32(args, "modelSpecHash")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -228,7 +228,7 @@ func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args ma
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"value": value.String(),
|
||||
"decided": decided,
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func (g *Surface) readParamValue(ctx context.Context, ec evmread.Caller, spec [3
|
||||
// 3. param_history
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
limit := argLimit(args, defaultLimit)
|
||||
count, err := g.readRoundCount(ctx, ec)
|
||||
if err != nil {
|
||||
@@ -274,7 +274,7 @@ func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"roundCount": count.String(),
|
||||
"rounds": rounds,
|
||||
}
|
||||
@@ -300,8 +300,8 @@ func (g *Surface) readRoundCount(ctx context.Context, ec evmread.Caller) (*big.I
|
||||
// readParamHistory walks rounds DESCENDING from `from` (inclusive), capped at limit,
|
||||
// returning each round's fields and its proposals. `from` defaults to count-1 when not
|
||||
// supplied by the caller; rounds beyond count-1 are clamped.
|
||||
func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count, from *big.Int, limit int) ([]interface{}, error) {
|
||||
out := []interface{}{}
|
||||
func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count, from *big.Int, limit int) ([]any, error) {
|
||||
out := []any{}
|
||||
if count.Sign() == 0 {
|
||||
return out, nil
|
||||
}
|
||||
@@ -319,7 +319,7 @@ func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, map[string]interface{}{
|
||||
out = append(out, map[string]any{
|
||||
"roundId": new(big.Int).Set(i).String(),
|
||||
"round": roundJSON(round),
|
||||
"proposals": proposalsJSON(proposals),
|
||||
@@ -344,7 +344,7 @@ func (g *Surface) readProposals(ctx context.Context, ec evmread.Caller, roundID
|
||||
// 4. thought_status
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolThoughtStatus(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolThoughtStatus(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
taskID, err := argUint256(args, "taskId")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -390,7 +390,7 @@ func derivedStatus(status uint8) string {
|
||||
// 5. receipt_lookup
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
id, err := argBytes32(args, "receiptId")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -411,7 +411,7 @@ func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("receipt_lookup: receiptCount not *big.Int")
|
||||
}
|
||||
res := map[string]interface{}{
|
||||
res := map[string]any{
|
||||
"exists": exists,
|
||||
"receiptCount": count.String(),
|
||||
}
|
||||
@@ -434,7 +434,7 @@ func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args
|
||||
// 6. quorum_status
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
taskID, err := argUint256(args, "taskId")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -474,7 +474,7 @@ func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args
|
||||
// settle, the only gate — there is no full-committee early exit in the code path). So a
|
||||
// quorum is only ACTUALLY settleable once the deadline has passed.
|
||||
deadlinePassed := now >= t.Deadline
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"verdicts": verdictsJSON(verdicts),
|
||||
"threshold": t.Threshold,
|
||||
"deadline": t.Deadline,
|
||||
@@ -649,7 +649,7 @@ func tallyQuorum(verdicts []Verdict, threshold uint8, bonded map[common.Address]
|
||||
// 7. operator_reputation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
op, err := argAddress(args, "operator")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -690,7 +690,7 @@ func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"isOperator": isOp,
|
||||
"bond": bond.String(),
|
||||
"weight": weight,
|
||||
@@ -711,7 +711,7 @@ func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller,
|
||||
// 8. pending_operations
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
|
||||
func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
|
||||
limit := argLimit(args, defaultLimit)
|
||||
// Pin reads to one block so taskCount, each thought, and the "now" used for the
|
||||
// deadlinePassed flag all reflect the same chain point.
|
||||
@@ -731,7 +731,7 @@ func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
val := map[string]interface{}{
|
||||
val := map[string]any{
|
||||
"taskCount": count.String(),
|
||||
"pending": open,
|
||||
// truncated is true when OLDER tasks below the scanned window were NOT inspected, so
|
||||
@@ -759,16 +759,13 @@ func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller,
|
||||
// truncated=true when the scan stopped above task 0 (older tasks were not inspected), and
|
||||
// the lowest task id scanned. The bounded window plus the per-request eth_call ceiling keep
|
||||
// a huge taskCount from issuing an unbounded scan.
|
||||
func (g *Surface) readPendingOperations(ctx context.Context, ec evmread.Caller, block, count *big.Int, limit int, now uint64) ([]interface{}, bool, *big.Int, error) {
|
||||
out := []interface{}{}
|
||||
func (g *Surface) readPendingOperations(ctx context.Context, ec evmread.Caller, block, count *big.Int, limit int, now uint64) ([]any, bool, *big.Int, error) {
|
||||
out := []any{}
|
||||
last := new(big.Int).Sub(count, big.NewInt(1))
|
||||
if count.Sign() == 0 {
|
||||
return out, false, big.NewInt(0), nil
|
||||
}
|
||||
scanWindow := int64(limit)
|
||||
if scanWindow < pendingScanFloor {
|
||||
scanWindow = pendingScanFloor
|
||||
}
|
||||
scanWindow := max(int64(limit), pendingScanFloor)
|
||||
floor := new(big.Int).Sub(last, big.NewInt(scanWindow-1))
|
||||
if floor.Sign() < 0 {
|
||||
floor = big.NewInt(0)
|
||||
@@ -814,8 +811,8 @@ func (g *Surface) readTaskCountAt(ctx context.Context, ec evmread.Caller, block
|
||||
// JSON projections — stable, all-scalar renderings of the mirror structs.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func roundJSON(r *Round) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func roundJSON(r *Round) map[string]any {
|
||||
return map[string]any{
|
||||
"modelSpecHash": hexBytes32(r.ModelSpecHash),
|
||||
"promptHash": hexBytes32(r.PromptHash),
|
||||
"knobKey": r.KnobKey,
|
||||
@@ -833,10 +830,10 @@ func roundJSON(r *Round) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func proposalsJSON(ps []Proposal) []interface{} {
|
||||
out := make([]interface{}, 0, len(ps))
|
||||
func proposalsJSON(ps []Proposal) []any {
|
||||
out := make([]any, 0, len(ps))
|
||||
for i := range ps {
|
||||
out = append(out, map[string]interface{}{
|
||||
out = append(out, map[string]any{
|
||||
"operator": ps[i].Operator.Hex(),
|
||||
"value": bigString(ps[i].Value),
|
||||
"confidenceBucket": ps[i].ConfidenceBucket,
|
||||
@@ -847,8 +844,8 @@ func proposalsJSON(ps []Proposal) []interface{} {
|
||||
return out
|
||||
}
|
||||
|
||||
func thoughtJSON(t *Thought) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func thoughtJSON(t *Thought) map[string]any {
|
||||
return map[string]any{
|
||||
"modelSpecHash": hexBytes32(t.ModelSpecHash),
|
||||
"promptHash": hexBytes32(t.PromptHash),
|
||||
"evidenceHash": hexBytes32(t.EvidenceHash),
|
||||
@@ -870,10 +867,10 @@ func thoughtJSON(t *Thought) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func verdictsJSON(vs []Verdict) []interface{} {
|
||||
out := make([]interface{}, 0, len(vs))
|
||||
func verdictsJSON(vs []Verdict) []any {
|
||||
out := make([]any, 0, len(vs))
|
||||
for i := range vs {
|
||||
out = append(out, map[string]interface{}{
|
||||
out = append(out, map[string]any{
|
||||
"operator": vs[i].Operator.Hex(),
|
||||
"vote": vs[i].Vote,
|
||||
"confidenceBucket": vs[i].ConfidenceBucket,
|
||||
@@ -884,8 +881,8 @@ func verdictsJSON(vs []Verdict) []interface{} {
|
||||
return out
|
||||
}
|
||||
|
||||
func receiptJSON(rc *ThoughtReceipt) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func receiptJSON(rc *ThoughtReceipt) map[string]any {
|
||||
return map[string]any{
|
||||
"modelId": hexBytes32(rc.ModelId),
|
||||
"promptHash": hexBytes32(rc.PromptHash),
|
||||
"outputHash": hexBytes32(rc.OutputHash),
|
||||
@@ -899,8 +896,8 @@ func receiptJSON(rc *ThoughtReceipt) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func repJSON(r *Rep) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func repJSON(r *Rep) map[string]any {
|
||||
return map[string]any{
|
||||
"weightBps": r.WeightBps,
|
||||
"participated": r.Participated,
|
||||
"agreed": r.Agreed,
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ func dedupLastWins(reads []ObservedFact) []ObservedFact {
|
||||
return reads
|
||||
}
|
||||
out := reads[:0]
|
||||
for i := 0; i < len(reads); i++ {
|
||||
for i := range reads {
|
||||
// Skip this entry if the NEXT entry has the same key (a later writer wins).
|
||||
if i+1 < len(reads) && reads[i+1].Key == reads[i].Key {
|
||||
continue
|
||||
|
||||
+25
-25
@@ -72,8 +72,8 @@ const (
|
||||
type Tool struct {
|
||||
Name string
|
||||
Description string
|
||||
InputSchema map[string]interface{}
|
||||
Read func(ctx context.Context, args map[string]interface{}) (interface{}, *ChainObservation, error)
|
||||
InputSchema map[string]any
|
||||
Read func(ctx context.Context, args map[string]any) (any, *ChainObservation, error)
|
||||
}
|
||||
|
||||
// Surface is a domain's contribution to the transport: a set of read tools. governance/
|
||||
@@ -159,7 +159,7 @@ type rpcRequest struct {
|
||||
type rpcResponse struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id,omitempty"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Result any `json:"result,omitempty"`
|
||||
Error *rpcErr `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ const (
|
||||
// toolDescriptor is the wire shape of a tool in tools/list (the MCP `inputSchema` key).
|
||||
// It is projected from a Tool value so the handler closure is never serialized.
|
||||
type toolDescriptor struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema map[string]interface{} `json:"inputSchema"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema map[string]any `json:"inputSchema"`
|
||||
}
|
||||
|
||||
// content is one MCP tool-result content block. Tool results are returned as a single
|
||||
@@ -296,7 +296,7 @@ func (s *Server) handle(ctx context.Context, line []byte) *rpcResponse {
|
||||
if isNotification {
|
||||
return nil
|
||||
}
|
||||
return okResp(req.ID, map[string]interface{}{"tools": s.toolDescriptors()})
|
||||
return okResp(req.ID, map[string]any{"tools": s.toolDescriptors()})
|
||||
case "tools/call":
|
||||
if isNotification {
|
||||
return nil
|
||||
@@ -310,13 +310,13 @@ func (s *Server) handle(ctx context.Context, line []byte) *rpcResponse {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) initializeResult() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func (s *Server) initializeResult() map[string]any {
|
||||
return map[string]any{
|
||||
"protocolVersion": ProtocolVersion,
|
||||
"capabilities": map[string]interface{}{
|
||||
"tools": map[string]interface{}{},
|
||||
"capabilities": map[string]any{
|
||||
"tools": map[string]any{},
|
||||
},
|
||||
"serverInfo": map[string]interface{}{
|
||||
"serverInfo": map[string]any{
|
||||
"name": ServerName,
|
||||
"version": ServerVersion,
|
||||
},
|
||||
@@ -335,8 +335,8 @@ func (s *Server) toolDescriptors() []toolDescriptor {
|
||||
|
||||
// toolCallParams is the MCP tools/call params shape.
|
||||
type toolCallParams struct {
|
||||
Name string `json:"name"`
|
||||
Arguments map[string]interface{} `json:"arguments"`
|
||||
Name string `json:"name"`
|
||||
Arguments map[string]any `json:"arguments"`
|
||||
}
|
||||
|
||||
func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw json.RawMessage) *rpcResponse {
|
||||
@@ -353,7 +353,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
|
||||
// MCP response with isError=true so the client model sees the failure text (per
|
||||
// MCP tool-result convention), not as a JSON-RPC protocol error, and the stdio
|
||||
// loop keeps serving the next request.
|
||||
return okResp(id, map[string]interface{}{
|
||||
return okResp(id, map[string]any{
|
||||
"content": []content{{Type: "text", Text: err.Error()}},
|
||||
"isError": true,
|
||||
})
|
||||
@@ -361,7 +361,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
|
||||
// Embed the value AND (when present) the verifiable observation in the tool-result
|
||||
// content, so a caller can read the value and independently re-derive/verify the
|
||||
// observation hash against the chain state it claims.
|
||||
payload := map[string]interface{}{"value": result}
|
||||
payload := map[string]any{"value": result}
|
||||
if obs != nil {
|
||||
payload["observation"] = observationView(obs)
|
||||
}
|
||||
@@ -369,7 +369,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
|
||||
if merr != nil {
|
||||
return errResp(id, codeInternalError, "encode result: "+merr.Error())
|
||||
}
|
||||
return okResp(id, map[string]interface{}{
|
||||
return okResp(id, map[string]any{
|
||||
"content": []content{{Type: "text", Text: string(encoded)}},
|
||||
})
|
||||
}
|
||||
@@ -377,8 +377,8 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
|
||||
// observationView is the JSON shape of an observation embedded in a tool result: the
|
||||
// block context, the tool name, the sorted/deduped reads, and the binding hash. A caller
|
||||
// re-derives the hash from these reads via NewObservation/Hash to check the binding.
|
||||
func observationView(o *ChainObservation) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
func observationView(o *ChainObservation) map[string]any {
|
||||
return map[string]any{
|
||||
"chainId": bigString(o.ChainID),
|
||||
"blockNumber": o.BlockNumber,
|
||||
"blockHash": o.BlockHash.Hex(),
|
||||
@@ -396,13 +396,13 @@ func observationView(o *ChainObservation) map[string]interface{} {
|
||||
// the domain's Read closure, not here, so the transport stays chain-agnostic. Used by
|
||||
// both the stdio path and CallTool, so the invariants hold no matter how a tool is
|
||||
// invoked.
|
||||
func (s *Server) dispatch(parent context.Context, name string, args map[string]interface{}) (result interface{}, obs *ChainObservation, err error) {
|
||||
func (s *Server) dispatch(parent context.Context, name string, args map[string]any) (result any, obs *ChainObservation, err error) {
|
||||
t, ok := s.tools[name]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("mcp: unknown tool %q", name)
|
||||
}
|
||||
if args == nil {
|
||||
args = map[string]interface{}{}
|
||||
args = map[string]any{}
|
||||
}
|
||||
ctx := parent
|
||||
var cancel context.CancelFunc
|
||||
@@ -424,7 +424,7 @@ func (s *Server) dispatch(parent context.Context, name string, args map[string]i
|
||||
// decoded result VALUE. Exposed for in-process callers and tests; it is read-only like
|
||||
// every tool. The observation is dropped here (in-process callers that want it can build
|
||||
// it from the same reads); CallToolObserved returns both. Unknown tool names error.
|
||||
func (s *Server) CallTool(ctx context.Context, name string, args map[string]interface{}) (interface{}, error) {
|
||||
func (s *Server) CallTool(ctx context.Context, name string, args map[string]any) (any, error) {
|
||||
v, _, err := s.dispatch(ctx, name, args)
|
||||
return v, err
|
||||
}
|
||||
@@ -432,14 +432,14 @@ func (s *Server) CallTool(ctx context.Context, name string, args map[string]inte
|
||||
// CallToolObserved runs a read tool and returns BOTH its value and the observation it
|
||||
// produced (nil when the tool observes nothing). Exposed for callers that bind the
|
||||
// observation into a verdict.
|
||||
func (s *Server) CallToolObserved(ctx context.Context, name string, args map[string]interface{}) (interface{}, *ChainObservation, error) {
|
||||
func (s *Server) CallToolObserved(ctx context.Context, name string, args map[string]any) (any, *ChainObservation, error) {
|
||||
return s.dispatch(ctx, name, args)
|
||||
}
|
||||
|
||||
// Tools returns the descriptors for the registered read tools (the tools/list surface).
|
||||
func (s *Server) Tools() []Tool { return s.descs }
|
||||
|
||||
func okResp(id json.RawMessage, result interface{}) *rpcResponse {
|
||||
func okResp(id json.RawMessage, result any) *rpcResponse {
|
||||
return &rpcResponse{JSONRPC: "2.0", ID: id, Result: result}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ func errResp(id json.RawMessage, code int, msg string) *rpcResponse {
|
||||
return &rpcResponse{JSONRPC: "2.0", ID: id, Error: &rpcErr{Code: code, Message: msg}}
|
||||
}
|
||||
|
||||
func writeJSONLine(w io.Writer, v interface{}) error {
|
||||
func writeJSONLine(w io.Writer, v any) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+15
-15
@@ -25,9 +25,9 @@ func okTool(name string) Tool {
|
||||
return Tool{
|
||||
Name: name,
|
||||
Description: name,
|
||||
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
|
||||
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
|
||||
return map[string]interface{}{"ok": true, "tool": name}, nil, nil
|
||||
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Read: func(_ context.Context, _ map[string]any) (any, *ChainObservation, error) {
|
||||
return map[string]any{"ok": true, "tool": name}, nil, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,8 @@ func blockingTool(name string) Tool {
|
||||
return Tool{
|
||||
Name: name,
|
||||
Description: name,
|
||||
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
|
||||
Read: func(ctx context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
|
||||
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Read: func(ctx context.Context, _ map[string]any) (any, *ChainObservation, error) {
|
||||
<-ctx.Done()
|
||||
return nil, nil, ctx.Err()
|
||||
},
|
||||
@@ -51,8 +51,8 @@ func panicTool(name string) Tool {
|
||||
return Tool{
|
||||
Name: name,
|
||||
Description: name,
|
||||
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
|
||||
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
|
||||
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Read: func(_ context.Context, _ map[string]any) (any, *ChainObservation, error) {
|
||||
panic("kaboom")
|
||||
},
|
||||
}
|
||||
@@ -86,17 +86,17 @@ func TestPerCallTimeoutDoesNotWedgeServer(t *testing.T) {
|
||||
}
|
||||
|
||||
// 1) The blocked call returns a tool error (isError=true) mentioning a deadline.
|
||||
r1 := resps[0]["result"].(map[string]interface{})
|
||||
r1 := resps[0]["result"].(map[string]any)
|
||||
if r1["isError"] != true {
|
||||
t.Fatalf("blocked call should be isError, got: %v", resps[0])
|
||||
}
|
||||
txt := r1["content"].([]interface{})[0].(map[string]interface{})["text"].(string)
|
||||
txt := r1["content"].([]any)[0].(map[string]any)["text"].(string)
|
||||
if !strings.Contains(strings.ToLower(txt), "deadline") && !strings.Contains(strings.ToLower(txt), "context") {
|
||||
t.Fatalf("blocked call error %q does not look like a timeout", txt)
|
||||
}
|
||||
|
||||
// 2) The SECOND request was answered — the server is still responsive.
|
||||
r2 := resps[1]["result"].(map[string]interface{})
|
||||
r2 := resps[1]["result"].(map[string]any)
|
||||
if _, ok := r2["content"]; !ok {
|
||||
t.Fatalf("second request not answered (server wedged?): %v", resps[1])
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func TestOversizedLineIsRejectedAndLoopSurvives(t *testing.T) {
|
||||
if resps[0]["error"] == nil {
|
||||
t.Fatalf("oversized line should yield a parse error, got: %v", resps[0])
|
||||
}
|
||||
em := resps[0]["error"].(map[string]interface{})
|
||||
em := resps[0]["error"].(map[string]any)
|
||||
if int(em["code"].(float64)) != codeParseError {
|
||||
t.Fatalf("oversized line error code=%v, want %d", em["code"], codeParseError)
|
||||
}
|
||||
@@ -216,21 +216,21 @@ func TestEmptyToolNameRejected(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNilReadHandlerRejected(t *testing.T) {
|
||||
bad := Tool{Name: "noread", Description: "noread", InputSchema: map[string]interface{}{"type": "object"}}
|
||||
bad := Tool{Name: "noread", Description: "noread", InputSchema: map[string]any{"type": "object"}}
|
||||
if _, err := NewServer(fakeSurface{tools: []Tool{bad}}); err == nil {
|
||||
t.Fatal("expected a nil-Read-handler error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// decodeLines parses newline-delimited JSON-RPC responses.
|
||||
func decodeLines(t *testing.T, s string) []map[string]interface{} {
|
||||
func decodeLines(t *testing.T, s string) []map[string]any {
|
||||
t.Helper()
|
||||
var out []map[string]interface{}
|
||||
var out []map[string]any
|
||||
r := bufio.NewReader(strings.NewReader(s))
|
||||
for {
|
||||
line, err := r.ReadString('\n')
|
||||
if tl := strings.TrimSpace(line); tl != "" {
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
if jerr := json.Unmarshal([]byte(tl), &m); jerr != nil {
|
||||
t.Fatalf("bad response line: %s", tl)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user