Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

123 lines
4.6 KiB
Go

package agentruntime
// Nexight's ACP provider config. nexight-acp is codex-acp derived: transport
// retry text arrives as ordinary chunks and stderr logs, and model/effort are
// spawn-time codex config flags.
import (
"encoding/json"
"strings"
"github.com/tutti-os/tutti/packages/agent/daemon/providerregistry"
)
func NewNexightAdapter(transport ProcessTransport) *standardACPAdapter {
return NewNexightAdapterWithHostMetadata(transport, LegacyHostMetadata())
}
func NewNexightAdapterWithHostMetadata(transport ProcessTransport, host HostMetadata) *standardACPAdapter {
descriptor, ok := providerregistry.Find(ProviderNexight)
if !ok {
panic("nexight provider descriptor is missing")
}
return newNexightAdapterFromProviderDescriptor(descriptor, transport, host, nil)
}
func newNexightAdapterFromProviderDescriptor(descriptor providerregistry.ProviderDescriptor, transport ProcessTransport, host HostMetadata, commandResolver ProviderCommandResolver) *standardACPAdapter {
adapter := newStandardACPAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver)
adapter.config.allowSyntheticNotice = true
adapter.config.stderrMessageMapper = nexightACPSystemNoticeMessageFromStderr
adapter.config.commandWithSettings = nexightACPCommandWithSettings
adapter.config.requiresNewSessionForSettings = nexightRequiresNewSessionForSettings
return adapter
}
// nexightACPSystemNoticeMessageFromStderr projects codex-acp "handled error
// during turn" stderr retry logs into a synthetic stream_error session/update
// so reconnect attempts surface as transport notices instead of vanishing.
// (Recovered from the retired CodexAdapter; nexight-acp shares that stderr
// format.)
func nexightACPSystemNoticeMessageFromStderr(stderr []byte) (acpMessage, bool) {
text := strings.TrimSpace(string(stderr))
if text == "" {
return acpMessage{}, false
}
normalized := strings.ToLower(text)
if !strings.Contains(normalized, "handled error during turn") {
return acpMessage{}, false
}
if !strings.Contains(normalized, "responsestreamdisconnected") &&
!strings.Contains(normalized, "broken pipe") &&
!strings.Contains(normalized, "response stream") {
return acpMessage{}, false
}
detail := truncateACPLogValue(text, 4000)
params, err := json.Marshal(map[string]any{
"update": map[string]any{
"kind": "agent_system_notice",
"sessionUpdate": "stream_error",
"message": "ResponseStreamDisconnected",
"noticeKind": "transport_retry",
"severity": "warning",
"title": "Codex connection interrupted. Reconnecting...",
"detail": detail,
"additionalDetails": detail,
"retryable": true,
"source": "acp_stderr",
},
})
if err != nil {
return acpMessage{}, false
}
return acpMessage{
JSONRPC: "2.0",
Method: acpMethodUpdate,
Params: params,
}, true
}
// nexightACPConfigFlag is the codex-acp CLI flag for spawn-time config
// overrides (recovered with the settings logic from the retired CodexAdapter).
const nexightACPConfigFlag = "--config"
// nexightACPCommandWithSettings appends the session's model/effort settings as
// spawn-time codex config flags; without them model selection depends entirely
// on the agent advertising a "model" config option after session/new.
func nexightACPCommandWithSettings(base []string, session Session) []string {
command := append([]string(nil), base...)
if len(command) == 0 {
return command
}
for _, entry := range nexightACPConfigEntries(session) {
command = append(command, nexightACPConfigFlag, entry)
}
return command
}
func nexightACPConfigEntries(session Session) []string {
settings := session.SettingsValue()
entries := make([]string, 0, 4)
if model := strings.TrimSpace(settings.Model); model != "" {
entries = append(entries, "model="+model)
if summary := codexACPReasoningSummaryOverride(model); summary != "" {
entries = append(entries, codexACPConfigModelReasoningSummary+"="+summary)
}
}
if reasoning := codexACPReasoningEffortValue(settings.ReasoningEffort); reasoning != "" {
entries = append(entries, "model_reasoning_effort="+reasoning)
}
return entries
}
// nexightRequiresNewSessionForSettings forces a new session when the
// spark-family model_reasoning_summary spawn override would change: it is a
// process-start-only flag, so a live session cannot apply it.
func nexightRequiresNewSessionForSettings(session Session, patch SessionSettingsPatch) bool {
if patch.Model == nil {
return false
}
currentModel := session.SettingsValue().Model
nextModel := strings.TrimSpace(*patch.Model)
return codexACPReasoningSummaryOverride(currentModel) != codexACPReasoningSummaryOverride(nextModel)
}