{blockedReason}
} {canInspect &&{protocol.referenceLimitSource === 'verification' ? result.status === 'passed' && result.verifiedReferenceImageCount ? `参考图:已逐张验证 ${result.verifiedReferenceImageCount} 张,本平台最多 ${Math.min(protocol.maxImages, result.verifiedReferenceImageCount)} 张` : `参考图:数量待测试(本平台最多测试 ${protocol.maxImages} 张)` : `参考图:最多 ${protocol.maxImages} 张`}
}协议:{protocol.id}
接口:{protocol.path}
{(protocol.scopeId || protocol.routeId || protocol.operationId) &&路由:{[protocol.scopeId, protocol.routeId, protocol.operationId].filter(Boolean).join(' / ')}
}>} + {protocol?.maxPromptLength &&提示词:最多 {protocol.maxPromptLength} 字
} + {protocol?.inputField &&参考图参数:{protocol.inputField}({protocol.referenceTransport === 'multipart' ? '文件上传' : '内联图片'})
} + {protocol?.requestParameters &&当前请求参数
{Object.keys(protocol.requestParameters).length ? '未列出的可选参数使用上游默认值。' : '未指定尺寸、比例和画质,使用上游默认值。'}
{new Date(sharedCheckedAt).toLocaleString('zh-CN')}
} {sharedRequestId &&请求编号:{sharedRequestId}
} diff --git a/docs/ai-model-contracts-2026-10-16.md b/docs/ai-model-contracts-2026-10-16.md index 279bd63..98a8169 100644 --- a/docs/ai-model-contracts-2026-10-16.md +++ b/docs/ai-model-contracts-2026-10-16.md @@ -1,8 +1,8 @@ # AI 模型参数与能力核查 -核查时间:2026/10/16 11:01:27(UTC+8);目录抓取于 2026/10/16 11:01:27(UTC+8);平台状态读取于 2026/10/16 11:01:20(UTC+8)。只读取 [98k 公开模型目录](https://api.vip98k.com/api/v2/catalog/models) 及本平台管理员状态,不调用生成接口。 +核查时间:2026/10/16 14:22:23(UTC+8);目录抓取于 2026/10/16 13:57:04(UTC+8);平台状态读取于 2026/10/16 13:58:02(UTC+8)。只读取 [98k 公开模型目录](https://api.vip98k.com/api/v2/catalog/models) 及本平台管理员状态,不调用生成接口。 -公开目录共 75 个模型。本平台旧目录中另有 3 个不在本次公开目录。目录规范JSON摘要 SHA-256:`123f87ba50547c03e1c54b7f8c8308c993eeccffd6053f6fbc3284e2c1b26946`。 +公开目录共 75 个模型。本平台旧目录中另有 3 个不在本次公开目录。目录规范JSON摘要 SHA-256:`a11230d97f59052681539f9f45f610d312d4b07482874e57663f2e53f2d6b34a`。 透传字段仅表示网关接受/转发该字段,不证明具体模型执行该功能;只有实际测试记录可作为用途开放证据。未声明上限保持未知,不按模型名称推断。 @@ -29,29 +29,29 @@ | claude-fable-5-1 | vision | 分析、文案 | 分析:通过;文案:通过 | — | | claude-haiku-4-5-20251001 | text | 分析、文案 | 分析:失败;文案:失败 | — | | claude-opus-4-5-20251101 | vision | 分析、文案 | 分析:待核查;文案:待核查 | — | -| claude-opus-4-6 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| claude-opus-4-7 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| claude-opus-4-8 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| claude-opus-5 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| claude-opus-5-5 | vision | 分析、文案 | 分析:通过;文案:未测试 | 分析 | -| claude-sonnet-4-6 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| claude-sonnet-5 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3-flash | text | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | -| gemini-3-pro | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.1-flash-lite | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.1-pro | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.5-flash | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.5-flash-lite | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.7-flash | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gemini-3.8-flash | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gpt-5.6-sol | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gpt-5.6-terra | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gpt-6-astra | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gpt-6-sol | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| gpt-6.1-sol | text | 分析、文案 | 分析:未测试;文案:未测试 | — | -| grok-4.5 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| grok-4.6 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | -| grok-4.7 | vision | 分析、文案 | 分析:未测试;文案:未测试 | — | +| claude-opus-4-6 | vision | 分析、文案 | 分析:通过;文案:通过 | — | +| claude-opus-4-7 | vision | 分析、文案 | 分析:通过;文案:通过 | 分析 | +| claude-opus-4-8 | vision | 分析、文案 | 分析:通过;文案:通过 | — | +| claude-opus-5 | vision | 分析、文案 | 分析:通过;文案:通过 | — | +| claude-opus-5-5 | vision | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| claude-sonnet-4-6 | vision | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| claude-sonnet-5 | vision | 分析、文案 | 分析:通过;文案:通过 | — | +| gemini-3-flash | text | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| gemini-3-pro | text | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| gemini-3.1-flash-lite | text | 分析、文案 | 分析:通过;文案:通过 | 分析 | +| gemini-3.1-pro | text | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| gemini-3.5-flash | text | 分析、文案 | 分析:通过;文案:通过 | — | +| gemini-3.5-flash-lite | text | 分析、文案 | 分析:通过;文案:通过 | 分析 | +| gemini-3.7-flash | text | 分析、文案 | 分析:通过;文案:通过 | 分析 | +| gemini-3.8-flash | text | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| gpt-5.6-sol | text | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | +| gpt-5.6-terra | text | 分析、文案 | 分析:通过;文案:通过 | 文案 | +| gpt-6-astra | text | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | +| gpt-6-sol | text | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | +| gpt-6.1-sol | text | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | +| grok-4.5 | vision | 分析、文案 | 分析:失败;文案:通过 | 文案 | +| grok-4.6 | vision | 分析、文案 | 分析:通过;文案:通过 | 分析、文案 | +| grok-4.7 | vision | 分析、文案 | 分析:通过;文案:通过 | 分析 | | mimo-v2.5-asr | asr | 未接入详情页 | — | — | | mimo-v2.5-tts | tts | 未接入详情页 | — | — | | mimo-v2.5-tts-voiceclone | tts | 未接入详情页 | — | — | @@ -614,7 +614,6 @@ ### gpt-image-2 - 本平台可适配合同:`openai-image-generate`;有图片输入适配;参考图字段 `image`;平台合同限制最多8张(仍需实测);请求参数 `{"n":1,"quality":"medium","response_format":"b64_json","size":"1024x1024","stream":false}`;公开协议头 `{"X-Relay-Route-Option":"914de45a-498e-4cd8-9d21-b1515fe8b5f2"}`。 -- 本平台可适配合同:`async-image-task`;有图片输入适配;参考图字段 `file_urls`;平台合同限制最多8张(仍需实测);公开协议头 `{"X-Relay-Route-Option":"914de45a-498e-4cd8-9d21-b1515fe8b5f2"}`。 - 操作:`openai.images.generations.create`;`POST /v1/images/generations`;`application/json`。 - 输入声明:未声明;输出声明:未声明。 - 参数:`background`(offer_disabled)、`file_urls`(schema-only)、`image`(schema-only)、`image[]`(schema-only)、`images`(schema-only)、`input_fidelity`(schema-only)、`mask`(schema-only)、`model`(typed)、`moderation`(passthrough)、`n`(passthrough)、`output_compression`(offer_disabled)、`output_format`(offer_disabled)、`partial_images`(offer_disabled)、`prompt`(typed)、`quality`(passthrough)、`response_format`(typed)、`size`(passthrough)、`stream`(typed)、`style`(passthrough)、`user`(passthrough)。 @@ -660,7 +659,6 @@ ### nano-banana-2 - 本平台可适配合同:`openai-image-generate`;有图片输入适配;参考图字段 `image`;平台合同限制最多8张(仍需实测);请求参数 `{"aspect_ratio":"1:1","n":1,"response_format":"b64_json","size":"1024x1024","stream":false,"thinkingLevel":"minimal"}`;公开协议头 `{"X-Relay-Route-Option":"0d05dafd-e27c-4073-bd19-6a457582a55f"}`。 -- 本平台可适配合同:`async-image-task`;有图片输入适配;参考图字段 `file_urls`;平台合同限制最多8张(仍需实测);公开协议头 `{"X-Relay-Route-Option":"0d05dafd-e27c-4073-bd19-6a457582a55f"}`。 - 操作:`openai.images.generations.create`;`POST /v1/images/generations`;`application/json`。 - 输入声明:未声明;输出声明:未声明。 - 参数:`background`(typed)、`model`(typed)、`moderation`(passthrough)、`n`(passthrough)、`output_compression`(passthrough)、`output_format`(passthrough)、`partial_images`(passthrough)、`prompt`(typed)、`quality`(passthrough)、`response_format`(typed)、`size`(passthrough)、`stream`(typed)、`style`(passthrough)、`user`(passthrough)。 @@ -727,7 +725,6 @@ ### nano-banana-pro - 本平台可适配合同:`openai-image-generate`;有图片输入适配;参考图字段 `image`;平台合同限制最多8张(仍需实测);请求参数 `{"aspect_ratio":"1:1","n":1,"output_format":"jpeg","response_format":"b64_json","size":"1024x1024","stream":false}`;公开协议头 `{"X-Relay-Route-Option":"dbc2a91d-146a-48bc-9ae0-d3631d5efcd3"}`。 -- 本平台可适配合同:`async-image-task`;有图片输入适配;参考图字段 `file_urls`;平台合同限制最多8张(仍需实测);公开协议头 `{"X-Relay-Route-Option":"dbc2a91d-146a-48bc-9ae0-d3631d5efcd3"}`。 - 操作:`openai.images.generations.create`;`POST /v1/images/generations`;`application/json`。 - 输入声明:未声明;输出声明:未声明。 - 参数:`background`(typed)、`model`(typed)、`moderation`(passthrough)、`n`(passthrough)、`output_compression`(passthrough)、`output_format`(passthrough)、`partial_images`(passthrough)、`prompt`(typed)、`quality`(passthrough)、`response_format`(typed)、`size`(passthrough)、`stream`(typed)、`style`(passthrough)、`user`(passthrough)。 diff --git a/docs/ai-model-management.md b/docs/ai-model-management.md index 8c115bb..3053d11 100644 --- a/docs/ai-model-management.md +++ b/docs/ai-model-management.md @@ -40,6 +40,10 @@ HTTP 410“未留存”、访问失败、格式或模型身份无法核对时, 模型行的详情按钮默认折叠。展开后可查看当前用途实际绑定的协议、接口路径,以及目录提供的服务范围、路由和操作标识;已有结果还显示测试时间、消息和请求编号。这里显示服务端绑定,不允许前端随意修改路由。 +图片用途的展开详情还显示参考图字段、内联或文件上传方式、提示词长度限制及当前实际请求参数。未传的可选参数明确使用上游默认值,不用示例或模型名称声称已指定尺寸和画质。此处是只读诊断,展开不会调用模型或保存设置。 + +同步目录时,若同一服务范围明确提供可接收私有参考图的异步图片任务,则优先绑定该任务协议,先保存任务编号再读取同一任务结果。尺寸、比例、分辨率及其它已适配参数按对应公开 Schema 校验后传入;不向异步任务强加同步 Images 接口的 `n` 或 `response_format`。明确像素尺寸与可选 `resolution` 不重复发送;固定约束冲突或必填参数尚未适配时拒绝该路由。协议或参数改变会使相应模型需要重新测试,不会自动转移旧通过记录或扩大开放范围。原任务查询严格只允许 GET,不接受新的正文、幂等键或路由覆盖。 + ## 当前适配协议 | 用途 | 已实现的协议适配 | 实际测试检查 | @@ -85,3 +89,5 @@ HTTP 410“未留存”、访问失败、格式或模型身份无法核对时, 新阶段启动时清除上一阶段的请求号,防止把分析请求误当成文案请求查询。恢复前的本地检查失败、410、连接中断或读取权限失败都不能当作原请求执行失败。截断、工具调用要求、非 JSON 或不符合结构的已完成响应则明确记为未通过,不执行模型要求的工具,也不自动重试。分析警告会带入文案,不能被文案模型悄悄删除。 逐模型参数、公开声明、实际适配与已有测试记录见 [2026-10-16 模型合同核查](ai-model-contracts-2026-10-16.md)。本次恢复验收与未完成项见 [资料分析和文案恢复验收记录](ai-model-text-recovery-acceptance-2026-10-16.md)。公开 Schema 的透传字段不等于该模型的功能已经验收。 + +2026-10-16 下午通过 98k 对外接口复核恢复:公开目录、鉴权模型列表和此前已提交的异步图片任务结果均可读取,原任务已由生产 Worker 恢复完成,未重发生图。隔离验收中,Nano Banana 2、GPT Image 2.5 均通过四张独立参考图逐张内容校验,原结果可重新下载;GPT 6.1 的一次联合请求分别通过分析与文案校验,测试后的用途启用、默认值保存及权限拦截均已检查。GPT Image 2 的新测试返回 524,未返回客户请求号,保持结果未知且禁止自动重发;这不代表所有线路都已恢复。本轮隔离测试不改写生产用途验证记录,也不把测试数据库的记录复制为生产通过证据。证据见 `output/98k-integration-recovery-20261016/`。 diff --git a/lib/__tests__/ai-model-capabilities.test.ts b/lib/__tests__/ai-model-capabilities.test.ts index c067eb4..5bb740a 100644 --- a/lib/__tests__/ai-model-capabilities.test.ts +++ b/lib/__tests__/ai-model-capabilities.test.ts @@ -157,3 +157,10 @@ test('an ID-only private model entry can still withdraw a publicly available cap const merged = mergeAiModelCapabilityMetadata(unavailable, publicMetadata(model({ id: 'gemini-2.5-flash' }))) expect(getAiModelCapabilities(base, 'gemini-2.5-flash', merged)).toMatchObject({ kind: 'text', allowedStages: [] }) }) + +test('admin image diagnostics use the same bounded parameters as the execution adapter', () => { + const evidence = { version: 1, inputModalities: ['image', 'text'], outputModalities: ['image'], endpoints: ['/extensions/v1/tasks'], sourceUrl: null, + inputDeclared: true, outputDeclared: true, protocols: [{ id: 'async-image-task', path: '/extensions/v1/tasks', headers: {}, supportsImageInput: true, inputField: 'file_urls', maxPromptLength: 32000, requestParameters: { size: 'auto', resolution: '1K' } }] } + expect(getAiModelCapabilities(base, 'arbitrary-model', evidence).protocols?.image).toMatchObject({ inputField: 'file_urls', referenceTransport: 'json-inline', maxPromptLength: 32000, requestParameters: { size: 'auto', resolution: '1K' } }) + expect(JSON.stringify(getAiModelCapabilities(base, 'arbitrary-model', evidence))).not.toContain('headers') +}) diff --git a/lib/__tests__/ai-model-protocol-http.test.ts b/lib/__tests__/ai-model-protocol-http.test.ts index 1d217e6..362401f 100644 --- a/lib/__tests__/ai-model-protocol-http.test.ts +++ b/lib/__tests__/ai-model-protocol-http.test.ts @@ -41,6 +41,14 @@ test('retained response endpoint only permits an authenticated GET on the config expect(sent).toHaveLength(1) }) +test('accepted task results reject a body, a new idempotency key or route overrides before transport', async () => { + const path = '/extensions/v1/tasks/original-task/result' + for (const [body, key, headers] of [ + ['{}', undefined, {}], [undefined, 'new-generation', {}], [undefined, undefined, { 'X-Relay-Route-Option': 'different-route' }], + ] as const) await expect(requestAiModelOperation('https://models.example.com/v1', 'secret', path, body, key, headers)).rejects.toMatchObject({ code: 'protocol_invalid' }) + expect(lookup).not.toHaveBeenCalled(); expect(httpsRequest).not.toHaveBeenCalled() +}) + test.each(['/v1/unknown', '//169.254.169.254/metadata', '/extensions/v1/tasks/../../secret/result', '/extensions/v1/tasks/task-1/result?url=private'])('rejects unsupported or injected operation %s before DNS', async path => { await expect(requestAiModelOperation('https://models.example.com/v1', 'secret', path, '{}', 'request-1', {})).rejects.toMatchObject({ code: 'protocol_unsupported' }) expect(lookup).not.toHaveBeenCalled(); expect(httpsRequest).not.toHaveBeenCalled() diff --git a/lib/__tests__/ai-model-protocol.test.ts b/lib/__tests__/ai-model-protocol.test.ts index 446b322..d03e556 100644 --- a/lib/__tests__/ai-model-protocol.test.ts +++ b/lib/__tests__/ai-model-protocol.test.ts @@ -13,6 +13,74 @@ let png: Buffer beforeAll(async () => { png = await sharp({ create: { width: 32, height: 32, channels: 3, background: '#ff0000' } }).png().toBuffer() }) beforeEach(() => { jest.clearAllMocks() }) +const parameterizedAsyncModel = (properties: Record