diff --git a/changelog/unreleased/codearts-credential-import.md b/changelog/unreleased/codearts-credential-import.md new file mode 100644 index 00000000..934c3baf --- /dev/null +++ b/changelog/unreleased/codearts-credential-import.md @@ -0,0 +1,7 @@ +### English + +- CodeArts (码道) console credential import works again: the adapter exposes its importer where the console looks for it, so pasting a `codearts-oauth-v1` credential no longer answers `unsupported_format`. + +### 中文 + +- 码道(CodeArts)控制台「导入凭证」恢复正常:适配器按控制台查找的位置暴露导入器,粘贴 `codearts-oauth-v1` 凭证不再返回 `unsupported_format`。 \ No newline at end of file diff --git a/internal/providers/codearts/codearts_test.go b/internal/providers/codearts/codearts_test.go index 7b793d00..7bbd396e 100644 --- a/internal/providers/codearts/codearts_test.go +++ b/internal/providers/codearts/codearts_test.go @@ -672,7 +672,7 @@ func TestAdapterSurface(t *testing.T) { t.Fatalf("adapter missing %s", capability) } } - var _ providers.CredentialImporter = importer{} + var _ providers.CredentialImporter = credentialCodec{} var _ providers.ModelCatalogProvider = client var _ providers.ProviderChat = client var _ providers.AccountProber = client diff --git a/internal/providers/codearts/import.go b/internal/providers/codearts/import.go index b492f371..490d24af 100644 --- a/internal/providers/codearts/import.go +++ b/internal/providers/codearts/import.go @@ -6,16 +6,19 @@ import ( "github.com/caigee-cmd/cli2api/internal/providers" ) -type importer struct{} - -func (importer) Format() string { return CredentialFormat } - -func (importer) ValidateImport(payload []byte) error { return ValidateCredential(payload) } +// The console resolves credential import through Adapter.Credential +// (providers.CredentialImporter), so the codec carries Format and PrepareImport. +// The ImportExport slot keeps ValidateImport and Export only. +func (credentialCodec) Format() string { return CredentialFormat } -func (importer) PrepareImport(payload []byte) (providers.CredentialImport, error) { +func (credentialCodec) PrepareImport(payload []byte) (providers.CredentialImport, error) { return PrepareImport(payload) } +type importer struct{} + +func (importer) ValidateImport(payload []byte) error { return ValidateCredential(payload) } + // Export is intentionally unsupported. A CodeArts credential contains the STS // keys and the refresh token; exporting it would hand out the account. func (importer) Export(context.Context, string) (map[string]any, error) { diff --git a/internal/providers/codearts/s04_interfaces_test.go b/internal/providers/codearts/s04_interfaces_test.go index 3b40f6b1..35caa144 100644 --- a/internal/providers/codearts/s04_interfaces_test.go +++ b/internal/providers/codearts/s04_interfaces_test.go @@ -27,13 +27,13 @@ func (requiredStore) Observe(context.Context, string, string, string, string, st func TestS04InterfaceContract(t *testing.T) { client := NewClient(requiredStore{}) var _ providers.CredentialCodec = credentialCodec{} + var _ providers.CredentialImporter = credentialCodec{} var _ providers.LoginSessionProvider = client var _ providers.LoginCompleter = client var _ providers.ProviderChat = client var _ providers.ModelCatalogProvider = client var _ providers.AccountProber = client var _ providers.ErrorClassifier = classifier{} - var _ providers.CredentialImporter = importer{} var _ providers.ImportExporter = importer{} var _ providers.AccountCheckiner = client @@ -46,6 +46,35 @@ func TestS04InterfaceContract(t *testing.T) { t.Fatalf("adapter missing capability %q", capability) } } + // control.Accounts.Import reads the importer off Adapter.Credential, not + // Adapter.ImportExport: wiring PrepareImport on the wrong slot makes every + // console credential import answer unsupported_format. + if _, ok := adapter.Credential.(providers.CredentialImporter); !ok { + t.Fatal("Adapter.Credential must implement providers.CredentialImporter") + } +} + +func TestCredentialCodecServesConsoleImport(t *testing.T) { + encoded, err := testCredential().Encode() + if err != nil { + t.Fatalf("encode: %v", err) + } + // The console sends the format it reads from the registry and expects a + // ready payload back, so both halves are part of the import contract. + codec, ok := any(credentialCodec{}).(providers.CredentialImporter) + if !ok { + t.Fatal("credentialCodec must implement providers.CredentialImporter") + } + if got := codec.Format(); got != CredentialFormat { + t.Fatalf("format = %q, want %q", got, CredentialFormat) + } + prepared, err := codec.PrepareImport(encoded) + if err != nil { + t.Fatalf("prepare import: %v", err) + } + if !prepared.Ready || len(prepared.Payload) == 0 { + t.Fatalf("prepared = %+v", prepared) + } } func TestMissingSecretReaderSkipsGlobalProxy(t *testing.T) {