You've already forked agentic-coding-workflow
340 lines
12 KiB
Go
340 lines
12 KiB
Go
package acp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// AgentKindAdminDTO 是 admin 视图的完整字段。EnvKeys 仅返回 key 列表(不返回值)。
|
|
type AgentKindAdminDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
Description string `json:"description"`
|
|
BinaryPath string `json:"binary_path"`
|
|
Args []string `json:"args"`
|
|
EnvKeys []string `json:"env_keys"`
|
|
Enabled bool `json:"enabled"`
|
|
ToolAllowlist []string `json:"tool_allowlist"`
|
|
ClientType string `json:"client_type"`
|
|
// MCPServers 明文返回(admin only,编辑所需);条目可能含 header/env 密钥。
|
|
MCPServers []McpServerSpec `json:"mcp_servers"`
|
|
// 成本核算:model 价格来源 + 默认预算上限(均可空)。
|
|
ModelID *string `json:"model_id"`
|
|
MaxCostUSD *float64 `json:"max_cost_usd"`
|
|
MaxTokens *int64 `json:"max_tokens"`
|
|
MaxWallClockSeconds *int32 `json:"max_wall_clock_seconds"`
|
|
CreatedBy uuid.UUID `json:"created_by"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// AgentKindPublicDTO 是普通用户视图,不暴露 binary_path / args / env_keys。
|
|
type AgentKindPublicDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// CreateAgentKindReq / UpdateAgentKindReq 是 HTTP 请求体。
|
|
type CreateAgentKindReq struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
Description string `json:"description"`
|
|
BinaryPath string `json:"binary_path"`
|
|
Args []string `json:"args"`
|
|
Env map[string]string `json:"env"`
|
|
Enabled *bool `json:"enabled"`
|
|
ToolAllowlist []string `json:"tool_allowlist"`
|
|
ClientType string `json:"client_type"`
|
|
MCPServers []McpServerSpec `json:"mcp_servers"`
|
|
// 成本核算:model_id 为字符串 UUID(空/缺省 = 不设置);预算上限均可选。
|
|
ModelID *string `json:"model_id"`
|
|
MaxCostUSD *float64 `json:"max_cost_usd"`
|
|
MaxTokens *int64 `json:"max_tokens"`
|
|
MaxWallClockSeconds *int32 `json:"max_wall_clock_seconds"`
|
|
}
|
|
|
|
type UpdateAgentKindReq struct {
|
|
DisplayName *string `json:"display_name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
BinaryPath *string `json:"binary_path,omitempty"`
|
|
Args []string `json:"args,omitempty"`
|
|
Env map[string]string `json:"env,omitempty"`
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
ToolAllowlist []string `json:"tool_allowlist,omitempty"`
|
|
ClientType *string `json:"client_type,omitempty"`
|
|
MCPServers []McpServerSpec `json:"mcp_servers,omitempty"` // nil = 不改;非 nil(含空)= 替换
|
|
// 成本核算三态:字段存在(非 nil)= 设置;缺省(nil)= 不改。
|
|
// ModelID 空串 = 清空;预算上限值 <=0 = 清空。
|
|
ModelID *string `json:"model_id,omitempty"`
|
|
MaxCostUSD *float64 `json:"max_cost_usd,omitempty"`
|
|
MaxTokens *int64 `json:"max_tokens,omitempty"`
|
|
MaxWallClockSeconds *int32 `json:"max_wall_clock_seconds,omitempty"`
|
|
}
|
|
|
|
// ConfigFileDTO 是 AgentKind 配置文件的对外视图(admin only,content 明文)。
|
|
type ConfigFileDTO struct {
|
|
RelPath string `json:"rel_path"`
|
|
Content string `json:"content"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// UpsertConfigFileReq 是 PUT config-files 请求体。
|
|
type UpsertConfigFileReq struct {
|
|
RelPath string `json:"rel_path"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
func configFileToDTO(f *ConfigFileContent) ConfigFileDTO {
|
|
return ConfigFileDTO{
|
|
RelPath: f.RelPath,
|
|
Content: f.Content,
|
|
UpdatedAt: f.UpdatedAt.UTC().Format("2006-01-02T15:04:05Z"),
|
|
}
|
|
}
|
|
|
|
// PermissionRequestDTO 是权限请求的对外视图。
|
|
type PermissionRequestDTO struct {
|
|
ID string `json:"id"`
|
|
SessionID string `json:"session_id"`
|
|
ToolName string `json:"tool_name"`
|
|
ToolCall json.RawMessage `json:"tool_call"`
|
|
Options json.RawMessage `json:"options"`
|
|
Status string `json:"status"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
func permissionRequestToDTO(p *PermissionRequest) PermissionRequestDTO {
|
|
return PermissionRequestDTO{
|
|
ID: p.ID.String(),
|
|
SessionID: p.SessionID.String(),
|
|
ToolName: p.ToolName,
|
|
ToolCall: json.RawMessage(p.ToolCall),
|
|
Options: json.RawMessage(p.Options),
|
|
Status: string(p.Status),
|
|
CreatedAt: p.CreatedAt.Format(time.RFC3339),
|
|
}
|
|
}
|
|
|
|
// CreateSessionReq is the request body for POST /api/v1/acp/sessions.
|
|
type CreateSessionReq struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
AgentKindID string `json:"agent_kind_id"`
|
|
Branch *string `json:"branch,omitempty"`
|
|
IssueNumber *int `json:"issue_number,omitempty"`
|
|
RequirementNumber *int `json:"requirement_number,omitempty"`
|
|
InitialPrompt *string `json:"initial_prompt,omitempty"`
|
|
}
|
|
|
|
// SessionDTO is the response shape for sessions.
|
|
type SessionDTO struct {
|
|
ID string `json:"id"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
ProjectID string `json:"project_id"`
|
|
AgentKindID string `json:"agent_kind_id"`
|
|
UserID string `json:"user_id"`
|
|
IssueID *string `json:"issue_id"`
|
|
RequirementID *string `json:"requirement_id"`
|
|
AgentSessionID *string `json:"agent_session_id"`
|
|
Branch string `json:"branch"`
|
|
CwdPath string `json:"cwd_path"`
|
|
IsMainWorktree bool `json:"is_main_worktree"`
|
|
Status string `json:"status"`
|
|
PID *int32 `json:"pid"`
|
|
ExitCode *int32 `json:"exit_code"`
|
|
LastError *string `json:"last_error"`
|
|
StartedAt string `json:"started_at"`
|
|
EndedAt *string `json:"ended_at"`
|
|
// 成本核算:运行时累计 + 有效预算上限 + 终止原因。
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens"`
|
|
TotalCostUSD float64 `json:"total_cost_usd"`
|
|
BudgetMaxCostUSD *float64 `json:"budget_max_cost_usd"`
|
|
BudgetMaxTokens *int64 `json:"budget_max_tokens"`
|
|
BudgetMaxWallClockSeconds *int32 `json:"budget_max_wall_clock_seconds"`
|
|
TerminatedReason *string `json:"terminated_reason"`
|
|
}
|
|
|
|
func sessionToDTO(s *Session) SessionDTO {
|
|
var issueID, reqID, ended *string
|
|
if s.IssueID != nil {
|
|
v := s.IssueID.String()
|
|
issueID = &v
|
|
}
|
|
if s.RequirementID != nil {
|
|
v := s.RequirementID.String()
|
|
reqID = &v
|
|
}
|
|
if s.EndedAt != nil {
|
|
v := s.EndedAt.UTC().Format("2006-01-02T15:04:05Z")
|
|
ended = &v
|
|
}
|
|
return SessionDTO{
|
|
ID: s.ID.String(),
|
|
WorkspaceID: s.WorkspaceID.String(),
|
|
ProjectID: s.ProjectID.String(),
|
|
AgentKindID: s.AgentKindID.String(),
|
|
UserID: s.UserID.String(),
|
|
IssueID: issueID,
|
|
RequirementID: reqID,
|
|
AgentSessionID: s.AgentSessionID,
|
|
Branch: s.Branch,
|
|
CwdPath: s.CwdPath,
|
|
IsMainWorktree: s.IsMainWorktree,
|
|
Status: string(s.Status),
|
|
PID: s.PID,
|
|
ExitCode: s.ExitCode,
|
|
LastError: s.LastError,
|
|
StartedAt: s.StartedAt.UTC().Format("2006-01-02T15:04:05Z"),
|
|
EndedAt: ended,
|
|
|
|
PromptTokens: s.PromptTokens,
|
|
CompletionTokens: s.CompletionTokens,
|
|
ThinkingTokens: s.ThinkingTokens,
|
|
TotalCostUSD: s.TotalCostUSD,
|
|
BudgetMaxCostUSD: s.BudgetMaxCostUSD,
|
|
BudgetMaxTokens: s.BudgetMaxTokens,
|
|
BudgetMaxWallClockSeconds: s.BudgetMaxWallClockSeconds,
|
|
TerminatedReason: s.TerminatedReason,
|
|
}
|
|
}
|
|
|
|
// SessionUsageDTO 是单条 per-turn 账目的对外视图。
|
|
type SessionUsageDTO struct {
|
|
ID int64 `json:"id"`
|
|
ModelID *string `json:"model_id"`
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// SessionUsageResponse 是 GET /sessions/{id}/usage 的响应:会话累计 + 最近账目。
|
|
type SessionUsageResponse struct {
|
|
SessionID string `json:"session_id"`
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens"`
|
|
TotalCostUSD float64 `json:"total_cost_usd"`
|
|
BudgetMaxCostUSD *float64 `json:"budget_max_cost_usd"`
|
|
BudgetMaxTokens *int64 `json:"budget_max_tokens"`
|
|
Recent []SessionUsageDTO `json:"recent"`
|
|
}
|
|
|
|
// AcpUsageItem 是 admin ACP 用量聚合的一行(镜像 chat AdminUsageItem)。
|
|
type AcpUsageItem struct {
|
|
Key string `json:"key"`
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
}
|
|
|
|
// AcpUsageResponse 包裹分组用量项。
|
|
type AcpUsageResponse struct {
|
|
Items []AcpUsageItem `json:"items"`
|
|
}
|
|
|
|
// ===== run-history dashboard DTO =====
|
|
|
|
// DashboardResponse 是 GET /api/v1/acp/dashboard 的响应:总览 + 按天序列。
|
|
type DashboardResponse struct {
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Rollup SessionRollupDTO `json:"rollup"`
|
|
ByDay []SessionDayDTO `json:"by_day"`
|
|
}
|
|
|
|
// SessionRollupDTO 是会话总览(计数 + 成功率 + 成本 + 平均时长)。
|
|
type SessionRollupDTO struct {
|
|
Total int64 `json:"total"`
|
|
Succeeded int64 `json:"succeeded"`
|
|
Crashed int64 `json:"crashed"`
|
|
Active int64 `json:"active"`
|
|
SuccessRate float64 `json:"success_rate"`
|
|
TotalCostUSD float64 `json:"total_cost_usd"`
|
|
TotalTokens int64 `json:"total_tokens"`
|
|
AvgDurationSeconds float64 `json:"avg_duration_seconds"`
|
|
}
|
|
|
|
// SessionDayDTO 是按天的时间序列一行。
|
|
type SessionDayDTO struct {
|
|
Day string `json:"day"`
|
|
Total int64 `json:"total"`
|
|
Succeeded int64 `json:"succeeded"`
|
|
Crashed int64 `json:"crashed"`
|
|
TotalCostUSD float64 `json:"total_cost_usd"`
|
|
}
|
|
|
|
// SessionTimelineDTO 是单会话事件时间线一行(按 method/kind 分桶)。
|
|
type SessionTimelineDTO struct {
|
|
Direction string `json:"direction"`
|
|
Kind string `json:"kind"`
|
|
Method string `json:"method"`
|
|
EventCount int64 `json:"event_count"`
|
|
FirstAt string `json:"first_at"`
|
|
LastAt string `json:"last_at"`
|
|
}
|
|
|
|
func dashboardToResponse(r *DashboardResult) DashboardResponse {
|
|
resp := DashboardResponse{
|
|
From: r.From.UTC().Format(time.RFC3339),
|
|
To: r.To.UTC().Format(time.RFC3339),
|
|
Rollup: SessionRollupDTO{
|
|
Total: r.Rollup.Total,
|
|
Succeeded: r.Rollup.Succeeded,
|
|
Crashed: r.Rollup.Crashed,
|
|
Active: r.Rollup.Active,
|
|
SuccessRate: r.Rollup.SuccessRate(),
|
|
TotalCostUSD: r.Rollup.TotalCostUSD,
|
|
TotalTokens: r.Rollup.TotalTokens,
|
|
AvgDurationSeconds: r.Rollup.AvgDurationSeconds,
|
|
},
|
|
ByDay: make([]SessionDayDTO, 0, len(r.ByDay)),
|
|
}
|
|
for _, d := range r.ByDay {
|
|
resp.ByDay = append(resp.ByDay, SessionDayDTO{
|
|
Day: d.Day.UTC().Format(time.RFC3339),
|
|
Total: d.Total,
|
|
Succeeded: d.Succeeded,
|
|
Crashed: d.Crashed,
|
|
TotalCostUSD: d.TotalCostUSD,
|
|
})
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func sessionTimelineToDTO(b SessionTimelineBucket) SessionTimelineDTO {
|
|
return SessionTimelineDTO{
|
|
Direction: b.Direction,
|
|
Kind: b.RPCKind,
|
|
Method: b.Method,
|
|
EventCount: b.EventCount,
|
|
FirstAt: b.FirstAt.UTC().Format(time.RFC3339),
|
|
LastAt: b.LastAt.UTC().Format(time.RFC3339),
|
|
}
|
|
}
|
|
|
|
func sessionUsageToDTO(r *SessionUsageRecord) SessionUsageDTO {
|
|
var modelID *string
|
|
if r.ModelID != nil {
|
|
v := r.ModelID.String()
|
|
modelID = &v
|
|
}
|
|
return SessionUsageDTO{
|
|
ID: r.ID,
|
|
ModelID: modelID,
|
|
PromptTokens: r.PromptTokens,
|
|
CompletionTokens: r.CompletionTokens,
|
|
ThinkingTokens: r.ThinkingTokens,
|
|
CostUSD: r.CostUSD,
|
|
CreatedAt: r.CreatedAt.UTC().Format(time.RFC3339),
|
|
}
|
|
}
|