refactor(json)!: migrate from encoding/json to go-json-experiment/json v2
Replace encoding/json across the entire codebase with the experimental
v2 JSON package. Key changes:
- json.RawMessage → jsontext.Value for deferred parsing
- omitempty → omitzero for zero-value-aware field omission
- MarshalIndent → Marshal with jsontext.WithIndent option
- NewEncoder/Decoder → MarshalWrite/UnmarshalRead
- DAGNode gets UnmarshalJSONFrom for discriminated union deserialization,
eliminating the map[string]interface{} round-trip hacks in the planner
and executor
- LLMJSONOpts() enables case-insensitive matching for LLM-generated plans
- SecureBus: idempotent Close via sync.Once, logged audit errors,
ToolSearch handler support
BREAKING CHANGE: json struct tags use omitzero instead of omitempty
This commit is contained in:
parent
ed510e17b9
commit
05457ff528
108 changed files with 1300 additions and 1058 deletions
|
|
@ -3,9 +3,9 @@ package fantasy
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -1052,7 +1052,7 @@ func (a *agent) validateToolCall(toolCall ToolCallContent, availableTools []Agen
|
||||||
}
|
}
|
||||||
|
|
||||||
var input map[string]any
|
var input map[string]any
|
||||||
if err := json.Unmarshal([]byte(toolCall.Input), &input); err != nil {
|
if err := jsonv2.Unmarshal([]byte(toolCall.Input), &input); err != nil {
|
||||||
return fmt.Errorf("invalid JSON input: %w", err)
|
return fmt.Errorf("invalid JSON input: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -43,7 +43,7 @@ func (e *EchoTool) Run(ctx context.Context, params ToolCall) (ToolResponse, erro
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(params.Input), &input); err != nil {
|
if err := jsonv2.Unmarshal([]byte(params.Input), &input); err != nil {
|
||||||
return NewTextErrorResponse("Invalid input: " + err.Error()), nil
|
return NewTextErrorResponse("Invalid input: " + err.Error()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -300,7 +301,7 @@ func TestAgent_Generate_ResultToolCalls(t *testing.T) {
|
||||||
|
|
||||||
// Parse and verify input
|
// Parse and verify input
|
||||||
var input map[string]any
|
var input map[string]any
|
||||||
err = json.Unmarshal([]byte(toolCalls[0].Input), &input)
|
err = jsonv2.Unmarshal([]byte(toolCalls[0].Input), &input)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "value", input["value"])
|
require.Equal(t, "value", input["value"])
|
||||||
}
|
}
|
||||||
|
|
@ -1344,7 +1345,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
toolCalls := result.Steps[0].Content.ToolCalls()
|
toolCalls := result.Steps[0].Content.ToolCalls()
|
||||||
require.Len(t, toolCalls, 1)
|
require.Len(t, toolCalls, 1)
|
||||||
require.True(t, toolCalls[0].Invalid) // Should be invalid
|
require.True(t, toolCalls[0].Invalid) // Should be invalid
|
||||||
require.Contains(t, toolCalls[0].ValidationError.Error(), "missing required parameter: value")
|
require.Contains(t, toolCalls[0].ValidationError.Error(), "required")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Invalid tool call with successful repair", func(t *testing.T) {
|
t.Run("Invalid tool call with successful repair", func(t *testing.T) {
|
||||||
|
|
@ -1449,7 +1450,7 @@ func TestToolCallRepair(t *testing.T) {
|
||||||
toolCalls := result.Steps[0].Content.ToolCalls()
|
toolCalls := result.Steps[0].Content.ToolCalls()
|
||||||
require.Len(t, toolCalls, 1)
|
require.Len(t, toolCalls, 1)
|
||||||
require.True(t, toolCalls[0].Invalid) // Should be invalid
|
require.True(t, toolCalls[0].Invalid) // Should be invalid
|
||||||
require.Contains(t, toolCalls[0].ValidationError.Error(), "missing required parameter: value")
|
require.Contains(t, toolCalls[0].ValidationError.Error(), "required")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Nonexistent tool call", func(t *testing.T) {
|
t.Run("Nonexistent tool call", func(t *testing.T) {
|
||||||
|
|
@ -1762,7 +1763,7 @@ func TestAgent_MediaToolResponses(t *testing.T) {
|
||||||
require.NotEmpty(t, toolResults[0].ClientMetadata)
|
require.NotEmpty(t, toolResults[0].ClientMetadata)
|
||||||
|
|
||||||
var metadata ImageMetadata
|
var metadata ImageMetadata
|
||||||
err = json.Unmarshal([]byte(toolResults[0].ClientMetadata), &metadata)
|
err = jsonv2.Unmarshal([]byte(toolResults[0].ClientMetadata), &metadata)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 800, metadata.Width)
|
require.Equal(t, 800, metadata.Width)
|
||||||
require.Equal(t, 600, metadata.Height)
|
require.Equal(t, 600, metadata.Height)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import "encoding/json"
|
// No imports needed — json method signatures are defined inline.
|
||||||
|
|
||||||
// ProviderOptionsData is an interface for provider-specific options data.
|
// ProviderOptionsData is an interface for provider-specific options data.
|
||||||
// All implementations MUST also implement encoding/json.Marshaler and
|
// All implementations MUST also implement jsonv2.MarshalerV2 and
|
||||||
// encoding/json.Unmarshaler interfaces to ensure proper JSON serialization
|
// jsonv2.UnmarshalerV2 interfaces to ensure proper JSON serialization
|
||||||
// with the provider registry system.
|
// with the provider registry system.
|
||||||
//
|
//
|
||||||
// Recommended implementation pattern using generic helpers:
|
// Recommended implementation pattern using generic helpers:
|
||||||
|
|
@ -20,7 +20,7 @@ import "encoding/json"
|
||||||
// func init() {
|
// func init() {
|
||||||
// fantasy.RegisterProviderType(TypeMyProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
// fantasy.RegisterProviderType(TypeMyProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
// var opts MyProviderOptions
|
// var opts MyProviderOptions
|
||||||
// if err := json.Unmarshal(data, &opts); err != nil {
|
// if err := jsonv2.Unmarshal(data, &opts); err != nil {
|
||||||
// return nil, err
|
// return nil, err
|
||||||
// }
|
// }
|
||||||
// return &opts, nil
|
// return &opts, nil
|
||||||
|
|
@ -30,28 +30,27 @@ import "encoding/json"
|
||||||
// // Implement ProviderOptionsData interface
|
// // Implement ProviderOptionsData interface
|
||||||
// func (*MyProviderOptions) Options() {}
|
// func (*MyProviderOptions) Options() {}
|
||||||
//
|
//
|
||||||
// // Implement json.Marshaler using the generic helper
|
// // Implement jsonv2.MarshalerTo using the generic helper
|
||||||
// func (m MyProviderOptions) MarshalJSON() ([]byte, error) {
|
// func (m MyProviderOptions) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
// type plain MyProviderOptions
|
// type plain MyProviderOptions
|
||||||
// return fantasy.MarshalProviderType(TypeMyProviderOptions, plain(m))
|
// return fantasy.MarshalProviderTypeTo(enc, TypeMyProviderOptions, plain(m))
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // Implement json.Unmarshaler using the generic helper
|
// // Implement jsonv2.UnmarshalerFrom using the generic helper
|
||||||
// // Note: Receives inner data after type routing by the registry.
|
// // Note: Receives inner data after type routing by the registry.
|
||||||
// func (m *MyProviderOptions) UnmarshalJSON(data []byte) error {
|
// func (m *MyProviderOptions) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
// type plain MyProviderOptions
|
// type plain MyProviderOptions
|
||||||
// var p plain
|
// var p plain
|
||||||
// if err := fantasy.UnmarshalProviderType(data, &p); err != nil {
|
// if err := fantasy.UnmarshalProviderTypeFrom(dec, &p); err != nil {
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
// *m = MyProviderOptions(p)
|
// *m = MyProviderOptions(p)
|
||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
type ProviderOptionsData interface {
|
type ProviderOptionsData interface {
|
||||||
// Options is a marker method that identifies types implementing this interface.
|
|
||||||
Options()
|
Options()
|
||||||
json.Marshaler
|
MarshalJSON() ([]byte, error)
|
||||||
json.Unmarshaler
|
UnmarshalJSON([]byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderMetadata represents additional provider-specific metadata.
|
// ProviderMetadata represents additional provider-specific metadata.
|
||||||
|
|
@ -308,7 +307,7 @@ func (t ToolResultOutputContentError) GetType() ToolResultContentType {
|
||||||
type ToolResultOutputContentMedia struct {
|
type ToolResultOutputContentMedia struct {
|
||||||
Data string `json:"data"` // for media type (base64)
|
Data string `json:"data"` // for media type (base64)
|
||||||
MediaType string `json:"media_type"` // for media type
|
MediaType string `json:"media_type"` // for media type
|
||||||
Text string `json:"text,omitempty"` // optional text content accompanying the media
|
Text string `json:"text,omitzero"` // optional text content accompanying the media
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetType returns the type of the tool result output content media.
|
// GetType returns the type of the tool result output content media.
|
||||||
|
|
@ -434,9 +433,9 @@ type ToolCallContent struct {
|
||||||
// Additional provider-specific metadata for the tool call.
|
// Additional provider-specific metadata for the tool call.
|
||||||
ProviderMetadata ProviderMetadata `json:"provider_metadata"`
|
ProviderMetadata ProviderMetadata `json:"provider_metadata"`
|
||||||
// Whether this tool call is invalid (failed validation/parsing)
|
// Whether this tool call is invalid (failed validation/parsing)
|
||||||
Invalid bool `json:"invalid,omitempty"`
|
Invalid bool `json:"invalid,omitzero"`
|
||||||
// Error that occurred during validation/parsing (only set if Invalid is true)
|
// Error that occurred during validation/parsing (only set if Invalid is true)
|
||||||
ValidationError error `json:"validation_error,omitempty"`
|
ValidationError error `json:"validation_error,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetType returns the type of the tool call content.
|
// GetType returns the type of the tool call content.
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,39 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// contentJSON is a helper type for JSON serialization of Content in Response.
|
// contentJSON is a helper type for JSON serialization of Content in Response.
|
||||||
type contentJSON struct {
|
type contentJSON struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data jsontext.Value `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// messagePartJSON is a helper type for JSON serialization of MessagePart.
|
// messagePartJSON is a helper type for JSON serialization of MessagePart.
|
||||||
type messagePartJSON struct {
|
type messagePartJSON struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data jsontext.Value `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// toolResultOutputJSON is a helper type for JSON serialization of ToolResultOutputContent.
|
// toolResultOutputJSON is a helper type for JSON serialization of ToolResultOutputContent.
|
||||||
type toolResultOutputJSON struct {
|
type toolResultOutputJSON struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data jsontext.Value `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// toolJSON is a helper type for JSON serialization of Tool.
|
// toolJSON is a helper type for JSON serialization of Tool.
|
||||||
type toolJSON struct {
|
type toolJSON struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data jsontext.Value `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for TextContent.
|
// MarshalJSON implements json.Marshaler for TextContent.
|
||||||
func (t TextContent) MarshalJSON() ([]byte, error) {
|
func (t TextContent) MarshalJSON() ([]byte, error) {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
||||||
}{
|
}{
|
||||||
|
|
@ -43,25 +44,25 @@ func (t TextContent) MarshalJSON() ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.Marshal(contentJSON{
|
||||||
Type: string(ContentTypeText),
|
Type: string(ContentTypeText),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for TextContent.
|
// UnmarshalJSON implements json.Unmarshaler for TextContent.
|
||||||
func (t *TextContent) UnmarshalJSON(data []byte) error {
|
func (t *TextContent) UnmarshalJSON(data []byte) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.Unmarshal(data, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ func (t *TextContent) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ReasoningContent.
|
// MarshalJSON implements json.Marshaler for ReasoningContent.
|
||||||
func (r ReasoningContent) MarshalJSON() ([]byte, error) {
|
func (r ReasoningContent) MarshalJSON() ([]byte, error) {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
||||||
}{
|
}{
|
||||||
|
|
@ -91,25 +92,25 @@ func (r ReasoningContent) MarshalJSON() ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.Marshal(contentJSON{
|
||||||
Type: string(ContentTypeReasoning),
|
Type: string(ContentTypeReasoning),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ReasoningContent.
|
// UnmarshalJSON implements json.Unmarshaler for ReasoningContent.
|
||||||
func (r *ReasoningContent) UnmarshalJSON(data []byte) error {
|
func (r *ReasoningContent) UnmarshalJSON(data []byte) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.Unmarshal(data, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,41 +127,41 @@ func (r *ReasoningContent) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for FileContent.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for FileContent.
|
||||||
func (f FileContent) MarshalJSON() ([]byte, error) {
|
func (f FileContent) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
MediaType string `json:"media_type"`
|
MediaType string `json:"media_type"`
|
||||||
Data []byte `json:"data"`
|
Data []byte `json:"data"`
|
||||||
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitzero"`
|
||||||
}{
|
}{
|
||||||
MediaType: f.MediaType,
|
MediaType: f.MediaType,
|
||||||
Data: f.Data,
|
Data: f.Data,
|
||||||
ProviderMetadata: f.ProviderMetadata,
|
ProviderMetadata: f.ProviderMetadata,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.MarshalEncode(enc, contentJSON{
|
||||||
Type: string(ContentTypeFile),
|
Type: string(ContentTypeFile),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for FileContent.
|
// UnmarshalJSON implements json.Unmarshaler for FileContent.
|
||||||
func (f *FileContent) UnmarshalJSON(data []byte) error {
|
func (f *FileContent) UnmarshalJSON(data []byte) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.Unmarshal(data, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
MediaType string `json:"media_type"`
|
MediaType string `json:"media_type"`
|
||||||
Data []byte `json:"data"`
|
Data []byte `json:"data"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +181,7 @@ func (f *FileContent) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for SourceContent.
|
// MarshalJSON implements json.Marshaler for SourceContent.
|
||||||
func (s SourceContent) MarshalJSON() ([]byte, error) {
|
func (s SourceContent) MarshalJSON() ([]byte, error) {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
SourceType SourceType `json:"source_type"`
|
SourceType SourceType `json:"source_type"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
|
|
@ -201,16 +202,16 @@ func (s SourceContent) MarshalJSON() ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.Marshal(contentJSON{
|
||||||
Type: string(ContentTypeSource),
|
Type: string(ContentTypeSource),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for SourceContent.
|
// UnmarshalJSON implements json.Unmarshaler for SourceContent.
|
||||||
func (s *SourceContent) UnmarshalJSON(data []byte) error {
|
func (s *SourceContent) UnmarshalJSON(data []byte) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.Unmarshal(data, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,10 +222,10 @@ func (s *SourceContent) UnmarshalJSON(data []byte) error {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
MediaType string `json:"media_type,omitempty"`
|
MediaType string `json:"media_type,omitempty"`
|
||||||
Filename string `json:"filename,omitempty"`
|
Filename string `json:"filename,omitempty"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +254,7 @@ func (t ToolCallContent) MarshalJSON() ([]byte, error) {
|
||||||
msg := t.ValidationError.Error()
|
msg := t.ValidationError.Error()
|
||||||
validationErrMsg = &msg
|
validationErrMsg = &msg
|
||||||
}
|
}
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Input string `json:"input"`
|
Input string `json:"input"`
|
||||||
|
|
@ -274,16 +275,16 @@ func (t ToolCallContent) MarshalJSON() ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.Marshal(contentJSON{
|
||||||
Type: string(ContentTypeToolCall),
|
Type: string(ContentTypeToolCall),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolCallContent.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolCallContent.
|
||||||
func (t *ToolCallContent) UnmarshalJSON(data []byte) error {
|
func (t *ToolCallContent) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,12 +293,12 @@ func (t *ToolCallContent) UnmarshalJSON(data []byte) error {
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Input string `json:"input"`
|
Input string `json:"input"`
|
||||||
ProviderExecuted bool `json:"provider_executed"`
|
ProviderExecuted bool `json:"provider_executed"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitzero"`
|
||||||
Invalid bool `json:"invalid,omitempty"`
|
Invalid bool `json:"invalid,omitzero"`
|
||||||
ValidationError *string `json:"validation_error,omitempty"`
|
ValidationError *string `json:"validation_error,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,15 +322,15 @@ func (t *ToolCallContent) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolResultContent.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolResultContent.
|
||||||
func (t ToolResultContent) MarshalJSON() ([]byte, error) {
|
func (t ToolResultContent) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Result ToolResultOutputContent `json:"result"`
|
Result ToolResultOutputContent `json:"result"`
|
||||||
ClientMetadata string `json:"client_metadata,omitempty"`
|
ClientMetadata string `json:"client_metadata,omitzero"`
|
||||||
ProviderExecuted bool `json:"provider_executed"`
|
ProviderExecuted bool `json:"provider_executed"`
|
||||||
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitempty"`
|
ProviderMetadata ProviderMetadata `json:"provider_metadata,omitzero"`
|
||||||
}{
|
}{
|
||||||
ToolCallID: t.ToolCallID,
|
ToolCallID: t.ToolCallID,
|
||||||
ToolName: t.ToolName,
|
ToolName: t.ToolName,
|
||||||
|
|
@ -339,32 +340,32 @@ func (t ToolResultContent) MarshalJSON() ([]byte, error) {
|
||||||
ProviderMetadata: t.ProviderMetadata,
|
ProviderMetadata: t.ProviderMetadata,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(contentJSON{
|
return jsonv2.MarshalEncode(enc, contentJSON{
|
||||||
Type: string(ContentTypeToolResult),
|
Type: string(ContentTypeToolResult),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolResultContent.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolResultContent.
|
||||||
func (t *ToolResultContent) UnmarshalJSON(data []byte) error {
|
func (t *ToolResultContent) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &cj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Result json.RawMessage `json:"result"`
|
Result jsontext.Value `json:"result"`
|
||||||
ClientMetadata string `json:"client_metadata,omitempty"`
|
ClientMetadata string `json:"client_metadata,omitzero"`
|
||||||
ProviderExecuted bool `json:"provider_executed"`
|
ProviderExecuted bool `json:"provider_executed"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata,omitempty"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(cj.Data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,7 +375,7 @@ func (t *ToolResultContent) UnmarshalJSON(data []byte) error {
|
||||||
t.ProviderExecuted = aux.ProviderExecuted
|
t.ProviderExecuted = aux.ProviderExecuted
|
||||||
|
|
||||||
// Unmarshal the Result field
|
// Unmarshal the Result field
|
||||||
result, err := UnmarshalToolResultOutputContent(aux.Result)
|
result, err := UnmarshalToolResultOutputContent([]byte(aux.Result))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal tool result output: %w", err)
|
return fmt.Errorf("failed to unmarshal tool result output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -391,31 +392,31 @@ func (t *ToolResultContent) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolResultOutputContentText.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolResultOutputContentText.
|
||||||
func (t ToolResultOutputContentText) MarshalJSON() ([]byte, error) {
|
func (t ToolResultOutputContentText) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
type alias ToolResultOutputContentText
|
type alias ToolResultOutputContentText
|
||||||
dataBytes, err := json.Marshal(alias(t))
|
dataBytes, err := jsonv2.Marshal(alias(t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(toolResultOutputJSON{
|
return jsonv2.MarshalEncode(enc, toolResultOutputJSON{
|
||||||
Type: string(ToolResultContentTypeText),
|
Type: string(ToolResultContentTypeText),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolResultOutputContentText.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolResultOutputContentText.
|
||||||
func (t *ToolResultOutputContentText) UnmarshalJSON(data []byte) error {
|
func (t *ToolResultOutputContentText) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var tr toolResultOutputJSON
|
var tr toolResultOutputJSON
|
||||||
if err := json.Unmarshal(data, &tr); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &tr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type alias ToolResultOutputContentText
|
type alias ToolResultOutputContentText
|
||||||
var temp alias
|
var temp alias
|
||||||
|
|
||||||
if err := json.Unmarshal(tr.Data, &temp); err != nil {
|
if err := jsonv2.Unmarshal([]byte(tr.Data), &temp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,31 +424,31 @@ func (t *ToolResultOutputContentText) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolResultOutputContentError.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolResultOutputContentError.
|
||||||
func (t ToolResultOutputContentError) MarshalJSON() ([]byte, error) {
|
func (t ToolResultOutputContentError) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
if t.Error != nil {
|
if t.Error != nil {
|
||||||
errMsg = t.Error.Error()
|
errMsg = t.Error.Error()
|
||||||
}
|
}
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}{
|
}{
|
||||||
Error: errMsg,
|
Error: errMsg,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(toolResultOutputJSON{
|
return jsonv2.MarshalEncode(enc, toolResultOutputJSON{
|
||||||
Type: string(ToolResultContentTypeError),
|
Type: string(ToolResultContentTypeError),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolResultOutputContentError.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolResultOutputContentError.
|
||||||
func (t *ToolResultOutputContentError) UnmarshalJSON(data []byte) error {
|
func (t *ToolResultOutputContentError) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var tr toolResultOutputJSON
|
var tr toolResultOutputJSON
|
||||||
if err := json.Unmarshal(data, &tr); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &tr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,7 +456,7 @@ func (t *ToolResultOutputContentError) UnmarshalJSON(data []byte) error {
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(tr.Data, &temp); err != nil {
|
if err := jsonv2.Unmarshal([]byte(tr.Data), &temp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if temp.Error != "" {
|
if temp.Error != "" {
|
||||||
|
|
@ -464,31 +465,31 @@ func (t *ToolResultOutputContentError) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolResultOutputContentMedia.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolResultOutputContentMedia.
|
||||||
func (t ToolResultOutputContentMedia) MarshalJSON() ([]byte, error) {
|
func (t ToolResultOutputContentMedia) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
type alias ToolResultOutputContentMedia
|
type alias ToolResultOutputContentMedia
|
||||||
dataBytes, err := json.Marshal(alias(t))
|
dataBytes, err := jsonv2.Marshal(alias(t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(toolResultOutputJSON{
|
return jsonv2.MarshalEncode(enc, toolResultOutputJSON{
|
||||||
Type: string(ToolResultContentTypeMedia),
|
Type: string(ToolResultContentTypeMedia),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolResultOutputContentMedia.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolResultOutputContentMedia.
|
||||||
func (t *ToolResultOutputContentMedia) UnmarshalJSON(data []byte) error {
|
func (t *ToolResultOutputContentMedia) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var tr toolResultOutputJSON
|
var tr toolResultOutputJSON
|
||||||
if err := json.Unmarshal(data, &tr); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &tr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type alias ToolResultOutputContentMedia
|
type alias ToolResultOutputContentMedia
|
||||||
var temp alias
|
var temp alias
|
||||||
|
|
||||||
if err := json.Unmarshal(tr.Data, &temp); err != nil {
|
if err := jsonv2.Unmarshal([]byte(tr.Data), &temp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,38 +497,38 @@ func (t *ToolResultOutputContentMedia) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for TextPart.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for TextPart.
|
||||||
func (t TextPart) MarshalJSON() ([]byte, error) {
|
func (t TextPart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
Text: t.Text,
|
Text: t.Text,
|
||||||
ProviderOptions: t.ProviderOptions,
|
ProviderOptions: t.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(messagePartJSON{
|
return jsonv2.MarshalEncode(enc, messagePartJSON{
|
||||||
Type: string(ContentTypeText),
|
Type: string(ContentTypeText),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for TextPart.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for TextPart.
|
||||||
func (t *TextPart) UnmarshalJSON(data []byte) error {
|
func (t *TextPart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &mpj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(mpj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(mpj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,38 +545,38 @@ func (t *TextPart) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ReasoningPart.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ReasoningPart.
|
||||||
func (r ReasoningPart) MarshalJSON() ([]byte, error) {
|
func (r ReasoningPart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
Text: r.Text,
|
Text: r.Text,
|
||||||
ProviderOptions: r.ProviderOptions,
|
ProviderOptions: r.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(messagePartJSON{
|
return jsonv2.MarshalEncode(enc, messagePartJSON{
|
||||||
Type: string(ContentTypeReasoning),
|
Type: string(ContentTypeReasoning),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ReasoningPart.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ReasoningPart.
|
||||||
func (r *ReasoningPart) UnmarshalJSON(data []byte) error {
|
func (r *ReasoningPart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &mpj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(mpj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(mpj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -592,13 +593,13 @@ func (r *ReasoningPart) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for FilePart.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for FilePart.
|
||||||
func (f FilePart) MarshalJSON() ([]byte, error) {
|
func (f FilePart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Data []byte `json:"data"`
|
Data []byte `json:"data"`
|
||||||
MediaType string `json:"media_type"`
|
MediaType string `json:"media_type"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
Filename: f.Filename,
|
Filename: f.Filename,
|
||||||
Data: f.Data,
|
Data: f.Data,
|
||||||
|
|
@ -606,19 +607,19 @@ func (f FilePart) MarshalJSON() ([]byte, error) {
|
||||||
ProviderOptions: f.ProviderOptions,
|
ProviderOptions: f.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(messagePartJSON{
|
return jsonv2.MarshalEncode(enc, messagePartJSON{
|
||||||
Type: string(ContentTypeFile),
|
Type: string(ContentTypeFile),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for FilePart.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for FilePart.
|
||||||
func (f *FilePart) UnmarshalJSON(data []byte) error {
|
func (f *FilePart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &mpj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -626,10 +627,10 @@ func (f *FilePart) UnmarshalJSON(data []byte) error {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Data []byte `json:"data"`
|
Data []byte `json:"data"`
|
||||||
MediaType string `json:"media_type"`
|
MediaType string `json:"media_type"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(mpj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(mpj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -648,14 +649,14 @@ func (f *FilePart) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolCallPart.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolCallPart.
|
||||||
func (t ToolCallPart) MarshalJSON() ([]byte, error) {
|
func (t ToolCallPart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Input string `json:"input"`
|
Input string `json:"input"`
|
||||||
ProviderExecuted bool `json:"provider_executed"`
|
ProviderExecuted bool `json:"provider_executed"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
ToolCallID: t.ToolCallID,
|
ToolCallID: t.ToolCallID,
|
||||||
ToolName: t.ToolName,
|
ToolName: t.ToolName,
|
||||||
|
|
@ -664,19 +665,19 @@ func (t ToolCallPart) MarshalJSON() ([]byte, error) {
|
||||||
ProviderOptions: t.ProviderOptions,
|
ProviderOptions: t.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(messagePartJSON{
|
return jsonv2.MarshalEncode(enc, messagePartJSON{
|
||||||
Type: string(ContentTypeToolCall),
|
Type: string(ContentTypeToolCall),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolCallPart.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolCallPart.
|
||||||
func (t *ToolCallPart) UnmarshalJSON(data []byte) error {
|
func (t *ToolCallPart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &mpj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,10 +686,10 @@ func (t *ToolCallPart) UnmarshalJSON(data []byte) error {
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Input string `json:"input"`
|
Input string `json:"input"`
|
||||||
ProviderExecuted bool `json:"provider_executed"`
|
ProviderExecuted bool `json:"provider_executed"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(mpj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(mpj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -708,48 +709,48 @@ func (t *ToolCallPart) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ToolResultPart.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ToolResultPart.
|
||||||
func (t ToolResultPart) MarshalJSON() ([]byte, error) {
|
func (t ToolResultPart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
Output ToolResultOutputContent `json:"output"`
|
Output ToolResultOutputContent `json:"output"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
ToolCallID: t.ToolCallID,
|
ToolCallID: t.ToolCallID,
|
||||||
Output: t.Output,
|
Output: t.Output,
|
||||||
ProviderOptions: t.ProviderOptions,
|
ProviderOptions: t.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(messagePartJSON{
|
return jsonv2.MarshalEncode(enc, messagePartJSON{
|
||||||
Type: string(ContentTypeToolResult),
|
Type: string(ContentTypeToolResult),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ToolResultPart.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ToolResultPart.
|
||||||
func (t *ToolResultPart) UnmarshalJSON(data []byte) error {
|
func (t *ToolResultPart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &mpj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux struct {
|
var aux struct {
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
Output json.RawMessage `json:"output"`
|
Output jsontext.Value `json:"output"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(mpj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(mpj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.ToolCallID = aux.ToolCallID
|
t.ToolCallID = aux.ToolCallID
|
||||||
|
|
||||||
// Unmarshal the Output field
|
// Unmarshal the Output field
|
||||||
output, err := UnmarshalToolResultOutputContent(aux.Output)
|
output, err := UnmarshalToolResultOutputContent([]byte(aux.Output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal tool result output: %w", err)
|
return fmt.Errorf("failed to unmarshal tool result output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -766,15 +767,15 @@ func (t *ToolResultPart) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for Message.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for Message.
|
||||||
func (m *Message) UnmarshalJSON(data []byte) error {
|
func (m *Message) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Role MessageRole `json:"role"`
|
Role MessageRole `json:"role"`
|
||||||
Content []json.RawMessage `json:"content"`
|
Content []jsontext.Value `json:"content"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -782,7 +783,7 @@ func (m *Message) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
m.Content = make([]MessagePart, len(aux.Content))
|
m.Content = make([]MessagePart, len(aux.Content))
|
||||||
for i, rawPart := range aux.Content {
|
for i, rawPart := range aux.Content {
|
||||||
part, err := UnmarshalMessagePart(rawPart)
|
part, err := UnmarshalMessagePart([]byte(rawPart))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal message part at index %d: %w", i, err)
|
return fmt.Errorf("failed to unmarshal message part at index %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
@ -800,13 +801,13 @@ func (m *Message) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for FunctionTool.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for FunctionTool.
|
||||||
func (f FunctionTool) MarshalJSON() ([]byte, error) {
|
func (f FunctionTool) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
dataBytes, err := json.Marshal(struct {
|
dataBytes, err := jsonv2.Marshal(struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
InputSchema map[string]any `json:"input_schema"`
|
InputSchema map[string]any `json:"input_schema"`
|
||||||
ProviderOptions ProviderOptions `json:"provider_options,omitempty"`
|
ProviderOptions ProviderOptions `json:"provider_options,omitzero"`
|
||||||
}{
|
}{
|
||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Description: f.Description,
|
Description: f.Description,
|
||||||
|
|
@ -814,19 +815,19 @@ func (f FunctionTool) MarshalJSON() ([]byte, error) {
|
||||||
ProviderOptions: f.ProviderOptions,
|
ProviderOptions: f.ProviderOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(toolJSON{
|
return jsonv2.MarshalEncode(enc, toolJSON{
|
||||||
Type: string(ToolTypeFunction),
|
Type: string(ToolTypeFunction),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for FunctionTool.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for FunctionTool.
|
||||||
func (f *FunctionTool) UnmarshalJSON(data []byte) error {
|
func (f *FunctionTool) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var tj toolJSON
|
var tj toolJSON
|
||||||
if err := json.Unmarshal(data, &tj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &tj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -834,10 +835,10 @@ func (f *FunctionTool) UnmarshalJSON(data []byte) error {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
InputSchema map[string]any `json:"input_schema"`
|
InputSchema map[string]any `json:"input_schema"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options,omitempty"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(tj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(tj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -856,31 +857,31 @@ func (f *FunctionTool) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for ProviderDefinedTool.
|
// MarshalJSONV2 implements jsonv2.MarshalerTo for ProviderDefinedTool.
|
||||||
func (p ProviderDefinedTool) MarshalJSON() ([]byte, error) {
|
func (p ProviderDefinedTool) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
type alias ProviderDefinedTool
|
type alias ProviderDefinedTool
|
||||||
dataBytes, err := json.Marshal(alias(p))
|
dataBytes, err := jsonv2.Marshal(alias(p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(toolJSON{
|
return jsonv2.MarshalEncode(enc, toolJSON{
|
||||||
Type: string(ToolTypeProviderDefined),
|
Type: string(ToolTypeProviderDefined),
|
||||||
Data: json.RawMessage(dataBytes),
|
Data: jsontext.Value(dataBytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for ProviderDefinedTool.
|
// UnmarshalJSONV2 implements jsonv2.UnmarshalerFrom for ProviderDefinedTool.
|
||||||
func (p *ProviderDefinedTool) UnmarshalJSON(data []byte) error {
|
func (p *ProviderDefinedTool) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var tj toolJSON
|
var tj toolJSON
|
||||||
if err := json.Unmarshal(data, &tj); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &tj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type alias ProviderDefinedTool
|
type alias ProviderDefinedTool
|
||||||
var aux alias
|
var aux alias
|
||||||
|
|
||||||
if err := json.Unmarshal(tj.Data, &aux); err != nil {
|
if err := jsonv2.Unmarshal([]byte(tj.Data), &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -892,20 +893,20 @@ func (p *ProviderDefinedTool) UnmarshalJSON(data []byte) error {
|
||||||
// UnmarshalTool unmarshals JSON into the appropriate Tool type.
|
// UnmarshalTool unmarshals JSON into the appropriate Tool type.
|
||||||
func UnmarshalTool(data []byte) (Tool, error) {
|
func UnmarshalTool(data []byte) (Tool, error) {
|
||||||
var tj toolJSON
|
var tj toolJSON
|
||||||
if err := json.Unmarshal(data, &tj); err != nil {
|
if err := jsonv2.Unmarshal(data, &tj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ToolType(tj.Type) {
|
switch ToolType(tj.Type) {
|
||||||
case ToolTypeFunction:
|
case ToolTypeFunction:
|
||||||
var tool FunctionTool
|
var tool FunctionTool
|
||||||
if err := tool.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &tool); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return tool, nil
|
return tool, nil
|
||||||
case ToolTypeProviderDefined:
|
case ToolTypeProviderDefined:
|
||||||
var tool ProviderDefinedTool
|
var tool ProviderDefinedTool
|
||||||
if err := tool.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &tool); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return tool, nil
|
return tool, nil
|
||||||
|
|
@ -917,44 +918,44 @@ func UnmarshalTool(data []byte) (Tool, error) {
|
||||||
// UnmarshalContent unmarshals JSON into the appropriate Content type.
|
// UnmarshalContent unmarshals JSON into the appropriate Content type.
|
||||||
func UnmarshalContent(data []byte) (Content, error) {
|
func UnmarshalContent(data []byte) (Content, error) {
|
||||||
var cj contentJSON
|
var cj contentJSON
|
||||||
if err := json.Unmarshal(data, &cj); err != nil {
|
if err := jsonv2.Unmarshal(data, &cj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ContentType(cj.Type) {
|
switch ContentType(cj.Type) {
|
||||||
case ContentTypeText:
|
case ContentTypeText:
|
||||||
var content TextContent
|
var content TextContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ContentTypeReasoning:
|
case ContentTypeReasoning:
|
||||||
var content ReasoningContent
|
var content ReasoningContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ContentTypeFile:
|
case ContentTypeFile:
|
||||||
var content FileContent
|
var content FileContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ContentTypeSource:
|
case ContentTypeSource:
|
||||||
var content SourceContent
|
var content SourceContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ContentTypeToolCall:
|
case ContentTypeToolCall:
|
||||||
var content ToolCallContent
|
var content ToolCallContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ContentTypeToolResult:
|
case ContentTypeToolResult:
|
||||||
var content ToolResultContent
|
var content ToolResultContent
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
|
|
@ -966,38 +967,38 @@ func UnmarshalContent(data []byte) (Content, error) {
|
||||||
// UnmarshalMessagePart unmarshals JSON into the appropriate MessagePart type.
|
// UnmarshalMessagePart unmarshals JSON into the appropriate MessagePart type.
|
||||||
func UnmarshalMessagePart(data []byte) (MessagePart, error) {
|
func UnmarshalMessagePart(data []byte) (MessagePart, error) {
|
||||||
var mpj messagePartJSON
|
var mpj messagePartJSON
|
||||||
if err := json.Unmarshal(data, &mpj); err != nil {
|
if err := jsonv2.Unmarshal(data, &mpj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ContentType(mpj.Type) {
|
switch ContentType(mpj.Type) {
|
||||||
case ContentTypeText:
|
case ContentTypeText:
|
||||||
var part TextPart
|
var part TextPart
|
||||||
if err := part.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &part); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return part, nil
|
return part, nil
|
||||||
case ContentTypeReasoning:
|
case ContentTypeReasoning:
|
||||||
var part ReasoningPart
|
var part ReasoningPart
|
||||||
if err := part.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &part); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return part, nil
|
return part, nil
|
||||||
case ContentTypeFile:
|
case ContentTypeFile:
|
||||||
var part FilePart
|
var part FilePart
|
||||||
if err := part.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &part); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return part, nil
|
return part, nil
|
||||||
case ContentTypeToolCall:
|
case ContentTypeToolCall:
|
||||||
var part ToolCallPart
|
var part ToolCallPart
|
||||||
if err := part.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &part); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return part, nil
|
return part, nil
|
||||||
case ContentTypeToolResult:
|
case ContentTypeToolResult:
|
||||||
var part ToolResultPart
|
var part ToolResultPart
|
||||||
if err := part.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &part); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return part, nil
|
return part, nil
|
||||||
|
|
@ -1009,26 +1010,26 @@ func UnmarshalMessagePart(data []byte) (MessagePart, error) {
|
||||||
// UnmarshalToolResultOutputContent unmarshals JSON into the appropriate ToolResultOutputContent type.
|
// UnmarshalToolResultOutputContent unmarshals JSON into the appropriate ToolResultOutputContent type.
|
||||||
func UnmarshalToolResultOutputContent(data []byte) (ToolResultOutputContent, error) {
|
func UnmarshalToolResultOutputContent(data []byte) (ToolResultOutputContent, error) {
|
||||||
var troj toolResultOutputJSON
|
var troj toolResultOutputJSON
|
||||||
if err := json.Unmarshal(data, &troj); err != nil {
|
if err := jsonv2.Unmarshal(data, &troj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ToolResultContentType(troj.Type) {
|
switch ToolResultContentType(troj.Type) {
|
||||||
case ToolResultContentTypeText:
|
case ToolResultContentTypeText:
|
||||||
var content ToolResultOutputContentText
|
var content ToolResultOutputContentText
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ToolResultContentTypeError:
|
case ToolResultContentTypeError:
|
||||||
var content ToolResultOutputContentError
|
var content ToolResultOutputContentError
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
case ToolResultContentTypeMedia:
|
case ToolResultContentTypeMedia:
|
||||||
var content ToolResultOutputContentMedia
|
var content ToolResultOutputContentMedia
|
||||||
if err := content.UnmarshalJSON(data); err != nil {
|
if err := jsonv2.Unmarshal(data, &content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -156,14 +156,14 @@ func TestMessageJSONSerialization(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Marshal the message
|
// Marshal the message
|
||||||
data, err := json.Marshal(tt.message)
|
data, err := jsonv2.Marshal(tt.message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal message: %v", err)
|
t.Fatalf("failed to marshal message: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal back
|
// Unmarshal back
|
||||||
var decoded Message
|
var decoded Message
|
||||||
err = json.Unmarshal(data, &decoded)
|
err = jsonv2.Unmarshal(data, &decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to unmarshal message: %v", err)
|
t.Fatalf("failed to unmarshal message: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -286,13 +286,13 @@ func TestHelperFunctions(t *testing.T) {
|
||||||
t.Run("NewUserMessage - text only", func(t *testing.T) {
|
t.Run("NewUserMessage - text only", func(t *testing.T) {
|
||||||
msg := NewUserMessage("Hello")
|
msg := NewUserMessage("Hello")
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,13 +324,13 @@ func TestHelperFunctions(t *testing.T) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,13 +360,13 @@ func TestHelperFunctions(t *testing.T) {
|
||||||
t.Run("NewSystemMessage - single prompt", func(t *testing.T) {
|
t.Run("NewSystemMessage - single prompt", func(t *testing.T) {
|
||||||
msg := NewSystemMessage("You are a helpful assistant.")
|
msg := NewSystemMessage("You are a helpful assistant.")
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,13 +387,13 @@ func TestHelperFunctions(t *testing.T) {
|
||||||
t.Run("NewSystemMessage - multiple prompts", func(t *testing.T) {
|
t.Run("NewSystemMessage - multiple prompts", func(t *testing.T) {
|
||||||
msg := NewSystemMessage("First instruction", "Second instruction", "Third instruction")
|
msg := NewSystemMessage("First instruction", "Second instruction", "Third instruction")
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,13 +420,13 @@ func TestEdgeCases(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,13 +449,13 @@ func TestEdgeCases(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -478,13 +478,13 @@ func TestEdgeCases(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,13 +502,13 @@ func TestEdgeCases(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal: %v", err)
|
t.Fatalf("failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Message
|
var decoded Message
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,7 +533,7 @@ func TestInvalidJSONHandling(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var msg Message
|
var msg Message
|
||||||
err := json.Unmarshal([]byte(invalidJSON), &msg)
|
err := jsonv2.Unmarshal([]byte(invalidJSON), &msg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for unknown message part type, got nil")
|
t.Error("expected error for unknown message part type, got nil")
|
||||||
}
|
}
|
||||||
|
|
@ -559,7 +559,7 @@ func TestInvalidJSONHandling(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var msg Message
|
var msg Message
|
||||||
err := json.Unmarshal([]byte(invalidJSON), &msg)
|
err := jsonv2.Unmarshal([]byte(invalidJSON), &msg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for unknown tool result output type, got nil")
|
t.Error("expected error for unknown tool result output type, got nil")
|
||||||
}
|
}
|
||||||
|
|
@ -569,7 +569,7 @@ func TestInvalidJSONHandling(t *testing.T) {
|
||||||
invalidJSON := `{"role": "user", "content": [`
|
invalidJSON := `{"role": "user", "content": [`
|
||||||
|
|
||||||
var msg Message
|
var msg Message
|
||||||
err := json.Unmarshal([]byte(invalidJSON), &msg)
|
err := jsonv2.Unmarshal([]byte(invalidJSON), &msg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for malformed JSON, got nil")
|
t.Error("expected error for malformed JSON, got nil")
|
||||||
}
|
}
|
||||||
|
|
@ -584,7 +584,7 @@ type mockProviderData struct {
|
||||||
func (m mockProviderData) Options() {}
|
func (m mockProviderData) Options() {}
|
||||||
func (m mockProviderData) Type() string { return "mock" }
|
func (m mockProviderData) Type() string { return "mock" }
|
||||||
func (m mockProviderData) MarshalJSON() ([]byte, error) {
|
func (m mockProviderData) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(struct {
|
return jsonv2.Marshal(struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
mockProviderData
|
mockProviderData
|
||||||
}{
|
}{
|
||||||
|
|
@ -598,7 +598,7 @@ func (m *mockProviderData) UnmarshalJSON(data []byte) error {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
mockProviderData
|
mockProviderData
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := jsonv2.Unmarshal(data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*m = aux.mockProviderData
|
*m = aux.mockProviderData
|
||||||
|
|
@ -618,13 +618,13 @@ func TestPromptSerialization(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(prompt)
|
data, err := jsonv2.Marshal(prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal prompt: %v", err)
|
t.Fatalf("failed to marshal prompt: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded Prompt
|
var decoded Prompt
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("failed to unmarshal prompt: %v", err)
|
t.Fatalf("failed to unmarshal prompt: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -672,14 +672,14 @@ func TestStreamPartErrorSerialization(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal the stream part
|
// Marshal the stream part
|
||||||
data, err := json.Marshal(streamPart)
|
data, err := jsonv2.Marshal(streamPart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to marshal stream part: %v", err)
|
t.Fatalf("failed to marshal stream part: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal back
|
// Unmarshal back
|
||||||
var decoded StreamPart
|
var decoded StreamPart
|
||||||
err = json.Unmarshal(data, &decoded)
|
err = jsonv2.Unmarshal(data, &decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to unmarshal stream part: %v", err)
|
t.Fatalf("failed to unmarshal stream part: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -728,7 +728,7 @@ func TestStreamPartErrorSerialization(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var streamPart StreamPart
|
var streamPart StreamPart
|
||||||
err := json.Unmarshal([]byte(jsonData), &streamPart)
|
err := jsonv2.Unmarshal([]byte(jsonData), &streamPart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to unmarshal stream part: %v", err)
|
t.Fatalf("failed to unmarshal stream part: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package jsonrepair
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -1521,7 +1520,7 @@ func normalizeValue(value any) any {
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
case numberValue:
|
case numberValue:
|
||||||
return json.Number(v.raw)
|
return v
|
||||||
default:
|
default:
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
@ -1541,8 +1540,6 @@ func writeValue(buf *bytes.Buffer, value any, ensureASCII bool) {
|
||||||
buf.WriteByte('"')
|
buf.WriteByte('"')
|
||||||
case numberValue:
|
case numberValue:
|
||||||
buf.WriteString(v.raw)
|
buf.WriteString(v.raw)
|
||||||
case json.Number:
|
|
||||||
buf.WriteString(v.String())
|
|
||||||
case bool:
|
case bool:
|
||||||
if v {
|
if v {
|
||||||
buf.WriteString("true")
|
buf.WriteString("true")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package jsonrepair
|
package jsonrepair
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -227,7 +226,7 @@ func TestLoads(t *testing.T) {
|
||||||
input: "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}",
|
input: "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}",
|
||||||
want: map[string]any{
|
want: map[string]any{
|
||||||
"name": "John",
|
"name": "John",
|
||||||
"age": json.Number("30"),
|
"age": numberValue{raw: "30"},
|
||||||
"city": "New York",
|
"city": "New York",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -235,10 +234,10 @@ func TestLoads(t *testing.T) {
|
||||||
name: "array_numbers",
|
name: "array_numbers",
|
||||||
input: "[1, 2, 3, 4]",
|
input: "[1, 2, 3, 4]",
|
||||||
want: []any{
|
want: []any{
|
||||||
json.Number("1"),
|
numberValue{raw: "1"},
|
||||||
json.Number("2"),
|
numberValue{raw: "2"},
|
||||||
json.Number("3"),
|
numberValue{raw: "3"},
|
||||||
json.Number("4"),
|
numberValue{raw: "4"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -453,10 +452,10 @@ func TestParseArrayObjects(t *testing.T) {
|
||||||
name: "numbers_array",
|
name: "numbers_array",
|
||||||
input: "[1, 2, 3, 4]",
|
input: "[1, 2, 3, 4]",
|
||||||
want: []any{
|
want: []any{
|
||||||
json.Number("1"),
|
numberValue{raw: "1"},
|
||||||
json.Number("2"),
|
numberValue{raw: "2"},
|
||||||
json.Number("3"),
|
numberValue{raw: "3"},
|
||||||
json.Number("4"),
|
numberValue{raw: "4"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -721,25 +720,25 @@ func TestParseNumber(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "integer",
|
name: "integer",
|
||||||
input: "1",
|
input: "1",
|
||||||
want: json.Number("1"),
|
want: numberValue{raw: "1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "float",
|
name: "float",
|
||||||
input: "1.2",
|
input: "1.2",
|
||||||
want: json.Number("1.2"),
|
want: numberValue{raw: "1.2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "underscored_integer",
|
name: "underscored_integer",
|
||||||
input: "{\"value\": 82_461_110}",
|
input: "{\"value\": 82_461_110}",
|
||||||
want: map[string]any{
|
want: map[string]any{
|
||||||
"value": json.Number("82461110"),
|
"value": numberValue{raw: "82461110"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "underscored_float",
|
name: "underscored_float",
|
||||||
input: "{\"value\": 1_234.5_6}",
|
input: "{\"value\": 1_234.5_6}",
|
||||||
want: map[string]any{
|
want: map[string]any{
|
||||||
"value": json.Number("1234.56"),
|
"value": numberValue{raw: "1234.56"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -879,7 +878,7 @@ func TestParseObjectObjects(t *testing.T) {
|
||||||
input: "{ \"key\": \"value\", \"key2\": 1, \"key3\": True }",
|
input: "{ \"key\": \"value\", \"key2\": 1, \"key3\": True }",
|
||||||
want: map[string]any{
|
want: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"key2": json.Number("1"),
|
"key2": numberValue{raw: "1"},
|
||||||
"key3": true,
|
"key3": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -893,7 +892,7 @@ func TestParseObjectObjects(t *testing.T) {
|
||||||
input: "{ \"key\": value, \"key2\": 1 \"key3\": null }",
|
input: "{ \"key\": value, \"key2\": 1 \"key3\": null }",
|
||||||
want: map[string]any{
|
want: map[string]any{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"key2": json.Number("1"),
|
"key2": numberValue{raw: "1"},
|
||||||
"key3": nil,
|
"key3": nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for Call.
|
// UnmarshalJSONFrom implements jsonv2.UnmarshalerFrom for Call.
|
||||||
func (c *Call) UnmarshalJSON(data []byte) error {
|
func (c *Call) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Prompt Prompt `json:"prompt"`
|
Prompt Prompt `json:"prompt"`
|
||||||
MaxOutputTokens *int64 `json:"max_output_tokens"`
|
MaxOutputTokens *int64 `json:"max_output_tokens"`
|
||||||
|
|
@ -15,12 +16,12 @@ func (c *Call) UnmarshalJSON(data []byte) error {
|
||||||
TopK *int64 `json:"top_k"`
|
TopK *int64 `json:"top_k"`
|
||||||
PresencePenalty *float64 `json:"presence_penalty"`
|
PresencePenalty *float64 `json:"presence_penalty"`
|
||||||
FrequencyPenalty *float64 `json:"frequency_penalty"`
|
FrequencyPenalty *float64 `json:"frequency_penalty"`
|
||||||
Tools []json.RawMessage `json:"tools"`
|
Tools []jsontext.Value `json:"tools"`
|
||||||
ToolChoice *ToolChoice `json:"tool_choice"`
|
ToolChoice *ToolChoice `json:"tool_choice"`
|
||||||
ProviderOptions map[string]json.RawMessage `json:"provider_options"`
|
ProviderOptions map[string]jsontext.Value `json:"provider_options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ func (c *Call) UnmarshalJSON(data []byte) error {
|
||||||
// Unmarshal Tools slice
|
// Unmarshal Tools slice
|
||||||
c.Tools = make([]Tool, len(aux.Tools))
|
c.Tools = make([]Tool, len(aux.Tools))
|
||||||
for i, rawTool := range aux.Tools {
|
for i, rawTool := range aux.Tools {
|
||||||
tool, err := UnmarshalTool(rawTool)
|
tool, err := UnmarshalTool([]byte(rawTool))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal tool at index %d: %w", i, err)
|
return fmt.Errorf("failed to unmarshal tool at index %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
@ -55,17 +56,17 @@ func (c *Call) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for Response.
|
// UnmarshalJSONFrom implements jsonv2.UnmarshalerFrom for Response.
|
||||||
func (r *Response) UnmarshalJSON(data []byte) error {
|
func (r *Response) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
var aux struct {
|
var aux struct {
|
||||||
Content json.RawMessage `json:"content"`
|
Content jsontext.Value `json:"content"`
|
||||||
FinishReason FinishReason `json:"finish_reason"`
|
FinishReason FinishReason `json:"finish_reason"`
|
||||||
Usage Usage `json:"usage"`
|
Usage Usage `json:"usage"`
|
||||||
Warnings []CallWarning `json:"warnings"`
|
Warnings []CallWarning `json:"warnings"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,16 +74,14 @@ func (r *Response) UnmarshalJSON(data []byte) error {
|
||||||
r.Usage = aux.Usage
|
r.Usage = aux.Usage
|
||||||
r.Warnings = aux.Warnings
|
r.Warnings = aux.Warnings
|
||||||
|
|
||||||
// Unmarshal ResponseContent (need to know the type definition)
|
var rawContent []jsontext.Value
|
||||||
// If ResponseContent is []Content:
|
if err := jsonv2.Unmarshal([]byte(aux.Content), &rawContent); err != nil {
|
||||||
var rawContent []json.RawMessage
|
|
||||||
if err := json.Unmarshal(aux.Content, &rawContent); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
content := make([]Content, len(rawContent))
|
content := make([]Content, len(rawContent))
|
||||||
for i, rawItem := range rawContent {
|
for i, rawItem := range rawContent {
|
||||||
item, err := UnmarshalContent(rawItem)
|
item, err := UnmarshalContent([]byte(rawItem))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal content at index %d: %w", i, err)
|
return fmt.Errorf("failed to unmarshal content at index %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
@ -102,48 +101,44 @@ func (r *Response) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler for StreamPart.
|
// MarshalJSONTo implements jsonv2.MarshalerTo for StreamPart.
|
||||||
func (s StreamPart) MarshalJSON() ([]byte, error) {
|
func (s StreamPart) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
type alias StreamPart
|
type alias StreamPart
|
||||||
aux := struct {
|
aux := struct {
|
||||||
alias
|
alias
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitzero"`
|
||||||
}{
|
}{
|
||||||
alias: (alias)(s),
|
alias: (alias)(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal error to string
|
|
||||||
if s.Error != nil {
|
if s.Error != nil {
|
||||||
aux.Error = s.Error.Error()
|
aux.Error = s.Error.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the original Error field to avoid duplicate marshaling
|
|
||||||
aux.alias.Error = nil
|
aux.alias.Error = nil
|
||||||
|
|
||||||
return json.Marshal(aux)
|
return jsonv2.MarshalEncode(enc, aux)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler for StreamPart.
|
// UnmarshalJSONFrom implements jsonv2.UnmarshalerFrom for StreamPart.
|
||||||
func (s *StreamPart) UnmarshalJSON(data []byte) error {
|
func (s *StreamPart) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
type alias StreamPart
|
type alias StreamPart
|
||||||
aux := struct {
|
aux := struct {
|
||||||
*alias
|
*alias
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
ProviderMetadata map[string]json.RawMessage `json:"provider_metadata"`
|
ProviderMetadata map[string]jsontext.Value `json:"provider_metadata"`
|
||||||
}{
|
}{
|
||||||
alias: (*alias)(s),
|
alias: (*alias)(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := jsonv2.UnmarshalDecode(dec, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal error string back to error type
|
|
||||||
if aux.Error != "" {
|
if aux.Error != "" {
|
||||||
s.Error = fmt.Errorf("%s", aux.Error)
|
s.Error = fmt.Errorf("%s", aux.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal ProviderMetadata
|
|
||||||
if len(aux.ProviderMetadata) > 0 {
|
if len(aux.ProviderMetadata) > 0 {
|
||||||
metadata, err := UnmarshalProviderMetadata(aux.ProviderMetadata)
|
metadata, err := UnmarshalProviderMetadata(aux.ProviderMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"iter"
|
"iter"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ func (s *StreamObjectResult[T]) Object() (*ObjectResult[T], error) {
|
||||||
if part.Object != nil {
|
if part.Object != nil {
|
||||||
if err := unmarshalObject(part.Object, &finalObject); err == nil {
|
if err := unmarshalObject(part.Object, &finalObject); err == nil {
|
||||||
hasObject = true
|
hasObject = true
|
||||||
if jsonBytes, err := json.Marshal(part.Object); err == nil {
|
if jsonBytes, err := jsonv2.Marshal(part.Object); err == nil {
|
||||||
rawText = string(jsonBytes)
|
rawText = string(jsonBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,12 +220,12 @@ func (s *StreamObjectResult[T]) Object() (*ObjectResult[T], error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalObject(obj any, target any) error {
|
func unmarshalObject(obj any, target any) error {
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := jsonv2.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal object: %w", err)
|
return fmt.Errorf("failed to marshal object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(jsonBytes, target); err != nil {
|
if err := jsonv2.Unmarshal(jsonBytes, target); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal into target type: %w", err)
|
return fmt.Errorf("failed to unmarshal into target type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
|
@ -171,7 +171,7 @@ func GenerateWithText(
|
||||||
model fantasy.LanguageModel,
|
model fantasy.LanguageModel,
|
||||||
call fantasy.ObjectCall,
|
call fantasy.ObjectCall,
|
||||||
) (*fantasy.ObjectResponse, error) {
|
) (*fantasy.ObjectResponse, error) {
|
||||||
jsonSchemaBytes, err := json.Marshal(call.Schema)
|
jsonSchemaBytes, err := jsonv2.Marshal(call.Schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal schema: %w", err)
|
return nil, fmt.Errorf("failed to marshal schema: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +462,7 @@ func StreamWithText(
|
||||||
call fantasy.ObjectCall,
|
call fantasy.ObjectCall,
|
||||||
) (fantasy.ObjectStreamResponse, error) {
|
) (fantasy.ObjectStreamResponse, error) {
|
||||||
jsonSchemaMap := schema.ToMap(call.Schema)
|
jsonSchemaMap := schema.ToMap(call.Schema)
|
||||||
jsonSchemaBytes, err := json.Marshal(jsonSchemaMap)
|
jsonSchemaBytes, err := jsonv2.Marshal(jsonSchemaMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal schema: %w", err)
|
return nil, fmt.Errorf("failed to marshal schema: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -603,12 +603,12 @@ func StreamWithText(
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshal(obj any, target any) error {
|
func unmarshal(obj any, target any) error {
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := jsonv2.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal object: %w", err)
|
return fmt.Errorf("failed to marshal object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(jsonBytes, target); err != nil {
|
if err := jsonv2.Unmarshal(jsonBytes, target); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal into target type: %w", err)
|
return fmt.Errorf("failed to unmarshal into target type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// providerDataJSON is the serialized wrapper used by the registry.
|
// providerDataJSON is the serialized wrapper used by the registry.
|
||||||
type providerDataJSON struct {
|
type providerDataJSON struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data jsontext.Value `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalFunc converts raw JSON into a ProviderOptionsData implementation.
|
// UnmarshalFunc converts raw JSON into a ProviderOptionsData implementation.
|
||||||
|
|
@ -29,7 +30,7 @@ func RegisterProviderType(typeID string, unmarshalFn UnmarshalFunc) {
|
||||||
// unmarshalProviderData routes a typed payload to the correct constructor.
|
// unmarshalProviderData routes a typed payload to the correct constructor.
|
||||||
func unmarshalProviderData(data []byte) (ProviderOptionsData, error) {
|
func unmarshalProviderData(data []byte) (ProviderOptionsData, error) {
|
||||||
var pj providerDataJSON
|
var pj providerDataJSON
|
||||||
if err := json.Unmarshal(data, &pj); err != nil {
|
if err := jsonv2.Unmarshal(data, &pj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ func unmarshalProviderData(data []byte) (ProviderOptionsData, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalProviderDataMap is a helper for unmarshaling maps of provider data.
|
// unmarshalProviderDataMap is a helper for unmarshaling maps of provider data.
|
||||||
func unmarshalProviderDataMap(data map[string]json.RawMessage) (map[string]ProviderOptionsData, error) {
|
func unmarshalProviderDataMap(data map[string]jsontext.Value) (map[string]ProviderOptionsData, error) {
|
||||||
result := make(map[string]ProviderOptionsData)
|
result := make(map[string]ProviderOptionsData)
|
||||||
for provider, rawData := range data {
|
for provider, rawData := range data {
|
||||||
providerData, err := unmarshalProviderData(rawData)
|
providerData, err := unmarshalProviderData(rawData)
|
||||||
|
|
@ -56,12 +57,12 @@ func unmarshalProviderDataMap(data map[string]json.RawMessage) (map[string]Provi
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalProviderOptions unmarshals a map of provider options by type.
|
// UnmarshalProviderOptions unmarshals a map of provider options by type.
|
||||||
func UnmarshalProviderOptions(data map[string]json.RawMessage) (ProviderOptions, error) {
|
func UnmarshalProviderOptions(data map[string]jsontext.Value) (ProviderOptions, error) {
|
||||||
return unmarshalProviderDataMap(data)
|
return unmarshalProviderDataMap(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalProviderMetadata unmarshals a map of provider metadata by type.
|
// UnmarshalProviderMetadata unmarshals a map of provider metadata by type.
|
||||||
func UnmarshalProviderMetadata(data map[string]json.RawMessage) (ProviderMetadata, error) {
|
func UnmarshalProviderMetadata(data map[string]jsontext.Value) (ProviderMetadata, error) {
|
||||||
return unmarshalProviderDataMap(data)
|
return unmarshalProviderDataMap(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,14 +76,14 @@ func UnmarshalProviderMetadata(data map[string]json.RawMessage) (ProviderMetadat
|
||||||
// return fantasy.MarshalProviderType(TypeProviderOptions, plain(o))
|
// return fantasy.MarshalProviderType(TypeProviderOptions, plain(o))
|
||||||
// }
|
// }
|
||||||
func MarshalProviderType[T any](typeID string, data T) ([]byte, error) {
|
func MarshalProviderType[T any](typeID string, data T) ([]byte, error) {
|
||||||
rawData, err := json.Marshal(data)
|
rawData, err := jsonv2.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(providerDataJSON{
|
return jsonv2.Marshal(providerDataJSON{
|
||||||
Type: typeID,
|
Type: typeID,
|
||||||
Data: json.RawMessage(rawData),
|
Data: jsontext.Value(rawData),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,5 +103,5 @@ func MarshalProviderType[T any](typeID string, data T) ([]byte, error) {
|
||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
func UnmarshalProviderType[T any](data []byte, target *T) error {
|
func UnmarshalProviderType[T any](data []byte, target *T) error {
|
||||||
return json.Unmarshal(data, target)
|
return jsonv2.Unmarshal(data, target)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -777,7 +777,7 @@ func toPrompt(prompt fantasy.Prompt, sendReasoningData bool) ([]anthropic.TextBl
|
||||||
// in the Anthropic assistant message; the ProviderExecuted flag
|
// in the Anthropic assistant message; the ProviderExecuted flag
|
||||||
// is only informational metadata about who ran the tool.
|
// is only informational metadata about who ran the tool.
|
||||||
var inputMap map[string]any
|
var inputMap map[string]any
|
||||||
if err := json.Unmarshal([]byte(toolCall.Input), &inputMap); err != nil {
|
if err := jsonv2.Unmarshal([]byte(toolCall.Input), &inputMap); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
toolUseBlock := anthropic.NewToolUseBlock(toolCall.ToolCallID, inputMap, toolCall.ToolName)
|
toolUseBlock := anthropic.NewToolUseBlock(toolCall.ToolCallID, inputMap, toolCall.ToolName)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package anthropic
|
package anthropic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
)
|
)
|
||||||
|
|
@ -18,21 +18,21 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeReasoningOptionMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeReasoningOptionMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ReasoningOptionMetadata
|
var v ReasoningOptionMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeProviderCacheControl, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderCacheControl, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderCacheControlOptions
|
var v ProviderCacheControlOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ package google
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -428,7 +428,7 @@ func toGooglePrompt(prompt fantasy.Prompt) (*genai.Content, []*genai.Content, []
|
||||||
}
|
}
|
||||||
|
|
||||||
var result map[string]any
|
var result map[string]any
|
||||||
err := json.Unmarshal([]byte(toolCall.Input), &result)
|
err := jsonv2.Unmarshal([]byte(toolCall.Input), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -750,7 +750,7 @@ func (g *languageModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args, err := json.Marshal(part.FunctionCall.Args)
|
args, err := jsonv2.Marshal(part.FunctionCall.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
yield(fantasy.StreamPart{
|
yield(fantasy.StreamPart{
|
||||||
Type: fantasy.StreamPartTypeError,
|
Type: fantasy.StreamPartTypeError,
|
||||||
|
|
@ -1380,7 +1380,7 @@ func (g languageModel) mapResponse(response *genai.GenerateContentResponse, warn
|
||||||
content = append(content, fantasy.TextContent{Text: part.Text})
|
content = append(content, fantasy.TextContent{Text: part.Text})
|
||||||
}
|
}
|
||||||
case part.FunctionCall != nil:
|
case part.FunctionCall != nil:
|
||||||
input, err := json.Marshal(part.FunctionCall.Args)
|
input, err := jsonv2.Marshal(part.FunctionCall.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package google
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
)
|
)
|
||||||
|
|
@ -17,14 +17,14 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeReasoningMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeReasoningMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ReasoningMetadata
|
var v ReasoningMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ package openai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -642,7 +642,7 @@ func parseAnnotationsFromDelta(delta openai.ChatCompletionChunkChoiceDelta) []op
|
||||||
|
|
||||||
// Parse the raw JSON to extract annotations
|
// Parse the raw JSON to extract annotations
|
||||||
var deltaData map[string]any
|
var deltaData map[string]any
|
||||||
if err := json.Unmarshal([]byte(delta.RawJSON()), &deltaData); err != nil {
|
if err := jsonv2.Unmarshal([]byte(delta.RawJSON()), &deltaData); err != nil {
|
||||||
return annotations
|
return annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package openai
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
@ -11,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"github.com/openai/openai-go/v2/packages/param"
|
"github.com/openai/openai-go/v2/packages/param"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -427,10 +427,10 @@ func TestToOpenAiPrompt_ToolCalls(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
inputArgs := map[string]any{"foo": "bar123"}
|
inputArgs := map[string]any{"foo": "bar123"}
|
||||||
inputJSON, _ := json.Marshal(inputArgs)
|
inputJSON, _ := jsonv2.Marshal(inputArgs)
|
||||||
|
|
||||||
outputResult := map[string]any{"oof": "321rab"}
|
outputResult := map[string]any{"oof": "321rab"}
|
||||||
outputJSON, _ := json.Marshal(outputResult)
|
outputJSON, _ := jsonv2.Marshal(outputResult)
|
||||||
|
|
||||||
prompt := fantasy.Prompt{
|
prompt := fantasy.Prompt{
|
||||||
{
|
{
|
||||||
|
|
@ -551,7 +551,7 @@ func TestToOpenAiPrompt_AssistantMessages(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
inputArgs := map[string]any{"query": "test"}
|
inputArgs := map[string]any{"query": "test"}
|
||||||
inputJSON, _ := json.Marshal(inputArgs)
|
inputJSON, _ := jsonv2.Marshal(inputArgs)
|
||||||
|
|
||||||
prompt := fantasy.Prompt{
|
prompt := fantasy.Prompt{
|
||||||
{
|
{
|
||||||
|
|
@ -723,7 +723,7 @@ func newMockServer() *mockServer {
|
||||||
// Parse request body
|
// Parse request body
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
var body map[string]any
|
var body map[string]any
|
||||||
json.NewDecoder(r.Body).Decode(&body)
|
jsonv2.UnmarshalRead(r.Body, &body)
|
||||||
call.body = body
|
call.body = body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -731,7 +731,7 @@ func newMockServer() *mockServer {
|
||||||
|
|
||||||
// Return mock response
|
// Return mock response
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(ms.response)
|
jsonv2.MarshalWrite(w, ms.response)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return ms
|
return ms
|
||||||
|
|
@ -2038,7 +2038,7 @@ func newStreamingMockServer() *streamingMockServer {
|
||||||
// Parse request body
|
// Parse request body
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
var body map[string]any
|
var body map[string]any
|
||||||
json.NewDecoder(r.Body).Decode(&body)
|
jsonv2.UnmarshalRead(r.Body, &body)
|
||||||
call.body = body
|
call.body = body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2141,7 +2141,7 @@ func (sms *streamingMockServer) prepareStreamResponse(opts map[string]any) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
initialData, _ := json.Marshal(initialChunk)
|
initialData, _ := jsonv2.Marshal(initialChunk)
|
||||||
chunks = append(chunks, "data: "+string(initialData)+"\n\n")
|
chunks = append(chunks, "data: "+string(initialData)+"\n\n")
|
||||||
|
|
||||||
// Content chunks
|
// Content chunks
|
||||||
|
|
@ -2162,7 +2162,7 @@ func (sms *streamingMockServer) prepareStreamResponse(opts map[string]any) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
contentData, _ := json.Marshal(contentChunk)
|
contentData, _ := jsonv2.Marshal(contentChunk)
|
||||||
chunks = append(chunks, "data: "+string(contentData)+"\n\n")
|
chunks = append(chunks, "data: "+string(contentData)+"\n\n")
|
||||||
|
|
||||||
// Add annotations if this is the last content chunk and we have annotations
|
// Add annotations if this is the last content chunk and we have annotations
|
||||||
|
|
@ -2184,7 +2184,7 @@ func (sms *streamingMockServer) prepareStreamResponse(opts map[string]any) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
annotationData, _ := json.Marshal(annotationChunk)
|
annotationData, _ := jsonv2.Marshal(annotationChunk)
|
||||||
chunks = append(chunks, "data: "+string(annotationData)+"\n\n")
|
chunks = append(chunks, "data: "+string(annotationData)+"\n\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2210,7 +2210,7 @@ func (sms *streamingMockServer) prepareStreamResponse(opts map[string]any) {
|
||||||
finishChunk["choices"].([]map[string]any)[0]["logprobs"] = logprobs
|
finishChunk["choices"].([]map[string]any)[0]["logprobs"] = logprobs
|
||||||
}
|
}
|
||||||
|
|
||||||
finishData, _ := json.Marshal(finishChunk)
|
finishData, _ := jsonv2.Marshal(finishChunk)
|
||||||
chunks = append(chunks, "data: "+string(finishData)+"\n\n")
|
chunks = append(chunks, "data: "+string(finishData)+"\n\n")
|
||||||
|
|
||||||
// Usage chunk
|
// Usage chunk
|
||||||
|
|
@ -2223,7 +2223,7 @@ func (sms *streamingMockServer) prepareStreamResponse(opts map[string]any) {
|
||||||
"choices": []map[string]any{},
|
"choices": []map[string]any{},
|
||||||
"usage": usage,
|
"usage": usage,
|
||||||
}
|
}
|
||||||
usageData, _ := json.Marshal(usageChunk)
|
usageData, _ := jsonv2.Marshal(usageChunk)
|
||||||
chunks = append(chunks, "data: "+string(usageData)+"\n\n")
|
chunks = append(chunks, "data: "+string(usageData)+"\n\n")
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package openai
|
package openai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"github.com/openai/openai-go/v2"
|
"github.com/openai/openai-go/v2"
|
||||||
|
|
@ -33,21 +33,21 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeProviderFileOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderFileOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderFileOptions
|
var v ProviderFileOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderMetadata
|
var v ProviderMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package openai
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -479,7 +479,7 @@ func toResponsesPrompt(prompt fantasy.Prompt, systemMessageMode string) (respons
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
inputJSON, err := json.Marshal(toolCallPart.Input)
|
inputJSON, err := jsonv2.Marshal(toolCallPart.Input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, fantasy.CallWarning{
|
warnings = append(warnings, fantasy.CallWarning{
|
||||||
Type: fantasy.CallWarningTypeOther,
|
Type: fantasy.CallWarningTypeOther,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package openai
|
package openai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
|
@ -18,14 +18,14 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeResponsesProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeResponsesProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ResponsesProviderOptions
|
var v ResponsesProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeResponsesReasoningMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeResponsesReasoningMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ResponsesReasoningMetadata
|
var v ResponsesReasoningMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package openaicompat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
|
@ -50,7 +50,7 @@ func PrepareCallFunc(_ fantasy.LanguageModel, params *openaisdk.ChatCompletionNe
|
||||||
func ExtraContentFunc(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
func ExtraContentFunc(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
||||||
var content []fantasy.Content
|
var content []fantasy.Content
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +84,7 @@ func StreamExtraFunc(chunk openaisdk.ChatCompletionChunk, yield func(fantasy.Str
|
||||||
|
|
||||||
for inx, choice := range chunk.Choices {
|
for inx, choice := range chunk.Choices {
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
yield(fantasy.StreamPart{
|
yield(fantasy.StreamPart{
|
||||||
Type: fantasy.StreamPartTypeError,
|
Type: fantasy.StreamPartTypeError,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package openaicompat
|
package openaicompat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"charm.land/fantasy/providers/openai"
|
"charm.land/fantasy/providers/openai"
|
||||||
|
|
@ -17,7 +17,7 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package openrouter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ func languagePrepareModelCall(_ fantasy.LanguageModel, params *openaisdk.ChatCom
|
||||||
func languageModelExtraContent(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
func languageModelExtraContent(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
||||||
content := make([]fantasy.Content, 0)
|
content := make([]fantasy.Content, 0)
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +212,7 @@ func languageModelStreamExtra(chunk openaisdk.ChatCompletionChunk, yield func(fa
|
||||||
inx := 0
|
inx := 0
|
||||||
choice := chunk.Choices[inx]
|
choice := chunk.Choices[inx]
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
yield(fantasy.StreamPart{
|
yield(fantasy.StreamPart{
|
||||||
Type: fantasy.StreamPartTypeError,
|
Type: fantasy.StreamPartTypeError,
|
||||||
|
|
@ -423,7 +423,7 @@ func languageModelUsage(response openaisdk.ChatCompletion) (fantasy.Usage, fanta
|
||||||
openrouterUsage := UsageAccounting{}
|
openrouterUsage := UsageAccounting{}
|
||||||
usage := response.Usage
|
usage := response.Usage
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(usage.RawJSON()), &openrouterUsage)
|
_ = jsonv2.Unmarshal([]byte(usage.RawJSON()), &openrouterUsage)
|
||||||
|
|
||||||
completionTokenDetails := usage.CompletionTokensDetails
|
completionTokenDetails := usage.CompletionTokensDetails
|
||||||
promptTokenDetails := usage.PromptTokensDetails
|
promptTokenDetails := usage.PromptTokensDetails
|
||||||
|
|
@ -464,7 +464,7 @@ func languageModelStreamUsage(chunk openaisdk.ChatCompletionChunk, _ map[string]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openrouterUsage := UsageAccounting{}
|
openrouterUsage := UsageAccounting{}
|
||||||
_ = json.Unmarshal([]byte(usage.RawJSON()), &openrouterUsage)
|
_ = jsonv2.Unmarshal([]byte(usage.RawJSON()), &openrouterUsage)
|
||||||
streamProviderMetadata.Usage = openrouterUsage
|
streamProviderMetadata.Usage = openrouterUsage
|
||||||
|
|
||||||
if p, ok := chunk.JSON.ExtraFields["provider"]; ok {
|
if p, ok := chunk.JSON.ExtraFields["provider"]; ok {
|
||||||
|
|
@ -810,9 +810,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Text: reasoningPart.Text,
|
Text: reasoningPart.Text,
|
||||||
Signature: metadata.Signature,
|
Signature: metadata.Signature,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
"reasoning": reasoningPart.Text,
|
"reasoning": reasoningPart.Text,
|
||||||
|
|
@ -847,9 +847,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Data: *metadata.EncryptedContent,
|
Data: *metadata.EncryptedContent,
|
||||||
ID: metadata.ItemID,
|
ID: metadata.ItemID,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -883,9 +883,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Data: *metadata.EncryptedContent,
|
Data: *metadata.EncryptedContent,
|
||||||
ID: metadata.ItemID,
|
ID: metadata.ItemID,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -915,9 +915,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Data: metadata.Signature,
|
Data: metadata.Signature,
|
||||||
ID: metadata.ToolID,
|
ID: metadata.ToolID,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -927,9 +927,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Text: reasoningPart.Text,
|
Text: reasoningPart.Text,
|
||||||
Format: "unknown",
|
Format: "unknown",
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package openrouter
|
package openrouter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"charm.land/fantasy/providers/openai"
|
"charm.land/fantasy/providers/openai"
|
||||||
|
|
@ -101,11 +101,11 @@ func WithObjectMode(om fantasy.ObjectMode) Option {
|
||||||
|
|
||||||
func structToMapJSON(s any) (map[string]any, error) {
|
func structToMapJSON(s any) (map[string]any, error) {
|
||||||
var result map[string]any
|
var result map[string]any
|
||||||
jsonBytes, err := json.Marshal(s)
|
jsonBytes, err := jsonv2.Marshal(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(jsonBytes, &result)
|
err = jsonv2.Unmarshal(jsonBytes, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package openrouter
|
package openrouter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
)
|
)
|
||||||
|
|
@ -29,14 +29,14 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderMetadata
|
var v ProviderMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
@ -100,53 +100,53 @@ func (m *ProviderMetadata) UnmarshalJSON(data []byte) error {
|
||||||
// ReasoningOptions represents reasoning options for OpenRouter.
|
// ReasoningOptions represents reasoning options for OpenRouter.
|
||||||
type ReasoningOptions struct {
|
type ReasoningOptions struct {
|
||||||
// Whether reasoning is enabled
|
// Whether reasoning is enabled
|
||||||
Enabled *bool `json:"enabled,omitempty"`
|
Enabled *bool `json:"enabled,omitzero"`
|
||||||
// Whether to exclude reasoning from the response
|
// Whether to exclude reasoning from the response
|
||||||
Exclude *bool `json:"exclude,omitempty"`
|
Exclude *bool `json:"exclude,omitzero"`
|
||||||
// Maximum number of tokens to use for reasoning
|
// Maximum number of tokens to use for reasoning
|
||||||
MaxTokens *int64 `json:"max_tokens,omitempty"`
|
MaxTokens *int64 `json:"max_tokens,omitzero"`
|
||||||
// Reasoning effort level: "low" | "medium" | "high"
|
// Reasoning effort level: "low" | "medium" | "high"
|
||||||
Effort *ReasoningEffort `json:"effort,omitempty"`
|
Effort *ReasoningEffort `json:"effort,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider represents provider routing preferences for OpenRouter.
|
// Provider represents provider routing preferences for OpenRouter.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
// List of provider slugs to try in order (e.g. ["anthropic", "openai"])
|
// List of provider slugs to try in order (e.g. ["anthropic", "openai"])
|
||||||
Order []string `json:"order,omitempty"`
|
Order []string `json:"order,omitzero"`
|
||||||
// Whether to allow backup providers when primary is unavailable (default: true)
|
// Whether to allow backup providers when primary is unavailable (default: true)
|
||||||
AllowFallbacks *bool `json:"allow_fallbacks,omitempty"`
|
AllowFallbacks *bool `json:"allow_fallbacks,omitzero"`
|
||||||
// Only use providers that support all parameters in your request (default: false)
|
// Only use providers that support all parameters in your request (default: false)
|
||||||
RequireParameters *bool `json:"require_parameters,omitempty"`
|
RequireParameters *bool `json:"require_parameters,omitzero"`
|
||||||
// Control whether to use providers that may store data: "allow" | "deny"
|
// Control whether to use providers that may store data: "allow" | "deny"
|
||||||
DataCollection *string `json:"data_collection,omitempty"`
|
DataCollection *string `json:"data_collection,omitzero"`
|
||||||
// List of provider slugs to allow for this request
|
// List of provider slugs to allow for this request
|
||||||
Only []string `json:"only,omitempty"`
|
Only []string `json:"only,omitzero"`
|
||||||
// List of provider slugs to skip for this request
|
// List of provider slugs to skip for this request
|
||||||
Ignore []string `json:"ignore,omitempty"`
|
Ignore []string `json:"ignore,omitzero"`
|
||||||
// List of quantization levels to filter by (e.g. ["int4", "int8"])
|
// List of quantization levels to filter by (e.g. ["int4", "int8"])
|
||||||
Quantizations []string `json:"quantizations,omitempty"`
|
Quantizations []string `json:"quantizations,omitzero"`
|
||||||
// Sort providers by "price" | "throughput" | "latency"
|
// Sort providers by "price" | "throughput" | "latency"
|
||||||
Sort *string `json:"sort,omitempty"`
|
Sort *string `json:"sort,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderOptions represents additional options for OpenRouter provider.
|
// ProviderOptions represents additional options for OpenRouter provider.
|
||||||
type ProviderOptions struct {
|
type ProviderOptions struct {
|
||||||
Reasoning *ReasoningOptions `json:"reasoning,omitempty"`
|
Reasoning *ReasoningOptions `json:"reasoning,omitzero"`
|
||||||
ExtraBody map[string]any `json:"extra_body,omitempty"`
|
ExtraBody map[string]any `json:"extra_body,omitzero"`
|
||||||
IncludeUsage *bool `json:"include_usage,omitempty"`
|
IncludeUsage *bool `json:"include_usage,omitzero"`
|
||||||
// Modify the likelihood of specified tokens appearing in the completion.
|
// Modify the likelihood of specified tokens appearing in the completion.
|
||||||
// Accepts a map that maps tokens (specified by their token ID) to an associated bias value from -100 to 100.
|
// Accepts a map that maps tokens (specified by their token ID) to an associated bias value from -100 to 100.
|
||||||
// The bias is added to the logits generated by the model prior to sampling.
|
// The bias is added to the logits generated by the model prior to sampling.
|
||||||
LogitBias map[string]int64 `json:"logit_bias,omitempty"`
|
LogitBias map[string]int64 `json:"logit_bias,omitzero"`
|
||||||
// Return the log probabilities of the tokens. Including logprobs will increase the response size.
|
// Return the log probabilities of the tokens. Including logprobs will increase the response size.
|
||||||
// Setting to true will return the log probabilities of the tokens that were generated.
|
// Setting to true will return the log probabilities of the tokens that were generated.
|
||||||
LogProbs *bool `json:"log_probs,omitempty"`
|
LogProbs *bool `json:"log_probs,omitzero"`
|
||||||
// Whether to enable parallel function calling during tool use. Default to true.
|
// Whether to enable parallel function calling during tool use. Default to true.
|
||||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
|
ParallelToolCalls *bool `json:"parallel_tool_calls,omitzero"`
|
||||||
// A unique identifier representing your end-user, which can help OpenRouter to monitor and detect abuse.
|
// A unique identifier representing your end-user, which can help OpenRouter to monitor and detect abuse.
|
||||||
User *string `json:"user,omitempty"`
|
User *string `json:"user,omitzero"`
|
||||||
// Provider routing preferences to control request routing behavior
|
// Provider routing preferences to control request routing behavior
|
||||||
Provider *Provider `json:"provider,omitempty"`
|
Provider *Provider `json:"provider,omitzero"`
|
||||||
// Plugins is the ordered list of OpenRouter plugins to enable for this
|
// Plugins is the ordered list of OpenRouter plugins to enable for this
|
||||||
// request. Use WebSearchPlugin to activate online search:
|
// request. Use WebSearchPlugin to activate online search:
|
||||||
//
|
//
|
||||||
|
|
@ -154,15 +154,15 @@ type ProviderOptions struct {
|
||||||
//
|
//
|
||||||
// Refer to https://openrouter.ai/docs/features/web-search for the full
|
// Refer to https://openrouter.ai/docs/features/web-search for the full
|
||||||
// plugin reference.
|
// plugin reference.
|
||||||
Plugins []Plugin `json:"plugins,omitempty"`
|
Plugins []Plugin `json:"plugins,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSearchPlugin configures the OpenRouter web-search plugin.
|
// WebSearchPlugin configures the OpenRouter web-search plugin.
|
||||||
type WebSearchPlugin struct {
|
type WebSearchPlugin struct {
|
||||||
// MaxResults caps how many search results the plugin returns (0 = provider default).
|
// MaxResults caps how many search results the plugin returns (0 = provider default).
|
||||||
MaxResults int `json:"max_results,omitempty"`
|
MaxResults int `json:"max_results,omitzero"`
|
||||||
// SearchPrompt overrides the system prompt used internally by the plugin.
|
// SearchPrompt overrides the system prompt used internally by the plugin.
|
||||||
SearchPrompt string `json:"search_prompt,omitempty"`
|
SearchPrompt string `json:"search_prompt,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin represents a single OpenRouter plugin entry.
|
// Plugin represents a single OpenRouter plugin entry.
|
||||||
|
|
@ -172,7 +172,7 @@ type Plugin struct {
|
||||||
// ID is the plugin identifier (e.g. "web").
|
// ID is the plugin identifier (e.g. "web").
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
// WebSearch holds optional web-search configuration. Omit for defaults.
|
// WebSearch holds optional web-search configuration. Omit for defaults.
|
||||||
WebSearch *WebSearchPlugin `json:"web,omitempty"`
|
WebSearch *WebSearchPlugin `json:"web,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebSearchPlugin is a convenience constructor that returns a Plugin slice
|
// NewWebSearchPlugin is a convenience constructor that returns a Plugin slice
|
||||||
|
|
@ -209,13 +209,13 @@ func (o *ProviderOptions) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// ReasoningDetail represents a reasoning detail for OpenRouter.
|
// ReasoningDetail represents a reasoning detail for OpenRouter.
|
||||||
type ReasoningDetail struct {
|
type ReasoningDetail struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitzero"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitzero"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitzero"`
|
||||||
Data string `json:"data,omitempty"`
|
Data string `json:"data,omitzero"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitzero"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitzero"`
|
||||||
Signature string `json:"signature,omitempty"`
|
Signature string `json:"signature,omitzero"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package vercel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ func languagePrepareModelCall(_ fantasy.LanguageModel, params *openaisdk.ChatCom
|
||||||
func languageModelExtraContent(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
func languageModelExtraContent(choice openaisdk.ChatCompletionChoice) []fantasy.Content {
|
||||||
content := make([]fantasy.Content, 0)
|
content := make([]fantasy.Content, 0)
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Message.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
@ -251,7 +251,7 @@ func languageModelStreamExtra(chunk openaisdk.ChatCompletionChunk, yield func(fa
|
||||||
inx := 0
|
inx := 0
|
||||||
choice := chunk.Choices[inx]
|
choice := chunk.Choices[inx]
|
||||||
reasoningData := ReasoningData{}
|
reasoningData := ReasoningData{}
|
||||||
err := json.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
err := jsonv2.Unmarshal([]byte(choice.Delta.RawJSON()), &reasoningData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
yield(fantasy.StreamPart{
|
yield(fantasy.StreamPart{
|
||||||
Type: fantasy.StreamPartTypeError,
|
Type: fantasy.StreamPartTypeError,
|
||||||
|
|
@ -844,9 +844,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Text: reasoningPart.Text,
|
Text: reasoningPart.Text,
|
||||||
Signature: metadata.Signature,
|
Signature: metadata.Signature,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
"reasoning": reasoningPart.Text,
|
"reasoning": reasoningPart.Text,
|
||||||
|
|
@ -880,9 +880,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Data: *metadata.EncryptedContent,
|
Data: *metadata.EncryptedContent,
|
||||||
ID: metadata.ItemID,
|
ID: metadata.ItemID,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -911,9 +911,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Data: metadata.Signature,
|
Data: metadata.Signature,
|
||||||
ID: metadata.ToolID,
|
ID: metadata.ToolID,
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -923,9 +923,9 @@ func languageModelToPrompt(prompt fantasy.Prompt, _, model string) ([]openaisdk.
|
||||||
Text: reasoningPart.Text,
|
Text: reasoningPart.Text,
|
||||||
Format: "unknown",
|
Format: "unknown",
|
||||||
})
|
})
|
||||||
data, _ := json.Marshal(reasoningDetails)
|
data, _ := jsonv2.Marshal(reasoningDetails)
|
||||||
reasoningDetailsMap := []map[string]any{}
|
reasoningDetailsMap := []map[string]any{}
|
||||||
_ = json.Unmarshal(data, &reasoningDetailsMap)
|
_ = jsonv2.Unmarshal(data, &reasoningDetailsMap)
|
||||||
assistantMsg.SetExtraFields(map[string]any{
|
assistantMsg.SetExtraFields(map[string]any{
|
||||||
"reasoning_details": reasoningDetailsMap,
|
"reasoning_details": reasoningDetailsMap,
|
||||||
})
|
})
|
||||||
|
|
@ -1040,11 +1040,11 @@ func hasVisibleUserContent(content []openaisdk.ChatCompletionContentPartUnionPar
|
||||||
|
|
||||||
func structToMapJSON(s any) (map[string]any, error) {
|
func structToMapJSON(s any) (map[string]any, error) {
|
||||||
var result map[string]any
|
var result map[string]any
|
||||||
jsonBytes, err := json.Marshal(s)
|
jsonBytes, err := jsonv2.Marshal(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(jsonBytes, &result)
|
err = jsonv2.Unmarshal(jsonBytes, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package vercel
|
package vercel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
)
|
)
|
||||||
|
|
@ -17,14 +17,14 @@ const (
|
||||||
func init() {
|
func init() {
|
||||||
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderOptions, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderOptions
|
var v ProviderOptions
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
})
|
})
|
||||||
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
fantasy.RegisterProviderType(TypeProviderMetadata, func(data []byte) (fantasy.ProviderOptionsData, error) {
|
||||||
var v ProviderMetadata
|
var v ProviderMetadata
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
if err := jsonv2.Unmarshal(data, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &v, nil
|
return &v, nil
|
||||||
|
|
@ -52,58 +52,58 @@ const (
|
||||||
// ReasoningOptions represents reasoning configuration for Vercel AI Gateway.
|
// ReasoningOptions represents reasoning configuration for Vercel AI Gateway.
|
||||||
type ReasoningOptions struct {
|
type ReasoningOptions struct {
|
||||||
// Enabled enables reasoning output. When true, the model will provide its reasoning process.
|
// Enabled enables reasoning output. When true, the model will provide its reasoning process.
|
||||||
Enabled *bool `json:"enabled,omitempty"`
|
Enabled *bool `json:"enabled,omitzero"`
|
||||||
// MaxTokens is the maximum number of tokens to allocate for reasoning.
|
// MaxTokens is the maximum number of tokens to allocate for reasoning.
|
||||||
// Cannot be used with Effort.
|
// Cannot be used with Effort.
|
||||||
MaxTokens *int64 `json:"max_tokens,omitempty"`
|
MaxTokens *int64 `json:"max_tokens,omitzero"`
|
||||||
// Effort controls reasoning effort level.
|
// Effort controls reasoning effort level.
|
||||||
// Mutually exclusive with MaxTokens.
|
// Mutually exclusive with MaxTokens.
|
||||||
Effort *ReasoningEffort `json:"effort,omitempty"`
|
Effort *ReasoningEffort `json:"effort,omitzero"`
|
||||||
// Exclude excludes reasoning content from the response but still generates it internally.
|
// Exclude excludes reasoning content from the response but still generates it internally.
|
||||||
Exclude *bool `json:"exclude,omitempty"`
|
Exclude *bool `json:"exclude,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GatewayProviderOptions represents provider routing preferences for Vercel AI Gateway.
|
// GatewayProviderOptions represents provider routing preferences for Vercel AI Gateway.
|
||||||
type GatewayProviderOptions struct {
|
type GatewayProviderOptions struct {
|
||||||
// Order is the list of provider slugs to try in order (e.g. ["vertex", "anthropic"]).
|
// Order is the list of provider slugs to try in order (e.g. ["vertex", "anthropic"]).
|
||||||
Order []string `json:"order,omitempty"`
|
Order []string `json:"order,omitzero"`
|
||||||
// Models is the list of fallback models to try if the primary model fails.
|
// Models is the list of fallback models to try if the primary model fails.
|
||||||
Models []string `json:"models,omitempty"`
|
Models []string `json:"models,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BYOKCredential represents a single provider credential for BYOK.
|
// BYOKCredential represents a single provider credential for BYOK.
|
||||||
type BYOKCredential struct {
|
type BYOKCredential struct {
|
||||||
APIKey string `json:"apiKey,omitempty"`
|
APIKey string `json:"apiKey,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BYOKOptions represents Bring Your Own Key options for Vercel AI Gateway.
|
// BYOKOptions represents Bring Your Own Key options for Vercel AI Gateway.
|
||||||
type BYOKOptions struct {
|
type BYOKOptions struct {
|
||||||
Anthropic map[string][]BYOKCredential `json:"anthropic,omitempty"`
|
Anthropic map[string][]BYOKCredential `json:"anthropic,omitzero"`
|
||||||
OpenAI map[string][]BYOKCredential `json:"openai,omitempty"`
|
OpenAI map[string][]BYOKCredential `json:"openai,omitzero"`
|
||||||
Vertex map[string][]BYOKCredential `json:"vertex,omitempty"`
|
Vertex map[string][]BYOKCredential `json:"vertex,omitzero"`
|
||||||
Bedrock map[string][]BYOKCredential `json:"bedrock,omitempty"`
|
Bedrock map[string][]BYOKCredential `json:"bedrock,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderOptions represents additional options for Vercel AI Gateway provider.
|
// ProviderOptions represents additional options for Vercel AI Gateway provider.
|
||||||
type ProviderOptions struct {
|
type ProviderOptions struct {
|
||||||
// Reasoning configuration for models that support extended thinking.
|
// Reasoning configuration for models that support extended thinking.
|
||||||
Reasoning *ReasoningOptions `json:"reasoning,omitempty"`
|
Reasoning *ReasoningOptions `json:"reasoning,omitzero"`
|
||||||
// ProviderOptions for gateway routing preferences.
|
// ProviderOptions for gateway routing preferences.
|
||||||
ProviderOptions *GatewayProviderOptions `json:"providerOptions,omitempty"`
|
ProviderOptions *GatewayProviderOptions `json:"providerOptions,omitzero"`
|
||||||
// BYOK for request-scoped provider credentials.
|
// BYOK for request-scoped provider credentials.
|
||||||
BYOK *BYOKOptions `json:"byok,omitempty"`
|
BYOK *BYOKOptions `json:"byok,omitzero"`
|
||||||
// User is a unique identifier representing your end-user.
|
// User is a unique identifier representing your end-user.
|
||||||
User *string `json:"user,omitempty"`
|
User *string `json:"user,omitzero"`
|
||||||
// LogitBias modifies the likelihood of specified tokens appearing in the completion.
|
// LogitBias modifies the likelihood of specified tokens appearing in the completion.
|
||||||
LogitBias map[string]int64 `json:"logit_bias,omitempty"`
|
LogitBias map[string]int64 `json:"logit_bias,omitzero"`
|
||||||
// LogProbs returns the log probabilities of the tokens.
|
// LogProbs returns the log probabilities of the tokens.
|
||||||
LogProbs *bool `json:"logprobs,omitempty"`
|
LogProbs *bool `json:"logprobs,omitzero"`
|
||||||
// TopLogProbs is the number of top log probabilities to return.
|
// TopLogProbs is the number of top log probabilities to return.
|
||||||
TopLogProbs *int64 `json:"top_logprobs,omitempty"`
|
TopLogProbs *int64 `json:"top_logprobs,omitzero"`
|
||||||
// ParallelToolCalls enables parallel function calling during tool use.
|
// ParallelToolCalls enables parallel function calling during tool use.
|
||||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
|
ParallelToolCalls *bool `json:"parallel_tool_calls,omitzero"`
|
||||||
// ExtraBody for additional request body fields.
|
// ExtraBody for additional request body fields.
|
||||||
ExtraBody map[string]any `json:"extra_body,omitempty"`
|
ExtraBody map[string]any `json:"extra_body,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options implements the ProviderOptionsData interface for ProviderOptions.
|
// Options implements the ProviderOptionsData interface for ProviderOptions.
|
||||||
|
|
@ -128,7 +128,7 @@ func (o *ProviderOptions) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// ProviderMetadata represents metadata from Vercel AI Gateway provider.
|
// ProviderMetadata represents metadata from Vercel AI Gateway provider.
|
||||||
type ProviderMetadata struct {
|
type ProviderMetadata struct {
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options implements the ProviderOptionsData interface for ProviderMetadata.
|
// Options implements the ProviderOptionsData interface for ProviderMetadata.
|
||||||
|
|
@ -153,20 +153,20 @@ func (m *ProviderMetadata) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// ReasoningDetail represents a reasoning detail from Vercel AI Gateway.
|
// ReasoningDetail represents a reasoning detail from Vercel AI Gateway.
|
||||||
type ReasoningDetail struct {
|
type ReasoningDetail struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitzero"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitzero"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitzero"`
|
||||||
Data string `json:"data,omitempty"`
|
Data string `json:"data,omitzero"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitzero"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitzero"`
|
||||||
Signature string `json:"signature,omitempty"`
|
Signature string `json:"signature,omitzero"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReasoningData represents reasoning data from Vercel AI Gateway response.
|
// ReasoningData represents reasoning data from Vercel AI Gateway response.
|
||||||
type ReasoningData struct {
|
type ReasoningData struct {
|
||||||
Reasoning string `json:"reasoning,omitempty"`
|
Reasoning string `json:"reasoning,omitzero"`
|
||||||
ReasoningDetails []ReasoningDetail `json:"reasoning_details,omitempty"`
|
ReasoningDetails []ReasoningDetail `json:"reasoning_details,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReasoningEffortOption creates a pointer to a ReasoningEffort value.
|
// ReasoningEffortOption creates a pointer to a ReasoningEffort value.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package providertests
|
package providertests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
|
@ -24,13 +24,13 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var raw struct {
|
var raw struct {
|
||||||
ProviderOptions map[string]map[string]any `json:"provider_options"`
|
ProviderOptions map[string]map[string]any `json:"provider_options"`
|
||||||
}
|
}
|
||||||
require.NoError(t, json.Unmarshal(data, &raw))
|
require.NoError(t, jsonv2.Unmarshal(data, &raw))
|
||||||
|
|
||||||
po, ok := raw.ProviderOptions[openai.Name]
|
po, ok := raw.ProviderOptions[openai.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -41,7 +41,7 @@ func TestProviderRegistry_Serialization_OpenAIOptions(t *testing.T) {
|
||||||
require.Equal(t, "tester", inner["user"])
|
require.Equal(t, "tester", inner["user"])
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
got, ok := decoded.ProviderOptions[openai.Name]
|
got, ok := decoded.ProviderOptions[openai.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -66,14 +66,14 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// JSON should include the typed wrapper with constant TypeResponsesProviderOptions
|
// JSON should include the typed wrapper with constant TypeResponsesProviderOptions
|
||||||
var raw struct {
|
var raw struct {
|
||||||
ProviderOptions map[string]map[string]any `json:"provider_options"`
|
ProviderOptions map[string]map[string]any `json:"provider_options"`
|
||||||
}
|
}
|
||||||
require.NoError(t, json.Unmarshal(data, &raw))
|
require.NoError(t, jsonv2.Unmarshal(data, &raw))
|
||||||
|
|
||||||
po := raw.ProviderOptions[openai.Name]
|
po := raw.ProviderOptions[openai.Name]
|
||||||
require.Equal(t, openai.TypeResponsesProviderOptions, po["type"]) // no magic strings
|
require.Equal(t, openai.TypeResponsesProviderOptions, po["type"]) // no magic strings
|
||||||
|
|
@ -84,7 +84,7 @@ func TestProviderRegistry_Serialization_OpenAIResponses(t *testing.T) {
|
||||||
|
|
||||||
// Unmarshal back and assert concrete type
|
// Unmarshal back and assert concrete type
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
got := decoded.ProviderOptions[openai.Name]
|
got := decoded.ProviderOptions[openai.Name]
|
||||||
reqOpts, ok := got.(*openai.ResponsesProviderOptions)
|
reqOpts, ok := got.(*openai.ResponsesProviderOptions)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -109,7 +109,7 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(resp)
|
data, err := jsonv2.Marshal(resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Ensure the provider metadata is wrapped with type using constant
|
// Ensure the provider metadata is wrapped with type using constant
|
||||||
|
|
@ -119,7 +119,7 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
|
||||||
Data map[string]any `json:"data"`
|
Data map[string]any `json:"data"`
|
||||||
} `json:"content"`
|
} `json:"content"`
|
||||||
}
|
}
|
||||||
require.NoError(t, json.Unmarshal(data, &raw))
|
require.NoError(t, jsonv2.Unmarshal(data, &raw))
|
||||||
require.Greater(t, len(raw.Content), 0)
|
require.Greater(t, len(raw.Content), 0)
|
||||||
tc := raw.Content[0]
|
tc := raw.Content[0]
|
||||||
pm, ok := tc.Data["provider_metadata"].(map[string]any)
|
pm, ok := tc.Data["provider_metadata"].(map[string]any)
|
||||||
|
|
@ -133,7 +133,7 @@ func TestProviderRegistry_Serialization_OpenAIResponsesReasoningMetadata(t *test
|
||||||
|
|
||||||
// Unmarshal back
|
// Unmarshal back
|
||||||
var decoded fantasy.Response
|
var decoded fantasy.Response
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
pmDecoded := decoded.Content[0].(fantasy.TextContent).ProviderMetadata
|
pmDecoded := decoded.Content[0].(fantasy.TextContent).ProviderMetadata
|
||||||
val, ok := pmDecoded[openai.Name]
|
val, ok := pmDecoded[openai.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -157,11 +157,11 @@ func TestProviderRegistry_Serialization_AnthropicOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
got, ok := decoded.ProviderOptions[anthropic.Name]
|
got, ok := decoded.ProviderOptions[anthropic.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -185,11 +185,11 @@ func TestProviderRegistry_Serialization_GoogleOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
got, ok := decoded.ProviderOptions[google.Name]
|
got, ok := decoded.ProviderOptions[google.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -214,11 +214,11 @@ func TestProviderRegistry_Serialization_OpenRouterOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
got, ok := decoded.ProviderOptions[openrouter.Name]
|
got, ok := decoded.ProviderOptions[openrouter.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -245,11 +245,11 @@ func TestProviderRegistry_Serialization_OpenAICompatOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
got, ok := decoded.ProviderOptions[openaicompat.Name]
|
got, ok := decoded.ProviderOptions[openaicompat.Name]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
@ -277,11 +277,11 @@ func TestProviderRegistry_MultiProvider(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
require.NoError(t, json.Unmarshal(data, &decoded))
|
require.NoError(t, jsonv2.Unmarshal(data, &decoded))
|
||||||
|
|
||||||
// Check OpenAI options
|
// Check OpenAI options
|
||||||
openaiOpt, ok := decoded.ProviderOptions[openai.Name]
|
openaiOpt, ok := decoded.ProviderOptions[openai.Name]
|
||||||
|
|
@ -312,7 +312,7 @@ func TestProviderRegistry_ErrorHandling(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var msg fantasy.Message
|
var msg fantasy.Message
|
||||||
err := json.Unmarshal([]byte(invalidJSON), &msg)
|
err := jsonv2.Unmarshal([]byte(invalidJSON), &msg)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), "unknown provider data type")
|
require.Contains(t, err.Error(), "unknown provider data type")
|
||||||
})
|
})
|
||||||
|
|
@ -327,7 +327,7 @@ func TestProviderRegistry_ErrorHandling(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var msg fantasy.Message
|
var msg fantasy.Message
|
||||||
err := json.Unmarshal([]byte(invalidJSON), &msg)
|
err := jsonv2.Unmarshal([]byte(invalidJSON), &msg)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -364,11 +364,11 @@ func TestProviderRegistry_AllTypesRegistered(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal and unmarshal
|
// Marshal and unmarshal
|
||||||
data, err := json.Marshal(msg)
|
data, err := jsonv2.Marshal(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Message
|
var decoded fantasy.Message
|
||||||
err = json.Unmarshal(data, &decoded)
|
err = jsonv2.Unmarshal(data, &decoded)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Verify the provider options exist
|
// Verify the provider options exist
|
||||||
|
|
@ -404,11 +404,11 @@ func TestProviderRegistry_AllTypesRegistered(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal and unmarshal
|
// Marshal and unmarshal
|
||||||
data, err := json.Marshal(resp)
|
data, err := jsonv2.Marshal(resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var decoded fantasy.Response
|
var decoded fantasy.Response
|
||||||
err = json.Unmarshal(data, &decoded)
|
err = jsonv2.Unmarshal(data, &decoded)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Verify the provider metadata exists
|
// Verify the provider metadata exists
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -259,7 +259,7 @@ func ParsePartialJSON(text string) (any, ParseState, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var result any
|
var result any
|
||||||
if err := json.Unmarshal([]byte(text), &result); err == nil {
|
if err := jsonv2.Unmarshal([]byte(text), &result); err == nil {
|
||||||
return result, ParseStateSuccessful, nil
|
return result, ParseStateSuccessful, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,7 +268,7 @@ func ParsePartialJSON(text string) (any, ParseState, error) {
|
||||||
return nil, ParseStateFailed, fmt.Errorf("json repair failed: %w", err)
|
return nil, ParseStateFailed, fmt.Errorf("json repair failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(repaired), &result); err != nil {
|
if err := jsonv2.Unmarshal([]byte(repaired), &result); err != nil {
|
||||||
return nil, ParseStateFailed, fmt.Errorf("failed to parse repaired json: %w", err)
|
return nil, ParseStateFailed, fmt.Errorf("failed to parse repaired json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,7 +317,7 @@ func ValidateAgainstSchema(obj any, schema Schema) error {
|
||||||
// This is a convenience wrapper for use sites that hold the schema as a map rather
|
// This is a convenience wrapper for use sites that hold the schema as a map rather
|
||||||
// than the typed Schema struct.
|
// than the typed Schema struct.
|
||||||
func ValidateAgainstSchemaMap(obj any, schemaMap map[string]any) error {
|
func ValidateAgainstSchemaMap(obj any, schemaMap map[string]any) error {
|
||||||
schemaBytes, err := json.Marshal(schemaMap)
|
schemaBytes, err := jsonv2.Marshal(schemaMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal schema map: %w", err)
|
return fmt.Errorf("failed to marshal schema map: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +341,7 @@ func ValidateAgainstSchemaMap(obj any, schemaMap map[string]any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAgainstSchema(obj any, schema Schema) error {
|
func validateAgainstSchema(obj any, schema Schema) error {
|
||||||
jsonSchemaBytes, err := json.Marshal(schema)
|
jsonSchemaBytes, err := jsonv2.Marshal(schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal schema: %w", err)
|
return fmt.Errorf("failed to marshal schema: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"charm.land/fantasy/schema"
|
"charm.land/fantasy/schema"
|
||||||
|
|
@ -78,7 +78,7 @@ func NewMediaResponse(data []byte, mediaType string) ToolResponse {
|
||||||
// WithResponseMetadata adds metadata to a response.
|
// WithResponseMetadata adds metadata to a response.
|
||||||
func WithResponseMetadata(response ToolResponse, metadata any) ToolResponse {
|
func WithResponseMetadata(response ToolResponse, metadata any) ToolResponse {
|
||||||
if metadata != nil {
|
if metadata != nil {
|
||||||
metadataBytes, err := json.Marshal(metadata)
|
metadataBytes, err := jsonv2.Marshal(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +167,7 @@ func (w *funcToolWrapper[TInput]) Info() ToolInfo {
|
||||||
|
|
||||||
func (w *funcToolWrapper[TInput]) Run(ctx context.Context, params ToolCall) (ToolResponse, error) {
|
func (w *funcToolWrapper[TInput]) Run(ctx context.Context, params ToolCall) (ToolResponse, error) {
|
||||||
var input TInput
|
var input TInput
|
||||||
if err := json.Unmarshal([]byte(params.Input), &input); err != nil {
|
if err := jsonv2.Unmarshal([]byte(params.Input), &input); err != nil {
|
||||||
return NewTextErrorResponse(fmt.Sprintf("invalid parameters: %s", err)), nil
|
return NewTextErrorResponse(fmt.Sprintf("invalid parameters: %s", err)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ func extractToolDependencies(input string) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var v any
|
var v any
|
||||||
if err := json.Unmarshal([]byte(input), &v); err != nil {
|
if err := jsonv2.Unmarshal([]byte(input), &v); err != nil {
|
||||||
// If the tool input isn't valid JSON, treat it as having no dependencies.
|
// If the tool input isn't valid JSON, treat it as having no dependencies.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -233,7 +233,7 @@ func resolveToolRefsInInput(input string, results map[string]ToolResultContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
var v any
|
var v any
|
||||||
if err := json.Unmarshal([]byte(input), &v); err != nil {
|
if err := jsonv2.Unmarshal([]byte(input), &v); err != nil {
|
||||||
// Not JSON; nothing to resolve.
|
// Not JSON; nothing to resolve.
|
||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ func resolveToolRefsInInput(input string, results map[string]ToolResultContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(updated)
|
b, err := jsonv2.Marshal(updated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +328,7 @@ func resolveToolRefValue(ref string, results map[string]ToolResultContent) (any,
|
||||||
|
|
||||||
// Path resolution: interpret base as JSON and walk.
|
// Path resolution: interpret base as JSON and walk.
|
||||||
var cur any
|
var cur any
|
||||||
if err := json.Unmarshal([]byte(base), &cur); err != nil {
|
if err := jsonv2.Unmarshal([]byte(base), &cur); err != nil {
|
||||||
return nil, fmt.Errorf("tool ref %q path requires JSON output, got non-JSON", ref)
|
return nil, fmt.Errorf("tool ref %q path requires JSON output, got non-JSON", ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ package conversations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
|
|
@ -100,7 +100,7 @@ func (s *Store) EditMessage(ctx context.Context, p EditMessageParams) (sqlc.Agen
|
||||||
return sqlc.AgentMessage{}, err
|
return sqlc.AgentMessage{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
metaJSON, _ := json.Marshal(p.Metadata)
|
metaJSON, _ := jsonv2.Marshal(p.Metadata)
|
||||||
|
|
||||||
// Best-effort revision record — a failure here does not abort the edit.
|
// Best-effort revision record — a failure here does not abort the edit.
|
||||||
_, _ = s.q.AddAgentMessageRevision(ctx, sqlc.AddAgentMessageRevisionParams{
|
_, _ = s.q.AddAgentMessageRevision(ctx, sqlc.AddAgentMessageRevisionParams{
|
||||||
|
|
@ -174,7 +174,7 @@ func (s *Store) ForkFromCheckpoint(ctx context.Context, p ForkFromCheckpointPara
|
||||||
Messages []msgSnapshot `json:"messages"`
|
Messages []msgSnapshot `json:"messages"`
|
||||||
}
|
}
|
||||||
var snap snapshot
|
var snap snapshot
|
||||||
_ = json.Unmarshal(runState.SnapshotJson, &snap)
|
_ = jsonv2.Unmarshal([]byte(runState.SnapshotJson), &snap)
|
||||||
|
|
||||||
conv, err := s.q.CreateAgentConversation(ctx, sqlc.CreateAgentConversationParams{
|
conv, err := s.q.CreateAgentConversation(ctx, sqlc.CreateAgentConversationParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -188,7 +188,7 @@ func (s *Store) ForkFromCheckpoint(ctx context.Context, p ForkFromCheckpointPara
|
||||||
"checkpoint_name": cpName,
|
"checkpoint_name": cpName,
|
||||||
"run_state_id": cp.RunStateID.String(),
|
"run_state_id": cp.RunStateID.String(),
|
||||||
}
|
}
|
||||||
forkMetaJSON, _ := json.Marshal(forkMeta)
|
forkMetaJSON, _ := jsonv2.Marshal(forkMeta)
|
||||||
|
|
||||||
_, _ = s.q.CreateAgentConversationFork(ctx, sqlc.CreateAgentConversationForkParams{
|
_, _ = s.q.CreateAgentConversationFork(ctx, sqlc.CreateAgentConversationForkParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -209,7 +209,7 @@ func (s *Store) ForkFromCheckpoint(ctx context.Context, p ForkFromCheckpointPara
|
||||||
"checkpoint_name": cpName,
|
"checkpoint_name": cpName,
|
||||||
"run_state_id": cp.RunStateID.String(),
|
"run_state_id": cp.RunStateID.String(),
|
||||||
}
|
}
|
||||||
seedMetaJSON, _ := json.Marshal(seedMeta)
|
seedMetaJSON, _ := jsonv2.Marshal(seedMeta)
|
||||||
|
|
||||||
for _, m := range msgs {
|
for _, m := range msgs {
|
||||||
if m.Role != "user" && m.Role != "assistant" {
|
if m.Role != "user" && m.Role != "assistant" {
|
||||||
|
|
@ -277,7 +277,7 @@ func (s *Store) MergeAsLinkedContext(ctx context.Context, p MergeAsLinkedContext
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]any{"source": "merge_as_linked_context"}
|
meta := map[string]any{"source": "merge_as_linked_context"}
|
||||||
metaJSON, _ := json.Marshal(meta)
|
metaJSON, _ := jsonv2.Marshal(meta)
|
||||||
|
|
||||||
_, _ = s.q.CreateAgentConversationLink(ctx, sqlc.CreateAgentConversationLinkParams{
|
_, _ = s.q.CreateAgentConversationLink(ctx, sqlc.CreateAgentConversationLinkParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -321,7 +321,7 @@ type AncestryParams struct {
|
||||||
// AncestryResult is the structured result of an Ancestry query.
|
// AncestryResult is the structured result of an Ancestry query.
|
||||||
type AncestryResult struct {
|
type AncestryResult struct {
|
||||||
Conversation sqlc.AgentConversation `json:"conversation"`
|
Conversation sqlc.AgentConversation `json:"conversation"`
|
||||||
ForkParent *sqlc.AgentConversationFork `json:"fork_parent,omitempty"`
|
ForkParent *sqlc.AgentConversationFork `json:"fork_parent,omitzero"`
|
||||||
ForkChildren []sqlc.AgentConversationFork `json:"fork_children"`
|
ForkChildren []sqlc.AgentConversationFork `json:"fork_children"`
|
||||||
Links []sqlc.AgentConversationLink `json:"links"`
|
Links []sqlc.AgentConversationLink `json:"links"`
|
||||||
}
|
}
|
||||||
|
|
@ -434,7 +434,7 @@ type GraphParams struct {
|
||||||
// GraphNode is a single conversation node in the fork/link graph.
|
// GraphNode is a single conversation node in the fork/link graph.
|
||||||
type GraphNode struct {
|
type GraphNode struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title *string `json:"title,omitempty"`
|
Title *string `json:"title,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphEdge is a directed edge between two conversation nodes.
|
// GraphEdge is a directed edge between two conversation nodes.
|
||||||
|
|
@ -444,9 +444,9 @@ type GraphEdge struct {
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
// CheckpointID is set on fork edges.
|
// CheckpointID is set on fork edges.
|
||||||
CheckpointID *string `json:"checkpoint_id,omitempty"`
|
CheckpointID *string `json:"checkpoint_id,omitzero"`
|
||||||
// Kind is set on link edges (e.g. "merge").
|
// Kind is set on link edges (e.g. "merge").
|
||||||
Kind *string `json:"kind,omitempty"`
|
Kind *string `json:"kind,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphResult is the full fork/link graph rooted at a conversation.
|
// GraphResult is the full fork/link graph rooted at a conversation.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
memstore "github.com/sipeed/picoclaw/pkg/memory/store"
|
memstore "github.com/sipeed/picoclaw/pkg/memory/store"
|
||||||
"github.com/sipeed/picoclaw/pkg/tools"
|
"github.com/sipeed/picoclaw/pkg/tools"
|
||||||
|
|
@ -84,7 +84,7 @@ func (t *MemGPTTool) Parameters() map[string]interface{} {
|
||||||
|
|
||||||
func (t *MemGPTTool) Execute(ctx context.Context, args map[string]interface{}) *tools.ToolResult {
|
func (t *MemGPTTool) Execute(ctx context.Context, args map[string]interface{}) *tools.ToolResult {
|
||||||
// Marshal the args back to JSON for the inner MemoryTool.Execute()
|
// Marshal the args back to JSON for the inner MemoryTool.Execute()
|
||||||
input, err := json.Marshal(args)
|
input, err := jsonv2.Marshal(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tools.ErrorResult("invalid arguments: " + err.Error())
|
return tools.ErrorResult("invalid arguments: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ package mentions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
|
|
@ -76,7 +76,7 @@ func (s *Store) Add(ctx context.Context, p AddParams) (sqlc.AgentMention, error)
|
||||||
return sqlc.AgentMention{}, pcerrors.Wrapf(pcerrors.CodeInvalidArgument, err, "parse target_id %q", targetIDStr)
|
return sqlc.AgentMention{}, pcerrors.Wrapf(pcerrors.CodeInvalidArgument, err, "parse target_id %q", targetIDStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
metaJSON, _ := json.Marshal(p.Metadata)
|
metaJSON, _ := jsonv2.Marshal(p.Metadata)
|
||||||
|
|
||||||
return s.q.AddAgentMention(ctx, sqlc.AddAgentMentionParams{
|
return s.q.AddAgentMention(ctx, sqlc.AddAgentMentionParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ func (r OffloadingToolRuntime) Execute(ctx context.Context, tools []fantasy.Agen
|
||||||
fullKey := toolResultFullKey(r.ConversationID, r.RunID, stepIndex, tc.ToolCallID)
|
fullKey := toolResultFullKey(r.ConversationID, r.RunID, stepIndex, tc.ToolCallID)
|
||||||
|
|
||||||
payload, payloadType, payloadText := toolResultPayload(res)
|
payload, payloadType, payloadText := toolResultPayload(res)
|
||||||
b, marshalErr := json.Marshal(payload)
|
b, marshalErr := jsonv2.Marshal(payload)
|
||||||
if marshalErr != nil {
|
if marshalErr != nil {
|
||||||
b = []byte(`{"error":"failed to marshal tool result payload"}`)
|
b = []byte(`{"error":"failed to marshal tool result payload"}`)
|
||||||
payloadType = "error"
|
payloadType = "error"
|
||||||
|
|
@ -134,7 +134,7 @@ func (r OffloadingToolRuntime) Execute(ctx context.Context, tools []fantasy.Agen
|
||||||
"result_type": payloadType,
|
"result_type": payloadType,
|
||||||
"step_index": stepIndex,
|
"step_index": stepIndex,
|
||||||
}
|
}
|
||||||
metaJSON, _ := json.Marshal(meta)
|
metaJSON, _ := jsonv2.Marshal(meta)
|
||||||
|
|
||||||
var previewPtr *string
|
var previewPtr *string
|
||||||
if strings.TrimSpace(preview) != "" {
|
if strings.TrimSpace(preview) != "" {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
"github.com/sipeed/picoclaw/pkg/memory/sqlc"
|
"github.com/sipeed/picoclaw/pkg/memory/sqlc"
|
||||||
|
|
@ -50,8 +52,8 @@ func (s *StateStore) UpdateRunStatus(ctx context.Context, runID ids.UUID, status
|
||||||
|
|
||||||
metaJSON := json.RawMessage(`{}`)
|
metaJSON := json.RawMessage(`{}`)
|
||||||
if meta != nil {
|
if meta != nil {
|
||||||
if b, err := json.Marshal(meta); err == nil {
|
if b, err := jsonv2.Marshal(meta); err == nil {
|
||||||
metaJSON = b
|
metaJSON = json.RawMessage(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,8 +77,8 @@ func (s *StateStore) AddRunState(ctx context.Context, runID ids.UUID, stepIndex
|
||||||
|
|
||||||
snapJSON := json.RawMessage(`{}`)
|
snapJSON := json.RawMessage(`{}`)
|
||||||
if snapshot != nil {
|
if snapshot != nil {
|
||||||
if b, err := json.Marshal(snapshot); err == nil {
|
if b, err := jsonv2.Marshal(snapshot); err == nil {
|
||||||
snapJSON = b
|
snapJSON = json.RawMessage(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +101,8 @@ func (s *StateStore) AddTransition(ctx context.Context, runID ids.UUID, t fantas
|
||||||
|
|
||||||
metaJSON := json.RawMessage(`{}`)
|
metaJSON := json.RawMessage(`{}`)
|
||||||
if t.Meta != nil {
|
if t.Meta != nil {
|
||||||
if b, err := json.Marshal(t.Meta); err == nil {
|
if b, err := jsonv2.Marshal(t.Meta); err == nil {
|
||||||
metaJSON = b
|
metaJSON = json.RawMessage(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,8 +154,8 @@ func (s *CheckpointStore) CreateCheckpoint(ctx context.Context, conversationID i
|
||||||
|
|
||||||
metaJSON := json.RawMessage(`{}`)
|
metaJSON := json.RawMessage(`{}`)
|
||||||
if meta != nil {
|
if meta != nil {
|
||||||
if b, err := json.Marshal(meta); err == nil {
|
if b, err := jsonv2.Marshal(meta); err == nil {
|
||||||
metaJSON = b
|
metaJSON = json.RawMessage(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ package threads
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
|
|
@ -45,7 +45,7 @@ func (s *Store) Create(ctx context.Context, p CreateParams) (sqlc.AgentThread, e
|
||||||
return sqlc.AgentThread{}, pcerrors.Wrapf(pcerrors.CodeInvalidArgument, err, "parse conversation_id %q", convIDStr)
|
return sqlc.AgentThread{}, pcerrors.Wrapf(pcerrors.CodeInvalidArgument, err, "parse conversation_id %q", convIDStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
metaJSON, _ := json.Marshal(p.Metadata)
|
metaJSON, _ := jsonv2.Marshal(p.Metadata)
|
||||||
|
|
||||||
return s.q.CreateAgentThread(ctx, sqlc.CreateAgentThreadParams{
|
return s.q.CreateAgentThread(ctx, sqlc.CreateAgentThreadParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
@ -108,7 +108,7 @@ func (s *Store) AddMessage(ctx context.Context, p AddMessageParams) (sqlc.AgentT
|
||||||
return sqlc.AgentThreadMessage{}, pcerrors.New(pcerrors.CodeInvalidArgument, "content is empty")
|
return sqlc.AgentThreadMessage{}, pcerrors.New(pcerrors.CodeInvalidArgument, "content is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
metaJSON, _ := json.Marshal(p.Metadata)
|
metaJSON, _ := jsonv2.Marshal(p.Metadata)
|
||||||
|
|
||||||
return s.q.AddAgentThreadMessage(ctx, sqlc.AddAgentThreadMessageParams{
|
return s.q.AddAgentThreadMessage(ctx, sqlc.AddAgentThreadMessageParams{
|
||||||
ID: ids.New(),
|
ID: ids.New(),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
"github.com/sipeed/picoclaw/pkg/memory/sqlc"
|
"github.com/sipeed/picoclaw/pkg/memory/sqlc"
|
||||||
|
|
@ -13,23 +15,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ToolResultSearchView struct {
|
type ToolResultSearchView struct {
|
||||||
StartLine int `json:"start_line,omitempty" description:"Optional. 1-indexed start line (inclusive)."`
|
StartLine int `json:"start_line,omitzero" description:"Optional. 1-indexed start line (inclusive)."`
|
||||||
EndLine int `json:"end_line,omitempty" description:"Optional. 1-indexed end line (inclusive)."`
|
EndLine int `json:"end_line,omitzero" description:"Optional. 1-indexed end line (inclusive)."`
|
||||||
MaxLines int `json:"max_lines,omitempty" description:"Optional. Default 30, max 200."`
|
MaxLines int `json:"max_lines,omitzero" description:"Optional. Default 30, max 200."`
|
||||||
StartChunk int `json:"start_chunk,omitempty" description:"Optional. 0-indexed start chunk (inclusive)."`
|
StartChunk int `json:"start_chunk,omitzero" description:"Optional. 0-indexed start chunk (inclusive)."`
|
||||||
EndChunk int `json:"end_chunk,omitempty" description:"Optional. 0-indexed end chunk (inclusive)."`
|
EndChunk int `json:"end_chunk,omitzero" description:"Optional. 0-indexed end chunk (inclusive)."`
|
||||||
MaxChunks int `json:"max_chunks,omitempty" description:"Optional. Default 3, max 20."`
|
MaxChunks int `json:"max_chunks,omitzero" description:"Optional. Default 3, max 20."`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolResultSearchInput struct {
|
type ToolResultSearchInput struct {
|
||||||
ConversationID string `json:"conversation_id,omitempty" description:"Optional. Agent conversation UUID."`
|
ConversationID string `json:"conversation_id,omitzero" description:"Optional. Agent conversation UUID."`
|
||||||
RunID string `json:"run_id,omitempty" description:"Optional. Agent run UUID."`
|
RunID string `json:"run_id,omitzero" description:"Optional. Agent run UUID."`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty" description:"Optional. Tool call id to fetch (requires run_id)."`
|
ToolCallID string `json:"tool_call_id,omitzero" description:"Optional. Tool call id to fetch (requires run_id)."`
|
||||||
ToolName string `json:"tool_name,omitempty" description:"Optional. Filter by tool name."`
|
ToolName string `json:"tool_name,omitzero" description:"Optional. Filter by tool name."`
|
||||||
Query string `json:"query,omitempty" description:"Optional. Case-insensitive substring match on tool_name/tool_call_id/summary."`
|
Query string `json:"query,omitzero" description:"Optional. Case-insensitive substring match on tool_name/tool_call_id/summary."`
|
||||||
|
|
||||||
Limit int `json:"limit,omitempty" description:"Optional. Default 5, max 50."`
|
Limit int `json:"limit,omitzero" description:"Optional. Default 5, max 50."`
|
||||||
View *ToolResultSearchView `json:"view,omitempty" description:"Optional. File view range for each result."`
|
View *ToolResultSearchView `json:"view,omitzero" description:"Optional. File view range for each result."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewToolResultSearchTool creates the tool_result_search agent tool for
|
// NewToolResultSearchTool creates the tool_result_search agent tool for
|
||||||
|
|
@ -92,12 +94,12 @@ func NewToolResultSearchTool(q *sqlc.Queries, kv KVDelegate) fantasy.AgentTool {
|
||||||
StepIndex int64 `json:"step_index"`
|
StepIndex int64 `json:"step_index"`
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
ToolName string `json:"tool_name"`
|
ToolName string `json:"tool_name"`
|
||||||
Preview *string `json:"preview,omitempty"`
|
Preview *string `json:"preview,omitzero"`
|
||||||
FullKey string `json:"full_key"`
|
FullKey string `json:"full_key"`
|
||||||
ChunkCount int64 `json:"chunk_count"`
|
ChunkCount int64 `json:"chunk_count"`
|
||||||
View string `json:"view"`
|
View string `json:"view"`
|
||||||
ViewRange map[string]int `json:"view_range"`
|
ViewRange map[string]int `json:"view_range"`
|
||||||
Metadata json.RawMessage `json:"metadata_json"`
|
Metadata jsontext.Value `json:"metadata_json"` // sqlc gives json.RawMessage; both are []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
out := struct {
|
out := struct {
|
||||||
|
|
@ -131,11 +133,11 @@ func NewToolResultSearchTool(q *sqlc.Queries, kv KVDelegate) fantasy.AgentTool {
|
||||||
ChunkCount: r.ChunkCount,
|
ChunkCount: r.ChunkCount,
|
||||||
View: sel,
|
View: sel,
|
||||||
ViewRange: viewRange,
|
ViewRange: viewRange,
|
||||||
Metadata: r.MetadataJson,
|
Metadata: jsontext.Value(r.MetadataJson),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
b, _ := json.Marshal(out)
|
b, _ := jsonv2.Marshal(out)
|
||||||
return fantasy.NewTextResponse(string(b)), nil
|
return fantasy.NewTextResponse(string(b)), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package agent_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -58,13 +58,13 @@ func invokeSearch(t *testing.T, tool fantasy.AgentTool, input agent.ToolResultSe
|
||||||
require.False(t, resp.IsError, "search tool must not error: %s", resp.Content)
|
require.False(t, resp.IsError, "search tool must not error: %s", resp.Content)
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
require.NoError(t, json.Unmarshal([]byte(resp.Content), &out))
|
require.NoError(t, jsonv2.Unmarshal([]byte(resp.Content), &out))
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalInput(t *testing.T, v any) string {
|
func marshalInput(t *testing.T, v any) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
b, err := json.Marshal(v)
|
b, err := jsonv2.Marshal(v)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -16,6 +15,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OAuthProviderConfig struct {
|
type OAuthProviderConfig struct {
|
||||||
|
|
@ -130,10 +132,10 @@ func parseDeviceCodeResponse(body []byte) (deviceCodeResponse, error) {
|
||||||
var raw struct {
|
var raw struct {
|
||||||
DeviceAuthID string `json:"device_auth_id"`
|
DeviceAuthID string `json:"device_auth_id"`
|
||||||
UserCode string `json:"user_code"`
|
UserCode string `json:"user_code"`
|
||||||
Interval json.RawMessage `json:"interval"`
|
Interval jsontext.Value `json:"interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &raw); err != nil {
|
if err := jsonv2.Unmarshal(body, &raw); err != nil {
|
||||||
return deviceCodeResponse{}, err
|
return deviceCodeResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,18 +151,18 @@ func parseDeviceCodeResponse(body []byte) (deviceCodeResponse, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFlexibleInt(raw json.RawMessage) (int, error) {
|
func parseFlexibleInt(raw jsontext.Value) (int, error) {
|
||||||
if len(raw) == 0 || string(raw) == "null" {
|
if len(raw) == 0 || string(raw) == "null" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var interval int
|
var interval int
|
||||||
if err := json.Unmarshal(raw, &interval); err == nil {
|
if err := jsonv2.Unmarshal(raw, &interval); err == nil {
|
||||||
return interval, nil
|
return interval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var intervalStr string
|
var intervalStr string
|
||||||
if err := json.Unmarshal(raw, &intervalStr); err == nil {
|
if err := jsonv2.Unmarshal(raw, &intervalStr); err == nil {
|
||||||
intervalStr = strings.TrimSpace(intervalStr)
|
intervalStr = strings.TrimSpace(intervalStr)
|
||||||
if intervalStr == "" {
|
if intervalStr == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
|
@ -172,7 +174,7 @@ func parseFlexibleInt(raw json.RawMessage) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) {
|
func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) {
|
||||||
reqBody, _ := json.Marshal(map[string]string{
|
reqBody, _ := jsonv2.Marshal(map[string]string{
|
||||||
"client_id": cfg.ClientID,
|
"client_id": cfg.ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -224,7 +226,7 @@ func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*AuthCredential, error) {
|
func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*AuthCredential, error) {
|
||||||
reqBody, _ := json.Marshal(map[string]string{
|
reqBody, _ := jsonv2.Marshal(map[string]string{
|
||||||
"device_auth_id": deviceAuthID,
|
"device_auth_id": deviceAuthID,
|
||||||
"user_code": userCode,
|
"user_code": userCode,
|
||||||
})
|
})
|
||||||
|
|
@ -250,7 +252,7 @@ func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*Au
|
||||||
CodeChallenge string `json:"code_challenge"`
|
CodeChallenge string `json:"code_challenge"`
|
||||||
CodeVerifier string `json:"code_verifier"`
|
CodeVerifier string `json:"code_verifier"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
if err := jsonv2.Unmarshal(body, &tokenResp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +351,7 @@ func parseTokenResponse(body []byte, provider string) (*AuthCredential, error) {
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
IDToken string `json:"id_token"`
|
IDToken string `json:"id_token"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
if err := jsonv2.Unmarshal(body, &tokenResp); err != nil {
|
||||||
return nil, fmt.Errorf("parsing token response: %w", err)
|
return nil, fmt.Errorf("parsing token response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,7 +437,7 @@ func parseJWTClaims(token string) (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims map[string]interface{}
|
var claims map[string]interface{}
|
||||||
if err := json.Unmarshal(decoded, &claims); err != nil {
|
if err := jsonv2.Unmarshal(decoded, &claims); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,20 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeJWTForClaims(t *testing.T, claims map[string]interface{}) string {
|
func makeJWTForClaims(t *testing.T, claims map[string]interface{}) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`))
|
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`))
|
||||||
payloadJSON, err := json.Marshal(claims)
|
payloadJSON, err := jsonv2.Marshal(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("marshal claims: %v", err)
|
t.Fatalf("marshal claims: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +96,7 @@ func TestParseTokenResponse(t *testing.T) {
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
"id_token": "test-id-token",
|
"id_token": "test-id-token",
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body, _ := jsonv2.Marshal(resp)
|
||||||
|
|
||||||
cred, err := parseTokenResponse(body, "openai")
|
cred, err := parseTokenResponse(body, "openai")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -127,7 +128,7 @@ func TestParseTokenResponseExtractsAccountIDFromIDToken(t *testing.T) {
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
"id_token": idToken,
|
"id_token": idToken,
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body, _ := jsonv2.Marshal(resp)
|
||||||
|
|
||||||
cred, err := parseTokenResponse(body, "openai")
|
cred, err := parseTokenResponse(body, "openai")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -166,7 +167,7 @@ func TestParseTokenResponseAccountIDFromIDToken(t *testing.T) {
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
"id_token": idToken,
|
"id_token": idToken,
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body, _ := jsonv2.Marshal(resp)
|
||||||
|
|
||||||
cred, err := parseTokenResponse(body, "openai")
|
cred, err := parseTokenResponse(body, "openai")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -206,7 +207,7 @@ func TestExchangeCodeForTokens(t *testing.T) {
|
||||||
"refresh_token": "mock-refresh-token",
|
"refresh_token": "mock-refresh-token",
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(resp)
|
jsonv2.MarshalWrite(w, resp)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
|
@ -245,7 +246,7 @@ func TestRefreshAccessToken(t *testing.T) {
|
||||||
"refresh_token": "refreshed-refresh-token",
|
"refresh_token": "refreshed-refresh-token",
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(resp)
|
jsonv2.MarshalWrite(w, resp)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
|
@ -294,7 +295,7 @@ func TestRefreshAccessTokenPreservesRefreshAndAccountID(t *testing.T) {
|
||||||
"access_token": "new-access-token-only",
|
"access_token": "new-access-token-only",
|
||||||
"expires_in": 3600,
|
"expires_in": 3600,
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(resp)
|
jsonv2.MarshalWrite(w, resp)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthCredential struct {
|
type AuthCredential struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
RefreshToken string `json:"refresh_token,omitempty"`
|
RefreshToken string `json:"refresh_token,omitzero"`
|
||||||
AccountID string `json:"account_id,omitempty"`
|
AccountID string `json:"account_id,omitzero"`
|
||||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
ExpiresAt time.Time `json:"expires_at,omitzero"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
AuthMethod string `json:"auth_method"`
|
AuthMethod string `json:"auth_method"`
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +52,7 @@ func LoadStore() (*AuthStore, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var store AuthStore
|
var store AuthStore
|
||||||
if err := json.Unmarshal(data, &store); err != nil {
|
if err := jsonv2.Unmarshal(data, &store); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if store.Credentials == nil {
|
if store.Credentials == nil {
|
||||||
|
|
@ -66,7 +68,7 @@ func SaveStore(store *AuthStore) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(store, "", " ")
|
data, err := jsonv2.Marshal(store, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
lark "github.com/larksuite/oapi-sdk-go/v3"
|
lark "github.com/larksuite/oapi-sdk-go/v3"
|
||||||
larkdispatcher "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
|
larkdispatcher "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
|
||||||
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
||||||
|
|
@ -97,7 +98,7 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||||
return fmt.Errorf("chat ID is empty")
|
return fmt.Errorf("chat ID is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(map[string]string{"text": msg.Content})
|
payload, err := jsonv2.Marshal(map[string]string{"text": msg.Content})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal feishu content: %w", err)
|
return fmt.Errorf("failed to marshal feishu content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +203,7 @@ func extractFeishuMessageContent(message *larkim.EventMessage) string {
|
||||||
var textPayload struct {
|
var textPayload struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal([]byte(*message.Content), &textPayload); err == nil {
|
if err := jsonv2.Unmarshal([]byte(*message.Content), &textPayload); err == nil {
|
||||||
return textPayload.Text
|
return textPayload.Text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -15,6 +14,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
|
@ -140,7 +142,7 @@ func (c *LINEChannel) fetchBotInfo() error {
|
||||||
BasicID string `json:"basicId"`
|
BasicID string `json:"basicId"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
if err := jsonv2.UnmarshalRead(resp.Body, &info); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,7 +201,7 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var payload struct {
|
var payload struct {
|
||||||
Events []lineEvent `json:"events"`
|
Events []lineEvent `json:"events"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
if err := jsonv2.Unmarshal(body, &payload); err != nil {
|
||||||
logger.ErrorCF("line", "Failed to parse webhook payload", map[string]interface{}{
|
logger.ErrorCF("line", "Failed to parse webhook payload", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|
@ -233,7 +235,7 @@ type lineEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
ReplyToken string `json:"replyToken"`
|
ReplyToken string `json:"replyToken"`
|
||||||
Source lineSource `json:"source"`
|
Source lineSource `json:"source"`
|
||||||
Message json.RawMessage `json:"message"`
|
Message jsontext.Value `json:"message"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,7 +279,7 @@ func (c *LINEChannel) processEvent(event lineEvent) {
|
||||||
isGroup := event.Source.Type == "group" || event.Source.Type == "room"
|
isGroup := event.Source.Type == "group" || event.Source.Type == "room"
|
||||||
|
|
||||||
var msg lineMessage
|
var msg lineMessage
|
||||||
if err := json.Unmarshal(event.Message, &msg); err != nil {
|
if err := jsonv2.Unmarshal(event.Message, &msg); err != nil {
|
||||||
logger.ErrorCF("line", "Failed to parse message", map[string]interface{}{
|
logger.ErrorCF("line", "Failed to parse message", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|
@ -558,7 +560,7 @@ func (c *LINEChannel) sendLoading(chatID string) {
|
||||||
|
|
||||||
// callAPI makes an authenticated POST request to the LINE API.
|
// callAPI makes an authenticated POST request to the LINE API.
|
||||||
func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload interface{}) error {
|
func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload interface{}) error {
|
||||||
body, err := json.Marshal(payload)
|
body, err := jsonv2.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
|
@ -102,15 +103,13 @@ func (c *MaixCamChannel) handleConnection(conn net.Conn, ctx context.Context) {
|
||||||
logger.DebugC("maixcam", "Connection closed")
|
logger.DebugC("maixcam", "Connection closed")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
decoder := json.NewDecoder(conn)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
var msg MaixCamMessage
|
var msg MaixCamMessage
|
||||||
if err := decoder.Decode(&msg); err != nil {
|
if err := jsonv2.UnmarshalRead(conn, &msg); err != nil {
|
||||||
if err.Error() != "EOF" {
|
if err.Error() != "EOF" {
|
||||||
logger.ErrorCF("maixcam", "Failed to decode message", map[string]interface{}{
|
logger.ErrorCF("maixcam", "Failed to decode message", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
|
|
@ -221,7 +220,7 @@ func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||||
"chat_id": msg.ChatID,
|
"chat_id": msg.ChatID,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(response)
|
data, err := jsonv2.Marshal(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal response: %w", err)
|
return fmt.Errorf("failed to marshal response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
|
@ -34,17 +35,17 @@ type oneBotRawEvent struct {
|
||||||
PostType string `json:"post_type"`
|
PostType string `json:"post_type"`
|
||||||
MessageType string `json:"message_type"`
|
MessageType string `json:"message_type"`
|
||||||
SubType string `json:"sub_type"`
|
SubType string `json:"sub_type"`
|
||||||
MessageID json.RawMessage `json:"message_id"`
|
MessageID jsontext.Value `json:"message_id"`
|
||||||
UserID json.RawMessage `json:"user_id"`
|
UserID jsontext.Value `json:"user_id"`
|
||||||
GroupID json.RawMessage `json:"group_id"`
|
GroupID jsontext.Value `json:"group_id"`
|
||||||
RawMessage string `json:"raw_message"`
|
RawMessage string `json:"raw_message"`
|
||||||
Message json.RawMessage `json:"message"`
|
Message jsontext.Value `json:"message"`
|
||||||
Sender json.RawMessage `json:"sender"`
|
Sender jsontext.Value `json:"sender"`
|
||||||
SelfID json.RawMessage `json:"self_id"`
|
SelfID jsontext.Value `json:"self_id"`
|
||||||
Time json.RawMessage `json:"time"`
|
Time jsontext.Value `json:"time"`
|
||||||
MetaEventType string `json:"meta_event_type"`
|
MetaEventType string `json:"meta_event_type"`
|
||||||
Echo string `json:"echo"`
|
Echo string `json:"echo"`
|
||||||
RetCode json.RawMessage `json:"retcode"`
|
RetCode jsontext.Value `json:"retcode"`
|
||||||
Status BotStatus `json:"status"`
|
Status BotStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +55,7 @@ type BotStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type oneBotSender struct {
|
type oneBotSender struct {
|
||||||
UserID json.RawMessage `json:"user_id"`
|
UserID jsontext.Value `json:"user_id"`
|
||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
Card string `json:"card"`
|
Card string `json:"card"`
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +79,7 @@ type oneBotEvent struct {
|
||||||
type oneBotAPIRequest struct {
|
type oneBotAPIRequest struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
Params interface{} `json:"params"`
|
Params interface{} `json:"params"`
|
||||||
Echo string `json:"echo,omitempty"`
|
Echo string `json:"echo,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type oneBotSendPrivateMsgParams struct {
|
type oneBotSendPrivateMsgParams struct {
|
||||||
|
|
@ -236,7 +237,7 @@ func (c *OneBotChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||||
Echo: echo,
|
Echo: echo,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(req)
|
data, err := jsonv2.Marshal(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal OneBot request: %w", err)
|
return fmt.Errorf("failed to marshal OneBot request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +327,7 @@ func (c *OneBotChannel) listen() {
|
||||||
})
|
})
|
||||||
|
|
||||||
var raw oneBotRawEvent
|
var raw oneBotRawEvent
|
||||||
if err := json.Unmarshal(message, &raw); err != nil {
|
if err := jsonv2.Unmarshal(message, &raw); err != nil {
|
||||||
logger.WarnCF("onebot", "Failed to unmarshal raw event", map[string]interface{}{
|
logger.WarnCF("onebot", "Failed to unmarshal raw event", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"payload": string(message),
|
"payload": string(message),
|
||||||
|
|
@ -354,29 +355,29 @@ func (c *OneBotChannel) listen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJSONInt64(raw json.RawMessage) (int64, error) {
|
func parseJSONInt64(raw jsontext.Value) (int64, error) {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var n int64
|
var n int64
|
||||||
if err := json.Unmarshal(raw, &n); err == nil {
|
if err := jsonv2.Unmarshal(raw, &n); err == nil {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(raw, &s); err == nil {
|
if err := jsonv2.Unmarshal(raw, &s); err == nil {
|
||||||
return strconv.ParseInt(s, 10, 64)
|
return strconv.ParseInt(s, 10, 64)
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("cannot parse as int64: %s", string(raw))
|
return 0, fmt.Errorf("cannot parse as int64: %s", string(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJSONString(raw json.RawMessage) string {
|
func parseJSONString(raw jsontext.Value) string {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(raw, &s); err == nil {
|
if err := jsonv2.Unmarshal(raw, &s); err == nil {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,13 +389,13 @@ type parseMessageResult struct {
|
||||||
IsBotMentioned bool
|
IsBotMentioned bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMessageContentEx(raw json.RawMessage, selfID int64) parseMessageResult {
|
func parseMessageContentEx(raw jsontext.Value, selfID int64) parseMessageResult {
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return parseMessageResult{}
|
return parseMessageResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(raw, &s); err == nil {
|
if err := jsonv2.Unmarshal(raw, &s); err == nil {
|
||||||
mentioned := false
|
mentioned := false
|
||||||
if selfID > 0 {
|
if selfID > 0 {
|
||||||
cqAt := fmt.Sprintf("[CQ:at,qq=%d]", selfID)
|
cqAt := fmt.Sprintf("[CQ:at,qq=%d]", selfID)
|
||||||
|
|
@ -408,7 +409,7 @@ func parseMessageContentEx(raw json.RawMessage, selfID int64) parseMessageResult
|
||||||
}
|
}
|
||||||
|
|
||||||
var segments []map[string]interface{}
|
var segments []map[string]interface{}
|
||||||
if err := json.Unmarshal(raw, &segments); err == nil {
|
if err := jsonv2.Unmarshal(raw, &segments); err == nil {
|
||||||
var text string
|
var text string
|
||||||
mentioned := false
|
mentioned := false
|
||||||
selfIDStr := strconv.FormatInt(selfID, 10)
|
selfIDStr := strconv.FormatInt(selfID, 10)
|
||||||
|
|
@ -497,7 +498,7 @@ func (c *OneBotChannel) normalizeMessageEvent(raw *oneBotRawEvent) (*oneBotEvent
|
||||||
|
|
||||||
var sender oneBotSender
|
var sender oneBotSender
|
||||||
if len(raw.Sender) > 0 {
|
if len(raw.Sender) > 0 {
|
||||||
if err := json.Unmarshal(raw.Sender, &sender); err != nil {
|
if err := jsonv2.Unmarshal(raw.Sender, &sender); err != nil {
|
||||||
logger.WarnCF("onebot", "Failed to parse sender", map[string]interface{}{
|
logger.WarnCF("onebot", "Failed to parse sender", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"sender": string(raw.Sender),
|
"sender": string(raw.Sender),
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
|
|
@ -92,7 +92,7 @@ func (c *WhatsAppChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
"content": msg.Content,
|
"content": msg.Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(payload)
|
data, err := jsonv2.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal message: %w", err)
|
return fmt.Errorf("failed to marshal message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ func (c *WhatsAppChannel) listen(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg map[string]interface{}
|
var msg map[string]interface{}
|
||||||
if err := json.Unmarshal(message, &msg); err != nil {
|
if err := jsonv2.Unmarshal(message, &msg); err != nil {
|
||||||
log.Printf("Failed to unmarshal WhatsApp message: %v", err)
|
log.Printf("Failed to unmarshal WhatsApp message: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/caarlos0/env/v11"
|
"github.com/caarlos0/env/v11"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FlexibleStringSlice is a []string that also accepts JSON numbers,
|
// FlexibleStringSlice is a []string that also accepts JSON numbers,
|
||||||
|
|
@ -17,14 +18,14 @@ type FlexibleStringSlice []string
|
||||||
func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||||
// Try []string first
|
// Try []string first
|
||||||
var ss []string
|
var ss []string
|
||||||
if err := json.Unmarshal(data, &ss); err == nil {
|
if err := jsonv2.Unmarshal(data, &ss); err == nil {
|
||||||
*f = ss
|
*f = ss
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try []interface{} to handle mixed types
|
// Try []interface{} to handle mixed types
|
||||||
var raw []interface{}
|
var raw []interface{}
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := jsonv2.Unmarshal(data, &raw); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,10 +252,10 @@ type ProvidersConfig struct {
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
|
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
|
||||||
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
|
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
|
||||||
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
|
Proxy string `json:"proxy,omitzero" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
|
||||||
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"`
|
AuthMethod string `json:"auth_method,omitzero" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"`
|
||||||
Timeout int `json:"timeout,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_TIMEOUT"` // seconds, 0 = default (120s)
|
Timeout int `json:"timeout,omitzero" env:"PICOCLAW_PROVIDERS_{{.Name}}_TIMEOUT"` // seconds, 0 = default (120s)
|
||||||
ConnectMode string `json:"connect_mode,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
|
ConnectMode string `json:"connect_mode,omitzero" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenAIProviderConfig struct {
|
type OpenAIProviderConfig struct {
|
||||||
|
|
@ -450,7 +451,7 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, cfg); err != nil {
|
if err := jsonv2.Unmarshal(data, cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -522,7 +523,7 @@ func SaveConfig(path string, cfg *Config) error {
|
||||||
cfg.mu.RLock()
|
cfg.mu.RLock()
|
||||||
defer cfg.mu.RUnlock()
|
defer cfg.mu.RUnlock()
|
||||||
|
|
||||||
data, err := json.MarshalIndent(cfg, "", " ")
|
data, err := jsonv2.Marshal(cfg, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -13,31 +12,34 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/adhocore/gronx"
|
"github.com/adhocore/gronx"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/memory"
|
"github.com/sipeed/picoclaw/pkg/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CronSchedule struct {
|
type CronSchedule struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
AtMS *int64 `json:"atMs,omitempty"`
|
AtMS *int64 `json:"atMs,omitzero"`
|
||||||
EveryMS *int64 `json:"everyMs,omitempty"`
|
EveryMS *int64 `json:"everyMs,omitzero"`
|
||||||
Expr string `json:"expr,omitempty"`
|
Expr string `json:"expr,omitzero"`
|
||||||
TZ string `json:"tz,omitempty"`
|
TZ string `json:"tz,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronPayload struct {
|
type CronPayload struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Command string `json:"command,omitempty"`
|
Command string `json:"command,omitzero"`
|
||||||
Deliver bool `json:"deliver"`
|
Deliver bool `json:"deliver"`
|
||||||
Channel string `json:"channel,omitempty"`
|
Channel string `json:"channel,omitzero"`
|
||||||
To string `json:"to,omitempty"`
|
To string `json:"to,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronJobState struct {
|
type CronJobState struct {
|
||||||
NextRunAtMS *int64 `json:"nextRunAtMs,omitempty"`
|
NextRunAtMS *int64 `json:"nextRunAtMs,omitzero"`
|
||||||
LastRunAtMS *int64 `json:"lastRunAtMs,omitempty"`
|
LastRunAtMS *int64 `json:"lastRunAtMs,omitzero"`
|
||||||
LastStatus string `json:"lastStatus,omitempty"`
|
LastStatus string `json:"lastStatus,omitzero"`
|
||||||
LastError string `json:"lastError,omitempty"`
|
LastError string `json:"lastError,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronJob struct {
|
type CronJob struct {
|
||||||
|
|
@ -349,7 +351,7 @@ func (cs *CronService) loadStore() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal(data, cs.store)
|
return jsonv2.Unmarshal(data, cs.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CronService) loadStoreFromDelegate() error {
|
func (cs *CronService) loadStoreFromDelegate() error {
|
||||||
|
|
@ -360,7 +362,7 @@ func (cs *CronService) loadStoreFromDelegate() error {
|
||||||
if err != nil || val == "" {
|
if err != nil || val == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal([]byte(val), cs.store)
|
return jsonv2.Unmarshal([]byte(val), cs.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CronService) saveStoreUnsafe() error {
|
func (cs *CronService) saveStoreUnsafe() error {
|
||||||
|
|
@ -373,7 +375,7 @@ func (cs *CronService) saveStoreUnsafe() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(cs.store, "", " ")
|
data, err := jsonv2.Marshal(cs.store, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +384,7 @@ func (cs *CronService) saveStoreUnsafe() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CronService) saveStoreToDelegate() error {
|
func (cs *CronService) saveStoreToDelegate() error {
|
||||||
data, err := json.Marshal(cs.store)
|
data, err := jsonv2.Marshal(cs.store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
memstore "github.com/sipeed/picoclaw/pkg/memory/store"
|
memstore "github.com/sipeed/picoclaw/pkg/memory/store"
|
||||||
|
|
@ -170,7 +171,7 @@ func parseToolArgs(input string) (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.Unmarshal([]byte(input), &args); err != nil {
|
if err := jsonv2.Unmarshal([]byte(input), &args); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse tool arguments: %w", err)
|
return nil, fmt.Errorf("failed to parse tool arguments: %w", err)
|
||||||
}
|
}
|
||||||
return args, nil
|
return args, nil
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ package fantasy
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// claudeCliProvider implements fantasy.Provider using the claude CLI subprocess.
|
// claudeCliProvider implements fantasy.Provider using the claude CLI subprocess.
|
||||||
|
|
@ -147,7 +147,7 @@ func extractTextFromCLIOutput(output []byte) string {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(output, &results); err == nil {
|
if err := jsonv2.Unmarshal(output, &results); err == nil {
|
||||||
var texts []string
|
var texts []string
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -167,7 +167,7 @@ func extractTextFromCLIOutput(output []byte) string {
|
||||||
Result string `json:"result"`
|
Result string `json:"result"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(output, &single); err == nil {
|
if err := jsonv2.Unmarshal(output, &single); err == nil {
|
||||||
if single.Result != "" {
|
if single.Result != "" {
|
||||||
return single.Result
|
return single.Result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
package fantasy
|
package fantasy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"charm.land/fantasy"
|
"charm.land/fantasy"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/messages"
|
"github.com/sipeed/picoclaw/pkg/messages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ func MessageToFantasy(msg messages.Message) fantasy.Message {
|
||||||
input = tc.Function.Arguments
|
input = tc.Function.Arguments
|
||||||
} else if tc.Arguments != nil {
|
} else if tc.Arguments != nil {
|
||||||
// Fallback: serialize the map to JSON.
|
// Fallback: serialize the map to JSON.
|
||||||
data, _ := json.Marshal(tc.Arguments)
|
data, _ := jsonv2.Marshal(tc.Arguments)
|
||||||
input = string(data)
|
input = string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
|
@ -20,14 +21,14 @@ type Server struct {
|
||||||
type Check struct {
|
type Check struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitzero"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusResponse struct {
|
type StatusResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Uptime string `json:"uptime"`
|
Uptime string `json:"uptime"`
|
||||||
Checks map[string]Check `json:"checks,omitempty"`
|
Checks map[string]Check `json:"checks,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(host string, port int) *Server {
|
func NewServer(host string, port int) *Server {
|
||||||
|
|
@ -113,7 +114,7 @@ func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Uptime: uptime.String(),
|
Uptime: uptime.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(resp)
|
jsonv2.MarshalWrite(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -129,7 +130,7 @@ func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if !ready {
|
if !ready {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
json.NewEncoder(w).Encode(StatusResponse{
|
jsonv2.MarshalWrite(w, StatusResponse{
|
||||||
Status: "not ready",
|
Status: "not ready",
|
||||||
Checks: checks,
|
Checks: checks,
|
||||||
})
|
})
|
||||||
|
|
@ -139,7 +140,7 @@ func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
if check.Status == "fail" {
|
if check.Status == "fail" {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
json.NewEncoder(w).Encode(StatusResponse{
|
jsonv2.MarshalWrite(w, StatusResponse{
|
||||||
Status: "not ready",
|
Status: "not ready",
|
||||||
Checks: checks,
|
Checks: checks,
|
||||||
})
|
})
|
||||||
|
|
@ -149,7 +150,7 @@ func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
uptime := time.Since(s.startTime)
|
uptime := time.Since(s.startTime)
|
||||||
json.NewEncoder(w).Encode(StatusResponse{
|
jsonv2.MarshalWrite(w, StatusResponse{
|
||||||
Status: "ready",
|
Status: "ready",
|
||||||
Uptime: uptime.String(),
|
Uptime: uptime.String(),
|
||||||
Checks: checks,
|
Checks: checks,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package ids
|
package ids
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNew_IsV7(t *testing.T) {
|
func TestNew_IsV7(t *testing.T) {
|
||||||
|
|
@ -159,14 +160,14 @@ func TestScan_InvalidType(t *testing.T) {
|
||||||
func TestJSON_RoundTrip(t *testing.T) {
|
func TestJSON_RoundTrip(t *testing.T) {
|
||||||
u := New()
|
u := New()
|
||||||
|
|
||||||
b, err := json.Marshal(u)
|
b, err := jsonv2.Marshal(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Marshal: %v", err)
|
t.Fatalf("Marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be a quoted string
|
// Should be a quoted string
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(b, &s); err != nil {
|
if err := jsonv2.Unmarshal(b, &s); err != nil {
|
||||||
t.Fatalf("Unmarshal to string: %v", err)
|
t.Fatalf("Unmarshal to string: %v", err)
|
||||||
}
|
}
|
||||||
if s != u.String() {
|
if s != u.String() {
|
||||||
|
|
@ -174,7 +175,7 @@ func TestJSON_RoundTrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsed UUID
|
var parsed UUID
|
||||||
if err := json.Unmarshal(b, &parsed); err != nil {
|
if err := jsonv2.Unmarshal(b, &parsed); err != nil {
|
||||||
t.Fatalf("Unmarshal: %v", err)
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
if parsed != u {
|
if parsed != u {
|
||||||
|
|
@ -184,7 +185,7 @@ func TestJSON_RoundTrip(t *testing.T) {
|
||||||
|
|
||||||
func TestJSON_ZeroUUID(t *testing.T) {
|
func TestJSON_ZeroUUID(t *testing.T) {
|
||||||
var zero UUID
|
var zero UUID
|
||||||
b, err := json.Marshal(zero)
|
b, err := jsonv2.Marshal(zero)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Marshal zero: %v", err)
|
t.Fatalf("Marshal zero: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -201,13 +202,13 @@ func TestJSON_InStruct(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := record{ID: New(), Name: "test"}
|
r := record{ID: New(), Name: "test"}
|
||||||
b, err := json.Marshal(r)
|
b, err := jsonv2.Marshal(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Marshal struct: %v", err)
|
t.Fatalf("Marshal struct: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded record
|
var decoded record
|
||||||
if err := json.Unmarshal(b, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(b, &decoded); err != nil {
|
||||||
t.Fatalf("Unmarshal struct: %v", err)
|
t.Fatalf("Unmarshal struct: %v", err)
|
||||||
}
|
}
|
||||||
if decoded.ID != r.ID {
|
if decoded.ID != r.ID {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package ids
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UUID represents a 16-byte RFC-9562 UUIDv7 value.
|
// UUID represents a 16-byte RFC-9562 UUIDv7 value.
|
||||||
|
|
@ -85,13 +86,13 @@ func (u *UUID) Scan(src interface{}) error {
|
||||||
|
|
||||||
// MarshalJSON encodes UUID as JSON string.
|
// MarshalJSON encodes UUID as JSON string.
|
||||||
func (u UUID) MarshalJSON() ([]byte, error) {
|
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(u.String())
|
return jsonv2.Marshal(u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON decodes UUID from JSON string.
|
// UnmarshalJSON decodes UUID from JSON string.
|
||||||
func (u *UUID) UnmarshalJSON(b []byte) error {
|
func (u *UUID) UnmarshalJSON(b []byte) error {
|
||||||
var s string
|
var s string
|
||||||
if err := json.Unmarshal(b, &s); err != nil {
|
if err := jsonv2.Unmarshal(b, &s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parsed, err := Parse(s)
|
parsed, err := Parse(s)
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,15 @@
|
||||||
// recursive decomposition operations — are serialized as ToolRequest /
|
// recursive decomposition operations — are serialized as ToolRequest /
|
||||||
// ToolResponse pairs.
|
// ToolResponse pairs.
|
||||||
//
|
//
|
||||||
// The canonical schema lives in commands.fbs. This file provides the Go
|
// The canonical schema lives in commands.fbs. Wire encoding uses FlatBuffers
|
||||||
// types and encoding helpers that replace the raw flatc-generated code while
|
// for all internal transport (socket, daemon, WASM). JSON encoding is
|
||||||
// keeping the same wire format semantics. The encoding uses encoding/json
|
// available via MarshalJSON / UnmarshalRequestJSON for the LLM boundary only.
|
||||||
// on the initial implementation path; the FlatBuffers binary encoding is
|
|
||||||
// available via the flatbuffers package when performance demands it.
|
|
||||||
//
|
|
||||||
// Migration path: when the codebase moves to the full FlatBuffers binary
|
|
||||||
// encoding, replace the JSON marshal/unmarshal calls with flatbuffers builder
|
|
||||||
// calls without changing any call sites — the public types remain stable.
|
|
||||||
package itr
|
package itr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -113,6 +108,10 @@ type CodeExec struct {
|
||||||
// command payload and declares dependencies on other nodes by ID.
|
// command payload and declares dependencies on other nodes by ID.
|
||||||
// Args may contain "#nodeN" references that are resolved to the output of
|
// Args may contain "#nodeN" references that are resolved to the output of
|
||||||
// the dependency node at execution time.
|
// the dependency node at execution time.
|
||||||
|
//
|
||||||
|
// Implements json.Unmarshaler to deserialize Payload directly into the
|
||||||
|
// concrete type indicated by Type, avoiding the map[string]interface{}
|
||||||
|
// erasure that encoding/json applies to interface{} fields.
|
||||||
type DAGNode struct {
|
type DAGNode struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type CommandType `json:"type"`
|
Type CommandType `json:"type"`
|
||||||
|
|
@ -120,6 +119,92 @@ type DAGNode struct {
|
||||||
DependsOn []string `json:"depends_on,omitempty"`
|
DependsOn []string `json:"depends_on,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSONFrom implements jsonv2.UnmarshalerFrom. It defers Payload
|
||||||
|
// deserialization via jsontext.Value until the Type discriminator is known,
|
||||||
|
// then unmarshals directly into the concrete Go type.
|
||||||
|
func (n *DAGNode) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||||
|
var raw struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type CommandType `json:"type"`
|
||||||
|
Payload jsontext.Value `json:"payload"`
|
||||||
|
DependsOn []string `json:"depends_on,omitempty"`
|
||||||
|
}
|
||||||
|
if err := jsonv2.UnmarshalDecode(dec, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.ID = raw.ID
|
||||||
|
n.Type = raw.Type
|
||||||
|
n.DependsOn = raw.DependsOn
|
||||||
|
|
||||||
|
if len(raw.Payload) == 0 || string(raw.Payload) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshalPayload := func(dst any) error {
|
||||||
|
return jsonv2.Unmarshal(raw.Payload, dst, jsonv2.MatchCaseInsensitiveNames(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch raw.Type {
|
||||||
|
case CmdToolExec:
|
||||||
|
var p ToolExec
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdToolSearch:
|
||||||
|
var p ToolSearch
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdPeek:
|
||||||
|
var p Peek
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdGrep:
|
||||||
|
var p Grep
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdPartition:
|
||||||
|
var p Partition
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdRecurse:
|
||||||
|
var p Recurse
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdCodeExec:
|
||||||
|
var p CodeExec
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdExecWasm:
|
||||||
|
var p ExecWasm
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
case CmdFinal:
|
||||||
|
var p Final
|
||||||
|
if err := unmarshalPayload(&p); err != nil {
|
||||||
|
return fmt.Errorf("DAGNode %s payload: %w", raw.ID, err)
|
||||||
|
}
|
||||||
|
n.Payload = p
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("DAGNode %s: unknown type %q", raw.ID, raw.Type)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DAGPlan is a composite command that contains multiple DAGNodes forming a
|
// DAGPlan is a composite command that contains multiple DAGNodes forming a
|
||||||
// Directed Acyclic Graph. The executor dispatches nodes in topological order,
|
// Directed Acyclic Graph. The executor dispatches nodes in topological order,
|
||||||
// running independent nodes in parallel.
|
// running independent nodes in parallel.
|
||||||
|
|
@ -269,30 +354,77 @@ func NewLeakResponse(id, redactedResult string, redactedKeys []string) ToolRespo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Serialisation helpers ────────────────────────────────────────────────────
|
// ── Serialisation helpers (FlatBuffers default) ──────────────────────────────
|
||||||
|
|
||||||
// Marshal encodes a ToolRequest to JSON bytes for transmission.
|
// Marshal encodes a ToolRequest to FlatBuffers bytes for internal transport.
|
||||||
func (r ToolRequest) Marshal() ([]byte, error) {
|
func (r ToolRequest) Marshal() ([]byte, error) {
|
||||||
return json.Marshal(r)
|
return MarshalRequestFB(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal encodes a ToolResponse to JSON bytes for transmission.
|
// Marshal encodes a ToolResponse to FlatBuffers bytes for internal transport.
|
||||||
func (r ToolResponse) Marshal() ([]byte, error) {
|
func (r ToolResponse) Marshal() ([]byte, error) {
|
||||||
return json.Marshal(r)
|
return MarshalResponseFB(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalRequest decodes a ToolRequest from JSON bytes.
|
// UnmarshalRequest decodes a ToolRequest from FlatBuffers bytes.
|
||||||
func UnmarshalRequest(data []byte) (ToolRequest, error) {
|
func UnmarshalRequest(data []byte) (ToolRequest, error) {
|
||||||
|
return UnmarshalRequestFB(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalResponse decodes a ToolResponse from FlatBuffers bytes.
|
||||||
|
func UnmarshalResponse(data []byte) (ToolResponse, error) {
|
||||||
|
return UnmarshalResponseFB(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── JSON helpers (LLM boundary only) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
// MarshalJSONTo implements jsonv2.MarshalerTo. Use only at the LLM boundary.
|
||||||
|
func (r ToolRequest) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
|
type alias struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type CommandType `json:"type"`
|
||||||
|
Payload interface{} `json:"payload"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Depth uint8 `json:"depth,omitzero"`
|
||||||
|
SessionKey string `json:"session_key,omitzero"`
|
||||||
|
ToolCallID string `json:"tool_call_id,omitzero"`
|
||||||
|
}
|
||||||
|
return jsonv2.MarshalEncode(enc, alias{r.ID, r.Type, r.Payload, r.Timestamp, r.Depth, r.SessionKey, r.ToolCallID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSONTo implements jsonv2.MarshalerTo. Use only at the LLM boundary.
|
||||||
|
func (r ToolResponse) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||||
|
type alias struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Result string `json:"result,omitzero"`
|
||||||
|
IsError bool `json:"is_error,omitzero"`
|
||||||
|
LeakDetected bool `json:"leak_detected,omitzero"`
|
||||||
|
CostTokens uint32 `json:"cost_tokens,omitzero"`
|
||||||
|
RedactedKeys []string `json:"redacted_keys,omitzero"`
|
||||||
|
}
|
||||||
|
return jsonv2.MarshalEncode(enc, alias{r.ID, r.Result, r.IsError, r.LeakDetected, r.CostTokens, r.RedactedKeys})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLMJSONOpts returns json/v2 options suitable for unmarshaling LLM-generated JSON,
|
||||||
|
// which may use inconsistent casing.
|
||||||
|
func LLMJSONOpts() jsonv2.Options {
|
||||||
|
return jsonv2.MatchCaseInsensitiveNames(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalRequestJSON decodes a ToolRequest from JSON bytes. Use only at
|
||||||
|
// the LLM boundary (planner output, tool schemas).
|
||||||
|
func UnmarshalRequestJSON(data []byte) (ToolRequest, error) {
|
||||||
|
opts := LLMJSONOpts()
|
||||||
var raw struct {
|
var raw struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type CommandType `json:"type"`
|
Type CommandType `json:"type"`
|
||||||
Payload json.RawMessage `json:"payload"`
|
Payload jsontext.Value `json:"payload"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Depth uint8 `json:"depth"`
|
Depth uint8 `json:"depth"`
|
||||||
SessionKey string `json:"session_key"`
|
SessionKey string `json:"session_key"`
|
||||||
ToolCallID string `json:"tool_call_id"`
|
ToolCallID string `json:"tool_call_id"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := jsonv2.Unmarshal(data, &raw, opts); err != nil {
|
||||||
return ToolRequest{}, err
|
return ToolRequest{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,47 +437,51 @@ func UnmarshalRequest(data []byte) (ToolRequest, error) {
|
||||||
ToolCallID: raw.ToolCallID,
|
ToolCallID: raw.ToolCallID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unmarshalPayload := func(dst any) error {
|
||||||
|
return jsonv2.Unmarshal(raw.Payload, dst, opts)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch raw.Type {
|
switch raw.Type {
|
||||||
case CmdPeek:
|
case CmdPeek:
|
||||||
var p Peek
|
var p Peek
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdGrep:
|
case CmdGrep:
|
||||||
var p Grep
|
var p Grep
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdPartition:
|
case CmdPartition:
|
||||||
var p Partition
|
var p Partition
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdRecurse:
|
case CmdRecurse:
|
||||||
var p Recurse
|
var p Recurse
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdToolExec:
|
case CmdToolExec:
|
||||||
var p ToolExec
|
var p ToolExec
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdExecWasm:
|
case CmdExecWasm:
|
||||||
var p ExecWasm
|
var p ExecWasm
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdFinal:
|
case CmdFinal:
|
||||||
var p Final
|
var p Final
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdToolSearch:
|
case CmdToolSearch:
|
||||||
var p ToolSearch
|
var p ToolSearch
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdCodeExec:
|
case CmdCodeExec:
|
||||||
var p CodeExec
|
var p CodeExec
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
case CmdDAGPlan:
|
case CmdDAGPlan:
|
||||||
var p DAGPlan
|
var p DAGPlan
|
||||||
err = json.Unmarshal(raw.Payload, &p)
|
err = unmarshalPayload(&p)
|
||||||
req.Payload = p
|
req.Payload = p
|
||||||
default:
|
default:
|
||||||
return ToolRequest{}, fmt.Errorf("unknown command type: %q", raw.Type)
|
return ToolRequest{}, fmt.Errorf("unknown command type: %q", raw.Type)
|
||||||
|
|
@ -354,8 +490,8 @@ func UnmarshalRequest(data []byte) (ToolRequest, error) {
|
||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalResponse decodes a ToolResponse from JSON bytes.
|
// UnmarshalResponseJSON decodes a ToolResponse from JSON bytes.
|
||||||
func UnmarshalResponse(data []byte) (ToolResponse, error) {
|
func UnmarshalResponseJSON(data []byte) (ToolResponse, error) {
|
||||||
var r ToolResponse
|
var r ToolResponse
|
||||||
return r, json.Unmarshal(data, &r)
|
return r, jsonv2.Unmarshal(data, &r)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ package dag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -133,8 +132,9 @@ func (e *Executor) Execute(ctx context.Context, sessionKey string, plan *itr.DAG
|
||||||
select {
|
select {
|
||||||
case <-depState.done:
|
case <-depState.done:
|
||||||
if _, depErr := depState.getResult(); depErr != nil {
|
if _, depErr := depState.getResult(); depErr != nil {
|
||||||
ns.setResult("", fmt.Errorf("dependency %s failed: %w", dep, depErr))
|
wrapped := fmt.Errorf("dependency %s failed: %w", dep, depErr)
|
||||||
errOnce.Do(func() { waveErr = depErr })
|
ns.setResult("", wrapped)
|
||||||
|
errOnce.Do(func() { waveErr = wrapped })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -215,34 +215,31 @@ func (e *Executor) Execute(ctx context.Context, sessionKey string, plan *itr.DAG
|
||||||
// executeNode dispatches a single node through the SecureBus after resolving
|
// executeNode dispatches a single node through the SecureBus after resolving
|
||||||
// dependency references in its payload.
|
// dependency references in its payload.
|
||||||
func (e *Executor) executeNode(ctx context.Context, sessionKey string, node *itr.DAGNode, states map[string]*nodeState) itr.ToolResponse {
|
func (e *Executor) executeNode(ctx context.Context, sessionKey string, node *itr.DAGNode, states map[string]*nodeState) itr.ToolResponse {
|
||||||
req := nodeToRequest(sessionKey, node, states)
|
req, err := nodeToRequest(sessionKey, node, states)
|
||||||
|
if err != nil {
|
||||||
|
return itr.NewErrorResponse(node.ID, err.Error())
|
||||||
|
}
|
||||||
return e.bus.Execute(ctx, req)
|
return e.bus.Execute(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeToRequest converts a DAGNode into a ToolRequest, resolving #nodeN
|
// nodeToRequest converts a DAGNode into a ToolRequest, resolving #nodeN
|
||||||
// references in tool arguments.
|
// references in tool arguments.
|
||||||
func nodeToRequest(sessionKey string, node *itr.DAGNode, states map[string]*nodeState) itr.ToolRequest {
|
func nodeToRequest(sessionKey string, node *itr.DAGNode, states map[string]*nodeState) (itr.ToolRequest, error) {
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case itr.CmdToolExec:
|
case itr.CmdToolExec:
|
||||||
te, ok := node.Payload.(itr.ToolExec)
|
te, ok := node.Payload.(itr.ToolExec)
|
||||||
if !ok {
|
if !ok {
|
||||||
if m, ok := node.Payload.(map[string]interface{}); ok {
|
return itr.ToolRequest{}, fmt.Errorf("node %s: expected ToolExec payload, got %T", node.ID, node.Payload)
|
||||||
b, _ := json.Marshal(m)
|
|
||||||
_ = json.Unmarshal(b, &te)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
te.ArgsJSON = resolveToolExecArgs(te.ArgsJSON, states)
|
te.ArgsJSON = resolveToolExecArgs(te.ArgsJSON, states)
|
||||||
return itr.NewToolExecRequest(node.ID, sessionKey, node.ID, te.ToolName, te.ArgsJSON)
|
return itr.NewToolExecRequest(node.ID, sessionKey, node.ID, te.ToolName, te.ArgsJSON), nil
|
||||||
|
|
||||||
case itr.CmdToolSearch:
|
case itr.CmdToolSearch:
|
||||||
ts, ok := node.Payload.(itr.ToolSearch)
|
ts, ok := node.Payload.(itr.ToolSearch)
|
||||||
if !ok {
|
if !ok {
|
||||||
if m, ok := node.Payload.(map[string]interface{}); ok {
|
return itr.ToolRequest{}, fmt.Errorf("node %s: expected ToolSearch payload, got %T", node.ID, node.Payload)
|
||||||
b, _ := json.Marshal(m)
|
|
||||||
_ = json.Unmarshal(b, &ts)
|
|
||||||
}
|
}
|
||||||
}
|
return itr.NewToolSearchRequest(node.ID, sessionKey, ts.Query, ts.MaxResults), nil
|
||||||
return itr.NewToolSearchRequest(node.ID, sessionKey, ts.Query, ts.MaxResults)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return itr.ToolRequest{
|
return itr.ToolRequest{
|
||||||
|
|
@ -250,7 +247,7 @@ func nodeToRequest(sessionKey string, node *itr.DAGNode, states map[string]*node
|
||||||
Type: node.Type,
|
Type: node.Type,
|
||||||
Payload: node.Payload,
|
Payload: node.Payload,
|
||||||
SessionKey: sessionKey,
|
SessionKey: sessionKey,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package dag_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/itr"
|
"github.com/sipeed/picoclaw/pkg/itr"
|
||||||
|
|
@ -123,7 +123,11 @@ func TestExecutor_WithJoiner(t *testing.T) {
|
||||||
defer bus.Close()
|
defer bus.Close()
|
||||||
|
|
||||||
joiner := func(_ context.Context, _, userQuery string) (string, uint32, error) {
|
joiner := func(_ context.Context, _, userQuery string) (string, uint32, error) {
|
||||||
return "synthesized: " + userQuery[:20], 50, nil
|
n := len(userQuery)
|
||||||
|
if n > 20 {
|
||||||
|
n = 20
|
||||||
|
}
|
||||||
|
return "synthesized: " + userQuery[:n], 50, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
executor := dag.NewExecutor(bus, joiner)
|
executor := dag.NewExecutor(bus, joiner)
|
||||||
|
|
@ -173,7 +177,8 @@ func TestResolver_NodeRefSubstitution(t *testing.T) {
|
||||||
|
|
||||||
result, err := executor.Execute(context.Background(), "test-sess", plan)
|
result, err := executor.Execute(context.Background(), "test-sess", plan)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_ = result
|
assert.Contains(t, result.NodeResults["prev"], "previous-output")
|
||||||
|
assert.Contains(t, result.NodeResults["search"], "found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouter_SimpleQuerySelectsReAct(t *testing.T) {
|
func TestRouter_SimpleQuerySelectsReAct(t *testing.T) {
|
||||||
|
|
@ -225,7 +230,7 @@ func TestPlanner_ValidatePlan(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var plan itr.DAGPlan
|
var plan itr.DAGPlan
|
||||||
err := json.Unmarshal([]byte(tt.plan), &plan)
|
err := jsonv2.Unmarshal([]byte(tt.plan), &plan)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Use planner with a mock that returns the pre-built plan JSON
|
// Use planner with a mock that returns the pre-built plan JSON
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package dag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/itr"
|
"github.com/sipeed/picoclaw/pkg/itr"
|
||||||
"github.com/sipeed/picoclaw/pkg/tools"
|
"github.com/sipeed/picoclaw/pkg/tools"
|
||||||
|
|
@ -115,32 +115,10 @@ func parsePlanResponse(response string) (*itr.DAGPlan, error) {
|
||||||
response = extractJSON(response)
|
response = extractJSON(response)
|
||||||
|
|
||||||
var plan itr.DAGPlan
|
var plan itr.DAGPlan
|
||||||
if err := json.Unmarshal([]byte(response), &plan); err != nil {
|
if err := jsonv2.Unmarshal([]byte(response), &plan, itr.LLMJSONOpts()); err != nil {
|
||||||
return nil, fmt.Errorf("JSON parse error: %w\nraw: %s", err, truncate(response, 500))
|
return nil, fmt.Errorf("JSON parse error: %w\nraw: %s", err, truncate(response, 500))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal re-encodes Payload as map[string]interface{} via JSON round-trip.
|
|
||||||
// Convert payload maps to their proper types.
|
|
||||||
for i := range plan.Nodes {
|
|
||||||
n := &plan.Nodes[i]
|
|
||||||
m, ok := n.Payload.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b, _ := json.Marshal(m)
|
|
||||||
|
|
||||||
switch n.Type {
|
|
||||||
case itr.CmdToolExec:
|
|
||||||
var te itr.ToolExec
|
|
||||||
_ = json.Unmarshal(b, &te)
|
|
||||||
n.Payload = te
|
|
||||||
case itr.CmdToolSearch:
|
|
||||||
var ts itr.ToolSearch
|
|
||||||
_ = json.Unmarshal(b, &ts)
|
|
||||||
n.Payload = ts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &plan, nil
|
return &plan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package dag
|
package dag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -67,11 +67,10 @@ func resolveToolExecArgs(argsJSON string, states map[string]*nodeState) string {
|
||||||
|
|
||||||
// escapeForJSON makes a string safe for embedding into a JSON value.
|
// escapeForJSON makes a string safe for embedding into a JSON value.
|
||||||
func escapeForJSON(s string) string {
|
func escapeForJSON(s string) string {
|
||||||
b, err := json.Marshal(s)
|
b, err := jsonv2.Marshal(s)
|
||||||
if err != nil {
|
if err != nil || len(b) < 2 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
// Strip surrounding quotes since we're replacing within an existing string.
|
|
||||||
return string(b[1 : len(b)-1])
|
return string(b[1 : len(b)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -9,6 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogLevel int
|
type LogLevel int
|
||||||
|
|
@ -43,10 +44,10 @@ type Logger struct {
|
||||||
type LogEntry struct {
|
type LogEntry struct {
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
Component string `json:"component,omitempty"`
|
Component string `json:"component,omitzero"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
Fields map[string]interface{} `json:"fields,omitzero"`
|
||||||
Caller string `json:"caller,omitempty"`
|
Caller string `json:"caller,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -117,7 +118,7 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||||
}
|
}
|
||||||
|
|
||||||
if logger.file != nil {
|
if logger.file != nil {
|
||||||
jsonData, err := json.Marshal(entry)
|
jsonData, err := jsonv2.Marshal(entry)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.file.WriteString(string(jsonData) + "\n")
|
logger.file.WriteString(string(jsonData) + "\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package delegate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
|
|
@ -38,7 +38,7 @@ func TestCronKVBackend_Roundtrip(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(store)
|
data, err := jsonv2.Marshal(store)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, d.UpsertKV(ctx, agentID, kvKey, string(data)))
|
require.NoError(t, d.UpsertKV(ctx, agentID, kvKey, string(data)))
|
||||||
|
|
@ -48,7 +48,7 @@ func TestCronKVBackend_Roundtrip(t *testing.T) {
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
|
|
||||||
var loaded cronStore
|
var loaded cronStore
|
||||||
require.NoError(t, json.Unmarshal([]byte(raw), &loaded))
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &loaded))
|
||||||
|
|
||||||
assert.Equal(t, 1, loaded.Version)
|
assert.Equal(t, 1, loaded.Version)
|
||||||
assert.Len(t, loaded.Jobs, 2)
|
assert.Len(t, loaded.Jobs, 2)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -23,7 +23,7 @@ const migrationMarkerFile = ".sessions_migrated"
|
||||||
type SessionFile struct {
|
type SessionFile struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Messages []SessionMsg `json:"messages"`
|
Messages []SessionMsg `json:"messages"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitzero"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ func MigrateFileSessions(ctx context.Context, del MemoryDelegate, agentID, sessi
|
||||||
}
|
}
|
||||||
|
|
||||||
var sess SessionFile
|
var sess SessionFile
|
||||||
if err := json.Unmarshal(data, &sess); err != nil {
|
if err := jsonv2.Unmarshal(data, &sess); err != nil {
|
||||||
logger.WarnCF("migrate", "Failed to parse session file",
|
logger.WarnCF("migrate", "Failed to parse session file",
|
||||||
map[string]interface{}{"path": sessPath, "error": err.Error()})
|
map[string]interface{}{"path": sessPath, "error": err.Error()})
|
||||||
result.Errors++
|
result.Errors++
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -103,7 +103,7 @@ func (m *mockDelegate) CountAuditEntries(_ context.Context, _ string) (int, erro
|
||||||
|
|
||||||
func writeSessionFile(t *testing.T, dir, name string, sess SessionFile) {
|
func writeSessionFile(t *testing.T, dir, name string, sess SessionFile) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
data, err := json.Marshal(sess)
|
data, err := jsonv2.Marshal(sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("marshal session: %v", err)
|
t.Fatalf("marshal session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -10,8 +10,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type legacyState struct {
|
type legacyState struct {
|
||||||
LastChannel string `json:"last_channel,omitempty"`
|
LastChannel string `json:"last_channel,omitzero"`
|
||||||
LastChatID string `json:"last_chat_id,omitempty"`
|
LastChatID string `json:"last_chat_id,omitzero"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ func MigrateState(ctx context.Context, workspace string, delegate MemoryDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
var s legacyState
|
var s legacyState
|
||||||
if err := json.Unmarshal(data, &s); err != nil {
|
if err := jsonv2.Unmarshal(data, &s); err != nil {
|
||||||
log.Printf("[WARN] migrate_state: failed to parse %s: %v", stateFile, err)
|
log.Printf("[WARN] migrate_state: failed to parse %s: %v", stateFile, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
package observation
|
package observation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -97,7 +97,7 @@ func FormatBlock(observations []Observation) string {
|
||||||
|
|
||||||
// MarshalObservations serializes observations to JSON for KV storage.
|
// MarshalObservations serializes observations to JSON for KV storage.
|
||||||
func MarshalObservations(obs []Observation) (string, error) {
|
func MarshalObservations(obs []Observation) (string, error) {
|
||||||
data, err := json.Marshal(obs)
|
data, err := jsonv2.Marshal(obs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("marshal observations: %w", err)
|
return "", fmt.Errorf("marshal observations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ func UnmarshalObservations(data string) ([]Observation, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var obs []Observation
|
var obs []Observation
|
||||||
if err := json.Unmarshal([]byte(data), &obs); err != nil {
|
if err := jsonv2.Unmarshal([]byte(data), &obs); err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal observations: %w", err)
|
return nil, fmt.Errorf("unmarshal observations: %w", err)
|
||||||
}
|
}
|
||||||
return obs, nil
|
return obs, nil
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -38,11 +38,12 @@ func TestNewEmbedderFromConfig_OpenAI_NoKey(t *testing.T) {
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"data": []map[string]interface{}{
|
"data": []map[string]interface{}{
|
||||||
{"index": 0, "embedding": []float32{0.1, 0.2, 0.3}},
|
{"index": 0, "embedding": []float32{0.1, 0.2, 0.3}},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -70,9 +71,10 @@ func TestNewEmbedderFromConfig_OpenAI_FallbackKey(t *testing.T) {
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
|
func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.1, 0.2, 0.3}},
|
"embeddings": [][]float32{{0.1, 0.2, 0.3}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -97,9 +99,10 @@ func TestNewEmbedderFromConfig_Ollama(t *testing.T) {
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
|
func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.5}},
|
"embeddings": [][]float32{{0.5}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -119,9 +122,10 @@ func TestNewEmbedderFromConfig_Ollama_FallbackBase(t *testing.T) {
|
||||||
|
|
||||||
func TestNewEmbedderFromConfig_CaseInsensitive(t *testing.T) {
|
func TestNewEmbedderFromConfig_CaseInsensitive(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
respData, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"embeddings": [][]float32{{0.1}},
|
"embeddings": [][]float32{{0.1}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package store
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -93,7 +93,7 @@ func (o *OllamaEmbedder) embedBatch(ctx context.Context, texts []string) ([]memo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OllamaEmbedder) embedSingle(ctx context.Context, text string) (memory.Embedding, error) {
|
func (o *OllamaEmbedder) embedSingle(ctx context.Context, text string) (memory.Embedding, error) {
|
||||||
body, err := json.Marshal(ollamaEmbedRequest{
|
body, err := jsonv2.Marshal(ollamaEmbedRequest{
|
||||||
Model: o.model,
|
Model: o.model,
|
||||||
Input: text,
|
Input: text,
|
||||||
})
|
})
|
||||||
|
|
@ -119,7 +119,7 @@ func (o *OllamaEmbedder) embedSingle(ctx context.Context, text string) (memory.E
|
||||||
}
|
}
|
||||||
|
|
||||||
var result ollamaEmbedResponse
|
var result ollamaEmbedResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := jsonv2.UnmarshalRead(resp.Body, &result); err != nil {
|
||||||
return nil, fmt.Errorf("decode response: %w", err)
|
return nil, fmt.Errorf("decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -18,14 +18,15 @@ func TestOllamaEmbedder_Embed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var req ollamaEmbedRequest
|
var req ollamaEmbedRequest
|
||||||
json.NewDecoder(r.Body).Decode(&req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
if req.Model != "test-model" {
|
if req.Model != "test-model" {
|
||||||
t.Errorf("expected model 'test-model', got %q", req.Model)
|
t.Errorf("expected model 'test-model', got %q", req.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(ollamaEmbedResponse{
|
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{
|
||||||
Embeddings: [][]float32{{0.1, 0.2, 0.3, 0.4}},
|
Embeddings: [][]float32{{0.1, 0.2, 0.3, 0.4}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -55,9 +56,10 @@ func TestOllamaEmbedder_EmbedBatch(t *testing.T) {
|
||||||
callCount := 0
|
callCount := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
callCount++
|
callCount++
|
||||||
json.NewEncoder(w).Encode(ollamaEmbedResponse{
|
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{
|
||||||
Embeddings: [][]float32{{float32(callCount) * 0.1}},
|
Embeddings: [][]float32{{float32(callCount) * 0.1}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -91,7 +93,8 @@ func TestOllamaEmbedder_ServerError(t *testing.T) {
|
||||||
|
|
||||||
func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
|
func TestOllamaEmbedder_EmptyResponse(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(ollamaEmbedResponse{Embeddings: [][]float32{}})
|
respData, _ := jsonv2.Marshal(ollamaEmbedResponse{Embeddings: [][]float32{}})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package store
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -59,7 +59,7 @@ func NewOpenAIEmbedder(cfg OpenAIEmbedderConfig) *OpenAIEmbedder {
|
||||||
type openAIEmbedRequest struct {
|
type openAIEmbedRequest struct {
|
||||||
Input interface{} `json:"input"` // string or []string
|
Input interface{} `json:"input"` // string or []string
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
EncodingFormat string `json:"encoding_format,omitempty"`
|
EncodingFormat string `json:"encoding_format,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type openAIEmbedResponse struct {
|
type openAIEmbedResponse struct {
|
||||||
|
|
@ -100,7 +100,7 @@ func (o *OpenAIEmbedder) call(ctx context.Context, texts []string) ([]memory.Emb
|
||||||
input = texts
|
input = texts
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := json.Marshal(openAIEmbedRequest{
|
body, err := jsonv2.Marshal(openAIEmbedRequest{
|
||||||
Input: input,
|
Input: input,
|
||||||
Model: o.model,
|
Model: o.model,
|
||||||
EncodingFormat: "float",
|
EncodingFormat: "float",
|
||||||
|
|
@ -130,7 +130,7 @@ func (o *OpenAIEmbedder) call(ctx context.Context, texts []string) ([]memory.Emb
|
||||||
}
|
}
|
||||||
|
|
||||||
var result openAIEmbedResponse
|
var result openAIEmbedResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := jsonv2.UnmarshalRead(resp.Body, &result); err != nil {
|
||||||
return nil, fmt.Errorf("decode response: %w", err)
|
return nil, fmt.Errorf("decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -20,16 +20,17 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var req openAIEmbedRequest
|
var req openAIEmbedRequest
|
||||||
json.NewDecoder(r.Body).Decode(&req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
if req.Model != "test-embed" {
|
if req.Model != "test-embed" {
|
||||||
t.Errorf("expected model 'test-embed', got %q", req.Model)
|
t.Errorf("expected model 'test-embed', got %q", req.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(openAIEmbedResponse{
|
respData, _ := jsonv2.Marshal(openAIEmbedResponse{
|
||||||
Data: []openAIEmbedData{
|
Data: []openAIEmbedData{
|
||||||
{Index: 0, Embedding: []float32{0.5, 0.6, 0.7}},
|
{Index: 0, Embedding: []float32{0.5, 0.6, 0.7}},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ func TestOpenAIEmbedder_Embed(t *testing.T) {
|
||||||
func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req openAIEmbedRequest
|
var req openAIEmbedRequest
|
||||||
json.NewDecoder(r.Body).Decode(&req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
|
|
||||||
// Batch request should send array
|
// Batch request should send array
|
||||||
texts, ok := req.Input.([]interface{})
|
texts, ok := req.Input.([]interface{})
|
||||||
|
|
@ -73,7 +74,8 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(openAIEmbedResponse{Data: data})
|
respData, _ := jsonv2.Marshal(openAIEmbedResponse{Data: data})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -94,16 +96,17 @@ func TestOpenAIEmbedder_EmbedBatch(t *testing.T) {
|
||||||
func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
|
func TestOpenAIEmbedder_SingleTextNotArray(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req openAIEmbedRequest
|
var req openAIEmbedRequest
|
||||||
json.NewDecoder(r.Body).Decode(&req)
|
jsonv2.UnmarshalRead(r.Body, &req)
|
||||||
|
|
||||||
// Single text should be sent as string, not array
|
// Single text should be sent as string, not array
|
||||||
if _, ok := req.Input.(string); !ok {
|
if _, ok := req.Input.(string); !ok {
|
||||||
t.Errorf("expected string input for single text, got %T", req.Input)
|
t.Errorf("expected string input for single text, got %T", req.Input)
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(openAIEmbedResponse{
|
respData, _ := jsonv2.Marshal(openAIEmbedResponse{
|
||||||
Data: []openAIEmbedData{{Index: 0, Embedding: []float32{1.0}}},
|
Data: []openAIEmbedData{{Index: 0, Embedding: []float32{1.0}}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -140,9 +143,10 @@ func TestOpenAIEmbedder_NoAuth(t *testing.T) {
|
||||||
if r.Header.Get("Authorization") != "" {
|
if r.Header.Get("Authorization") != "" {
|
||||||
t.Error("expected no auth header when key is empty")
|
t.Error("expected no auth header when key is empty")
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(openAIEmbedResponse{
|
respData, _ := jsonv2.Marshal(openAIEmbedResponse{
|
||||||
Data: []openAIEmbedData{{Index: 0, Embedding: []float32{1.0}}},
|
Data: []openAIEmbedData{{Index: 0, Embedding: []float32{1.0}}},
|
||||||
})
|
})
|
||||||
|
w.Write(respData)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
|
|
@ -25,31 +25,31 @@ const (
|
||||||
// MemoryToolRequest is the input to the memory tool.
|
// MemoryToolRequest is the input to the memory tool.
|
||||||
type MemoryToolRequest struct {
|
type MemoryToolRequest struct {
|
||||||
Action MemoryToolAction `json:"action"`
|
Action MemoryToolAction `json:"action"`
|
||||||
Query string `json:"query,omitempty"` // For search
|
Query string `json:"query,omitzero"` // For search
|
||||||
ID string `json:"id,omitempty"` // For read/update/delete
|
ID string `json:"id,omitzero"` // For read/update/delete
|
||||||
Content string `json:"content,omitempty"` // For write/update
|
Content string `json:"content,omitzero"` // For write/update
|
||||||
Source string `json:"source,omitempty"` // For write
|
Source string `json:"source,omitzero"` // For write
|
||||||
Sector string `json:"sector,omitempty"` // For write: episodic/semantic/procedural/reflective
|
Sector string `json:"sector,omitzero"` // For write: episodic/semantic/procedural/reflective
|
||||||
Tags string `json:"tags,omitempty"` // For write: comma-separated
|
Tags string `json:"tags,omitzero"` // For write: comma-separated
|
||||||
Tier string `json:"tier,omitempty"` // "recall" or "archival" — defaults to "recall"
|
Tier string `json:"tier,omitzero"` // "recall" or "archival" — defaults to "recall"
|
||||||
Limit int `json:"limit,omitempty"` // For search — defaults to 5
|
Limit int `json:"limit,omitzero"` // For search — defaults to 5
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryToolResponse is the output of the memory tool.
|
// MemoryToolResponse is the output of the memory tool.
|
||||||
type MemoryToolResponse struct {
|
type MemoryToolResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitzero"`
|
||||||
Results []MemoryToolEntry `json:"results,omitempty"`
|
Results []MemoryToolEntry `json:"results,omitzero"`
|
||||||
Status *MemoryToolStatus `json:"status,omitempty"`
|
Status *MemoryToolStatus `json:"status,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryToolEntry is a single memory entry in tool results.
|
// MemoryToolEntry is a single memory entry in tool results.
|
||||||
type MemoryToolEntry struct {
|
type MemoryToolEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitzero"`
|
||||||
Sector string `json:"sector,omitempty"`
|
Sector string `json:"sector,omitzero"`
|
||||||
Score float64 `json:"score,omitempty"`
|
Score float64 `json:"score,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryToolStatus summarizes the memory system state.
|
// MemoryToolStatus summarizes the memory system state.
|
||||||
|
|
@ -82,7 +82,7 @@ func NewMemoryTool(store *MemoryStore, agentID, session string) *MemoryTool {
|
||||||
// Execute processes a memory tool request and returns a JSON response.
|
// Execute processes a memory tool request and returns a JSON response.
|
||||||
func (t *MemoryTool) Execute(ctx context.Context, input string) (string, error) {
|
func (t *MemoryTool) Execute(ctx context.Context, input string) (string, error) {
|
||||||
var req MemoryToolRequest
|
var req MemoryToolRequest
|
||||||
if err := json.Unmarshal([]byte(input), &req); err != nil {
|
if err := jsonv2.Unmarshal([]byte(input), &req); err != nil {
|
||||||
return t.errorResponse("invalid input: " + err.Error()), nil
|
return t.errorResponse("invalid input: " + err.Error()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,6 +325,6 @@ func (t *MemoryTool) errorResponse(msg string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemoryTool) jsonResponse(resp *MemoryToolResponse) string {
|
func (t *MemoryTool) jsonResponse(resp *MemoryToolResponse) string {
|
||||||
b, _ := json.Marshal(resp)
|
b, _ := jsonv2.Marshal(resp)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -22,7 +22,7 @@ func executeAndParse(t *testing.T, tool *MemoryTool, input string) *MemoryToolRe
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var resp MemoryToolResponse
|
var resp MemoryToolResponse
|
||||||
require.NoError(t, json.Unmarshal([]byte(raw), &resp))
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &resp))
|
||||||
return &resp
|
return &resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -51,7 +52,7 @@ func LoadOpenClawConfig(configPath string) (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var raw map[string]interface{}
|
var raw map[string]interface{}
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := jsonv2.Unmarshal(data, &raw); err != nil {
|
||||||
return nil, fmt.Errorf("parsing OpenClaw config: %w", err)
|
return nil, fmt.Errorf("parsing OpenClaw config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -104,7 +105,7 @@ func TestLoadOpenClawConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(openclawConfig)
|
data, err := jsonv2.Marshal(openclawConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -583,7 +584,7 @@ func TestRunDryRun(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(configData)
|
data, _ := jsonv2.Marshal(configData)
|
||||||
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
||||||
|
|
||||||
opts := Options{
|
opts := Options{
|
||||||
|
|
@ -638,7 +639,7 @@ func TestRunFullMigration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(configData)
|
data, _ := jsonv2.Marshal(configData)
|
||||||
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
||||||
|
|
||||||
opts := Options{
|
opts := Options{
|
||||||
|
|
@ -784,7 +785,7 @@ func TestRunConfigOnly(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(configData)
|
data, _ := jsonv2.Marshal(configData)
|
||||||
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
||||||
|
|
||||||
opts := Options{
|
opts := Options{
|
||||||
|
|
@ -824,7 +825,7 @@ func TestRunWorkspaceOnly(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(configData)
|
data, _ := jsonv2.Marshal(configData)
|
||||||
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
os.WriteFile(filepath.Join(openclawHome, "openclaw.json"), data, 0644)
|
||||||
|
|
||||||
opts := Options{
|
opts := Options{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package pcerrors
|
package pcerrors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLIHandler is the PicoClaw error lifecycle boundary for the CLI.
|
// CLIHandler is the PicoClaw error lifecycle boundary for the CLI.
|
||||||
|
|
@ -50,7 +51,7 @@ func (h CLIHandler) Handle(err error) int {
|
||||||
if h.Verbose {
|
if h.Verbose {
|
||||||
record["detail"] = err.Error()
|
record["detail"] = err.Error()
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(record)
|
data, _ := jsonv2.Marshal(record)
|
||||||
_, _ = fmt.Fprintln(w, string(data))
|
_, _ = fmt.Fprintln(w, string(data))
|
||||||
} else {
|
} else {
|
||||||
_, _ = fmt.Fprintln(w, msg)
|
_, _ = fmt.Fprintln(w, msg)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -50,12 +50,12 @@ func ExtractJSON(text string, dest interface{}, opts *ExtractJSONOptions) error
|
||||||
return ErrNoJSON
|
return ErrNoJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(strings.NewReader(cleaned))
|
var jsonOpts []jsonv2.Options
|
||||||
if opts.DisallowUnknownFields {
|
if opts.DisallowUnknownFields {
|
||||||
dec.DisallowUnknownFields()
|
jsonOpts = append(jsonOpts, jsonv2.RejectUnknownMembers(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dec.Decode(dest); err != nil {
|
if err := jsonv2.Unmarshal([]byte(cleaned), dest, jsonOpts...); err != nil {
|
||||||
return fmt.Errorf("json decode: %w", err)
|
return fmt.Errorf("json decode: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ func TestExtractJSON_InjectionAttempts(t *testing.T) {
|
||||||
&ExtractJSONOptions{DisallowUnknownFields: true},
|
&ExtractJSONOptions{DisallowUnknownFields: true},
|
||||||
func(t *testing.T, err error) {
|
func(t *testing.T, err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "unknown field")
|
assert.Contains(t, err.Error(), "unknown object member name")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -132,7 +133,7 @@ func (ss *SecretStore) load() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var entries []SecretEntry
|
var entries []SecretEntry
|
||||||
if err := json.Unmarshal(data, &entries); err != nil {
|
if err := jsonv2.Unmarshal(data, &entries); err != nil {
|
||||||
return fmt.Errorf("parse secret store: %w", err)
|
return fmt.Errorf("parse secret store: %w", err)
|
||||||
}
|
}
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
|
|
@ -150,7 +151,7 @@ func (ss *SecretStore) save() error {
|
||||||
}
|
}
|
||||||
ss.mu.RUnlock()
|
ss.mu.RUnlock()
|
||||||
|
|
||||||
data, err := json.MarshalIndent(entries, "", " ")
|
data, err := jsonv2.Marshal(entries, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshal secret store: %w", err)
|
return fmt.Errorf("marshal secret store: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package securebus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/itr"
|
"github.com/sipeed/picoclaw/pkg/itr"
|
||||||
|
|
@ -62,6 +64,7 @@ type Bus struct {
|
||||||
executor ToolExecutor
|
executor ToolExecutor
|
||||||
toolSearch ToolSearchFunc // nil = no tool search support
|
toolSearch ToolSearchFunc // nil = no tool search support
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Bus and starts background worker goroutines.
|
// New creates a Bus and starts background worker goroutines.
|
||||||
|
|
@ -106,10 +109,12 @@ func (b *Bus) AuditLog() *AuditLog {
|
||||||
return b.audit
|
return b.audit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close shuts down the bus workers gracefully.
|
// Close shuts down the bus workers gracefully. Safe to call multiple times.
|
||||||
func (b *Bus) Close() {
|
func (b *Bus) Close() {
|
||||||
|
b.closeOnce.Do(func() {
|
||||||
b.transport.Close()
|
b.transport.Close()
|
||||||
close(b.done)
|
close(b.done)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute is a convenience method for in-process callers that don't want to
|
// Execute is a convenience method for in-process callers that don't want to
|
||||||
|
|
@ -155,12 +160,16 @@ func (b *Bus) dispatch(ctx context.Context, req itr.ToolRequest) itr.ToolRespons
|
||||||
case itr.CmdToolSearch:
|
case itr.CmdToolSearch:
|
||||||
resp := b.handleToolSearch(ctx, req)
|
resp := b.handleToolSearch(ctx, req)
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
default:
|
default:
|
||||||
resp := b.handleRLMCommand(ctx, req)
|
resp := b.handleRLMCommand(ctx, req)
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +177,9 @@ func (b *Bus) dispatch(ctx context.Context, req itr.ToolRequest) itr.ToolRespons
|
||||||
if !ok {
|
if !ok {
|
||||||
event.IsError = true
|
event.IsError = true
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return itr.NewErrorResponse(req.ID, "internal: payload is not ToolExec")
|
return itr.NewErrorResponse(req.ID, "internal: payload is not ToolExec")
|
||||||
}
|
}
|
||||||
event.ToolName = te.ToolName
|
event.ToolName = te.ToolName
|
||||||
|
|
@ -184,17 +195,21 @@ func (b *Bus) dispatch(ctx context.Context, req itr.ToolRequest) itr.ToolRespons
|
||||||
event.IsError = true
|
event.IsError = true
|
||||||
event.PolicyViolation = err.Error()
|
event.PolicyViolation = err.Error()
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return itr.NewErrorResponse(req.ID, "policy violation: "+err.Error())
|
return itr.NewErrorResponse(req.ID, "policy violation: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Deserialise args — always produce a non-nil map for safe injection.
|
// 3. Deserialise args — always produce a non-nil map for safe injection.
|
||||||
args := make(map[string]interface{})
|
args := make(map[string]interface{})
|
||||||
if te.ArgsJSON != "" && te.ArgsJSON != "null" {
|
if te.ArgsJSON != "" && te.ArgsJSON != "null" {
|
||||||
if err := json.Unmarshal([]byte(te.ArgsJSON), &args); err != nil {
|
if err := jsonv2.Unmarshal([]byte(te.ArgsJSON), &args); err != nil {
|
||||||
event.IsError = true
|
event.IsError = true
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return itr.NewErrorResponse(req.ID, fmt.Sprintf("invalid args JSON: %v", err))
|
return itr.NewErrorResponse(req.ID, fmt.Sprintf("invalid args JSON: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +219,9 @@ func (b *Bus) dispatch(ctx context.Context, req itr.ToolRequest) itr.ToolRespons
|
||||||
if err != nil {
|
if err != nil {
|
||||||
event.IsError = true
|
event.IsError = true
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return itr.NewErrorResponse(req.ID, "secret injection failed: "+err.Error())
|
return itr.NewErrorResponse(req.ID, "secret injection failed: "+err.Error())
|
||||||
}
|
}
|
||||||
event.SecretsAccessed = injectedSecrets
|
event.SecretsAccessed = injectedSecrets
|
||||||
|
|
@ -232,7 +249,9 @@ func (b *Bus) dispatch(ctx context.Context, req itr.ToolRequest) itr.ToolRespons
|
||||||
|
|
||||||
event.IsError = resp.IsError
|
event.IsError = resp.IsError
|
||||||
event.DurationMS = time.Since(start).Milliseconds()
|
event.DurationMS = time.Since(start).Milliseconds()
|
||||||
_ = b.audit.Append(event)
|
if err := b.audit.Append(event); err != nil {
|
||||||
|
log.Printf("securebus: audit append failed: %v", err)
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package securebus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/itr"
|
"github.com/sipeed/picoclaw/pkg/itr"
|
||||||
|
|
@ -49,7 +49,7 @@ func makeArgsJSON(kv map[string]interface{}) string {
|
||||||
if kv == nil {
|
if kv == nil {
|
||||||
return "{}"
|
return "{}"
|
||||||
}
|
}
|
||||||
b, _ := json.Marshal(kv)
|
b, _ := jsonv2.Marshal(kv)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,3 +248,56 @@ func TestBus_RLMFinalCommand(t *testing.T) {
|
||||||
assert.False(t, resp.IsError)
|
assert.False(t, resp.IsError)
|
||||||
assert.Equal(t, "the answer", resp.Result)
|
assert.Equal(t, "the answer", resp.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBus_CloseIdempotent(t *testing.T) {
|
||||||
|
bus := makeBus(t, nil, nil)
|
||||||
|
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
bus.Close()
|
||||||
|
bus.Close()
|
||||||
|
bus.Close()
|
||||||
|
}, "Close() must be safe to call multiple times")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBus_ToolSearch(t *testing.T) {
|
||||||
|
bus := makeBus(t, nil, nil)
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
bus.SetToolSearch(func(query string, maxResults int) string {
|
||||||
|
return `[{"name":"read_file","description":"reads a file"}]`
|
||||||
|
})
|
||||||
|
|
||||||
|
req := itr.NewToolSearchRequest("req-search", "sess", "file operations", 5)
|
||||||
|
resp := bus.Execute(context.Background(), req)
|
||||||
|
|
||||||
|
assert.False(t, resp.IsError)
|
||||||
|
assert.Contains(t, resp.Result, "read_file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBus_ToolSearchNotConfigured(t *testing.T) {
|
||||||
|
bus := makeBus(t, nil, nil)
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
req := itr.NewToolSearchRequest("req-search2", "sess", "anything", 5)
|
||||||
|
resp := bus.Execute(context.Background(), req)
|
||||||
|
|
||||||
|
assert.True(t, resp.IsError)
|
||||||
|
assert.Contains(t, resp.Result, "not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBus_NilToolResult(t *testing.T) {
|
||||||
|
executor := func(ctx context.Context, name string, args map[string]interface{}) *tools.ToolResult {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
capLookup := func(name string) (tools.ToolCapabilities, bool) {
|
||||||
|
return tools.ZeroCapabilities(), true
|
||||||
|
}
|
||||||
|
bus := securebus.New(securebus.DefaultBusConfig(), nil, capLookup, executor)
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
req := itr.NewToolExecRequest("req-nil", "sess", "tc", "something", "{}")
|
||||||
|
resp := bus.Execute(context.Background(), req)
|
||||||
|
|
||||||
|
assert.False(t, resp.IsError)
|
||||||
|
assert.Empty(t, resp.Result)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/cache"
|
"github.com/sipeed/picoclaw/pkg/cache"
|
||||||
"github.com/sipeed/picoclaw/pkg/ids"
|
"github.com/sipeed/picoclaw/pkg/ids"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
|
@ -19,7 +21,7 @@ import (
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Messages []messages.Message `json:"messages"`
|
Messages []messages.Message `json:"messages"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitzero"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +199,7 @@ func (sm *SessionManager) loadSessionFromDisk(key string) *Session {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var session Session
|
var session Session
|
||||||
if err := json.Unmarshal(data, &session); err != nil {
|
if err := jsonv2.Unmarshal(data, &session); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &session
|
return &session
|
||||||
|
|
@ -487,7 +489,7 @@ func snapshotSession(s *Session) Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SessionManager) writeSessionToDisk(key string, session *Session) error {
|
func (sm *SessionManager) writeSessionToDisk(key string, session *Session) error {
|
||||||
data, err := json.MarshalIndent(session, "", " ")
|
data, err := jsonv2.Marshal(session, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -558,7 +560,7 @@ func (sm *SessionManager) loadSessions() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var session Session
|
var session Session
|
||||||
if err := json.Unmarshal(data, &session); err != nil {
|
if err := jsonv2.Unmarshal(data, &session); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package skills
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -10,6 +9,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SkillInstaller struct {
|
type SkillInstaller struct {
|
||||||
|
|
@ -117,7 +118,7 @@ func (si *SkillInstaller) ListAvailableSkills(ctx context.Context) ([]AvailableS
|
||||||
}
|
}
|
||||||
|
|
||||||
var skills []AvailableSkill
|
var skills []AvailableSkill
|
||||||
if err := json.Unmarshal(body, &skills); err != nil {
|
if err := jsonv2.Unmarshal(body, &skills); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse skills list: %w", err)
|
return nil, fmt.Errorf("failed to parse skills list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package skills
|
package skills
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
@ -9,6 +8,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var namePattern = regexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`)
|
var namePattern = regexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`)
|
||||||
|
|
@ -21,10 +22,10 @@ const (
|
||||||
type SkillMetadata struct {
|
type SkillMetadata struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitzero"`
|
||||||
Links []string `json:"links,omitempty"`
|
Links []string `json:"links,omitzero"`
|
||||||
Domain string `json:"domain,omitempty"`
|
Domain string `json:"domain,omitzero"`
|
||||||
IsMOC bool `json:"is_moc,omitempty"`
|
IsMOC bool `json:"is_moc,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SkillInfo struct {
|
type SkillInfo struct {
|
||||||
|
|
@ -32,8 +33,8 @@ type SkillInfo struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitzero"`
|
||||||
Domain string `json:"domain,omitempty"`
|
Domain string `json:"domain,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (info SkillInfo) validate() error {
|
func (info SkillInfo) validate() error {
|
||||||
|
|
@ -281,7 +282,7 @@ func (sl *SkillsLoader) getSkillMetadata(skillPath string) *SkillMetadata {
|
||||||
|
|
||||||
// Try JSON first (for backward compatibility)
|
// Try JSON first (for backward compatibility)
|
||||||
var jsonMeta SkillMetadata
|
var jsonMeta SkillMetadata
|
||||||
if err := json.Unmarshal([]byte(frontmatter), &jsonMeta); err == nil && jsonMeta.Name != "" {
|
if err := jsonv2.Unmarshal([]byte(frontmatter), &jsonMeta); err == nil && jsonMeta.Name != "" {
|
||||||
return &jsonMeta
|
return &jsonMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -10,6 +9,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/memory"
|
"github.com/sipeed/picoclaw/pkg/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,10 +21,10 @@ const kvAgentID = "picoclaw"
|
||||||
// It includes information about the last active channel/chat.
|
// It includes information about the last active channel/chat.
|
||||||
type State struct {
|
type State struct {
|
||||||
// LastChannel is the last channel used for communication
|
// LastChannel is the last channel used for communication
|
||||||
LastChannel string `json:"last_channel,omitempty"`
|
LastChannel string `json:"last_channel,omitzero"`
|
||||||
|
|
||||||
// LastChatID is the last chat ID used for communication
|
// LastChatID is the last chat ID used for communication
|
||||||
LastChatID string `json:"last_chat_id,omitempty"`
|
LastChatID string `json:"last_chat_id,omitzero"`
|
||||||
|
|
||||||
// Timestamp is the last time this state was updated
|
// Timestamp is the last time this state was updated
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
|
@ -73,7 +75,7 @@ func NewManager(workspace string, opts ...Option) *Manager {
|
||||||
|
|
||||||
if _, err := os.Stat(stateFile); os.IsNotExist(err) {
|
if _, err := os.Stat(stateFile); os.IsNotExist(err) {
|
||||||
if data, err := os.ReadFile(oldStateFile); err == nil {
|
if data, err := os.ReadFile(oldStateFile); err == nil {
|
||||||
if err := json.Unmarshal(data, sm.state); err == nil {
|
if err := jsonv2.Unmarshal(data, sm.state); err == nil {
|
||||||
sm.saveAtomic()
|
sm.saveAtomic()
|
||||||
log.Printf("[INFO] state: migrated state from %s to %s", oldStateFile, stateFile)
|
log.Printf("[INFO] state: migrated state from %s to %s", oldStateFile, stateFile)
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +182,7 @@ func (sm *Manager) saveAtomic() error {
|
||||||
tempFile := sm.stateFile + ".tmp"
|
tempFile := sm.stateFile + ".tmp"
|
||||||
|
|
||||||
// Marshal state to JSON
|
// Marshal state to JSON
|
||||||
data, err := json.MarshalIndent(sm.state, "", " ")
|
data, err := jsonv2.Marshal(sm.state, jsontext.WithIndent(" "))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal state: %w", err)
|
return fmt.Errorf("failed to marshal state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +213,7 @@ func (sm *Manager) load() error {
|
||||||
return fmt.Errorf("failed to read state file: %w", err)
|
return fmt.Errorf("failed to read state file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, sm.state); err != nil {
|
if err := jsonv2.Unmarshal(data, sm.state); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal state: %w", err)
|
return fmt.Errorf("failed to unmarshal state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAtomicSave(t *testing.T) {
|
func TestAtomicSave(t *testing.T) {
|
||||||
|
|
@ -162,7 +163,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var state State
|
var state State
|
||||||
if err := json.Unmarshal(data, &state); err != nil {
|
if err := jsonv2.Unmarshal(data, &state); err != nil {
|
||||||
t.Errorf("State file contains invalid JSON: %v", err)
|
t.Errorf("State file contains invalid JSON: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ func (t *ToolCallTool) Execute(ctx context.Context, args map[string]interface{})
|
||||||
if len(v) > maxArgsJSON {
|
if len(v) > maxArgsJSON {
|
||||||
return ErrorResult(fmt.Sprintf("arguments JSON too large: %d bytes (max %d)", len(v), maxArgsJSON))
|
return ErrorResult(fmt.Sprintf("arguments JSON too large: %d bytes (max %d)", len(v), maxArgsJSON))
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal([]byte(v), &toolArgs); err != nil {
|
if err := jsonv2.Unmarshal([]byte(v), &toolArgs); err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("invalid arguments JSON: %v", err))
|
return ErrorResult(fmt.Sprintf("invalid arguments JSON: %v", err))
|
||||||
}
|
}
|
||||||
case nil:
|
case nil:
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
"github.com/sipeed/picoclaw/pkg/messages"
|
"github.com/sipeed/picoclaw/pkg/messages"
|
||||||
"github.com/sipeed/picoclaw/pkg/session"
|
"github.com/sipeed/picoclaw/pkg/session"
|
||||||
|
|
@ -109,7 +110,7 @@ func (t *StartFocusTool) Execute(ctx context.Context, args map[string]interface{
|
||||||
StartedAt: time.Now(),
|
StartedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(state)
|
data, err := jsonv2.Marshal(state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("marshal focus state: %v", err))
|
return ErrorResult(fmt.Sprintf("marshal focus state: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +182,7 @@ func (t *CompleteFocusTool) Execute(ctx context.Context, args map[string]interfa
|
||||||
}
|
}
|
||||||
|
|
||||||
var state FocusState
|
var state FocusState
|
||||||
if err := json.Unmarshal([]byte(raw), &state); err != nil {
|
if err := jsonv2.Unmarshal([]byte(raw), &state); err != nil {
|
||||||
return ErrorResult(fmt.Sprintf("corrupt focus state: %v", err))
|
return ErrorResult(fmt.Sprintf("corrupt focus state: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,7 +252,7 @@ func (t *CompleteFocusTool) appendKnowledge(ctx context.Context, sessionKey, top
|
||||||
kb := &KnowledgeBlock{}
|
kb := &KnowledgeBlock{}
|
||||||
raw, err := t.delegate.GetKV(ctx, focusAgentID, kvKey)
|
raw, err := t.delegate.GetKV(ctx, focusAgentID, kvKey)
|
||||||
if err == nil && raw != "" {
|
if err == nil && raw != "" {
|
||||||
_ = json.Unmarshal([]byte(raw), kb)
|
_ = jsonv2.Unmarshal([]byte(raw), kb)
|
||||||
}
|
}
|
||||||
|
|
||||||
kb.Entries = append(kb.Entries, KnowledgeEntry{
|
kb.Entries = append(kb.Entries, KnowledgeEntry{
|
||||||
|
|
@ -260,7 +261,7 @@ func (t *CompleteFocusTool) appendKnowledge(ctx context.Context, sessionKey, top
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
data, err := json.Marshal(kb)
|
data, err := jsonv2.Marshal(kb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +281,7 @@ func LoadKnowledgeBlock(ctx context.Context, delegate KVStore, sessionKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
var kb KnowledgeBlock
|
var kb KnowledgeBlock
|
||||||
if err := json.Unmarshal([]byte(raw), &kb); err != nil {
|
if err := jsonv2.Unmarshal([]byte(raw), &kb); err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/messages"
|
"github.com/sipeed/picoclaw/pkg/messages"
|
||||||
"github.com/sipeed/picoclaw/pkg/session"
|
"github.com/sipeed/picoclaw/pkg/session"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -75,7 +76,7 @@ func TestStartFocus(t *testing.T) {
|
||||||
require.NotEmpty(t, raw)
|
require.NotEmpty(t, raw)
|
||||||
|
|
||||||
var state FocusState
|
var state FocusState
|
||||||
require.NoError(t, json.Unmarshal([]byte(raw), &state))
|
require.NoError(t, jsonv2.Unmarshal([]byte(raw), &state))
|
||||||
assert.Equal(t, "investigate auth bug", state.Topic)
|
assert.Equal(t, "investigate auth bug", state.Topic)
|
||||||
assert.Equal(t, 2, state.CheckpointIndex)
|
assert.Equal(t, 2, state.CheckpointIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +135,7 @@ func TestCompleteFocus(t *testing.T) {
|
||||||
require.NotEmpty(t, knowledgeRaw)
|
require.NotEmpty(t, knowledgeRaw)
|
||||||
|
|
||||||
var kb KnowledgeBlock
|
var kb KnowledgeBlock
|
||||||
require.NoError(t, json.Unmarshal([]byte(knowledgeRaw), &kb))
|
require.NoError(t, jsonv2.Unmarshal([]byte(knowledgeRaw), &kb))
|
||||||
require.Len(t, kb.Entries, 1)
|
require.Len(t, kb.Entries, 1)
|
||||||
assert.Equal(t, "debug auth", kb.Entries[0].Topic)
|
assert.Equal(t, "debug auth", kb.Entries[0].Topic)
|
||||||
assert.Contains(t, kb.Entries[0].Summary, "token validation")
|
assert.Contains(t, kb.Entries[0].Summary, "token validation")
|
||||||
|
|
@ -184,7 +185,7 @@ func TestCompleteFocus_MultipleKnowledgeEntries(t *testing.T) {
|
||||||
|
|
||||||
knowledgeRaw, _ := delegate.GetKV(ctx, focusAgentID, knowledgeKVPrefix+sk)
|
knowledgeRaw, _ := delegate.GetKV(ctx, focusAgentID, knowledgeKVPrefix+sk)
|
||||||
var kb KnowledgeBlock
|
var kb KnowledgeBlock
|
||||||
require.NoError(t, json.Unmarshal([]byte(knowledgeRaw), &kb))
|
require.NoError(t, jsonv2.Unmarshal([]byte(knowledgeRaw), &kb))
|
||||||
require.Len(t, kb.Entries, 2)
|
require.Len(t, kb.Entries, 2)
|
||||||
assert.Equal(t, "topic A", kb.Entries[0].Topic)
|
assert.Equal(t, "topic A", kb.Entries[0].Topic)
|
||||||
assert.Equal(t, "topic B", kb.Entries[1].Topic)
|
assert.Equal(t, "topic B", kb.Entries[1].Topic)
|
||||||
|
|
@ -286,7 +287,7 @@ func TestLoadKnowledgeBlock(t *testing.T) {
|
||||||
{Topic: "Test", Summary: "Test summary"},
|
{Topic: "Test", Summary: "Test summary"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(kb)
|
data, _ := jsonv2.Marshal(kb)
|
||||||
_ = delegate.UpsertKV(ctx, focusAgentID, knowledgeKVPrefix+"test-session", string(data))
|
_ = delegate.UpsertKV(ctx, focusAgentID, knowledgeKVPrefix+"test-session", string(data))
|
||||||
|
|
||||||
block = LoadKnowledgeBlock(ctx, delegate, "test-session")
|
block = LoadKnowledgeBlock(ctx, delegate, "test-session")
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2CTool provides I2C bus interaction for reading sensors and controlling peripherals.
|
// I2CTool provides I2C bus interaction for reading sensors and controlling peripherals.
|
||||||
|
|
@ -111,7 +113,7 @@ func (t *I2CTool) detect() *ToolResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(buses, "", " ")
|
result, _ := jsonv2.Marshal(buses, jsontext.WithIndent(" "))
|
||||||
return SilentResult(fmt.Sprintf("Found %d I2C bus(es):\n%s", len(buses), string(result)))
|
return SilentResult(fmt.Sprintf("Found %d I2C bus(es):\n%s", len(buses), string(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
|
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
|
||||||
|
|
@ -104,7 +106,7 @@ func (t *I2CTool) scan(args map[string]interface{}) *ToolResult {
|
||||||
|
|
||||||
type deviceEntry struct {
|
type deviceEntry struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var found []deviceEntry
|
var found []deviceEntry
|
||||||
|
|
@ -133,11 +135,11 @@ func (t *I2CTool) scan(args map[string]interface{}) *ToolResult {
|
||||||
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
|
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]interface{}{
|
result, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"bus": devPath,
|
"bus": devPath,
|
||||||
"devices": found,
|
"devices": found,
|
||||||
"count": len(found),
|
"count": len(found),
|
||||||
}, "", " ")
|
}, jsontext.WithIndent(" "))
|
||||||
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
|
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,13 +203,13 @@ func (t *I2CTool) readDevice(args map[string]interface{}) *ToolResult {
|
||||||
intBytes[i] = int(buf[i])
|
intBytes[i] = int(buf[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]interface{}{
|
result, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"bus": devPath,
|
"bus": devPath,
|
||||||
"address": fmt.Sprintf("0x%02x", addr),
|
"address": fmt.Sprintf("0x%02x", addr),
|
||||||
"bytes": intBytes,
|
"bytes": intBytes,
|
||||||
"hex": hexBytes,
|
"hex": hexBytes,
|
||||||
"length": n,
|
"length": n,
|
||||||
}, "", " ")
|
}, jsontext.WithIndent(" "))
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import "encoding/json"
|
import jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
// ToolResult represents the structured return value from tool execution.
|
// ToolResult represents the structured return value from tool execution.
|
||||||
// It provides clear semantics for different types of results and supports
|
// It provides clear semantics for different types of results and supports
|
||||||
|
|
@ -124,7 +124,7 @@ func UserResult(content string) *ToolResult {
|
||||||
// The Err field is excluded from JSON output via the json:"-" tag.
|
// The Err field is excluded from JSON output via the json:"-" tag.
|
||||||
func (tr *ToolResult) MarshalJSON() ([]byte, error) {
|
func (tr *ToolResult) MarshalJSON() ([]byte, error) {
|
||||||
type Alias ToolResult
|
type Alias ToolResult
|
||||||
return json.Marshal(&struct {
|
return jsonv2.Marshal(&struct {
|
||||||
*Alias
|
*Alias
|
||||||
}{
|
}{
|
||||||
Alias: (*Alias)(tr),
|
Alias: (*Alias)(tr),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewToolResult(t *testing.T) {
|
func TestNewToolResult(t *testing.T) {
|
||||||
|
|
@ -125,14 +126,14 @@ func TestToolResultJSONSerialization(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Marshal to JSON
|
// Marshal to JSON
|
||||||
data, err := json.Marshal(tt.result)
|
data, err := jsonv2.Marshal(tt.result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to marshal: %v", err)
|
t.Fatalf("Failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal back
|
// Unmarshal back
|
||||||
var decoded ToolResult
|
var decoded ToolResult
|
||||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
if err := jsonv2.Unmarshal(data, &decoded); err != nil {
|
||||||
t.Fatalf("Failed to unmarshal: %v", err)
|
t.Fatalf("Failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,13 +169,13 @@ func TestToolResultWithErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify Err is not serialized
|
// Verify Err is not serialized
|
||||||
data, marshalErr := json.Marshal(result)
|
data, marshalErr := jsonv2.Marshal(result)
|
||||||
if marshalErr != nil {
|
if marshalErr != nil {
|
||||||
t.Fatalf("Failed to marshal: %v", marshalErr)
|
t.Fatalf("Failed to marshal: %v", marshalErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded ToolResult
|
var decoded ToolResult
|
||||||
if unmarshalErr := json.Unmarshal(data, &decoded); unmarshalErr != nil {
|
if unmarshalErr := jsonv2.Unmarshal(data, &decoded); unmarshalErr != nil {
|
||||||
t.Fatalf("Failed to unmarshal: %v", unmarshalErr)
|
t.Fatalf("Failed to unmarshal: %v", unmarshalErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,14 +187,14 @@ func TestToolResultWithErrors(t *testing.T) {
|
||||||
func TestToolResultJSONStructure(t *testing.T) {
|
func TestToolResultJSONStructure(t *testing.T) {
|
||||||
result := UserResult("test content")
|
result := UserResult("test content")
|
||||||
|
|
||||||
data, err := json.Marshal(result)
|
data, err := jsonv2.Marshal(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to marshal: %v", err)
|
t.Fatalf("Failed to marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify JSON structure
|
// Verify JSON structure
|
||||||
var parsed map[string]interface{}
|
var parsed map[string]interface{}
|
||||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
if err := jsonv2.Unmarshal(data, &parsed); err != nil {
|
||||||
t.Fatalf("Failed to parse JSON: %v", err)
|
t.Fatalf("Failed to parse JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToolSearchTool implements tool discovery via fuzzy search over the registry.
|
// ToolSearchTool implements tool discovery via fuzzy search over the registry.
|
||||||
|
|
@ -88,7 +89,7 @@ func (t *ToolSearchTool) Execute(_ context.Context, args map[string]interface{})
|
||||||
return &ToolResult{ForLLM: fmt.Sprintf("No tools match query: %q. Try a broader search or use tool_search with no query to list all.", query)}
|
return &ToolResult{ForLLM: fmt.Sprintf("No tools match query: %q. Try a broader search or use tool_search with no query to list all.", query)}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, _ := json.Marshal(results)
|
b, _ := jsonv2.Marshal(results)
|
||||||
return &ToolResult{ForLLM: string(b)}
|
return &ToolResult{ForLLM: string(b)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +112,7 @@ func (t *ToolSearchTool) listAll() *ToolResult {
|
||||||
return results[i].Name < results[j].Name
|
return results[i].Name < results[j].Name
|
||||||
})
|
})
|
||||||
|
|
||||||
b, _ := json.Marshal(results)
|
b, _ := jsonv2.Marshal(results)
|
||||||
return &ToolResult{ForLLM: string(b)}
|
return &ToolResult{ForLLM: string(b)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToolSearchTool_Name(t *testing.T) {
|
func TestToolSearchTool_Name(t *testing.T) {
|
||||||
|
|
@ -52,7 +53,7 @@ func TestToolSearchTool_ListAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
if err := json.Unmarshal([]byte(result.ForLLM), &results); err != nil {
|
if err := jsonv2.Unmarshal([]byte(result.ForLLM), &results); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ func TestToolSearchTool_EmptyQuery_ListsAll(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{"query": ""})
|
result := s.Execute(context.Background(), map[string]interface{}{"query": ""})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
if err := json.Unmarshal([]byte(result.ForLLM), &results); err != nil {
|
if err := jsonv2.Unmarshal([]byte(result.ForLLM), &results); err != nil {
|
||||||
t.Fatalf("failed to unmarshal: %v", err)
|
t.Fatalf("failed to unmarshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@ func TestToolSearchTool_ExactNameMatch(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{"query": "read_file"})
|
result := s.Execute(context.Background(), map[string]interface{}{"query": "read_file"})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
json.Unmarshal([]byte(result.ForLLM), &results)
|
jsonv2.Unmarshal([]byte(result.ForLLM), &results)
|
||||||
|
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
t.Fatal("expected at least one result")
|
t.Fatal("expected at least one result")
|
||||||
|
|
@ -116,7 +117,7 @@ func TestToolSearchTool_PartialMatch(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{"query": "file"})
|
result := s.Execute(context.Background(), map[string]interface{}{"query": "file"})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
json.Unmarshal([]byte(result.ForLLM), &results)
|
jsonv2.Unmarshal([]byte(result.ForLLM), &results)
|
||||||
|
|
||||||
// Both file tools should match, web_search should not (unless "file" appears somewhere)
|
// Both file tools should match, web_search should not (unless "file" appears somewhere)
|
||||||
if len(results) < 2 {
|
if len(results) < 2 {
|
||||||
|
|
@ -133,7 +134,7 @@ func TestToolSearchTool_DescriptionMatch(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{"query": "internet"})
|
result := s.Execute(context.Background(), map[string]interface{}{"query": "internet"})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
json.Unmarshal([]byte(result.ForLLM), &results)
|
jsonv2.Unmarshal([]byte(result.ForLLM), &results)
|
||||||
|
|
||||||
if len(results) != 1 {
|
if len(results) != 1 {
|
||||||
t.Errorf("expected 1 result, got %d", len(results))
|
t.Errorf("expected 1 result, got %d", len(results))
|
||||||
|
|
@ -153,7 +154,7 @@ func TestToolSearchTool_MultiTermQuery(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{"query": "web search"})
|
result := s.Execute(context.Background(), map[string]interface{}{"query": "web search"})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
json.Unmarshal([]byte(result.ForLLM), &results)
|
jsonv2.Unmarshal([]byte(result.ForLLM), &results)
|
||||||
|
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
t.Fatal("expected at least one result")
|
t.Fatal("expected at least one result")
|
||||||
|
|
@ -189,7 +190,7 @@ func TestToolSearchTool_ExcludesMetaTools(t *testing.T) {
|
||||||
result := s.Execute(context.Background(), map[string]interface{}{})
|
result := s.Execute(context.Background(), map[string]interface{}{})
|
||||||
|
|
||||||
var results []toolSearchResult
|
var results []toolSearchResult
|
||||||
json.Unmarshal([]byte(result.ForLLM), &results)
|
jsonv2.Unmarshal([]byte(result.ForLLM), &results)
|
||||||
|
|
||||||
for _, res := range results {
|
for _, res := range results {
|
||||||
if res.Name == "tool_search" || res.Name == "tool_call" {
|
if res.Name == "tool_search" || res.Name == "tool_call" {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPITool provides SPI bus interaction for high-speed peripheral communication.
|
// SPITool provides SPI bus interaction for high-speed peripheral communication.
|
||||||
|
|
@ -113,7 +115,7 @@ func (t *SPITool) list() *ToolResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(devices, "", " ")
|
result, _ := jsonv2.Marshal(devices, jsontext.WithIndent(" "))
|
||||||
return SilentResult(fmt.Sprintf("Found %d SPI device(s):\n%s", len(devices), string(result)))
|
return SilentResult(fmt.Sprintf("Found %d SPI device(s):\n%s", len(devices), string(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
"github.com/go-json-experiment/json/jsontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI ioctl constants from Linux kernel headers.
|
// SPI ioctl constants from Linux kernel headers.
|
||||||
|
|
@ -130,12 +132,12 @@ func (t *SPITool) transfer(args map[string]interface{}) *ToolResult {
|
||||||
intBytes[i] = int(b)
|
intBytes[i] = int(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]interface{}{
|
result, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"device": devPath,
|
"device": devPath,
|
||||||
"sent": len(txBuf),
|
"sent": len(txBuf),
|
||||||
"received": intBytes,
|
"received": intBytes,
|
||||||
"hex": hexBytes,
|
"hex": hexBytes,
|
||||||
}, "", " ")
|
}, jsontext.WithIndent(" "))
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,11 +188,11 @@ func (t *SPITool) readDevice(args map[string]interface{}) *ToolResult {
|
||||||
intBytes[i] = int(b)
|
intBytes[i] = int(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(map[string]interface{}{
|
result, _ := jsonv2.Marshal(map[string]interface{}{
|
||||||
"device": devPath,
|
"device": devPath,
|
||||||
"bytes": intBytes,
|
"bytes": intBytes,
|
||||||
"hex": hexBytes,
|
"hex": hexBytes,
|
||||||
"length": len(rxBuf),
|
"length": len(rxBuf),
|
||||||
}, "", " ")
|
}, jsontext.WithIndent(" "))
|
||||||
return SilentResult(string(result))
|
return SilentResult(string(result))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
|
||||||
fantasy "charm.land/fantasy"
|
fantasy "charm.land/fantasy"
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
|
@ -257,7 +258,7 @@ func parseToolCallArgs(input string) (map[string]interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var args map[string]interface{}
|
var args map[string]interface{}
|
||||||
if err := json.Unmarshal([]byte(input), &args); err != nil {
|
if err := jsonv2.Unmarshal([]byte(input), &args); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse tool arguments: %w", err)
|
return nil, fmt.Errorf("failed to parse tool arguments: %w", err)
|
||||||
}
|
}
|
||||||
return args, nil
|
return args, nil
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue