Refactor AI Agent configuration and workflow handling

- Removed runtime mode handling from AIAgentConfigWorkbench and related components.
- Updated tests to reflect changes in AI Agent policy copy and configuration.
- Changed terminology from "workflow" to "revision" in various components and API responses.
- Simplified agent binding logic in channel editing.
- Cleaned up unused variables and types related to runtime modes.
- Updated localization files for consistency with new terminology.
This commit is contained in:
mlogclub
2026-07-27 23:29:02 +08:00
parent 241f274927
commit 847f688398
97 changed files with 1666 additions and 5941 deletions
+5 -17
View File
@@ -49,7 +49,7 @@ func DebugRunSkill(ctx context.Context, req request.SkillDebugRunRequest) (*resp
MessageType: enums.IMMessageTypeText,
Content: strings.TrimSpace(req.UserMessage),
}
summary, err := applicationruntime.DefaultAgentApplicationService.RunPrepared(ctx, applicationruntime.Request{
summary, err := applicationruntime.DefaultAgentApplicationService.RunPrepared(ctx, applicationruntime.RunInput{
Conversation: *conversation,
UserMessage: message,
AIAgent: debugAgent,
@@ -93,7 +93,7 @@ func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest)
return nil, errorsx.InvalidParamI18n("error.e0117")
}
resumeText := strings.TrimSpace(req.UserMessage)
summary, err := applicationruntime.DefaultAgentApplicationService.ResumePrepared(ctx, applicationruntime.ResumeRequest{
summary, err := applicationruntime.DefaultAgentApplicationService.ResumePrepared(ctx, applicationruntime.ResumeInput{
Conversation: *conversation,
AIAgent: *aiAgent,
AIConfig: *aiConfig,
@@ -105,7 +105,7 @@ func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest)
})
if err != nil {
if isCheckpointMissingError(err) {
summary = &applicationruntime.Summary{
summary = &applicationruntime.RunResult{
Status: "expired",
ReplyText: graphs.ConfirmationExpiredReply,
}
@@ -128,7 +128,7 @@ func DebugResumeSkill(ctx context.Context, req request.SkillDebugResumeRequest)
return buildSkillDebugResumeResponse(req, summary, conversationID), nil
}
func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *applicationruntime.Summary, skill *models.SkillDefinition) *response.SkillDebugRunResponse {
func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *applicationruntime.RunResult, skill *models.SkillDefinition) *response.SkillDebugRunResponse {
resp := &response.SkillDebugRunResponse{
ConversationID: req.ConversationID,
AIAgentID: req.AIAgentID,
@@ -144,14 +144,8 @@ func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *appli
resp.SkillDefinitionID = summary.PlannedSkillID
}
resp.ReplyText = summary.ReplyText
resp.PlanReason = summary.PlanReason
resp.SkillRouteTrace = summary.SkillRouteTrace
resp.ToolWhitelist = append([]string(nil), summary.SkillAllowedToolCodes...)
resp.ExposedToolCodes = append([]string(nil), summary.ToolCodes...)
resp.InvokedToolCodes = append([]string(nil), summary.InvokedToolCodes...)
resp.ToolSearchTrace = extractToolSearchTrace(summary)
resp.GraphToolTrace = extractGraphToolTrace(summary)
resp.GraphToolCode = firstGraphToolCode(summary)
resp.InterruptType = firstInterruptType(summary)
resp.CheckPointID = summary.CheckPointID
resp.Interrupted = summary.Interrupted
@@ -160,7 +154,7 @@ func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *appli
return resp
}
func buildSkillDebugResumeResponse(req request.SkillDebugResumeRequest, summary *applicationruntime.Summary, conversationID int64) *response.SkillDebugRunResponse {
func buildSkillDebugResumeResponse(req request.SkillDebugResumeRequest, summary *applicationruntime.RunResult, conversationID int64) *response.SkillDebugRunResponse {
resp := &response.SkillDebugRunResponse{
ConversationID: conversationID,
AIAgentID: req.AIAgentID,
@@ -171,14 +165,8 @@ func buildSkillDebugResumeResponse(req request.SkillDebugResumeRequest, summary
resp.SkillDefinitionID = summary.PlannedSkillID
resp.SkillName = strings.TrimSpace(summary.PlannedSkillName)
resp.ReplyText = summary.ReplyText
resp.PlanReason = summary.PlanReason
resp.SkillRouteTrace = summary.SkillRouteTrace
resp.ToolWhitelist = append([]string(nil), summary.SkillAllowedToolCodes...)
resp.ExposedToolCodes = append([]string(nil), summary.ToolCodes...)
resp.InvokedToolCodes = append([]string(nil), summary.InvokedToolCodes...)
resp.ToolSearchTrace = extractToolSearchTrace(summary)
resp.GraphToolTrace = extractGraphToolTrace(summary)
resp.GraphToolCode = firstGraphToolCode(summary)
resp.InterruptType = firstInterruptType(summary)
resp.CheckPointID = summary.CheckPointID
resp.Interrupted = summary.Interrupted
+3 -3
View File
@@ -28,7 +28,7 @@ func RunAgentEvaluation(ctx context.Context, req request.RunAgentEvaluationReque
for _, item := range req.Cases {
cases = append(cases, applicationruntime.OfflineEvaluationCase{ID: item.ID, Category: item.Category, Message: item.Message, History: item.History, Expect: item.Expect})
}
report, err := applicationruntime.NewService().RunOfflineEvaluation(ctx, req.EngineCode, *agent, *config, cases)
report, err := applicationruntime.NewService().RunOfflineEvaluation(ctx, *agent, *config, cases)
if err != nil {
return nil, err
}
@@ -36,9 +36,9 @@ func RunAgentEvaluation(ctx context.Context, req request.RunAgentEvaluationReque
if err != nil {
return nil, err
}
ret := &response.AgentEvaluationReportResponse{EngineCode: report.EngineCode, Total: report.Total, Passed: report.Passed, CSV: csv, Results: make([]response.AgentEvaluationResultResponse, 0, len(report.Results))}
ret := &response.AgentEvaluationReportResponse{Total: report.Total, Passed: report.Passed, CSV: csv, Results: make([]response.AgentEvaluationResultResponse, 0, len(report.Results))}
for _, item := range report.Results {
ret.Results = append(ret.Results, response.AgentEvaluationResultResponse{CaseID: item.CaseID, Category: item.Category, EngineCode: item.EngineCode, Passed: item.Passed, ReplyText: item.ReplyText, Interrupted: item.Interrupted, Error: item.Error, Finding: item.Finding})
ret.Results = append(ret.Results, response.AgentEvaluationResultResponse{CaseID: item.CaseID, Category: item.Category, Passed: item.Passed, ReplyText: item.ReplyText, Interrupted: item.Interrupted, Error: item.Error, Finding: item.Finding})
}
return ret, nil
}
+2 -2
View File
@@ -9,11 +9,11 @@ type aiReplyContext struct {
Conversation models.Conversation
Message models.Message
AIAgent models.AIAgent
SummaryRef **applicationruntime.Summary
SummaryRef **applicationruntime.RunResult
PendingInterrupt *models.ConversationInterrupt
}
func (c aiReplyContext) setSummary(summary *applicationruntime.Summary) {
func (c aiReplyContext) setSummary(summary *applicationruntime.RunResult) {
if c.SummaryRef != nil {
*c.SummaryRef = summary
}
+1 -20
View File
@@ -5,27 +5,8 @@ import (
applicationruntime "agent-desk/internal/ai/application/runtime"
"agent-desk/internal/models"
"agent-desk/internal/pkg/toolx"
)
func TestExtractRuntimeToolTraces(t *testing.T) {
summary := &applicationruntime.Summary{
TraceData: `{
"toolSearch": {"items": [{"targetToolCode":"mcp/server/tool_a"}]},
"graphTools": {"items": [{"toolCode":"` + toolx.GraphAnalyzeConversation.Code + `"}]}
}`,
}
if got := extractToolSearchTrace(summary); got == "" {
t.Fatalf("expected tool search trace")
}
if got := extractGraphToolTrace(summary); got == "" {
t.Fatalf("expected graph tool trace")
}
if got := firstGraphToolCode(summary); got != toolx.GraphAnalyzeConversation.Code {
t.Fatalf("unexpected graph tool code: %q", got)
}
}
func TestExtractInterruptMessageAndCheckpointError(t *testing.T) {
if got := extractInterruptMessage(`{"message":"请补充订单号"}`); got != "请补充订单号" {
t.Fatalf("unexpected interrupt message: %q", got)
@@ -44,7 +25,7 @@ func TestExtractInterruptMessageAndCheckpointError(t *testing.T) {
}
func TestBuildConversationInterruptStoresWorkflowCheckpointData(t *testing.T) {
item := buildConversationInterrupt(testConversation(1), testMessage(2), testAIAgent(3), &applicationruntime.Summary{
item := buildConversationInterrupt(testConversation(1), testMessage(2), testAIAgent(3), &applicationruntime.RunResult{
CheckPointData: `{"confirmNodeId":"confirm_1"}`,
Interrupted: true,
WorkflowRunID: 99,
@@ -15,7 +15,7 @@ type interruptMessagePreview struct {
Message string `json:"message"`
}
func buildConversationInterrupt(conversation models.Conversation, message models.Message, aiAgent models.AIAgent, summary *applicationruntime.Summary) *models.ConversationInterrupt {
func buildConversationInterrupt(conversation models.Conversation, message models.Message, aiAgent models.AIAgent, summary *applicationruntime.RunResult) *models.ConversationInterrupt {
if summary == nil {
return nil
}
@@ -42,7 +42,7 @@ func buildConversationInterrupt(conversation models.Conversation, message models
return item
}
func resolveInterruptPrompt(summary *applicationruntime.Summary) string {
func resolveInterruptPrompt(summary *applicationruntime.RunResult) string {
if summary == nil || len(summary.Interrupts) == 0 {
return i18nx.Get("conversation.interrupt.defaultPrompt")
}
@@ -67,14 +67,14 @@ func extractInterruptMessage(infoPreview string) string {
return strings.TrimSpace(payload.Message)
}
func firstInterruptID(summary *applicationruntime.Summary) string {
func firstInterruptID(summary *applicationruntime.RunResult) string {
if summary == nil || len(summary.Interrupts) == 0 {
return ""
}
return strings.TrimSpace(summary.Interrupts[0].ID)
}
func firstInterruptType(summary *applicationruntime.Summary) string {
func firstInterruptType(summary *applicationruntime.RunResult) string {
if summary == nil || len(summary.Interrupts) == 0 {
return ""
}
@@ -80,7 +80,7 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne
return svc.ConversationInterruptService.MarkResolved(replyCtx.PendingInterrupt.ID, 0)
}
func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.Summary) error {
func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.RunResult) error {
pending := buildConversationInterrupt(replyCtx.Conversation, replyCtx.Message, replyCtx.AIAgent, summary)
if pending != nil && pending.AgentRunID > 0 {
pending.AgentStepID = svc.AgentRunService.GetLatestStepID(pending.AgentRunID)
@@ -107,7 +107,7 @@ func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService,
return nil
}
func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.Summary) error {
func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.RunResult) error {
if replyCtx.PendingInterrupt == nil {
return fmt.Errorf("pending interrupt is required")
}
+1 -1
View File
@@ -29,7 +29,7 @@ type aiReplyService struct {
commit *replyCommitService
}
func firstInvokedToolCode(summary *applicationruntime.Summary) string {
func firstInvokedToolCode(summary *applicationruntime.RunResult) string {
if summary == nil {
return ""
}
+1 -1
View File
@@ -86,7 +86,7 @@ func TestResolveReplyTimeout(t *testing.T) {
}
func TestResolveInterruptPrompt(t *testing.T) {
summary := &applicationruntime.Summary{
summary := &applicationruntime.RunResult{
Interrupts: []applicationruntime.InterruptContextSummary{
{
ID: "interrupt-1",
+1 -1
View File
@@ -45,7 +45,7 @@ func (s *aiReplyService) TriggerReplyAsync(conversation models.Conversation, mes
}
func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.Conversation, message models.Message, aiAgent models.AIAgent) (retErr error) {
var summary *applicationruntime.Summary
var summary *applicationruntime.RunResult
replyCtx := aiReplyContext{
Conversation: conversation,
Message: message,
@@ -29,7 +29,7 @@ func newRuntimeReplyExecutor() *runtimeReplyExecutor {
return &runtimeReplyExecutor{}
}
func (e *runtimeReplyExecutor) Run(ctx context.Context, input runtimeReplyRunInput) (*applicationruntime.Summary, error) {
func (e *runtimeReplyExecutor) Run(ctx context.Context, input runtimeReplyRunInput) (*applicationruntime.RunResult, error) {
summary, err := applicationruntime.DefaultAgentApplicationService.Run(ctx, applicationruntime.ApplicationRunInput{
ConversationID: input.Conversation.ID,
MessageID: input.Message.ID,
@@ -38,7 +38,7 @@ func (e *runtimeReplyExecutor) Run(ctx context.Context, input runtimeReplyRunInp
return summary, err
}
func (e *runtimeReplyExecutor) ResumePendingInterrupt(ctx context.Context, input runtimeReplyResumeInput) (*applicationruntime.Summary, error) {
func (e *runtimeReplyExecutor) ResumePendingInterrupt(ctx context.Context, input runtimeReplyResumeInput) (*applicationruntime.RunResult, error) {
if input.PendingInterrupt == nil {
return nil, fmt.Errorf("pending interrupt is required")
}
@@ -56,8 +56,8 @@ func (e *runtimeReplyExecutor) ResumePendingInterrupt(ctx context.Context, input
return summary, err
}
func expiredInterruptSummary() *applicationruntime.Summary {
return &applicationruntime.Summary{
func expiredInterruptSummary() *applicationruntime.RunResult {
return &applicationruntime.RunResult{
Status: "expired",
ReplyText: graphs.ConfirmationExpiredReply,
}
@@ -1,82 +0,0 @@
package runtime
import (
"encoding/json"
"strings"
applicationruntime "agent-desk/internal/ai/application/runtime"
)
func extractToolSearchTrace(summary *applicationruntime.Summary) string {
if summary == nil {
return ""
}
trace := parseRuntimeTraceData(summary.TraceData)
if len(trace.ToolSearch.Items) == 0 {
return ""
}
buf, err := json.Marshal(trace.ToolSearch)
if err != nil {
return ""
}
return string(buf)
}
func extractGraphToolTrace(summary *applicationruntime.Summary) string {
if summary == nil {
return ""
}
trace := parseRuntimeTraceData(summary.TraceData)
if len(trace.GraphTools.Items) == 0 {
return ""
}
buf, err := json.Marshal(trace.GraphTools)
if err != nil {
return ""
}
return string(buf)
}
func firstGraphToolCode(summary *applicationruntime.Summary) string {
if summary == nil {
return ""
}
trace := parseRuntimeTraceData(summary.TraceData)
for _, item := range trace.GraphTools.Items {
toolCode := strings.TrimSpace(item.ToolCode)
if toolCode != "" {
return toolCode
}
}
return ""
}
type runtimeTraceProjection struct {
ToolSearch struct {
Items []struct {
TargetToolCode string `json:"targetToolCode"`
CandidateToolCodes []string `json:"candidateToolCodes"`
} `json:"items"`
} `json:"toolSearch"`
GraphTools struct {
Items []struct {
ToolCode string `json:"toolCode"`
Arguments json.RawMessage `json:"arguments"`
RecommendedAction string `json:"recommendedAction"`
RiskLevel string `json:"riskLevel"`
TicketDraftReady bool `json:"ticketDraftReady"`
} `json:"items"`
} `json:"graphTools"`
}
func parseRuntimeTraceData(raw string) runtimeTraceProjection {
raw = strings.TrimSpace(raw)
if raw == "" {
return runtimeTraceProjection{}
}
var trace runtimeTraceProjection
if err := json.Unmarshal([]byte(raw), &trace); err != nil {
return runtimeTraceProjection{}
}
return trace
}
+2 -2
View File
@@ -18,10 +18,10 @@ type service struct {
app *applicationruntime.Service
}
func (s *service) Run(ctx context.Context, req applicationruntime.Request) (*applicationruntime.Summary, error) {
func (s *service) Run(ctx context.Context, req applicationruntime.RunInput) (*applicationruntime.RunResult, error) {
return s.app.Run(ctx, req)
}
func (s *service) Resume(ctx context.Context, req applicationruntime.ResumeRequest) (*applicationruntime.Summary, error) {
func (s *service) Resume(ctx context.Context, req applicationruntime.ResumeInput) (*applicationruntime.RunResult, error) {
return s.app.Resume(ctx, req)
}
@@ -168,9 +168,8 @@ func (t *ToolSearchTool) invokeTargetTool(ctx context.Context, toolCode string,
if !containsToolCode(t.allowedToolCodes, toolCode) {
return "", i18nx.Errorf("error.e0279")
}
// A workflow administrator's allow-list is the explicit approval boundary
// for MCP tools. The registry still enforces its call limit and normalizes
// the safety metadata used by future autonomous engines.
// The published Agent allow-list is the approval boundary for MCP tools.
// The registry still enforces call limits and safety metadata.
_, result, err := aitooling.DefaultMCPExecutor.Execute(ctx, toolCode, arguments, aitooling.Policy{
AllowedToolCodes: t.allowedToolCodes,
Confirmed: true,
-142
View File
@@ -1,48 +1,5 @@
package traces
type ToolTraceItem struct {
ToolCode string `json:"toolCode"`
ServerCode string `json:"serverCode"`
ToolName string `json:"toolName"`
Arguments map[string]any `json:"arguments,omitempty"`
ResultPreview string `json:"resultPreview,omitempty"`
ResultReduced bool `json:"resultReduced,omitempty"`
OriginalChars int `json:"originalChars,omitempty"`
KeptChars int `json:"keptChars,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
Status string `json:"status,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Blocked bool `json:"blocked,omitempty"`
BlockedReason string `json:"blockedReason,omitempty"`
}
type ToolSearchTraceItem struct {
Action string `json:"action,omitempty"`
Query string `json:"query,omitempty"`
TargetToolCode string `json:"targetToolCode,omitempty"`
TargetServerCode string `json:"targetServerCode,omitempty"`
TargetToolName string `json:"targetToolName,omitempty"`
CandidateToolCodes []string `json:"candidateToolCodes,omitempty"`
Status string `json:"status,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
}
type GraphToolTraceItem struct {
ToolCode string `json:"toolCode"`
ToolName string `json:"toolName"`
Arguments map[string]any `json:"arguments,omitempty"`
ResultPreview string `json:"resultPreview,omitempty"`
ResultReduced bool `json:"resultReduced,omitempty"`
OriginalChars int `json:"originalChars,omitempty"`
KeptChars int `json:"keptChars,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
Status string `json:"status,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
RecommendedAction string `json:"recommendedAction,omitempty"`
RiskLevel string `json:"riskLevel,omitempty"`
TicketDraftReady bool `json:"ticketDraftReady,omitempty"`
}
type RetrieverTraceItem struct {
Query string `json:"query,omitempty"`
KnowledgeBaseID int64 `json:"knowledgeBaseId,omitempty"`
@@ -65,107 +22,8 @@ type RetrieverTraceSummary struct {
Policies []RetrieverPolicyTraceItem
}
type AnswerabilityTraceData struct {
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
SupportingChunkIDs []string `json:"supportingChunkIds,omitempty"`
MissingInfo []string `json:"missingInfo,omitempty"`
LatencyMs int64 `json:"latencyMs,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
}
type RetrieverPolicyTraceItem struct {
KnowledgeBaseID int64 `json:"knowledgeBaseId,omitempty"`
TopK int `json:"topK,omitempty"`
ScoreThreshold float64 `json:"scoreThreshold,omitempty"`
}
type InstructionTraceSummary struct {
SectionTitles []string
HasAgentRule bool
HasSkillRule bool
HasToolRule bool
}
type RuntimeTraceData struct {
Version string `json:"version"`
Status string `json:"status"`
RunID string `json:"runId,omitempty"`
Skill SkillTraceData `json:"skill,omitempty"`
Interrupt struct {
CheckPointID string `json:"checkPointId,omitempty"`
Items []InterruptTraceContext `json:"items,omitempty"`
} `json:"interrupt"`
Model struct {
Provider string `json:"provider,omitempty"`
Name string `json:"name,omitempty"`
} `json:"model"`
Instruction struct {
SectionTitles []string `json:"sectionTitles,omitempty"`
HasAgentRule bool `json:"hasAgentRule,omitempty"`
HasSkillRule bool `json:"hasSkillRule,omitempty"`
HasToolRule bool `json:"hasToolRule,omitempty"`
} `json:"instruction"`
Input struct {
HistoryMessageCount int `json:"historyMessageCount,omitempty"`
KnowledgeBaseIDs []int64 `json:"knowledgeBaseIds,omitempty"`
ToolCodes []string `json:"toolCodes,omitempty"`
StaticToolCodes []string `json:"staticToolCodes,omitempty"`
DynamicToolCodes []string `json:"dynamicToolCodes,omitempty"`
ToolSearchEnabled bool `json:"toolSearchEnabled,omitempty"`
CurrentUserMessagePreview string `json:"currentUserMessagePreview,omitempty"`
} `json:"input"`
Retriever struct {
Count int `json:"count,omitempty"`
TopK int `json:"topK,omitempty"`
ScoreThreshold float64 `json:"scoreThreshold,omitempty"`
ContextMaxTokens int `json:"contextMaxTokens,omitempty"`
MaxContextItems int `json:"maxContextItems,omitempty"`
ContextCount int `json:"contextCount,omitempty"`
EmbeddingMs int64 `json:"embeddingMs,omitempty"`
VectorSearchMs int64 `json:"vectorSearchMs,omitempty"`
HydrateMs int64 `json:"hydrateMs,omitempty"`
Policies []RetrieverPolicyTraceItem `json:"policies,omitempty"`
Items []RetrieverTraceItem `json:"items,omitempty"`
} `json:"retriever"`
Answerability AnswerabilityTraceData `json:"answerability,omitempty"`
Tools struct {
Count int `json:"count,omitempty"`
Items []ToolTraceItem `json:"items,omitempty"`
} `json:"tools"`
ToolSearch struct {
Count int `json:"count,omitempty"`
Items []ToolSearchTraceItem `json:"items,omitempty"`
} `json:"toolSearch"`
GraphTools struct {
Count int `json:"count,omitempty"`
Items []GraphToolTraceItem `json:"items,omitempty"`
} `json:"graphTools"`
Output struct {
ReplyText string `json:"replyText,omitempty"`
FinishReason string `json:"finishReason,omitempty"`
} `json:"output"`
Error struct {
Message string `json:"message,omitempty"`
Stage string `json:"stage,omitempty"`
} `json:"error"`
}
type SkillTraceData struct {
ID int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
RouteReason string `json:"routeReason,omitempty"`
RouteTrace string `json:"routeTrace,omitempty"`
AllowedToolCodes []string `json:"allowedToolCodes,omitempty"`
FilteredToolCodes []string `json:"filteredToolCodes,omitempty"`
MiddlewareEnabled bool `json:"middlewareEnabled,omitempty"`
MiddlewareToolName string `json:"middlewareToolName,omitempty"`
VisibleIDs []int64 `json:"visibleIds,omitempty"`
}
type InterruptTraceContext struct {
Type string `json:"type,omitempty"`
ID string `json:"id"`
InfoPreview string `json:"infoPreview,omitempty"`
}