Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented Jan 16, 2026

  • createAIRegistry関数を追加し、リクエストごとにProvider Registryを作成
  • aiServiceConfigを廃止し、createProviderRegistryを使用
  • getLanguageModel関数でregistry.languageModel()を使用してモデルを取得
  • streamAiText/generateAiTextの引数をregistry + service形式に変更
  • テストを新しいAPIに対応

Summary by CodeRabbit

  • 新機能

    • 複数AIプロバイダーを扱うレジストリベースのプロバイダ解決を導入し、プロバイダー選択と連携を改善しました。
    • 中国語ロケールの音声言語マッピングを拡張し、'zh' の扱いを明確化しました。
  • バグ修正

    • エラーハンドリングとAPIエラーレスポンスの表示を改善しました。
  • テスト

    • レジストリベースの動作とモデル解決を検証するテストを追加しました。

✏️ Tip: You can customize this high-level summary in your review settings.

- createAIRegistry関数を追加し、リクエストごとにProvider Registryを作成
- aiServiceConfigを廃止し、createProviderRegistryを使用
- getLanguageModel関数でregistry.languageModel()を使用してモデルを取得
- streamAiText/generateAiTextの引数をregistry + service形式に変更
- テストを新しいAPIに対応

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
aituber-kit Error Error Jan 16, 2026 4:37pm

@claude
Copy link
Contributor

claude bot commented Jan 16, 2026

Claude finished @tegnike's task —— View job


PR Review: AI SDK Provider Registry Refactoring

I've completed my review of this pull request. This refactoring introduces a more structured approach to AI provider management using the AI SDK's Provider Registry pattern. Overall, it's a solid architectural improvement with good test coverage.

Review Checklist:

  • Read and understand the architectural changes
  • Review main service file (vercelAi.ts) for code quality and potential issues
  • Review API endpoint changes (vercel.ts)
  • Analyze test file changes for coverage and quality
  • Check for security concerns and performance implications
  • Provide comprehensive feedback and recommendations

Positives

  1. Clean Architecture: The new Provider Registry pattern provides better separation of concerns and more maintainable code structure
  2. Comprehensive Provider Support: All 12 AI providers are properly implemented with appropriate configurations
  3. Excellent Test Coverage: Tests cover all major code paths including error scenarios, edge cases, and different provider configurations
  4. Type Safety: Good use of TypeScript with proper type annotations and generic types
  5. Error Handling: Consistent error handling with appropriate HTTP status codes and error messages

⚠️ Issues and Concerns

Type Safety Issues (High Priority)

Location: src/lib/api-services/vercelAi.ts:48-114

Multiple providers are being cast to unknown then back to ReturnType<typeof createOpenAI>, which defeats TypeScript's type safety:

