feat: commandcode usage query via /alpha endpoints with Provider API key (v0.5.0)

- New config commandcode_api_key (user_... provider key, long-lived);
  when set, usage queries hit /alpha/billing/credits + /alpha/usage/summary
  with Bearer auth — no more 7-day session cookie rotation
- session_token kept as optional fallback (legacy /internal + cookie path)
- alpha credits lack opensourceMonthlyCredits; formatter tolerates
- alpha non-200 passes through status without echoing upstream body
This commit is contained in:
2026-09-23 17:13:01 +08:00
parent c2b8d89ba6
commit d7733552ee
8 changed files with 649 additions and 38 deletions
+42 -4
View File
@@ -46,15 +46,15 @@ api_base: "https://custom-api.commandcode.ai"
t.Errorf("Capabilities.ManagementAPI = false, want true")
}
// Verify config fields (v0.4.0: 4 → 5, adds opencode_api_keys)
if len(reg.Metadata.ConfigFields) != 5 {
t.Fatalf("ConfigFields len = %d, want 5", len(reg.Metadata.ConfigFields))
// Verify config fields (v0.5.0: 5 → 6, adds commandcode_api_key)
if len(reg.Metadata.ConfigFields) != 6 {
t.Fatalf("ConfigFields len = %d, want 6", len(reg.Metadata.ConfigFields))
}
fieldNames := map[string]bool{}
for _, f := range reg.Metadata.ConfigFields {
fieldNames[f.Name] = true
}
if !fieldNames["session_token"] || !fieldNames["api_base"] || !fieldNames["opencode_api_key"] || !fieldNames["opencode_api_keys"] || !fieldNames["opencode_api_base"] {
if !fieldNames["session_token"] || !fieldNames["commandcode_api_key"] || !fieldNames["api_base"] || !fieldNames["opencode_api_key"] || !fieldNames["opencode_api_keys"] || !fieldNames["opencode_api_base"] {
t.Errorf("ConfigFields missing expected fields: %+v", reg.Metadata.ConfigFields)
}
@@ -83,6 +83,44 @@ session_token: "new-token-abc"
}
}
func TestPluginConfig_CommandCodeAPIKey(t *testing.T) {
p := NewPlugin()
// Trimmed, and NOT run through ExtractSessionToken (it is a plain
// Bearer token, not a Command Code cookie string).
configYAML := []byte("commandcode_api_key: \" user_abc123xyz \"\n")
lifecycleReq, _ := json.Marshal(LifecycleRequest{ConfigYAML: configYAML})
if _, err := p.HandleMethod("plugin.register", lifecycleReq); err != nil {
t.Fatalf("handleMethod(plugin.register) error: %v", err)
}
if got := p.config.GetCommandCodeAPIKey(); got != "user_abc123xyz" {
t.Errorf("CommandCodeAPIKey = %q, want user_abc123xyz (trimmed, raw)", got)
}
// Whitespace-only value clears the field.
p2 := NewPlugin()
spaceReq, _ := json.Marshal(LifecycleRequest{ConfigYAML: []byte("commandcode_api_key: \" \"\n")})
if _, err := p2.HandleMethod("plugin.register", spaceReq); err != nil {
t.Fatalf("handleMethod(plugin.register) error: %v", err)
}
if got := p2.config.GetCommandCodeAPIKey(); got != "" {
t.Errorf("CommandCodeAPIKey = %q, want empty (whitespace-only)", got)
}
// Omitting the key in a reconfigure must not clear a configured value.
reconfReq, _ := json.Marshal(LifecycleRequest{ConfigYAML: []byte("session_token: \"tok\"\n")})
if _, err := p.HandleMethod("plugin.reconfigure", reconfReq); err != nil {
t.Fatalf("handleMethod(plugin.reconfigure) error: %v", err)
}
if got := p.config.GetCommandCodeAPIKey(); got != "user_abc123xyz" {
t.Errorf("CommandCodeAPIKey after reconfigure = %q, want kept user_abc123xyz", got)
}
// Default is empty.
if got := NewPlugin().config.GetCommandCodeAPIKey(); got != "" {
t.Errorf("default CommandCodeAPIKey = %q, want empty", got)
}
}
func TestPluginAuthIdentifier_NotHandled(t *testing.T) {
p := NewPlugin()
raw, err := p.HandleMethod("auth.identifier", nil)