Files
easyai-ai-gateway/apps/web/src/api.ts
T
chengcheng e9f287971a
ci / verify (pull_request) Failing after 6m43s
fix(auth): 为登录链路增加有界超时
为 PostgreSQL 连接、就绪检查和本地登录设置分层超时,数据库不可用时返回稳定 503 错误码并记录无凭据的连接池统计。

前端登录在 10 秒后取消请求并兼容调用方 AbortSignal,Nginx 登录精确路由限制上游为 15 秒,同时更新 OpenAPI 和回归测试。
2026-07-21 11:14:35 +08:00

1374 lines
44 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
AuthResponse,
AuthUser,
BaseModelCatalogItem,
BaseModelUpsertRequest,
BillingSettlementListResponse,
BillingSettlementRetryResponse,
CatalogProvider,
CatalogProviderUpsertRequest,
ClientCustomizationSettings,
ClientCustomizationSettingsUpdateRequest,
CreatedGatewayApiKey,
FileStorageChannel,
FileStorageSettings,
FileStorageSettingsUpdateRequest,
FileStorageChannelUpsertRequest,
GatewayAccessRuleBatchRequest,
GatewayAccessRule,
GatewayAccessRuleUpsertRequest,
GatewayApiKey,
GatewayApiKeyScopeUpdateRequest,
GatewayAuditLog,
GatewayRunnerPolicy,
GatewayRunnerPolicyUpsertRequest,
IdentityConfigurationRevision,
IdentityConfigurationView,
IdentityPairingExchange,
IdentityPairingInput,
IdentityRevisionPolicyUpdate,
GatewayTenant,
GatewayTenantUpsertRequest,
GatewayNetworkProxyConfig,
GatewayPricingEstimate,
GatewaySkillBundleMetadata,
GatewayTask,
GatewayTaskParamPreprocessingLog,
GatewayUser,
GatewayUserUpsertRequest,
GatewayWalletTransaction,
IntegrationPlatform,
ListResponse,
ModelCatalogResponse,
ModelRateLimitStatus,
PlatformDynamicPriorityState,
PlatformDynamicPriorityUpdateRequest,
PlatformModel,
PlayableGatewayApiKey,
PricingRule,
PricingRuleSet,
PricingRuleSetUpsertRequest,
RateLimitWindow,
RuntimePolicySet,
RuntimePolicySetUpsertRequest,
SecurityEventConnectionResponse,
UserGroup,
UserGroupUpsertRequest,
WalletAdjustmentResponse,
WalletBalanceAdjustmentRequest,
WalletRechargeRequest,
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';
export const OIDC_BROWSER_SESSION_CREDENTIAL = '__easyai_gateway_oidc_browser_session__';
export 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<HealthResponse> {
return request<HealthResponse>('/healthz', { auth: false });
}
export async function getOpsManagementSkillMetadata(): Promise<GatewaySkillBundleMetadata> {
return request<GatewaySkillBundleMetadata>('/api/v1/public/skills/ai-gateway-ops-management/metadata', { auth: false });
}
export async function registerLocalAccount(input: {
username: string;
email?: string;
password: string;
displayName?: string;
invitationCode?: string;
}): Promise<AuthResponse> {
return request<AuthResponse>('/api/v1/auth/register', {
auth: false,
body: input,
method: 'POST',
});
}
export async function loginLocalAccount(input: { account: string; password: string }): Promise<AuthResponse> {
return request<AuthResponse>('/api/v1/auth/login', {
auth: false,
body: input,
method: 'POST',
timeoutMs: 10_000,
});
}
export async function deleteOIDCBrowserSession(): Promise<void> {
await request<void>('/api/v1/auth/oidc/session', {
auth: false,
method: 'DELETE',
});
}
export async function getCurrentUser(token: string): Promise<AuthUser> {
return request<AuthUser>('/api/v1/me', { token });
}
export async function listPlatforms(token: string): Promise<ListResponse<IntegrationPlatform>> {
return request<ListResponse<IntegrationPlatform>>('/api/admin/platforms', { token });
}
export async function updatePlatformDynamicPriority(
token: string,
platformId: string,
input: PlatformDynamicPriorityUpdateRequest,
): Promise<PlatformDynamicPriorityState> {
return request<PlatformDynamicPriorityState>(`/api/admin/platforms/${platformId}/dynamic-priority`, {
body: input,
method: 'PATCH',
token,
});
}
export async function listModels(token: string): Promise<ListResponse<PlatformModel>> {
return request<ListResponse<PlatformModel>>('/api/admin/models', { token });
}
export async function listPlayableModels(token: string): Promise<ListResponse<PlatformModel>> {
return request<ListResponse<PlatformModel>>('/api/v1/models', { token });
}
export async function listModelCatalog(token: string): Promise<ModelCatalogResponse> {
return request<ModelCatalogResponse>('/api/v1/model-catalog', { token });
}
export async function listPublicCatalogProviders(): Promise<ListResponse<CatalogProvider>> {
return request<ListResponse<CatalogProvider>>('/api/v1/public/catalog/providers', { auth: false });
}
export async function listCatalogProviders(token: string): Promise<ListResponse<CatalogProvider>> {
return request<ListResponse<CatalogProvider>>('/api/admin/catalog/providers', { token });
}
export async function createCatalogProvider(
token: string,
input: CatalogProviderUpsertRequest,
): Promise<CatalogProvider> {
return request<CatalogProvider>('/api/admin/catalog/providers', {
body: input,
method: 'POST',
token,
});
}
export async function updateCatalogProvider(
token: string,
providerId: string,
input: CatalogProviderUpsertRequest,
): Promise<CatalogProvider> {
return request<CatalogProvider>(`/api/admin/catalog/providers/${providerId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteCatalogProvider(token: string, providerId: string): Promise<void> {
await request<void>(`/api/admin/catalog/providers/${providerId}`, {
method: 'DELETE',
token,
});
}
export async function listPublicBaseModels(): Promise<ListResponse<BaseModelCatalogItem>> {
return request<ListResponse<BaseModelCatalogItem>>('/api/v1/public/catalog/base-models', { auth: false });
}
export async function listBaseModels(token: string): Promise<ListResponse<BaseModelCatalogItem>> {
return request<ListResponse<BaseModelCatalogItem>>('/api/admin/catalog/base-models', { token });
}
export async function createBaseModel(token: string, input: BaseModelUpsertRequest): Promise<BaseModelCatalogItem> {
return request<BaseModelCatalogItem>('/api/admin/catalog/base-models', {
body: input,
method: 'POST',
token,
});
}
export async function updateBaseModel(
token: string,
baseModelId: string,
input: BaseModelUpsertRequest,
): Promise<BaseModelCatalogItem> {
return request<BaseModelCatalogItem>(`/api/admin/catalog/base-models/${baseModelId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function resetBaseModel(token: string, baseModelId: string): Promise<BaseModelCatalogItem> {
return request<BaseModelCatalogItem>(`/api/admin/catalog/base-models/${baseModelId}/reset`, {
method: 'POST',
token,
});
}
export async function resetAllBaseModels(token: string): Promise<ListResponse<BaseModelCatalogItem>> {
return request<ListResponse<BaseModelCatalogItem>>('/api/admin/catalog/base-models/reset-all', {
method: 'POST',
token,
});
}
export async function deleteBaseModel(token: string, baseModelId: string): Promise<void> {
await request<void>(`/api/admin/catalog/base-models/${baseModelId}`, {
method: 'DELETE',
token,
});
}
export async function listPricingRules(token: string): Promise<ListResponse<PricingRule>> {
return request<ListResponse<PricingRule>>('/api/admin/pricing/rules', { token });
}
export async function listPricingRuleSets(token: string): Promise<ListResponse<PricingRuleSet>> {
return request<ListResponse<PricingRuleSet>>('/api/admin/pricing/rule-sets', { token });
}
export async function createPricingRuleSet(
token: string,
input: PricingRuleSetUpsertRequest,
): Promise<PricingRuleSet> {
return request<PricingRuleSet>('/api/admin/pricing/rule-sets', {
body: input,
method: 'POST',
token,
});
}
export async function updatePricingRuleSet(
token: string,
ruleSetId: string,
input: PricingRuleSetUpsertRequest,
): Promise<PricingRuleSet> {
return request<PricingRuleSet>(`/api/admin/pricing/rule-sets/${ruleSetId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deletePricingRuleSet(token: string, ruleSetId: string): Promise<void> {
await request<void>(`/api/admin/pricing/rule-sets/${ruleSetId}`, {
method: 'DELETE',
token,
});
}
export async function listRuntimePolicySets(token: string): Promise<ListResponse<RuntimePolicySet>> {
return request<ListResponse<RuntimePolicySet>>('/api/admin/runtime/policy-sets', { token });
}
export async function getRunnerPolicy(token: string): Promise<GatewayRunnerPolicy> {
return request<GatewayRunnerPolicy>('/api/admin/runtime/runner-policy', { token });
}
export async function updateRunnerPolicy(
token: string,
input: GatewayRunnerPolicyUpsertRequest,
): Promise<GatewayRunnerPolicy> {
return request<GatewayRunnerPolicy>('/api/admin/runtime/runner-policy', {
body: input,
method: 'PATCH',
token,
});
}
export async function createRuntimePolicySet(
token: string,
input: RuntimePolicySetUpsertRequest,
): Promise<RuntimePolicySet> {
return request<RuntimePolicySet>('/api/admin/runtime/policy-sets', {
body: input,
method: 'POST',
token,
});
}
export async function updateRuntimePolicySet(
token: string,
policySetId: string,
input: RuntimePolicySetUpsertRequest,
): Promise<RuntimePolicySet> {
return request<RuntimePolicySet>(`/api/admin/runtime/policy-sets/${policySetId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteRuntimePolicySet(token: string, policySetId: string): Promise<void> {
await request<void>(`/api/admin/runtime/policy-sets/${policySetId}`, {
method: 'DELETE',
token,
});
}
export async function listTenants(token: string): Promise<ListResponse<GatewayTenant>> {
return request<ListResponse<GatewayTenant>>('/api/admin/tenants', { token });
}
export async function createTenant(token: string, input: GatewayTenantUpsertRequest): Promise<GatewayTenant> {
return request<GatewayTenant>('/api/admin/tenants', {
body: input,
method: 'POST',
token,
});
}
export async function updateTenant(token: string, tenantId: string, input: GatewayTenantUpsertRequest): Promise<GatewayTenant> {
return request<GatewayTenant>(`/api/admin/tenants/${tenantId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteTenant(token: string, tenantId: string): Promise<void> {
await request<void>(`/api/admin/tenants/${tenantId}`, {
method: 'DELETE',
token,
});
}
export async function listUsers(token: string): Promise<ListResponse<GatewayUser>> {
return request<ListResponse<GatewayUser>>('/api/admin/users', { token });
}
export async function createGatewayUser(token: string, input: GatewayUserUpsertRequest): Promise<GatewayUser> {
return request<GatewayUser>('/api/admin/users', {
body: input,
method: 'POST',
token,
});
}
export async function updateGatewayUser(token: string, userId: string, input: GatewayUserUpsertRequest): Promise<GatewayUser> {
return request<GatewayUser>(`/api/admin/users/${userId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function setUserWalletBalance(
token: string,
userId: string,
input: WalletBalanceAdjustmentRequest,
): Promise<WalletAdjustmentResponse> {
return request<WalletAdjustmentResponse>(`/api/admin/users/${userId}/wallet`, {
body: input,
method: 'PATCH',
token,
});
}
export async function rechargeUserWalletBalance(
token: string,
userId: string,
input: WalletRechargeRequest,
): Promise<WalletAdjustmentResponse> {
return request<WalletAdjustmentResponse>(`/api/admin/users/${userId}/wallet/recharge`, {
body: input,
method: 'POST',
token,
});
}
export async function deleteGatewayUser(token: string, userId: string): Promise<void> {
await request<void>(`/api/admin/users/${userId}`, {
method: 'DELETE',
token,
});
}
export async function listAuditLogs(token: string): Promise<ListResponse<GatewayAuditLog>> {
return request<ListResponse<GatewayAuditLog>>('/api/admin/audit-logs', { token });
}
export async function listUserGroups(token: string): Promise<ListResponse<UserGroup>> {
return request<ListResponse<UserGroup>>('/api/admin/user-groups', { token });
}
export async function listCurrentUserGroups(token: string): Promise<ListResponse<UserGroup>> {
return request<ListResponse<UserGroup>>('/api/workspace/user-groups', { token });
}
export async function createUserGroup(token: string, input: UserGroupUpsertRequest): Promise<UserGroup> {
return request<UserGroup>('/api/admin/user-groups', {
body: input,
method: 'POST',
token,
});
}
export async function updateUserGroup(token: string, groupId: string, input: UserGroupUpsertRequest): Promise<UserGroup> {
return request<UserGroup>(`/api/admin/user-groups/${groupId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteUserGroup(token: string, groupId: string): Promise<void> {
await request<void>(`/api/admin/user-groups/${groupId}`, {
method: 'DELETE',
token,
});
}
export async function listAccessRules(token: string): Promise<ListResponse<GatewayAccessRule>> {
return request<ListResponse<GatewayAccessRule>>('/api/admin/access-rules', { token });
}
export async function listApiKeyAccessRules(token: string): Promise<ListResponse<GatewayAccessRule>> {
return request<ListResponse<GatewayAccessRule>>('/api/v1/api-keys/access-rules', { token });
}
export async function createAccessRule(token: string, input: GatewayAccessRuleUpsertRequest): Promise<GatewayAccessRule> {
return request<GatewayAccessRule>('/api/admin/access-rules', {
body: input,
method: 'POST',
token,
});
}
export async function batchAccessRules(token: string, input: GatewayAccessRuleBatchRequest): Promise<ListResponse<GatewayAccessRule>> {
return request<ListResponse<GatewayAccessRule>>('/api/admin/access-rules/batch', {
body: input,
method: 'POST',
token,
});
}
export async function batchApiKeyAccessRules(token: string, input: GatewayAccessRuleBatchRequest): Promise<ListResponse<GatewayAccessRule>> {
return request<ListResponse<GatewayAccessRule>>('/api/v1/api-keys/access-rules/batch', {
body: input,
method: 'POST',
token,
});
}
export async function updateAccessRule(
token: string,
ruleId: string,
input: GatewayAccessRuleUpsertRequest,
): Promise<GatewayAccessRule> {
return request<GatewayAccessRule>(`/api/admin/access-rules/${ruleId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteAccessRule(token: string, ruleId: string): Promise<void> {
await request<void>(`/api/admin/access-rules/${ruleId}`, {
method: 'DELETE',
token,
});
}
export async function listApiKeys(token: string): Promise<ListResponse<GatewayApiKey>> {
return request<ListResponse<GatewayApiKey>>('/api/v1/api-keys', { token });
}
export async function listPlayableApiKeys(token: string): Promise<ListResponse<PlayableGatewayApiKey>> {
return request<ListResponse<PlayableGatewayApiKey>>('/api/playground/api-keys', { token });
}
export async function createApiKey(
token: string,
input: { name: string; scopes?: string[]; expiresAt?: string },
): Promise<CreatedGatewayApiKey> {
return request<CreatedGatewayApiKey>('/api/v1/api-keys', {
body: input,
method: 'POST',
token,
});
}
export async function updateApiKeyScopes(token: string, apiKeyId: string, input: GatewayApiKeyScopeUpdateRequest): Promise<GatewayApiKey> {
return request<GatewayApiKey>(`/api/v1/api-keys/${apiKeyId}/scopes`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteApiKey(token: string, apiKeyId: string): Promise<void> {
await request<void>(`/api/v1/api-keys/${apiKeyId}`, {
method: 'DELETE',
token,
});
}
export async function createPlatform(token: string, input: PlatformCreateInput): Promise<IntegrationPlatform> {
return request<IntegrationPlatform>('/api/admin/platforms', {
body: input,
method: 'POST',
token,
});
}
export async function updatePlatform(token: string, platformId: string, input: PlatformCreateInput): Promise<IntegrationPlatform> {
return request<IntegrationPlatform>(`/api/admin/platforms/${platformId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deletePlatform(token: string, platformId: string): Promise<void> {
await request<void>(`/api/admin/platforms/${platformId}`, {
method: 'DELETE',
token,
});
}
export async function createPlatformModel(
token: string,
platformId: string,
input: PlatformModelBindingInput,
): Promise<PlatformModel> {
return request<PlatformModel>(`/api/admin/platforms/${platformId}/models`, {
body: input,
method: 'POST',
token,
});
}
export async function replacePlatformModels(
token: string,
platformId: string,
models: PlatformModelBindingInput[],
): Promise<ListResponse<PlatformModel>> {
return request<ListResponse<PlatformModel>>(`/api/admin/platforms/${platformId}/models`, {
body: { models },
method: 'PUT',
token,
});
}
export async function deletePlatformModel(token: string, modelId: string): Promise<void> {
await request<void>(`/api/admin/platform-models/${modelId}`, {
method: 'DELETE',
token,
});
}
export async function createChatTask(
token: string,
input: { model: string; messages: Array<Record<string, unknown>>; runMode?: string; simulation?: boolean },
): Promise<{ task: GatewayTask; next: Record<string, string> }> {
return request<{ task: GatewayTask; next: Record<string, string> }>('/api/v1/chat/completions', {
body: input,
headers: { 'X-Async': 'true' },
method: 'POST',
token,
});
}
export async function createCompatibleChatCompletion(
token: string,
input: { model: string; messages: Array<Record<string, unknown>>; runMode?: string; simulation?: boolean; stream?: boolean },
): Promise<Record<string, unknown>> {
return request<Record<string, unknown>>('/v1/chat/completions', {
body: input,
method: 'POST',
token,
});
}
export async function createResponse(
token: string,
input: {
model: string;
input: unknown;
instructions?: string;
previous_response_id?: string;
runMode?: string;
simulation?: boolean;
store?: boolean;
stream?: boolean;
},
): Promise<Record<string, unknown>> {
return request<Record<string, unknown>>('/v1/responses', {
body: input,
method: 'POST',
token,
});
}
export async function createEmbedding(
token: string,
input: { model: string; input: string | string[]; dimensions?: number; runMode?: string; simulation?: boolean },
): Promise<Record<string, unknown>> {
return request<Record<string, unknown>>('/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<Record<string, unknown>> {
return request<Record<string, unknown>>('/v1/reranks', {
body: input,
method: 'POST',
token,
});
}
export async function streamChatCompletions(
token: string,
input: { model: string; messages: Array<Record<string, unknown>>; simulation?: boolean },
onDelta: (delta: string) => void,
): Promise<void> {
for await (const delta of streamChatCompletionText(token, input)) {
onDelta(delta);
}
}
export async function* streamChatCompletionText(
token: string,
input: { model: string; messages: Array<Record<string, unknown>>; simulation?: boolean },
signal?: AbortSignal,
): AsyncGenerator<string> {
const response = await fetch(`${API_BASE}/v1/chat/completions`, {
body: JSON.stringify({ ...input, stream: true }),
headers: {
...authorizationHeader(token),
'Content-Type': 'application/json',
},
credentials: 'include',
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<Record<string, unknown>>;
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<string, string> }> {
return request<{ task: GatewayTask; next: Record<string, string> }>('/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<Record<string, unknown>>;
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<string, string> }> {
return request<{ task: GatewayTask; next: Record<string, string> }>('/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;
runMode?: string;
simulation?: boolean;
}
export async function createVideoGenerationTask(
token: string,
input: VideoGenerationParams,
): Promise<{ task: GatewayTask; next: Record<string, string> }> {
return request<{ task: GatewayTask; next: Record<string, string> }>('/api/v1/videos/generations', {
body: input,
headers: { 'X-Async': 'true' },
method: 'POST',
token,
});
}
export interface GatewayFileUploadResponse extends Record<string, unknown> {
fileUrl?: string;
file_url?: string;
url?: string;
}
export async function uploadFileToStorage(
token: string,
file: File,
source = 'ai-gateway-playground',
): Promise<GatewayFileUploadResponse> {
const form = new FormData();
form.append('file', file);
form.append('source', source);
const response = await fetch(`${API_BASE}/v1/files/upload`, {
body: form,
credentials: 'include',
headers: authorizationHeader(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<string, unknown>,
signal?: AbortSignal,
): Promise<GatewayPricingEstimate> {
return request<GatewayPricingEstimate>('/api/v1/pricing/estimate', {
body: input,
method: 'POST',
signal,
token,
});
}
export async function getTask(token: string, taskId: string): Promise<GatewayTask> {
return request<GatewayTask>(`/api/workspace/tasks/${taskId}`, { token });
}
export async function getAPITask(token: string, taskId: string): Promise<GatewayTask> {
return request<GatewayTask>(`/api/v1/tasks/${taskId}`, { token });
}
export async function listTaskParamPreprocessing(
token: string,
taskId: string,
): Promise<ListResponse<GatewayTaskParamPreprocessingLog>> {
return request<ListResponse<GatewayTaskParamPreprocessingLog>>(`/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<GatewayTask> {
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<ListResponse<GatewayTask>> {
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<ListResponse<GatewayTask>>(`/api/workspace/tasks?${search.toString()}`, { token });
}
export async function getWalletSummary(token: string): Promise<WalletSummaryResponse> {
return request<WalletSummaryResponse>('/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<ListResponse<GatewayWalletTransaction>> {
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<ListResponse<GatewayWalletTransaction>>(`/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<ListResponse<RateLimitWindow>> {
return request<ListResponse<RateLimitWindow>>('/api/admin/runtime/rate-limit-windows', { token });
}
export async function listModelRateLimitStatuses(token: string): Promise<ListResponse<ModelRateLimitStatus>> {
return request<ListResponse<ModelRateLimitStatus>>('/api/admin/runtime/model-rate-limits', { token });
}
export async function restoreModelRuntimeStatus(token: string, platformModelId: string): Promise<ModelRateLimitStatus> {
return request<ModelRateLimitStatus>(`/api/admin/runtime/model-rate-limits/${platformModelId}/restore`, {
method: 'POST',
token,
});
}
export async function listBillingSettlements(
token: string,
input: { status?: string; action?: string; page?: number; pageSize?: number; signal?: AbortSignal } = {},
): Promise<BillingSettlementListResponse> {
const search = new URLSearchParams({
page: String(input.page ?? 1),
pageSize: String(input.pageSize ?? 50),
});
if (input.status) search.set('status', input.status);
if (input.action) search.set('action', input.action);
return request<BillingSettlementListResponse>(`/api/admin/runtime/billing-settlements?${search.toString()}`, { token, signal: input.signal });
}
export async function retryBillingSettlement(token: string, settlementId: string): Promise<BillingSettlementRetryResponse> {
const idempotencyKey = crypto.randomUUID();
return request<BillingSettlementRetryResponse>(`/api/admin/runtime/billing-settlements/${settlementId}/retry`, {
method: 'POST',
token,
headers: { 'Idempotency-Key': idempotencyKey },
});
}
export async function getNetworkProxyConfig(token: string): Promise<GatewayNetworkProxyConfig> {
return request<GatewayNetworkProxyConfig>('/api/admin/config/network-proxy', { token });
}
export async function listFileStorageChannels(token: string): Promise<ListResponse<FileStorageChannel>> {
return request<ListResponse<FileStorageChannel>>('/api/admin/system/file-storage/channels', { token });
}
export async function getFileStorageSettings(token: string): Promise<FileStorageSettings> {
return request<FileStorageSettings>('/api/admin/system/file-storage/settings', { token });
}
export async function updateFileStorageSettings(
token: string,
input: FileStorageSettingsUpdateRequest,
): Promise<FileStorageSettings> {
return request<FileStorageSettings>('/api/admin/system/file-storage/settings', {
body: input,
method: 'PATCH',
token,
});
}
export async function getClientCustomizationSettings(token: string): Promise<ClientCustomizationSettings> {
return request<ClientCustomizationSettings>('/api/admin/system/client-customization/settings', { token });
}
export async function updateClientCustomizationSettings(
token: string,
input: ClientCustomizationSettingsUpdateRequest,
): Promise<ClientCustomizationSettings> {
return request<ClientCustomizationSettings>('/api/admin/system/client-customization/settings', {
body: input,
method: 'PATCH',
token,
});
}
const identityConfigurationPath = '/api/admin/system/identity';
export async function getIdentityConfiguration(token: string): Promise<IdentityConfigurationView> {
return request<IdentityConfigurationView>(`${identityConfigurationPath}/configuration`, { token });
}
export async function startIdentityPairing(token: string, input: IdentityPairingInput): Promise<IdentityPairingExchange> {
return identityConfigurationWrite<IdentityPairingExchange>(token, `${identityConfigurationPath}/pairings`, 'POST', 0, input, 'pair');
}
export async function getIdentityPairing(token: string, pairingId: string): Promise<IdentityPairingExchange> {
return request<IdentityPairingExchange>(`${identityConfigurationPath}/pairings/${pairingId}`, { token });
}
export async function cancelIdentityPairing(token: string, pairingId: string, version: number): Promise<IdentityPairingExchange> {
return identityConfigurationWrite<IdentityPairingExchange>(token, `${identityConfigurationPath}/pairings/${pairingId}/cancel`, 'POST', version, undefined, 'cancel');
}
export async function retireIdentityPairingSecurityEventConflict(token: string, pairingId: string, version: number): Promise<IdentityPairingExchange> {
return identityConfigurationWrite<IdentityPairingExchange>(token, `${identityConfigurationPath}/pairings/${pairingId}/retire-conflicting-security-event`, 'POST', version, undefined, 'retire-ssf-conflict');
}
export async function updateIdentityRevisionPolicy(
token: string,
revisionId: string,
version: number,
input: IdentityRevisionPolicyUpdate,
): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}`, 'PATCH', version, input, 'policy');
}
export async function validateIdentityRevision(token: string, revisionId: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}/validate`, 'POST', version, undefined, 'validate');
}
export async function activateIdentityRevision(token: string, revisionId: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/revisions/${revisionId}/activate`, 'POST', version, undefined, 'activate');
}
export async function disableIdentityConfiguration(token: string, version: number): Promise<IdentityConfigurationRevision> {
return identityConfigurationWrite<IdentityConfigurationRevision>(token, `${identityConfigurationPath}/disable`, 'POST', version, undefined, 'disable');
}
function identityConfigurationWrite<T>(
token: string,
path: string,
method: string,
version: number,
body: unknown,
action: string,
): Promise<T> {
return request<T>(path, {
body,
method,
token,
headers: {
'Idempotency-Key': `identity-${action}-${crypto.randomUUID()}`,
'If-Match': `W/"${version}"`,
},
});
}
const securityEventConnectionPath = '/api/admin/system/identity/security-events/connection';
export async function getSecurityEventConnection(token: string): Promise<SecurityEventConnectionResponse> {
return request<SecurityEventConnectionResponse>(securityEventConnectionPath, { token });
}
export async function connectSecurityEventTransmitter(
token: string,
transmitterIssuer: string,
managementClientId: string,
managementClientSecret: string,
version?: number,
): Promise<SecurityEventConnectionResponse> {
const headers: Record<string, string> = { 'Idempotency-Key': `ssf-connect-${crypto.randomUUID()}` };
if (version !== undefined) headers['If-Match'] = `W/"${version}"`;
return request<SecurityEventConnectionResponse>(securityEventConnectionPath, {
body: {
transmitter_issuer: transmitterIssuer,
management_client_id: managementClientId,
management_client_secret: managementClientSecret,
}, method: 'PUT', token,
headers,
});
}
export async function verifySecurityEventConnection(token: string, version: number): Promise<SecurityEventConnectionResponse> {
return securityEventConnectionWrite(token, '/verify', version, 'POST', 'verify');
}
export async function rotateSecurityEventConnectionCredential(token: string, version: number): Promise<SecurityEventConnectionResponse> {
return securityEventConnectionWrite(token, '/rotate-credential', version, 'POST', 'rotate');
}
export async function disconnectSecurityEventConnection(token: string, version: number): Promise<SecurityEventConnectionResponse> {
return securityEventConnectionWrite(token, '', version, 'DELETE', 'disconnect');
}
function securityEventConnectionWrite(token: string, suffix: string, version: number, method: string, action: string) {
return request<SecurityEventConnectionResponse>(securityEventConnectionPath + suffix, {
method, token, headers: {
'Idempotency-Key': `ssf-${action}-${crypto.randomUUID()}`,
'If-Match': `W/"${version}"`,
},
});
}
export async function getPublicClientCustomization(): Promise<ClientCustomizationSettings> {
return request<ClientCustomizationSettings>('/api/v1/public/client-customization', { auth: false });
}
export async function createFileStorageChannel(
token: string,
input: FileStorageChannelUpsertRequest,
): Promise<FileStorageChannel> {
return request<FileStorageChannel>('/api/admin/system/file-storage/channels', {
body: input,
method: 'POST',
token,
});
}
export async function updateFileStorageChannel(
token: string,
channelId: string,
input: FileStorageChannelUpsertRequest,
): Promise<FileStorageChannel> {
return request<FileStorageChannel>(`/api/admin/system/file-storage/channels/${channelId}`, {
body: input,
method: 'PATCH',
token,
});
}
export async function deleteFileStorageChannel(token: string, channelId: string): Promise<void> {
await request<void>(`/api/admin/system/file-storage/channels/${channelId}`, {
method: 'DELETE',
token,
});
}
async function request<T>(
path: string,
options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record<string, string>; signal?: AbortSignal; timeoutMs?: number } = {},
): Promise<T> {
const headers: Record<string, string> = { ...(options.headers ?? {}) };
if (options.auth !== false && options.token && options.token !== OIDC_BROWSER_SESSION_CREDENTIAL) {
headers.Authorization = `Bearer ${options.token}`;
}
if (options.body !== undefined) {
headers['Content-Type'] = 'application/json';
}
const controller = options.timeoutMs ? new AbortController() : undefined;
const timeout = controller ? globalThis.setTimeout(() => controller.abort(), options.timeoutMs) : undefined;
const signal = controller && options.signal
? AbortSignal.any([controller.signal, options.signal])
: controller?.signal ?? options.signal;
try {
const response = await fetch(`${API_BASE}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: 'include',
signal,
});
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<T>;
} catch (error) {
if (controller?.signal.aborted) {
throw new GatewayApiError('登录请求超时,请稍后重试');
}
throw error;
} finally {
if (timeout !== undefined) globalThis.clearTimeout(timeout);
}
}
function authorizationHeader(token: string): Record<string, string> {
if (!token || token === OIDC_BROWSER_SESSION_CREDENTIAL) return {};
return { Authorization: `Bearer ${token}` };
}
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 = gatewayErrorMessage(details);
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;
}
const gatewayProvisioningErrorMessages: Record<string, string> = {
GATEWAY_USER_NOT_PROVISIONED: '该账号尚未开通 EasyAI Gateway',
GATEWAY_USER_DISABLED: '该 Gateway 账号已停用,请联系管理员',
GATEWAY_TENANT_UNAVAILABLE: 'Gateway 租户尚未就绪,请联系管理员',
GATEWAY_USER_PROVISIONING_FAILED: 'Gateway 账号初始化失败,请稍后重试',
OIDC_BROWSER_SESSION_DISABLED: 'Gateway 浏览器会话尚未启用',
OIDC_SESSION_INVALID: '统一认证会话无效,请重新登录',
OIDC_SESSION_EXPIRED: '统一认证会话已过期,请重新登录',
OIDC_SESSION_REFRESH_UNAVAILABLE: '认证中心暂时不可用,请稍后重试',
OIDC_SESSION_STORE_UNAVAILABLE: '登录会话存储暂时不可用,请稍后重试',
OIDC_SESSION_CSRF_REJECTED: '登录会话来源校验失败,请刷新后重试',
IDENTITY_PAIRING_IN_PROGRESS: '请先完成或放弃当前统一认证配对',
IDENTITY_PAIRING_NOT_CANCELLABLE: '当前统一认证配置不能放弃',
IDENTITY_PAIRING_CONFLICT_NOT_RESOLVABLE: '当前配对状态已变化,请刷新后重试',
IDENTITY_VERSION_CONFLICT: '统一认证状态已变化,请刷新后重试',
};
export function gatewayErrorMessage(details: GatewayErrorDetails) {
return (details.code && gatewayProvisioningErrorMessages[details.code]) || details.message || '请求失败';
}
function recordFromUnknown(value: unknown): Record<string, unknown> | undefined {
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
return value as Record<string, unknown>;
}
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;
}