100 lines
5.1 KiB
Go
100 lines
5.1 KiB
Go
package protocol
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestManifestAgentMapsRequestAndResponse(t *testing.T) {
|
|
manifest := []byte(`{
|
|
"apiVersion":"yingce.plugin/v1",
|
|
"id":"agent-test","version":"1.0.0","name":"Agent Test","author":"Test","documentation":"# Agent Test",
|
|
"contributes":{"providers":[{"id":"agent-test","label":"Agent Test","capabilities":["text"],"scopes":["agent"],"create":{"method":"POST","path":"/create"},"agent":{"method":"POST","path":"/messages","fields":{"model":"request.model","messages":"request.extra.agent.chatCompletion.messages","tools":"request.extra.agent.chatCompletion.tools","tool_choice":"request.extra.agent.chatCompletion.tool_choice"}},"agentResponse":{"textPaths":["choices.0.message.content"],"toolCallsPath":"choices.0.message.tool_calls","toolCallIdPaths":["id"],"toolCallNamePaths":["function.name"],"toolCallArgumentsPaths":["function.arguments"]},"response":{}}]}
|
|
}`)
|
|
adapter, err := LoadManifest(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
agent, ok := adapter.(AgentAdapter)
|
|
if !ok {
|
|
t.Fatal("manifest adapter does not expose AgentAdapter")
|
|
}
|
|
request := map[string]any{
|
|
"chatCompletion": map[string]any{
|
|
"messages": []any{map[string]any{"role": "user", "content": "hello"}},
|
|
"tools": []any{map[string]any{"type": "function"}},
|
|
"tool_choice": "required",
|
|
},
|
|
"responses": map[string]any{},
|
|
}
|
|
spec, err := agent.BuildAgent(context.Background(), AgentRequestContext{Model: "model-a", Request: request})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body, ok := spec.Body.(map[string]any)
|
|
if !ok || body["model"] != "model-a" {
|
|
t.Fatalf("request body = %#v", spec.Body)
|
|
}
|
|
if _, ok := body["messages"].([]any); !ok {
|
|
t.Fatalf("messages were not mapped: %#v", body)
|
|
}
|
|
result, err := agent.ParseAgent(context.Background(), []byte(`{"choices":[{"message":{"content":"answer","tool_calls":[{"id":"call-1","function":{"name":"search","arguments":"{\"q\":\"x\"}"}}]}}]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Text != "answer" || len(result.ToolCalls) != 1 || result.ToolCalls[0].Name != "search" {
|
|
t.Fatalf("agent result = %#v", result)
|
|
}
|
|
if !json.Valid([]byte(result.ToolCalls[0].Arguments)) {
|
|
t.Fatalf("tool arguments are not JSON: %q", result.ToolCalls[0].Arguments)
|
|
}
|
|
|
|
result, err = agent.ParseAgent(context.Background(), []byte(`{"choices":[{"message":{"tool_calls":[{"id":"call-2","function":{"name":"search","arguments":{"q":"object"}}}]}}]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := result.ToolCalls[0].Arguments; got != `{"q":"object"}` {
|
|
t.Fatalf("object tool arguments = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestManifestMapsSynchronousTextResponse(t *testing.T) {
|
|
manifest := []byte(`{
|
|
"apiVersion":"yingce.plugin/v1",
|
|
"id":"text-test","version":"1.0.0","name":"Text Test","author":"Test","documentation":"# Text Test",
|
|
"contributes":{"providers":[{"id":"text-test","label":"Text Test","capabilities":["text"],"scopes":["canvas"],"create":{"method":"POST","path":"/chat","fields":{"prompt":"request.prompt"}},"response":{"textPaths":["choices.0.message.content"],"reasoningPaths":["choices.0.message.reasoning_content"]}}]}
|
|
}`)
|
|
adapter, err := LoadManifest(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
spec, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{Prompt: "hello"}})
|
|
if err != nil || spec.Body == nil {
|
|
t.Fatalf("build create = %#v, %v", spec, err)
|
|
}
|
|
result, err := adapter.ParseCreate(context.Background(), []byte(`{"choices":[{"message":{"content":"answer","reasoning_content":"thinking"}}]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != StatusSucceeded || result.Result == nil || result.Result.Text != "answer" || result.Result.Reasoning != "thinking" {
|
|
t.Fatalf("text result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestManifestUsesOnlyUnifiedTopLevelContract(t *testing.T) {
|
|
legacy := []byte(`{"apiVersion":"v1","metadata":{"id":"legacy","version":"1","name":"Legacy"},"create":{"method":"POST","path":"/tasks"},"response":{}}`)
|
|
if _, err := LoadManifest(legacy); err == nil {
|
|
t.Fatal("legacy protocol manifest was accepted")
|
|
}
|
|
canvasOnly := []byte(`{"apiVersion":"yingce.plugin/v1","id":"canvas-extension","name":"Canvas Extension","version":"1.0.0","contributes":{"canvasNodes":[{"id":"canvas-extension/node","label":"Node","defaultTitle":"Node","defaultSize":{"width":320,"height":200},"schema":{},"renderer":"declarative"}]}}`)
|
|
if _, err := LoadManifest(canvasOnly); err != nil {
|
|
t.Fatalf("providerless unified plugin was rejected: %v", err)
|
|
}
|
|
multi := []byte(`{"apiVersion":"yingce.plugin/v1","id":"multi-provider","name":"Multi Provider","version":"1.0.0","contributes":{"providers":[{"id":"multi-text","label":"Text","capabilities":["text"],"scopes":["canvas"],"create":{"method":"POST","path":"/text"},"response":{}},{"id":"multi-image","label":"Image","capabilities":["image"],"scopes":["canvas"],"create":{"method":"POST","path":"/image"},"response":{}}]}}`)
|
|
adapters, err := LoadInstalledProviders(multi, nil)
|
|
if err != nil || len(adapters) != 2 || adapters[0].Metadata().ID != "multi-text" || adapters[1].Metadata().ID != "multi-image" {
|
|
t.Fatalf("multi-provider load = %#v, %v", adapters, err)
|
|
}
|
|
}
|