1100 lines
33 KiB
TypeScript
1100 lines
33 KiB
TypeScript
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;
|
||
}
|