visual-journal / src /lib /agent-openapi.ts
misonL's picture
Deploy 574ece8 to Docker Space
32151b6 verified
Raw
History Blame
27.3 kB
import { MAX_IMAGE_COUNT, MAX_PROMPT_LENGTH } from './image-request-utils';
import {
AGENT_BACKGROUNDS,
AGENT_JOB_STATES,
AGENT_MODERATIONS,
AGENT_MODELS,
AGENT_OUTPUT_FORMATS,
AGENT_QUALITIES,
AGENT_RESPONSE_MODES,
type AgentAuthScheme,
buildAgentCapabilities,
readAgentPublicBaseUrl
} from './agent-api-contracts';
type AgentOpenApiSecurityRequirement = { BearerAuth: [] } | { AppPasswordHash: [] };
function buildAgentOpenApiSecurity(schemes: readonly AgentAuthScheme[]): AgentOpenApiSecurityRequirement[] {
return schemes.map((scheme) => (scheme === 'bearer' ? { BearerAuth: [] } : { AppPasswordHash: [] }));
}
function buildAgentOpenApiSecuritySchemes(schemes: readonly AgentAuthScheme[]) {
const securitySchemes: Record<string, { type: string; scheme?: string; in?: string; name?: string }> = {};
if (schemes.includes('bearer')) {
securitySchemes.BearerAuth = {
type: 'http',
scheme: 'bearer'
};
}
if (schemes.includes('x-app-password-hash')) {
securitySchemes.AppPasswordHash = {
type: 'apiKey',
in: 'header',
name: 'X-App-Password-Hash'
};
}
return securitySchemes;
}
export function buildAgentOpenApiDocument(env: Record<string, string | undefined>) {
const capabilities = buildAgentCapabilities(env);
const jsonContent = (schemaRef: string) => ({
content: {
'application/json': {
schema: { $ref: schemaRef }
}
}
});
const agentSecurity = buildAgentOpenApiSecurity(capabilities.auth.schemes);
const securitySchemes = buildAgentOpenApiSecuritySchemes(capabilities.auth.schemes);
const commonAgentErrors = {
'401': jsonContent('#/components/schemas/AgentError'),
'403': jsonContent('#/components/schemas/AgentError'),
'415': jsonContent('#/components/schemas/AgentError'),
'429': jsonContent('#/components/schemas/AgentError'),
'502': jsonContent('#/components/schemas/AgentError')
};
return {
openapi: '3.1.0',
info: {
title: 'GPT Image Playground Agent API',
version: capabilities.api_version
},
servers: [{ url: readAgentPublicBaseUrl(env) }],
paths: {
'/api/agent/capabilities': {
get: {
summary: '获取机器可读的 Agent API 能力信息',
responses: {
'200': jsonContent('#/components/schemas/AgentCapabilities')
}
}
},
'/api/agent/openapi.json': {
get: {
summary: '获取 Agent API 的 OpenAPI 文档',
responses: {
'200': { description: 'OpenAPI 文档' }
}
}
},
'/api/agent/images/generate': {
post: {
summary: '为 Agent 生成图片',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }],
requestBody: {
required: true,
...jsonContent('#/components/schemas/GenerateRequest')
},
responses: {
'200': jsonContent('#/components/schemas/AgentImageResponse'),
'400': jsonContent('#/components/schemas/AgentError'),
'409': {
...jsonContent('#/components/schemas/AgentError'),
headers: {
'Retry-After': { schema: { type: 'integer', minimum: 1 } }
}
},
...commonAgentErrors,
'422': jsonContent('#/components/schemas/AgentError'),
'500': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/images/edit': {
post: {
summary: '为 Agent 编辑图片',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }],
requestBody: {
required: true,
content: {
'multipart/form-data': {
schema: { $ref: '#/components/schemas/EditRequest' }
}
}
},
responses: {
'200': jsonContent('#/components/schemas/AgentImageResponse'),
'400': jsonContent('#/components/schemas/AgentError'),
'409': {
...jsonContent('#/components/schemas/AgentError'),
headers: {
'Retry-After': { schema: { type: 'integer', minimum: 1 } }
}
},
...commonAgentErrors,
'422': jsonContent('#/components/schemas/AgentError'),
'500': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/jobs/images/generate': {
post: {
summary: '创建 Agent 图片生成 job',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }],
requestBody: {
required: true,
...jsonContent('#/components/schemas/GenerateRequest')
},
responses: {
'200': jsonContent('#/components/schemas/AgentJobStatusResponse'),
'202': {
...jsonContent('#/components/schemas/AgentJobStatusResponse'),
headers: {
'Retry-After': { schema: { type: 'integer', minimum: 1 } }
}
},
'400': jsonContent('#/components/schemas/AgentError'),
'409': jsonContent('#/components/schemas/AgentError'),
...commonAgentErrors,
'422': jsonContent('#/components/schemas/AgentError'),
'500': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/jobs/{id}': {
get: {
summary: '获取 Agent job 状态',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/JobId' }],
responses: {
'200': jsonContent('#/components/schemas/AgentJobStatusResponse'),
'401': jsonContent('#/components/schemas/AgentError'),
'404': jsonContent('#/components/schemas/AgentError'),
'410': jsonContent('#/components/schemas/AgentError'),
'500': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/jobs/{id}/result': {
get: {
summary: '获取 Agent job 结果',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/JobId' }],
responses: {
'200': jsonContent('#/components/schemas/AgentImageResponse'),
'401': jsonContent('#/components/schemas/AgentError'),
'403': jsonContent('#/components/schemas/AgentError'),
'404': jsonContent('#/components/schemas/AgentError'),
'409': {
...jsonContent('#/components/schemas/AgentError'),
headers: {
'Retry-After': { schema: { type: 'integer', minimum: 1 } }
}
},
'410': jsonContent('#/components/schemas/AgentError'),
'422': jsonContent('#/components/schemas/AgentError'),
'429': jsonContent('#/components/schemas/AgentError'),
'502': jsonContent('#/components/schemas/AgentError'),
'500': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/artifacts/{id}': {
get: {
summary: '获取产物元数据',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/ArtifactId' }],
responses: {
'200': jsonContent('#/components/schemas/ArtifactMetadataResponse'),
'401': jsonContent('#/components/schemas/AgentError'),
'404': jsonContent('#/components/schemas/AgentError')
}
},
delete: {
summary: '删除产物',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/ArtifactId' }],
responses: {
'200': jsonContent('#/components/schemas/DeleteArtifactResponse'),
'401': jsonContent('#/components/schemas/AgentError'),
'404': jsonContent('#/components/schemas/AgentError')
}
}
},
'/api/agent/artifacts/{id}/content': {
get: {
summary: '下载产物内容',
security: agentSecurity,
parameters: [{ $ref: '#/components/parameters/ArtifactId' }],
responses: {
'200': {
description: '图片二进制内容',
content: {
'image/png': { schema: { type: 'string', format: 'binary' } },
'image/jpeg': { schema: { type: 'string', format: 'binary' } },
'image/webp': { schema: { type: 'string', format: 'binary' } }
}
},
'401': jsonContent('#/components/schemas/AgentError'),
'404': jsonContent('#/components/schemas/AgentError')
}
}
}
},
components: {
securitySchemes,
parameters: {
IdempotencyKey: {
name: 'Idempotency-Key',
in: 'header',
required: true,
schema: { type: 'string', minLength: 1, maxLength: 200 }
},
ArtifactId: {
name: 'id',
in: 'path',
required: true,
schema: { type: 'string', minLength: 1 }
},
JobId: {
name: 'id',
in: 'path',
required: true,
schema: { type: 'string', minLength: 1 }
}
},
schemas: {
AgentCapabilities: {
type: 'object',
required: [
'api_version',
'schema_version',
'auth',
'endpoints',
'defaults',
'limits',
'model_limits',
'agent_streaming',
'agent_jobs',
'supported',
'storage',
'idempotency'
],
properties: {
api_version: { type: 'string' },
schema_version: { type: 'string' },
auth: {
type: 'object',
required: ['required', 'schemes'],
properties: {
required: { type: 'boolean', const: capabilities.auth.required },
schemes: {
type: 'array',
items: { type: 'string' },
const: capabilities.auth.schemes
}
}
},
endpoints: { type: 'object', additionalProperties: { type: 'string' } },
defaults: { type: 'object' },
limits: { type: 'object' },
model_limits: { $ref: '#/components/schemas/AgentModelLimits' },
agent_streaming: { $ref: '#/components/schemas/AgentStreamingCapabilities' },
agent_jobs: { $ref: '#/components/schemas/AgentJobCapabilities' },
supported: { type: 'object' },
storage: {
type: 'object',
required: ['image_storage_mode', 'postgres_configured'],
properties: {
image_storage_mode: { type: 'string' },
postgres_configured: { type: 'boolean' }
}
},
idempotency: { type: 'object' }
}
},
AgentModelLimits: {
type: 'object',
required: ['gpt-image-2'],
properties: {
'gpt-image-2': {
type: 'object',
required: [
'max_edge',
'max_pixels',
'edge_multiple',
'max_aspect',
'min_pixels',
'recommended_presets',
'high_4k_risk'
],
properties: {
max_edge: { type: 'integer', minimum: 1 },
max_pixels: { type: 'integer', minimum: 1 },
edge_multiple: { type: 'integer', minimum: 1 },
max_aspect: { type: 'number', minimum: 1 },
min_pixels: { type: 'integer', minimum: 1 },
recommended_presets: {
type: 'array',
items: {
type: 'object',
required: ['name', 'size', 'purpose'],
properties: {
name: { type: 'string' },
size: { type: 'string' },
purpose: { type: 'string' }
}
}
},
high_4k_risk: {
type: 'object',
required: ['applies_to', 'guidance'],
properties: {
applies_to: { type: 'array', items: { type: 'string' } },
guidance: { type: 'string' }
}
}
}
}
}
},
AgentStreamingCapabilities: {
type: 'object',
required: ['generate', 'edit', 'page_sse'],
properties: {
generate: { $ref: '#/components/schemas/AgentEndpointStreamingCapability' },
edit: { $ref: '#/components/schemas/AgentEndpointStreamingCapability' },
page_sse: {
type: 'object',
required: ['supported', 'mode', 'endpoint', 'contract'],
properties: {
supported: { type: 'boolean', const: true },
mode: { type: 'string', enum: ['form_data_sse'] },
endpoint: { type: 'string' },
contract: { type: 'string', enum: ['page_ui_only'] }
}
}
}
},
AgentEndpointStreamingCapability: {
type: 'object',
required: ['supported', 'mode', 'endpoint'],
properties: {
supported: { type: 'boolean', const: false },
mode: { type: 'string', enum: ['non_streaming_only'] },
endpoint: { type: 'string' }
}
},
AgentJobCapabilities: {
type: 'object',
required: [
'supported',
'mode',
'intended_for',
'endpoints',
'states',
'current_guidance'
],
properties: {
supported: { type: 'boolean', const: true },
mode: { type: 'string', enum: ['job_polling'] },
intended_for: { type: 'array', items: { type: 'string' } },
endpoints: {
type: 'object',
required: ['create_generate_job', 'get_job', 'get_job_result'],
properties: {
create_generate_job: { type: 'string' },
get_job: { type: 'string' },
get_job_result: { type: 'string' }
}
},
states: { type: 'array', items: { type: 'string', enum: AGENT_JOB_STATES } },
current_guidance: { type: 'string' }
}
},
GenerateRequest: {
type: 'object',
required: ['prompt'],
properties: {
prompt: { type: 'string', maxLength: MAX_PROMPT_LENGTH },
model: { type: 'string', enum: AGENT_MODELS },
n: { type: 'integer', minimum: 1, maximum: MAX_IMAGE_COUNT },
size: { type: 'string' },
quality: { type: 'string', enum: AGENT_QUALITIES, default: 'high' },
output_format: { type: 'string', enum: AGENT_OUTPUT_FORMATS },
output_compression: { type: 'integer', minimum: 0, maximum: 100 },
background: { type: 'string', enum: AGENT_BACKGROUNDS },
moderation: { type: 'string', enum: AGENT_MODERATIONS },
response_mode: { type: 'string', enum: AGENT_RESPONSE_MODES, default: 'path' }
}
},
EditRequest: {
type: 'object',
required: ['prompt', 'image_0'],
properties: {
prompt: { type: 'string', maxLength: MAX_PROMPT_LENGTH },
model: { type: 'string', enum: AGENT_MODELS, default: 'gpt-image-2' },
n: { type: 'integer', minimum: 1, maximum: MAX_IMAGE_COUNT },
size: { type: 'string', default: 'auto' },
quality: { type: 'string', enum: AGENT_QUALITIES, default: 'auto' },
response_mode: { type: 'string', enum: AGENT_RESPONSE_MODES, default: 'path' },
image_0: { type: 'string', format: 'binary' },
mask: { type: 'string', format: 'binary' }
}
},
AgentArtifact: {
type: 'object',
required: ['id', 'filename', 'content_url', 'metadata_url', 'output_format', 'mime_type', 'size_bytes', 'width', 'height'],
properties: {
id: { type: 'string' },
filename: { type: 'string' },
content_url: { type: 'string' },
metadata_url: { type: 'string' },
output_format: { type: 'string', enum: AGENT_OUTPUT_FORMATS },
mime_type: { type: 'string' },
size_bytes: { type: 'integer', minimum: 0 },
width: { type: ['integer', 'null'] },
height: { type: ['integer', 'null'] },
b64_json: { type: 'string' }
}
},
AgentImageResponse: {
type: 'object',
required: ['request_id', 'idempotency_key', 'cached', 'images', 'created_at'],
properties: {
request_id: { type: 'string' },
idempotency_key: { type: 'string' },
cached: { type: 'boolean' },
images: {
type: 'array',
items: { $ref: '#/components/schemas/AgentArtifact' }
},
usage: { type: 'object' },
created_at: { type: 'string', format: 'date-time' }
}
},
AgentJobStatusResponse: {
type: 'object',
required: ['job'],
properties: {
job: {
type: 'object',
required: [
'id',
'request_id',
'idempotency_key',
'mode',
'state',
'created_at',
'updated_at',
'expires_at'
],
properties: {
id: { type: 'string' },
request_id: { type: 'string' },
idempotency_key: { type: 'string' },
mode: { type: 'string', enum: ['generate', 'edit'] },
state: { type: 'string', enum: AGENT_JOB_STATES },
created_at: { type: 'string', format: 'date-time' },
updated_at: { type: 'string', format: 'date-time' },
expires_at: { type: 'string', format: 'date-time' },
result_url: { type: 'string' },
retry_after_seconds: { type: 'integer', minimum: 1 },
error: {
type: 'object',
required: ['code', 'message', 'retryable'],
properties: {
code: { type: 'string' },
message: { type: 'string' },
retryable: { type: 'boolean' },
details: { type: 'object' },
upstream_status: { type: 'integer' },
diagnostics: { $ref: '#/components/schemas/AgentErrorDiagnostics' }
}
}
}
}
}
},
ArtifactMetadataResponse: {
type: 'object',
required: ['artifact'],
properties: {
artifact: { $ref: '#/components/schemas/AgentArtifact' }
}
},
DeleteArtifactResponse: {
type: 'object',
required: ['deleted', 'id'],
properties: {
deleted: { type: 'boolean' },
id: { type: 'string' }
}
},
AgentError: {
type: 'object',
required: ['error'],
properties: {
error: {
type: 'object',
required: ['code', 'message', 'retryable', 'request_id'],
properties: {
code: { type: 'string' },
message: { type: 'string' },
retryable: { type: 'boolean' },
details: { type: 'object' },
upstream_status: { type: 'integer' },
diagnostics: { $ref: '#/components/schemas/AgentErrorDiagnostics' },
request_id: { type: 'string' }
}
}
}
},
AgentErrorDiagnostics: {
type: 'object',
properties: {
elapsed_ms: { type: 'integer', minimum: 0 },
selected_channel_id: { type: 'string' },
upstream_host: { type: 'string' },
upstream_status: { type: 'integer' },
transport_error: { type: 'boolean' },
retry_after_seconds: { type: 'integer', minimum: 1 },
channel_cooldown_scope: { type: 'string', enum: ['credential', 'channel'] },
response_headers: {
type: 'object',
additionalProperties: { type: 'string' }
}
},
additionalProperties: false
}
}
}
};
}