v2 is a new Go module (v2/) with a dramatically simpler API: - Unified Message type (no more Input marker interface) - Define[T] for ergonomic tool creation with standard context.Context - Chat session with automatic tool-call loop (agent loop) - Streaming via pull-based StreamReader - MCP one-call connect (MCPStdioServer, MCPHTTPServer, MCPSSEServer) - Middleware support (logging, retry, timeout, usage tracking) - Decoupled JSON Schema (map[string]any, no provider coupling) - Sample tools: WebSearch, Browser, Exec, ReadFile, WriteFile, HTTP - Providers: OpenAI, Anthropic, Google (all with streaming) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
182 lines
4.4 KiB
Go
182 lines
4.4 KiB
Go
package schema
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
type SimpleParams struct {
|
|
Name string `json:"name" description:"The name"`
|
|
Age int `json:"age" description:"The age"`
|
|
}
|
|
|
|
type OptionalParams struct {
|
|
Required string `json:"required" description:"A required field"`
|
|
Optional *string `json:"optional,omitempty" description:"An optional field"`
|
|
}
|
|
|
|
type EnumParams struct {
|
|
Color string `json:"color" description:"The color" enum:"red,green,blue"`
|
|
}
|
|
|
|
type NestedParams struct {
|
|
Inner SimpleParams `json:"inner" description:"Nested object"`
|
|
}
|
|
|
|
type ArrayParams struct {
|
|
Items []string `json:"items" description:"A list of items"`
|
|
}
|
|
|
|
type EmbeddedBase struct {
|
|
ID string `json:"id" description:"The ID"`
|
|
}
|
|
|
|
type EmbeddedParams struct {
|
|
EmbeddedBase
|
|
Name string `json:"name" description:"The name"`
|
|
}
|
|
|
|
func TestFromStruct_Simple(t *testing.T) {
|
|
s := FromStruct(SimpleParams{})
|
|
|
|
if s["type"] != "object" {
|
|
t.Errorf("expected type=object, got %v", s["type"])
|
|
}
|
|
|
|
props, ok := s["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected properties to be map[string]any")
|
|
}
|
|
|
|
if len(props) != 2 {
|
|
t.Errorf("expected 2 properties, got %d", len(props))
|
|
}
|
|
|
|
nameSchema, ok := props["name"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected name property to be map[string]any")
|
|
}
|
|
if nameSchema["type"] != "string" {
|
|
t.Errorf("expected name type=string, got %v", nameSchema["type"])
|
|
}
|
|
if nameSchema["description"] != "The name" {
|
|
t.Errorf("expected name description='The name', got %v", nameSchema["description"])
|
|
}
|
|
|
|
ageSchema, ok := props["age"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("expected age property to be map[string]any")
|
|
}
|
|
if ageSchema["type"] != "integer" {
|
|
t.Errorf("expected age type=integer, got %v", ageSchema["type"])
|
|
}
|
|
|
|
required, ok := s["required"].([]string)
|
|
if !ok {
|
|
t.Fatal("expected required to be []string")
|
|
}
|
|
if len(required) != 2 {
|
|
t.Errorf("expected 2 required fields, got %d", len(required))
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_Optional(t *testing.T) {
|
|
s := FromStruct(OptionalParams{})
|
|
|
|
required, ok := s["required"].([]string)
|
|
if !ok {
|
|
t.Fatal("expected required to be []string")
|
|
}
|
|
|
|
// Only "required" field should be required, not "optional"
|
|
if len(required) != 1 {
|
|
t.Errorf("expected 1 required field, got %d: %v", len(required), required)
|
|
}
|
|
if required[0] != "required" {
|
|
t.Errorf("expected required field 'required', got %v", required[0])
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_Enum(t *testing.T) {
|
|
s := FromStruct(EnumParams{})
|
|
|
|
props := s["properties"].(map[string]any)
|
|
colorSchema := props["color"].(map[string]any)
|
|
|
|
if colorSchema["type"] != "string" {
|
|
t.Errorf("expected enum type=string, got %v", colorSchema["type"])
|
|
}
|
|
|
|
enums, ok := colorSchema["enum"].([]string)
|
|
if !ok {
|
|
t.Fatal("expected enum to be []string")
|
|
}
|
|
if len(enums) != 3 {
|
|
t.Errorf("expected 3 enum values, got %d", len(enums))
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_Nested(t *testing.T) {
|
|
s := FromStruct(NestedParams{})
|
|
|
|
props := s["properties"].(map[string]any)
|
|
innerSchema := props["inner"].(map[string]any)
|
|
|
|
if innerSchema["type"] != "object" {
|
|
t.Errorf("expected nested type=object, got %v", innerSchema["type"])
|
|
}
|
|
|
|
innerProps := innerSchema["properties"].(map[string]any)
|
|
if len(innerProps) != 2 {
|
|
t.Errorf("expected 2 inner properties, got %d", len(innerProps))
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_Array(t *testing.T) {
|
|
s := FromStruct(ArrayParams{})
|
|
|
|
props := s["properties"].(map[string]any)
|
|
itemsSchema := props["items"].(map[string]any)
|
|
|
|
if itemsSchema["type"] != "array" {
|
|
t.Errorf("expected array type=array, got %v", itemsSchema["type"])
|
|
}
|
|
|
|
items := itemsSchema["items"].(map[string]any)
|
|
if items["type"] != "string" {
|
|
t.Errorf("expected items type=string, got %v", items["type"])
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_Embedded(t *testing.T) {
|
|
s := FromStruct(EmbeddedParams{})
|
|
|
|
props := s["properties"].(map[string]any)
|
|
|
|
// Should have both ID from embedded and Name
|
|
if len(props) != 2 {
|
|
t.Errorf("expected 2 properties (flattened), got %d", len(props))
|
|
}
|
|
|
|
if _, ok := props["id"]; !ok {
|
|
t.Error("expected 'id' property from embedded struct")
|
|
}
|
|
if _, ok := props["name"]; !ok {
|
|
t.Error("expected 'name' property")
|
|
}
|
|
}
|
|
|
|
func TestFromStruct_ValidJSON(t *testing.T) {
|
|
s := FromStruct(SimpleParams{})
|
|
|
|
data, err := json.Marshal(s)
|
|
if err != nil {
|
|
t.Fatalf("schema should be valid JSON: %v", err)
|
|
}
|
|
|
|
var parsed map[string]any
|
|
if err := json.Unmarshal(data, &parsed); err != nil {
|
|
t.Fatalf("schema should round-trip through JSON: %v", err)
|
|
}
|
|
}
|