Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions internal/agent/completion_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@ func (e *defaultEngine) completeTurn(ctx context.Context, request llm.ChatReques
return runner.client.CreateMessage(ctx, request)
}

request.OnStreamProgress = func(charCount int, active bool) {
runner.emit(Event{
Type: EventThinkingProgress,
ReasoningCharCount: charCount,
ReasoningActive: active,
})
}

reply, err := runner.client.StreamMessage(ctx, request, func(delta string) {
if delta != "" {
emitTurnEvent(ctx, TurnEvent{
Expand Down
5 changes: 0 additions & 5 deletions internal/agent/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const (
EventPlanUpdated EventType = "plan_updated"
EventUsageUpdated EventType = "usage_updated"
EventRunFinished EventType = "run_finished"
EventThinkingProgress EventType = "thinking_progress"
)

type Event struct {
Expand All @@ -34,10 +33,6 @@ type Event struct {
Usage llm.Usage
AgentID string // non-empty when emitted by a subagent
InvocationID string // non-empty when emitted by a subagent, globally unique per invocation

// EventThinkingProgress fields — reasoning progress without exposing content.
ReasoningCharCount int
ReasoningActive bool
}

type Observer interface {
Expand Down
65 changes: 0 additions & 65 deletions internal/agent/runner_usage_stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,68 +78,3 @@ func TestRunPromptEmitsUsageEventForStreamingReplyUsage(t *testing.T) {
t.Fatalf("expected exactly 1 usage event, got %d", usageEvents)
}
}

type reasoningProgressStreamClient struct {
reply llm.Message
}

func (c *reasoningProgressStreamClient) CreateMessage(context.Context, llm.ChatRequest) (llm.Message, error) {
return c.reply, nil
}

func (c *reasoningProgressStreamClient) StreamMessage(_ context.Context, req llm.ChatRequest, onDelta func(string)) (llm.Message, error) {
if req.OnStreamProgress != nil {
req.OnStreamProgress(50, true)
req.OnStreamProgress(100, false)
}
if onDelta != nil && strings.TrimSpace(c.reply.Content) != "" {
onDelta(c.reply.Content)
}
return c.reply, nil
}

func TestCompleteTurnEmitsThinkingProgressViaOnStreamProgress(t *testing.T) {
workspace := t.TempDir()
store, err := session.NewStore(t.TempDir())
if err != nil {
t.Fatal(err)
}
sess := session.New(workspace)

var events []Event
runner := NewRunner(Options{
Workspace: workspace,
Config: config.Config{
Provider: config.ProviderConfig{Model: "test-model"},
MaxIterations: 1,
Stream: true,
},
Client: &reasoningProgressStreamClient{reply: llm.Message{
Role: llm.RoleAssistant,
Content: "final answer",
}},
Store: store,
Registry: tools.DefaultRegistry(),
Observer: ObserverFunc(func(event Event) { events = append(events, event) }),
Stdin: strings.NewReader(""),
Stdout: io.Discard,
})

if _, err := runner.RunPrompt(context.Background(), sess, "hello", "build", io.Discard); err != nil {
t.Fatal(err)
}

progressEvents := 0
for _, event := range events {
if event.Type != EventThinkingProgress {
continue
}
progressEvents++
if event.ReasoningCharCount != 50 && event.ReasoningCharCount != 100 {
t.Fatalf("unexpected reasoning char count: %d", event.ReasoningCharCount)
}
}
if progressEvents != 2 {
t.Fatalf("expected exactly 2 thinking progress events, got %d", progressEvents)
}
}
30 changes: 13 additions & 17 deletions internal/app/tui_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,19 @@ func (a *tuiRunnerAdapter) DispatchSubAgent(

func mapAgentEvent(event agent.Event) tui.Event {
return tui.Event{
Type: mapAgentEventType(event.Type),
SessionID: string(event.SessionID),
UserInput: event.UserInput,
Content: event.Content,
ToolName: event.ToolName,
ToolCallID: event.ToolCallID,
ToolArguments: event.ToolArguments,
ToolResult: event.ToolResult,
Error: event.Error,
Plan: event.Plan,
Usage: event.Usage,
AgentID: event.AgentID,
InvocationID: event.InvocationID,
ReasoningCharCount: event.ReasoningCharCount,
ReasoningActive: event.ReasoningActive,
Type: mapAgentEventType(event.Type),
SessionID: string(event.SessionID),
UserInput: event.UserInput,
Content: event.Content,
ToolName: event.ToolName,
ToolCallID: event.ToolCallID,
ToolArguments: event.ToolArguments,
ToolResult: event.ToolResult,
Error: event.Error,
Plan: event.Plan,
Usage: event.Usage,
AgentID: event.AgentID,
InvocationID: event.InvocationID,
}
}

Expand All @@ -226,8 +224,6 @@ func mapAgentEventType(value agent.EventType) tui.EventType {
return tui.EventUsageUpdated
case agent.EventRunFinished:
return tui.EventRunFinished
case agent.EventThinkingProgress:
return tui.EventThinkingProgress
default:
return tui.EventType(value)
}
Expand Down
28 changes: 10 additions & 18 deletions internal/app/tui_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,16 @@ func TestTUIRunnerAdapterNilGuardMethods(t *testing.T) {

func TestMapAgentEventAndType(t *testing.T) {
source := agent.Event{
Type: agent.EventToolCallCompleted,
SessionID: "sess-1",
UserInput: "/review check changes",
Content: "done",
ToolName: "delegate_subagent",
ToolArguments: `{"agent":"review"}`,
ToolResult: `{"ok":true}`,
Error: "none",
Plan: planpkg.State{},
Usage: llm.Usage{InputTokens: 12, OutputTokens: 34},
ReasoningCharCount: 200,
ReasoningActive: true,
Type: agent.EventToolCallCompleted,
SessionID: "sess-1",
UserInput: "/review check changes",
Content: "done",
ToolName: "delegate_subagent",
ToolArguments: `{"agent":"review"}`,
ToolResult: `{"ok":true}`,
Error: "none",
Plan: planpkg.State{},
Usage: llm.Usage{InputTokens: 12, OutputTokens: 34},
}

mapped := mapAgentEvent(source)
Expand All @@ -191,16 +189,10 @@ func TestMapAgentEventAndType(t *testing.T) {
if mapped.Usage.InputTokens != 12 || mapped.Usage.OutputTokens != 34 {
t.Fatalf("expected mapped usage to be preserved, got %#v", mapped.Usage)
}
if mapped.ReasoningCharCount != 200 || !mapped.ReasoningActive {
t.Fatalf("expected reasoning progress fields to be mapped, got charCount=%d active=%v", mapped.ReasoningCharCount, mapped.ReasoningActive)
}

if got := mapAgentEventType(agent.EventRunStarted); got != tui.EventRunStarted {
t.Fatalf("expected run-started mapping, got %q", got)
}
if got := mapAgentEventType(agent.EventThinkingProgress); got != tui.EventThinkingProgress {
t.Fatalf("expected thinking-progress mapping, got %q", got)
}
const unknown = agent.EventType("custom")
if got := mapAgentEventType(unknown); got != tui.EventType(unknown) {
t.Fatalf("expected fallback mapping for unknown event type, got %q", got)
Expand Down
7 changes: 0 additions & 7 deletions internal/llm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,6 @@ type ChatRequest struct {
Tools []ToolDefinition
Assets map[AssetID]ImageAsset
Temperature float64

// OnStreamProgress is called during streaming when the provider receives
// non-content progress updates (e.g. reasoning_content chunks from
// DeepSeek-style models). charCount is the cumulative number of
// characters received so far; active is true while reasoning is still
// streaming.
OnStreamProgress func(charCount int, active bool)
}

type Client interface {
Expand Down
Loading
Loading