mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-08-11 09:44:46 +00:00
Some checks are pending
CI / cross-compile (arm64, darwin) (push) Waiting to run
CI / cross-compile (arm64, linux) (push) Waiting to run
CI / cross-compile (arm64, windows) (push) Waiting to run
CI / test (push) Waiting to run
CI / cross-compile (amd64, darwin) (push) Waiting to run
CI / cross-compile (amd64, windows) (push) Waiting to run
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Pages / build (push) Waiting to run
Deploy Pages / deploy (push) Blocked by required conditions
* chore: add SPDX license headers to all source files
Add Apache-2.0 SPDX license identifiers and copyright notices to all
tracked .go, .sh, .js, .mjs, .ts, and .tsx source files.
Introduce scripts/verify-license.sh and scripts/add-license.sh for
automated verification and bulk addition of license headers. Integrate
the check into CI (ci.yml) and the Makefile (license-check target as
a prerequisite of the existing check target).
This satisfies the OpenSSF Best Practices Badge requirements for
copyright_per_file and license_per_file.
* fix: restore execute permissions on scripts
* docs: add license header instructions to CONTRIBUTING guides
* docs: add license header instructions to pages contributing guides
* fix(pages): strip unclosed HTML comment markers to satisfy CodeQL
* fix: apply code review suggestions for license scripts
- Fix portability: detect macOS vs Linux stat for permission copy
- Fix has_header: check both SPDX and copyright (match verify logic)
- Fix is_ignored: match on path boundaries to avoid false positives
- Fix year extraction: use consistent pipeline across both scripts
- Fix Bash 3.2 compat: quote array length expansion for set -u
* fix(pages): use loop-until-clean for HTML comment stripping (CodeQL)
* fix(pages): use split/join instead of replace to avoid CodeQL false positive
CodeQL's js/incomplete-multi-character-sanitization rule flags any
.replace() that removes multi-character sequences like '<!--...-->',
regardless of context. The data here comes from readFileSync on the
project's own index.html (no untrusted input), making this a false
positive. Using split(regex).join('') achieves the same result without
triggering the taint-tracking rule.
110 lines
4.3 KiB
Go
110 lines
4.3 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2026 alibaba/open-code-review Contributors
|
|
|
|
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/alibaba/open-code-review/internal/llm"
|
|
"github.com/alibaba/open-code-review/internal/model"
|
|
)
|
|
|
|
// Cost-estimation heuristics for the diff-review path. These mirror
|
|
// internal/scan/estimate.go deliberately: scan and review are two paths over
|
|
// the same underlying review model, and their estimates should be comparable.
|
|
// The values are intentionally re-declared here (rather than imported from
|
|
// scan) to avoid a new agent→scan cross-package dependency; keep them in sync
|
|
// if scan's change.
|
|
//
|
|
// As in scan, these are rough — their job is to give the user an
|
|
// order-of-magnitude warning before a large review, not to be
|
|
// billing-accurate. Real usage is always reported from the API response after
|
|
// the run. The estimate also cannot account for agent tool-use inflation
|
|
// because tool use can multiply the prompt substantially, so it is a floor.
|
|
const (
|
|
// promptOverheadTokens approximates the fixed prompt scaffolding per LLM
|
|
// call (system prompt + template wrappers + tool definitions).
|
|
// KEEP IN SYNC with internal/scan/estimate.go.
|
|
promptOverheadTokens = 2000
|
|
// avgMainRoundsPerFile is the assumed number of MAIN_TASK tool-use rounds
|
|
// for a typical file. Observed ~6 on real repos; round up.
|
|
// KEEP IN SYNC with internal/scan/estimate.go.
|
|
avgMainRoundsPerFile = 7
|
|
// avgOutputTokensPerRound approximates completion tokens per round.
|
|
// KEEP IN SYNC with internal/scan/estimate.go.
|
|
avgOutputTokensPerRound = 700
|
|
)
|
|
|
|
// Estimate is a pre-run, order-of-magnitude projection of review cost. It
|
|
// mirrors scan.Estimate so callers can treat the two paths uniformly.
|
|
type Estimate struct {
|
|
Files int
|
|
InputTokens int64
|
|
OutputTokens int64
|
|
TotalTokens int64
|
|
}
|
|
|
|
// estimateDiffFileTokens projects the input+output token cost of reviewing a
|
|
// single diff (PLAN + MAIN_TASK rounds). It mirrors scan.estimateFileTokens
|
|
// but counts tokens of the diff text (d.Diff) rather than whole-file content,
|
|
// since the diff path reviews patches, not full files. Returns 0 for deleted
|
|
// files (they are skipped before dispatch and must not trip the gate). Used
|
|
// both by the aggregate estimate (estimateDiffCost) and by the per-file budget
|
|
// look-ahead in dispatchSubtasks.
|
|
func estimateDiffFileTokens(d model.Diff) int64 {
|
|
if d.IsDeleted || d.Diff == "" {
|
|
return 0
|
|
}
|
|
diffTokens := int64(llm.CountTokens(d.Diff))
|
|
|
|
// PLAN phase input/output (small JSON output).
|
|
total := diffTokens + promptOverheadTokens
|
|
total += 400
|
|
// MAIN_TASK: diff content carried across rounds + per-round overhead.
|
|
total += (diffTokens + promptOverheadTokens) * avgMainRoundsPerFile
|
|
total += avgOutputTokensPerRound * avgMainRoundsPerFile
|
|
return total
|
|
}
|
|
|
|
// estimateDiffCost projects token usage for reviewing the given diffs. It is
|
|
// the diff-path analogue of scan.estimateCost and is used for the pre-review
|
|
// scale warning.
|
|
func estimateDiffCost(diffs []model.Diff) Estimate {
|
|
var est Estimate
|
|
for _, d := range diffs {
|
|
if d.IsDeleted || d.Diff == "" {
|
|
continue
|
|
}
|
|
est.Files++
|
|
diffTokens := int64(llm.CountTokens(d.Diff))
|
|
est.InputTokens += diffTokens + promptOverheadTokens
|
|
est.OutputTokens += 400
|
|
est.InputTokens += (diffTokens + promptOverheadTokens) * avgMainRoundsPerFile
|
|
est.OutputTokens += avgOutputTokensPerRound * avgMainRoundsPerFile
|
|
}
|
|
est.TotalTokens = est.InputTokens + est.OutputTokens
|
|
return est
|
|
}
|
|
|
|
// String renders a one-line human-readable estimate, matching scan.Estimate's
|
|
// format so diff and scan warnings read consistently. Money is intentionally
|
|
// omitted — pricing varies per provider/model and we don't imply a precise
|
|
// dollar figure.
|
|
func (e Estimate) String() string {
|
|
return fmt.Sprintf("~%d file(s), est. %s input + %s output ≈ %s total tokens (rough; agent tool-use inflates this — actual reported after run)",
|
|
e.Files, humanTokens(e.InputTokens), humanTokens(e.OutputTokens), humanTokens(e.TotalTokens))
|
|
}
|
|
|
|
// humanTokens formats a token count as e.g. "1.2M" / "850K" / "420".
|
|
// Mirrors internal/scan/estimate.go; kept here so agent has no scan import.
|
|
func humanTokens(n int64) string {
|
|
switch {
|
|
case n >= 1_000_000:
|
|
return fmt.Sprintf("%.1fM", float64(n)/1_000_000)
|
|
case n >= 1_000:
|
|
return fmt.Sprintf("%.0fK", float64(n)/1_000)
|
|
default:
|
|
return fmt.Sprintf("%d", n)
|
|
}
|
|
}
|