easyai-ai-gateway/apps/web/src/api.ts

1100 lines
33 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,
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<HealthResponse> {
return request<HealthResponse>('/healthz', { 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',
});
}
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 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 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 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 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: {
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<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;
}
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,
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<string, unknown>,
): Promise<GatewayPricingEstimate> {
return request<GatewayPricingEstimate>('/api/v1/pricing/estimate', {
body: input,
method: 'POST',
token,
});
}
export async function getTask(token: string, taskId: string): Promise<GatewayTask> {
return request<GatewayTask>(`/api/workspace/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 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 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> } = {},
): Promise<T> {
const headers: Record<string, string> = { ...(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<T>;
}
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<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;
}