diff --git a/internal/auth/claude/token.go b/internal/auth/claude/token.go index cda10d58..c36f8e76 100644 --- a/internal/auth/claude/token.go +++ b/internal/auth/claude/token.go @@ -36,11 +36,21 @@ type ClaudeTokenStorage struct { // Expire is the timestamp when the current access token expires. Expire string `json:"expired"` + + // Metadata holds arbitrary key-value pairs injected via hooks. + // It is not exported to JSON directly to allow flattening during serialization. + Metadata map[string]any `json:"-"` +} + +// SetMetadata allows external callers to inject metadata into the storage before saving. +func (ts *ClaudeTokenStorage) SetMetadata(meta map[string]any) { + ts.Metadata = meta } // SaveTokenToFile serializes the Claude token storage to a JSON file. // This method creates the necessary directory structure and writes the token // data in JSON format to the specified file path for persistent storage. +// It merges any injected metadata into the top-level JSON object. // // Parameters: // - authFilePath: The full path where the token file should be saved @@ -65,8 +75,25 @@ func (ts *ClaudeTokenStorage) SaveTokenToFile(authFilePath string) error { _ = f.Close() }() + // Convert struct to map for merging + data := make(map[string]any) + temp, errJson := json.Marshal(ts) + if errJson != nil { + return fmt.Errorf("failed to marshal struct: %w", errJson) + } + if errUnmarshal := json.Unmarshal(temp, &data); errUnmarshal != nil { + return fmt.Errorf("failed to unmarshal struct map: %w", errUnmarshal) + } + + // Merge extra metadata + if ts.Metadata != nil { + for k, v := range ts.Metadata { + data[k] = v + } + } + // Encode and write the token data as JSON - if err = json.NewEncoder(f).Encode(ts); err != nil { + if err = json.NewEncoder(f).Encode(data); err != nil { return fmt.Errorf("failed to write token to file: %w", err) } return nil diff --git a/internal/auth/codex/token.go b/internal/auth/codex/token.go index e93fc417..1ea84f3a 100644 --- a/internal/auth/codex/token.go +++ b/internal/auth/codex/token.go @@ -32,11 +32,21 @@ type CodexTokenStorage struct { Type string `json:"type"` // Expire is the timestamp when the current access token expires. Expire string `json:"expired"` + + // Metadata holds arbitrary key-value pairs injected via hooks. + // It is not exported to JSON directly to allow flattening during serialization. + Metadata map[string]any `json:"-"` +} + +// SetMetadata allows external callers to inject metadata into the storage before saving. +func (ts *CodexTokenStorage) SetMetadata(meta map[string]any) { + ts.Metadata = meta } // SaveTokenToFile serializes the Codex token storage to a JSON file. // This method creates the necessary directory structure and writes the token // data in JSON format to the specified file path for persistent storage. +// It merges any injected metadata into the top-level JSON object. // // Parameters: // - authFilePath: The full path where the token file should be saved @@ -58,9 +68,25 @@ func (ts *CodexTokenStorage) SaveTokenToFile(authFilePath string) error { _ = f.Close() }() - if err = json.NewEncoder(f).Encode(ts); err != nil { + // Convert struct to map for merging + data := make(map[string]any) + temp, errJson := json.Marshal(ts) + if errJson != nil { + return fmt.Errorf("failed to marshal struct: %w", errJson) + } + if errUnmarshal := json.Unmarshal(temp, &data); errUnmarshal != nil { + return fmt.Errorf("failed to unmarshal struct map: %w", errUnmarshal) + } + + // Merge extra metadata + if ts.Metadata != nil { + for k, v := range ts.Metadata { + data[k] = v + } + } + + if err = json.NewEncoder(f).Encode(data); err != nil { return fmt.Errorf("failed to write token to file: %w", err) } return nil - } diff --git a/internal/auth/iflow/iflow_token.go b/internal/auth/iflow/iflow_token.go index 6d2beb39..13eb7de1 100644 --- a/internal/auth/iflow/iflow_token.go +++ b/internal/auth/iflow/iflow_token.go @@ -21,6 +21,15 @@ type IFlowTokenStorage struct { Scope string `json:"scope"` Cookie string `json:"cookie"` Type string `json:"type"` + + // Metadata holds arbitrary key-value pairs injected via hooks. + // It is not exported to JSON directly to allow flattening during serialization. + Metadata map[string]any `json:"-"` +} + +// SetMetadata allows external callers to inject metadata into the storage before saving. +func (ts *IFlowTokenStorage) SetMetadata(meta map[string]any) { + ts.Metadata = meta } // SaveTokenToFile serialises the token storage to disk. @@ -37,7 +46,24 @@ func (ts *IFlowTokenStorage) SaveTokenToFile(authFilePath string) error { } defer func() { _ = f.Close() }() - if err = json.NewEncoder(f).Encode(ts); err != nil { + // Convert struct to map for merging + data := make(map[string]any) + temp, errJson := json.Marshal(ts) + if errJson != nil { + return fmt.Errorf("failed to marshal struct: %w", errJson) + } + if errUnmarshal := json.Unmarshal(temp, &data); errUnmarshal != nil { + return fmt.Errorf("failed to unmarshal struct map: %w", errUnmarshal) + } + + // Merge extra metadata + if ts.Metadata != nil { + for k, v := range ts.Metadata { + data[k] = v + } + } + + if err = json.NewEncoder(f).Encode(data); err != nil { return fmt.Errorf("iflow token: encode token failed: %w", err) } return nil diff --git a/internal/auth/kimi/token.go b/internal/auth/kimi/token.go index d4d06b64..15171d93 100644 --- a/internal/auth/kimi/token.go +++ b/internal/auth/kimi/token.go @@ -29,6 +29,15 @@ type KimiTokenStorage struct { Expired string `json:"expired,omitempty"` // Type indicates the authentication provider type, always "kimi" for this storage. Type string `json:"type"` + + // Metadata holds arbitrary key-value pairs injected via hooks. + // It is not exported to JSON directly to allow flattening during serialization. + Metadata map[string]any `json:"-"` +} + +// SetMetadata allows external callers to inject metadata into the storage before saving. +func (ts *KimiTokenStorage) SetMetadata(meta map[string]any) { + ts.Metadata = meta } // KimiTokenData holds the raw OAuth token response from Kimi. @@ -86,9 +95,26 @@ func (ts *KimiTokenStorage) SaveTokenToFile(authFilePath string) error { _ = f.Close() }() + // Convert struct to map for merging + data := make(map[string]any) + temp, errJson := json.Marshal(ts) + if errJson != nil { + return fmt.Errorf("failed to marshal struct: %w", errJson) + } + if errUnmarshal := json.Unmarshal(temp, &data); errUnmarshal != nil { + return fmt.Errorf("failed to unmarshal struct map: %w", errUnmarshal) + } + + // Merge extra metadata + if ts.Metadata != nil { + for k, v := range ts.Metadata { + data[k] = v + } + } + encoder := json.NewEncoder(f) encoder.SetIndent("", " ") - if err = encoder.Encode(ts); err != nil { + if err = encoder.Encode(data); err != nil { return fmt.Errorf("failed to write token to file: %w", err) } return nil diff --git a/internal/auth/qwen/qwen_token.go b/internal/auth/qwen/qwen_token.go index 4a2b3a2d..8037bdb7 100644 --- a/internal/auth/qwen/qwen_token.go +++ b/internal/auth/qwen/qwen_token.go @@ -30,11 +30,21 @@ type QwenTokenStorage struct { Type string `json:"type"` // Expire is the timestamp when the current access token expires. Expire string `json:"expired"` + + // Metadata holds arbitrary key-value pairs injected via hooks. + // It is not exported to JSON directly to allow flattening during serialization. + Metadata map[string]any `json:"-"` +} + +// SetMetadata allows external callers to inject metadata into the storage before saving. +func (ts *QwenTokenStorage) SetMetadata(meta map[string]any) { + ts.Metadata = meta } // SaveTokenToFile serializes the Qwen token storage to a JSON file. // This method creates the necessary directory structure and writes the token // data in JSON format to the specified file path for persistent storage. +// It merges any injected metadata into the top-level JSON object. // // Parameters: // - authFilePath: The full path where the token file should be saved @@ -56,7 +66,24 @@ func (ts *QwenTokenStorage) SaveTokenToFile(authFilePath string) error { _ = f.Close() }() - if err = json.NewEncoder(f).Encode(ts); err != nil { + // Convert struct to map for merging + data := make(map[string]any) + temp, errJson := json.Marshal(ts) + if errJson != nil { + return fmt.Errorf("failed to marshal struct: %w", errJson) + } + if errUnmarshal := json.Unmarshal(temp, &data); errUnmarshal != nil { + return fmt.Errorf("failed to unmarshal struct map: %w", errUnmarshal) + } + + // Merge extra metadata + if ts.Metadata != nil { + for k, v := range ts.Metadata { + data[k] = v + } + } + + if err = json.NewEncoder(f).Encode(data); err != nil { return fmt.Errorf("failed to write token to file: %w", err) } return nil