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

205 lines
6.6 KiB
Go

package agentruntime
import (
"context"
"encoding/json"
"errors"
"strings"
)
const (
claudeSDKForkDriverKind = "claude-agent-sdk-session-fork"
// The official SDK forkSession API allocates the provider child identity.
// Host therefore permits one dispatch and fails closed instead of replaying
// an unknown result that could create a duplicate provider child.
claudeSDKForkDriverVersion = "0.3.220/sidecar-v8-full-turn-bindings"
)
func (a *ClaudeCodeSDKAdapter) ForkCapabilities(
_ context.Context,
_ Session,
) (SessionForkCapabilities, error) {
if a == nil || a.transport == nil {
return SessionForkCapabilities{}, nil
}
return SessionForkCapabilities{
DriverKind: claudeSDKForkDriverKind,
DriverVersion: claudeSDKForkDriverVersion,
StateBindingMode: "provider_owned",
ThroughTurn: true,
}, nil
}
func (a *ClaudeCodeSDKAdapter) Fork(
ctx context.Context,
input SessionForkInput,
) (SessionForkResult, error) {
result := SessionForkResult{
ForkedFromProviderSessionID: strings.TrimSpace(input.Source.ProviderSessionID),
ThroughProviderTurnID: strings.TrimSpace(input.ProviderTurnID),
DeliveryDisposition: SessionForkDeliveryNotStarted,
}
if a == nil || a.transport == nil {
return result, ErrSessionForkUnsupported
}
var sourceBinding claudeProviderTurnBinding
if json.Unmarshal(input.ProviderTurnBindingJSON, &sourceBinding) != nil ||
sourceBinding.SchemaVersion != providerTurnBindingSchemaVersion ||
strings.TrimSpace(sourceBinding.CheckpointMessageID) == "" {
return result, ErrSessionForkUnsupported
}
event, sent, err := a.statelessClaudeSDKForkRequest(
ctx,
input.Source,
claudeSDKSidecarRequest{
ID: newID(),
Type: "fork_session",
Payload: map[string]any{
"providerSessionId": strings.TrimSpace(input.Source.ProviderSessionID),
"providerTurnId": strings.TrimSpace(input.ProviderTurnID),
"providerCheckpointMessageId": strings.TrimSpace(sourceBinding.CheckpointMessageID),
"cwd": strings.TrimSpace(input.Source.CWD),
"title": strings.TrimSpace(input.TargetTitle),
},
},
)
if err != nil {
if sent {
result.DeliveryDisposition = SessionForkDeliveryUnknown
}
if eventDisposition := SessionForkDeliveryDisposition(
payloadString(event.Payload, "deliveryDisposition"),
); eventDisposition == SessionForkDeliveryNotStarted ||
eventDisposition == SessionForkDeliveryUnknown {
result.DeliveryDisposition = eventDisposition
}
return result, err
}
targetTurnBindings, ok := a.claudeSDKPayloadTurnBindings(
event.Payload,
"targetProviderTurnBindings",
)
if !ok {
result.DeliveryDisposition = SessionForkDeliveryUnknown
return result, errors.New(
"claude SDK fork returned invalid target provider turn bindings",
)
}
result.ProviderSessionID = payloadString(event.Payload, "providerSessionId")
result.TargetProviderTurnBindings = targetTurnBindings
result.StateBindingMode = payloadString(event.Payload, "stateBindingMode")
result.StateBindingReceipt = payloadString(event.Payload, "stateBindingReceipt")
result.DeliveryDisposition = SessionForkDeliveryDisposition(
payloadString(event.Payload, "deliveryDisposition"),
)
if result.ProviderSessionID == "" ||
result.ProviderSessionID == result.ForkedFromProviderSessionID ||
len(result.TargetProviderTurnBindings) == 0 ||
result.StateBindingMode != "provider_owned" ||
result.StateBindingReceipt == "" ||
result.DeliveryDisposition != SessionForkDeliveryAccepted {
result.DeliveryDisposition = SessionForkDeliveryUnknown
return result, errors.New("claude SDK fork returned incomplete verification evidence")
}
return result, nil
}
func (a *ClaudeCodeSDKAdapter) statelessClaudeSDKForkRequest(
ctx context.Context,
session Session,
request claudeSDKSidecarRequest,
) (claudeSDKSidecarEvent, bool, error) {
spec, cleanup, err := prepareProviderLaunch(ctx, a.preparer, session, ProcessSpec{
Provider: strings.TrimSpace(session.Provider),
AgentSessionID: session.AgentSessionID,
RoomID: session.RoomID,
CWD: session.CWD,
Command: claudeSDKSidecarCommand(session.Env),
Env: claudeSDKSidecarEnv(session),
DirectStart: true,
})
if err != nil {
return claudeSDKSidecarEvent{}, false, err
}
conn, err := a.transport.Start(ctx, spec)
if err != nil {
cleanupPreparedLaunch(cleanup)
return claudeSDKSidecarEvent{}, false, err
}
trackInputUnits := providerInputUnitsEnabled(conn)
conn = wrapProviderLaunchCleanup(conn, cleanup)
defer conn.Close()
adapterSession := &claudeSDKAdapterSession{
conn: conn,
reader: newClaudeSDKLineReader(conn, trackInputUnits),
}
if err := adapterSession.send(request); err != nil {
return claudeSDKSidecarEvent{}, false, err
}
event, err := adapterSession.roundTripDirectResponse(ctx, request)
if err != nil {
return event, true, err
}
return event, true, nil
}
func (a *ClaudeCodeSDKAdapter) claudeSDKPayloadTurnBindings(
payload map[string]any,
key string,
) ([]SessionForkProviderTurnBinding, bool) {
raw, exists := payload[key]
if !exists {
return nil, false
}
values, ok := raw.([]any)
if !ok {
return nil, false
}
result := make([]SessionForkProviderTurnBinding, 0, len(values))
seenProviderTurnIDs := make(map[string]struct{}, len(values))
seenCheckpointMessageIDs := make(map[string]struct{}, len(values))
for _, rawValue := range values {
value, ok := rawValue.(map[string]any)
if !ok {
return nil, false
}
providerTurnID := strings.TrimSpace(
payloadString(value, "providerTurnId"),
)
checkpointMessageID := strings.TrimSpace(
payloadString(value, "checkpointMessageId"),
)
if providerTurnID == "" || checkpointMessageID == "" {
return nil, false
}
if _, duplicate := seenProviderTurnIDs[providerTurnID]; duplicate {
return nil, false
}
if _, duplicate := seenCheckpointMessageIDs[checkpointMessageID]; duplicate {
return nil, false
}
bindingJSON, err := a.WriteProviderTurnBinding(
ProviderTurnBindingWriteInput{
Kind: ProviderTurnBindingWriteForked,
ProviderTurnID: providerTurnID,
Payload: map[string]any{
"checkpointMessageId": checkpointMessageID,
},
},
)
if err != nil {
return nil, false
}
seenProviderTurnIDs[providerTurnID] = struct{}{}
seenCheckpointMessageIDs[checkpointMessageID] = struct{}{}
binding := SessionForkProviderTurnBinding{
ProviderTurnID: providerTurnID,
ProviderTurnBindingJSON: bindingJSON,
}
result = append(result, binding)
}
return result, len(result) > 0
}
var _ SessionForkAdapter = (*ClaudeCodeSDKAdapter)(nil)