|
|
@@ -88,8 +88,9 @@ type NewSessionParams struct {
|
|
|
|
|
|
// NewSessionResult 创建会话响应
|
|
|
type NewSessionResult struct {
|
|
|
- SessionId string `json:"sessionId"`
|
|
|
- Modes *SessionModes `json:"modes,omitempty"`
|
|
|
+ SessionId string `json:"sessionId"`
|
|
|
+ Modes *SessionModes `json:"modes,omitempty"`
|
|
|
+ Models *SessionModels `json:"models,omitempty"`
|
|
|
}
|
|
|
|
|
|
// SessionModes 会话模式
|
|
|
@@ -111,6 +112,19 @@ type SessionMeta struct {
|
|
|
WelcomeMessage string `json:"welcomeMessage"`
|
|
|
}
|
|
|
|
|
|
+// SessionModels 会话可用模型
|
|
|
+type SessionModels struct {
|
|
|
+ CurrentModelId string `json:"currentModelId"`
|
|
|
+ AvailableModels []SessionModel `json:"availableModels"`
|
|
|
+}
|
|
|
+
|
|
|
+// SessionModel 单个模型
|
|
|
+type SessionModel struct {
|
|
|
+ ModelId string `json:"modelId"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ Description string `json:"description"`
|
|
|
+}
|
|
|
+
|
|
|
// PromptParams 发送消息参数
|
|
|
type PromptParams struct {
|
|
|
SessionId string `json:"sessionId"`
|
|
|
@@ -219,6 +233,11 @@ type Client struct {
|
|
|
|
|
|
// 文本累积(用于合并日志输出)
|
|
|
textBuffer string
|
|
|
+
|
|
|
+ // 最近一次上下文用量与耗时(供首屏展示,读取需加 metaMu)
|
|
|
+ metaMu sync.Mutex
|
|
|
+ lastContextUsage float64
|
|
|
+ lastTurnDurationMs int64
|
|
|
}
|
|
|
|
|
|
// ClientConfig 客户端配置
|
|
|
@@ -289,6 +308,25 @@ func (c *Client) ResolvePermission(optionId string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// setMeta 更新最近的上下文用量与耗时(值 >0 时才覆盖)
|
|
|
+func (c *Client) setMeta(usage float64, turnMs int64) {
|
|
|
+ c.metaMu.Lock()
|
|
|
+ defer c.metaMu.Unlock()
|
|
|
+ if usage > 0 {
|
|
|
+ c.lastContextUsage = usage
|
|
|
+ }
|
|
|
+ if turnMs > 0 {
|
|
|
+ c.lastTurnDurationMs = turnMs
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// GetMeta 返回最近的上下文用量(百分比)与上一轮耗时(毫秒)
|
|
|
+func (c *Client) GetMeta() (float64, int64) {
|
|
|
+ c.metaMu.Lock()
|
|
|
+ defer c.metaMu.Unlock()
|
|
|
+ return c.lastContextUsage, c.lastTurnDurationMs
|
|
|
+}
|
|
|
+
|
|
|
// Initialize 执行初始化握手
|
|
|
func (c *Client) Initialize() error {
|
|
|
params := InitializeParams{
|
|
|
@@ -310,8 +348,8 @@ func (c *Client) Initialize() error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-// NewSession 创建新会话,返回 sessionId 和 welcomeMessage
|
|
|
-func (c *Client) NewSession(cwd string) (string, string, error) {
|
|
|
+// NewSession 创建新会话,返回 sessionId、welcomeMessage、实际生效的 agentId 和 modelId
|
|
|
+func (c *Client) NewSession(cwd string) (sessionId string, welcomeMsg string, agentId string, modelId string, err error) {
|
|
|
params := NewSessionParams{
|
|
|
CWD: cwd,
|
|
|
MCPServers: []any{},
|
|
|
@@ -319,21 +357,21 @@ func (c *Client) NewSession(cwd string) (string, string, error) {
|
|
|
|
|
|
result, err := c.sendAndWait("session/new", params)
|
|
|
if err != nil {
|
|
|
- return "", "", err
|
|
|
+ return "", "", "", "", err
|
|
|
}
|
|
|
|
|
|
var res NewSessionResult
|
|
|
if err := json.Unmarshal(result, &res); err != nil {
|
|
|
- return "", "", fmt.Errorf("解析 session/new 响应失败: %w", err)
|
|
|
+ return "", "", "", "", fmt.Errorf("解析 session/new 响应失败: %w", err)
|
|
|
}
|
|
|
|
|
|
zap.L().Info("session/new", zap.String("result", string(result)))
|
|
|
|
|
|
c.sessionId = res.SessionId
|
|
|
|
|
|
- // 提取当前 mode 的 welcomeMessage
|
|
|
- var welcomeMsg string
|
|
|
+ // 提取当前 mode 的 welcomeMessage 和实际生效的 agentId
|
|
|
if res.Modes != nil {
|
|
|
+ agentId = res.Modes.CurrentModeId
|
|
|
for _, mode := range res.Modes.AvailableModes {
|
|
|
if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
|
|
|
welcomeMsg = mode.Meta.WelcomeMessage
|
|
|
@@ -342,7 +380,55 @@ func (c *Client) NewSession(cwd string) (string, string, error) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return res.SessionId, welcomeMsg, nil
|
|
|
+ // 提取实际生效的 modelId
|
|
|
+ if res.Models != nil {
|
|
|
+ modelId = res.Models.CurrentModelId
|
|
|
+ }
|
|
|
+
|
|
|
+ // 捕获 session/new 之后 Kiro 紧跟发送的初始用量 metadata 通知
|
|
|
+ c.drainInitialMeta()
|
|
|
+
|
|
|
+ return res.SessionId, welcomeMsg, agentId, modelId, nil
|
|
|
+}
|
|
|
+
|
|
|
+// drainInitialMeta 读取 session/new 之后紧跟的一批通知,捕获其中的初始上下文用量。
|
|
|
+// Kiro 在 session/new 响应后会发送 commands/available、subagent/list_update、metadata 等
|
|
|
+// 一批通知,且 metadata(含 contextUsagePercentage)位于这批通知的末尾。这里读取直到
|
|
|
+// 遇到 metadata 通知即停止,避免读空后阻塞。设置行数上限作为安全兜底。
|
|
|
+func (c *Client) drainInitialMeta() {
|
|
|
+ const maxLines = 30
|
|
|
+ for i := 0; i < maxLines; i++ {
|
|
|
+ if !c.scanner.Scan() {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ line := c.scanner.Text()
|
|
|
+ if line == "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ var msg struct {
|
|
|
+ Method string `json:"method,omitempty"`
|
|
|
+ Params json.RawMessage `json:"params,omitempty"`
|
|
|
+ ID json.RawMessage `json:"id,omitempty"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal([]byte(line), &msg); err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if msg.Method == "_kiro.dev/metadata" {
|
|
|
+ var data struct {
|
|
|
+ ContextUsagePercentage float64 `json:"contextUsagePercentage"`
|
|
|
+ TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal(msg.Params, &data); err == nil {
|
|
|
+ zap.L().Debug("[ACP] 初始 metadata", zap.Float64("contextUsage%", data.ContextUsagePercentage))
|
|
|
+ c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 其他 pre-prompt 通知(commands/available、subagent 等)忽略
|
|
|
+ }
|
|
|
+ zap.L().Debug("[ACP] drainInitialMeta 未在上限内捕获到 metadata")
|
|
|
}
|
|
|
|
|
|
// ReplayMessage 回放的单条消息
|
|
|
@@ -353,7 +439,7 @@ type ReplayMessage struct {
|
|
|
}
|
|
|
|
|
|
// LoadSession 加载已有会话(恢复历史对话上下文),返回回放的消息列表
|
|
|
-func (c *Client) LoadSession(sessionId string, cwd string) (string, string, []ReplayMessage, error) {
|
|
|
+func (c *Client) LoadSession(sessionId string, cwd string) (string, string, string, string, []ReplayMessage, error) {
|
|
|
params := map[string]any{
|
|
|
"sessionId": sessionId,
|
|
|
"cwd": cwd,
|
|
|
@@ -394,7 +480,7 @@ func (c *Client) LoadSession(sessionId string, cwd string) (string, string, []Re
|
|
|
if numId == id {
|
|
|
// session/load 完成
|
|
|
if msg.Error != nil {
|
|
|
- return "", "", nil, fmt.Errorf("session/load 错误: %s", msg.Error.FullMessage())
|
|
|
+ return "", "", "", "", nil, fmt.Errorf("session/load 错误: %s", msg.Error.FullMessage())
|
|
|
}
|
|
|
c.sessionId = sessionId
|
|
|
// 追加最后一条未关闭的消息
|
|
|
@@ -402,21 +488,32 @@ func (c *Client) LoadSession(sessionId string, cwd string) (string, string, []Re
|
|
|
replayMessages = append(replayMessages, *currentMsg)
|
|
|
}
|
|
|
|
|
|
- // 解析响应中的 welcomeMessage
|
|
|
+ // 解析响应中的 welcomeMessage、实际生效的 agentId 和 modelId
|
|
|
var welcomeMsg string
|
|
|
+ var agentId string
|
|
|
+ var modelId string
|
|
|
if len(msg.Result) > 0 {
|
|
|
var res NewSessionResult
|
|
|
- if err := json.Unmarshal(msg.Result, &res); err == nil && res.Modes != nil {
|
|
|
- for _, mode := range res.Modes.AvailableModes {
|
|
|
- if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
|
|
|
- welcomeMsg = mode.Meta.WelcomeMessage
|
|
|
- break
|
|
|
+ if err := json.Unmarshal(msg.Result, &res); err == nil {
|
|
|
+ if res.Modes != nil {
|
|
|
+ agentId = res.Modes.CurrentModeId
|
|
|
+ for _, mode := range res.Modes.AvailableModes {
|
|
|
+ if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
|
|
|
+ welcomeMsg = mode.Meta.WelcomeMessage
|
|
|
+ break
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ if res.Models != nil {
|
|
|
+ modelId = res.Models.CurrentModelId
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return sessionId, welcomeMsg, replayMessages, nil
|
|
|
+ // 捕获 session/load 之后紧跟的初始用量 metadata 通知
|
|
|
+ c.drainInitialMeta()
|
|
|
+
|
|
|
+ return sessionId, welcomeMsg, agentId, modelId, replayMessages, nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -471,7 +568,7 @@ func (c *Client) LoadSession(sessionId string, cwd string) (string, string, []Re
|
|
|
// 忽略其他通知(kiro 扩展等)
|
|
|
}
|
|
|
|
|
|
- return "", "", nil, fmt.Errorf("session/load 未收到响应(进程可能已退出)")
|
|
|
+ return "", "", "", "", nil, fmt.Errorf("session/load 未收到响应(进程可能已退出)")
|
|
|
}
|
|
|
|
|
|
// Prompt 发送消息并流式读取事件
|
|
|
@@ -792,18 +889,23 @@ func (c *Client) handleExtensionNotification(method string, params json.RawMessa
|
|
|
TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
|
|
|
}
|
|
|
if err := json.Unmarshal(params, &data); err == nil {
|
|
|
+ zap.L().Debug("[ACP] metadata",
|
|
|
+ zap.Float64("contextUsage%", data.ContextUsagePercentage),
|
|
|
+ zap.Int64("turnMs", data.TurnDurationMs),
|
|
|
+ )
|
|
|
+ // 缓存最近用量/耗时,供首屏读取
|
|
|
+ c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
|
|
|
+ // 上下文用量每条通知都会带(turnDurationMs 仅 turn 结束那条才有),
|
|
|
+ // 因此总是转发用量,耗时有值时才附带
|
|
|
+ content := map[string]any{
|
|
|
+ "contextUsagePercent": data.ContextUsagePercentage,
|
|
|
+ }
|
|
|
if data.TurnDurationMs > 0 {
|
|
|
- zap.L().Debug("[ACP] metadata",
|
|
|
- zap.Float64("contextUsage%", data.ContextUsagePercentage),
|
|
|
- zap.Int64("turnMs", data.TurnDurationMs),
|
|
|
- )
|
|
|
- eventCh <- Event{
|
|
|
- Type: "metadata",
|
|
|
- Content: toJSONStr(map[string]any{
|
|
|
- "contextUsagePercent": data.ContextUsagePercentage,
|
|
|
- "turnDurationMs": data.TurnDurationMs,
|
|
|
- }),
|
|
|
- }
|
|
|
+ content["turnDurationMs"] = data.TurnDurationMs
|
|
|
+ }
|
|
|
+ eventCh <- Event{
|
|
|
+ Type: "metadata",
|
|
|
+ Content: toJSONStr(content),
|
|
|
}
|
|
|
}
|
|
|
|