352 lines
13 KiB
Go
352 lines
13 KiB
Go
// Package eventwire defines the shared frontend JSON contract for event.Event.
|
|
package eventwire
|
|
|
|
import (
|
|
"reasonix/internal/event"
|
|
"reasonix/internal/provider"
|
|
)
|
|
|
|
// Event is the JSON-friendly form shared by event frontends.
|
|
type Event struct {
|
|
Kind string `json:"kind"`
|
|
Text string `json:"text,omitempty"`
|
|
Detail string `json:"detail,omitempty"`
|
|
Code string `json:"code,omitempty"`
|
|
Reasoning string `json:"reasoning,omitempty"`
|
|
MemoryCitations []MemoryCitation `json:"memoryCitations,omitempty"`
|
|
MemoryCompiler *MemoryCompiler `json:"memoryCompiler,omitempty"`
|
|
Level string `json:"level,omitempty"`
|
|
Tool *Tool `json:"tool,omitempty"`
|
|
Usage *Usage `json:"usage,omitempty"`
|
|
Approval *Approval `json:"approval,omitempty"`
|
|
Ask *Ask `json:"ask,omitempty"`
|
|
Compaction *Compaction `json:"compaction,omitempty"`
|
|
Guardian *Guardian `json:"guardian,omitempty"`
|
|
Err string `json:"err,omitempty"`
|
|
Outcome string `json:"outcome,omitempty"`
|
|
RetryAttempt int `json:"retryAttempt,omitempty"`
|
|
RetryMax int `json:"retryMax,omitempty"`
|
|
}
|
|
|
|
// ToWire converts a typed runtime event into the shared frontend JSON contract.
|
|
func ToWire(e event.Event) Event {
|
|
w := Event{Kind: kindNames[e.Kind], Text: e.Text, Detail: e.Detail, Reasoning: e.Reasoning}
|
|
if len(e.MemoryCitations) > 0 {
|
|
w.MemoryCitations = ToWireMemoryCitations(e.MemoryCitations)
|
|
}
|
|
switch e.Kind {
|
|
case event.Notice:
|
|
w.Code = e.Code
|
|
if e.Level == event.LevelWarn {
|
|
w.Level = "warn"
|
|
} else {
|
|
w.Level = "info"
|
|
}
|
|
case event.ToolDispatch, event.ToolResult, event.ToolProgress:
|
|
wt := &Tool{
|
|
ID: e.Tool.ID, Name: e.Tool.Name, Args: e.Tool.Args,
|
|
Output: e.Tool.Output, Err: e.Tool.Err,
|
|
ReadOnly: e.Tool.ReadOnly, Truncated: e.Tool.Truncated,
|
|
DurationMs: e.Tool.DurationMs, Partial: e.Tool.Partial,
|
|
ArgChars: e.Tool.ArgChars,
|
|
ParentID: e.Tool.ParentID,
|
|
Diff: e.Tool.Diff, Added: e.Tool.Added, Removed: e.Tool.Removed,
|
|
}
|
|
if e.Tool.Profile != nil {
|
|
wt.Profile = &Profile{Model: e.Tool.Profile.Model, Effort: e.Tool.Profile.Effort}
|
|
}
|
|
w.Tool = wt
|
|
case event.Usage:
|
|
if u := e.Usage; u != nil {
|
|
w.Usage = &Usage{
|
|
PromptTokens: u.PromptTokens, CompletionTokens: u.CompletionTokens,
|
|
TotalTokens: u.TotalTokens, CacheHitTokens: u.CacheHitTokens,
|
|
CacheMissTokens: u.CacheMissTokens, ReasoningTokens: u.ReasoningTokens,
|
|
Source: e.UsageSource,
|
|
SessionCacheHitTokens: e.SessionHit, SessionCacheMissTokens: e.SessionMiss,
|
|
}
|
|
if e.CacheDiagnostics != nil {
|
|
w.Usage.CacheDiagnostics = ToWireCacheDiagnostics(e.CacheDiagnostics)
|
|
}
|
|
if e.Pricing != nil {
|
|
cost := e.Pricing.Cost(u)
|
|
w.Usage.Cost = cost
|
|
w.Usage.Currency = e.Pricing.Symbol()
|
|
w.Usage.CostUSD = cost
|
|
}
|
|
}
|
|
case event.MemoryCompilerStatsEvent:
|
|
if m := e.MemoryCompiler; m != nil {
|
|
w.MemoryCompiler = &MemoryCompiler{
|
|
Injected: m.Injected,
|
|
UsefulIR: m.UsefulIR,
|
|
CompiledTokens: m.CompiledTokens,
|
|
IROverheadTokens: m.IROverheadTokens,
|
|
MemoryReferences: m.MemoryReferences,
|
|
Constraints: m.Constraints,
|
|
RiskNotes: m.RiskNotes,
|
|
ExecutionSteps: m.ExecutionSteps,
|
|
TotalNodes: m.TotalNodes,
|
|
HighSignalNodes: m.HighSignalNodes,
|
|
ToolResultNodes: m.ToolResultNodes,
|
|
DecisionNodes: m.DecisionNodes,
|
|
StrategyCount: m.StrategyCount,
|
|
LearningCount: m.LearningCount,
|
|
}
|
|
}
|
|
case event.ApprovalRequest:
|
|
w.Approval = &Approval{ID: e.Approval.ID, Tool: e.Approval.Tool, Subject: e.Approval.Subject, Reason: e.Approval.Reason}
|
|
case event.AskRequest:
|
|
w.Ask = ToWireAsk(e.Ask)
|
|
case event.CompactionStarted, event.CompactionDone:
|
|
w.Compaction = &Compaction{
|
|
Trigger: e.Compaction.Trigger, Messages: e.Compaction.Messages,
|
|
Summary: e.Compaction.Summary, Archive: e.Compaction.Archive,
|
|
}
|
|
case event.GuardianAssessment:
|
|
w.Guardian = ToWireGuardian(e.Guardian)
|
|
case event.TurnDone:
|
|
w.Outcome = e.Outcome
|
|
if e.Err != nil {
|
|
w.Err = e.Err.Error()
|
|
}
|
|
case event.Retrying:
|
|
w.RetryAttempt = e.RetryAttempt
|
|
w.RetryMax = e.RetryMax
|
|
}
|
|
return w
|
|
}
|
|
|
|
// MemoryCitation is the JSON form of provider.MemoryCitation.
|
|
type MemoryCitation struct {
|
|
ID string `json:"id,omitempty"`
|
|
Source string `json:"source"`
|
|
LineStart int `json:"lineStart,omitempty"`
|
|
LineEnd int `json:"lineEnd,omitempty"`
|
|
Note string `json:"note,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
}
|
|
|
|
// MemoryCompiler is the JSON form of content-free Memory v5 usage metrics.
|
|
type MemoryCompiler struct {
|
|
Injected bool `json:"injected"`
|
|
UsefulIR bool `json:"usefulIR"`
|
|
CompiledTokens int `json:"compiledTokens"`
|
|
IROverheadTokens int `json:"irOverheadTokens"`
|
|
MemoryReferences int `json:"memoryReferences"`
|
|
Constraints int `json:"constraints"`
|
|
RiskNotes int `json:"riskNotes"`
|
|
ExecutionSteps int `json:"executionSteps"`
|
|
TotalNodes int `json:"totalNodes"`
|
|
HighSignalNodes int `json:"highSignalNodes"`
|
|
ToolResultNodes int `json:"toolResultNodes"`
|
|
DecisionNodes int `json:"decisionNodes"`
|
|
StrategyCount int `json:"strategyCount"`
|
|
LearningCount int `json:"learningCount"`
|
|
}
|
|
|
|
// ToWireMemoryCitations converts local memory references into frontend JSON.
|
|
func ToWireMemoryCitations(in []provider.MemoryCitation) []MemoryCitation {
|
|
out := make([]MemoryCitation, 0, len(in))
|
|
for _, c := range in {
|
|
if c.Source == "" && c.ID == "" && c.Note == "" {
|
|
continue
|
|
}
|
|
out = append(out, MemoryCitation{
|
|
ID: c.ID,
|
|
Source: c.Source,
|
|
LineStart: c.LineStart,
|
|
LineEnd: c.LineEnd,
|
|
Note: c.Note,
|
|
Kind: c.Kind,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Compaction is the JSON form of an event.Compaction.
|
|
type Compaction struct {
|
|
Trigger string `json:"trigger,omitempty"`
|
|
Messages int `json:"messages,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
Archive string `json:"archive,omitempty"`
|
|
}
|
|
|
|
// AskOption is one JSON-formatted choice in a structured ask request.
|
|
type AskOption struct {
|
|
Label string `json:"label"`
|
|
Description string `json:"description,omitempty"`
|
|
}
|
|
|
|
// AskQuestion is one JSON-formatted structured ask question.
|
|
type AskQuestion struct {
|
|
ID string `json:"id"`
|
|
Header string `json:"header,omitempty"`
|
|
Prompt string `json:"prompt"`
|
|
Options []AskOption `json:"options"`
|
|
Multi bool `json:"multi,omitempty"`
|
|
}
|
|
|
|
// Ask is the JSON form of an event.Ask.
|
|
type Ask struct {
|
|
ID string `json:"id"`
|
|
Questions []AskQuestion `json:"questions"`
|
|
}
|
|
|
|
// Profile carries the subagent model/effort resolved for a tool call.
|
|
type Profile struct {
|
|
Model string `json:"model,omitempty"`
|
|
Effort string `json:"effort,omitempty"`
|
|
}
|
|
|
|
// Tool is the JSON form of an event.Tool.
|
|
type Tool struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name"`
|
|
Args string `json:"args,omitempty"`
|
|
Output string `json:"output,omitempty"`
|
|
Err string `json:"err,omitempty"`
|
|
ReadOnly bool `json:"readOnly"`
|
|
Truncated bool `json:"truncated,omitempty"`
|
|
DurationMs int64 `json:"durationMs,omitempty"`
|
|
Partial bool `json:"partial,omitempty"`
|
|
ArgChars int `json:"argChars,omitempty"`
|
|
ParentID string `json:"parentId,omitempty"`
|
|
Diff string `json:"diff,omitempty"`
|
|
Added int `json:"added,omitempty"`
|
|
Removed int `json:"removed,omitempty"`
|
|
Profile *Profile `json:"profile,omitempty"`
|
|
}
|
|
|
|
// Usage is the JSON form of provider usage telemetry.
|
|
type Usage struct {
|
|
PromptTokens int `json:"promptTokens"`
|
|
CompletionTokens int `json:"completionTokens"`
|
|
TotalTokens int `json:"totalTokens"`
|
|
CacheHitTokens int `json:"cacheHitTokens"`
|
|
CacheMissTokens int `json:"cacheMissTokens"`
|
|
ReasoningTokens int `json:"reasoningTokens,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
CacheDiagnostics *CacheDiagnostics `json:"cacheDiagnostics,omitempty"`
|
|
// Session-cumulative cache tokens keep status displays steadier than one-turn values.
|
|
SessionCacheHitTokens int `json:"sessionCacheHitTokens"`
|
|
SessionCacheMissTokens int `json:"sessionCacheMissTokens"`
|
|
Cost float64 `json:"cost,omitempty"`
|
|
Currency string `json:"currency,omitempty"`
|
|
// CostUSD is a compatibility alias for older consumers; it mirrors Cost.
|
|
CostUSD float64 `json:"costUsd,omitempty"`
|
|
}
|
|
|
|
// CacheDiagnostics is the JSON form of cache prefix diagnostics.
|
|
type CacheDiagnostics struct {
|
|
PrefixHash string `json:"prefixHash"`
|
|
PrefixChanged bool `json:"prefixChanged"`
|
|
PrefixChangeReasons []string `json:"prefixChangeReasons,omitempty"`
|
|
SystemHash string `json:"systemHash"`
|
|
ToolsHash string `json:"toolsHash"`
|
|
LogRewriteVersion int `json:"logRewriteVersion"`
|
|
ToolSchemaTokens int `json:"toolSchemaTokens"`
|
|
CacheMissTokens int `json:"cacheMissTokens"`
|
|
CacheHitTokens int `json:"cacheHitTokens"`
|
|
}
|
|
|
|
// Approval is the JSON form of an event.Approval.
|
|
type Approval struct {
|
|
ID string `json:"id"`
|
|
Tool string `json:"tool"`
|
|
Subject string `json:"subject"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
// Guardian is the JSON form of an event.GuardianResult.
|
|
type Guardian struct {
|
|
ID string `json:"id"`
|
|
Tool string `json:"tool"`
|
|
Subject string `json:"subject"`
|
|
Outcome string `json:"outcome"`
|
|
RiskLevel string `json:"risk_level,omitempty"`
|
|
UserAuthorization string `json:"user_authorization,omitempty"`
|
|
Rationale string `json:"rationale,omitempty"`
|
|
DurationMs int64 `json:"duration_ms,omitempty"`
|
|
Usage *Usage `json:"usage,omitempty"`
|
|
}
|
|
|
|
// ToWireGuardian converts an event.GuardianResult into its JSON wire form.
|
|
func ToWireGuardian(g event.GuardianResult) *Guardian {
|
|
out := &Guardian{
|
|
ID: g.ID,
|
|
Tool: g.Tool,
|
|
Subject: g.Subject,
|
|
Outcome: g.Outcome,
|
|
RiskLevel: g.RiskLevel,
|
|
UserAuthorization: g.UserAuthorization,
|
|
Rationale: g.Rationale,
|
|
DurationMs: g.DurationMs,
|
|
}
|
|
if u := g.Usage; u != nil {
|
|
out.Usage = &Usage{
|
|
PromptTokens: u.PromptTokens, CompletionTokens: u.CompletionTokens,
|
|
TotalTokens: u.TotalTokens, CacheHitTokens: u.CacheHitTokens,
|
|
CacheMissTokens: u.CacheMissTokens, ReasoningTokens: u.ReasoningTokens,
|
|
}
|
|
if g.Pricing != nil {
|
|
cost := g.Pricing.Cost(u)
|
|
out.Usage.Cost = cost
|
|
out.Usage.Currency = g.Pricing.Symbol()
|
|
out.Usage.CostUSD = cost
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ToWireAsk converts an event.Ask into its JSON wire form.
|
|
func ToWireAsk(a event.Ask) *Ask {
|
|
qs := make([]AskQuestion, len(a.Questions))
|
|
for i, q := range a.Questions {
|
|
opts := make([]AskOption, len(q.Options))
|
|
for j, o := range q.Options {
|
|
opts[j] = AskOption{Label: o.Label, Description: o.Description}
|
|
}
|
|
qs[i] = AskQuestion{ID: q.ID, Header: q.Header, Prompt: q.Prompt, Options: opts, Multi: q.Multi}
|
|
}
|
|
return &Ask{ID: a.ID, Questions: qs}
|
|
}
|
|
|
|
// ToWireCacheDiagnostics converts cache diagnostics into their JSON wire form.
|
|
func ToWireCacheDiagnostics(d *event.CacheDiagnostics) *CacheDiagnostics {
|
|
return &CacheDiagnostics{
|
|
PrefixHash: d.PrefixHash,
|
|
PrefixChanged: d.PrefixChanged,
|
|
PrefixChangeReasons: append([]string(nil), d.PrefixChangeReasons...),
|
|
SystemHash: d.SystemHash,
|
|
ToolsHash: d.ToolsHash,
|
|
LogRewriteVersion: d.LogRewriteVersion,
|
|
ToolSchemaTokens: d.ToolSchemaTokens,
|
|
CacheMissTokens: d.CacheMissTokens,
|
|
CacheHitTokens: d.CacheHitTokens,
|
|
}
|
|
}
|
|
|
|
var kindNames = map[event.Kind]string{
|
|
event.TurnStarted: "turn_started",
|
|
event.Reasoning: "reasoning",
|
|
event.Text: "text",
|
|
event.Message: "message",
|
|
event.ToolDispatch: "tool_dispatch",
|
|
event.ToolResult: "tool_result",
|
|
event.Usage: "usage",
|
|
event.Notice: "notice",
|
|
event.Phase: "phase",
|
|
event.ApprovalRequest: "approval_request",
|
|
event.AskRequest: "ask_request",
|
|
event.TurnDone: "turn_done",
|
|
event.CompactionStarted: "compaction_started",
|
|
event.CompactionDone: "compaction_done",
|
|
event.ToolProgress: "tool_progress",
|
|
event.MCPSurfaceReady: "mcp_surface_ready",
|
|
event.Retrying: "retrying",
|
|
event.Steer: "steer",
|
|
event.MemoryCompilerStatsEvent: "memory_compiler_stats",
|
|
event.GuardianAssessment: "guardian_assessment",
|
|
}
|