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
122 changes: 122 additions & 0 deletions internal/cmd/reviewcmd/adapter_progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package reviewcmd

import (
"context"
"path/filepath"
"strconv"
"strings"

"github.com/open-cli-collective/codereview-cli/internal/llm"
"github.com/open-cli-collective/codereview-cli/internal/progress"
)

type progressAdapter struct {
adapter llm.Adapter
logger *progress.Logger
provider string
harness string
}

type progressStream struct {
stream llm.Stream
span *progress.Span
}

func withProgressAdapter(logger *progress.Logger, adapter llm.Adapter, provider, harness string) llm.Adapter {
if adapter == nil || logger == nil {
return adapter
}
return progressAdapter{
adapter: adapter,
logger: logger,
provider: strings.TrimSpace(provider),
harness: strings.TrimSpace(harness),
}
}

func (a progressAdapter) Name() string { return a.adapter.Name() }

func (a progressAdapter) SupportsResume() bool { return a.adapter.SupportsResume() }

func (a progressAdapter) SupportsCacheAccounting() bool { return a.adapter.SupportsCacheAccounting() }

func (a progressAdapter) SupportsCostReporting() bool { return a.adapter.SupportsCostReporting() }

func (a progressAdapter) Quota(ctx context.Context) (llm.Quota, bool, error) {
return a.adapter.Quota(ctx)
}

func (a progressAdapter) Start(ctx context.Context, req llm.Request) (llm.Stream, error) {
return a.start(ctx, "start_llm", req, "")
}

func (a progressAdapter) Resume(ctx context.Context, sessionID string, req llm.Request) (llm.Stream, error) {
return a.start(ctx, "resume_llm", req, sessionID)
}

func (a progressAdapter) start(ctx context.Context, op string, req llm.Request, resumeSessionID string) (llm.Stream, error) {
fields := llmProgressFields(a.provider, a.harness, req, resumeSessionID)
span := a.logger.StartFields("review", op, "llm", fields...)
var (
stream llm.Stream
err error
)
if strings.TrimSpace(resumeSessionID) != "" {
stream, err = a.adapter.Resume(ctx, resumeSessionID, req)
} else {
stream, err = a.adapter.Start(ctx, req)
}
if err != nil {
span.End(err)
return nil, err
}
return progressStream{stream: stream, span: span}, nil
}

func (s progressStream) SessionID() string {
return s.stream.SessionID()
}

func (s progressStream) Wait(ctx context.Context) (llm.Response, error) {
resp, err := s.stream.Wait(ctx)
fields := llmResponseFields(resp)
s.span.EndFields(err, fields...)
return resp, err
}

func llmProgressFields(provider, harness string, req llm.Request, _ string) []progress.Field {
fields := []progress.Field{}
if provider = strings.TrimSpace(provider); provider != "" {
fields = append(fields, progress.Field{Key: "provider", Value: provider})
}
if harness = strings.TrimSpace(harness); harness != "" {
fields = append(fields, progress.Field{Key: "harness", Value: harness})
}
if model := strings.TrimSpace(req.Model); model != "" {
fields = append(fields, progress.Field{Key: "model", Value: model})
}
if effort := strings.TrimSpace(req.Effort); effort != "" {
fields = append(fields, progress.Field{Key: "effort", Value: effort})
}
if logPath := strings.TrimSpace(req.LogPath); logPath != "" {
fields = append(fields, progress.Field{Key: "log_file", Value: filepath.Base(logPath)})
}
return fields
}

func llmResponseFields(resp llm.Response) []progress.Field {
fields := []progress.Field{}
if resp.Usage.TokensIn != nil {
fields = append(fields, progress.Field{Key: "tokens_in", Value: strconv.Itoa(*resp.Usage.TokensIn)})
}
if resp.Usage.TokensOut != nil {
fields = append(fields, progress.Field{Key: "tokens_out", Value: strconv.Itoa(*resp.Usage.TokensOut)})
}
if resp.Usage.CacheRead != nil {
fields = append(fields, progress.Field{Key: "cache_read", Value: strconv.Itoa(*resp.Usage.CacheRead)})
}
if resp.Usage.CacheCreate != nil {
fields = append(fields, progress.Field{Key: "cache_create", Value: strconv.Itoa(*resp.Usage.CacheCreate)})
}
return fields
}
61 changes: 61 additions & 0 deletions internal/cmd/reviewcmd/live_progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package reviewcmd

import (
"context"
"strconv"

"github.com/open-cli-collective/codereview-cli/internal/approvaloverride"
"github.com/open-cli-collective/codereview-cli/internal/ledger"
"github.com/open-cli-collective/codereview-cli/internal/pipeline"
"github.com/open-cli-collective/codereview-cli/internal/progress"
"github.com/open-cli-collective/codereview-cli/internal/reviewrun"
)

type progressPlanner struct {
planner reviewrun.Planner
logger *progress.Logger
}

type progressApprovalOverrideClassifier struct {
classifier approvaloverride.Classifier
logger *progress.Logger
}

func withProgressPlanner(logger *progress.Logger, planner reviewrun.Planner) reviewrun.Planner {
if planner == nil || logger == nil {
return planner
}
return progressPlanner{planner: planner, logger: logger}
}

