mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
a05da5f3ab
Remove the early ValidateEnvironment gate from CreateProvider that only checked env vars and --provider-api-key, blocking stored OAuth credentials from working. Each provider creation function already handles its own auth resolution with clear error messages. Update ValidateEnvironment to also check stored Anthropic credentials so the model selector UI correctly shows Anthropic models for OAuth users. Add automatic token refresh in oauthTransport so long-lived ACP sessions survive token renewals. Surface actionable auth error messages in ACP session creation. Fix pre-existing staticcheck SA5011 warnings in test files.
114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package acpserver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
kit "github.com/mark3labs/kit/pkg/kit"
|
|
)
|
|
|
|
// acpSession maps an ACP session to a Kit instance with its own tree session.
|
|
type acpSession struct {
|
|
kit *kit.Kit
|
|
cancelFn context.CancelFunc // cancels the current prompt
|
|
cancelMu sync.Mutex
|
|
cwd string
|
|
sessionID string // Kit-generated session ID (from JSONL header)
|
|
}
|
|
|
|
// sessionRegistry is a thread-safe registry of ACP session ID → Kit sessions.
|
|
type sessionRegistry struct {
|
|
mu sync.RWMutex
|
|
sessions map[string]*acpSession // ACP session ID → session
|
|
}
|
|
|
|
func newSessionRegistry() *sessionRegistry {
|
|
return &sessionRegistry{
|
|
sessions: make(map[string]*acpSession),
|
|
}
|
|
}
|
|
|
|
// create creates a new Kit instance with a persisted tree session for the
|
|
// given working directory. The Kit-generated session ID is used as the ACP
|
|
// session ID so the mapping is 1:1.
|
|
func (r *sessionRegistry) create(ctx context.Context, cwd string) (*acpSession, error) {
|
|
kitInstance, err := kit.New(ctx, &kit.Options{
|
|
SessionDir: cwd,
|
|
Quiet: true,
|
|
Streaming: true,
|
|
})
|
|
if err != nil {
|
|
// Provide actionable guidance for provider auth errors, which are
|
|
// the most common failure mode when running via ACP.
|
|
msg := err.Error()
|
|
if strings.Contains(msg, "API key") || strings.Contains(msg, "credentials") || strings.Contains(msg, "OAuth") {
|
|
return nil, fmt.Errorf("provider authentication failed: %w — run 'kit auth login <provider>' or set the appropriate environment variable before starting 'kit acp'", err)
|
|
}
|
|
return nil, fmt.Errorf("create kit instance: %w", err)
|
|
}
|
|
|
|
sessionID := kitInstance.GetSessionID()
|
|
if sessionID == "" {
|
|
_ = kitInstance.Close()
|
|
return nil, fmt.Errorf("kit instance has no session ID")
|
|
}
|
|
|
|
sess := &acpSession{
|
|
kit: kitInstance,
|
|
cwd: cwd,
|
|
sessionID: sessionID,
|
|
}
|
|
|
|
r.mu.Lock()
|
|
r.sessions[sessionID] = sess
|
|
r.mu.Unlock()
|
|
|
|
return sess, nil
|
|
}
|
|
|
|
// get retrieves a session by ACP session ID.
|
|
func (r *sessionRegistry) get(sessionID string) (*acpSession, bool) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
s, ok := r.sessions[sessionID]
|
|
return s, ok
|
|
}
|
|
|
|
// closeAll closes all sessions.
|
|
func (r *sessionRegistry) closeAll() {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
for id, sess := range r.sessions {
|
|
if sess.kit != nil {
|
|
_ = sess.kit.Close()
|
|
}
|
|
delete(r.sessions, id)
|
|
}
|
|
}
|
|
|
|
// cancelPrompt cancels the current prompt for a session, if any.
|
|
func (s *acpSession) cancelPrompt() {
|
|
s.cancelMu.Lock()
|
|
defer s.cancelMu.Unlock()
|
|
if s.cancelFn != nil {
|
|
s.cancelFn()
|
|
s.cancelFn = nil
|
|
}
|
|
}
|
|
|
|
// setCancel stores a cancel function for the current prompt.
|
|
func (s *acpSession) setCancel(cancel context.CancelFunc) {
|
|
s.cancelMu.Lock()
|
|
defer s.cancelMu.Unlock()
|
|
s.cancelFn = cancel
|
|
}
|
|
|
|
// clearCancel clears the stored cancel function (called when prompt completes).
|
|
func (s *acpSession) clearCancel() {
|
|
s.cancelMu.Lock()
|
|
defer s.cancelMu.Unlock()
|
|
s.cancelFn = nil
|
|
}
|