Files
go-sip/cmd/sip-call/main.go
T

146 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// sip-call is a standalone, operator-invoked Asterisk line test. It does not
// import the Agent/Dispatcher commands or require a business configuration.
package main
import (
"context"
"errors"
"fmt"
"git.ipao.vip/rogee/go-sip/internal/sipcall"
"github.com/spf13/cobra"
"io"
"os"
"os/signal"
"syscall"
)
type callRunner func(context.Context, sipcall.Config, string, bool) (*sipcall.Report, error)
type aiCallRunner func(context.Context, sipcall.Config, string, bool, string, string) (*sipcall.AIReport, error)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if err := newRootCommand(sipcall.Run).ExecuteContext(ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func newRootCommand(run callRunner) *cobra.Command {
return newRootCommandWithAI(run, sipcall.RunAI)
}
func newRootCommandWithAI(run callRunner, runAI aiCallRunner) *cobra.Command {
var debug, aiEnabled bool
var taskPath, providerPath string
root := &cobra.Command{Use: "sip-call <线路.env> <原始号码>", Short: "单通 Asterisk 外呼测试;--ai 启用本地任务驱动的 AI 测试", SilenceUsage: true, SilenceErrors: true, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error {
taskSet := cmd.Flags().Changed("task")
providerSet := cmd.Flags().Changed("provider")
if !aiEnabled && (taskSet || providerSet) {
return errors.New("--task and --provider require --ai")
}
if aiEnabled && (!taskSet || taskPath == "" || !providerSet || providerPath == "") {
return errors.New("--ai requires non-empty --task and --provider file paths")
}
c, err := sipcall.LoadConfig(args[0])
if err != nil {
return err
}
if _, err = c.Route(args[1]); err != nil {
return err
}
if aiEnabled {
result, runErr := runAI(cmd.Context(), c, args[1], debug, taskPath, providerPath)
if result != nil {
runErr = errors.Join(runErr, printAIReport(cmd.OutOrStdout(), result))
}
return runErr
}
result, runErr := run(cmd.Context(), c, args[1], debug)
if result != nil {
runErr = errors.Join(runErr, printReport(cmd.OutOrStdout(), result))
}
return runErr
}}
root.Flags().BoolVar(&aiEnabled, "ai", false, "复用现有 AI 呼叫逻辑;需要本地任务和 provider 文件")
root.Flags().StringVar(&taskPath, "task", "", "完整 task_config JSON 文件(仅与 --ai 配合)")
root.Flags().StringVar(&providerPath, "provider", "", "完整 ai_providers JSON 文件(仅与 --ai 配合)")
root.Flags().BoolVarP(&debug, "debug", "D", false, "开启本通抓包和 SIP/RTP 证据提取;缺少工具或权限就拒绝拨号")
return root
}
func printAIReport(out io.Writer, report *sipcall.AIReport) error {
if report == nil || report.Call == nil {
return errors.New("AI test result is incomplete")
}
if err := printCallFacts(out, report.Call); err != nil {
return err
}
if err := printCaptureSummary(out, report.Call, true, report.MediaFlowStarted); err != nil {
return err
}
flowStatus := "未启动"
if report.MediaFlowStarted {
flowStatus = "已启动"
}
if _, err := fmt.Fprintf(out, "AI 模式:%s\nAI 媒体流程:%s\n", report.Mode, flowStatus); err != nil {
return err
}
if report.RecordingPath != "" {
if _, err := fmt.Fprintf(out, "本地录音:%s\n", report.RecordingPath); err != nil {
return err
}
}
for i, turn := range report.Turns {
if turn.Transcript != "" {
if _, err := fmt.Fprintf(out, "第 %d 轮识别:%q\n", i+1, turn.Transcript); err != nil {
return err
}
}
if turn.Reply != "" {
if _, err := fmt.Fprintf(out, "第 %d 轮回复:%q\n", i+1, turn.Reply); err != nil {
return err
}
}
}
return nil
}
func printReport(out io.Writer, r *sipcall.Report) error {
if err := printCallFacts(out, r); err != nil {
return err
}
return printCaptureSummary(out, r, false, false)
}
func printCallFacts(out io.Writer, r *sipcall.Report) error {
status := map[string]string{"connected": "已接通并确认结束", "not_connected": "未接通", "not_dialed": "未拨号", "unknown": "状态不明,禁止自动重拨", "failed": "失败(详情见结果文件)"}[r.Status]
if status == "" {
status = "状态不明"
}
_, err := fmt.Fprintf(out, "结论:%s\n线路:%s (%s)\n主叫:%s\n原始被叫:%s\n线路被叫:%s\n详情:%s\n", status, r.Line, r.Server, r.CallerID, r.Callee, r.DialedCallee, r.Directory+"/result.json")
return err
}
func printCaptureSummary(out io.Writer, r *sipcall.Report, aiMode, aiMediaFlowStarted bool) error {
if r.Evidence != nil {
code := "未观测到最终响应"
if r.Evidence.FinalINVITECode != 0 {
code = fmt.Sprint(r.Evidence.FinalINVITECode)
}
_, err := fmt.Fprintf(out, "抓包:%s\nSHA-256:%s\nSIP 时间线:%s\n原始 INVITE 最终响应:%s\n媒体观察:%s\n", r.Evidence.PCAP, r.Evidence.PCAPSHA256, r.Evidence.SIPTimeline, code, r.Evidence.RTPStatus)
return err
}
message := "抓包:未启用(使用 --debug/-D 开启);没有抓包链路证据或媒体验证。"
if r.Debug {
message = "抓包:已请求,但未启动;没有抓包链路证据或媒体验证。"
if aiMode && aiMediaFlowStarted {
message = "抓包:已请求,但未生成证据;AI 媒体流程已启动。"
}
} else if aiMode && aiMediaFlowStarted {
message = "抓包:未启用(使用 --debug/-D 开启);AI 媒体流程已启动。"
} else if aiMode {
message = "抓包:未启用(使用 --debug/-D 开启);AI 媒体流程未启动。"
}
_, err := fmt.Fprintln(out, message)
return err
}