package openai import ( "bufio" "context" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" iop "iop/proto/gen/iop" ) func TestHotPathChatDirectStreamCodec(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") providerStream := strings.Join([]string{ `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{"content":"alpha "},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{"reasoning_content":"think "},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{"content":"omega"},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"provider-chat-gate","type":"function","function":{"name":"read_file","arguments":"{\"path\":"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"README.md\"}"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-gate","object":"chat.completion.chunk","created":1777001001,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":9,"completion_tokens":7,"total_tokens":16}}`, `data: [DONE]`, "", }, "\n\n") srv, fake := newHotPathHandlerServer(t, candidate, staticProviderTunnelFrames(providerStream)) body := `{"model":"virtual-model","messages":[{"role":"user","content":"hello"}],"tools":[{"type":"function","function":{"name":"read_file","parameters":{"type":"object"}}}],"max_completion_tokens":64,"stream":true}` response := serveHotPathChatBody(t, srv, body) if response.Code != http.StatusOK { t.Fatalf("status=%d body=%s", response.Code, response.Body.String()) } chunks, done := decodeHotPathChatSSE(t, response.Body.String()) if done != 1 { t.Fatalf("DONE count=%d body=%s", done, response.Body.String()) } assertHotPathChatChunks(t, chunks, hotPathChatChunkExpectation{ ResponseID: "chatcmpl-chat-gate", Model: "virtual-model", Content: "alpha omega", Reasoning: "think ", Kinds: []string{"content", "reasoning", "content", "tool", "tool", "terminal"}, ToolID: "chatcmpl-chat-gate-tool-1", ToolName: "read_file", ToolArgs: `{"path":"README.md"}`, FinishReason: "tool_calls", PromptTokens: 9, CompletionTokens: 7, }) if fake.poolSubmitCountSnapshot() != 1 { t.Fatalf("selector submissions=%d, want 1", fake.poolSubmitCountSnapshot()) } assertHotPathWaiting(t, srv, "chatcmpl-chat-gate-tool-1", "provider-chat-gate") } func TestHotPathChatToolIndexesAreMonotonic(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") providerStream := strings.Join([]string{ `data: {"id":"chatcmpl-chat-tools","created":1777001004,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"provider-tool-0","type":"function","function":{"name":"read_file","arguments":"{\"path\":"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-tools","created":1777001004,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"a\"}"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-tools","created":1777001004,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"provider-tool-1","type":"function","function":{"name":"read_file","arguments":"{\"path\":"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-tools","created":1777001004,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"b\"}"}}]},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-tools","created":1777001004,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}`, `data: [DONE]`, "", }, "\n\n") srv, _ := newHotPathHandlerServer(t, candidate, staticProviderTunnelFrames(providerStream)) response := serveHotPathChatBody(t, srv, `{"model":"virtual-model","messages":[{"role":"user","content":"tools"}],"tools":[{"type":"function","function":{"name":"read_file","parameters":{"type":"object"}}}],"stream":true}`) chunks, done := decodeHotPathChatSSE(t, response.Body.String()) if response.Code != http.StatusOK || done != 1 { t.Fatalf("status=%d DONE=%d body=%s", response.Code, done, response.Body.String()) } var indexes []int var ids []string for _, chunk := range chunks { choice := chunk["choices"].([]any)[0].(map[string]any) delta := choice["delta"].(map[string]any) tools, ok := delta["tool_calls"].([]any) if !ok { continue } tool := tools[0].(map[string]any) indexes = append(indexes, int(tool["index"].(float64))) if id, _ := tool["id"].(string); id != "" { ids = append(ids, id) } } if fmt.Sprint(indexes) != "[0 0 1 1]" || fmt.Sprint(ids) != "[chatcmpl-chat-tools-tool-1 chatcmpl-chat-tools-tool-2]" { t.Fatalf("tool index/id sequence: indexes=%v ids=%v body=%s", indexes, ids, response.Body.String()) } } func TestHotPathChatCallerCapAndNonStream(t *testing.T) { for _, capField := range []string{"max_tokens", "max_completion_tokens"} { capField := capField t.Run(capField+" preserves provider terminal", func(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") providerStream := strings.Join([]string{ `data: {"id":"chatcmpl-chat-cap","created":1777001002,"choices":[{"index":0,"delta":{"content":"abcdefghij"},"finish_reason":null}]}`, `data: {"id":"chatcmpl-chat-cap","created":1777001002,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}`, `data: [DONE]`, "", }, "\n\n") srv, _ := newHotPathHandlerServer(t, candidate, staticProviderTunnelFrames(providerStream)) body := fmt.Sprintf(`{"model":"virtual-model","messages":[{"role":"user","content":"cap"}],%q:2,"stream":true}`, capField) response := serveHotPathChatBody(t, srv, body) chunks, done := decodeHotPathChatSSE(t, response.Body.String()) if response.Code != http.StatusOK || done != 1 { t.Fatalf("status=%d DONE=%d body=%s", response.Code, done, response.Body.String()) } assertHotPathChatChunks(t, chunks, hotPathChatChunkExpectation{ ResponseID: "chatcmpl-chat-cap", Model: "virtual-model", Content: "abcdefghij", Kinds: []string{"content", "terminal"}, FinishReason: "stop", }) assertHotPathTerminal(t, srv) }) } t.Run("non-stream compatibility", func(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") providerBody := `{"id":"chatcmpl-chat-json","object":"chat.completion","created":1777001003,"model":"served-selector","choices":[{"index":0,"message":{"role":"assistant","content":"json final","reasoning_content":"json thought"},"finish_reason":"stop"}],"usage":{"prompt_tokens":4,"completion_tokens":3,"total_tokens":7}}` srv, fake := newHotPathHandlerServer(t, candidate, staticProviderTunnelFrames(providerBody)) response := serveHotPathChatBody(t, srv, `{"model":"virtual-model","messages":[{"role":"user","content":"json"}],"stream":false}`) if response.Code != http.StatusOK { t.Fatalf("status=%d body=%s", response.Code, response.Body.String()) } var decoded struct { ID string `json:"id"` Model string `json:"model"` Choices []struct { Message chatMessage `json:"message"` FinishReason string `json:"finish_reason"` } `json:"choices"` Usage openAIUsage `json:"usage"` } if err := json.Unmarshal(response.Body.Bytes(), &decoded); err != nil { t.Fatal(err) } if decoded.ID != "chatcmpl-chat-json" || decoded.Model != "virtual-model" || len(decoded.Choices) != 1 || decoded.Choices[0].Message.Content != "json final" || decoded.Choices[0].Message.ReasoningContent != "json thought" || decoded.Choices[0].FinishReason != "stop" || decoded.Usage.PromptTokens != 4 || decoded.Usage.CompletionTokens != 3 { t.Fatalf("non-stream response mismatch: %+v", decoded) } if fake.poolSubmitCountSnapshot() != 1 { t.Fatalf("selector submissions=%d, want 1", fake.poolSubmitCountSnapshot()) } assertHotPathTerminal(t, srv) }) } func TestHotPathChatMixedProviderStages(t *testing.T) { decodedReview, err := decodeAnthropicPresetSSE([]byte(hotPathChatMixedReviewSSE("req-decode-check"))) if err != nil || len(decodedReview.ToolCalls) != 1 || len(decodedReview.Deltas) != 3 { t.Fatalf("mixed review fixture decode: tools=%d deltas=%d err=%v output=%+v", len(decodedReview.ToolCalls), len(decodedReview.Deltas), err, decodedReview) } openAICandidate := anthropicTestCandidate(t, "openai") anthropicCandidate := anthropicTestCandidate(t, "anthropic") service := &hotPathChatGateScriptedService{} service.steps = []hotPathChatGateStep{ {candidate: openAICandidate, body: func(requestID string) string { return scriptedArtifactPrepare("openai", requestID) }}, {candidate: openAICandidate, body: func(requestID string) string { return scriptedArtifactPair("openai", requestID) }}, {candidate: openAICandidate, body: func(requestID string) string { return scriptedArtifactLocalRead("openai", requestID) }}, {candidate: openAICandidate, contentType: "text/event-stream", body: func(string) string { return hotPathChatMixedLocalSSE() }}, {candidate: anthropicCandidate, contentType: "text/event-stream", body: hotPathChatMixedReviewSSE}, } preset := hotPathSelectorPreset([]string{config.ModeDirect, config.ModeLight}) preset.WorkspaceTools = []config.ExecutionWorkspaceToolAlternative{scriptedLightWorkspaceAlternative()} srv := NewServer(config.EdgeOpenAIConf{}, service, nil) srv.SetEdgeID("edge-chat-gate-mixed") srv.SetExecutionPresets([]config.ExecutionPreset{preset}) providers := map[string]string{ openAICandidate.ProviderID: "served-openai", anthropicCandidate.ProviderID: "served-anthropic", } srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "virtual-model", ExecutionPreset: preset.ID}, {ID: "selector-model", Providers: providers}, {ID: "local-model", Providers: providers}, {ID: "review-model", Providers: providers}, }) tools := scriptedLightTools("openai") history := []any{map[string]any{"role": "user", "content": "mixed provider task"}} consume := func(response *httptest.ResponseRecorder, results []string) { t.Helper() assistant, ids, err := artifactAssistantFromResponse("openai", response.Body.Bytes()) if err != nil || len(ids) != len(results) { t.Fatalf("consume tool response: ids=%v err=%v body=%s", ids, err, response.Body.String()) } history = append(history, assistant) history = scriptedArtifactAppendResults("openai", history, ids, results) } request := func(stream bool) *httptest.ResponseRecorder { t.Helper() body := scriptedArtifactRequestBodyWithOptions(t, "openai", tools, history, 64, stream) return serveScriptedArtifactRequest(t, srv, "openai", body) } consume(request(false), []string{`{"written":true}`}) consume(request(false), []string{`{"written":true}`, `{"written":true}`}) consume(request(false), []string{`{"written":true}`}) response := request(true) if response.Code != http.StatusOK { t.Fatalf("status=%d body=%s", response.Code, response.Body.String()) } chunks, done := decodeHotPathChatSSE(t, response.Body.String()) if done != 1 { t.Fatalf("DONE count=%d body=%s", done, response.Body.String()) } requestID, snapshot := soleHotPathSnapshot(t, srv) assertHotPathChatChunks(t, chunks, hotPathChatChunkExpectation{ ResponseID: "chatcmpl-mixed-local", Model: "virtual-model", Content: "local-A local-Breview-visible", Reasoning: "local-think review-think ", Kinds: []string{"content", "reasoning", "content", "reasoning", "content", "tool", "terminal"}, ToolName: "write_file", ToolArgs: hotPathChatReviewArguments(requestID), FinishReason: "tool_calls", PromptTokens: 12, CompletionTokens: 7, }) for _, chunk := range chunks { if chunk["id"] == requestID { t.Fatalf("logical request identity became the public response id: %+v", chunk) } } for _, internalID := range []string{snapshot.ActiveStageID, "run-chat-gate-4", "run-chat-gate-5", "msg-mixed-review"} { if strings.Contains(response.Body.String(), internalID) { t.Fatalf("internal or later-stage identity %q leaked: %s", internalID, response.Body.String()) } } if got := service.requestCount(); got != 5 { t.Fatalf("provider submissions=%d, want 5", got) } } func TestHotPathChatProviderErrorBeforeCommit(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") frames := make(chan *iop.ProviderTunnelFrame, 2) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: http.StatusBadGateway, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, fake := newHotPathHandlerServer(t, candidate, frames) response := serveHotPathChatBody(t, srv, `{"model":"virtual-model","messages":[{"role":"user","content":"fail"}],"stream":true}`) if response.Code != http.StatusBadGateway || !strings.Contains(response.Body.String(), `"type":"run_error"`) || strings.Contains(response.Body.String(), "[DONE]") { t.Fatalf("pre-commit error mismatch: status=%d body=%s", response.Code, response.Body.String()) } if fake.poolSubmitCountSnapshot() != 1 { t.Fatalf("selector submissions=%d, want 1", fake.poolSubmitCountSnapshot()) } assertHotPathTerminal(t, srv) } func TestHotPathChatFlushesVisibleDeltaBeforeProviderTerminal(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") frames := make(chan *iop.ProviderTunnelFrame, 4) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: http.StatusOK, Headers: map[string]string{"Content-Type": "text/event-stream"}, RunId: "run-chat-gate-4", } frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: {\"id\":\"chatcmpl-live-local\",\"created\":1777001201,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"early-visible\"},\"finish_reason\":null}]}\n\n"), RunId: "run-chat-gate-4", } service := &hotPathChatGateScriptedService{} service.steps = []hotPathChatGateStep{ {candidate: candidate, body: func(requestID string) string { return scriptedArtifactPrepare("openai", requestID) }}, {candidate: candidate, body: func(requestID string) string { return scriptedArtifactPair("openai", requestID) }}, {candidate: candidate, body: func(requestID string) string { return scriptedArtifactLocalRead("openai", requestID) }}, {candidate: candidate, contentType: "text/event-stream", frames: frames}, } preset := hotPathSelectorPreset([]string{config.ModeDirect, config.ModeLight}) preset.WorkspaceTools = []config.ExecutionWorkspaceToolAlternative{scriptedLightWorkspaceAlternative()} srv := NewServer(config.EdgeOpenAIConf{}, service, nil) srv.SetEdgeID("edge-chat-gate-live") srv.SetExecutionPresets([]config.ExecutionPreset{preset}) providers := map[string]string{candidate.ProviderID: "served-openai"} srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "virtual-model", ExecutionPreset: preset.ID}, {ID: "selector-model", Providers: providers}, {ID: "local-model", Providers: providers}, {ID: "review-model", Providers: providers}, }) tools := scriptedLightTools("openai") history := []any{map[string]any{"role": "user", "content": "flush before terminal"}} consume := func(response *httptest.ResponseRecorder, results []string) { t.Helper() assistant, ids, err := artifactAssistantFromResponse("openai", response.Body.Bytes()) if err != nil || len(ids) != len(results) { t.Fatalf("consume setup response: ids=%v err=%v body=%s", ids, err, response.Body.String()) } history = append(history, assistant) history = scriptedArtifactAppendResults("openai", history, ids, results) } requestSetup := func() *httptest.ResponseRecorder { body := scriptedArtifactRequestBodyWithOptions(t, "openai", tools, history, 64, false) return serveScriptedArtifactRequest(t, srv, "openai", body) } consume(requestSetup(), []string{`{"written":true}`}) consume(requestSetup(), []string{`{"written":true}`, `{"written":true}`}) consume(requestSetup(), []string{`{"written":true}`}) httpServer := httptest.NewServer(srv.routes()) defer httpServer.Close() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() body := scriptedArtifactRequestBodyWithOptions(t, "openai", tools, history, 64, true) request, err := http.NewRequestWithContext(ctx, http.MethodPost, httpServer.URL+"/v1/chat/completions", strings.NewReader(string(body))) if err != nil { t.Fatal(err) } response, err := http.DefaultClient.Do(request) if err != nil { t.Fatalf("stream request did not flush before terminal: %v", err) } defer response.Body.Close() reader := bufio.NewReader(response.Body) roleFrame, err := readHotPathSSEFrame(reader) if err != nil { t.Fatalf("read role before terminal: %v", err) } contentFrame, err := readHotPathSSEFrame(reader) if err != nil { t.Fatalf("read content before terminal: %v", err) } early := roleFrame + contentFrame if response.StatusCode != http.StatusOK || !strings.Contains(early, `"role":"assistant"`) || !strings.Contains(early, `"content":"early-visible"`) || !strings.Contains(early, `"id":"chatcmpl-live-local"`) || strings.Contains(early, "[DONE]") || strings.Contains(early, `"finish_reason":"`) { t.Fatalf("pre-terminal flush mismatch: status=%d body=%s", response.StatusCode, early) } requestID, snapshot := soleHotPathSnapshot(t, srv) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: {\"id\":\"chatcmpl-live-local\",\"created\":1777001201,\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"provider-live-tool\",\"type\":\"function\",\"function\":{\"name\":\"run_command\",\"arguments\":\"{\\\"command\\\":\\\"status\\\"}\"}}]},\"finish_reason\":null}]}\n\n"), RunId: "run-chat-gate-4", } toolFrame, err := readHotPathSSEFrame(reader) if err != nil { t.Fatalf("read tool fragment before terminal: %v", err) } if !strings.Contains(toolFrame, `"tool_calls"`) || !strings.Contains(toolFrame, `"name":"run_command"`) || strings.Contains(toolFrame, "[DONE]") || strings.Contains(toolFrame, `"finish_reason":"`) { t.Fatalf("pre-terminal tool flush mismatch: %s", toolFrame) } frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: {\"id\":\"chatcmpl-live-local\",\"created\":1777001201,\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":1,\"total_tokens\":4}}\n\ndata: [DONE]\n\n"), RunId: "run-chat-gate-4", } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true, RunId: "run-chat-gate-4"} close(frames) rest, err := io.ReadAll(reader) if err != nil { t.Fatalf("read terminal stream: %v", err) } wire := early + toolFrame + string(rest) chunks, done := decodeHotPathChatSSE(t, wire) if done != 1 { t.Fatalf("DONE count=%d body=%s", done, wire) } assertHotPathChatChunks(t, chunks, hotPathChatChunkExpectation{ ResponseID: "chatcmpl-live-local", Model: "virtual-model", Content: "early-visible", Kinds: []string{"content", "tool", "terminal"}, ToolName: "run_command", ToolArgs: `{"command":"status"}`, FinishReason: "tool_calls", PromptTokens: 3, CompletionTokens: 1, }) for _, internalID := range []string{requestID, snapshot.ActiveStageID, "run-chat-gate-4", "provider-live-tool"} { if strings.Contains(wire, internalID) { t.Fatalf("internal identity %q leaked: %s", internalID, wire) } } } func TestHotPathNormalizedStageSourceRequiresIdentityOnEveryVisibleAndCompleteEvent(t *testing.T) { for _, eventType := range []string{"delta", "reasoning_delta", "complete"} { eventType := eventType t.Run(eventType, func(t *testing.T) { source := &hotPathNormalizedStageSource{} if err := source.observeRunEvent(&iop.RunEvent{ Type: "delta", Delta: "first", Metadata: map[string]string{hotPathOpenAIResponseIDMetadata: "chatcmpl-event-scoped"}, }); err != nil { t.Fatalf("observe valid first event: %v", err) } if err := source.observeRunEvent(&iop.RunEvent{Type: eventType, Delta: "missing"}); err == nil { t.Fatalf("%s without event-scoped identity was accepted", eventType) } }) } } func TestHotPathLiveStageTerminalReason(t *testing.T) { tests := []struct { name, protocol, want string frames chan *iop.ProviderTunnelFrame }{ { name: "OpenAI length", protocol: "openai", want: "length", frames: staticProviderTunnelFrames(strings.Join([]string{ `data: {"id":"chatcmpl-length-probe","choices":[{"delta":{"content":"limited"},"finish_reason":null}]}`, `data: {"id":"chatcmpl-length-probe","choices":[{"delta":{},"finish_reason":"length"}]}`, `data: [DONE]`, "", }, "\n\n")), }, { name: "Anthropic max tokens", protocol: "anthropic", want: "max_tokens", frames: anthropicTunnelFrames(http.StatusOK, "text/event-stream", []byte(strings.ReplaceAll(strings.Join([]string{ `event: message_start\ndata: {"type":"message_start","message":{"id":"msg-length-probe","usage":{"input_tokens":2}}}`, `event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":"limited"}}`, `event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"max_tokens","stop_sequence":null},"usage":{"output_tokens":3}}`, `event: message_stop\ndata: {"type":"message_stop"}`, "", }, "\n\n"), `\n`, "\n"))), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { source := newHotPathTunnelStageSource( edgeservice.ProviderTunnelStream{Frames: test.frames}, time.Second, newHotPathStageDecoderForProtocol(test.protocol), ) outer := newHotPathOuterTurn("") output, terminal, err := runHotPathStreamingStage( context.Background(), outer, hotPathStageMeta{StageID: "terminal-reason", Protocol: test.protocol, Model: "model", Provider: "provider", AttemptID: test.name}, source, source, &hotPathCountingController{}, ) if err != nil { t.Fatalf("run live stage: %v", err) } if !terminal.Success || terminal.Reason != test.want || output.TerminalReason != test.want || output.Content != "limited" { t.Fatalf("terminal reason projection: terminal=%+v output=%+v", terminal, output) } }) } t.Run("Normalized max tokens", func(t *testing.T) { const responseID = "chatcmpl-normalized-length-probe" source := newHotPathNormalizedStageSource(edgeservice.RunStream{Events: bufferedRunEvents( &iop.RunEvent{Type: "delta", Delta: "limited", Metadata: map[string]string{hotPathOpenAIResponseIDMetadata: responseID}}, &iop.RunEvent{Type: "complete", Metadata: map[string]string{ hotPathOpenAIResponseIDMetadata: responseID, "finish_reason": "max_tokens", }}, )}, time.Second) outer := newHotPathOuterTurn("") output, terminal, err := runHotPathStreamingStage( context.Background(), outer, hotPathStageMeta{StageID: "normalized-terminal-reason", Protocol: "openai", Model: "model", Provider: "provider", AttemptID: "normalized"}, source, source, &hotPathCountingController{}, ) if err != nil { t.Fatalf("run normalized live stage: %v", err) } if !terminal.Success || terminal.Reason != "max_tokens" || output.TerminalReason != "max_tokens" || output.Content != "limited" { t.Fatalf("normalized terminal reason projection: terminal=%+v output=%+v", terminal, output) } }) } func TestHotPathChatProviderLengthFlushesBeforeTerminalAndStopsLight(t *testing.T) { candidate := anthropicTestCandidate(t, "openai") frames := make(chan *iop.ProviderTunnelFrame, 4) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: http.StatusOK, Headers: map[string]string{"Content-Type": "text/event-stream"}, RunId: "run-chat-length-local", } frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, RunId: "run-chat-length-local", Body: []byte("data: {\"id\":\"chatcmpl-provider-length\",\"created\":1777001301,\"choices\":[{\"index\":0,\"delta\":{\"content\":\"provider-limited\"},\"finish_reason\":null}]}\n\n"), } service := &hotPathChatGateScriptedService{} service.steps = []hotPathChatGateStep{ {candidate: candidate, body: func(requestID string) string { return scriptedArtifactPrepare("openai", requestID) }}, {candidate: candidate, body: func(requestID string) string { return scriptedArtifactPair("openai", requestID) }}, {candidate: candidate, body: func(requestID string) string { return scriptedArtifactLocalRead("openai", requestID) }}, {candidate: candidate, contentType: "text/event-stream", frames: frames}, } preset := hotPathSelectorPreset([]string{config.ModeDirect, config.ModeLight}) preset.WorkspaceTools = []config.ExecutionWorkspaceToolAlternative{scriptedLightWorkspaceAlternative()} srv := NewServer(config.EdgeOpenAIConf{}, service, nil) srv.SetEdgeID("edge-chat-provider-length") srv.SetExecutionPresets([]config.ExecutionPreset{preset}) providers := map[string]string{candidate.ProviderID: "served-openai"} srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "virtual-model", ExecutionPreset: preset.ID}, {ID: "selector-model", Providers: providers}, {ID: "local-model", Providers: providers}, {ID: "review-model", Providers: providers}, }) tools := scriptedLightTools("openai") history := []any{map[string]any{"role": "user", "content": "provider length terminal"}} consume := func(response *httptest.ResponseRecorder, results []string) { t.Helper() assistant, ids, err := artifactAssistantFromResponse("openai", response.Body.Bytes()) if err != nil || len(ids) != len(results) { t.Fatalf("consume setup response: ids=%v err=%v body=%s", ids, err, response.Body.String()) } history = append(history, assistant) history = scriptedArtifactAppendResults("openai", history, ids, results) } requestSetup := func() *httptest.ResponseRecorder { body := scriptedArtifactRequestBodyWithOptions(t, "openai", tools, history, 64, false) return serveScriptedArtifactRequest(t, srv, "openai", body) } consume(requestSetup(), []string{`{"written":true}`}) consume(requestSetup(), []string{`{"written":true}`, `{"written":true}`}) consume(requestSetup(), []string{`{"written":true}`}) httpServer := httptest.NewServer(srv.routes()) defer httpServer.Close() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() body := scriptedArtifactRequestBodyWithOptions(t, "openai", tools, history, 64, true) request, err := http.NewRequestWithContext(ctx, http.MethodPost, httpServer.URL+"/v1/chat/completions", strings.NewReader(string(body))) if err != nil { t.Fatal(err) } response, err := http.DefaultClient.Do(request) if err != nil { t.Fatalf("stream request did not flush before terminal: %v", err) } defer response.Body.Close() reader := bufio.NewReader(response.Body) roleFrame, err := readHotPathSSEFrame(reader) if err != nil { t.Fatalf("read role before provider terminal: %v", err) } contentFrame, err := readHotPathSSEFrame(reader) if err != nil { t.Fatalf("read content before provider terminal: %v", err) } early := roleFrame + contentFrame if response.StatusCode != http.StatusOK || !strings.Contains(early, `"role":"assistant"`) || !strings.Contains(early, `"content":"provider-limited"`) || !strings.Contains(early, `"id":"chatcmpl-provider-length"`) || strings.Contains(early, "[DONE]") || strings.Contains(early, `"finish_reason":"`) { t.Fatalf("pre-terminal provider length flush mismatch: status=%d body=%s", response.StatusCode, early) } requestID, snapshot := soleHotPathSnapshot(t, srv) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, RunId: "run-chat-length-local", Body: []byte("data: {\"id\":\"chatcmpl-provider-length\",\"created\":1777001301,\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"length\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":6,\"total_tokens\":11}}\n\ndata: [DONE]\n\n"), } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true, RunId: "run-chat-length-local"} close(frames) rest, err := io.ReadAll(reader) if err != nil { t.Fatalf("read provider length terminal: %v", err) } wire := early + string(rest) chunks, done := decodeHotPathChatSSE(t, wire) if done != 1 { t.Fatalf("DONE count=%d body=%s", done, wire) } assertHotPathChatChunks(t, chunks, hotPathChatChunkExpectation{ ResponseID: "chatcmpl-provider-length", Model: "virtual-model", Content: "provider-limited", Kinds: []string{"content", "terminal"}, FinishReason: "length", PromptTokens: 5, CompletionTokens: 6, }) if got := service.requestCount(); got != 4 { t.Fatalf("provider submissions=%d, want 4 with no review dispatch", got) } if srv.lightFlows.has(requestID, srv.edgeIDValue()) { t.Fatalf("provider length retained light state for %q", requestID) } assertHotPathTerminal(t, srv) for _, internalID := range []string{requestID, snapshot.ActiveStageID, "run-chat-length-local"} { if strings.Contains(wire, internalID) { t.Fatalf("internal identity %q leaked: %s", internalID, wire) } } } func readHotPathSSEFrame(reader *bufio.Reader) (string, error) { var frame strings.Builder for { line, err := reader.ReadString('\n') frame.WriteString(line) if err != nil { return frame.String(), err } if line == "\n" || line == "\r\n" { return frame.String(), nil } } } type hotPathChatChunkExpectation struct { ResponseID, Model, Content, Reasoning string ToolID, ToolName, ToolArgs, FinishReason string Kinds []string PromptTokens, CompletionTokens int } func assertHotPathChatChunks(t *testing.T, chunks []map[string]any, want hotPathChatChunkExpectation) { t.Helper() var content, reasoning, toolID, toolName, toolArgs, finish string var kinds []string roleCount := 0 terminalCount := 0 toolIndex := -1 promptTokens := 0 completionTokens := 0 for _, chunk := range chunks { if chunk["id"] != want.ResponseID || chunk["model"] != want.Model { t.Fatalf("chunk identity mismatch: %+v", chunk) } choices, ok := chunk["choices"].([]any) if !ok || len(choices) != 1 { t.Fatalf("chunk choices mismatch: %+v", chunk) } choice := choices[0].(map[string]any) delta := choice["delta"].(map[string]any) if delta["role"] == "assistant" { roleCount++ } if text, _ := delta["content"].(string); text != "" { content += text kinds = append(kinds, "content") } if text, _ := delta["reasoning_content"].(string); text != "" { reasoning += text kinds = append(kinds, "reasoning") } if tools, ok := delta["tool_calls"].([]any); ok { if len(tools) != 1 { t.Fatalf("tool delta count=%d chunk=%+v", len(tools), chunk) } tool := tools[0].(map[string]any) index := int(tool["index"].(float64)) if toolIndex == -1 { toolIndex = index } else if toolIndex != index { t.Fatalf("tool index changed from %d to %d", toolIndex, index) } if id, _ := tool["id"].(string); id != "" { toolID = id } function := tool["function"].(map[string]any) if name, _ := function["name"].(string); name != "" { toolName = name } if args, _ := function["arguments"].(string); args != "" { toolArgs += args } kinds = append(kinds, "tool") } if reason, _ := choice["finish_reason"].(string); reason != "" { finish = reason terminalCount++ kinds = append(kinds, "terminal") if usage, ok := chunk["usage"].(map[string]any); ok { promptTokens = int(usage["prompt_tokens"].(float64)) completionTokens = int(usage["completion_tokens"].(float64)) } } } if roleCount != 1 || terminalCount != 1 || content != want.Content || reasoning != want.Reasoning || finish != want.FinishReason || promptTokens != want.PromptTokens || completionTokens != want.CompletionTokens || strings.Join(kinds, ",") != strings.Join(want.Kinds, ",") { t.Fatalf("chunk aggregate mismatch: role=%d terminal=%d content=%q reasoning=%q finish=%q usage=%d/%d kinds=%v chunks=%+v", roleCount, terminalCount, content, reasoning, finish, promptTokens, completionTokens, kinds, chunks) } if want.ToolName != "" { if toolIndex != 0 || toolName != want.ToolName || toolArgs != want.ToolArgs { t.Fatalf("tool aggregate mismatch: index=%d id=%q name=%q args=%q", toolIndex, toolID, toolName, toolArgs) } if want.ToolID != "" && toolID != want.ToolID { t.Fatalf("tool id=%q, want %q", toolID, want.ToolID) } } } func decodeHotPathChatSSE(t *testing.T, body string) ([]map[string]any, int) { t.Helper() var chunks []map[string]any done := 0 for _, frame := range strings.Split(body, "\n\n") { frame = strings.TrimSpace(frame) if frame == "" { continue } if !strings.HasPrefix(frame, "data: ") { t.Fatalf("unexpected SSE frame %q", frame) } data := strings.TrimSpace(strings.TrimPrefix(frame, "data: ")) if data == "[DONE]" { done++ continue } var chunk map[string]any if err := json.Unmarshal([]byte(data), &chunk); err != nil { t.Fatalf("decode SSE chunk: %v data=%s", err, data) } chunks = append(chunks, chunk) } return chunks, done } func serveHotPathChatBody(t *testing.T, srv *Server, body string) *httptest.ResponseRecorder { t.Helper() request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) response := httptest.NewRecorder() srv.routes().ServeHTTP(response, request) return response } type hotPathChatGateStep struct { candidate edgeservice.ProviderPoolCandidate contentType string body func(string) string frames chan *iop.ProviderTunnelFrame } type hotPathChatGateScriptedService struct { providerFakeRunService mu sync.Mutex steps []hotPathChatGateStep requests []edgeservice.ProviderPoolDispatchRequest } func (s *hotPathChatGateScriptedService) SubmitProviderPool(ctx context.Context, req edgeservice.ProviderPoolDispatchRequest) (*edgeservice.ProviderPoolDispatchResult, error) { if err := ctx.Err(); err != nil { return nil, err } s.mu.Lock() index := len(s.requests) if index >= len(s.steps) { s.mu.Unlock() return nil, fmt.Errorf("unexpected Chat gate dispatch %d", index+1) } s.requests = append(s.requests, req) step := s.steps[index] s.mu.Unlock() dispatch := edgeservice.RunDispatch{ RunID: fmt.Sprintf("run-chat-gate-%d", index+1), NodeID: "node-chat-gate", ModelGroupKey: req.Run.ModelGroupKey, ProviderID: step.candidate.ProviderID, ExecutionPath: string(edgeservice.ProviderPoolPathTunnel), ProfileID: step.candidate.ProfileID, ProfileDriver: step.candidate.ProfileDriver, ProfileCapabilities: append([]string(nil), step.candidate.ProfileCapabilities...), } contentType := step.contentType if contentType == "" { contentType = "application/json" } frames := step.frames if frames == nil { body := step.body(req.Run.Metadata["iop_logical_request_id"]) frames = hotPathTunnelFrames(body, contentType, dispatch.RunID, 1_777_001_100_000_000_000+int64(index)) } return &edgeservice.ProviderPoolDispatchResult{ Path: edgeservice.ProviderPoolPathTunnel, Tunnel: &fakeTunnelHandle{dispatch: dispatch, frames: frames}, DispatchInfo: dispatch, }, nil } func (s *hotPathChatGateScriptedService) requestCount() int { s.mu.Lock() defer s.mu.Unlock() return len(s.requests) } func hotPathChatMixedLocalSSE() string { return strings.Join([]string{ `data: {"id":"chatcmpl-mixed-local","created":1777001101,"choices":[{"index":0,"delta":{"content":"local-A "},"finish_reason":null}]}`, `data: {"id":"chatcmpl-mixed-local","created":1777001101,"choices":[{"index":0,"delta":{"reasoning_content":"local-think "},"finish_reason":null}]}`, `data: {"id":"chatcmpl-mixed-local","created":1777001101,"choices":[{"index":0,"delta":{"content":"local-B"},"finish_reason":null}]}`, `data: {"id":"chatcmpl-mixed-local","created":1777001101,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":4,"total_tokens":9}}`, `data: [DONE]`, "", }, "\n\n") } func hotPathChatMixedReviewSSE(requestID string) string { args := hotPathChatReviewArguments(requestID) events := []any{ map[string]any{"type": "message_start", "message": map[string]any{ "id": "msg-mixed-review", "type": "message", "role": "assistant", "content": []any{}, "usage": map[string]any{"input_tokens": 7, "output_tokens": 0}, }}, map[string]any{"type": "content_block_start", "index": 0, "content_block": map[string]any{"type": "thinking", "thinking": "review-think ", "signature": ""}}, map[string]any{"type": "content_block_start", "index": 1, "content_block": map[string]any{"type": "text", "text": "review-visible"}}, map[string]any{"type": "content_block_start", "index": 2, "content_block": map[string]any{"type": "tool_use", "id": "provider-review-write", "name": "write_file", "input": json.RawMessage(args)}}, map[string]any{"type": "message_delta", "delta": map[string]any{"stop_reason": "tool_use", "stop_sequence": nil}, "usage": map[string]any{"output_tokens": 3}}, map[string]any{"type": "message_stop"}, } var builder strings.Builder for _, event := range events { encoded, _ := json.Marshal(event) fmt.Fprintf(&builder, "data: %s\n\n", encoded) } return builder.String() } func hotPathChatReviewArguments(requestID string) string { encoded, _ := json.Marshal(map[string]string{ "content": "review", "path": newReservedPaths(requestID).ReviewPath, }) return string(encoded) }