-
Notifications
You must be signed in to change notification settings - Fork 625
feat: add usage for cherryin #998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,70 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LLM_PROVIDER, MODEL_META, IConfigPresenter } from '@shared/presenter' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LLM_PROVIDER, MODEL_META, IConfigPresenter, KeyStatus } from '@shared/presenter' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { OpenAICompatibleProvider } from './openAICompatibleProvider' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface CherryInUsageResponse { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| total_usage: number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class CherryInProvider extends OpenAICompatibleProvider { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(provider: LLM_PROVIDER, configPresenter: IConfigPresenter) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super(provider, configPresenter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private getBaseUrl(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (this.provider.baseUrl || 'https://open.cherryin.ai/v1').replace(/\/$/, '') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async getKeyStatus(): Promise<KeyStatus> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!this.provider.apiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('API key is required') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseUrl = this.getBaseUrl() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headers = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Authorization: `Bearer ${this.provider.apiKey}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const usageResponse = await fetch(`${baseUrl}/dashboard/billing/usage`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: 'GET', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add timeout to prevent indefinite blocking. The fetch call lacks a timeout, which can cause the main process to hang indefinitely if the API is unresponsive. This violates the guideline about "blocking calls without timeouts on request threads." Apply this diff to add a timeout: - const usageResponse = await fetch(`${baseUrl}/dashboard/billing/usage`, {
- method: 'GET',
- headers
- })
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
+
+ try {
+ const usageResponse = await fetch(`${baseUrl}/dashboard/billing/usage`, {
+ method: 'GET',
+ headers,
+ signal: controller.signal
+ })
+ clearTimeout(timeoutId)
+
+ if (!usageResponse.ok) {
+ const errorText = await usageResponse.text()
+ throw new Error(
+ `CherryIn usage check failed: ${usageResponse.status} ${usageResponse.statusText} - ${errorText}`
+ )
+ }
+
+ const usageData: CherryInUsageResponse = await usageResponse.json()
+ // ... rest of the logic
+ } catch (error) {
+ clearTimeout(timeoutId)
+ if (error.name === 'AbortError') {
+ throw new Error('CherryIn usage check timed out after 10 seconds')
+ }
+ throw error
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!usageResponse.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const errorText = await usageResponse.text() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `CherryIn usage check failed: ${usageResponse.status} ${usageResponse.statusText} - ${errorText}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const usageData: CherryInUsageResponse = await usageResponse.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalUsage = Number(usageData?.total_usage) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const usageUsd = Number.isFinite(totalUsage) ? totalUsage / 100 : 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage: `$${usageUsd.toFixed(2)}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async check(): Promise<{ isOk: boolean; errorMsg: string | null }> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.getKeyStatus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { isOk: true, errorMsg: null } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error: unknown) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let errorMessage = 'An unknown error occurred during CherryIn API key check.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error instanceof Error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessage = error.message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeof error === 'string') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMessage = error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('CherryIn API key check failed:', error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { isOk: false, errorMsg: errorMessage } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected async fetchOpenAIModels(options?: { timeout: number }): Promise<MODEL_META[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const models = await super.fetchOpenAIModels(options) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add timeout to prevent indefinite hangs.
The fetch call lacks a timeout, which could cause the key status check to hang indefinitely if the CherryIn API is unresponsive.
Apply this diff to add an AbortController with timeout:
Update error handling to catch AbortError:
🤖 Prompt for AI Agents