Repository navigation
Include inner interactive-toolkit cost in session cost #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1de68e9
47a6c6c
fed25ca
ec3dfec
495ee19
abf64a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,6 +120,65 @@ func (l *Loop) usageByModelSnapshot() []pipeline.ModelUsage { | |
| return out | ||
| } | ||
|
|
||
| // toolUsageBucket is one interactive toolkit's running cost accumulation across | ||
| // its invocations this session, keyed by outer tool name. | ||
| type toolUsageBucket struct { | ||
| tool string | ||
| costMicroUSD int64 | ||
| costReported bool | ||
| } | ||
|
|
||
| // AddToolCost folds one interactive toolkit result event's provider-reported | ||
| // cost into the bucket for outerTool. Shares usageMu with addUsage/addModelUsage | ||
| // (see usageMu's doc). ok is the toolkit's success flag; v1 accumulates cost | ||
| // regardless — a run the provider billed cost money whether or not it succeeded, | ||
| // and an unbilled run reports 0 anyway. | ||
| // | ||
| // Exported (unlike addModelUsage) because it is wired from internal/cmd/runner's | ||
| // interactive-tool result callback, which lives in a different package; the | ||
| // runner's turn loop feeds addModelUsage internally, so that one stays private. | ||
| func (l *Loop) AddToolCost(outerTool string, costUSD float64, ok bool) { | ||
| l.usageMu.Lock() | ||
| defer l.usageMu.Unlock() | ||
| if l.usageByToolIdx == nil { | ||
| l.usageByToolIdx = make(map[string]int) | ||
| } | ||
| i, seen := l.usageByToolIdx[outerTool] | ||
| if !seen { | ||
| i = len(l.usageByTool) | ||
| l.usageByToolIdx[outerTool] = i | ||
| l.usageByTool = append(l.usageByTool, toolUsageBucket{tool: outerTool}) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bucket is appended before the |
||
| } | ||
| b := &l.usageByTool[i] | ||
| if costUSD < 0 { | ||
| // A negative provider-reported tool cost would reduce the session total. | ||
| // Treat it as unreported rather than netting it against real charges — | ||
| // same guard as addModelUsage. | ||
| slog.Default().Info("negative reported tool cost; ignoring", | ||
| "tool", outerTool, "costUSD", costUSD) | ||
| return | ||
| } | ||
| // Round once at the USD->micro-USD boundary so accumulation is pure int64. | ||
| b.costMicroUSD += int64(math.Round(costUSD * 1e6)) | ||
| b.costReported = true | ||
| _ = ok | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| // usageByToolSnapshot returns the Loop's per-tool cost as pipeline.ToolUsage in | ||
| // first-encountered order (nil when unused). Safe for concurrent use. | ||
| func (l *Loop) usageByToolSnapshot() []pipeline.ToolUsage { | ||
| l.usageMu.Lock() | ||
| defer l.usageMu.Unlock() | ||
| if len(l.usageByTool) == 0 { | ||
| return nil | ||
| } | ||
| out := make([]pipeline.ToolUsage, len(l.usageByTool)) | ||
| for i, b := range l.usageByTool { | ||
| out[i] = pipeline.ToolUsage{Tool: b.tool, CostMicroUSD: b.costMicroUSD, CostReported: b.costReported} | ||
| } | ||
| return out | ||
| } | ||
|
|
||
| // StampEstimatedCost persists the cost estimate via the StatusPatcher. nil-safe: | ||
| // a Loop without a Status (tests/kubectl-driven) silently no-ops. | ||
| func (l *Loop) StampEstimatedCost(ctx context.Context, c spiceboxv1alpha1.EstimatedSessionCost) error { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okis passed frommain.gothroughbuildToolSessionEventPublisherand then discarded with_ = ok. Since this is a new exported method, could it be dropped toAddToolCost(outerTool string, costUSD float64)? Readers will otherwise assume success affects the accounting.