// Lines 48-49, 53-54, 59-60, etc.
providers.anthropic = createAnthropic({
  apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>

Recommendation: Define a proper union type or use generics to maintain type safety while accommodating different provider types.

Unsafe Type Assertions (High Priority)

Location: src/lib/api-services/vercelAi.ts:138-143

The registry casting to access providers directly is unsafe:

const provider = (registry as unknown as Record<string, CallableFunction>)[service]

Recommendation: Create a proper interface or use type guards to safely access provider methods.

Security Concern (Medium Priority)

Location: src/pages/api/ai/vercel.ts:52-58

API key fallback logic could expose keys in logs:

const servicePrefix = aiService.toUpperCase()
aiApiKey = process.env[`${servicePrefix}_KEY`] || process.env[`${servicePrefix}_API_KEY`] || ''

Recommendation: Validate aiService input against an allowlist to prevent potential environment variable enumeration.

Error Handling Inconsistency (Low Priority)

Location: src/lib/api-services/vercelAi.ts:180-196, 231-247

Error handling is duplicated between streamAiText and generateAiText functions.

Recommendation: Extract error handling to a shared utility function.


🚀 Performance Considerations

  1. Registry Creation: Creating a new registry for each request is lightweight but consider caching if performance becomes an issue
  2. Provider Initialization: Current lazy initialization approach is good for performance
  3. Memory Usage: The refactoring doesn't introduce any obvious memory leaks

🔒 Security Assessment

  1. API Key Handling: Properly secured, keys are not logged in error messages
  2. Input Validation: Good validation of service types and model names
  3. Environment Variables: Secure pattern for API key retrieval

📋 Test Quality

  • Coverage: Excellent coverage of all major functions and error paths
  • Mocking Strategy: Well-structured mocks for external dependencies
  • Edge Cases: Tests cover null registries, failed API calls, and different provider configurations
  • Response Testing: Proper validation of response formats and status codes

🔧 Recommendations for Improvement

  1. Fix Type Safety: Address the type casting issues to maintain TypeScript benefits
  2. Input Validation: Add service name validation for security
  3. Error Handling: Consolidate duplicate error handling logic
  4. Documentation: Consider adding JSDoc comments for complex functions like getLanguageModel
  5. Constants: Extract hardcoded URLs (Groq, Perplexity, Fireworks) to constants

🎯 Summary

This is a well-executed refactoring that improves the codebase architecture. The Provider Registry pattern is a good choice for managing multiple AI providers. The main concerns are around type safety and some minor security considerations, but these don't prevent the PR from being merged.

Recommendation: Approve with minor fixes - Address the type safety issues and consider the security improvement, but the core refactoring is solid and ready for production.


@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Walkthrough

Vercel AIサービスの実装をモデルインスタンスベースからプロバイダレジストリベースへリファクタリング。新たに createAIRegistrygetLanguageModel を導入し、ストリーミング/生成エンドポイントのシグネチャを registry と service ベースに変更。

Changes

コホート / ファイル 変更概要
Vercel AI コア実装
src/lib/api-services/vercelAi.ts
ProviderParams / AIRegistry 型を追加。createAIRegistry(service, params) でプロバイダレジストリを構築。getLanguageModel(registry, service, model, options?) を追加し、streamAiText / generateAiText の署名を registryservice を受け取る形に変更。エラーハンドリングと公開エクスポートを更新。
API エンドポイント
src/pages/api/ai/vercel.ts
サービス初期化を createAIRegistry(aiService, { apiKey, baseURL, resourceName }) に置換。無効なサービスで 400 を返すバリデーション追加。以降の呼び出しは registryservice を渡す流れへ変更。
テストスイート
src/__tests__/lib/api-services/vercelAi.test.ts, src/__tests__/pages/api/vercel.test.ts
プロバイダ SDK(@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google)のモックを導入。createAIRegistry のモック/ファクトリと mockRegistry を用いるようにテストを更新。createAIRegistrygetLanguageModel の追加テストを追加し、既存の期待値をレジストリ経由の呼び出しに適応。
ユーティリティ
src/utils/voiceLanguage.ts
中国語ロケールのマッピングを調整:'zh''zh-TW' に割り当て、'zh-CN' は別ケースで明示的に 'zh-CN' を返すよう変更。

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant VercelAPI as "Vercel API\n(src/pages/api/ai/vercel.ts)"
  participant RegistryFactory as "createAIRegistry\n(src/lib/api-services/vercelAi.ts)"
  participant Registry as "AIRegistry"
  participant Resolver as "getLanguageModel\n(stream/generate entry)"
  participant ProviderSDK as "Provider SDK\n(`@ai-sdk/`*)"
  participant LanguageModel

  Client->>VercelAPI: リクエスト (service, model, options)
  VercelAPI->>RegistryFactory: createAIRegistry(service, {apiKey, baseURL, resourceName})
  RegistryFactory-->>VercelAPI: registry (AIRegistry)
  VercelAPI->>Resolver: getLanguageModel(registry, service, model, options)
  Resolver->>Registry: lookup provider / languageModel
  Registry->>ProviderSDK: initialize provider (if needed)
  ProviderSDK-->>Resolver: provider instance / languageModel
  Resolver-->>VercelAPI: LanguageModel
  VercelAPI->>LanguageModel: streamText / generateText (calls)
  LanguageModel-->>Client: streamed/generated response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

devin

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは「refactor: AI SDKのProvider Registry方式に移行」で、主な変更内容であるAI SDKのProvider Registry方式への移行を明確に表しており、実装内容と一致している。
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/ai-sdk-provider-registry


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c07a4b9 and 0e53a37.

📒 Files selected for processing (1)
  • src/utils/voiceLanguage.ts
🔇 Additional comments (1)
src/utils/voiceLanguage.ts (1)

12-16: LGTM!

中国語バリアントの処理が適切に実装されています:

  • 'zh'(汎用中国語)→ 'zh-TW'(繁体字・台湾)へのフォールスルー
  • 'zh-CN'(簡体字・中国)の明示的なケース追加

以前は 'zh''zh-CN' がデフォルトの 'ja-JP' にフォールスルーしていた問題が修正されています。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/lib/api-services/vercelAi.ts`:
- Around line 42-119: The switch on service (in the block that populates
providers) lacks a default branch, so adding a default case that throws a clear
runtime error (e.g., throw new Error(`Unsupported VercelAIService: ${service}`))
or otherwise returns null will prevent accidentally calling
createProviderRegistry with an empty providers object when a new VercelAIService
value is added; update the switch(service) to include this default branch
referencing the same providers variable and ensure callers of
createProviderRegistry will not receive an empty providers map.
- Around line 135-144: When options are provided but the provider lookup
((registry as unknown as Record<string, CallableFunction>)[service]) returns
undefined, add a warning log so the omission of options isn't silent;
specifically, inside the branch that checks options &&
Object.keys(options).length > 0, detect when provider is falsy and log a clear
warning (including the service name and the options payload) before falling back
to registry.languageModel(model). Update the code around the provider
lookup/return (the provider variable, service, model, registry.languageModel
cast to LanguageModel) to emit this warning via the module's logger (or
console.warn if no logger is available).
🧹 Nitpick comments (6)
src/lib/api-services/vercelAi.ts (1)

31-31: Record<string, any>の使用による型安全性の低下

AIRegistryの型定義でRecord<string, any>を使用しており、プロバイダーの型情報が失われます。現時点では動作しますが、将来的なリファクタリングでの型チェックが効かなくなるリスクがあります。

src/__tests__/lib/api-services/vercelAi.test.ts (1)

93-110: createAIRegistryのテストカバレッジ

openaigooglecustom-apiのケースはテストされていますが、他のプロバイダー(azureanthropicなど)のテストがありません。少なくともazureresourceNameパラメータを使用するため、テスト追加を検討してください。

🧪 提案: azureのテスト追加
     it('returns null for custom-api service', () => {
       const registry = createAIRegistry('custom-api', {})
       expect(registry).toBeNull()
     })
+
+    it('creates registry for azure service with resourceName', () => {
+      const registry = createAIRegistry('azure', {
+        apiKey: 'azure-key',
+        resourceName: 'my-resource',
+      })
+      expect(registry).toBeDefined()
+      expect(mockCreateProviderRegistry).toHaveBeenCalled()
+    })
   })
src/__tests__/pages/api/vercel.test.ts (2)

166-170: baseURLの値が異なるケースのテスト

googleではbaseURL: ''azureではbaseURL: undefinedが期待されています。これはハンドラーの実際の動作を反映していますが、一貫性のためにハンドラー側でlocalLlmUrl || undefinedのような正規化を検討しても良いかもしれません。


77-220: custom-api(registry === null)のテストケースがありません

createAIRegistrynullを返した場合の400エラーレスポンスをテストするケースの追加を検討してください。

🧪 提案: nullレジストリのテスト追加
it('returns 400 when registry is null for custom-api', async () => {
  mockCreateAIRegistry.mockReturnValue(null)
  
  const res = await handler(
    buildRequest({
      messages: [],
      apiKey: 'test-key',
      aiService: 'custom-api',
      model: 'custom-model',
      stream: true,
      temperature: 1,
      maxTokens: 10,
    })
  )

  expect(res.status).toBe(400)
  await expect(res.json()).resolves.toEqual({
    error: 'Invalid AI service',
    errorCode: 'InvalidAIService',
  })
})
src/pages/api/ai/vercel.ts (2)

158-158: デバッグログが残っています

console.log('options', options)はデバッグ用と思われます。本番環境では不要な情報がログに出力される可能性があるため、削除を検討してください。

🔧 提案: デバッグログの削除
-    console.log('options', options)

162-179: 型キャストの重複

aiService as VercelAIServiceがLines 114、165、175で繰り返されています。Line 114で型付き変数に代入すれば、後続のキャストは不要になります。

♻️ 提案: 型キャストの統一
     // Provider Registryの作成
+    const service = aiService as VercelAIService
-    const registry = createAIRegistry(aiService as VercelAIService, {
+    const registry = createAIRegistry(service, {
       apiKey: aiApiKey,
       baseURL: localLlmUrl,
       resourceName: modifiedAzureEndpoint,
     })

そして後続の呼び出しで:

       return await streamAiText({
         model: modifiedModel,
         registry,
-        service: aiService as VercelAIService,
+        service,
         messages: modifiedMessages,
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2105c5 and c07a4b9.

📒 Files selected for processing (4)
  • src/__tests__/lib/api-services/vercelAi.test.ts
  • src/__tests__/pages/api/vercel.test.ts
  • src/lib/api-services/vercelAi.ts
  • src/pages/api/ai/vercel.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/__tests__/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

テストは__tests__ディレクトリに配置

Files:

  • src/__tests__/pages/api/vercel.test.ts
  • src/__tests__/lib/api-services/vercelAi.test.ts
🧠 Learnings (1)
📚 Learning: 2025-08-05T17:30:02.776Z
Learnt from: CR
Repo: tegnike/aituber-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T17:30:02.776Z
Learning: Node.js環境用にcanvasをモック化済み

Applied to files:

  • src/__tests__/pages/api/vercel.test.ts
🧬 Code graph analysis (4)
src/__tests__/pages/api/vercel.test.ts (1)
src/lib/api-services/vercelAi.ts (1)
  • createAIRegistry (36-122)
src/lib/api-services/vercelAi.ts (1)
src/features/constants/settings.ts (1)
  • VercelAIService (26-26)
src/pages/api/ai/vercel.ts (2)
src/lib/api-services/vercelAi.ts (1)
  • createAIRegistry (36-122)
src/features/constants/settings.ts (1)
  • VercelAIService (26-26)
src/__tests__/lib/api-services/vercelAi.test.ts (1)
src/lib/api-services/vercelAi.ts (2)
  • createAIRegistry (36-122)
  • getLanguageModel (127-147)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: claude-review
🔇 Additional comments (5)
src/lib/api-services/vercelAi.ts (1)

180-183: エラーハンドリングの改善は良い変更です

error: unknownの型付けとinstanceof Errorチェックにより、型安全なエラーメッセージ抽出が実現されています。両関数で一貫したパターンが使用されている点も良いです。

src/__tests__/lib/api-services/vercelAi.test.ts (2)

112-133: getLanguageModelのテストは適切です

オプションなし・ありの両方のコードパスがテストされており、引数の検証も行われています。


135-225: streamAiTextとgenerateAiTextのテストが新しいAPIに正しく対応しています

registryserviceパラメータへの移行が適切にテストされており、エラーケースも含めてカバレッジが維持されています。

src/__tests__/pages/api/vercel.test.ts (1)

41-45: mockRegistryの構造が実装と一致しています

languageModelメソッドとプロバイダー別のメソッド(googleazure)を持つモックは、実際のレジストリ構造を適切に模倣しています。

src/pages/api/ai/vercel.ts (1)

113-131: レジストリベースのアーキテクチャへの移行は適切です

createAIRegistryを使用した動的プロバイダー作成と、nullチェックによるcustom-apiケースの処理が明確に実装されています。エラーレスポンスも適切な形式です。

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +42 to +119
switch (service) {
case 'openai':
providers.openai = createOpenAI({ apiKey: params.apiKey })
break
case 'anthropic':
providers.anthropic = createAnthropic({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'google':
providers.google = createGoogleGenerativeAI({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'azure':
providers.azure = createAzure({
resourceName: params.resourceName,
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'xai':
providers.xai = createXai({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'groq':
providers.groq = createOpenAI({
baseURL: 'https://api.groq.com/openai/v1',
apiKey: params.apiKey,
})
break
case 'cohere':
providers.cohere = createCohere({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'mistralai':
providers.mistralai = createMistral({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'perplexity':
providers.perplexity = createOpenAI({
baseURL: 'https://api.perplexity.ai/',
apiKey: params.apiKey,
})
break
case 'fireworks':
providers.fireworks = createOpenAI({
baseURL: 'https://api.fireworks.ai/inference/v1',
apiKey: params.apiKey,
})
break
case 'deepseek':
providers.deepseek = createDeepSeek({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'openrouter':
providers.openrouter = createOpenRouter({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'lmstudio':
providers.lmstudio = createOpenAICompatible({
name: 'lmstudio',
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'ollama':
providers.ollama = createOllama({
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'custom-api':
// custom-apiは別途処理されるため、ここでは空
return null
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

switch文にdefaultケースがありません

VercelAIService型に新しいサービスが追加された場合、switch文がどのケースにもマッチせず、空のprovidersオブジェクトでcreateProviderRegistryが呼ばれます。これは実行時エラーの原因になる可能性があります。

🔧 提案: defaultケースの追加
     case 'custom-api':
       // custom-apiは別途処理されるため、ここでは空
       return null
+    default:
+      // 未知のサービスの場合はnullを返す
+      console.warn(`Unknown AI service: ${service}`)
+      return null
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch (service) {
case 'openai':
providers.openai = createOpenAI({ apiKey: params.apiKey })
break
case 'anthropic':
providers.anthropic = createAnthropic({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'google':
providers.google = createGoogleGenerativeAI({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'azure':
providers.azure = createAzure({
resourceName: params.resourceName,
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'xai':
providers.xai = createXai({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'groq':
providers.groq = createOpenAI({
baseURL: 'https://api.groq.com/openai/v1',
apiKey: params.apiKey,
})
break
case 'cohere':
providers.cohere = createCohere({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'mistralai':
providers.mistralai = createMistral({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'perplexity':
providers.perplexity = createOpenAI({
baseURL: 'https://api.perplexity.ai/',
apiKey: params.apiKey,
})
break
case 'fireworks':
providers.fireworks = createOpenAI({
baseURL: 'https://api.fireworks.ai/inference/v1',
apiKey: params.apiKey,
})
break
case 'deepseek':
providers.deepseek = createDeepSeek({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'openrouter':
providers.openrouter = createOpenRouter({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'lmstudio':
providers.lmstudio = createOpenAICompatible({
name: 'lmstudio',
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'ollama':
providers.ollama = createOllama({
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'custom-api':
// custom-apiは別途処理されるため、ここでは空
return null
}
switch (service) {
case 'openai':
providers.openai = createOpenAI({ apiKey: params.apiKey })
break
case 'anthropic':
providers.anthropic = createAnthropic({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'google':
providers.google = createGoogleGenerativeAI({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'azure':
providers.azure = createAzure({
resourceName: params.resourceName,
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'xai':
providers.xai = createXai({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'groq':
providers.groq = createOpenAI({
baseURL: 'https://api.groq.com/openai/v1',
apiKey: params.apiKey,
})
break
case 'cohere':
providers.cohere = createCohere({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'mistralai':
providers.mistralai = createMistral({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'perplexity':
providers.perplexity = createOpenAI({
baseURL: 'https://api.perplexity.ai/',
apiKey: params.apiKey,
})
break
case 'fireworks':
providers.fireworks = createOpenAI({
baseURL: 'https://api.fireworks.ai/inference/v1',
apiKey: params.apiKey,
})
break
case 'deepseek':
providers.deepseek = createDeepSeek({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'openrouter':
providers.openrouter = createOpenRouter({
apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'lmstudio':
providers.lmstudio = createOpenAICompatible({
name: 'lmstudio',
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'ollama':
providers.ollama = createOllama({
baseURL: params.baseURL ?? '',
}) as unknown as ReturnType<typeof createOpenAI>
break
case 'custom-api':
// custom-apiは別途処理されるため、ここでは空
return null
default:
// 未知のサービスの場合はnullを返す
console.warn(`Unknown AI service: ${service}`)
return null
}
🤖 Prompt for AI Agents
In `@src/lib/api-services/vercelAi.ts` around lines 42 - 119, The switch on
service (in the block that populates providers) lacks a default branch, so
adding a default case that throws a clear runtime error (e.g., throw new
Error(`Unsupported VercelAIService: ${service}`)) or otherwise returns null will
prevent accidentally calling createProviderRegistry with an empty providers
object when a new VercelAIService value is added; update the switch(service) to
include this default branch referencing the same providers variable and ensure
callers of createProviderRegistry will not receive an empty providers map.

Comment on lines +135 to +144
if (options && Object.keys(options).length > 0) {
// オプションがある場合(例:Google Search Grounding)
// registryから直接プロバイダーを取得してオプション付きでモデルを作成
const provider = (registry as unknown as Record<string, CallableFunction>)[
service
]
if (provider) {
return provider(model, options) as LanguageModel
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

optionsが無視される可能性がある

optionsが提供されているがproviderが見つからない場合、Line 146のregistry.languageModel()へフォールスルーし、optionsが暗黙的に無視されます。Google Search Groundingなどの機能が動作しない可能性があります。

🔧 提案: 警告ログの追加
   if (options && Object.keys(options).length > 0) {
     // オプションがある場合(例:Google Search Grounding)
     // registryから直接プロバイダーを取得してオプション付きでモデルを作成
     const provider = (registry as unknown as Record<string, CallableFunction>)[
       service
     ]
     if (provider) {
       return provider(model, options) as LanguageModel
     }
+    console.warn(`Provider ${service} not found in registry, options will be ignored`)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (options && Object.keys(options).length > 0) {
// オプションがある場合(例:Google Search Grounding)
// registryから直接プロバイダーを取得してオプション付きでモデルを作成
const provider = (registry as unknown as Record<string, CallableFunction>)[
service
]
if (provider) {
return provider(model, options) as LanguageModel
}
}
if (options && Object.keys(options).length > 0) {
// オプションがある場合(例:Google Search Grounding)
// registryから直接プロバイダーを取得してオプション付きでモデルを作成
const provider = (registry as unknown as Record<string, CallableFunction>)[
service
]
if (provider) {
return provider(model, options) as LanguageModel
}
console.warn(`Provider ${service} not found in registry, options will be ignored`)
}
🤖 Prompt for AI Agents
In `@src/lib/api-services/vercelAi.ts` around lines 135 - 144, When options are
provided but the provider lookup ((registry as unknown as Record<string,
CallableFunction>)[service]) returns undefined, add a warning log so the
omission of options isn't silent; specifically, inside the branch that checks
options && Object.keys(options).length > 0, detect when provider is falsy and
log a clear warning (including the service name and the options payload) before
falling back to registry.languageModel(model). Update the code around the
provider lookup/return (the provider variable, service, model,
registry.languageModel cast to LanguageModel) to emit this warning via the
module's logger (or console.warn if no logger is available).

テストで期待されていたzh→zh-TWのマッピングが
実装に欠けていたため追加

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Jan 16, 2026

Claude finished @tegnike's task —— View job


PR Review: AI SDK Provider Registry Refactoring

I've completed my review of this pull request. This refactoring introduces a more structured approach to AI provider management using the AI SDK's Provider Registry pattern. Overall, it's a solid architectural improvement with good test coverage.

Review Checklist:

  • Read and understand the architectural changes
  • Review main service file (vercelAi.ts) for code quality and potential issues
  • Review API endpoint changes (vercel.ts)
  • Analyze test file changes for coverage and quality
  • Check for security concerns and performance implications
  • Provide comprehensive feedback and recommendations

Positives

  1. Clean Architecture: The new Provider Registry pattern provides better separation of concerns and more maintainable code structure
  2. Comprehensive Provider Support: All 12 AI providers are properly implemented with appropriate configurations
  3. Excellent Test Coverage: Tests cover all major code paths including error scenarios, edge cases, and different provider configurations
  4. Type Safety: Good use of TypeScript with proper type annotations and generic types
  5. Error Handling: Consistent error handling with appropriate HTTP status codes and error messages

⚠️ Issues and Concerns

Type Safety Issues (High Priority)

Location: src/lib/api-services/vercelAi.ts:48-114

Multiple providers are being cast to unknown then back to ReturnType<typeof createOpenAI>, which defeats TypeScript's type safety:

// Lines 48-49, 53-54, 59-60, etc.
providers.anthropic = createAnthropic({
  apiKey: params.apiKey,
}) as unknown as ReturnType<typeof createOpenAI>

Recommendation: Define a proper union type or use generics to maintain type safety while accommodating different provider types.

Unsafe Type Assertions (High Priority)

Location: src/lib/api-services/vercelAi.ts:138-143

The registry casting to access providers directly is unsafe:

const provider = (registry as unknown as Record<string, CallableFunction>)[service]

Recommendation: Create a proper interface or use type guards to safely access provider methods.

Security Concern (Medium Priority)

Location: src/pages/api/ai/vercel.ts:52-58

API key fallback logic could expose keys in logs:

const servicePrefix = aiService.toUpperCase()
aiApiKey = process.env[`${servicePrefix}_KEY`] || process.env[`${servicePrefix}_API_KEY`] || ''

Recommendation: Validate aiService input against an allowlist to prevent potential environment variable enumeration.

Error Handling Inconsistency (Low Priority)

Location: src/lib/api-services/vercelAi.ts:180-196, 231-247

Error handling is duplicated between streamAiText and generateAiText functions.

Recommendation: Extract error handling to a shared utility function.


🚀 Performance Considerations

  1. Registry Creation: Creating a new registry for each request is lightweight but consider caching if performance becomes an issue
  2. Provider Initialization: Current lazy initialization approach is good for performance
  3. Memory Usage: The refactoring doesn't introduce any obvious memory leaks

🔒 Security Assessment

  1. API Key Handling: Properly secured, keys are not logged in error messages
  2. Input Validation: Good validation of service types and model names
  3. Environment Variables: Secure pattern for API key retrieval

📋 Test Quality

  • Coverage: Excellent coverage of all major functions and error paths
  • Mocking Strategy: Well-structured mocks for external dependencies
  • Edge Cases: Tests cover null registries, failed API calls, and different provider configurations
  • Response Testing: Proper validation of response formats and status codes

🔧 Recommendations for Improvement

  1. Fix Type Safety: Address the type casting issues to maintain TypeScript benefits
  2. Input Validation: Add service name validation for security
  3. Error Handling: Consolidate duplicate error handling logic
  4. Documentation: Consider adding JSDoc comments for complex functions like getLanguageModel
  5. Constants: Extract hardcoded URLs (Groq, Perplexity, Fireworks) to constants

🎯 Summary

This is a well-executed refactoring that improves the codebase architecture. The Provider Registry pattern is a good choice for managing multiple AI providers. The main concerns are around type safety and some minor security considerations, but these don't prevent the PR from being merged.

Recommendation: Approve with minor fixes - Address the type safety issues and consider the security improvement, but the core refactoring is solid and ready for production.


@tegnike tegnike merged commit 1974b15 into develop Jan 16, 2026
5 of 6 checks passed
@tegnike tegnike deleted the refactor/ai-sdk-provider-registry branch January 16, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants