import type { AuthResponse, BaseModelCatalogItem, BaseModelUpsertRequest, CatalogProvider, CatalogProviderUpsertRequest, CreatedGatewayApiKey, FileStorageChannel, FileStorageSettings, FileStorageSettingsUpdateRequest, FileStorageChannelUpsertRequest, GatewayAccessRuleBatchRequest, GatewayAccessRule, GatewayAccessRuleUpsertRequest, GatewayApiKey, GatewayAuditLog, GatewayRunnerPolicy, GatewayRunnerPolicyUpsertRequest, GatewayTenant, GatewayTenantUpsertRequest, GatewayNetworkProxyConfig, GatewayPricingEstimate, GatewayTask, GatewayTaskParamPreprocessingLog, GatewayUser, GatewayUserUpsertRequest, GatewayWalletTransaction, IntegrationPlatform, ListResponse, ModelCatalogResponse, ModelRateLimitStatus, PlatformDynamicPriorityState, PlatformDynamicPriorityUpdateRequest, PlatformModel, PlayableGatewayApiKey, PricingRule, PricingRuleSet, PricingRuleSetUpsertRequest, RateLimitWindow, RuntimePolicySet, RuntimePolicySetUpsertRequest, UserGroup, UserGroupUpsertRequest, WalletAdjustmentResponse, WalletBalanceAdjustmentRequest, WalletSummaryResponse, } from '@easyai-ai-gateway/contracts'; import type { PlatformCreateInput, PlatformModelBindingInput, WorkspaceTaskQuery } from './types'; const API_BASE = import.meta.env.VITE_GATEWAY_API_BASE_URL ?? 'http://localhost:8088'; interface GatewayErrorDetails { code?: string; message: string; requestId?: string; status?: number; taskId?: string; } export class GatewayApiError extends Error { readonly details: GatewayErrorDetails; constructor(details: GatewayErrorDetails | string) { const normalized = typeof details === 'string' ? { message: details } : details; super(formatGatewayErrorDetails(normalized)); this.name = 'GatewayApiError'; this.details = normalized; } toString() { return this.message; } } export interface HealthResponse { ok: boolean; service: string; env: string; identityMode?: string; } export async function getHealth(): Promise { return request('/healthz', { auth: false }); } export async function registerLocalAccount(input: { username: string; email?: string; password: string; displayName?: string; invitationCode?: string; }): Promise { return request('/api/v1/auth/register', { auth: false, body: input, method: 'POST', }); } export async function loginLocalAccount(input: { account: string; password: string }): Promise { return request('/api/v1/auth/login', { auth: false, body: input, method: 'POST', }); } export async function listPlatforms(token: string): Promise> { return request>('/api/admin/platforms', { token }); } export async function updatePlatformDynamicPriority( token: string, platformId: string, input: PlatformDynamicPriorityUpdateRequest, ): Promise { return request(`/api/admin/platforms/${platformId}/dynamic-priority`, { body: input, method: 'PATCH', token, }); } export async function listModels(token: string): Promise> { return request>('/api/admin/models', { token }); } export async function listPlayableModels(token: string): Promise> { return request>('/api/v1/models', { token }); } export async function listModelCatalog(token: string): Promise { return request('/api/v1/model-catalog', { token }); } export async function listPublicCatalogProviders(): Promise> { return request>('/api/v1/public/catalog/providers', { auth: false }); } export async function listCatalogProviders(token: string): Promise> { return request>('/api/admin/catalog/providers', { token }); } export async function createCatalogProvider( token: string, input: CatalogProviderUpsertRequest, ): Promise { return request('/api/admin/catalog/providers', { body: input, method: 'POST', token, }); } export async function updateCatalogProvider( token: string, providerId: string, input: CatalogProviderUpsertRequest, ): Promise { return request(`/api/admin/catalog/providers/${providerId}`, { body: input, method: 'PATCH', token, }); } export async function deleteCatalogProvider(token: string, providerId: string): Promise { await request(`/api/admin/catalog/providers/${providerId}`, { method: 'DELETE', token, }); } export async function listPublicBaseModels(): Promise> { return request>('/api/v1/public/catalog/base-models', { auth: false }); } export async function listBaseModels(token: string): Promise> { return request>('/api/admin/catalog/base-models', { token }); } export async function createBaseModel(token: string, input: BaseModelUpsertRequest): Promise { return request('/api/admin/catalog/base-models', { body: input, method: 'POST', token, }); } export async function updateBaseModel( token: string, baseModelId: string, input: BaseModelUpsertRequest, ): Promise { return request(`/api/admin/catalog/base-models/${baseModelId}`, { body: input, method: 'PATCH', token, }); } export async function resetBaseModel(token: string, baseModelId: string): Promise { return request(`/api/admin/catalog/base-models/${baseModelId}/reset`, { method: 'POST', token, }); } export async function resetAllBaseModels(token: string): Promise> { return request>('/api/admin/catalog/base-models/reset-all', { method: 'POST', token, }); } export async function deleteBaseModel(token: string, baseModelId: string): Promise { await request(`/api/admin/catalog/base-models/${baseModelId}`, { method: 'DELETE', token, }); } export async function listPricingRules(token: string): Promise> { return request>('/api/admin/pricing/rules', { token }); } export async function listPricingRuleSets(token: string): Promise> { return request>('/api/admin/pricing/rule-sets', { token }); } export async function createPricingRuleSet( token: string, input: PricingRuleSetUpsertRequest, ): Promise { return request('/api/admin/pricing/rule-sets', { body: input, method: 'POST', token, }); } export async function updatePricingRuleSet( token: string, ruleSetId: string, input: PricingRuleSetUpsertRequest, ): Promise { return request(`/api/admin/pricing/rule-sets/${ruleSetId}`, { body: input, method: 'PATCH', token, }); } export async function deletePricingRuleSet(token: string, ruleSetId: string): Promise { await request(`/api/admin/pricing/rule-sets/${ruleSetId}`, { method: 'DELETE', token, }); } export async function listRuntimePolicySets(token: string): Promise> { return request>('/api/admin/runtime/policy-sets', { token }); } export async function getRunnerPolicy(token: string): Promise { return request('/api/admin/runtime/runner-policy', { token }); } export async function updateRunnerPolicy( token: string, input: GatewayRunnerPolicyUpsertRequest, ): Promise { return request('/api/admin/runtime/runner-policy', { body: input, method: 'PATCH', token, }); } export async function createRuntimePolicySet( token: string, input: RuntimePolicySetUpsertRequest, ): Promise { return request('/api/admin/runtime/policy-sets', { body: input, method: 'POST', token, }); } export async function updateRuntimePolicySet( token: string, policySetId: string, input: RuntimePolicySetUpsertRequest, ): Promise { return request(`/api/admin/runtime/policy-sets/${policySetId}`, { body: input, method: 'PATCH', token, }); } export async function deleteRuntimePolicySet(token: string, policySetId: string): Promise { await request(`/api/admin/runtime/policy-sets/${policySetId}`, { method: 'DELETE', token, }); } export async function listTenants(token: string): Promise> { return request>('/api/admin/tenants', { token }); } export async function createTenant(token: string, input: GatewayTenantUpsertRequest): Promise { return request('/api/admin/tenants', { body: input, method: 'POST', token, }); } export async function updateTenant(token: string, tenantId: string, input: GatewayTenantUpsertRequest): Promise { return request(`/api/admin/tenants/${tenantId}`, { body: input, method: 'PATCH', token, }); } export async function deleteTenant(token: string, tenantId: string): Promise { await request(`/api/admin/tenants/${tenantId}`, { method: 'DELETE', token, }); } export async function listUsers(token: string): Promise> { return request>('/api/admin/users', { token }); } export async function createGatewayUser(token: string, input: GatewayUserUpsertRequest): Promise { return request('/api/admin/users', { body: input, method: 'POST', token, }); } export async function updateGatewayUser(token: string, userId: string, input: GatewayUserUpsertRequest): Promise { return request(`/api/admin/users/${userId}`, { body: input, method: 'PATCH', token, }); } export async function setUserWalletBalance( token: string, userId: string, input: WalletBalanceAdjustmentRequest, ): Promise { return request(`/api/admin/users/${userId}/wallet`, { body: input, method: 'PATCH', token, }); } export async function deleteGatewayUser(token: string, userId: string): Promise { await request(`/api/admin/users/${userId}`, { method: 'DELETE', token, }); } export async function listAuditLogs(token: string): Promise> { return request>('/api/admin/audit-logs', { token }); } export async function listUserGroups(token: string): Promise> { return request>('/api/admin/user-groups', { token }); } export async function createUserGroup(token: string, input: UserGroupUpsertRequest): Promise { return request('/api/admin/user-groups', { body: input, method: 'POST', token, }); } export async function updateUserGroup(token: string, groupId: string, input: UserGroupUpsertRequest): Promise { return request(`/api/admin/user-groups/${groupId}`, { body: input, method: 'PATCH', token, }); } export async function deleteUserGroup(token: string, groupId: string): Promise { await request(`/api/admin/user-groups/${groupId}`, { method: 'DELETE', token, }); } export async function listAccessRules(token: string): Promise> { return request>('/api/admin/access-rules', { token }); } export async function listApiKeyAccessRules(token: string): Promise> { return request>('/api/v1/api-keys/access-rules', { token }); } export async function createAccessRule(token: string, input: GatewayAccessRuleUpsertRequest): Promise { return request('/api/admin/access-rules', { body: input, method: 'POST', token, }); } export async function batchAccessRules(token: string, input: GatewayAccessRuleBatchRequest): Promise> { return request>('/api/admin/access-rules/batch', { body: input, method: 'POST', token, }); } export async function batchApiKeyAccessRules(token: string, input: GatewayAccessRuleBatchRequest): Promise> { return request>('/api/v1/api-keys/access-rules/batch', { body: input, method: 'POST', token, }); } export async function updateAccessRule( token: string, ruleId: string, input: GatewayAccessRuleUpsertRequest, ): Promise { return request(`/api/admin/access-rules/${ruleId}`, { body: input, method: 'PATCH', token, }); } export async function deleteAccessRule(token: string, ruleId: string): Promise { await request(`/api/admin/access-rules/${ruleId}`, { method: 'DELETE', token, }); } export async function listApiKeys(token: string): Promise> { return request>('/api/v1/api-keys', { token }); } export async function listPlayableApiKeys(token: string): Promise> { return request>('/api/playground/api-keys', { token }); } export async function createApiKey( token: string, input: { name: string; scopes?: string[]; expiresAt?: string }, ): Promise { return request('/api/v1/api-keys', { body: input, method: 'POST', token, }); } export async function deleteApiKey(token: string, apiKeyId: string): Promise { await request(`/api/v1/api-keys/${apiKeyId}`, { method: 'DELETE', token, }); } export async function createPlatform(token: string, input: PlatformCreateInput): Promise { return request('/api/admin/platforms', { body: input, method: 'POST', token, }); } export async function updatePlatform(token: string, platformId: string, input: PlatformCreateInput): Promise { return request(`/api/admin/platforms/${platformId}`, { body: input, method: 'PATCH', token, }); } export async function deletePlatform(token: string, platformId: string): Promise { await request(`/api/admin/platforms/${platformId}`, { method: 'DELETE', token, }); } export async function createPlatformModel( token: string, platformId: string, input: PlatformModelBindingInput, ): Promise { return request(`/api/admin/platforms/${platformId}/models`, { body: input, method: 'POST', token, }); } export async function replacePlatformModels( token: string, platformId: string, models: PlatformModelBindingInput[], ): Promise> { return request>(`/api/admin/platforms/${platformId}/models`, { body: { models }, method: 'PUT', token, }); } export async function deletePlatformModel(token: string, modelId: string): Promise { await request(`/api/admin/platform-models/${modelId}`, { method: 'DELETE', token, }); } export async function createChatTask( token: string, input: { model: string; messages: Array>; runMode?: string; simulation?: boolean }, ): Promise<{ task: GatewayTask; next: Record }> { return request<{ task: GatewayTask; next: Record }>('/api/v1/chat/completions', { body: input, headers: { 'X-Async': 'true' }, method: 'POST', token, }); } export async function createCompatibleChatCompletion( token: string, input: { model: string; messages: Array>; runMode?: string; simulation?: boolean; stream?: boolean }, ): Promise> { return request>('/v1/chat/completions', { body: input, method: 'POST', token, }); } export async function createEmbedding( token: string, input: { model: string; input: string | string[]; dimensions?: number; runMode?: string; simulation?: boolean }, ): Promise> { return request>('/v1/embeddings', { body: input, method: 'POST', token, }); } export async function createRerank( token: string, input: { model: string; query: string; documents: string[]; top_n?: number; runMode?: string; simulation?: boolean }, ): Promise> { return request>('/v1/reranks', { body: input, method: 'POST', token, }); } export async function streamChatCompletions( token: string, input: { model: string; messages: Array>; simulation?: boolean }, onDelta: (delta: string) => void, ): Promise { for await (const delta of streamChatCompletionText(token, input)) { onDelta(delta); } } export async function* streamChatCompletionText( token: string, input: { model: string; messages: Array>; simulation?: boolean }, signal?: AbortSignal, ): AsyncGenerator { const response = await fetch(`${API_BASE}/v1/chat/completions`, { body: JSON.stringify({ ...input, stream: true }), headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, method: 'POST', signal, }); if (!response.ok) { const body = await response.text(); throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`)); } if (!response.body) { return; } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const events = buffer.split(/\n\n/); buffer = events.pop() ?? ''; for (const eventBlock of events) { const event = parseSSEBlock(eventBlock); if (event.error) throw new GatewayApiError(event.error); if (event.delta) yield event.delta; } } if (buffer.trim()) { const event = parseSSEBlock(buffer); if (event.error) throw new GatewayApiError(event.error); if (event.delta) yield event.delta; } } export async function createImageGenerationTask( token: string, input: { model: string; prompt: string; aspect_ratio?: string; content?: Array>; count?: number; height?: number; image?: string | string[]; image_url?: string | string[]; image_urls?: string[]; images?: string[]; n?: number; quality?: string; referenceImage?: string | string[]; reference_image?: string | string[]; resolution?: string; runMode?: string; simulation?: boolean; size?: string; width?: number; }, ): Promise<{ task: GatewayTask; next: Record }> { return request<{ task: GatewayTask; next: Record }>('/api/v1/images/generations', { body: input, headers: { 'X-Async': 'true' }, method: 'POST', token, }); } export async function createImageEditTask( token: string, input: { model: string; prompt: string; aspect_ratio?: string; content?: Array>; count?: number; height?: number; image?: string | string[]; image_url?: string | string[]; image_urls?: string[]; images?: string[]; mask?: string; n?: number; quality?: string; resolution?: string; runMode?: string; simulation?: boolean; size?: string; width?: number; }, ): Promise<{ task: GatewayTask; next: Record }> { return request<{ task: GatewayTask; next: Record }>('/api/v1/images/edits', { body: input, headers: { 'X-Async': 'true' }, method: 'POST', token, }); } export type VideoGenerationContentRole = | 'first_frame' | 'last_frame' | 'reference_image' | 'reference_video' | 'reference_audio' | 'digital_human_frame' | 'reference' | 'element' | 'video_feature' | 'video_base' | 'shot_prompt'; export interface VideoGenerationContent { type: 'text' | 'image_url' | 'audio_url' | 'video_url' | 'element'; text?: string; image_url?: { url: string; }; video_url?: { url: string; mime_type?: string; refer_type?: 'feature' | 'base'; keep_original_sound?: 'yes' | 'no'; }; audio_url?: { url: string; mime_type?: string; }; role?: VideoGenerationContentRole; shot_index?: number; duration?: number; name?: string; element?: { system_element_id?: string; inline_element?: { name: string; description?: string; frontal_image_url: string; refer_images: Array<{ url: string; slot_key?: string }>; tags?: string[]; }; }; } export interface VideoGenerationParams { content: VideoGenerationContent[]; model: string; aspect_ratio?: string; resolution?: string; duration?: number; audio_list?: Array<{ url?: string; audio_url?: string; name?: string; }>; audio?: boolean; framespersecond?: number; watermark?: boolean; seed?: number; camerafixed?: boolean; camera_control?: string; camera_control_strength?: number; prompt_extend?: boolean; size?: string; task_id?: string; conversation_id?: string; histories?: string; callback_url?: string; prompt_optimizer?: boolean; fast_pretreatment?: boolean; mode?: 'std' | 'pro'; negative_prompt?: string; cfg_scale?: number; } export async function createVideoGenerationTask( token: string, input: VideoGenerationParams, ): Promise<{ task: GatewayTask; next: Record }> { return request<{ task: GatewayTask; next: Record }>('/api/v1/videos/generations', { body: input, headers: { 'X-Async': 'true' }, method: 'POST', token, }); } export interface GatewayFileUploadResponse extends Record { fileUrl?: string; file_url?: string; url?: string; } export async function uploadFileToStorage( token: string, file: File, source = 'ai-gateway-playground', ): Promise { const form = new FormData(); form.append('file', file); form.append('source', source); const response = await fetch(`${API_BASE}/v1/files/upload`, { body: form, headers: { Authorization: `Bearer ${token}`, }, method: 'POST', }); const body = await response.text(); if (!response.ok) { throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`)); } if (!body) return {}; try { const parsed = JSON.parse(body) as unknown; return recordFromUnknown(parsed) ? (parsed as GatewayFileUploadResponse) : {}; } catch { return { url: body }; } } export async function estimatePricing( token: string, input: Record, ): Promise { return request('/api/v1/pricing/estimate', { body: input, method: 'POST', token, }); } export async function getTask(token: string, taskId: string): Promise { return request(`/api/workspace/tasks/${taskId}`, { token }); } export async function listTaskParamPreprocessing( token: string, taskId: string, ): Promise> { return request>(`/api/workspace/tasks/${taskId}/param-preprocessing`, { token }); } export async function pollTaskUntilSettled( token: string, task: GatewayTask, options: { intervalMs?: number; maxAttempts?: number | null; onUpdate?: (task: GatewayTask) => void } = {}, ): Promise { let detail = task; const intervalMs = options.intervalMs ?? 1200; const maxAttempts = options.maxAttempts ?? Number.POSITIVE_INFINITY; for (let attempt = 0; attempt < maxAttempts; attempt += 1) { if (!taskIsPending(detail.status)) return detail; try { detail = await getTask(token, detail.id); options.onUpdate?.(detail); if (!taskIsPending(detail.status)) return detail; } catch { // Backend restarts or short network gaps should not turn a durable task into a failed UI run. // Only an explicit terminal task status from the task detail endpoint settles the run. } await delay(intervalMs); } return detail; } export function taskIsPending(status: string) { return status === 'queued' || status === 'running' || status === 'submitting'; } export async function listTasks(token: string, query: WorkspaceTaskQuery): Promise> { const search = new URLSearchParams({ page: String(query.page), pageSize: String(query.pageSize), }); if (query.query) search.set('q', query.query); if (query.modelType) search.set('modelType', query.modelType); if (query.createdFrom) search.set('createdFrom', query.createdFrom); if (query.createdTo) search.set('createdTo', query.createdTo); return request>(`/api/workspace/tasks?${search.toString()}`, { token }); } export async function getWalletSummary(token: string): Promise { return request('/api/workspace/wallet', { token }); } export async function listWalletTransactions( token: string, input: { query?: string; direction?: string; transactionType?: string; createdFrom?: string; createdTo?: string; page?: number; pageSize?: number } = {}, ): Promise> { const search = new URLSearchParams({ page: String(input.page ?? 1), pageSize: String(input.pageSize ?? 50), }); if (input.query) search.set('q', input.query); if (input.direction) search.set('direction', input.direction); if (input.transactionType) search.set('transactionType', input.transactionType); if (input.createdFrom) search.set('createdFrom', input.createdFrom); if (input.createdTo) search.set('createdTo', input.createdTo); return request>(`/api/workspace/wallet/transactions?${search.toString()}`, { token }); } export function resolveApiAssetUrl(src: string) { if (/^(https?:|data:|blob:)/i.test(src)) return src; return `${API_BASE}${src.startsWith('/') ? src : `/${src}`}`; } export async function listRateLimitWindows(token: string): Promise> { return request>('/api/admin/runtime/rate-limit-windows', { token }); } export async function listModelRateLimitStatuses(token: string): Promise> { return request>('/api/admin/runtime/model-rate-limits', { token }); } export async function restoreModelRuntimeStatus(token: string, platformModelId: string): Promise { return request(`/api/admin/runtime/model-rate-limits/${platformModelId}/restore`, { method: 'POST', token, }); } export async function getNetworkProxyConfig(token: string): Promise { return request('/api/admin/config/network-proxy', { token }); } export async function listFileStorageChannels(token: string): Promise> { return request>('/api/admin/system/file-storage/channels', { token }); } export async function getFileStorageSettings(token: string): Promise { return request('/api/admin/system/file-storage/settings', { token }); } export async function updateFileStorageSettings( token: string, input: FileStorageSettingsUpdateRequest, ): Promise { return request('/api/admin/system/file-storage/settings', { body: input, method: 'PATCH', token, }); } export async function createFileStorageChannel( token: string, input: FileStorageChannelUpsertRequest, ): Promise { return request('/api/admin/system/file-storage/channels', { body: input, method: 'POST', token, }); } export async function updateFileStorageChannel( token: string, channelId: string, input: FileStorageChannelUpsertRequest, ): Promise { return request(`/api/admin/system/file-storage/channels/${channelId}`, { body: input, method: 'PATCH', token, }); } export async function deleteFileStorageChannel(token: string, channelId: string): Promise { await request(`/api/admin/system/file-storage/channels/${channelId}`, { method: 'DELETE', token, }); } async function request( path: string, options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record } = {}, ): Promise { const headers: Record = { ...(options.headers ?? {}) }; if (options.auth !== false && options.token) { headers.Authorization = `Bearer ${options.token}`; } if (options.body !== undefined) { headers['Content-Type'] = 'application/json'; } const response = await fetch(`${API_BASE}${path}`, { method: options.method ?? 'GET', headers, body: options.body === undefined ? undefined : JSON.stringify(options.body), }); if (!response.ok) { const body = await response.text(); throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`)); } if (response.status === 204) { return undefined as T; } return response.json() as Promise; } function delay(ms: number) { return new Promise((resolve) => window.setTimeout(resolve, ms)); } function parseErrorMessage(body: string) { return formatGatewayErrorDetails(parseErrorDetails(body)); } function parseErrorDetails(body: string, status?: number, fallback = ''): GatewayErrorDetails { if (!body) { return { message: fallback, status }; } try { const parsed = JSON.parse(body) as unknown; return errorDetailsFromParsed(parsed, status, fallback || body); } catch { return { message: body || fallback, status }; } } function parseSSEBlock(block: string): { delta: string; error?: GatewayErrorDetails } { const eventName = block .split(/\n/) .find((line) => line.startsWith('event:')) ?.replace(/^event:\s?/, '') .trim(); const data = block .split(/\n/) .filter((line) => line.startsWith('data:')) .map((line) => line.replace(/^data:\s?/, '')) .join('\n') .trim(); if (!data || data === '[DONE]') return { delta: '' }; try { const parsed = JSON.parse(data) as { choices?: Array<{ delta?: { content?: string }; message?: { content?: string } }>; delta?: string; error?: unknown; output_text?: string; }; if (eventName === 'error' || parsed.error) { return { delta: '', error: errorDetailsFromParsed(parsed, undefined, data) }; } return { delta: parsed.choices?.[0]?.delta?.content ?? parsed.delta ?? parsed.output_text ?? '' }; } catch { if (eventName === 'error') return { delta: '', error: { message: data } }; return { delta: data }; } } function errorDetailsFromParsed(parsed: unknown, status?: number, fallback = ''): GatewayErrorDetails { const root = recordFromUnknown(parsed); if (!root) return { message: fallback, status }; const error = recordFromUnknown(root.error); const message = firstString( error?.message, error?.error, root.message, root.errorMessage, fallback, ); return { code: firstString(error?.code, error?.type, root.code, root.errorCode), message: message || fallback, requestId: firstString(error?.requestId, error?.request_id, root.requestId, root.request_id), status: numberFromUnknown(error?.status) ?? numberFromUnknown(root.status) ?? status, taskId: firstString(error?.taskId, error?.task_id, root.taskId, root.task_id), }; } function formatGatewayErrorDetails(details: GatewayErrorDetails) { const message = details.message || '请求失败'; const meta = [ details.code ? `错误码: ${details.code}` : '', details.status ? `状态: ${details.status}` : '', details.requestId ? `RequestID: ${details.requestId}` : '', details.taskId ? `TaskID: ${details.taskId}` : '', ].filter(Boolean); return meta.length ? `${message}(${meta.join(',')})` : message; } function recordFromUnknown(value: unknown): Record | undefined { if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined; return value as Record; } function firstString(...values: unknown[]) { return values.map((value) => typeof value === 'string' ? value.trim() : '').find(Boolean) ?? ''; } function numberFromUnknown(value: unknown) { if (typeof value === 'number' && Number.isFinite(value)) return value; if (typeof value === 'string' && value.trim()) { const parsed = Number(value); if (Number.isFinite(parsed)) return parsed; } return undefined; }