mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 04:18:44 +00:00
fix: allow OAuth providers in media tool chain (read_audio, read_image, etc.) (#150)
ExecuteWithChain previously required all providers to implement credentialProvider (APIKey/APIBase). OAuth-based providers like CodexProvider (ChatGPT OAuth) don't expose static credentials, causing all media tools to fail with "does not expose API credentials". Make credentialProvider optional (nil when unsupported). Each callProvider gracefully falls back to the provider's Chat() API when credentials are unavailable. Generation tools (create_image, create_video, create_audio) return a clear error since they require direct API access with no Chat fallback. Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
This commit is contained in:
1 parent
2fdb791802
commit
fa5f51e72e
7 files changed
+36
-26
No files matched your search
@@ -173,6 +173,9 @@ func (t *CreateAudioTool) Execute(ctx context.Context, args map[string]any) *Res
|
||||
|
||||
// callProvider dispatches to the correct music generation implementation based on provider type.
|
||||
func (t *CreateAudioTool) callProvider(ctx context.Context, cp credentialProvider, providerName, model string, params map[string]any) ([]byte, *providers.Usage, error) {
|
||||
if cp == nil {
|
||||
return nil, nil, fmt.Errorf("provider %q does not expose API credentials required for audio generation", providerName)
|
||||
}
|
||||
prompt := GetParamString(params, "prompt", "")
|
||||
|
||||
slog.Info("create_audio: calling music generation API",
|
||||
|
||||
@@ -129,6 +129,9 @@ func (t *CreateImageTool) Execute(ctx context.Context, args map[string]any) *Res
|
||||
|
||||
// callProvider dispatches to the correct image generation implementation based on provider type.
|
||||
func (t *CreateImageTool) callProvider(ctx context.Context, cp credentialProvider, providerName, model string, params map[string]any) ([]byte, *providers.Usage, error) {
|
||||
if cp == nil {
|
||||
return nil, nil, fmt.Errorf("provider %q does not expose API credentials required for image generation", providerName)
|
||||
}
|
||||
prompt := GetParamString(params, "prompt", "")
|
||||
aspectRatio := GetParamString(params, "aspect_ratio", "1:1")
|
||||
|
||||
|
||||
@@ -140,6 +140,9 @@ func (t *CreateVideoTool) Execute(ctx context.Context, args map[string]any) *Res
|
||||
|
||||
// callProvider dispatches to the correct video generation implementation based on provider type.
|
||||
func (t *CreateVideoTool) callProvider(ctx context.Context, cp credentialProvider, providerName, model string, params map[string]any) ([]byte, *providers.Usage, error) {
|
||||
if cp == nil {
|
||||
return nil, nil, fmt.Errorf("provider %q does not expose API credentials required for video generation", providerName)
|
||||
}
|
||||
prompt := GetParamString(params, "prompt", "")
|
||||
duration := GetParamInt(params, "duration", 8)
|
||||
aspectRatio := GetParamString(params, "aspect_ratio", "16:9")
|
||||
|
||||
@@ -169,13 +169,10 @@ func ExecuteWithChain(
|
||||
continue
|
||||
}
|
||||
|
||||
cp, ok := p.(credentialProvider)
|
||||
if !ok {
|
||||
slog.Warn("media_chain: provider does not expose credentials, skipping",
|
||||
"provider", entry.Provider)
|
||||
lastErr = fmt.Errorf("provider %q does not expose API credentials", entry.Provider)
|
||||
continue
|
||||
}
|
||||
// credentialProvider is optional — providers that don't expose static
|
||||
// credentials (e.g. OAuth-based CodexProvider) pass nil and each
|
||||
// callProvider falls back to using the provider's Chat() API.
|
||||
cp, _ := p.(credentialProvider)
|
||||
|
||||
// Retry loop for this provider
|
||||
for attempt := 1; attempt <= entry.MaxRetries; attempt++ {
|
||||
|
||||
@@ -60,24 +60,28 @@ func (t *ReadAudioTool) callProvider(ctx context.Context, cp credentialProvider,
|
||||
data, _ := params["data"].([]byte)
|
||||
mime := GetParamString(params, "mime", "audio/mpeg")
|
||||
|
||||
// Gemini: use File API (inlineData doesn't work for audio).
|
||||
if strings.HasPrefix(providerName, "gemini") {
|
||||
slog.Info("read_audio: using gemini file API", "provider", providerName, "model", model, "size", len(data), "mime", mime)
|
||||
resp, err := geminiFileAPICall(ctx, cp.APIKey(), model, prompt, data, mime, 120*time.Second)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("gemini file API: %w", err)
|
||||
// Provider-specific paths require API credentials; skip when cp is nil
|
||||
// (e.g. OAuth-based providers that don't expose static keys).
|
||||
if cp != nil {
|
||||
// Gemini: use File API (inlineData doesn't work for audio).
|
||||
if strings.HasPrefix(providerName, "gemini") {
|
||||
slog.Info("read_audio: using gemini file API", "provider", providerName, "model", model, "size", len(data), "mime", mime)
|
||||
resp, err := geminiFileAPICall(ctx, cp.APIKey(), model, prompt, data, mime, 120*time.Second)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("gemini file API: %w", err)
|
||||
}
|
||||
return []byte(resp.Content), resp.Usage, nil
|
||||
}
|
||||
return []byte(resp.Content), resp.Usage, nil
|
||||
}
|
||||
|
||||
// OpenAI: use input_audio content part (supports wav, mp3).
|
||||
if strings.HasPrefix(providerName, "openai") {
|
||||
slog.Info("read_audio: using openai input_audio API", "provider", providerName, "model", model, "size", len(data), "mime", mime)
|
||||
resp, err := openaiAudioCall(ctx, cp.APIKey(), cp.APIBase(), model, prompt, data, mime)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("openai audio call: %w", err)
|
||||
// OpenAI: use input_audio content part (supports wav, mp3).
|
||||
if strings.HasPrefix(providerName, "openai") {
|
||||
slog.Info("read_audio: using openai input_audio API", "provider", providerName, "model", model, "size", len(data), "mime", mime)
|
||||
resp, err := openaiAudioCall(ctx, cp.APIKey(), cp.APIBase(), model, prompt, data, mime)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("openai audio call: %w", err)
|
||||
}
|
||||
return []byte(resp.Content), resp.Usage, nil
|
||||
}
|
||||
return []byte(resp.Content), resp.Usage, nil
|
||||
}
|
||||
|
||||
// Other providers: try standard Chat API with base64 audio as image_url (best effort).
|
||||
|
||||
@@ -61,8 +61,8 @@ func (t *ReadDocumentTool) callProvider(ctx context.Context, cp credentialProvid
|
||||
data, _ := params["data"].([]byte)
|
||||
mime := GetParamString(params, "mime", "application/octet-stream")
|
||||
|
||||
// Gemini: use native API (OpenAI-compat endpoint doesn't support non-image MIME types).
|
||||
if strings.HasPrefix(providerName, "gemini") {
|
||||
// Gemini: use native API (requires credentials; OpenAI-compat endpoint doesn't support non-image MIME types).
|
||||
if cp != nil && strings.HasPrefix(providerName, "gemini") {
|
||||
slog.Info("read_document: using gemini native API",
|
||||
"provider", providerName, "model", model,
|
||||
"doc_size", len(data), "mime", mime)
|
||||
|
||||
@@ -59,8 +59,8 @@ func (t *ReadVideoTool) callProvider(ctx context.Context, cp credentialProvider,
|
||||
data, _ := params["data"].([]byte)
|
||||
mime := GetParamString(params, "mime", "video/mp4")
|
||||
|
||||
// Gemini: use File API.
|
||||
if strings.HasPrefix(providerName, "gemini") {
|
||||
// Gemini: use File API (requires credentials).
|
||||
if cp != nil && strings.HasPrefix(providerName, "gemini") {
|
||||
slog.Info("read_video: using gemini file API", "provider", providerName, "model", model, "size", len(data), "mime", mime)
|
||||
resp, err := geminiFileAPICall(ctx, cp.APIKey(), model, prompt, data, mime, 180*time.Second)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user