func withProgressApprovalOverrideClassifier(logger *progress.Logger, classifier approvaloverride.Classifier) approvaloverride.Classifier {
if classifier == nil || logger == nil {
return classifier
}
return progressApprovalOverrideClassifier{classifier: classifier, logger: logger}
}

func (p progressPlanner) Live(ctx context.Context, req pipeline.Request, run ledger.Run) (pipeline.Result, error) {
span := p.logger.StartFields("review", "plan_live_review", "pr", progress.Field{Key: "run_id", Value: run.RunID})
result, err := p.planner.Live(ctx, req, run)
return result, endProgressSpanFields(span, err, progress.Field{Key: "run_id", Value: run.RunID})
}

func (c progressApprovalOverrideClassifier) ClassifyApprovalOverride(ctx context.Context, req approvaloverride.Request) (approvaloverride.Result, error) {
fields := []progress.Field{{Key: "candidate_count", Value: strconv.Itoa(len(req.Candidates))}}
span := c.logger.StartFields("review", "classify_approval_override", "pr", fields...)
result, err := c.classifier.ClassifyApprovalOverride(ctx, req)
return result, endProgressSpanFieldsResult(span, err, result, fields...)
}

func endProgressSpanFields(span *progress.Span, err error, fields ...progress.Field) error {
if span != nil {
span.EndFields(err, fields...)
}
return err
}

func endProgressSpanFieldsResult(span *progress.Span, err error, result approvaloverride.Result, fields ...progress.Field) error {
fields = append(fields, progress.Field{Key: "approve", Value: strconv.FormatBool(result.Approve)})
return endProgressSpanFields(span, err, fields...)
}
81 changes: 81 additions & 0 deletions internal/cmd/reviewcmd/pipeline_progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package reviewcmd

import (
"path/filepath"
"strconv"
"strings"

"github.com/open-cli-collective/codereview-cli/internal/pipeline"
"github.com/open-cli-collective/codereview-cli/internal/progress"
)

type pipelineTaskProgress struct {
logger *progress.Logger
}

type pipelineTaskSpan struct {
span *progress.Span
}

func newPipelineTaskProgress(logger *progress.Logger) pipeline.LLMTaskProgress {
if logger == nil {
return nil
}
return pipelineTaskProgress{logger: logger}
}

func (p pipelineTaskProgress) StartLLMTask(event pipeline.LLMTaskProgressEvent) pipeline.LLMTaskProgressSpan {
return pipelineTaskSpan{
span: p.logger.StartFields("review", "run_llm_task", "llm_task", pipelineTaskProgressFields(event)...),
}
}

func (p pipelineTaskProgress) LoadLLMTask(event pipeline.LLMTaskProgressEvent, result pipeline.LLMTaskProgressResult) {
span := p.logger.StartFields("review", "load_llm_task", "llm_task", pipelineTaskProgressFields(event)...)
span.EndFields(nil, pipelineTaskProgressResultFields(result)...)
}

func (s pipelineTaskSpan) End(err error, result pipeline.LLMTaskProgressResult) {
if s.span == nil {
return
}
s.span.EndFields(err, pipelineTaskProgressResultFields(result)...)
}

func pipelineTaskProgressFields(event pipeline.LLMTaskProgressEvent) []progress.Field {
fields := []progress.Field{
{Key: "task_id", Value: event.TaskID},
{Key: "phase", Value: event.Phase},
{Key: "source", Value: event.Source},
}
if agentID := strings.TrimSpace(event.AgentID); agentID != "" {
fields = append(fields, progress.Field{Key: "agent_id", Value: agentID})
}
if model := strings.TrimSpace(event.Model); model != "" {
fields = append(fields, progress.Field{Key: "model", Value: model})
}
if effort := strings.TrimSpace(event.Effort); effort != "" {
fields = append(fields, progress.Field{Key: "effort", Value: effort})
}
if logPath := strings.TrimSpace(event.LogPath); logPath != "" {
fields = append(fields, progress.Field{Key: "log_file", Value: filepath.Base(logPath)})
}
if resumeSessionID := strings.TrimSpace(event.ResumeSessionID); resumeSessionID != "" {
fields = append(fields, progress.Field{Key: "resume_session_id", Value: resumeSessionID})
}
return fields
}

func pipelineTaskProgressResultFields(result pipeline.LLMTaskProgressResult) []progress.Field {
fields := []progress.Field{
{Key: "cached", Value: strconv.FormatBool(result.Cached)},
{Key: "task_status", Value: result.Status},
}
if sessionID := strings.TrimSpace(result.ProviderSessionID); sessionID != "" {
fields = append(fields, progress.Field{Key: "session_id", Value: sessionID})
}
if result.ValidationAttempts > 0 {
fields = append(fields, progress.Field{Key: "validation_attempts", Value: strconv.Itoa(result.ValidationAttempts)})
}
return fields
}
20 changes: 20 additions & 0 deletions internal/cmd/reviewcmd/progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package reviewcmd

import (
"github.com/open-cli-collective/codereview-cli/internal/cmd/root"
"github.com/open-cli-collective/codereview-cli/internal/progress"
)

func newProgressLogger(opts *root.Options) *progress.Logger {
if opts == nil {
return progress.New(nil, true, nil)
}
return progress.New(opts.Stderr, opts.Quiet, nil)
}

func endProgressSpan(span *progress.Span, err error) error {
if span != nil {
span.End(err)
}
return err
}
Loading
Loading