kunchenguid--no-mistakes
26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
572 行
17 KiB
Go
572 行
17 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseRovodevSSE_TextEvent(t *testing.T) {
|
|
input := `event: text
|
|
data: {"content":"hello world"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
var chunks []string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), func(text string) {
|
|
chunks = append(chunks, text)
|
|
}, &usage, &latestText)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != "hello world" {
|
|
t.Errorf("expected latest text 'hello world', got %q", latestText)
|
|
}
|
|
if len(chunks) != 1 || chunks[0] != "hello world" {
|
|
t.Errorf("expected 1 chunk 'hello world', got %v", chunks)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_UsageEvent(t *testing.T) {
|
|
// Real rovodev emits usage fields at the top level of the data payload,
|
|
// matching the fixture format used by the gnhf integration.
|
|
input := `event: request-usage
|
|
data: {"input_tokens":100,"output_tokens":50,"cache_read_tokens":30,"cache_write_tokens":10}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if usage.InputTokens != 100 {
|
|
t.Errorf("expected input tokens 100, got %d", usage.InputTokens)
|
|
}
|
|
if usage.OutputTokens != 50 {
|
|
t.Errorf("expected output tokens 50, got %d", usage.OutputTokens)
|
|
}
|
|
if usage.CacheReadTokens != 30 {
|
|
t.Errorf("expected cache read tokens 30, got %d", usage.CacheReadTokens)
|
|
}
|
|
if usage.CacheCreationTokens != 10 {
|
|
t.Errorf("expected cache creation tokens 10, got %d", usage.CacheCreationTokens)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_SeparatesAfterToolReturn(t *testing.T) {
|
|
input := `event: text
|
|
data: {"content":"before tool"}
|
|
|
|
event: tool-return
|
|
data: {"content":"ignored"}
|
|
|
|
event: text
|
|
data: {"content":"after tool"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
var chunks []string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), func(text string) {
|
|
chunks = append(chunks, text)
|
|
}, &usage, &latestText)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(chunks) != 3 {
|
|
t.Fatalf("expected 3 chunks (text, separator, text), got %d: %v", len(chunks), chunks)
|
|
}
|
|
if chunks[0] != "before tool" {
|
|
t.Errorf("expected 'before tool', got %q", chunks[0])
|
|
}
|
|
if chunks[1] != "\n\n" {
|
|
t.Errorf("expected separator '\\n\\n', got %q", chunks[1])
|
|
}
|
|
if chunks[2] != "after tool" {
|
|
t.Errorf("expected 'after tool', got %q", chunks[2])
|
|
}
|
|
if latestText != "after tool" {
|
|
t.Errorf("expected latest text 'after tool' after tool-return reset, got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_EventKindFallback(t *testing.T) {
|
|
// When event: field is missing, fall back to event_kind in JSON
|
|
input := `data: {"event_kind":"text","content":"fallback text"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != "fallback text" {
|
|
t.Errorf("expected latest text 'fallback text', got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_MultipleUsageAccumulates(t *testing.T) {
|
|
input := `event: request-usage
|
|
data: {"input_tokens":50,"output_tokens":20}
|
|
|
|
event: request-usage
|
|
data: {"input_tokens":100,"output_tokens":30}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if usage.InputTokens != 150 {
|
|
t.Errorf("expected accumulated input tokens 150, got %d", usage.InputTokens)
|
|
}
|
|
if usage.OutputTokens != 50 {
|
|
t.Errorf("expected accumulated output tokens 50, got %d", usage.OutputTokens)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_PartStart(t *testing.T) {
|
|
input := `event: part_start
|
|
data: {"index":0,"part":{"content":"hello","part_kind":"text"},"event_kind":"part_start"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
var chunks []string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), func(text string) {
|
|
chunks = append(chunks, text)
|
|
}, &usage, &latestText)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != "hello" {
|
|
t.Errorf("expected latest text 'hello', got %q", latestText)
|
|
}
|
|
if len(chunks) != 1 || chunks[0] != "hello" {
|
|
t.Errorf("expected 1 chunk 'hello', got %v", chunks)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_PartStartThenDelta(t *testing.T) {
|
|
// Matches the shape used by gnhf's "starts the server..." fixture: the
|
|
// final JSON answer arrives as a part_start (initial chunk) followed by
|
|
// one or more part_deltas carrying incremental content_delta strings.
|
|
input := `event: part_start
|
|
data: {"index":0,"part":{"content":"{\"success\":true","part_kind":"text"},"event_kind":"part_start"}
|
|
|
|
event: part_delta
|
|
data: {"index":0,"delta":{"content_delta":",\"summary\":\"done\"}","part_delta_kind":"text"},"event_kind":"part_delta"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
var chunks []string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), func(text string) {
|
|
chunks = append(chunks, text)
|
|
}, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
wantText := `{"success":true,"summary":"done"}`
|
|
if latestText != wantText {
|
|
t.Errorf("expected latest text %q, got %q", wantText, latestText)
|
|
}
|
|
if len(chunks) != 2 {
|
|
t.Fatalf("expected 2 chunks (start, delta), got %d: %v", len(chunks), chunks)
|
|
}
|
|
if chunks[0] != `{"success":true` {
|
|
t.Errorf("first chunk wrong: %q", chunks[0])
|
|
}
|
|
if chunks[1] != `,"summary":"done"}` {
|
|
t.Errorf("second chunk wrong: %q", chunks[1])
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_PartStartResetsAfterToolActivity(t *testing.T) {
|
|
// Mirrors gnhf's "treats text separated by tool activity as distinct
|
|
// message segments" fixture: prose text, then tool activity, then the
|
|
// real JSON answer. The text between tool events must be discarded.
|
|
input := `event: part_start
|
|
data: {"index":0,"part":{"content":"I will inspect the file.","part_kind":"text"},"event_kind":"part_start"}
|
|
|
|
event: on_call_tools_start
|
|
data: {"parts":[{"tool_name":"open_files","part_kind":"tool-call"}]}
|
|
|
|
event: tool-return
|
|
data: {"tool_name":"open_files","content":"ok","part_kind":"tool-return"}
|
|
|
|
event: part_start
|
|
data: {"index":0,"part":{"content":"{\"success\":true}","part_kind":"text"},"event_kind":"part_start"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != `{"success":true}` {
|
|
t.Errorf("expected final JSON text only, got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_PartDeltaWithoutStart(t *testing.T) {
|
|
// Defensive: a part_delta that arrives before any part_start still
|
|
// accumulates into the buffer instead of being dropped.
|
|
input := `event: part_delta
|
|
data: {"index":0,"delta":{"content_delta":"orphan","part_delta_kind":"text"},"event_kind":"part_delta"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != "orphan" {
|
|
t.Errorf("expected 'orphan', got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_PreservesWhitespaceInAssembledText(t *testing.T) {
|
|
input := `event: part_start
|
|
data: {"index":0,"part":{"content":" hello","part_kind":"text"},"event_kind":"part_start"}
|
|
|
|
event: part_delta
|
|
data: {"index":0,"delta":{"content_delta":" world\n","part_delta_kind":"text"},"event_kind":"part_delta"}
|
|
|
|
`
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != " hello world\n" {
|
|
t.Errorf("expected whitespace-preserved latest text, got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_GnhfFixture(t *testing.T) {
|
|
// Full stream replay from gnhf's primary rovodev integration test, which
|
|
// exercises part_start + part_delta assembly and top-level request-usage.
|
|
input := strings.Join([]string{
|
|
"event: user-prompt",
|
|
`data: {"content":"test","part_kind":"user-prompt"}`,
|
|
"",
|
|
"event: part_start",
|
|
`data: {"index":0,"part":{"content":"{\"success\":true","part_kind":"text"},"event_kind":"part_start"}`,
|
|
"",
|
|
"event: part_delta",
|
|
`data: {"index":0,"delta":{"content_delta":",\"summary\":\"done\",\"key_changes_made\":[\"a\"],\"key_learnings\":[\"b\"]}","part_delta_kind":"text"},"event_kind":"part_delta"}`,
|
|
"",
|
|
"event: request-usage",
|
|
`data: {"input_tokens":10,"cache_write_tokens":2,"cache_read_tokens":3,"output_tokens":4}`,
|
|
"",
|
|
"event: close",
|
|
"data: ",
|
|
"",
|
|
}, "\n")
|
|
|
|
var usage TokenUsage
|
|
var latestText string
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
wantText := `{"success":true,"summary":"done","key_changes_made":["a"],"key_learnings":["b"]}`
|
|
if latestText != wantText {
|
|
t.Errorf("latestText mismatch:\n want %q\n got %q", wantText, latestText)
|
|
}
|
|
if usage.InputTokens != 10 {
|
|
t.Errorf("input tokens: want 10, got %d", usage.InputTokens)
|
|
}
|
|
if usage.OutputTokens != 4 {
|
|
t.Errorf("output tokens: want 4, got %d", usage.OutputTokens)
|
|
}
|
|
if usage.CacheReadTokens != 3 {
|
|
t.Errorf("cache read tokens: want 3, got %d", usage.CacheReadTokens)
|
|
}
|
|
if usage.CacheCreationTokens != 2 {
|
|
t.Errorf("cache creation tokens: want 2, got %d", usage.CacheCreationTokens)
|
|
}
|
|
}
|
|
|
|
func TestParseRovodevSSE_EmptyData(t *testing.T) {
|
|
input := "event: text\ndata: \n\n"
|
|
var usage TokenUsage
|
|
var latestText string
|
|
|
|
err := parseRovodevSSE(strings.NewReader(input), nil, &usage, &latestText)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if latestText != "" {
|
|
t.Errorf("expected empty latest text, got %q", latestText)
|
|
}
|
|
}
|
|
|
|
func TestDoJSON_Success(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("expected POST, got %s", r.Method)
|
|
}
|
|
if r.Header.Get("Content-Type") != "application/json" {
|
|
t.Error("expected Content-Type application/json")
|
|
}
|
|
if r.Header.Get("x-custom") != "value" {
|
|
t.Error("expected x-custom header")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, `{"result":"ok"}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
headers := map[string]string{"x-custom": "value"}
|
|
body := map[string]string{"key": "val"}
|
|
resp, err := doJSON(context.Background(), http.MethodPost, server.URL+"/test", headers, body)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(resp) != `{"result":"ok"}` {
|
|
t.Errorf("unexpected response: %s", string(resp))
|
|
}
|
|
}
|
|
|
|
func TestDoJSON_ErrorStatus(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
fmt.Fprint(w, "bad request")
|
|
}))
|
|
defer server.Close()
|
|
|
|
_, err := doJSON(context.Background(), http.MethodGet, server.URL+"/test", nil, nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for 400 status")
|
|
}
|
|
if !strings.Contains(err.Error(), "400") {
|
|
t.Errorf("error should contain status code: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoJSON_NilBody(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Content-Type") == "application/json" {
|
|
t.Error("should not set Content-Type for nil body")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, `{}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
resp, err := doJSON(context.Background(), http.MethodGet, server.URL+"/test", nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(resp) != `{}` {
|
|
t.Errorf("unexpected response: %s", string(resp))
|
|
}
|
|
}
|
|
|
|
func TestGetAvailablePort(t *testing.T) {
|
|
port, err := getAvailablePort()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if port <= 0 || port > 65535 {
|
|
t.Errorf("expected valid port, got %d", port)
|
|
}
|
|
|
|
// Should return different ports on successive calls
|
|
port2, err := getAvailablePort()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// Ports may be the same in rare cases, but should be valid
|
|
if port2 <= 0 || port2 > 65535 {
|
|
t.Errorf("expected valid port, got %d", port2)
|
|
}
|
|
}
|
|
|
|
func TestRovodevAgent_CloseWithoutServer(t *testing.T) {
|
|
a := &rovodevAgent{bin: "acli"}
|
|
if err := a.Close(); err != nil {
|
|
t.Errorf("Close without server should not error: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestRovodevAgent_FullFlow tests the full session lifecycle using a mock HTTP server.
|
|
func TestRovodevAgent_FullFlow(t *testing.T) {
|
|
step := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.URL.Path == "/v3/sessions/create" && r.Method == http.MethodPost:
|
|
step++
|
|
fmt.Fprint(w, `{"session_id":"test-session-123"}`)
|
|
|
|
case r.URL.Path == "/v3/inline-system-prompt" && r.Method == http.MethodPut:
|
|
step++
|
|
if r.Header.Get("x-session-id") != "test-session-123" {
|
|
t.Error("expected x-session-id header")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
case r.URL.Path == "/v3/set_chat_message" && r.Method == http.MethodPost:
|
|
step++
|
|
if r.Header.Get("x-session-id") != "test-session-123" {
|
|
t.Error("expected x-session-id header")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
case r.URL.Path == "/v3/stream_chat" && r.Method == http.MethodGet:
|
|
step++
|
|
if r.Header.Get("x-session-id") != "test-session-123" {
|
|
t.Error("expected x-session-id header")
|
|
}
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
w.WriteHeader(http.StatusOK)
|
|
// Real rovodev emits top-level usage fields and streams text
|
|
// via part_start + part_delta events.
|
|
fmt.Fprint(w, "event: request-usage\ndata: {\"input_tokens\":100,\"output_tokens\":50}\n\n")
|
|
fmt.Fprint(w, "event: part_start\ndata: {\"index\":0,\"part\":{\"content\":\"{\\\"success\\\":true\",\"part_kind\":\"text\"},\"event_kind\":\"part_start\"}\n\n")
|
|
fmt.Fprint(w, "event: part_delta\ndata: {\"index\":0,\"delta\":{\"content_delta\":\",\\\"summary\\\":\\\"all good\\\"}\",\"part_delta_kind\":\"text\"},\"event_kind\":\"part_delta\"}\n\n")
|
|
|
|
case r.URL.Path == "/v3/sessions/test-session-123" && r.Method == http.MethodDelete:
|
|
step++
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
default:
|
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Create agent with a mock server already running
|
|
a := &rovodevAgent{
|
|
bin: "acli",
|
|
server: &managedServer{port: 0}, // will be overridden
|
|
}
|
|
// Parse the test server's port from URL
|
|
a.server = &managedServer{
|
|
port: mustParsePort(server.URL),
|
|
}
|
|
|
|
var chunks []string
|
|
result, err := a.Run(context.Background(), RunOpts{
|
|
Prompt: "review this code",
|
|
CWD: t.TempDir(),
|
|
JSONSchema: json.RawMessage(`{"type":"object"}`),
|
|
OnChunk: func(text string) { chunks = append(chunks, text) },
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result == nil {
|
|
t.Fatal("expected result")
|
|
}
|
|
if result.Usage.InputTokens != 100 {
|
|
t.Errorf("expected input tokens 100, got %d", result.Usage.InputTokens)
|
|
}
|
|
if result.Usage.OutputTokens != 50 {
|
|
t.Errorf("expected output tokens 50, got %d", result.Usage.OutputTokens)
|
|
}
|
|
// Streamed as part_start + part_delta, so onChunk fires twice.
|
|
if len(chunks) != 2 {
|
|
t.Errorf("expected 2 streamed chunks (part_start + part_delta), got %d: %v", len(chunks), chunks)
|
|
}
|
|
|
|
// Verify structured output parsed
|
|
var output map[string]any
|
|
if err := json.Unmarshal(result.Output, &output); err != nil {
|
|
t.Fatalf("failed to parse output: %v", err)
|
|
}
|
|
if output["success"] != true {
|
|
t.Errorf("expected success=true, got %v", output["success"])
|
|
}
|
|
|
|
// Verify steps: create session, set prompt, set message, stream, delete
|
|
if step < 4 {
|
|
t.Errorf("expected at least 4 API calls, got %d", step)
|
|
}
|
|
}
|
|
|
|
// TestRovodevAgent_NoSchema tests that system prompt is skipped when no schema.
|
|
func TestRovodevAgent_NoSchema(t *testing.T) {
|
|
calledPaths := make(map[string]bool)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calledPaths[r.URL.Path] = true
|
|
switch {
|
|
case r.URL.Path == "/v3/sessions/create":
|
|
fmt.Fprint(w, `{"session_id":"s1"}`)
|
|
case r.URL.Path == "/v3/set_chat_message":
|
|
w.WriteHeader(http.StatusOK)
|
|
case r.URL.Path == "/v3/stream_chat":
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
fmt.Fprint(w, "event: part_start\ndata: {\"index\":0,\"part\":{\"content\":\"done\",\"part_kind\":\"text\"},\"event_kind\":\"part_start\"}\n\n")
|
|
case r.URL.Path == "/v3/sessions/s1":
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
a := &rovodevAgent{
|
|
bin: "acli",
|
|
server: &managedServer{port: mustParsePort(server.URL)},
|
|
}
|
|
|
|
result, err := a.Run(context.Background(), RunOpts{
|
|
Prompt: "hello",
|
|
CWD: t.TempDir(),
|
|
// No JSONSchema
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if result.Text != "done" {
|
|
t.Fatalf("expected plain text result, got %q", result.Text)
|
|
}
|
|
if result.Output != nil {
|
|
t.Fatalf("expected nil structured output, got %s", string(result.Output))
|
|
}
|
|
|
|
// System prompt endpoint should NOT have been called
|
|
if calledPaths["/v3/inline-system-prompt"] {
|
|
t.Error("should not call inline-system-prompt when no schema")
|
|
}
|
|
}
|
|
|
|
func mustParsePort(url string) int {
|
|
// url format: http://127.0.0.1:PORT
|
|
var port int
|
|
fmt.Sscanf(url, "http://127.0.0.1:%d", &port)
|
|
return port
|
|
}
|