fix(workspace): project Anthropic adapter creds for BYOK MiniMax on restart with workspace override (core#2712) #2735
@@ -446,3 +446,42 @@ func TestApplyPlatformManagedLLMEnv_ProxyEnvPresentInjectsCredential(t *testing.
|
||||
t.Errorf("sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyPlatformManagedLLMEnv_BYOKMiniMaxWorkspaceOverrideProjectsCreds is
|
||||
// core#2712: a claude-code workspace with an explicit per-workspace BYOK
|
||||
// override and a stored MiniMax model must still project ANTHROPIC_AUTH_TOKEN
|
||||
// and ANTHROPIC_BASE_URL from MINIMAX_API_KEY.
|
||||
//
|
||||
// ResolveLLMBillingModeDerived returns early on a workspace_override with
|
||||
// ProviderSelection=nil. Without a fallback derivation here, the core#2709
|
||||
// projection block would skip because providerFromRegistry("") fails, leaving
|
||||
// the Anthropic SDK adapter credential-less after restart.
|
||||
func TestApplyPlatformManagedLLMEnv_BYOKMiniMaxWorkspaceOverrideProjectsCreds(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const wsID = "b4914c3d-7ce0-4e14-aa32-02da048e2ae7"
|
||||
|
||||
mock := setupTestDB(t)
|
||||
expectOverrideQuery(mock, wsID, LLMBillingModeBYOK)
|
||||
|
||||
envVars := map[string]string{
|
||||
"MODEL": "MiniMax-M2.7",
|
||||
"MINIMAX_API_KEY": "real-minimax-key",
|
||||
}
|
||||
res := applyPlatformManagedLLMEnv(ctx, envVars, wsID, "claude-code", "", nil)
|
||||
|
||||
if res.ResolvedMode != LLMBillingModeBYOK {
|
||||
t.Fatalf("resolved mode = %q, want byok", res.ResolvedMode)
|
||||
}
|
||||
if got := envVars["ANTHROPIC_AUTH_TOKEN"]; got != "real-minimax-key" {
|
||||
t.Fatalf("ANTHROPIC_AUTH_TOKEN = %q, want real-minimax-key", got)
|
||||
}
|
||||
if got := envVars["ANTHROPIC_BASE_URL"]; got != "https://api.minimax.io/anthropic/v1" {
|
||||
t.Fatalf("ANTHROPIC_BASE_URL = %q, want https://api.minimax.io/anthropic/v1", got)
|
||||
}
|
||||
if got := envVars["MINIMAX_API_KEY"]; got != "real-minimax-key" {
|
||||
t.Fatalf("MINIMAX_API_KEY was overwritten: %q", got)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,7 +1170,20 @@ func applyPlatformManagedLLMEnv(ctx context.Context, envVars map[string]string,
|
||||
// restart. Project the provider's preferred auth token env and Anthropic
|
||||
// base URL from the workspace's available provider credential.
|
||||
if res.ResolvedMode == LLMBillingModeBYOK && runtimeUsesAnthropicNativeProxy(runtime) {
|
||||
if provider, ok := providerFromRegistry(derefOrEmpty(res.ProviderSelection)); ok && provider.AuthTokenEnv != "" {
|
||||
providerName := derefOrEmpty(res.ProviderSelection)
|
||||
// core#2712: a per-workspace billing-mode override (source=workspace_override)
|
||||
// short-circuits ResolveLLMBillingModeDerived before it sets ProviderSelection,
|
||||
// but the Anthropic-adapter projection still needs to know WHICH BYOK
|
||||
// provider the workspace is running so it can map MINIMAX_API_KEY (etc.)
|
||||
// to ANTHROPIC_AUTH_TOKEN. Derive it from the effective model when missing.
|
||||
if providerName == "" && effectiveModel != "" {
|
||||
if manifest, mErr := providerRegistry(); mErr == nil && manifest != nil {
|
||||
if p, dErr := manifest.DeriveProvider(runtime, effectiveModel, availableAuthEnv); dErr == nil {
|
||||
providerName = p.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
if provider, ok := providerFromRegistry(providerName); ok && provider.AuthTokenEnv != "" {
|
||||
if _, hasToken := envVars[provider.AuthTokenEnv]; !hasToken {
|
||||
for _, authEnv := range provider.AuthEnv {
|
||||
if v := strings.TrimSpace(envVars[authEnv]); v != "" {
|
||||
|
||||
Reference in New Issue
Block a user