Merge pull request #4572 from catoncat/fix/openai-agent-identity-team-isolation

fix(openai): 按 Team 隔离 Agent Identity 导入
This commit is contained in:
Wesley Liddick
2026-07-20 15:30:26 +08:00
committed by GitHub
2 changed files with 105 additions and 6 deletions
@@ -47,6 +47,100 @@ func TestNormalizeCodexImportEntryAcceptsAgentIdentityAuthJSON(t *testing.T) {
require.NotEmpty(t, item.WarningTexts)
}
func TestBuildCodexAgentIdentityKeysUseChatGPTAccountOnly(t *testing.T) {
keys := buildCodexAgentIdentityKeys("team-a")
require.Equal(t, []string{"account:team-a"}, keys)
}
func TestCodexAgentIdentityIndexSeparatesTeamsForSameUser(t *testing.T) {
existing := service.Account{
ID: 1,
Credentials: map[string]any{
"auth_mode": service.OpenAIAuthModeAgentIdentity,
"chatgpt_account_id": "team-a",
"chatgpt_user_id": "same-user",
"agent_runtime_id": "runtime-a",
},
}
index := buildCodexAccountIndex([]service.Account{existing})
teamBKeys := buildCodexAgentIdentityKeys("team-b")
matched, _ := index.Find(teamBKeys, "same-user")
require.Nil(t, matched)
teamAKeys := buildCodexAgentIdentityKeys("team-a")
matched, matchedKey := index.Find(teamAKeys, "same-user")
require.NotNil(t, matched)
require.Equal(t, int64(1), matched.ID)
require.Equal(t, "account:team-a", matchedKey)
}
func TestImportCodexSessionsKeepsAgentIdentityTeamsSeparate(t *testing.T) {
first := buildAgentIdentityImportValue(t, "runtime-a", "team-a", "same-user", "task-a")
second := buildAgentIdentityImportValue(t, "runtime-b", "team-b", "same-user", "task-b")
svc := newCodexImportMemoryAdminService(nil)
handler := NewAccountHandler(svc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
result, err := handler.importCodexSessions(context.Background(), CodexSessionImportRequest{
SkipDefaultGroupBind: boolPtr(true),
}, []codexImportEntry{{Index: 1, Value: first}, {Index: 2, Value: second}})
require.NoError(t, err)
require.Equal(t, 2, result.Created)
require.Zero(t, result.Updated)
require.Zero(t, result.Skipped)
require.Len(t, svc.createdAccounts, 2)
}
func TestImportCodexSessionsMergesAgentIdentityRuntimesForSameTeam(t *testing.T) {
first := buildAgentIdentityImportValue(t, "runtime-a", "team-a", "same-user", "task-a")
second := buildAgentIdentityImportValue(t, "runtime-b", "team-a", "same-user", "task-b")
firstIdentity, ok := first["agent_identity"].(map[string]any)
require.True(t, ok)
existing := service.Account{
ID: 41,
Platform: service.PlatformOpenAI,
Type: service.AccountTypeOAuth,
Credentials: map[string]any{
"auth_mode": service.OpenAIAuthModeAgentIdentity,
"agent_runtime_id": firstIdentity["agent_runtime_id"],
"agent_private_key": firstIdentity["agent_private_key"],
"task_id": firstIdentity["task_id"],
"chatgpt_account_id": firstIdentity["account_id"],
"chatgpt_user_id": firstIdentity["chatgpt_user_id"],
},
}
svc := newCodexImportMemoryAdminService([]service.Account{existing})
handler := NewAccountHandler(svc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
result, err := handler.importCodexSessions(context.Background(), CodexSessionImportRequest{
SkipDefaultGroupBind: boolPtr(true),
}, []codexImportEntry{{Index: 1, Value: second}})
require.NoError(t, err)
require.Zero(t, result.Created)
require.Equal(t, 1, result.Updated)
require.Len(t, svc.updatedAccounts, 1)
require.Equal(t, "runtime-b", svc.updatedAccounts[0].input.Credentials["agent_runtime_id"])
require.Equal(t, "task-b", svc.updatedAccounts[0].input.Credentials["task_id"])
}
func buildAgentIdentityImportValue(t *testing.T, runtimeID, accountID, userID, taskID string) map[string]any {
t.Helper()
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
der, err := x509.MarshalPKCS8PrivateKey(privateKey)
require.NoError(t, err)
return map[string]any{
"auth_mode": "agentIdentity",
"agent_identity": map[string]any{
"agent_runtime_id": runtimeID,
"agent_private_key": base64.StdEncoding.EncodeToString(der),
"task_id": taskID,
"account_id": accountID,
"chatgpt_user_id": userID,
},
}
}
func TestImportCodexSessionsCreatesAgentIdentityWithoutOAuthExpiry(t *testing.T) {
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
@@ -532,7 +532,7 @@ func normalizeCodexImportEntry(entry codexImportEntry) (*codexImportAccount, err
if item.AgentTaskID == "" {
item.WarningTexts = append(item.WarningTexts, "未包含 task_id,首次请求会使用现有 runtime 注册新 task")
}
item.IdentityKeys = buildCodexAgentIdentityKeys(item.AccountID, item.UserID, item.Email, item.AgentRuntimeID)
item.IdentityKeys = buildCodexAgentIdentityKeys(item.AccountID)
item.Name = buildCodexImportAccountName(item, entry.Index)
return item, nil
}
@@ -894,12 +894,17 @@ func buildCodexImportIdentityKeys(accountID, userID, email, accessToken, refresh
return buildCodexStoredIdentityKeys(accountID, userID, email, accessToken)
}
func buildCodexAgentIdentityKeys(accountID, userID, email, runtimeID string) []string {
keys := buildCodexStoredIdentityKeys(accountID, userID, email, "")
if runtimeID = strings.TrimSpace(runtimeID); runtimeID != "" {
keys = append([]string{"agent:" + runtimeID}, keys...)
func buildCodexAgentIdentityKeys(accountID string) []string {
// Agent Identity credentials belonging to the same ChatGPT account are
// intentionally merged, while the same user may own multiple accounts.
// Do not use user/email/runtime as fallback keys here: user_id is shared
// across Team workspaces and runtime_id changes when a new runtime is
// registered for the same account.
accountID = strings.TrimSpace(accountID)
if accountID == "" {
return nil
}
return keys
return []string{"account:" + accountID}
}
// buildCodexStoredIdentityKeys 生成存量账号索引键,保留 user/account 维度,