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 = {}; 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) { 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 } } } }; }