@acosmi/sdk-ts 2.19.5 → 2.19.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +7 -2
- package/dist/browser/index.mjs +10 -2
- package/dist/browser/index.mjs.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/index.mjs.map +1 -1
- package/dist/node/adapters/anthropic.cjs.map +1 -1
- package/dist/node/adapters/anthropic.d.cts +1 -1
- package/dist/node/adapters/anthropic.d.ts +1 -1
- package/dist/node/adapters/anthropic.mjs.map +1 -1
- package/dist/node/adapters/openai.cjs.map +1 -1
- package/dist/node/adapters/openai.d.cts +2 -2
- package/dist/node/adapters/openai.d.ts +2 -2
- package/dist/node/adapters/openai.mjs.map +1 -1
- package/dist/node/{format-DWA12pVT.d.cts → format-CKpXSSqY.d.cts} +22 -0
- package/dist/node/{format-DWA12pVT.d.ts → format-CKpXSSqY.d.ts} +22 -0
- package/dist/node/index.cjs +10 -2
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +4 -4
- package/dist/node/index.d.ts +4 -4
- package/dist/node/index.mjs +10 -2
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/{openai-Cuu_DPxM.d.cts → openai-ByUO-cSR.d.cts} +1 -1
- package/dist/node/{openai-DsbJUOOW.d.ts → openai-CH0wnJXv.d.ts} +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/adapters/openai.ts"],"names":["events"],"mappings":"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","file":"openai.mjs","sourcesContent":["// models/types.ts — 模型网关域类型。\n//\n// 端口自 acosmi-sdk-go/types.go (v0.19.0, 1275 行)\n//\n// 命名约定:\n// - 字段名 = Go json tag 字面量 (wire format), 不做 camelCase 重映射\n// - mix 风格 (modelId / max_tokens / supports_thinking) 是历史 wire 设计的真实样貌\n// - 这样 JSON.parse / JSON.stringify 可直接来回, 与 Go 侧 0 偏差\n//\n// int64 字段用 number — JS number 安全上限 2^53, Acosmi 业务量级远低于此。\n// time.Time 字段用 ISO 8601 string 形态, 与 Go json wire 一致。\n// json.RawMessage 用 unknown (caller 自行 typed)。\n// json.Number 用 string (避免精度丢失)。\n\n// =============================================================================\n// Managed Models\n// =============================================================================\n\n/** 模型能力矩阵 — 下游通过此结构决定 UI 功能开关和 Beta Header 注入 */\nexport interface ModelCapabilities {\n // 思考能力\n supports_thinking: boolean;\n supports_adaptive_thinking: boolean;\n /** 交错思考 (Interleaved Thinking) */\n supports_isp: boolean;\n\n // 工具与搜索\n supports_web_search: boolean;\n supports_tool_search: boolean;\n supports_structured_output: boolean;\n\n // 推理控制\n supports_effort: boolean;\n /** 模型是否支持 thinking_level=\"max\" 强度档 (深度思考) */\n supports_max_effort: boolean;\n /** Opus 4.6 独有 (Speed=\"fast\") */\n supports_fast_mode: boolean;\n /** Auto 模式 (模型自主选择工具/搜索策略) */\n supports_auto_mode: boolean;\n\n // 上下文与缓存\n supports_1m_context: boolean;\n supports_prompt_cache: boolean;\n /** 通过 context-management beta 控制 */\n supports_cache_editing: boolean;\n\n // 输出控制\n /** Claude 4 内置 */\n supports_token_efficient: boolean;\n supports_redact_thinking: boolean;\n\n // Token 上限 (冗余但便于查询)\n max_input_tokens: number;\n max_output_tokens: number;\n\n /**\n * 桌面视觉理解 sidecar 能力 (v1.2+):\n * 为 true 表示该模型适合作为桌面截图解析 sidecar — 输入 screenshot,\n * 输出结构化 UI/视觉描述, 供非多模态主模型消费 (CrabCode desktop automation /\n * computer-use 选模型时使用).\n *\n * 与 ManagedModel.inputModalities=['image'] 是正交两件事:\n * - inputModalities 描述\"模型能不能吃图片输入\"\n * - supports_desktop_visual_understanding 描述\"模型是不是被运营标记为\n * 适合做桌面 UI 解析的 sidecar\" (有些通用视觉模型并不擅长 UI 解析)\n *\n * 调用方不应通过模型名 substring 推断此能力; 必须读上游下发字段.\n */\n supports_desktop_visual_understanding?: boolean;\n\n /**\n * 图片生成能力 (v1.3+): 为 true 表示该托管模型是图片生成模型 (DALL·E / SD / 即梦 等),\n * 应通过 client.generateImage() 调用, 而非 chat(). 计量维度由上游派生为 IMAGE (按图计费)。\n */\n supports_image_generation?: boolean;\n\n /**\n * 视频生成能力 (v1.3+): 为 true 表示该托管模型是视频生成模型 (Sora / 可灵 / Runway 等),\n * 应通过 client.generateVideo() + pollVideoTask() 调用。计量维度派生为 VIDEO (按时长计费)。\n */\n supports_video_generation?: boolean;\n\n /**\n * 向量能力 (v2.9+): 为 true 表示该托管模型是向量模型 (DashScope text-embedding-v4 等),\n * 应通过 client.embeddings() 调用。\n */\n supports_embedding?: boolean;\n\n /**\n * 重排序能力 (v2.9+): 为 true 表示该托管模型是重排序模型 (DashScope gte-rerank-v2 / qwen3-rerank 等),\n * 应通过 client.rerank() 调用。\n */\n supports_rerank?: boolean;\n}\n\n/** 图片生成请求 (client.generateImage) */\nexport interface ImageGenerationRequest {\n prompt: string;\n /** 缺省 1024 */\n width?: number;\n /** 缺省 1024 */\n height?: number;\n style?: string;\n}\n\n/** 图片生成响应 */\nexport interface ImageGenerationResponse {\n url?: string;\n b64_json?: string;\n revised_prompt?: string;\n requestId?: string;\n}\n\n/** 视频生成请求 (client.generateVideo) */\nexport interface VideoGenerationRequest {\n prompt: string;\n resolution?: string;\n /** 时长 (秒) */\n duration?: number;\n}\n\n/** 视频任务响应 (创建返回 taskId; 轮询返回 status/videoUrl) */\nexport interface VideoTaskResponse {\n taskId: string;\n /** pending | running | completed | failed */\n status?: string;\n videoUrl?: string;\n error?: string;\n requestId?: string;\n}\n\n// ── 向量 (Embedding) / 重排序 (Rerank) (v2.9.0) ──────────────────────────────\n// SDK 订阅会员经 POST /managed-models/:id/{embeddings,rerank} 调用; 仅\n// capabilities.supports_embedding / supports_rerank 的托管模型可用。上游接 DashScope。\n\n/**\n * 多模态内容片段 (v2.10+; 向量/重排序 qwen3-vl-embedding / qwen3-vl-rerank 线路)。\n * text / image / video 至少填一项; image/video 取 URL 或 base64 data URI。\n */\nexport interface MultimodalContent {\n /** 文本片段 */\n text?: string;\n /** 图片 (URL 或 base64 data URI) */\n image?: string;\n /** 视频 (URL); 仅多模态线路支持 */\n video?: string;\n}\n\n/** 向量请求 (client.embeddings)。文本线路 = OpenAI /v1/embeddings 标准; 多模态线路用 contents。 */\nexport interface EmbeddingRequest {\n /** 文本输入 (文本向量线路): 单条文本或文本数组 (批量)。多模态线路改用 contents。 */\n input?: string | string[];\n /**\n * 多模态内容 (v2.10+; qwen3-vl-embedding 线路): text/image/video 片段数组。\n * 与 input 二选一; 多模态托管模型须用 contents。\n */\n contents?: MultimodalContent[];\n /** 向量维度 (可选; DashScope text-embedding-v4 支持 2048/1536/1024/768/512/256/128/64) */\n dimensions?: number;\n /** 编码格式 (可选, 如 \"float\") */\n encoding_format?: string;\n /** 输出向量类型 (可选; 多模态线路, 如 \"dense\") */\n output_type?: string;\n /** 视频抽帧率 fps (可选; 多模态视频输入) */\n fps?: number;\n /** 是否启用多模态特征融合 (可选; 多模态线路) */\n enable_fusion?: boolean;\n}\n\n/** 单条向量结果 (OpenAI 标准)。 */\nexport interface EmbeddingData {\n object: string;\n index: number;\n embedding: number[];\n}\n\n/** 向量响应 (OpenAI /v1/embeddings 标准)。 */\nexport interface EmbeddingResponse {\n object: string;\n model: string;\n data: EmbeddingData[];\n usage: { prompt_tokens?: number; total_tokens: number };\n id?: string;\n}\n\n/** 重排序查询 (v2.10+): 文本字符串, 或多模态 {text|image}。 */\nexport type RerankQuery = string | { text?: string; image?: string };\n/** 重排序候选文档 (v2.10+): 文本字符串, 或多模态 {text|image|video}。 */\nexport type RerankDocument = string | MultimodalContent;\n\n/** 重排序请求 (client.rerank)。统一扁平契约, 网关内部按模型线路转换。 */\nexport interface RerankRequest {\n /** 查询: 文本字符串 (文本线路), 或多模态 {text|image} (qwen3-vl-rerank 线路) */\n query: RerankQuery;\n /** 候选文档: 文本字符串数组, 或多模态 {text|image|video} 对象数组 */\n documents: RerankDocument[];\n /** 返回前 N 条 (可选; 缺省返回全部) */\n top_n?: number;\n /** 是否在结果中回传文档原文 (可选, 缺省 false) */\n return_documents?: boolean;\n /** 排序指令 (可选; 仅 OpenAI 兼容线路支持, 原生线路忽略) */\n instruct?: string;\n /** 视频抽帧率 fps (可选; 多模态视频文档) */\n fps?: number;\n}\n\n/** 单条重排序结果。 */\nexport interface RerankResult {\n /** 文档在原 documents 数组中的下标 */\n index: number;\n /** 相关性得分 (越高越相关) */\n relevance_score: number;\n /** 文档原文 (仅 return_documents=true 时存在) */\n document?: string;\n}\n\n/** 重排序响应。 */\nexport interface RerankResponse {\n results: RerankResult[];\n usage: { total_tokens: number };\n model?: string;\n}\n\n/**\n * 模型可接收的用户输入模态 —— **本 SDK 已知的那些** (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n * - 'video': 视频输入 (多模态向量 / 重排序类模型, 网关 2026-06-20 起下发)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n *\n * 这个联合体是**查询用**的 (helper 的 modality 参数), 不是数据域的全集 —— 见\n * {@link InputModalityTag}.\n */\nexport type InputModality = 'text' | 'image' | 'video';\n\n/**\n * 目录里实际可能出现的模态标签 —— **开放值域**, 已知取值见 {@link InputModality}.\n *\n * 值域由网关托管模型目录持有, 靠运营动作扩张 (不是靠发 SDK 新版)。把数据字段\n * 声明成封闭联合体, 等于让类型对在网真实数据撒谎; 2026-08-02 CrabCode 客户端\n * 因同一形态的封闭枚举 (Rust 侧) 把整个网关模型列表打空过数周。\n *\n * 因此: **查询**用 {@link InputModality} (已知标签, 有自动补全),\n * **数据**用本类型 (照收未知标签, 不丢信息、不谎报)。\n *\n * 刻意就是朴素 `string`, 不用 `InputModality | (string & {})` 那个\"保留补全\"的\n * 技巧: 补全只在**书写**字面量时有价值, 而本类型描述的是**读到**的目录数据;\n * 那个技巧还要靠 `{}` —— 本仓 eslint `ban-types` 明令禁止。\n */\nexport type InputModalityTag = string;\n\n/** BucketClass 字面量常量 — V30 二轮审计 D-P1-3 修复 */\nexport const BucketClassCommercial = 'COMMERCIAL';\nexport const BucketClassGeneric = 'GENERIC';\n\n/**\n * 用户在某 modelId 上的桶余额聚合视图 (V30 entitlement-listing).\n *\n * 多桶聚合规则 (上游 nexus-v4 计算后下发, 全部单位为 ETU):\n * - quotaEtu / usedEtu / remainingEtu: 该 modelId 下用户全部 active 桶求和\n * - sharedPoolEtu: 求和量中\"来自通配桶\"的部分 (跨模型可消耗)\n * - bucketClass / expiresAt: 取最高优先级桶\n * - expired: 全部桶都过期才置 true\n */\nexport interface BucketInfo {\n quotaEtu: number;\n usedEtu: number;\n remainingEtu: number;\n sharedPoolEtu?: number;\n expiresAt?: string;\n bucketClass: string;\n expired?: boolean;\n /** v0.19+ — GENERIC alive 桶 tokenRemaining 求和 (\"免费余额\", 真实可消费) */\n freeRemainingEtu: number;\n /** v0.19+ — COMMERCIAL alive 桶 tokenRemaining 求和 (\"付费余额\") */\n paidRemainingEtu: number;\n}\n\n/** 大小写不敏感判定 — 与 buildBucketView (managed_model.go) EqualFold 语义对齐 */\nexport function bucketInfoIsCommercial(b: BucketInfo | null | undefined): boolean {\n if (!b) return false;\n return b.bucketClass.toLowerCase() === BucketClassCommercial.toLowerCase();\n}\n\n/** 托管模型 */\nexport interface ManagedModel {\n id: string;\n name: string;\n provider: string;\n modelId: string;\n maxTokens: number;\n isEnabled: boolean;\n /** @deprecated 公开 /managed-models 端点不返回此字段 (最小暴露原则); 仅 admin 响应或历史端口残留, listModels() 结果中恒为 undefined。 */\n pricePerMTok?: number;\n /** @deprecated 公开 /managed-models 端点不返回此字段 (最小暴露原则); 仅 admin 响应或历史端口残留, listModels() 结果中恒为 undefined。 */\n isDefault?: boolean;\n contextWindow?: number;\n capabilities: ModelCapabilities;\n\n /** P1 商品化档位门控: 0=FREE..4=ULTRA。前端模型选择器按用户档位过滤。 */\n minPlanTier?: number;\n /** 该模型能否在 C 端对话 (ADK 托管运行时) 使用; false 时选择器应过滤。 */\n chatRuntimeSupported?: boolean;\n /** 模型默认绑定的工具 ID 列表。 */\n defaultToolIds?: string[];\n /** 该模型对当前用户档位是否被限制策略锁定 (limit_policy allowedModels 不含)。true=展示但置灰+升级引导。enforced 关/解析失败=false。 */\n locked?: boolean;\n /** 是否属于\"免费会员可用\"分区 (FREE 档 allowedModels 内)。与当前用户档位无关 (仅分类)。 */\n freeTier?: boolean;\n\n /**\n * 上游 gateway 为此模型启用的请求格式列表\n * 取值: \"anthropic\" | \"openai\"\n * 空值表示上游未声明, SDK 回落 provider 硬编码分支 (向后兼容)\n */\n supported_formats?: string[];\n\n /**\n * 上游建议客户端优先使用的格式\n * 取值: \"anthropic\" | \"openai\"; 空值等价于 supported_formats[0]\n */\n preferred_format?: string;\n\n /**\n * 该模型可选的思考深度档位 id(升序,v2.18+)。取值是 {@link ThinkingOff} / {@link ThinkingHigh} /\n * {@link ThinkingMax} 的子集;网关按「admin 声明 ∩ wire 层真投递」读时派生。\n * `[]` = 该模型没有思考档(不要渲染档位选择);`undefined` = 上游未播报(旧网关),\n * 调用方必须按\"未知\"处理,严禁回落到自己推档或按模型名猜。\n */\n thinking_levels?: string[];\n\n /**\n * 当前用户在此模型上的桶余额聚合 (V0.18 V30 entitlement-listing).\n *\n * 仅当调用方为非 admin 用户 (web user / desktop OAuth) 时上游才会返回此字段:\n * - admin / X-Internal-Bypass 调用 → 缺失\n * - 普通用户 → 非空, 含 quota/used/remaining 求和\n */\n bucketInfo?: BucketInfo;\n\n /**\n * 模型可接收的用户输入模态 (v1.2+, CrabCode desktop automation / computer-use 选模型用).\n *\n * 已知取值见 {@link InputModality} ('text' | 'image' | 'video'); 'image' 表示模型可\n * 直接接收 screenshot/image 输入。类型是**开放**的 ({@link InputModalityTag}) ——\n * 目录新增模态时旧版 SDK 照样原样透出, 不静默丢值。\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModalityTag[];\n}\n\n// =============================================================================\n// QuotaSummary — v0.19+ 账户级权益总览 (免费/付费切分)\n// =============================================================================\n\n/** 单桶视图 — QuotaSummary.freeBuckets/paidBuckets 元素 */\nexport interface BucketRow {\n bucketId: string;\n /** 精确桶为具体 modelId, 通配桶为 \"*\" */\n modelId: string;\n /** COMMERCIAL | GENERIC */\n bucketClass: string;\n tokenQuota: number;\n tokenUsed: number;\n tokenRemaining: number;\n /** 永久桶为 undefined */\n expiresAt?: string;\n expired?: boolean;\n}\n\nexport function bucketRowIsCommercial(r: BucketRow | null | undefined): boolean {\n if (!r) return false;\n return r.bucketClass.toLowerCase() === BucketClassCommercial.toLowerCase();\n}\n\n/** 滚动窗口限额状态 — QuotaSummary.windowLimits 元素 */\nexport interface WindowLimitStatus {\n /** 窗口档位: FIVE_HOUR = 5 小时滚动窗口, WEEKLY = 7 天滚动窗口 */\n kind: 'FIVE_HOUR' | 'WEEKLY';\n /** 窗口内 credit 用量上限 */\n limitCredits: number;\n /** 窗口内已用 credit */\n usedCredits: number;\n /** 预计恢复时间 (ISO-8601 UTC); 窗口内无用量时为 null / 缺失 */\n resetAt?: string | null;\n /**\n * [W1 2026-07-11 软限额] 该窗此刻可否被用户「继续」开关豁免 — 5h 软限额时 true (展示端可给\n * 严格模式切换入口), 周窗恒 false。老网关不返回时缺失。\n */\n overridable?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914] 网关 `quota_summary.go` 的服务端合成位: 该窗已用满, 但用户的「继续」\n * 开关正在放行。新网关恒下发, 老网关缺席 —— 缺席只说明网关没有这个字段, 不能当 false 读。\n */\n fiveHourSoftContinuing?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914 D3-7] 窗口内在途预占 (微 Credits): 已被进行中的请求预扣、尚未结算的额度。\n * `usedCredits` 只计已结算, 准入判定却按「已结算 + 在途」算 —— 本字段就是两者之差。\n * 缺失 / null = 不知道 (老网关或取数失败), 不能当 0 读; 0 = 此刻没有在途。\n */\n inflightHeldCredits?: number | null;\n}\n\n/**\n * 订阅池视图 — QuotaSummary.subscriptionPool 的形状。\n *\n * 由网关 `quota_summary.go` 从 alive 的 COMMERCIAL (付费) 桶派生。quota / used / remaining 的\n * 数值单位为**微 Credits** (÷1000 = Credits)。\n */\nexport interface QuotaSummarySubscriptionPool {\n /** 池总额度 (微 Credits) */\n quota: number;\n /** 已用额度 (微 Credits) */\n used: number;\n /** 剩余额度 (微 Credits) */\n remaining: number;\n /** 到期时间; 网关未下发时缺失 */\n expiresAt?: string;\n /** 网关下发的单位标识 */\n unit: string;\n}\n\n/**\n * GET /api/v4/entitlements/quota-summary 返回体, 调用 client.getQuotaSummary 获取.\n *\n * 设计目的: 个人中心钱包栏一次性展示\"我还有多少免费 + 多少付费\"+ 各自最近到期时间.\n */\nexport interface QuotaSummary {\n /** GENERIC (免费/赠送) alive 桶 tokenRemaining 求和 */\n freeTotalEtu: number;\n /** COMMERCIAL (付费购买) alive 桶 tokenRemaining 求和 */\n paidTotalEtu: number;\n /** GENERIC 桶详情 (含过期, UI 列流水); 空时为空数组 */\n freeBuckets: BucketRow[];\n /** COMMERCIAL 桶详情 (含过期); 空时为空数组 */\n paidBuckets: BucketRow[];\n /** GENERIC alive 桶中最早到期; 永久桶/无 alive 时缺失 */\n nextFreeExpiresAt?: string;\n /** COMMERCIAL alive 桶中最早到期; 同上 */\n nextPaidExpiresAt?: string;\n /** 滚动窗口限额状态 (5 小时 / 7 天); 后端未启用窗口限额时字段整个缺失 */\n windowLimits?: WindowLimitStatus[];\n /**\n * [W1 2026-07-11 软限额] 用户 5h「继续」开关现值 (true=软限额/默认: 到达 5h 仅提醒并继续消耗\n * 周配额; false=严格模式: 到达即等待恢复)。仅后端启用窗口限额时下发, 否则缺失。\n */\n windowFiveHourContinueEnabled?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914] 订阅池: 网关由 alive 的 COMMERCIAL (付费) 桶派生, 数值单位为微 Credits\n * (÷1000 = Credits)。缺席 = 老网关, 或当前没有 alive 付费桶 —— 两种都不可当 0 处理。\n */\n subscriptionPool?: QuotaSummarySubscriptionPool;\n}\n\n// =============================================================================\n// WindowResetSummary — v2.13+ 邀请奖励窗口重置券\n// =============================================================================\n\n/**\n * GET /api/v4/entitlements/window-reset/summary 返回体, 调用 client.getWindowResetSummary 获取.\n *\n * 设计目的: 个人中心邀请栏与 429 窗口限额弹窗一次性展示\"我还有几张重置券 + 最近一张何时过期\",\n * 并给出邀请进度 (已达标 / 待达标) 作为继续邀请的引导。\n *\n * 券由邀请奖励发放 (被邀请人激活并达到用量门槛后计一次), 每张可清空一次滚动窗口已用量\n * (见 client.redeemWindowReset)。\n */\nexport interface WindowResetSummary {\n /** 当前可用 (未使用且未过期) 的重置券张数 */\n availableCount: number;\n /** 可用券中最早到期时间 (ISO-8601 UTC); 无可用券时为 null */\n nextExpireAt: string | null;\n /** 历史累计发放张数 (含已用 / 已过期) */\n totalGranted: number;\n /** 历史累计已使用张数 */\n totalUsed: number;\n /** 已达标并已发放重置券的邀请数 */\n qualifiedInvites: number;\n /** 已激活但尚未达到发放门槛的邀请数 (仍可能转为已达标) */\n pendingInvites: number;\n}\n\n// =============================================================================\n// Chat\n// =============================================================================\n\n/** 聊天消息 (简单文本格式, CrabClaw 使用) */\nexport interface ChatMessage {\n role: string;\n content: string;\n}\n\n/** Anthropic 响应内容块 */\nexport interface ChatContentBlock {\n type: string;\n text?: string;\n citations?: unknown;\n thinking?: string;\n signature?: string;\n data?: string;\n id?: string;\n name?: string;\n /** json.RawMessage in Go */\n input?: unknown;\n server_name?: string;\n caller?: unknown;\n tool_use_id?: string;\n content?: unknown;\n is_error?: boolean;\n}\n\n/** Anthropic 格式 token 用量 */\nexport interface ChatUsage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n}\n\n// ---------- Thinking ----------\n\n/** 三档思考级别 (v0.9.0) */\nexport const ThinkingOff = 'off';\nexport const ThinkingLow = 'low';\nexport const ThinkingHigh = 'high';\nexport const ThinkingMax = 'max';\n\n/** 标准思考最低 maxTokens — CrabCode 默认 MAX_OUTPUT_TOKENS_DEFAULT = 32_000 */\nexport const ThinkingHighMinMaxTokens = 32_000;\n/** 深度思考回退 maxTokens — Opus 4.6 上限 128K */\nexport const ThinkingMaxFallbackMaxTokens = 128_000;\n\n/** 控制模型思考行为 */\nexport interface ThinkingConfig {\n /** \"enabled\" | \"disabled\" | \"adaptive\" */\n type: string;\n /** 仅 type=\"enabled\" 时 (旧模型回退) */\n budget_tokens?: number;\n /** 思考级别 (v0.9.0): \"off\" | \"high\" | \"max\" */\n level?: string;\n /** \"none\" | \"summary\" | \"\" (默认空=完整) */\n display?: string;\n}\n\n/** 根据三档 level 创建配置 */\nexport function newThinkingConfig(level: string): ThinkingConfig {\n if (level === '' || level === ThinkingOff) {\n return { type: 'disabled' };\n }\n return { type: 'adaptive', level };\n}\n\n/** 服务端工具定义 — SDK 将工具 schema 合入 API 请求的 tools 数组 */\nexport interface ServerTool {\n type: string;\n name: string;\n config?: Record<string, unknown>;\n}\n\n/** Server Tool 类型常量 */\nexport const ServerToolTypeWebSearch = 'web_search_20250305';\n\n/** 地理位置 */\nexport interface GeoLoc {\n /** ISO 3166-1 alpha-2 */\n country: string;\n city?: string;\n}\n\n/** ServerTool.config 结构 (type=web_search_20250305) */\nexport interface WebSearchConfig {\n /** 每请求最大搜索次数, 默认 8 */\n max_uses?: number;\n /** 域名白名单 (与 blocked_domains 互斥) */\n allowed_domains?: string[];\n /** 域名黑名单 (与 allowed_domains 互斥) */\n blocked_domains?: string[];\n user_location?: GeoLoc;\n}\n\n/** 控制推理努力级别 */\nexport interface EffortConfig {\n /** \"low\" | \"medium\" | \"high\" | \"max\" */\n level: string;\n}\n\n/** 控制输出格式 (结构化输出) */\nexport interface OutputConfig {\n /** \"json_schema\" | \"\" */\n format?: string;\n schema?: unknown;\n}\n\n/**\n * 创建搜索 Server Tool 的便捷方法\n * allowed_domains 与 blocked_domains 互斥, 同时传入抛 Error\n */\nexport function newWebSearchTool(cfg?: WebSearchConfig | null): ServerTool {\n const st: ServerTool = {\n type: ServerToolTypeWebSearch,\n name: 'web_search',\n };\n if (cfg) {\n if ((cfg.allowed_domains?.length ?? 0) > 0 && (cfg.blocked_domains?.length ?? 0) > 0) {\n throw new Error('web search: allowed_domains and blocked_domains are mutually exclusive');\n }\n const m: Record<string, unknown> = {};\n if (cfg.max_uses && cfg.max_uses > 0) m['max_uses'] = cfg.max_uses;\n if (cfg.allowed_domains?.length) m['allowed_domains'] = cfg.allowed_domains;\n if (cfg.blocked_domains?.length) m['blocked_domains'] = cfg.blocked_domains;\n if (cfg.user_location) m['user_location'] = cfg.user_location;\n if (Object.keys(m).length > 0) st.config = m;\n }\n return st;\n}\n\n/**\n * 聊天请求\n *\n * 基础字段供 CrabClaw 使用, 扩展字段供 CrabCode 使用。\n * 所有新增字段零值不改变行为 (向后兼容)。\n *\n * 注意:扩展字段(rawMessages/system/tools/...)不会被原样 JSON.stringify,\n * 而由 buildRequestBody (adapter) 选择性序列化到请求体。\n */\nexport interface ChatRequest {\n // ── 基础字段 (CrabClaw 兼容) ──\n messages?: ChatMessage[];\n stream?: boolean;\n max_tokens?: number;\n\n // ── 完整请求控制 (CrabCode 扩展) ── 不直接 JSON 序列化, 由 buildRequestBody 处理\n /** 复杂消息体 (含 content blocks / 多模态), 非 nil 时优先于 messages */\n rawMessages?: unknown;\n /** string 或 ContentBlock[] */\n system?: unknown;\n /** 标准工具定义 (Tool[]) */\n tools?: unknown;\n temperature?: number;\n thinking?: ThinkingConfig;\n metadata?: Record<string, string>;\n /** 显式 beta (SDK 自动合并) */\n betas?: string[];\n /** 服务端工具 (buildRequestBody 合入 tools 数组) */\n serverTools?: ServerTool[];\n /** \"\" | \"fast\" (Fast Mode) */\n speed?: string;\n effort?: EffortConfig;\n outputConfig?: OutputConfig;\n /** 任意扩展字段 (buildRequestBody 合入请求体) */\n extraBody?: Record<string, unknown>;\n\n /**\n * v0.13.0: OpenAI wire format 原生字段。AnthropicAdapter 忽略,\n * OpenAIAdapter 按 OpenAI 规范序列化。\n *\n * 对应 OpenAI `parallel_tool_calls` 顶层字段, undefined = 不设置 (沿用上游默认 true)。\n */\n parallelToolCalls?: boolean;\n\n /**\n * v1.6.0: 业务侧终端用户 id, 跨 provider 通用语义 (不绑死 DeepSeek)。\n * 仅当 caller 拥有 scope=endusr.set 时网关采信; 否则被网关 sanitizer 校验/覆盖。\n * 约束: 长度 ≤ 512, 字符集 [a-zA-Z0-9_-]+, 禁止包含用户隐私信息。\n * 二选一:\n * OpenAI wire → 序列化为顶层 body[\"user_id\"]\n * Anthropic wire → 合并到 body[\"metadata\"][\"user_id\"]; caller metadata 显式键优先, 不被覆盖\n */\n endUserId?: string;\n}\n\n// ---------- Web Search Sources ----------\n\n/** 联网搜索结果来源 (与后端 adk/stream_helpers.go SourceItem 对齐) */\nexport interface WebSearchSource {\n title: string;\n url: string;\n snippet?: string;\n}\n\n/** 搜索来源事件 (从 SSE \"sources\" 事件解析) */\nexport interface SourcesEvent {\n sources: WebSearchSource[];\n session_id?: string;\n}\n\n/** sources 事件结构错误的稳定机器码。 */\nexport type SourcesEventIssueCode =\n | 'invalid_json'\n | 'missing_sources'\n | 'sources_not_array'\n | 'source_not_object'\n | 'source_title_invalid'\n | 'source_url_invalid'\n | 'source_snippet_invalid'\n | 'session_id_invalid';\n\n/**\n * sources SSE 的无歧义分类结果。\n *\n * `empty_sources` 是合法零结果;`malformed_sources` 才表示结构损坏。\n */\nexport type SourcesEventParseResult =\n | { kind: 'not_sources' }\n | { kind: 'empty_sources'; session_id?: string }\n | { kind: 'sources'; value: SourcesEvent }\n | { kind: 'malformed_sources'; code: SourcesEventIssueCode };\n\n/**\n * 同时识别 SSE event name 与 JSON payload discriminator,并把合法空结果\n * 与非 sources、结构损坏明确分开。未知额外字段不影响分类。\n */\nexport function classifySourcesEvent(ev: StreamEvent): SourcesEventParseResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(ev.data);\n } catch {\n return ev.event === 'sources'\n ? { kind: 'malformed_sources', code: 'invalid_json' }\n : { kind: 'not_sources' };\n }\n\n const wrapper =\n typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n if (wrapper?.type !== 'sources' && ev.event !== 'sources') {\n return { kind: 'not_sources' };\n }\n if (!wrapper || !Object.prototype.hasOwnProperty.call(wrapper, 'sources')) {\n return { kind: 'malformed_sources', code: 'missing_sources' };\n }\n if (\n Object.prototype.hasOwnProperty.call(wrapper, 'session_id') &&\n wrapper.session_id !== undefined &&\n typeof wrapper.session_id !== 'string'\n ) {\n return { kind: 'malformed_sources', code: 'session_id_invalid' };\n }\n if (!Array.isArray(wrapper.sources)) {\n return { kind: 'malformed_sources', code: 'sources_not_array' };\n }\n\n const sessionID = typeof wrapper.session_id === 'string' ? wrapper.session_id : undefined;\n if (wrapper.sources.length === 0) {\n return {\n kind: 'empty_sources',\n ...(sessionID === undefined ? {} : { session_id: sessionID }),\n };\n }\n\n for (const source of wrapper.sources) {\n if (typeof source !== 'object' || source === null || Array.isArray(source)) {\n return { kind: 'malformed_sources', code: 'source_not_object' };\n }\n const item = source as Record<string, unknown>;\n if (typeof item.title !== 'string') {\n return { kind: 'malformed_sources', code: 'source_title_invalid' };\n }\n if (typeof item.url !== 'string') {\n return { kind: 'malformed_sources', code: 'source_url_invalid' };\n }\n if (\n Object.prototype.hasOwnProperty.call(item, 'snippet') &&\n item.snippet !== undefined &&\n typeof item.snippet !== 'string'\n ) {\n return { kind: 'malformed_sources', code: 'source_snippet_invalid' };\n }\n }\n\n return {\n kind: 'sources',\n value: {\n ...wrapper,\n sources: wrapper.sources as WebSearchSource[],\n ...(sessionID === undefined ? {} : { session_id: sessionID }),\n } as SourcesEvent,\n };\n}\n\n/**\n * 从 StreamEvent 中解析搜索来源\n *\n * @deprecated 需要区分合法空结果与结构损坏时使用 classifySourcesEvent。\n * 此 legacy API 保留原有宽松解析和返回对象形状;新代码不得据此区分\n * 合法空结果与结构损坏。\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: {\n type?: string;\n sources?: WebSearchSource[];\n session_id?: string;\n };\n try {\n wrapper = JSON.parse(ev.data);\n } catch {\n return null;\n }\n if (wrapper.type !== 'sources' && ev.event !== 'sources') {\n return null;\n }\n if (!wrapper.sources || wrapper.sources.length === 0) {\n return null;\n }\n return { sources: wrapper.sources, session_id: wrapper.session_id };\n}\n\n// =============================================================================\n// ChatResponse\n// =============================================================================\n\n/**\n * 同步聊天响应 (Anthropic format, v0.4.1)\n *\n * tokenRemaining / callRemaining / modelTokenRemaining* 不在 wire JSON 中, 由 client 从 Header 填充。\n * 哨兵 -1 表示服务端未返回。\n */\nexport interface ChatResponse {\n id: string;\n type: string;\n model: string;\n role: string;\n content: ChatContentBlock[];\n stop_reason: string;\n usage: ChatUsage;\n /** -1 表示服务端未返回 */\n tokenRemaining: number;\n callRemaining: number;\n modelTokenRemaining: number;\n modelTokenRemainingETU: number;\n}\n\n// =============================================================================\n// SSE Stream\n// =============================================================================\n\n/**\n * SSE 流式事件\n *\n * v0.11.0 新增 blockIndex/blockType/ephemeral 三字段 (in-band content block 元数据)。\n * 零值等价 v0.10.0 行为, 未识别的事件三字段全部零值。\n */\nexport interface StreamEvent {\n event: string;\n data: string;\n /** 对齐 Anthropic content_block_start/delta/stop 的 index 字段 */\n blockIndex?: number;\n /** 由 content_block_start 解析得到, delta/stop 从 index→type 映射查出 */\n blockType?: string;\n /** 网关标记此 block 下一轮不应回传 */\n ephemeral?: boolean;\n}\n\n/**\n * 流式结算事件 (从 settled SSE 事件解析)\n * 包含本次请求的 token 消耗及结算后的剩余余额\n */\nexport interface StreamSettlement {\n requestId: string;\n consumeStatus: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n /** 结算后剩余 token (-1 表示服务端未返回) */\n tokenRemaining: number;\n /** 结算后剩余调用次数 (-1 表示服务端未返回) */\n callRemaining: number;\n}\n\n/** 从 settled 类型的 StreamEvent 中解析结算信息. 不是 settled 类型则返回 null. */\nexport function parseSettlement(ev: StreamEvent): StreamSettlement | null {\n if (ev.event !== 'settled' && ev.event !== 'pending_settle') {\n return null;\n }\n let s: Partial<StreamSettlement>;\n try {\n s = JSON.parse(ev.data);\n } catch {\n return null;\n }\n return {\n requestId: s.requestId ?? '',\n consumeStatus: s.consumeStatus ?? '',\n inputTokens: s.inputTokens ?? 0,\n outputTokens: s.outputTokens ?? 0,\n totalTokens: s.totalTokens ?? 0,\n tokenRemaining: s.tokenRemaining ?? -1,\n callRemaining: s.callRemaining ?? -1,\n };\n}\n","// shared/errors.ts — 跨域类型化错误。\n//\n// 端口自 acosmi-sdk-go/types.go (v0.19.0) 的 Errors 段。\n\n// =============================================================================\n// Errors (类型化)\n// =============================================================================\n\n/** 下载限流错误 (429) */\nexport class RateLimitError extends Error {\n retryAfter: string;\n raw: string;\n\n constructor(message: string, retryAfter: string, raw: string) {\n super(message);\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n this.raw = raw;\n }\n}\n\n/**\n * API 业务层错误 (HTTP 200 但 code != 0)\n * tk-dist 代理透传 yudao 响应, HTTP 状态码为 200, 业务错误在 JSON code 字段\n */\nexport class BusinessError extends Error {\n code: number;\n\n constructor(code: number, message: string) {\n super(`API error (code=${code}): ${message}`);\n this.name = 'BusinessError';\n this.code = code;\n }\n}\n\n/**\n * 订单到达非成功终态 (FAILED/CANCELLED/CLOSED/EXPIRED/REFUNDED)\n * waitForPayment 在订单终态为非成功时抛此错误\n */\nexport class OrderTerminalError extends Error {\n orderId: string;\n status: string;\n\n constructor(orderId: string, status: string) {\n super(`order ${orderId} terminated: ${status}`);\n this.name = 'OrderTerminalError';\n this.orderId = orderId;\n this.status = status;\n }\n}\n\n/**\n * 模型缓存未命中且 listModels 刷新后仍未找到。\n *\n * 历史上 getCachedModel miss 时硬返 ManagedModel{provider:\"anthropic\"} 占位,\n * 导致未预热场景下的 chat 请求按 AnthropicAdapter 编码, 被发到错误端点。\n * v0.13.x 改为 miss → listModels 自动刷新一次; 仍 miss → 抛此错误。\n */\nexport class ModelNotFoundError extends Error {\n modelId: string;\n\n constructor(modelId: string) {\n super(`managed model \"${modelId}\" not found (list models to refresh cache, or verify model id)`);\n this.name = 'ModelNotFoundError';\n this.modelId = modelId;\n }\n}\n\n/**\n * 结构化 HTTP 非 2xx 错误.\n *\n * 用 instanceof 提取:\n * try { ... } catch (e) {\n * if (e instanceof HTTPError && e.statusCode === 429) { ... }\n * }\n */\nexport class HTTPError extends Error {\n statusCode: number;\n /** anthropic.error.type / openai.error.type, 缺失为空 */\n type: string;\n /** Retry-After 头解析的秒数, 0 表示未提供或解析失败 */\n retryAfter: number;\n /** 原始响应体 (截断到 maxErrorBodySize) */\n body: string;\n /** 后端业务机器码 (响应体顶层 errorCode, e.g. \"WINDOW_LIMIT_EXCEEDED\"); 缺失为 undefined */\n errorCode?: string;\n /** 窗口限额场景: 触发的滚动窗口档位 (FIVE_HOUR = 5 小时 / WEEKLY = 7 天); 缺失为 undefined */\n windowKind?: 'FIVE_HOUR' | 'WEEKLY';\n /** 窗口限额场景: 预计恢复时间 (ISO-8601 UTC); 缺失为 undefined */\n windowResetAt?: string;\n /**\n * [W1 2026-07-11 软限额] 窗口限额场景: 该 429 可否被用户「继续」开关豁免 —\n * true = 5 小时软限额严格模式 (客户端可提示「继续任务」以消耗周配额继续);\n * false/缺失 = 周窗/显式禁止/灰度关 (硬等待, 无豁免路径, 老网关不返回时为 undefined)。\n */\n windowOverridable?: boolean;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n retryable: boolean;\n\n constructor(\n statusCode: number,\n opts: {\n type?: string;\n message?: string;\n retryAfter?: number;\n body?: string;\n errorCode?: string;\n windowKind?: 'FIVE_HOUR' | 'WEEKLY';\n windowResetAt?: string;\n windowOverridable?: boolean;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n retryable?: boolean;\n } = {},\n ) {\n let msg: string;\n if (opts.type) {\n msg = `HTTP ${statusCode}: [${opts.type}] ${opts.message ?? ''}`;\n } else if (opts.message) {\n msg = `HTTP ${statusCode}: ${opts.message}`;\n } else if (opts.body) {\n msg = `HTTP ${statusCode}: ${opts.body}`;\n } else {\n msg = `HTTP ${statusCode}`;\n }\n super(msg);\n this.name = 'HTTPError';\n this.statusCode = statusCode;\n this.type = opts.type ?? '';\n this.retryAfter = opts.retryAfter ?? 0;\n this.body = opts.body ?? '';\n this.errorCode = opts.errorCode;\n this.windowKind = opts.windowKind;\n this.windowResetAt = opts.windowResetAt;\n this.windowOverridable = opts.windowOverridable;\n this.errorContractVersion = opts.errorContractVersion;\n this.faultDomain = opts.faultDomain;\n this.requestDisposition = opts.requestDisposition;\n this.transportRequestId = opts.transportRequestId;\n this.consumeRequestId = opts.consumeRequestId;\n this.providerRequestId = opts.providerRequestId;\n this.retryable = opts.retryable === true && opts.requestDisposition === 'not_accepted';\n }\n}\n\n/**\n * 结构化网络层错误 (传输失败, 区别于上游业务错误).\n *\n * 包装 fetch 抛出的错误 — 含 timeout / EOF / connection refused / DNS 失败等.\n * retry policy: isTimeout / isEOF 任一为 true → 默认可重试.\n */\nexport class NetworkError extends Error {\n /** 操作描述, e.g. \"POST /v1/messages\" */\n op: string;\n /** 请求 URL (脱敏后) */\n url: string;\n override cause?: unknown;\n timeout: boolean;\n eof: boolean;\n requestDisposition: RequestDisposition = 'unknown';\n\n constructor(op: string, url: string, cause: unknown, opts: { timeout?: boolean; eof?: boolean } = {}) {\n const causeMsg = cause instanceof Error ? cause.message : cause != null ? String(cause) : 'network error';\n super(`${op} ${url}: ${causeMsg}`);\n this.name = 'NetworkError';\n this.op = op;\n this.url = url;\n this.cause = cause;\n this.timeout = opts.timeout ?? false;\n this.eof = opts.eof ?? false;\n }\n\n isTimeout(): boolean {\n return this.timeout;\n }\n\n isEOF(): boolean {\n return this.eof;\n }\n}\n\n/** 只收有限非负数; 其余 (null / 负数 / NaN / Infinity / 非数字) 一律 undefined。 */\nfunction finiteNonNegative(value: number | null | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\n/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件 (/chat 线 `event: failed`) 与\n * Anthropic 线的 `event: error` 帧解析得到 —— 两条线同一张字段表, 同一个解析点\n * (`core/http.ts::parseStreamError`)。客户端可通过 instanceof (同 realm) 或\n * `isStreamError` (跨 realm) 提取, 并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** D23 gateway machine code; mirrors the legacy `code` field. */\n errorCode: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 (= 服务端 `retryable` **且** `requestDisposition === 'not_accepted'`) */\n retryable: boolean;\n /**\n * 服务端在帧上声明的 `retryable` **原值** (2.19.5 起) —— 不与 requestDisposition 合取,\n * 缺席即 undefined。\n *\n * 为什么要并列存一份: 上面的 `retryable` 是两个判据的合取, 网关的流内错误帧并不总是\n * 下发 `requestDisposition`, 于是\"服务端明说可重试\"会被合取成 false —— 两端各自正确,\n * 合起来恒否。消费方要区分**「服务端明说不可重试」**(false, 一票否决) 与**「服务端没说」**\n * (undefined, 回落到自己的本地分类表) 时, 只能读这个原值; 读 `retryable` 两种情形不可分。\n */\n serverRetryable?: boolean;\n /**\n * 服务端建议的等待秒数 (2.19.5 起)。**有限非负数**才收, 其余 (null / 负数 /\n * NaN / Infinity / 非数字) 一律 undefined —— 宁可不给, 绝不编造等待时间。\n */\n retryAfterSecs?: number;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean; retryAfterSecs?: number | null; errorContractVersion?: number; faultDomain?: FaultDomain; requestDisposition?: RequestDisposition; transportRequestId?: string | null; consumeRequestId?: string | null; providerRequestId?: string | null } = {}) {\n const code = opts.code ?? '';\n const stage = opts.stage ?? '';\n const userMessage = opts.message ?? '';\n const rawError = opts.rawError ?? '';\n const retryable = opts.retryable === true && opts.requestDisposition === 'not_accepted';\n\n const body = rawError !== '' ? rawError : userMessage;\n const msg = stage !== '' ? `stream failed: ${stage}: ${body}` : `stream failed: ${body}`;\n super(msg);\n this.name = 'StreamError';\n this.code = code;\n this.errorCode = code;\n this.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n this.serverRetryable = opts.retryable;\n this.retryAfterSecs = finiteNonNegative(opts.retryAfterSecs);\n this.errorContractVersion = opts.errorContractVersion;\n this.faultDomain = opts.faultDomain;\n this.requestDisposition = opts.requestDisposition;\n this.transportRequestId = opts.transportRequestId;\n this.consumeRequestId = opts.consumeRequestId;\n this.providerRequestId = opts.providerRequestId;\n }\n}\n\n/**\n * StreamError 的**结构**判据 (2.19.5 起) —— 跨 realm 可用。\n *\n * 为什么不用 `instanceof`: 消费方 (如 CrabCode) 会在 worker / `vm` 上下文里消费这条错误,\n * 或者把 SDK 打进第二份 bundle; 两种情形下 `instanceof StreamError` 都静默为 false ——\n * 症状与\"这段分类代码压根没写\"逐字相同, 且不报错。\n *\n * 判据只认这条错误**自己**产生的两个不变量: `name === 'StreamError'` 与\n * `errorCode` 是字符串 (构造函数恒赋值, 缺 code 时为 `''`)。`HTTPError` 也有可选的\n * `errorCode`, 但 name 不同, 因此不会被误收。\n */\nexport function isStreamError(err: unknown): err is StreamError {\n if (err == null || typeof err !== 'object') return false;\n const e = err as { name?: unknown; errorCode?: unknown };\n return e.name === 'StreamError' && typeof e.errorCode === 'string';\n}\n\nexport type RequestDisposition = 'not_accepted' | 'accepted' | 'unknown';\nexport type FaultDomain = 'user_auth' | 'caller_credentials' | 'account_quota' |\n 'account_permission' | 'provider' | 'gateway' | 'transport' | 'stream_ticket' | 'protocol';\n\nexport interface GatewayErrorContract {\n errorContractVersion: 1;\n faultDomain: FaultDomain;\n errorCode: string;\n transportRequestId: string | null;\n consumeRequestId: string | null;\n providerRequestId: string | null;\n requestDisposition: RequestDisposition;\n retryable: boolean;\n}\n\nconst gatewayFaultDomains: readonly FaultDomain[] = [\n 'user_auth', 'caller_credentials', 'account_quota', 'account_permission',\n 'provider', 'gateway', 'transport', 'stream_ticket', 'protocol',\n];\n\nfunction nullableID(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\nfunction decodeGatewayErrorContract(value: unknown): GatewayErrorContract | null {\n if (value == null || typeof value !== 'object') return null;\n const v = value as Record<string, unknown>;\n const version = v.errorContractVersion;\n const domain = v.faultDomain;\n const disposition = v.requestDisposition;\n const errorCode = v.errorCode;\n if (version !== 1 || typeof domain !== 'string' || !gatewayFaultDomains.includes(domain as FaultDomain)) return null;\n if (typeof errorCode !== 'string' || errorCode.length === 0) return null;\n if (disposition !== 'not_accepted' && disposition !== 'accepted' && disposition !== 'unknown') return null;\n if (!nullableID(v.transportRequestId) || !nullableID(v.consumeRequestId) || !nullableID(v.providerRequestId)) return null;\n if (typeof v.retryable !== 'boolean') return null;\n return { errorContractVersion: 1, faultDomain: domain as FaultDomain, errorCode,\n transportRequestId: v.transportRequestId, consumeRequestId: v.consumeRequestId,\n providerRequestId: v.providerRequestId, requestDisposition: disposition, retryable: v.retryable };\n}\n\n/** Read the closed v1 gateway error contract without exposing the raw response body. */\nexport function readGatewayErrorContract(error: unknown): GatewayErrorContract | null {\n const direct = decodeGatewayErrorContract(error);\n if (direct) return direct;\n if (error == null || typeof error !== 'object') return null;\n const e = error as { response?: { data?: unknown }; body?: unknown };\n const axios = decodeGatewayErrorContract(e.response?.data);\n if (axios) return axios;\n if (typeof e.body === 'string') {\n try { return decodeGatewayErrorContract(JSON.parse(e.body)); } catch { return null; }\n }\n return null;\n}\n\n/** True only for an HTTP 401 carrying the exact v1 rejected user-token contract. */\nexport function isUserAccessTokenRejected(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const e = error as { statusCode?: unknown; response?: { status?: unknown } };\n const status = e.statusCode ?? e.response?.status;\n const contract = readGatewayErrorContract(error);\n return status === 401 && contract?.faultDomain === 'user_auth' &&\n contract.errorCode === 'USER_ACCESS_TOKEN_INVALID' && contract.requestDisposition === 'not_accepted';\n}\n\n// =============================================================================\n// 窗口限额 (WINDOW_LIMIT_EXCEEDED) 识别\n// =============================================================================\n\n/** HTTP 路径窗口限额机器码 — 响应体顶层 errorCode / message 文案子串 (后端契约冻结值)。 */\nconst windowLimitErrorCode = 'WINDOW_LIMIT_EXCEEDED';\n/** 流式路径窗口限额 code — agent-run SSE error 事件用小写蛇形 (与 HTTP errorCode 大小写不同)。 */\nconst windowLimitStreamCode = 'window_limit_exceeded';\n\n/**\n * 判断错误是否为窗口限额拒绝 (5 小时 / 7 天滚动窗口 credit 用量达上限)。\n * 结构化 errorCode 优先, message/body 子串防御兜底 (后端部署版本错位期:\n * 旧网关只在 message 文案里带 \"(WINDOW_LIMIT_EXCEEDED)\" 而无顶层 errorCode)。\n *\n * 仅识别 HTTP 非 2xx 路径抛出的 HTTPError (/chat OpenAI 网关形态与 /anthropic\n * Anthropic 形态皆可)。流式场景 (agent-run SSE error 事件 / managed-model\n * failed 事件) 的 code 是小写 \"window_limit_exceeded\" 且产物不是 HTTPError,\n * 用伴生函数 isWindowLimitStreamError 判断。\n */\nexport function isWindowLimitError(err: unknown): err is HTTPError {\n if (!(err instanceof HTTPError)) return false;\n if (err.errorCode === windowLimitErrorCode) return true;\n // 防御兜底: 旧版后端无顶层 errorCode, 机器码只存活在 message 文案 / body 原串。\n return err.message.includes(windowLimitErrorCode) || err.body.includes(windowLimitErrorCode);\n}\n\n/**\n * isWindowLimitError 的流式伴生判断 — 面向 SSE error 事件产物\n * (StreamError / AgentRunStreamError / 裸 AgentRunErrorPayload) 的结构化鸭子判断。\n *\n * 入参形态跨三种且互无公共基类, 故返回 boolean 而非 type guard:\n * `code === \"window_limit_exceeded\"` 优先; message / userMessage / rawError\n * 含 \"WINDOW_LIMIT_EXCEEDED\" 子串防御兜底 (Anthropic 形态流错误事件只有\n * rate_limit_error type + 文案内机器码的部署错位期)。\n */\nexport function isWindowLimitStreamError(err: unknown): boolean {\n if (err == null || typeof err !== 'object') return false;\n const e = err as { code?: unknown; message?: unknown; userMessage?: unknown; rawError?: unknown };\n if (e.code === windowLimitStreamCode) return true;\n // 防御兜底 (后端部署版本错位期): 机器码只出现在文案 / 原始错误串。\n for (const field of [e.message, e.userMessage, e.rawError]) {\n if (typeof field === 'string' && field.includes(windowLimitErrorCode)) return true;\n }\n return false;\n}\n\n/**\n * [W1 2026-07-11 软限额] 判断错误是否为【可豁免】的窗口限额拒绝 (HTTP 路径) —\n * 即 5 小时软限额严格模式: 用户此前把「继续」开关关成严格模式, 到达 5h 被 429,\n * 但可通过重新开启继续 (setWindowContinuePreference(true)) 后重发放行。\n *\n * = isWindowLimitError(err) && err.windowOverridable === true。\n * 周窗 / 显式禁止 / 软限灰度关 / 老网关 (windowOverridable 缺失) 均返回 false (硬等待)。\n * 客户端据此决定是否展示「继续任务 / 重新开启」入口 (vs 纯「等待刷新」)。\n */\nexport function isContinuableWindowLimitError(err: unknown): err is HTTPError {\n return isWindowLimitError(err) && err.windowOverridable === true;\n}\n\n/**\n * [W1 2026-07-11 软限额] 从流式窗口限额 error 事件产物 (StreamError / AgentRunStreamError /\n * 裸 payload) 结构化读取窗口细节 — 供 crabcode 等消费端区分 5h 软限 (可继续) vs 周窗硬限。\n *\n * 非窗口限额错误返回 null; 是则返回 { windowKind, windowResetAt, windowOverridable } (缺字段为 undefined)。\n * overridable=true ⇒ 5h 严格模式可续跑; 缺失/false ⇒ 硬等待。\n */\nexport function getWindowLimitStreamDetails(\n err: unknown,\n): { windowKind?: 'FIVE_HOUR' | 'WEEKLY'; windowResetAt?: string; windowOverridable?: boolean } | null {\n if (!isWindowLimitStreamError(err)) return null;\n const e = (err ?? {}) as { windowKind?: unknown; windowResetAt?: unknown; windowOverridable?: unknown };\n const kind = e.windowKind === 'FIVE_HOUR' || e.windowKind === 'WEEKLY' ? e.windowKind : undefined;\n return {\n windowKind: kind,\n windowResetAt: typeof e.windowResetAt === 'string' ? e.windowResetAt : undefined,\n windowOverridable: typeof e.windowOverridable === 'boolean' ? e.windowOverridable : undefined,\n };\n}\n","// adapters/openai.ts — 端口自 acosmi-sdk-go/adapter_openai.go (535 行)\n//\n// 用于所有非 Anthropic 厂商 (DeepSeek, DashScope, Zhipu, Moonshot, VolcEngine 等)\n// SDK 只做格式转换, 厂商特定参数由 Nexus Gateway per-provider adapter 处理\n//\n// 关键区别:\n// - 不注入 Anthropic betas\n// - 端点后缀为 /chat (非 /anthropic)\n// - 流式使用 [DONE] 标记结束 (非 message_stop)\n// - 响应为 OpenAI choices 格式\n\nimport { type AnthropicContentBlock, type AnthropicResponse } from '../wire-anthropic';\nimport {\n type OpenAIChatResponse,\n type OpenAIStreamChunk,\n type OpenAIStreamDelta,\n} from '../wire-openai';\nimport {\n type ChatContentBlock,\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHigh,\n ThinkingMax,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { ProviderFormat, type ProviderAdapter } from './format';\n\n/** 实现 ProviderAdapter, 用于所有非 Anthropic 厂商 */\nexport class OpenAIAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.OpenAI;\n }\n\n endpointSuffix(): string {\n return '/chat';\n }\n\n /**\n * 构建 OpenAI 兼容格式请求体\n * 不注入 Anthropic betas, 扩展字段 (thinking/effort/speed) 以通用 JSON 传递\n */\n buildRequestBody(_caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // ── 消息: 直接透传 (Gateway 负责最终转换) ──\n if (req.rawMessages != null) {\n body['messages'] = req.rawMessages;\n } else if ((req.messages?.length ?? 0) > 0) {\n body['messages'] = req.messages;\n }\n\n body['stream'] = req.stream === true;\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n\n // ── System prompt: 透传给 Gateway ──\n if (req.system != null) {\n body['system'] = req.system;\n }\n\n // ── Temperature ──\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Tools: 透传原始格式, Gateway adapter 负责格式转换 ──\n if (req.tools != null) {\n body['tools'] = req.tools;\n }\n\n // ── 扩展字段 (v0.13.0: 按 OpenAI wire format 直接翻译) ──\n\n // Thinking / Effort → reasoning_effort\n // OpenAI 系只有顶层 `reasoning_effort: \"low\"|\"medium\"|\"high\"`, 无 thinking block;\n // 能接收到 reasoning_content (GLM/DeepSeek) 作为响应, 但请求侧只能控制级别。\n const eff = resolveOpenAIReasoningEffort(req, _caps.supports_max_effort);\n if (eff !== '') {\n body['reasoning_effort'] = eff;\n }\n if (_caps.supports_thinking && req.thinking?.level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n }\n\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n\n // outputConfig → response_format\n // Anthropic 心智模型通过 system prompt + prefill 实现 JSON 模式;\n // OpenAI 有顶层 response_format, SDK 直接翻译。\n const rf = resolveOpenAIResponseFormat(req);\n if (rf) {\n body['response_format'] = rf;\n }\n\n if (req.metadata) {\n body['metadata'] = req.metadata;\n }\n\n // parallel_tool_calls 是 OpenAI 原生字段, 无歧义直接写\n if (req.parallelToolCalls != null) {\n body['parallel_tool_calls'] = req.parallelToolCalls;\n }\n\n // ── 不注入 Anthropic Betas ──\n // OpenAI 格式不使用 anthropic-beta header\n\n // ── 透传 extraBody ──\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n body[k] = v;\n }\n }\n\n // ── v1.6.0: endUserId → 顶层 body[\"user_id\"] (OpenAI wire 形态) ──\n // 优先级最高: 在 extraBody 之后写入, 即便 caller 通过 extraBody[\"user_id\"] 自填,\n // 显式 endUserId 仍胜出 (单一真相, 避免双写歧义)。\n // 网关 sanitizer 仍会做最终校验与权限决断, 此处仅负责字段位置正确。\n if (req.endUserId && req.endUserId !== '') {\n body['user_id'] = req.endUserId;\n }\n\n // ── 流式选项 ──\n if (req.stream === true) {\n body['stream_options'] = { include_usage: true };\n }\n\n return body;\n }\n\n /**\n * 解析 OpenAI 格式同步响应为 ChatResponse\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 OpenAI JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n let raw = bodyStr;\n try {\n const wrapper = JSON.parse(bodyStr) as { code?: number; message?: string; data?: unknown };\n if (wrapper.data != null && wrapper.data !== null) {\n if ((wrapper.code ?? 0) !== 0) {\n throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? '');\n }\n raw = JSON.stringify(wrapper.data);\n }\n } catch (e) {\n if (e instanceof BusinessError) throw e;\n }\n\n let oaiResp: OpenAIChatResponse;\n try {\n oaiResp = JSON.parse(raw) as OpenAIChatResponse;\n } catch (e) {\n throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return convertOpenAIToChatResponse(oaiResp);\n }\n\n /**\n * 解析 OpenAI SSE 行\n * [DONE] 标记流结束\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean } {\n if (data === '[DONE]') {\n return { event: { event: '', data: '' }, done: true };\n }\n\n // 校验 chunk 是合法 JSON (与 Go 侧行为对齐)\n try {\n JSON.parse(data);\n } catch (e) {\n throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 把 Anthropic 心智模型的 thinking/effort 翻译成 OpenAI `reasoning_effort` 字段值\n * 返回空串表示不设置\n */\nexport function resolveOpenAIReasoningEffort(req: ChatRequest, supportsMax = false): string {\n // effort 优先级最高, 因为它本身就是通用级别语义\n if (req.effort && req.effort.level !== '') {\n switch (req.effort.level) {\n case 'low':\n case 'medium':\n case 'high':\n case 'xhigh':\n return req.effort.level;\n case 'max':\n // OpenAI 无 max 级别, 等价最深 = high\n return supportsMax ? 'max' : 'high';\n }\n }\n // thinking.level 次之\n if (req.thinking) {\n switch (req.thinking.level) {\n case 'low':\n case 'medium':\n case 'xhigh':\n return req.thinking.level;\n case ThinkingHigh:\n return 'high';\n case ThinkingMax:\n return supportsMax ? 'max' : 'high';\n case ThinkingOff:\n return '';\n }\n }\n return '';\n}\n\n/**\n * 把 outputConfig 翻译成 OpenAI response_format\n * 返回 null 表示不设置\n */\nexport function resolveOpenAIResponseFormat(req: ChatRequest): Record<string, unknown> | null {\n if (!req.outputConfig) return null;\n switch (req.outputConfig.format) {\n case 'json_schema': {\n // OpenAI schema 形态: {type:\"json_schema\", json_schema:{schema:{...},strict:true}}\n const js: Record<string, unknown> = {};\n if (req.outputConfig.schema != null) {\n js['schema'] = req.outputConfig.schema;\n }\n js['strict'] = true;\n return {\n type: 'json_schema',\n json_schema: js,\n };\n }\n case 'json_object':\n return { type: 'json_object' };\n case '':\n case undefined:\n return null;\n default:\n // 未知 format, 原样透传, 交 Gateway 处理\n return { type: req.outputConfig.format };\n }\n}\n\n/** 将 OpenAI 同步响应转换为 ChatResponse */\nfunction convertOpenAIToChatResponse(oai: OpenAIChatResponse): ChatResponse {\n const resp: ChatResponse = {\n id: oai.id,\n type: 'message',\n model: oai.model,\n role: 'assistant',\n content: [],\n stop_reason: '',\n // [W-SDK-OPENAI-PARITY] 缺 usage 的响应按 0 计数, 而不是让 `oai.usage.prompt_tokens`\n // 抛 TypeError 把整条响应打死 —— 上游不计量是常见形态, 正文照样是有效的。\n // 与 Go 侧同名函数逐字同语义 (那里 Usage 是值类型, 缺席即零值)。\n usage: {\n input_tokens: oai.usage?.prompt_tokens ?? 0,\n output_tokens: oai.usage?.completion_tokens ?? 0,\n },\n tokenRemaining: -1,\n callRemaining: -1,\n modelTokenRemaining: -1,\n modelTokenRemainingETU: -1,\n };\n\n // [W-SDK-OPENAI-PARITY] 缺 choices 的响应按空数组处理, 而不是让 `oai.choices.length`\n // 抛 TypeError —— 那会连 id / model / usage 这些确实到手的字段一起丢掉。判据用\n // Array.isArray 而不是 `?.length`, 与同文件流式路径的 choices 守卫取同一个表达式。\n if (Array.isArray(oai.choices) && oai.choices.length > 0) {\n const choice = oai.choices[0]!;\n\n // finish_reason 映射\n switch (choice.finish_reason) {\n case 'stop':\n resp.stop_reason = 'end_turn';\n break;\n case 'tool_calls':\n resp.stop_reason = 'tool_use';\n break;\n case 'length':\n resp.stop_reason = 'max_tokens';\n break;\n default:\n resp.stop_reason = choice.finish_reason;\n }\n\n // thinking content → thinking block\n if (choice.message.reasoning_content && choice.message.reasoning_content !== '') {\n resp.content.push({\n type: 'thinking',\n thinking: choice.message.reasoning_content,\n } as ChatContentBlock);\n }\n\n // text content → text block\n if (choice.message.content && choice.message.content !== '') {\n resp.content.push({\n type: 'text',\n text: choice.message.content,\n } as ChatContentBlock);\n }\n\n // tool_calls → tool_use blocks\n for (const tc of choice.message.tool_calls ?? []) {\n resp.content.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n // Anthropic 协议 input 是 raw JSON value; OpenAI 给的 arguments 是 string,\n // Go 侧用 json.RawMessage(arguments) 直透(原始字节). TS 我们尝试解析:\n input: tryParseJSON(tc.function.arguments),\n } as ChatContentBlock);\n }\n }\n\n return resp;\n}\n\nfunction tryParseJSON(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return s;\n }\n}\n\n// ============================================================================\n// OpenAI → Anthropic 响应转换 (供 ChatMessages 使用)\n// ============================================================================\n\n/**\n * 解析 OpenAI 格式响应并转换为 AnthropicResponse\n * 用于 chatMessagesOpenAI 方法, 使 Hub 层无需感知 provider 差异\n */\nexport function parseOpenAIResponseToAnthropic(raw: string | Uint8Array): AnthropicResponse {\n const rawStr = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n\n let data = rawStr;\n try {\n const wrapper = JSON.parse(rawStr) as { code?: number; message?: string; data?: unknown };\n if (wrapper.data != null && wrapper.data !== null) {\n if ((wrapper.code ?? 0) !== 0) {\n throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? '');\n }\n data = JSON.stringify(wrapper.data);\n }\n } catch (e) {\n if (e instanceof BusinessError) throw e;\n }\n\n let oaiResp: OpenAIChatResponse;\n try {\n oaiResp = JSON.parse(data) as OpenAIChatResponse;\n } catch (e) {\n throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const resp: AnthropicResponse = {\n id: oaiResp.id,\n type: 'message',\n role: 'assistant',\n content: [],\n model: oaiResp.model,\n stop_reason: '',\n // [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 usage 按 0 计数, 不抛。\n // 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。\n usage: {\n input_tokens: oaiResp.usage?.prompt_tokens ?? 0,\n output_tokens: oaiResp.usage?.completion_tokens ?? 0,\n },\n };\n\n // [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 choices 按空数组处理, 不抛。\n // 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。\n if (Array.isArray(oaiResp.choices) && oaiResp.choices.length > 0) {\n const choice = oaiResp.choices[0]!;\n\n switch (choice.finish_reason) {\n case 'stop':\n resp.stop_reason = 'end_turn';\n break;\n case 'tool_calls':\n resp.stop_reason = 'tool_use';\n break;\n case 'length':\n resp.stop_reason = 'max_tokens';\n break;\n default:\n resp.stop_reason = choice.finish_reason;\n }\n\n if (choice.message.reasoning_content && choice.message.reasoning_content !== '') {\n resp.content.push({\n type: 'thinking',\n thinking: choice.message.reasoning_content,\n } as AnthropicContentBlock);\n }\n\n if (choice.message.content && choice.message.content !== '') {\n resp.content.push({\n type: 'text',\n text: choice.message.content,\n } as AnthropicContentBlock);\n }\n\n for (const tc of choice.message.tool_calls ?? []) {\n resp.content.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n input: tryParseJSON(tc.function.arguments),\n } as AnthropicContentBlock);\n }\n }\n\n return resp;\n}\n\n// ============================================================================\n// OpenAI SSE → Anthropic 事件转换器 (供 chatMessagesStreamInternal 使用)\n// ============================================================================\n\n/** 收尾 message_delta 上的 usage。只含与非流式路径相同的两个键; 缺席的键表示上游没给, 不是 0。 */\ninterface StreamMessageUsage {\n input_tokens?: number;\n output_tokens?: number;\n}\n\n/**\n * [W-QUAD-CHAIN-20260914] 读出一帧流式 chunk 上的 usage 对象, 搬成收尾 message_delta 的 usage 形态。\n *\n * 字段映射与同文件非流式路径 (`convertOpenAIToChatResponse` / `parseOpenAIResponseToAnthropic`)\n * 逐字相同: 只搬 `prompt_tokens → input_tokens`、`completion_tokens → output_tokens`。\n * `cached_tokens` / `reasoning_tokens` 等明细刻意不映射, 也不做 `prompt_tokens − cached` 之类的\n * 净额换算 —— usage 的语义归一只住在网关, SDK 只做格式搬运。某个计数缺失或不是有限数时不写对应键。\n *\n * 帧上没有 usage 对象 (缺失 / null / 非对象) 返回 null: 调用方据此区分「这帧没带 usage」与\n * 「带了 usage 但计数都缺席」(后者返回空对象, 仍算见过 usage)。\n */\nfunction readStreamUsage(chunk: OpenAIStreamChunk): StreamMessageUsage | null {\n const raw: unknown = chunk.usage;\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n return null;\n }\n const { prompt_tokens: promptTokens, completion_tokens: completionTokens } = raw as {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n };\n const usage: StreamMessageUsage = {};\n if (typeof promptTokens === 'number' && Number.isFinite(promptTokens)) {\n usage.input_tokens = promptTokens;\n }\n if (typeof completionTokens === 'number' && Number.isFinite(completionTokens)) {\n usage.output_tokens = completionTokens;\n }\n return usage;\n}\n\n/**\n * 将 OpenAI SSE chunks 转换为 Anthropic 兼容的 StreamEvent\n * 有状态: 跨 chunk 追踪 block 索引\n */\nexport class OpenAIStreamConverter {\n private messageStarted = false;\n private thinkingStarted = false;\n private thinkingStopped = false;\n /** thinking block 打开时占用的 Anthropic block index — 关闭时必须用它, 不能用\n * 可能已被 text/tool 推进的 this.blockIndex (否则 content_block_stop 索引错配)。 */\n private thinkingBlockIndex = 0;\n private textStarted = false;\n /** OpenAI tool_call 键 → Anthropic block index。键正常是 `tc.index`;上游省略\n * index 时退化为 `id:<tool_call_id>`,两者都没有时沿用上一个键(见\n * {@link resolveToolKey})。 */\n private toolBlockIndex = new Map<string | number, number>();\n private blockIndex = 0;\n /** 每个 tool block 已发出的 `partial_json` 累积,用于识别「每片重发全量参数」\n * 的上游(见 tool_calls 分支的累计判别)。 */\n private toolArgsAccum = new Map<string | number, string>();\n /** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */\n private lastToolKey: string | number | null = null;\n /** 已发出 message_delta/message_stop,避免 finish_reason、usage 尾帧、`[DONE]` 与\n * {@link flush} 重复收口 —— 整条流恰好一个 message_stop。 */\n private messageClosed = false;\n /** 已为仍打开的块发出 content_block_stop。与 {@link messageClosed} 分开记:块在\n * finish_reason 帧上就关,message_delta/message_stop 却可能推迟到之后的帧。 */\n private blocksClosed = false;\n /** [W-QUAD-CHAIN-20260914] finish_reason 已到、但 message_delta/message_stop 因等待\n * usage 尾帧而推迟时记下的 stop_reason;没有推迟中的收尾时为 null。 */\n private pendingStopReason: string | null = null;\n /** [W-QUAD-CHAIN-20260914] 最近一次带 usage 对象的帧搬出的 usage(后到覆盖先到);\n * 整条流从未出现 usage 对象时为 null,收尾 message_delta 据此不写 usage 键。 */\n private usage: StreamMessageUsage | null = null;\n\n /**\n * 解析一条 tool_call delta 归属的块键。\n *\n * OpenAI 流式规范里 `index` 是必填,但兼容实现常有省略。此前这里直接用\n * `tc.index` 做 Map 键:两个都省略 index 的 tool_call 会共用键 `undefined`,\n * 于是只开一个块、两段参数拼进同一条 `partial_json` 流,产出 `{…}{…}` 这种\n * 必然非法的 JSON。这里按「index → id → 沿用上一个」三级降级,让至少一种\n * 稳定标识生效。\n */\n private resolveToolKey(tc: { index?: number; id?: string }): string | number {\n if (typeof tc.index === 'number' && Number.isFinite(tc.index)) {\n this.lastToolKey = tc.index;\n return tc.index;\n }\n if (typeof tc.id === 'string' && tc.id !== '') {\n const key = `id:${tc.id}`;\n this.lastToolKey = key;\n return key;\n }\n if (this.lastToolKey !== null) return this.lastToolKey;\n this.lastToolKey = 0;\n return 0;\n }\n\n /**\n * 关闭仍打开的 text / thinking / tool 块(整条流只关一次)。\n *\n * 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发\n * `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个\n * 永不闭合的 tool_use 块。\n *\n * [W-QUAD-CHAIN-20260914] 此前关块与 message_delta/message_stop 是同一个方法里同一时刻\n * 的事;usage 尾帧要求收尾推迟,于是拆成本方法与 {@link emitMessageEnd} 两段,各自防重。\n */\n private closeContentBlocks(events: StreamEvent[]): void {\n if (this.blocksClosed) return;\n this.blocksClosed = true;\n\n if (this.textStarted) {\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: this.blockIndex }),\n });\n this.textStarted = false;\n } else if (this.thinkingStarted && !this.thinkingStopped) {\n // 用 thinkingBlockIndex 关 — thinking-only 流末尾若有 tool block 推进过\n // blockIndex, 这里仍要用 thinking 自己打开时记下的 index, 否则错配。\n this.thinkingStopped = true;\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: this.thinkingBlockIndex }),\n });\n }\n for (const idx of this.toolBlockIndex.values()) {\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: idx }),\n });\n }\n }\n\n /**\n * 发出 message_delta + message_stop,整条流只发一次;同时清掉推迟中的收尾。\n *\n * 见过 usage 对象就把它放进 message_delta:usage 必须出现在唯一的 message_stop 之前,\n * message_stop 之后下游已无处安放用量。整条流从未出现 usage 对象时不写 usage 键 ——\n * 缺席表示「上游没给」,不是 0。\n */\n private emitMessageEnd(events: StreamEvent[], stopReason: string): void {\n if (this.messageClosed) return;\n this.messageClosed = true;\n this.pendingStopReason = null;\n\n const messageDelta: {\n type: 'message_delta';\n delta: { stop_reason: string };\n usage?: StreamMessageUsage;\n } = { type: 'message_delta', delta: { stop_reason: stopReason } };\n if (this.usage !== null) {\n messageDelta.usage = this.usage;\n }\n events.push({ event: 'message_delta', data: JSON.stringify(messageDelta) });\n events.push({\n event: 'message_stop',\n data: JSON.stringify({ type: 'message_stop' }),\n });\n }\n\n /**\n * 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent\n * 返回 { events, done }\n */\n convert(data: string): { events: StreamEvent[]; done: boolean } {\n if (data === '[DONE]') {\n // 上游可能在 `[DONE]` 之前不发 finish_reason(部分兼容实现、以及被中断的\n // 流)。此前这里直接返回空事件,已打开的块永不闭合,下游只能靠超时收场。\n const events: StreamEvent[] = [];\n if (this.pendingStopReason !== null) {\n // [W-QUAD-CHAIN-20260914] finish_reason 已到而 usage 尾帧始终没来:流已声明结束,\n // 推迟的收尾不能再等(块已在 finish_reason 帧上关过)。\n this.emitMessageEnd(events, this.pendingStopReason);\n } else if (this.messageStarted) {\n this.closeContentBlocks(events);\n this.emitMessageEnd(events, 'end_turn');\n }\n return { events, done: true };\n }\n\n let chunk: OpenAIStreamChunk;\n try {\n chunk = JSON.parse(data);\n } catch (e) {\n throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const events: StreamEvent[] = [];\n // [W-QUAD-CHAIN-20260914] usage 必须在「没有 choices 就返回」之前读: include_usage 的尾帧\n // 恰恰是 `{\"choices\":[],\"usage\":{...}}`。此前先判 choices 再返回, 尾帧整帧丢弃,\n // OpenAI 线每个回合的 usage 恒为 0。\n const frameUsage = readStreamUsage(chunk);\n if (frameUsage !== null) {\n this.usage = frameUsage; // 后到覆盖先到\n }\n\n // [W-QUAD-CHAIN-20260914] 纵深防御: 同一条流里可能出现**没有 choices 的 data 帧**\n // (网关错误契约帧、usage 尾帧)。此前这里直接解引用, 任何这类帧都会变成一句与真因无关的\n // TypeError, 把诊断信息彻底摧毁。正确的错误分流在 client.ts 的 SSE 事件名判断处; 这里\n // 只负责「不把自己炸掉」, 以及 usage 尾帧到达时补发推迟中的收尾。不带 usage 的这类帧零事件。\n if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {\n if (frameUsage !== null && this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n return { events, done: false };\n }\n const choice = chunk.choices[0]!;\n // [W-SDK-OPENAI-PARITY] 与上面的 choices 守卫同一档纵深防御: 兼容实现会发\n // `{\"choices\":[{\"index\":0}]}` 这种空心 choice (只声明「第 0 路还在」而本帧无增量)。\n // 此前三处直接读 `choice.delta.*`, 第一处就是 `reasoning_content` —— 一个 TypeError\n // 撕开整条 for-await 链, 整个回合失败。缺席按空 delta 处理: 三个分支各自的空值判断\n // 会让它零事件通过, finish_reason / usage 仍照常处理。\n const delta: OpenAIStreamDelta = choice.delta ?? {};\n\n // 首个 chunk: 发送 message_start\n if (!this.messageStarted) {\n this.messageStarted = true;\n const msgJSON = JSON.stringify({\n type: 'message_start',\n message: {\n id: chunk.id,\n type: 'message',\n role: 'assistant',\n content: [],\n model: '',\n },\n });\n events.push({ event: 'message_start', data: msgJSON });\n }\n\n // thinking delta (reasoning_content)\n if (delta.reasoning_content && delta.reasoning_content !== '') {\n if (!this.thinkingStarted) {\n // [W-SDK-OPENAI-PARITY] 关闭仍打开的 text 块 (镜像 text / tool_calls 两个分支)。\n // chunk 顺序 content → reasoning_content 时, text 块仍开着且 blockIndex 未推进;\n // 不在此关闭并递增, thinking 的 content_block_start 会与 text 撞同一个 index 0,\n // 而 closeContentBlocks 的 `textStarted / else if thinking` 分支只关得掉其中一个 ——\n // 另一个块永不闭合。三个分支必须两两互关, 才有「同一时刻至多一个非 tool 块打开」这条不变量。\n if (this.textStarted) {\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.blockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n this.textStarted = false;\n }\n this.thinkingStarted = true;\n this.thinkingBlockIndex = this.blockIndex; // 记下 thinking 占用的 index\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: { type: 'thinking', thinking: '' },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n }\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: this.thinkingBlockIndex,\n delta: { type: 'thinking_delta', thinking: delta.reasoning_content },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n\n // text delta (content)\n if (delta.content && delta.content !== '') {\n // 关闭 thinking block (如果有) — 用 thinkingBlockIndex 关, 不用可能已推进的 blockIndex\n if (this.thinkingStarted && !this.thinkingStopped) {\n this.thinkingStopped = true;\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.thinkingBlockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n }\n if (!this.textStarted) {\n this.textStarted = true;\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: { type: 'text', text: '' },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n }\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: this.blockIndex,\n delta: { type: 'text_delta', text: delta.content },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n\n // tool_calls delta\n for (const tc of delta.tool_calls ?? []) {\n // 上游给的 tool_call 未必带 `function`:OpenAI 流式规范允许后续增量只带\n // `{index}`。此前这里直接读 `tc.function.name` / `tc.function.arguments`,\n // 那种上游会抛 TypeError 撕开整条 for-await 链,整个回合失败。\n const fn = tc.function as { name?: string; arguments?: unknown } | undefined;\n const toolKey = this.resolveToolKey(tc);\n if (!this.toolBlockIndex.has(toolKey)) {\n // 关闭仍打开的 thinking block (镜像 text 分支): chunk 顺序 reasoning_content →\n // tool_calls (中间无 content text delta) 时, thinking 仍开着且 blockIndex 未推进,\n // 若不在此关闭并递增, tool block 会与 thinking 撞 index 0。用 thinkingBlockIndex 关。\n if (this.thinkingStarted && !this.thinkingStopped) {\n this.thinkingStopped = true;\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.thinkingBlockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n }\n // 关闭 text block (如果有)\n if (this.textStarted) {\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.blockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n this.textStarted = false;\n }\n this.toolBlockIndex.set(toolKey, this.blockIndex);\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: {\n type: 'tool_use',\n id: tc.id,\n name: fn?.name,\n input: {},\n },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n this.blockIndex++; // 递增, 为下一个 tool_call block 预留索引\n }\n if (fn?.arguments !== undefined && fn.arguments !== null && fn.arguments !== '') {\n // `arguments` 按 OpenAI 规范是 string,但部分兼容实现直接给对象。此前原样\n // 塞进 `partial_json`,下游 `input += delta.partial_json` 会拼出\n // \"[object Object]\"。这里统一成字符串,语义不变。\n const rawArgs =\n typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments);\n\n // 累计 vs 增量判别:部分上游每个 chunk 重发**全量**参数而非增量。此前无条件\n // 累加,会拼出 `{\"a\":1}{\"a\":1}{\"a\":1}` 这种必然非法的 JSON。判据取最保守的\n // 一种:新片严格以已累积内容为前缀**且**更长时,才认为上游在重发全量,只发\n // 差值。真增量流里某一片恰好等于「此前全部内容的延长」概率可忽略。\n const accum = this.toolArgsAccum.get(toolKey) ?? '';\n let emit = rawArgs;\n if (accum !== '' && rawArgs.length > accum.length && rawArgs.startsWith(accum)) {\n emit = rawArgs.slice(accum.length);\n this.toolArgsAccum.set(toolKey, rawArgs);\n } else {\n this.toolArgsAccum.set(toolKey, accum + rawArgs);\n }\n\n if (emit !== '') {\n const idx = this.toolBlockIndex.get(toolKey)!;\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: idx,\n delta: {\n type: 'input_json_delta',\n partial_json: emit,\n },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n }\n }\n\n // finish_reason: 关闭所有 block; message_delta + message_stop 视 usage 是否已到, 立即发或推迟\n if (choice.finish_reason != null && choice.finish_reason !== '') {\n // stop_reason 映射。`content_filter` 等未列出的值刻意保持原样透传而不是压成\n // `end_turn` —— 把内容审查拦截伪装成正常结束会让下游无从分辨。同文件的\n // `convertOpenAIToChatResponse` 一直是原样透传,这里跟它对齐。\n let stopReason: string;\n switch (choice.finish_reason) {\n case 'tool_calls':\n stopReason = 'tool_use';\n break;\n case 'length':\n stopReason = 'max_tokens';\n break;\n case 'stop':\n stopReason = 'end_turn';\n break;\n default:\n stopReason = choice.finish_reason;\n }\n // 只认第一个 finish_reason: 收尾已发出或已推迟时, 后到的 finish_reason 不改写 stop_reason。\n if (!this.messageClosed && this.pendingStopReason === null) {\n this.closeContentBlocks(events);\n if (this.usage !== null) {\n // usage 已在本帧或更早的帧到达: 一次发出带 usage 的收尾\n this.emitMessageEnd(events, stopReason);\n } else {\n // [W-QUAD-CHAIN-20260914] include_usage 的标准帧序里 finish_reason 帧先于 usage 尾帧。\n // 此刻收尾, message_delta 只能不带 usage, 之后到的 usage 已无处安放 —— 于是推迟到\n // usage 帧 / `[DONE]` / EOF (flush) 三者先到者。块照常在这一帧关闭。\n this.pendingStopReason = stopReason;\n }\n }\n }\n\n // [W-QUAD-CHAIN-20260914] 推迟收尾期间到达的带 choices 帧若携带 usage, 同样立即补发。\n if (frameUsage !== null && this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n\n return { events, done: false };\n }\n\n /**\n * 流在**没有** `[DONE]` 的情况下结束 (EOF) 时, 由驱动方在读循环结束后调用一次。\n *\n * 只补发「finish_reason 已到、仅因等待 usage 尾帧而推迟」的 message_delta + message_stop。\n * 从未收到 finish_reason 的流是被截断的流, 这里刻意**不**替它伪造正常结束 —— 一个\n * `end_turn` 的 message_stop 会把被截断的回答当成完整回答交给下游。\n * 已经收口 (usage 帧 / `[DONE]` / 上一次 flush) 后再调用返回空数组, 不会发出第二个 message_stop。\n */\n flush(): StreamEvent[] {\n const events: StreamEvent[] = [];\n if (this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n return events;\n }\n}\n\nexport function newOpenAIStreamConverter(): OpenAIStreamConverter {\n return new OpenAIStreamConverter();\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/adapters/openai.ts"],"names":["events"],"mappings":"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","file":"openai.mjs","sourcesContent":["// models/types.ts — 模型网关域类型。\n//\n// 端口自 acosmi-sdk-go/types.go (v0.19.0, 1275 行)\n//\n// 命名约定:\n// - 字段名 = Go json tag 字面量 (wire format), 不做 camelCase 重映射\n// - mix 风格 (modelId / max_tokens / supports_thinking) 是历史 wire 设计的真实样貌\n// - 这样 JSON.parse / JSON.stringify 可直接来回, 与 Go 侧 0 偏差\n//\n// int64 字段用 number — JS number 安全上限 2^53, Acosmi 业务量级远低于此。\n// time.Time 字段用 ISO 8601 string 形态, 与 Go json wire 一致。\n// json.RawMessage 用 unknown (caller 自行 typed)。\n// json.Number 用 string (避免精度丢失)。\n\n// =============================================================================\n// Managed Models\n// =============================================================================\n\n/** 模型能力矩阵 — 下游通过此结构决定 UI 功能开关和 Beta Header 注入 */\nexport interface ModelCapabilities {\n // 思考能力\n supports_thinking: boolean;\n supports_adaptive_thinking: boolean;\n /** 交错思考 (Interleaved Thinking) */\n supports_isp: boolean;\n\n // 工具与搜索\n supports_web_search: boolean;\n supports_tool_search: boolean;\n supports_structured_output: boolean;\n\n // 推理控制\n supports_effort: boolean;\n /** 模型是否支持 thinking_level=\"max\" 强度档 (深度思考) */\n supports_max_effort: boolean;\n /** Opus 4.6 独有 (Speed=\"fast\") */\n supports_fast_mode: boolean;\n /** Auto 模式 (模型自主选择工具/搜索策略) */\n supports_auto_mode: boolean;\n\n // 上下文与缓存\n supports_1m_context: boolean;\n supports_prompt_cache: boolean;\n /** 通过 context-management beta 控制 */\n supports_cache_editing: boolean;\n\n // 输出控制\n /** Claude 4 内置 */\n supports_token_efficient: boolean;\n supports_redact_thinking: boolean;\n\n // Token 上限 (冗余但便于查询)\n max_input_tokens: number;\n max_output_tokens: number;\n\n /**\n * 桌面视觉理解 sidecar 能力 (v1.2+):\n * 为 true 表示该模型适合作为桌面截图解析 sidecar — 输入 screenshot,\n * 输出结构化 UI/视觉描述, 供非多模态主模型消费 (CrabCode desktop automation /\n * computer-use 选模型时使用).\n *\n * 与 ManagedModel.inputModalities=['image'] 是正交两件事:\n * - inputModalities 描述\"模型能不能吃图片输入\"\n * - supports_desktop_visual_understanding 描述\"模型是不是被运营标记为\n * 适合做桌面 UI 解析的 sidecar\" (有些通用视觉模型并不擅长 UI 解析)\n *\n * 调用方不应通过模型名 substring 推断此能力; 必须读上游下发字段.\n */\n supports_desktop_visual_understanding?: boolean;\n\n /**\n * 图片生成能力 (v1.3+): 为 true 表示该托管模型是图片生成模型 (DALL·E / SD / 即梦 等),\n * 应通过 client.generateImage() 调用, 而非 chat(). 计量维度由上游派生为 IMAGE (按图计费)。\n */\n supports_image_generation?: boolean;\n\n /**\n * 视频生成能力 (v1.3+): 为 true 表示该托管模型是视频生成模型 (Sora / 可灵 / Runway 等),\n * 应通过 client.generateVideo() + pollVideoTask() 调用。计量维度派生为 VIDEO (按时长计费)。\n */\n supports_video_generation?: boolean;\n\n /**\n * 向量能力 (v2.9+): 为 true 表示该托管模型是向量模型 (DashScope text-embedding-v4 等),\n * 应通过 client.embeddings() 调用。\n */\n supports_embedding?: boolean;\n\n /**\n * 重排序能力 (v2.9+): 为 true 表示该托管模型是重排序模型 (DashScope gte-rerank-v2 / qwen3-rerank 等),\n * 应通过 client.rerank() 调用。\n */\n supports_rerank?: boolean;\n}\n\n/** 图片生成请求 (client.generateImage) */\nexport interface ImageGenerationRequest {\n prompt: string;\n /** 缺省 1024 */\n width?: number;\n /** 缺省 1024 */\n height?: number;\n style?: string;\n}\n\n/** 图片生成响应 */\nexport interface ImageGenerationResponse {\n url?: string;\n b64_json?: string;\n revised_prompt?: string;\n requestId?: string;\n}\n\n/** 视频生成请求 (client.generateVideo) */\nexport interface VideoGenerationRequest {\n prompt: string;\n resolution?: string;\n /** 时长 (秒) */\n duration?: number;\n}\n\n/** 视频任务响应 (创建返回 taskId; 轮询返回 status/videoUrl) */\nexport interface VideoTaskResponse {\n taskId: string;\n /** pending | running | completed | failed */\n status?: string;\n videoUrl?: string;\n error?: string;\n requestId?: string;\n}\n\n// ── 向量 (Embedding) / 重排序 (Rerank) (v2.9.0) ──────────────────────────────\n// SDK 订阅会员经 POST /managed-models/:id/{embeddings,rerank} 调用; 仅\n// capabilities.supports_embedding / supports_rerank 的托管模型可用。上游接 DashScope。\n\n/**\n * 多模态内容片段 (v2.10+; 向量/重排序 qwen3-vl-embedding / qwen3-vl-rerank 线路)。\n * text / image / video 至少填一项; image/video 取 URL 或 base64 data URI。\n */\nexport interface MultimodalContent {\n /** 文本片段 */\n text?: string;\n /** 图片 (URL 或 base64 data URI) */\n image?: string;\n /** 视频 (URL); 仅多模态线路支持 */\n video?: string;\n}\n\n/** 向量请求 (client.embeddings)。文本线路 = OpenAI /v1/embeddings 标准; 多模态线路用 contents。 */\nexport interface EmbeddingRequest {\n /** 文本输入 (文本向量线路): 单条文本或文本数组 (批量)。多模态线路改用 contents。 */\n input?: string | string[];\n /**\n * 多模态内容 (v2.10+; qwen3-vl-embedding 线路): text/image/video 片段数组。\n * 与 input 二选一; 多模态托管模型须用 contents。\n */\n contents?: MultimodalContent[];\n /** 向量维度 (可选; DashScope text-embedding-v4 支持 2048/1536/1024/768/512/256/128/64) */\n dimensions?: number;\n /** 编码格式 (可选, 如 \"float\") */\n encoding_format?: string;\n /** 输出向量类型 (可选; 多模态线路, 如 \"dense\") */\n output_type?: string;\n /** 视频抽帧率 fps (可选; 多模态视频输入) */\n fps?: number;\n /** 是否启用多模态特征融合 (可选; 多模态线路) */\n enable_fusion?: boolean;\n}\n\n/** 单条向量结果 (OpenAI 标准)。 */\nexport interface EmbeddingData {\n object: string;\n index: number;\n embedding: number[];\n}\n\n/** 向量响应 (OpenAI /v1/embeddings 标准)。 */\nexport interface EmbeddingResponse {\n object: string;\n model: string;\n data: EmbeddingData[];\n usage: { prompt_tokens?: number; total_tokens: number };\n id?: string;\n}\n\n/** 重排序查询 (v2.10+): 文本字符串, 或多模态 {text|image}。 */\nexport type RerankQuery = string | { text?: string; image?: string };\n/** 重排序候选文档 (v2.10+): 文本字符串, 或多模态 {text|image|video}。 */\nexport type RerankDocument = string | MultimodalContent;\n\n/** 重排序请求 (client.rerank)。统一扁平契约, 网关内部按模型线路转换。 */\nexport interface RerankRequest {\n /** 查询: 文本字符串 (文本线路), 或多模态 {text|image} (qwen3-vl-rerank 线路) */\n query: RerankQuery;\n /** 候选文档: 文本字符串数组, 或多模态 {text|image|video} 对象数组 */\n documents: RerankDocument[];\n /** 返回前 N 条 (可选; 缺省返回全部) */\n top_n?: number;\n /** 是否在结果中回传文档原文 (可选, 缺省 false) */\n return_documents?: boolean;\n /** 排序指令 (可选; 仅 OpenAI 兼容线路支持, 原生线路忽略) */\n instruct?: string;\n /** 视频抽帧率 fps (可选; 多模态视频文档) */\n fps?: number;\n}\n\n/** 单条重排序结果。 */\nexport interface RerankResult {\n /** 文档在原 documents 数组中的下标 */\n index: number;\n /** 相关性得分 (越高越相关) */\n relevance_score: number;\n /** 文档原文 (仅 return_documents=true 时存在) */\n document?: string;\n}\n\n/** 重排序响应。 */\nexport interface RerankResponse {\n results: RerankResult[];\n usage: { total_tokens: number };\n model?: string;\n}\n\n/**\n * 模型可接收的用户输入模态 —— **本 SDK 已知的那些** (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n * - 'video': 视频输入 (多模态向量 / 重排序类模型, 网关 2026-06-20 起下发)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n *\n * 这个联合体是**查询用**的 (helper 的 modality 参数), 不是数据域的全集 —— 见\n * {@link InputModalityTag}.\n */\nexport type InputModality = 'text' | 'image' | 'video';\n\n/**\n * 目录里实际可能出现的模态标签 —— **开放值域**, 已知取值见 {@link InputModality}.\n *\n * 值域由网关托管模型目录持有, 靠运营动作扩张 (不是靠发 SDK 新版)。把数据字段\n * 声明成封闭联合体, 等于让类型对在网真实数据撒谎; 2026-08-02 CrabCode 客户端\n * 因同一形态的封闭枚举 (Rust 侧) 把整个网关模型列表打空过数周。\n *\n * 因此: **查询**用 {@link InputModality} (已知标签, 有自动补全),\n * **数据**用本类型 (照收未知标签, 不丢信息、不谎报)。\n *\n * 刻意就是朴素 `string`, 不用 `InputModality | (string & {})` 那个\"保留补全\"的\n * 技巧: 补全只在**书写**字面量时有价值, 而本类型描述的是**读到**的目录数据;\n * 那个技巧还要靠 `{}` —— 本仓 eslint `ban-types` 明令禁止。\n */\nexport type InputModalityTag = string;\n\n/** BucketClass 字面量常量 — V30 二轮审计 D-P1-3 修复 */\nexport const BucketClassCommercial = 'COMMERCIAL';\nexport const BucketClassGeneric = 'GENERIC';\n\n/**\n * 用户在某 modelId 上的桶余额聚合视图 (V30 entitlement-listing).\n *\n * 多桶聚合规则 (上游 nexus-v4 计算后下发, 全部单位为 ETU):\n * - quotaEtu / usedEtu / remainingEtu: 该 modelId 下用户全部 active 桶求和\n * - sharedPoolEtu: 求和量中\"来自通配桶\"的部分 (跨模型可消耗)\n * - bucketClass / expiresAt: 取最高优先级桶\n * - expired: 全部桶都过期才置 true\n */\nexport interface BucketInfo {\n quotaEtu: number;\n usedEtu: number;\n remainingEtu: number;\n sharedPoolEtu?: number;\n expiresAt?: string;\n bucketClass: string;\n expired?: boolean;\n /** v0.19+ — GENERIC alive 桶 tokenRemaining 求和 (\"免费余额\", 真实可消费) */\n freeRemainingEtu: number;\n /** v0.19+ — COMMERCIAL alive 桶 tokenRemaining 求和 (\"付费余额\") */\n paidRemainingEtu: number;\n}\n\n/** 大小写不敏感判定 — 与 buildBucketView (managed_model.go) EqualFold 语义对齐 */\nexport function bucketInfoIsCommercial(b: BucketInfo | null | undefined): boolean {\n if (!b) return false;\n return b.bucketClass.toLowerCase() === BucketClassCommercial.toLowerCase();\n}\n\n/** 托管模型 */\nexport interface ManagedModel {\n id: string;\n name: string;\n provider: string;\n modelId: string;\n maxTokens: number;\n isEnabled: boolean;\n /** @deprecated 公开 /managed-models 端点不返回此字段 (最小暴露原则); 仅 admin 响应或历史端口残留, listModels() 结果中恒为 undefined。 */\n pricePerMTok?: number;\n /** @deprecated 公开 /managed-models 端点不返回此字段 (最小暴露原则); 仅 admin 响应或历史端口残留, listModels() 结果中恒为 undefined。 */\n isDefault?: boolean;\n contextWindow?: number;\n capabilities: ModelCapabilities;\n\n /** P1 商品化档位门控: 0=FREE..4=ULTRA。前端模型选择器按用户档位过滤。 */\n minPlanTier?: number;\n /** 该模型能否在 C 端对话 (ADK 托管运行时) 使用; false 时选择器应过滤。 */\n chatRuntimeSupported?: boolean;\n /** 模型默认绑定的工具 ID 列表。 */\n defaultToolIds?: string[];\n /** 该模型对当前用户档位是否被限制策略锁定 (limit_policy allowedModels 不含)。true=展示但置灰+升级引导。enforced 关/解析失败=false。 */\n locked?: boolean;\n /** 是否属于\"免费会员可用\"分区 (FREE 档 allowedModels 内)。与当前用户档位无关 (仅分类)。 */\n freeTier?: boolean;\n\n /**\n * 上游 gateway 为此模型启用的请求格式列表\n * 取值: \"anthropic\" | \"openai\"\n * 空值表示上游未声明, SDK 回落 provider 硬编码分支 (向后兼容)\n */\n supported_formats?: string[];\n\n /**\n * 上游建议客户端优先使用的格式\n * 取值: \"anthropic\" | \"openai\"; 空值等价于 supported_formats[0]\n */\n preferred_format?: string;\n\n /**\n * 该模型可选的思考深度档位 id(升序,v2.18+)。取值是 {@link ThinkingOff} / {@link ThinkingHigh} /\n * {@link ThinkingMax} 的子集;网关按「admin 声明 ∩ wire 层真投递」读时派生。\n * `[]` = 该模型没有思考档(不要渲染档位选择);`undefined` = 上游未播报(旧网关),\n * 调用方必须按\"未知\"处理,严禁回落到自己推档或按模型名猜。\n */\n thinking_levels?: string[];\n\n /**\n * 当前用户在此模型上的桶余额聚合 (V0.18 V30 entitlement-listing).\n *\n * 仅当调用方为非 admin 用户 (web user / desktop OAuth) 时上游才会返回此字段:\n * - admin / X-Internal-Bypass 调用 → 缺失\n * - 普通用户 → 非空, 含 quota/used/remaining 求和\n */\n bucketInfo?: BucketInfo;\n\n /**\n * 模型可接收的用户输入模态 (v1.2+, CrabCode desktop automation / computer-use 选模型用).\n *\n * 已知取值见 {@link InputModality} ('text' | 'image' | 'video'); 'image' 表示模型可\n * 直接接收 screenshot/image 输入。类型是**开放**的 ({@link InputModalityTag}) ——\n * 目录新增模态时旧版 SDK 照样原样透出, 不静默丢值。\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModalityTag[];\n}\n\n// =============================================================================\n// QuotaSummary — v0.19+ 账户级权益总览 (免费/付费切分)\n// =============================================================================\n\n/** 单桶视图 — QuotaSummary.freeBuckets/paidBuckets 元素 */\nexport interface BucketRow {\n bucketId: string;\n /** 精确桶为具体 modelId, 通配桶为 \"*\" */\n modelId: string;\n /** COMMERCIAL | GENERIC */\n bucketClass: string;\n tokenQuota: number;\n tokenUsed: number;\n tokenRemaining: number;\n /** 永久桶为 undefined */\n expiresAt?: string;\n expired?: boolean;\n}\n\nexport function bucketRowIsCommercial(r: BucketRow | null | undefined): boolean {\n if (!r) return false;\n return r.bucketClass.toLowerCase() === BucketClassCommercial.toLowerCase();\n}\n\n/** 滚动窗口限额状态 — QuotaSummary.windowLimits 元素 */\nexport interface WindowLimitStatus {\n /** 窗口档位: FIVE_HOUR = 5 小时滚动窗口, WEEKLY = 7 天滚动窗口 */\n kind: 'FIVE_HOUR' | 'WEEKLY';\n /** 窗口内 credit 用量上限 */\n limitCredits: number;\n /** 窗口内已用 credit */\n usedCredits: number;\n /** 预计恢复时间 (ISO-8601 UTC); 窗口内无用量时为 null / 缺失 */\n resetAt?: string | null;\n /**\n * [W1 2026-07-11 软限额] 该窗此刻可否被用户「继续」开关豁免 — 5h 软限额时 true (展示端可给\n * 严格模式切换入口), 周窗恒 false。老网关不返回时缺失。\n */\n overridable?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914] 网关 `quota_summary.go` 的服务端合成位: 该窗已用满, 但用户的「继续」\n * 开关正在放行。新网关恒下发, 老网关缺席 —— 缺席只说明网关没有这个字段, 不能当 false 读。\n */\n fiveHourSoftContinuing?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914 D3-7] 窗口内在途预占 (微 Credits): 已被进行中的请求预扣、尚未结算的额度。\n * `usedCredits` 只计已结算, 准入判定却按「已结算 + 在途」算 —— 本字段就是两者之差。\n * 缺失 / null = 不知道 (老网关或取数失败), 不能当 0 读; 0 = 此刻没有在途。\n */\n inflightHeldCredits?: number | null;\n}\n\n/**\n * 订阅池视图 — QuotaSummary.subscriptionPool 的形状。\n *\n * 由网关 `quota_summary.go` 从 alive 的 COMMERCIAL (付费) 桶派生。quota / used / remaining 的\n * 数值单位为**微 Credits** (÷1000 = Credits)。\n */\nexport interface QuotaSummarySubscriptionPool {\n /** 池总额度 (微 Credits) */\n quota: number;\n /** 已用额度 (微 Credits) */\n used: number;\n /** 剩余额度 (微 Credits) */\n remaining: number;\n /** 到期时间; 网关未下发时缺失 */\n expiresAt?: string;\n /** 网关下发的单位标识 */\n unit: string;\n /**\n * [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 池里由**加油包**贡献的剩余额度 (微 Credits)。\n *\n * 网关由 alive 的 COMMERCIAL 桶中 `sourceType === 'booster_pack'` 的那些行求和派生。\n *\n * **缺席 = 老网关, 或此刻一个加油包都没有 —— 两种都不可当 0 读。** 展示端的合同是\n * 「三个 booster* 字段齐全才渲染副行」, 少一个就整条不画; 把缺席当 0 渲染, 等于在老网关上\n * 向用户断言「你没有加油包」, 而真相是客户端不知道。\n */\n boosterRemaining?: number;\n /**\n * [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包桶的**个数** (一行 = 一个加油包)。\n * 缺席语义同 boosterRemaining。\n */\n boosterCount?: number;\n /**\n * [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包中最早的到期时刻 (ISO-8601)。\n *\n * 缺席有第三种可能: 加油包确实存在、但全是永久桶 (不参与到期选举) —— 与 expiresAt 同口径。\n * 所以它缺席时仍可能有 boosterRemaining / boosterCount, 展示端据此决定要不要说「最早 X 到期」。\n */\n boosterNextExpiresAt?: string;\n}\n\n/**\n * GET /api/v4/entitlements/quota-summary 返回体, 调用 client.getQuotaSummary 获取.\n *\n * 设计目的: 个人中心钱包栏一次性展示\"我还有多少免费 + 多少付费\"+ 各自最近到期时间.\n */\nexport interface QuotaSummary {\n /** GENERIC (免费/赠送) alive 桶 tokenRemaining 求和 */\n freeTotalEtu: number;\n /** COMMERCIAL (付费购买) alive 桶 tokenRemaining 求和 */\n paidTotalEtu: number;\n /** GENERIC 桶详情 (含过期, UI 列流水); 空时为空数组 */\n freeBuckets: BucketRow[];\n /** COMMERCIAL 桶详情 (含过期); 空时为空数组 */\n paidBuckets: BucketRow[];\n /** GENERIC alive 桶中最早到期; 永久桶/无 alive 时缺失 */\n nextFreeExpiresAt?: string;\n /** COMMERCIAL alive 桶中最早到期; 同上 */\n nextPaidExpiresAt?: string;\n /** 滚动窗口限额状态 (5 小时 / 7 天); 后端未启用窗口限额时字段整个缺失 */\n windowLimits?: WindowLimitStatus[];\n /**\n * [W1 2026-07-11 软限额] 用户 5h「继续」开关现值 (true=软限额/默认: 到达 5h 仅提醒并继续消耗\n * 周配额; false=严格模式: 到达即等待恢复)。仅后端启用窗口限额时下发, 否则缺失。\n */\n windowFiveHourContinueEnabled?: boolean;\n /**\n * [W-QUAD-CHAIN-20260914] 订阅池: 网关由 alive 的 COMMERCIAL (付费) 桶派生, 数值单位为微 Credits\n * (÷1000 = Credits)。缺席 = 老网关, 或当前没有 alive 付费桶 —— 两种都不可当 0 处理。\n */\n subscriptionPool?: QuotaSummarySubscriptionPool;\n}\n\n// =============================================================================\n// WindowResetSummary — v2.13+ 邀请奖励窗口重置券\n// =============================================================================\n\n/**\n * GET /api/v4/entitlements/window-reset/summary 返回体, 调用 client.getWindowResetSummary 获取.\n *\n * 设计目的: 个人中心邀请栏与 429 窗口限额弹窗一次性展示\"我还有几张重置券 + 最近一张何时过期\",\n * 并给出邀请进度 (已达标 / 待达标) 作为继续邀请的引导。\n *\n * 券由邀请奖励发放 (被邀请人激活并达到用量门槛后计一次), 每张可清空一次滚动窗口已用量\n * (见 client.redeemWindowReset)。\n */\nexport interface WindowResetSummary {\n /** 当前可用 (未使用且未过期) 的重置券张数 */\n availableCount: number;\n /** 可用券中最早到期时间 (ISO-8601 UTC); 无可用券时为 null */\n nextExpireAt: string | null;\n /** 历史累计发放张数 (含已用 / 已过期) */\n totalGranted: number;\n /** 历史累计已使用张数 */\n totalUsed: number;\n /** 已达标并已发放重置券的邀请数 */\n qualifiedInvites: number;\n /** 已激活但尚未达到发放门槛的邀请数 (仍可能转为已达标) */\n pendingInvites: number;\n}\n\n// =============================================================================\n// Chat\n// =============================================================================\n\n/** 聊天消息 (简单文本格式, CrabClaw 使用) */\nexport interface ChatMessage {\n role: string;\n content: string;\n}\n\n/** Anthropic 响应内容块 */\nexport interface ChatContentBlock {\n type: string;\n text?: string;\n citations?: unknown;\n thinking?: string;\n signature?: string;\n data?: string;\n id?: string;\n name?: string;\n /** json.RawMessage in Go */\n input?: unknown;\n server_name?: string;\n caller?: unknown;\n tool_use_id?: string;\n content?: unknown;\n is_error?: boolean;\n}\n\n/** Anthropic 格式 token 用量 */\nexport interface ChatUsage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n}\n\n// ---------- Thinking ----------\n\n/** 三档思考级别 (v0.9.0) */\nexport const ThinkingOff = 'off';\nexport const ThinkingLow = 'low';\nexport const ThinkingHigh = 'high';\nexport const ThinkingMax = 'max';\n\n/** 标准思考最低 maxTokens — CrabCode 默认 MAX_OUTPUT_TOKENS_DEFAULT = 32_000 */\nexport const ThinkingHighMinMaxTokens = 32_000;\n/** 深度思考回退 maxTokens — Opus 4.6 上限 128K */\nexport const ThinkingMaxFallbackMaxTokens = 128_000;\n\n/** 控制模型思考行为 */\nexport interface ThinkingConfig {\n /** \"enabled\" | \"disabled\" | \"adaptive\" */\n type: string;\n /** 仅 type=\"enabled\" 时 (旧模型回退) */\n budget_tokens?: number;\n /** 思考级别 (v0.9.0): \"off\" | \"high\" | \"max\" */\n level?: string;\n /** \"none\" | \"summary\" | \"\" (默认空=完整) */\n display?: string;\n}\n\n/** 根据三档 level 创建配置 */\nexport function newThinkingConfig(level: string): ThinkingConfig {\n if (level === '' || level === ThinkingOff) {\n return { type: 'disabled' };\n }\n return { type: 'adaptive', level };\n}\n\n/** 服务端工具定义 — SDK 将工具 schema 合入 API 请求的 tools 数组 */\nexport interface ServerTool {\n type: string;\n name: string;\n config?: Record<string, unknown>;\n}\n\n/** Server Tool 类型常量 */\nexport const ServerToolTypeWebSearch = 'web_search_20250305';\n\n/** 地理位置 */\nexport interface GeoLoc {\n /** ISO 3166-1 alpha-2 */\n country: string;\n city?: string;\n}\n\n/** ServerTool.config 结构 (type=web_search_20250305) */\nexport interface WebSearchConfig {\n /** 每请求最大搜索次数, 默认 8 */\n max_uses?: number;\n /** 域名白名单 (与 blocked_domains 互斥) */\n allowed_domains?: string[];\n /** 域名黑名单 (与 allowed_domains 互斥) */\n blocked_domains?: string[];\n user_location?: GeoLoc;\n}\n\n/** 控制推理努力级别 */\nexport interface EffortConfig {\n /** \"low\" | \"medium\" | \"high\" | \"max\" */\n level: string;\n}\n\n/** 控制输出格式 (结构化输出) */\nexport interface OutputConfig {\n /** \"json_schema\" | \"\" */\n format?: string;\n schema?: unknown;\n}\n\n/**\n * 创建搜索 Server Tool 的便捷方法\n * allowed_domains 与 blocked_domains 互斥, 同时传入抛 Error\n */\nexport function newWebSearchTool(cfg?: WebSearchConfig | null): ServerTool {\n const st: ServerTool = {\n type: ServerToolTypeWebSearch,\n name: 'web_search',\n };\n if (cfg) {\n if ((cfg.allowed_domains?.length ?? 0) > 0 && (cfg.blocked_domains?.length ?? 0) > 0) {\n throw new Error('web search: allowed_domains and blocked_domains are mutually exclusive');\n }\n const m: Record<string, unknown> = {};\n if (cfg.max_uses && cfg.max_uses > 0) m['max_uses'] = cfg.max_uses;\n if (cfg.allowed_domains?.length) m['allowed_domains'] = cfg.allowed_domains;\n if (cfg.blocked_domains?.length) m['blocked_domains'] = cfg.blocked_domains;\n if (cfg.user_location) m['user_location'] = cfg.user_location;\n if (Object.keys(m).length > 0) st.config = m;\n }\n return st;\n}\n\n/**\n * 聊天请求\n *\n * 基础字段供 CrabClaw 使用, 扩展字段供 CrabCode 使用。\n * 所有新增字段零值不改变行为 (向后兼容)。\n *\n * 注意:扩展字段(rawMessages/system/tools/...)不会被原样 JSON.stringify,\n * 而由 buildRequestBody (adapter) 选择性序列化到请求体。\n */\nexport interface ChatRequest {\n // ── 基础字段 (CrabClaw 兼容) ──\n messages?: ChatMessage[];\n stream?: boolean;\n max_tokens?: number;\n\n // ── 完整请求控制 (CrabCode 扩展) ── 不直接 JSON 序列化, 由 buildRequestBody 处理\n /** 复杂消息体 (含 content blocks / 多模态), 非 nil 时优先于 messages */\n rawMessages?: unknown;\n /** string 或 ContentBlock[] */\n system?: unknown;\n /** 标准工具定义 (Tool[]) */\n tools?: unknown;\n temperature?: number;\n thinking?: ThinkingConfig;\n metadata?: Record<string, string>;\n /** 显式 beta (SDK 自动合并) */\n betas?: string[];\n /** 服务端工具 (buildRequestBody 合入 tools 数组) */\n serverTools?: ServerTool[];\n /** \"\" | \"fast\" (Fast Mode) */\n speed?: string;\n effort?: EffortConfig;\n outputConfig?: OutputConfig;\n /** 任意扩展字段 (buildRequestBody 合入请求体) */\n extraBody?: Record<string, unknown>;\n\n /**\n * v0.13.0: OpenAI wire format 原生字段。AnthropicAdapter 忽略,\n * OpenAIAdapter 按 OpenAI 规范序列化。\n *\n * 对应 OpenAI `parallel_tool_calls` 顶层字段, undefined = 不设置 (沿用上游默认 true)。\n */\n parallelToolCalls?: boolean;\n\n /**\n * v1.6.0: 业务侧终端用户 id, 跨 provider 通用语义 (不绑死 DeepSeek)。\n * 仅当 caller 拥有 scope=endusr.set 时网关采信; 否则被网关 sanitizer 校验/覆盖。\n * 约束: 长度 ≤ 512, 字符集 [a-zA-Z0-9_-]+, 禁止包含用户隐私信息。\n * 二选一:\n * OpenAI wire → 序列化为顶层 body[\"user_id\"]\n * Anthropic wire → 合并到 body[\"metadata\"][\"user_id\"]; caller metadata 显式键优先, 不被覆盖\n */\n endUserId?: string;\n}\n\n// ---------- Web Search Sources ----------\n\n/** 联网搜索结果来源 (与后端 adk/stream_helpers.go SourceItem 对齐) */\nexport interface WebSearchSource {\n title: string;\n url: string;\n snippet?: string;\n}\n\n/** 搜索来源事件 (从 SSE \"sources\" 事件解析) */\nexport interface SourcesEvent {\n sources: WebSearchSource[];\n session_id?: string;\n}\n\n/** sources 事件结构错误的稳定机器码。 */\nexport type SourcesEventIssueCode =\n | 'invalid_json'\n | 'missing_sources'\n | 'sources_not_array'\n | 'source_not_object'\n | 'source_title_invalid'\n | 'source_url_invalid'\n | 'source_snippet_invalid'\n | 'session_id_invalid';\n\n/**\n * sources SSE 的无歧义分类结果。\n *\n * `empty_sources` 是合法零结果;`malformed_sources` 才表示结构损坏。\n */\nexport type SourcesEventParseResult =\n | { kind: 'not_sources' }\n | { kind: 'empty_sources'; session_id?: string }\n | { kind: 'sources'; value: SourcesEvent }\n | { kind: 'malformed_sources'; code: SourcesEventIssueCode };\n\n/**\n * 同时识别 SSE event name 与 JSON payload discriminator,并把合法空结果\n * 与非 sources、结构损坏明确分开。未知额外字段不影响分类。\n */\nexport function classifySourcesEvent(ev: StreamEvent): SourcesEventParseResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(ev.data);\n } catch {\n return ev.event === 'sources'\n ? { kind: 'malformed_sources', code: 'invalid_json' }\n : { kind: 'not_sources' };\n }\n\n const wrapper =\n typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n if (wrapper?.type !== 'sources' && ev.event !== 'sources') {\n return { kind: 'not_sources' };\n }\n if (!wrapper || !Object.prototype.hasOwnProperty.call(wrapper, 'sources')) {\n return { kind: 'malformed_sources', code: 'missing_sources' };\n }\n if (\n Object.prototype.hasOwnProperty.call(wrapper, 'session_id') &&\n wrapper.session_id !== undefined &&\n typeof wrapper.session_id !== 'string'\n ) {\n return { kind: 'malformed_sources', code: 'session_id_invalid' };\n }\n if (!Array.isArray(wrapper.sources)) {\n return { kind: 'malformed_sources', code: 'sources_not_array' };\n }\n\n const sessionID = typeof wrapper.session_id === 'string' ? wrapper.session_id : undefined;\n if (wrapper.sources.length === 0) {\n return {\n kind: 'empty_sources',\n ...(sessionID === undefined ? {} : { session_id: sessionID }),\n };\n }\n\n for (const source of wrapper.sources) {\n if (typeof source !== 'object' || source === null || Array.isArray(source)) {\n return { kind: 'malformed_sources', code: 'source_not_object' };\n }\n const item = source as Record<string, unknown>;\n if (typeof item.title !== 'string') {\n return { kind: 'malformed_sources', code: 'source_title_invalid' };\n }\n if (typeof item.url !== 'string') {\n return { kind: 'malformed_sources', code: 'source_url_invalid' };\n }\n if (\n Object.prototype.hasOwnProperty.call(item, 'snippet') &&\n item.snippet !== undefined &&\n typeof item.snippet !== 'string'\n ) {\n return { kind: 'malformed_sources', code: 'source_snippet_invalid' };\n }\n }\n\n return {\n kind: 'sources',\n value: {\n ...wrapper,\n sources: wrapper.sources as WebSearchSource[],\n ...(sessionID === undefined ? {} : { session_id: sessionID }),\n } as SourcesEvent,\n };\n}\n\n/**\n * 从 StreamEvent 中解析搜索来源\n *\n * @deprecated 需要区分合法空结果与结构损坏时使用 classifySourcesEvent。\n * 此 legacy API 保留原有宽松解析和返回对象形状;新代码不得据此区分\n * 合法空结果与结构损坏。\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: {\n type?: string;\n sources?: WebSearchSource[];\n session_id?: string;\n };\n try {\n wrapper = JSON.parse(ev.data);\n } catch {\n return null;\n }\n if (wrapper.type !== 'sources' && ev.event !== 'sources') {\n return null;\n }\n if (!wrapper.sources || wrapper.sources.length === 0) {\n return null;\n }\n return { sources: wrapper.sources, session_id: wrapper.session_id };\n}\n\n// =============================================================================\n// ChatResponse\n// =============================================================================\n\n/**\n * 同步聊天响应 (Anthropic format, v0.4.1)\n *\n * tokenRemaining / callRemaining / modelTokenRemaining* 不在 wire JSON 中, 由 client 从 Header 填充。\n * 哨兵 -1 表示服务端未返回。\n */\nexport interface ChatResponse {\n id: string;\n type: string;\n model: string;\n role: string;\n content: ChatContentBlock[];\n stop_reason: string;\n usage: ChatUsage;\n /** -1 表示服务端未返回 */\n tokenRemaining: number;\n callRemaining: number;\n modelTokenRemaining: number;\n modelTokenRemainingETU: number;\n}\n\n// =============================================================================\n// SSE Stream\n// =============================================================================\n\n/**\n * SSE 流式事件\n *\n * v0.11.0 新增 blockIndex/blockType/ephemeral 三字段 (in-band content block 元数据)。\n * 零值等价 v0.10.0 行为, 未识别的事件三字段全部零值。\n */\nexport interface StreamEvent {\n event: string;\n data: string;\n /** 对齐 Anthropic content_block_start/delta/stop 的 index 字段 */\n blockIndex?: number;\n /** 由 content_block_start 解析得到, delta/stop 从 index→type 映射查出 */\n blockType?: string;\n /** 网关标记此 block 下一轮不应回传 */\n ephemeral?: boolean;\n}\n\n/**\n * 流式结算事件 (从 settled SSE 事件解析)\n * 包含本次请求的 token 消耗及结算后的剩余余额\n */\nexport interface StreamSettlement {\n requestId: string;\n consumeStatus: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n /** 结算后剩余 token (-1 表示服务端未返回) */\n tokenRemaining: number;\n /** 结算后剩余调用次数 (-1 表示服务端未返回) */\n callRemaining: number;\n}\n\n/** 从 settled 类型的 StreamEvent 中解析结算信息. 不是 settled 类型则返回 null. */\nexport function parseSettlement(ev: StreamEvent): StreamSettlement | null {\n if (ev.event !== 'settled' && ev.event !== 'pending_settle') {\n return null;\n }\n let s: Partial<StreamSettlement>;\n try {\n s = JSON.parse(ev.data);\n } catch {\n return null;\n }\n return {\n requestId: s.requestId ?? '',\n consumeStatus: s.consumeStatus ?? '',\n inputTokens: s.inputTokens ?? 0,\n outputTokens: s.outputTokens ?? 0,\n totalTokens: s.totalTokens ?? 0,\n tokenRemaining: s.tokenRemaining ?? -1,\n callRemaining: s.callRemaining ?? -1,\n };\n}\n","// shared/errors.ts — 跨域类型化错误。\n//\n// 端口自 acosmi-sdk-go/types.go (v0.19.0) 的 Errors 段。\n\n// =============================================================================\n// Errors (类型化)\n// =============================================================================\n\n/** 下载限流错误 (429) */\nexport class RateLimitError extends Error {\n retryAfter: string;\n raw: string;\n\n constructor(message: string, retryAfter: string, raw: string) {\n super(message);\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n this.raw = raw;\n }\n}\n\n/**\n * API 业务层错误 (HTTP 200 但 code != 0)\n * tk-dist 代理透传 yudao 响应, HTTP 状态码为 200, 业务错误在 JSON code 字段\n */\nexport class BusinessError extends Error {\n code: number;\n\n constructor(code: number, message: string) {\n super(`API error (code=${code}): ${message}`);\n this.name = 'BusinessError';\n this.code = code;\n }\n}\n\n/**\n * 订单到达非成功终态 (FAILED/CANCELLED/CLOSED/EXPIRED/REFUNDED)\n * waitForPayment 在订单终态为非成功时抛此错误\n */\nexport class OrderTerminalError extends Error {\n orderId: string;\n status: string;\n\n constructor(orderId: string, status: string) {\n super(`order ${orderId} terminated: ${status}`);\n this.name = 'OrderTerminalError';\n this.orderId = orderId;\n this.status = status;\n }\n}\n\n/**\n * 模型缓存未命中且 listModels 刷新后仍未找到。\n *\n * 历史上 getCachedModel miss 时硬返 ManagedModel{provider:\"anthropic\"} 占位,\n * 导致未预热场景下的 chat 请求按 AnthropicAdapter 编码, 被发到错误端点。\n * v0.13.x 改为 miss → listModels 自动刷新一次; 仍 miss → 抛此错误。\n */\nexport class ModelNotFoundError extends Error {\n modelId: string;\n\n constructor(modelId: string) {\n super(`managed model \"${modelId}\" not found (list models to refresh cache, or verify model id)`);\n this.name = 'ModelNotFoundError';\n this.modelId = modelId;\n }\n}\n\n/**\n * 结构化 HTTP 非 2xx 错误.\n *\n * 用 instanceof 提取:\n * try { ... } catch (e) {\n * if (e instanceof HTTPError && e.statusCode === 429) { ... }\n * }\n */\nexport class HTTPError extends Error {\n statusCode: number;\n /** anthropic.error.type / openai.error.type, 缺失为空 */\n type: string;\n /** Retry-After 头解析的秒数, 0 表示未提供或解析失败 */\n retryAfter: number;\n /** 原始响应体 (截断到 maxErrorBodySize) */\n body: string;\n /** 后端业务机器码 (响应体顶层 errorCode, e.g. \"WINDOW_LIMIT_EXCEEDED\"); 缺失为 undefined */\n errorCode?: string;\n /** 窗口限额场景: 触发的滚动窗口档位 (FIVE_HOUR = 5 小时 / WEEKLY = 7 天); 缺失为 undefined */\n windowKind?: 'FIVE_HOUR' | 'WEEKLY';\n /** 窗口限额场景: 预计恢复时间 (ISO-8601 UTC); 缺失为 undefined */\n windowResetAt?: string;\n /**\n * [W1 2026-07-11 软限额] 窗口限额场景: 该 429 可否被用户「继续」开关豁免 —\n * true = 5 小时软限额严格模式 (客户端可提示「继续任务」以消耗周配额继续);\n * false/缺失 = 周窗/显式禁止/灰度关 (硬等待, 无豁免路径, 老网关不返回时为 undefined)。\n */\n windowOverridable?: boolean;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n retryable: boolean;\n\n constructor(\n statusCode: number,\n opts: {\n type?: string;\n message?: string;\n retryAfter?: number;\n body?: string;\n errorCode?: string;\n windowKind?: 'FIVE_HOUR' | 'WEEKLY';\n windowResetAt?: string;\n windowOverridable?: boolean;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n retryable?: boolean;\n } = {},\n ) {\n let msg: string;\n if (opts.type) {\n msg = `HTTP ${statusCode}: [${opts.type}] ${opts.message ?? ''}`;\n } else if (opts.message) {\n msg = `HTTP ${statusCode}: ${opts.message}`;\n } else if (opts.body) {\n msg = `HTTP ${statusCode}: ${opts.body}`;\n } else {\n msg = `HTTP ${statusCode}`;\n }\n super(msg);\n this.name = 'HTTPError';\n this.statusCode = statusCode;\n this.type = opts.type ?? '';\n this.retryAfter = opts.retryAfter ?? 0;\n this.body = opts.body ?? '';\n this.errorCode = opts.errorCode;\n this.windowKind = opts.windowKind;\n this.windowResetAt = opts.windowResetAt;\n this.windowOverridable = opts.windowOverridable;\n this.errorContractVersion = opts.errorContractVersion;\n this.faultDomain = opts.faultDomain;\n this.requestDisposition = opts.requestDisposition;\n this.transportRequestId = opts.transportRequestId;\n this.consumeRequestId = opts.consumeRequestId;\n this.providerRequestId = opts.providerRequestId;\n this.retryable = opts.retryable === true && opts.requestDisposition === 'not_accepted';\n }\n}\n\n/**\n * 结构化网络层错误 (传输失败, 区别于上游业务错误).\n *\n * 包装 fetch 抛出的错误 — 含 timeout / EOF / connection refused / DNS 失败等.\n * retry policy: isTimeout / isEOF 任一为 true → 默认可重试.\n */\nexport class NetworkError extends Error {\n /** 操作描述, e.g. \"POST /v1/messages\" */\n op: string;\n /** 请求 URL (脱敏后) */\n url: string;\n override cause?: unknown;\n timeout: boolean;\n eof: boolean;\n requestDisposition: RequestDisposition = 'unknown';\n\n constructor(op: string, url: string, cause: unknown, opts: { timeout?: boolean; eof?: boolean } = {}) {\n const causeMsg = cause instanceof Error ? cause.message : cause != null ? String(cause) : 'network error';\n super(`${op} ${url}: ${causeMsg}`);\n this.name = 'NetworkError';\n this.op = op;\n this.url = url;\n this.cause = cause;\n this.timeout = opts.timeout ?? false;\n this.eof = opts.eof ?? false;\n }\n\n isTimeout(): boolean {\n return this.timeout;\n }\n\n isEOF(): boolean {\n return this.eof;\n }\n}\n\n/** 只收有限非负数; 其余 (null / 负数 / NaN / Infinity / 非数字) 一律 undefined。 */\nfunction finiteNonNegative(value: number | null | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\n/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件 (/chat 线 `event: failed`) 与\n * Anthropic 线的 `event: error` 帧解析得到 —— 两条线同一张字段表, 同一个解析点\n * (`core/http.ts::parseStreamError`)。客户端可通过 instanceof (同 realm) 或\n * `isStreamError` (跨 realm) 提取, 并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** D23 gateway machine code; mirrors the legacy `code` field. */\n errorCode: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 (= 服务端 `retryable` **且** `requestDisposition === 'not_accepted'`) */\n retryable: boolean;\n /**\n * 服务端在帧上声明的 `retryable` **原值** (2.19.5 起) —— 不与 requestDisposition 合取,\n * 缺席即 undefined。\n *\n * 为什么要并列存一份: 上面的 `retryable` 是两个判据的合取, 网关的流内错误帧并不总是\n * 下发 `requestDisposition`, 于是\"服务端明说可重试\"会被合取成 false —— 两端各自正确,\n * 合起来恒否。消费方要区分**「服务端明说不可重试」**(false, 一票否决) 与**「服务端没说」**\n * (undefined, 回落到自己的本地分类表) 时, 只能读这个原值; 读 `retryable` 两种情形不可分。\n */\n serverRetryable?: boolean;\n /**\n * 服务端建议的等待秒数 (2.19.5 起)。**有限非负数**才收, 其余 (null / 负数 /\n * NaN / Infinity / 非数字) 一律 undefined —— 宁可不给, 绝不编造等待时间。\n */\n retryAfterSecs?: number;\n errorContractVersion?: number;\n faultDomain?: FaultDomain;\n requestDisposition?: RequestDisposition;\n transportRequestId?: string | null;\n consumeRequestId?: string | null;\n providerRequestId?: string | null;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean; retryAfterSecs?: number | null; errorContractVersion?: number; faultDomain?: FaultDomain; requestDisposition?: RequestDisposition; transportRequestId?: string | null; consumeRequestId?: string | null; providerRequestId?: string | null } = {}) {\n const code = opts.code ?? '';\n const stage = opts.stage ?? '';\n const userMessage = opts.message ?? '';\n const rawError = opts.rawError ?? '';\n const retryable = opts.retryable === true && opts.requestDisposition === 'not_accepted';\n\n const body = rawError !== '' ? rawError : userMessage;\n const msg = stage !== '' ? `stream failed: ${stage}: ${body}` : `stream failed: ${body}`;\n super(msg);\n this.name = 'StreamError';\n this.code = code;\n this.errorCode = code;\n this.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n this.serverRetryable = opts.retryable;\n this.retryAfterSecs = finiteNonNegative(opts.retryAfterSecs);\n this.errorContractVersion = opts.errorContractVersion;\n this.faultDomain = opts.faultDomain;\n this.requestDisposition = opts.requestDisposition;\n this.transportRequestId = opts.transportRequestId;\n this.consumeRequestId = opts.consumeRequestId;\n this.providerRequestId = opts.providerRequestId;\n }\n}\n\n/**\n * StreamError 的**结构**判据 (2.19.5 起) —— 跨 realm 可用。\n *\n * 为什么不用 `instanceof`: 消费方 (如 CrabCode) 会在 worker / `vm` 上下文里消费这条错误,\n * 或者把 SDK 打进第二份 bundle; 两种情形下 `instanceof StreamError` 都静默为 false ——\n * 症状与\"这段分类代码压根没写\"逐字相同, 且不报错。\n *\n * 判据只认这条错误**自己**产生的两个不变量: `name === 'StreamError'` 与\n * `errorCode` 是字符串 (构造函数恒赋值, 缺 code 时为 `''`)。`HTTPError` 也有可选的\n * `errorCode`, 但 name 不同, 因此不会被误收。\n */\nexport function isStreamError(err: unknown): err is StreamError {\n if (err == null || typeof err !== 'object') return false;\n const e = err as { name?: unknown; errorCode?: unknown };\n return e.name === 'StreamError' && typeof e.errorCode === 'string';\n}\n\nexport type RequestDisposition = 'not_accepted' | 'accepted' | 'unknown';\nexport type FaultDomain = 'user_auth' | 'caller_credentials' | 'account_quota' |\n 'account_permission' | 'provider' | 'gateway' | 'transport' | 'stream_ticket' | 'protocol';\n\nexport interface GatewayErrorContract {\n errorContractVersion: 1;\n faultDomain: FaultDomain;\n errorCode: string;\n transportRequestId: string | null;\n consumeRequestId: string | null;\n providerRequestId: string | null;\n requestDisposition: RequestDisposition;\n retryable: boolean;\n}\n\nconst gatewayFaultDomains: readonly FaultDomain[] = [\n 'user_auth', 'caller_credentials', 'account_quota', 'account_permission',\n 'provider', 'gateway', 'transport', 'stream_ticket', 'protocol',\n];\n\nfunction nullableID(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\nfunction decodeGatewayErrorContract(value: unknown): GatewayErrorContract | null {\n if (value == null || typeof value !== 'object') return null;\n const v = value as Record<string, unknown>;\n const version = v.errorContractVersion;\n const domain = v.faultDomain;\n const disposition = v.requestDisposition;\n const errorCode = v.errorCode;\n if (version !== 1 || typeof domain !== 'string' || !gatewayFaultDomains.includes(domain as FaultDomain)) return null;\n if (typeof errorCode !== 'string' || errorCode.length === 0) return null;\n if (disposition !== 'not_accepted' && disposition !== 'accepted' && disposition !== 'unknown') return null;\n if (!nullableID(v.transportRequestId) || !nullableID(v.consumeRequestId) || !nullableID(v.providerRequestId)) return null;\n if (typeof v.retryable !== 'boolean') return null;\n return { errorContractVersion: 1, faultDomain: domain as FaultDomain, errorCode,\n transportRequestId: v.transportRequestId, consumeRequestId: v.consumeRequestId,\n providerRequestId: v.providerRequestId, requestDisposition: disposition, retryable: v.retryable };\n}\n\n/** Read the closed v1 gateway error contract without exposing the raw response body. */\nexport function readGatewayErrorContract(error: unknown): GatewayErrorContract | null {\n const direct = decodeGatewayErrorContract(error);\n if (direct) return direct;\n if (error == null || typeof error !== 'object') return null;\n const e = error as { response?: { data?: unknown }; body?: unknown };\n const axios = decodeGatewayErrorContract(e.response?.data);\n if (axios) return axios;\n if (typeof e.body === 'string') {\n try { return decodeGatewayErrorContract(JSON.parse(e.body)); } catch { return null; }\n }\n return null;\n}\n\n/** True only for an HTTP 401 carrying the exact v1 rejected user-token contract. */\nexport function isUserAccessTokenRejected(error: unknown): boolean {\n if (error == null || typeof error !== 'object') return false;\n const e = error as { statusCode?: unknown; response?: { status?: unknown } };\n const status = e.statusCode ?? e.response?.status;\n const contract = readGatewayErrorContract(error);\n return status === 401 && contract?.faultDomain === 'user_auth' &&\n contract.errorCode === 'USER_ACCESS_TOKEN_INVALID' && contract.requestDisposition === 'not_accepted';\n}\n\n// =============================================================================\n// 窗口限额 (WINDOW_LIMIT_EXCEEDED) 识别\n// =============================================================================\n\n/** HTTP 路径窗口限额机器码 — 响应体顶层 errorCode / message 文案子串 (后端契约冻结值)。 */\nconst windowLimitErrorCode = 'WINDOW_LIMIT_EXCEEDED';\n/** 流式路径窗口限额 code — agent-run SSE error 事件用小写蛇形 (与 HTTP errorCode 大小写不同)。 */\nconst windowLimitStreamCode = 'window_limit_exceeded';\n\n/**\n * 判断错误是否为窗口限额拒绝 (5 小时 / 7 天滚动窗口 credit 用量达上限)。\n * 结构化 errorCode 优先, message/body 子串防御兜底 (后端部署版本错位期:\n * 旧网关只在 message 文案里带 \"(WINDOW_LIMIT_EXCEEDED)\" 而无顶层 errorCode)。\n *\n * 仅识别 HTTP 非 2xx 路径抛出的 HTTPError (/chat OpenAI 网关形态与 /anthropic\n * Anthropic 形态皆可)。流式场景 (agent-run SSE error 事件 / managed-model\n * failed 事件) 的 code 是小写 \"window_limit_exceeded\" 且产物不是 HTTPError,\n * 用伴生函数 isWindowLimitStreamError 判断。\n */\nexport function isWindowLimitError(err: unknown): err is HTTPError {\n if (!(err instanceof HTTPError)) return false;\n if (err.errorCode === windowLimitErrorCode) return true;\n // 防御兜底: 旧版后端无顶层 errorCode, 机器码只存活在 message 文案 / body 原串。\n return err.message.includes(windowLimitErrorCode) || err.body.includes(windowLimitErrorCode);\n}\n\n/**\n * isWindowLimitError 的流式伴生判断 — 面向 SSE error 事件产物\n * (StreamError / AgentRunStreamError / 裸 AgentRunErrorPayload) 的结构化鸭子判断。\n *\n * 入参形态跨三种且互无公共基类, 故返回 boolean 而非 type guard:\n * `code === \"window_limit_exceeded\"` 优先; message / userMessage / rawError\n * 含 \"WINDOW_LIMIT_EXCEEDED\" 子串防御兜底 (Anthropic 形态流错误事件只有\n * rate_limit_error type + 文案内机器码的部署错位期)。\n */\nexport function isWindowLimitStreamError(err: unknown): boolean {\n if (err == null || typeof err !== 'object') return false;\n const e = err as { code?: unknown; message?: unknown; userMessage?: unknown; rawError?: unknown };\n if (e.code === windowLimitStreamCode) return true;\n // 防御兜底 (后端部署版本错位期): 机器码只出现在文案 / 原始错误串。\n for (const field of [e.message, e.userMessage, e.rawError]) {\n if (typeof field === 'string' && field.includes(windowLimitErrorCode)) return true;\n }\n return false;\n}\n\n/**\n * [W1 2026-07-11 软限额] 判断错误是否为【可豁免】的窗口限额拒绝 (HTTP 路径) —\n * 即 5 小时软限额严格模式: 用户此前把「继续」开关关成严格模式, 到达 5h 被 429,\n * 但可通过重新开启继续 (setWindowContinuePreference(true)) 后重发放行。\n *\n * = isWindowLimitError(err) && err.windowOverridable === true。\n * 周窗 / 显式禁止 / 软限灰度关 / 老网关 (windowOverridable 缺失) 均返回 false (硬等待)。\n * 客户端据此决定是否展示「继续任务 / 重新开启」入口 (vs 纯「等待刷新」)。\n */\nexport function isContinuableWindowLimitError(err: unknown): err is HTTPError {\n return isWindowLimitError(err) && err.windowOverridable === true;\n}\n\n/**\n * [W1 2026-07-11 软限额] 从流式窗口限额 error 事件产物 (StreamError / AgentRunStreamError /\n * 裸 payload) 结构化读取窗口细节 — 供 crabcode 等消费端区分 5h 软限 (可继续) vs 周窗硬限。\n *\n * 非窗口限额错误返回 null; 是则返回 { windowKind, windowResetAt, windowOverridable } (缺字段为 undefined)。\n * overridable=true ⇒ 5h 严格模式可续跑; 缺失/false ⇒ 硬等待。\n */\nexport function getWindowLimitStreamDetails(\n err: unknown,\n): { windowKind?: 'FIVE_HOUR' | 'WEEKLY'; windowResetAt?: string; windowOverridable?: boolean } | null {\n if (!isWindowLimitStreamError(err)) return null;\n const e = (err ?? {}) as { windowKind?: unknown; windowResetAt?: unknown; windowOverridable?: unknown };\n const kind = e.windowKind === 'FIVE_HOUR' || e.windowKind === 'WEEKLY' ? e.windowKind : undefined;\n return {\n windowKind: kind,\n windowResetAt: typeof e.windowResetAt === 'string' ? e.windowResetAt : undefined,\n windowOverridable: typeof e.windowOverridable === 'boolean' ? e.windowOverridable : undefined,\n };\n}\n","// adapters/openai.ts — 端口自 acosmi-sdk-go/adapter_openai.go (535 行)\n//\n// 用于所有非 Anthropic 厂商 (DeepSeek, DashScope, Zhipu, Moonshot, VolcEngine 等)\n// SDK 只做格式转换, 厂商特定参数由 Nexus Gateway per-provider adapter 处理\n//\n// 关键区别:\n// - 不注入 Anthropic betas\n// - 端点后缀为 /chat (非 /anthropic)\n// - 流式使用 [DONE] 标记结束 (非 message_stop)\n// - 响应为 OpenAI choices 格式\n\nimport { type AnthropicContentBlock, type AnthropicResponse } from '../wire-anthropic';\nimport {\n type OpenAIChatResponse,\n type OpenAIStreamChunk,\n type OpenAIStreamDelta,\n} from '../wire-openai';\nimport {\n type ChatContentBlock,\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHigh,\n ThinkingMax,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { ProviderFormat, type ProviderAdapter } from './format';\n\n/** 实现 ProviderAdapter, 用于所有非 Anthropic 厂商 */\nexport class OpenAIAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.OpenAI;\n }\n\n endpointSuffix(): string {\n return '/chat';\n }\n\n /**\n * 构建 OpenAI 兼容格式请求体\n * 不注入 Anthropic betas, 扩展字段 (thinking/effort/speed) 以通用 JSON 传递\n */\n buildRequestBody(_caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // ── 消息: 直接透传 (Gateway 负责最终转换) ──\n if (req.rawMessages != null) {\n body['messages'] = req.rawMessages;\n } else if ((req.messages?.length ?? 0) > 0) {\n body['messages'] = req.messages;\n }\n\n body['stream'] = req.stream === true;\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n\n // ── System prompt: 透传给 Gateway ──\n if (req.system != null) {\n body['system'] = req.system;\n }\n\n // ── Temperature ──\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Tools: 透传原始格式, Gateway adapter 负责格式转换 ──\n if (req.tools != null) {\n body['tools'] = req.tools;\n }\n\n // ── 扩展字段 (v0.13.0: 按 OpenAI wire format 直接翻译) ──\n\n // Thinking / Effort → reasoning_effort\n // OpenAI 系只有顶层 `reasoning_effort: \"low\"|\"medium\"|\"high\"`, 无 thinking block;\n // 能接收到 reasoning_content (GLM/DeepSeek) 作为响应, 但请求侧只能控制级别。\n const eff = resolveOpenAIReasoningEffort(req, _caps.supports_max_effort);\n if (eff !== '') {\n body['reasoning_effort'] = eff;\n }\n if (_caps.supports_thinking && req.thinking?.level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n }\n\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n\n // outputConfig → response_format\n // Anthropic 心智模型通过 system prompt + prefill 实现 JSON 模式;\n // OpenAI 有顶层 response_format, SDK 直接翻译。\n const rf = resolveOpenAIResponseFormat(req);\n if (rf) {\n body['response_format'] = rf;\n }\n\n if (req.metadata) {\n body['metadata'] = req.metadata;\n }\n\n // parallel_tool_calls 是 OpenAI 原生字段, 无歧义直接写\n if (req.parallelToolCalls != null) {\n body['parallel_tool_calls'] = req.parallelToolCalls;\n }\n\n // ── 不注入 Anthropic Betas ──\n // OpenAI 格式不使用 anthropic-beta header\n\n // ── 透传 extraBody ──\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n body[k] = v;\n }\n }\n\n // ── v1.6.0: endUserId → 顶层 body[\"user_id\"] (OpenAI wire 形态) ──\n // 优先级最高: 在 extraBody 之后写入, 即便 caller 通过 extraBody[\"user_id\"] 自填,\n // 显式 endUserId 仍胜出 (单一真相, 避免双写歧义)。\n // 网关 sanitizer 仍会做最终校验与权限决断, 此处仅负责字段位置正确。\n if (req.endUserId && req.endUserId !== '') {\n body['user_id'] = req.endUserId;\n }\n\n // ── 流式选项 ──\n if (req.stream === true) {\n body['stream_options'] = { include_usage: true };\n }\n\n return body;\n }\n\n /**\n * 解析 OpenAI 格式同步响应为 ChatResponse\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 OpenAI JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n let raw = bodyStr;\n try {\n const wrapper = JSON.parse(bodyStr) as { code?: number; message?: string; data?: unknown };\n if (wrapper.data != null && wrapper.data !== null) {\n if ((wrapper.code ?? 0) !== 0) {\n throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? '');\n }\n raw = JSON.stringify(wrapper.data);\n }\n } catch (e) {\n if (e instanceof BusinessError) throw e;\n }\n\n let oaiResp: OpenAIChatResponse;\n try {\n oaiResp = JSON.parse(raw) as OpenAIChatResponse;\n } catch (e) {\n throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return convertOpenAIToChatResponse(oaiResp);\n }\n\n /**\n * 解析 OpenAI SSE 行\n * [DONE] 标记流结束\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean } {\n if (data === '[DONE]') {\n return { event: { event: '', data: '' }, done: true };\n }\n\n // 校验 chunk 是合法 JSON (与 Go 侧行为对齐)\n try {\n JSON.parse(data);\n } catch (e) {\n throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 把 Anthropic 心智模型的 thinking/effort 翻译成 OpenAI `reasoning_effort` 字段值\n * 返回空串表示不设置\n */\nexport function resolveOpenAIReasoningEffort(req: ChatRequest, supportsMax = false): string {\n // effort 优先级最高, 因为它本身就是通用级别语义\n if (req.effort && req.effort.level !== '') {\n switch (req.effort.level) {\n case 'low':\n case 'medium':\n case 'high':\n case 'xhigh':\n return req.effort.level;\n case 'max':\n // OpenAI 无 max 级别, 等价最深 = high\n return supportsMax ? 'max' : 'high';\n }\n }\n // thinking.level 次之\n if (req.thinking) {\n switch (req.thinking.level) {\n case 'low':\n case 'medium':\n case 'xhigh':\n return req.thinking.level;\n case ThinkingHigh:\n return 'high';\n case ThinkingMax:\n return supportsMax ? 'max' : 'high';\n case ThinkingOff:\n return '';\n }\n }\n return '';\n}\n\n/**\n * 把 outputConfig 翻译成 OpenAI response_format\n * 返回 null 表示不设置\n */\nexport function resolveOpenAIResponseFormat(req: ChatRequest): Record<string, unknown> | null {\n if (!req.outputConfig) return null;\n switch (req.outputConfig.format) {\n case 'json_schema': {\n // OpenAI schema 形态: {type:\"json_schema\", json_schema:{schema:{...},strict:true}}\n const js: Record<string, unknown> = {};\n if (req.outputConfig.schema != null) {\n js['schema'] = req.outputConfig.schema;\n }\n js['strict'] = true;\n return {\n type: 'json_schema',\n json_schema: js,\n };\n }\n case 'json_object':\n return { type: 'json_object' };\n case '':\n case undefined:\n return null;\n default:\n // 未知 format, 原样透传, 交 Gateway 处理\n return { type: req.outputConfig.format };\n }\n}\n\n/** 将 OpenAI 同步响应转换为 ChatResponse */\nfunction convertOpenAIToChatResponse(oai: OpenAIChatResponse): ChatResponse {\n const resp: ChatResponse = {\n id: oai.id,\n type: 'message',\n model: oai.model,\n role: 'assistant',\n content: [],\n stop_reason: '',\n // [W-SDK-OPENAI-PARITY] 缺 usage 的响应按 0 计数, 而不是让 `oai.usage.prompt_tokens`\n // 抛 TypeError 把整条响应打死 —— 上游不计量是常见形态, 正文照样是有效的。\n // 与 Go 侧同名函数逐字同语义 (那里 Usage 是值类型, 缺席即零值)。\n usage: {\n input_tokens: oai.usage?.prompt_tokens ?? 0,\n output_tokens: oai.usage?.completion_tokens ?? 0,\n },\n tokenRemaining: -1,\n callRemaining: -1,\n modelTokenRemaining: -1,\n modelTokenRemainingETU: -1,\n };\n\n // [W-SDK-OPENAI-PARITY] 缺 choices 的响应按空数组处理, 而不是让 `oai.choices.length`\n // 抛 TypeError —— 那会连 id / model / usage 这些确实到手的字段一起丢掉。判据用\n // Array.isArray 而不是 `?.length`, 与同文件流式路径的 choices 守卫取同一个表达式。\n if (Array.isArray(oai.choices) && oai.choices.length > 0) {\n const choice = oai.choices[0]!;\n\n // finish_reason 映射\n switch (choice.finish_reason) {\n case 'stop':\n resp.stop_reason = 'end_turn';\n break;\n case 'tool_calls':\n resp.stop_reason = 'tool_use';\n break;\n case 'length':\n resp.stop_reason = 'max_tokens';\n break;\n default:\n resp.stop_reason = choice.finish_reason;\n }\n\n // thinking content → thinking block\n if (choice.message.reasoning_content && choice.message.reasoning_content !== '') {\n resp.content.push({\n type: 'thinking',\n thinking: choice.message.reasoning_content,\n } as ChatContentBlock);\n }\n\n // text content → text block\n if (choice.message.content && choice.message.content !== '') {\n resp.content.push({\n type: 'text',\n text: choice.message.content,\n } as ChatContentBlock);\n }\n\n // tool_calls → tool_use blocks\n for (const tc of choice.message.tool_calls ?? []) {\n resp.content.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n // Anthropic 协议 input 是 raw JSON value; OpenAI 给的 arguments 是 string,\n // Go 侧用 json.RawMessage(arguments) 直透(原始字节). TS 我们尝试解析:\n input: tryParseJSON(tc.function.arguments),\n } as ChatContentBlock);\n }\n }\n\n return resp;\n}\n\nfunction tryParseJSON(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return s;\n }\n}\n\n// ============================================================================\n// OpenAI → Anthropic 响应转换 (供 ChatMessages 使用)\n// ============================================================================\n\n/**\n * 解析 OpenAI 格式响应并转换为 AnthropicResponse\n * 用于 chatMessagesOpenAI 方法, 使 Hub 层无需感知 provider 差异\n */\nexport function parseOpenAIResponseToAnthropic(raw: string | Uint8Array): AnthropicResponse {\n const rawStr = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n\n let data = rawStr;\n try {\n const wrapper = JSON.parse(rawStr) as { code?: number; message?: string; data?: unknown };\n if (wrapper.data != null && wrapper.data !== null) {\n if ((wrapper.code ?? 0) !== 0) {\n throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? '');\n }\n data = JSON.stringify(wrapper.data);\n }\n } catch (e) {\n if (e instanceof BusinessError) throw e;\n }\n\n let oaiResp: OpenAIChatResponse;\n try {\n oaiResp = JSON.parse(data) as OpenAIChatResponse;\n } catch (e) {\n throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const resp: AnthropicResponse = {\n id: oaiResp.id,\n type: 'message',\n role: 'assistant',\n content: [],\n model: oaiResp.model,\n stop_reason: '',\n // [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 usage 按 0 计数, 不抛。\n // 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。\n usage: {\n input_tokens: oaiResp.usage?.prompt_tokens ?? 0,\n output_tokens: oaiResp.usage?.completion_tokens ?? 0,\n },\n };\n\n // [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 choices 按空数组处理, 不抛。\n // 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。\n if (Array.isArray(oaiResp.choices) && oaiResp.choices.length > 0) {\n const choice = oaiResp.choices[0]!;\n\n switch (choice.finish_reason) {\n case 'stop':\n resp.stop_reason = 'end_turn';\n break;\n case 'tool_calls':\n resp.stop_reason = 'tool_use';\n break;\n case 'length':\n resp.stop_reason = 'max_tokens';\n break;\n default:\n resp.stop_reason = choice.finish_reason;\n }\n\n if (choice.message.reasoning_content && choice.message.reasoning_content !== '') {\n resp.content.push({\n type: 'thinking',\n thinking: choice.message.reasoning_content,\n } as AnthropicContentBlock);\n }\n\n if (choice.message.content && choice.message.content !== '') {\n resp.content.push({\n type: 'text',\n text: choice.message.content,\n } as AnthropicContentBlock);\n }\n\n for (const tc of choice.message.tool_calls ?? []) {\n resp.content.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n input: tryParseJSON(tc.function.arguments),\n } as AnthropicContentBlock);\n }\n }\n\n return resp;\n}\n\n// ============================================================================\n// OpenAI SSE → Anthropic 事件转换器 (供 chatMessagesStreamInternal 使用)\n// ============================================================================\n\n/** 收尾 message_delta 上的 usage。只含与非流式路径相同的两个键; 缺席的键表示上游没给, 不是 0。 */\ninterface StreamMessageUsage {\n input_tokens?: number;\n output_tokens?: number;\n}\n\n/**\n * [W-QUAD-CHAIN-20260914] 读出一帧流式 chunk 上的 usage 对象, 搬成收尾 message_delta 的 usage 形态。\n *\n * 字段映射与同文件非流式路径 (`convertOpenAIToChatResponse` / `parseOpenAIResponseToAnthropic`)\n * 逐字相同: 只搬 `prompt_tokens → input_tokens`、`completion_tokens → output_tokens`。\n * `cached_tokens` / `reasoning_tokens` 等明细刻意不映射, 也不做 `prompt_tokens − cached` 之类的\n * 净额换算 —— usage 的语义归一只住在网关, SDK 只做格式搬运。某个计数缺失或不是有限数时不写对应键。\n *\n * 帧上没有 usage 对象 (缺失 / null / 非对象) 返回 null: 调用方据此区分「这帧没带 usage」与\n * 「带了 usage 但计数都缺席」(后者返回空对象, 仍算见过 usage)。\n */\nfunction readStreamUsage(chunk: OpenAIStreamChunk): StreamMessageUsage | null {\n const raw: unknown = chunk.usage;\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n return null;\n }\n const { prompt_tokens: promptTokens, completion_tokens: completionTokens } = raw as {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n };\n const usage: StreamMessageUsage = {};\n if (typeof promptTokens === 'number' && Number.isFinite(promptTokens)) {\n usage.input_tokens = promptTokens;\n }\n if (typeof completionTokens === 'number' && Number.isFinite(completionTokens)) {\n usage.output_tokens = completionTokens;\n }\n return usage;\n}\n\n/**\n * 将 OpenAI SSE chunks 转换为 Anthropic 兼容的 StreamEvent\n * 有状态: 跨 chunk 追踪 block 索引\n */\nexport class OpenAIStreamConverter {\n private messageStarted = false;\n private thinkingStarted = false;\n private thinkingStopped = false;\n /** thinking block 打开时占用的 Anthropic block index — 关闭时必须用它, 不能用\n * 可能已被 text/tool 推进的 this.blockIndex (否则 content_block_stop 索引错配)。 */\n private thinkingBlockIndex = 0;\n private textStarted = false;\n /** OpenAI tool_call 键 → Anthropic block index。键正常是 `tc.index`;上游省略\n * index 时退化为 `id:<tool_call_id>`,两者都没有时沿用上一个键(见\n * {@link resolveToolKey})。 */\n private toolBlockIndex = new Map<string | number, number>();\n private blockIndex = 0;\n /** 每个 tool block 已发出的 `partial_json` 累积,用于识别「每片重发全量参数」\n * 的上游(见 tool_calls 分支的累计判别)。 */\n private toolArgsAccum = new Map<string | number, string>();\n /** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */\n private lastToolKey: string | number | null = null;\n /** 已发出 message_delta/message_stop,避免 finish_reason、usage 尾帧、`[DONE]` 与\n * {@link flush} 重复收口 —— 整条流恰好一个 message_stop。 */\n private messageClosed = false;\n /** 已为仍打开的块发出 content_block_stop。与 {@link messageClosed} 分开记:块在\n * finish_reason 帧上就关,message_delta/message_stop 却可能推迟到之后的帧。 */\n private blocksClosed = false;\n /** [W-QUAD-CHAIN-20260914] finish_reason 已到、但 message_delta/message_stop 因等待\n * usage 尾帧而推迟时记下的 stop_reason;没有推迟中的收尾时为 null。 */\n private pendingStopReason: string | null = null;\n /** [W-QUAD-CHAIN-20260914] 最近一次带 usage 对象的帧搬出的 usage(后到覆盖先到);\n * 整条流从未出现 usage 对象时为 null,收尾 message_delta 据此不写 usage 键。 */\n private usage: StreamMessageUsage | null = null;\n\n /**\n * 解析一条 tool_call delta 归属的块键。\n *\n * OpenAI 流式规范里 `index` 是必填,但兼容实现常有省略。此前这里直接用\n * `tc.index` 做 Map 键:两个都省略 index 的 tool_call 会共用键 `undefined`,\n * 于是只开一个块、两段参数拼进同一条 `partial_json` 流,产出 `{…}{…}` 这种\n * 必然非法的 JSON。这里按「index → id → 沿用上一个」三级降级,让至少一种\n * 稳定标识生效。\n */\n private resolveToolKey(tc: { index?: number; id?: string }): string | number {\n if (typeof tc.index === 'number' && Number.isFinite(tc.index)) {\n this.lastToolKey = tc.index;\n return tc.index;\n }\n if (typeof tc.id === 'string' && tc.id !== '') {\n const key = `id:${tc.id}`;\n this.lastToolKey = key;\n return key;\n }\n if (this.lastToolKey !== null) return this.lastToolKey;\n this.lastToolKey = 0;\n return 0;\n }\n\n /**\n * 关闭仍打开的 text / thinking / tool 块(整条流只关一次)。\n *\n * 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发\n * `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个\n * 永不闭合的 tool_use 块。\n *\n * [W-QUAD-CHAIN-20260914] 此前关块与 message_delta/message_stop 是同一个方法里同一时刻\n * 的事;usage 尾帧要求收尾推迟,于是拆成本方法与 {@link emitMessageEnd} 两段,各自防重。\n */\n private closeContentBlocks(events: StreamEvent[]): void {\n if (this.blocksClosed) return;\n this.blocksClosed = true;\n\n if (this.textStarted) {\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: this.blockIndex }),\n });\n this.textStarted = false;\n } else if (this.thinkingStarted && !this.thinkingStopped) {\n // 用 thinkingBlockIndex 关 — thinking-only 流末尾若有 tool block 推进过\n // blockIndex, 这里仍要用 thinking 自己打开时记下的 index, 否则错配。\n this.thinkingStopped = true;\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: this.thinkingBlockIndex }),\n });\n }\n for (const idx of this.toolBlockIndex.values()) {\n events.push({\n event: 'content_block_stop',\n data: JSON.stringify({ type: 'content_block_stop', index: idx }),\n });\n }\n }\n\n /**\n * 发出 message_delta + message_stop,整条流只发一次;同时清掉推迟中的收尾。\n *\n * 见过 usage 对象就把它放进 message_delta:usage 必须出现在唯一的 message_stop 之前,\n * message_stop 之后下游已无处安放用量。整条流从未出现 usage 对象时不写 usage 键 ——\n * 缺席表示「上游没给」,不是 0。\n */\n private emitMessageEnd(events: StreamEvent[], stopReason: string): void {\n if (this.messageClosed) return;\n this.messageClosed = true;\n this.pendingStopReason = null;\n\n const messageDelta: {\n type: 'message_delta';\n delta: { stop_reason: string };\n usage?: StreamMessageUsage;\n } = { type: 'message_delta', delta: { stop_reason: stopReason } };\n if (this.usage !== null) {\n messageDelta.usage = this.usage;\n }\n events.push({ event: 'message_delta', data: JSON.stringify(messageDelta) });\n events.push({\n event: 'message_stop',\n data: JSON.stringify({ type: 'message_stop' }),\n });\n }\n\n /**\n * 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent\n * 返回 { events, done }\n */\n convert(data: string): { events: StreamEvent[]; done: boolean } {\n if (data === '[DONE]') {\n // 上游可能在 `[DONE]` 之前不发 finish_reason(部分兼容实现、以及被中断的\n // 流)。此前这里直接返回空事件,已打开的块永不闭合,下游只能靠超时收场。\n const events: StreamEvent[] = [];\n if (this.pendingStopReason !== null) {\n // [W-QUAD-CHAIN-20260914] finish_reason 已到而 usage 尾帧始终没来:流已声明结束,\n // 推迟的收尾不能再等(块已在 finish_reason 帧上关过)。\n this.emitMessageEnd(events, this.pendingStopReason);\n } else if (this.messageStarted) {\n this.closeContentBlocks(events);\n this.emitMessageEnd(events, 'end_turn');\n }\n return { events, done: true };\n }\n\n let chunk: OpenAIStreamChunk;\n try {\n chunk = JSON.parse(data);\n } catch (e) {\n throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const events: StreamEvent[] = [];\n // [W-QUAD-CHAIN-20260914] usage 必须在「没有 choices 就返回」之前读: include_usage 的尾帧\n // 恰恰是 `{\"choices\":[],\"usage\":{...}}`。此前先判 choices 再返回, 尾帧整帧丢弃,\n // OpenAI 线每个回合的 usage 恒为 0。\n const frameUsage = readStreamUsage(chunk);\n if (frameUsage !== null) {\n this.usage = frameUsage; // 后到覆盖先到\n }\n\n // [W-QUAD-CHAIN-20260914] 纵深防御: 同一条流里可能出现**没有 choices 的 data 帧**\n // (网关错误契约帧、usage 尾帧)。此前这里直接解引用, 任何这类帧都会变成一句与真因无关的\n // TypeError, 把诊断信息彻底摧毁。正确的错误分流在 client.ts 的 SSE 事件名判断处; 这里\n // 只负责「不把自己炸掉」, 以及 usage 尾帧到达时补发推迟中的收尾。不带 usage 的这类帧零事件。\n if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {\n if (frameUsage !== null && this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n return { events, done: false };\n }\n const choice = chunk.choices[0]!;\n // [W-SDK-OPENAI-PARITY] 与上面的 choices 守卫同一档纵深防御: 兼容实现会发\n // `{\"choices\":[{\"index\":0}]}` 这种空心 choice (只声明「第 0 路还在」而本帧无增量)。\n // 此前三处直接读 `choice.delta.*`, 第一处就是 `reasoning_content` —— 一个 TypeError\n // 撕开整条 for-await 链, 整个回合失败。缺席按空 delta 处理: 三个分支各自的空值判断\n // 会让它零事件通过, finish_reason / usage 仍照常处理。\n const delta: OpenAIStreamDelta = choice.delta ?? {};\n\n // 首个 chunk: 发送 message_start\n if (!this.messageStarted) {\n this.messageStarted = true;\n const msgJSON = JSON.stringify({\n type: 'message_start',\n message: {\n id: chunk.id,\n type: 'message',\n role: 'assistant',\n content: [],\n model: '',\n },\n });\n events.push({ event: 'message_start', data: msgJSON });\n }\n\n // thinking delta (reasoning_content)\n if (delta.reasoning_content && delta.reasoning_content !== '') {\n if (!this.thinkingStarted) {\n // [W-SDK-OPENAI-PARITY] 关闭仍打开的 text 块 (镜像 text / tool_calls 两个分支)。\n // chunk 顺序 content → reasoning_content 时, text 块仍开着且 blockIndex 未推进;\n // 不在此关闭并递增, thinking 的 content_block_start 会与 text 撞同一个 index 0,\n // 而 closeContentBlocks 的 `textStarted / else if thinking` 分支只关得掉其中一个 ——\n // 另一个块永不闭合。三个分支必须两两互关, 才有「同一时刻至多一个非 tool 块打开」这条不变量。\n if (this.textStarted) {\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.blockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n this.textStarted = false;\n }\n this.thinkingStarted = true;\n this.thinkingBlockIndex = this.blockIndex; // 记下 thinking 占用的 index\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: { type: 'thinking', thinking: '' },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n }\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: this.thinkingBlockIndex,\n delta: { type: 'thinking_delta', thinking: delta.reasoning_content },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n\n // text delta (content)\n if (delta.content && delta.content !== '') {\n // 关闭 thinking block (如果有) — 用 thinkingBlockIndex 关, 不用可能已推进的 blockIndex\n if (this.thinkingStarted && !this.thinkingStopped) {\n this.thinkingStopped = true;\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.thinkingBlockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n }\n if (!this.textStarted) {\n this.textStarted = true;\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: { type: 'text', text: '' },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n }\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: this.blockIndex,\n delta: { type: 'text_delta', text: delta.content },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n\n // tool_calls delta\n for (const tc of delta.tool_calls ?? []) {\n // 上游给的 tool_call 未必带 `function`:OpenAI 流式规范允许后续增量只带\n // `{index}`。此前这里直接读 `tc.function.name` / `tc.function.arguments`,\n // 那种上游会抛 TypeError 撕开整条 for-await 链,整个回合失败。\n const fn = tc.function as { name?: string; arguments?: unknown } | undefined;\n const toolKey = this.resolveToolKey(tc);\n if (!this.toolBlockIndex.has(toolKey)) {\n // 关闭仍打开的 thinking block (镜像 text 分支): chunk 顺序 reasoning_content →\n // tool_calls (中间无 content text delta) 时, thinking 仍开着且 blockIndex 未推进,\n // 若不在此关闭并递增, tool block 会与 thinking 撞 index 0。用 thinkingBlockIndex 关。\n if (this.thinkingStarted && !this.thinkingStopped) {\n this.thinkingStopped = true;\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.thinkingBlockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n }\n // 关闭 text block (如果有)\n if (this.textStarted) {\n const stopJSON = JSON.stringify({\n type: 'content_block_stop',\n index: this.blockIndex,\n });\n events.push({ event: 'content_block_stop', data: stopJSON });\n this.blockIndex++;\n this.textStarted = false;\n }\n this.toolBlockIndex.set(toolKey, this.blockIndex);\n const blockJSON = JSON.stringify({\n type: 'content_block_start',\n index: this.blockIndex,\n content_block: {\n type: 'tool_use',\n id: tc.id,\n name: fn?.name,\n input: {},\n },\n });\n events.push({ event: 'content_block_start', data: blockJSON });\n this.blockIndex++; // 递增, 为下一个 tool_call block 预留索引\n }\n if (fn?.arguments !== undefined && fn.arguments !== null && fn.arguments !== '') {\n // `arguments` 按 OpenAI 规范是 string,但部分兼容实现直接给对象。此前原样\n // 塞进 `partial_json`,下游 `input += delta.partial_json` 会拼出\n // \"[object Object]\"。这里统一成字符串,语义不变。\n const rawArgs =\n typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments);\n\n // 累计 vs 增量判别:部分上游每个 chunk 重发**全量**参数而非增量。此前无条件\n // 累加,会拼出 `{\"a\":1}{\"a\":1}{\"a\":1}` 这种必然非法的 JSON。判据取最保守的\n // 一种:新片严格以已累积内容为前缀**且**更长时,才认为上游在重发全量,只发\n // 差值。真增量流里某一片恰好等于「此前全部内容的延长」概率可忽略。\n const accum = this.toolArgsAccum.get(toolKey) ?? '';\n let emit = rawArgs;\n if (accum !== '' && rawArgs.length > accum.length && rawArgs.startsWith(accum)) {\n emit = rawArgs.slice(accum.length);\n this.toolArgsAccum.set(toolKey, rawArgs);\n } else {\n this.toolArgsAccum.set(toolKey, accum + rawArgs);\n }\n\n if (emit !== '') {\n const idx = this.toolBlockIndex.get(toolKey)!;\n const deltaJSON = JSON.stringify({\n type: 'content_block_delta',\n index: idx,\n delta: {\n type: 'input_json_delta',\n partial_json: emit,\n },\n });\n events.push({ event: 'content_block_delta', data: deltaJSON });\n }\n }\n }\n\n // finish_reason: 关闭所有 block; message_delta + message_stop 视 usage 是否已到, 立即发或推迟\n if (choice.finish_reason != null && choice.finish_reason !== '') {\n // stop_reason 映射。`content_filter` 等未列出的值刻意保持原样透传而不是压成\n // `end_turn` —— 把内容审查拦截伪装成正常结束会让下游无从分辨。同文件的\n // `convertOpenAIToChatResponse` 一直是原样透传,这里跟它对齐。\n let stopReason: string;\n switch (choice.finish_reason) {\n case 'tool_calls':\n stopReason = 'tool_use';\n break;\n case 'length':\n stopReason = 'max_tokens';\n break;\n case 'stop':\n stopReason = 'end_turn';\n break;\n default:\n stopReason = choice.finish_reason;\n }\n // 只认第一个 finish_reason: 收尾已发出或已推迟时, 后到的 finish_reason 不改写 stop_reason。\n if (!this.messageClosed && this.pendingStopReason === null) {\n this.closeContentBlocks(events);\n if (this.usage !== null) {\n // usage 已在本帧或更早的帧到达: 一次发出带 usage 的收尾\n this.emitMessageEnd(events, stopReason);\n } else {\n // [W-QUAD-CHAIN-20260914] include_usage 的标准帧序里 finish_reason 帧先于 usage 尾帧。\n // 此刻收尾, message_delta 只能不带 usage, 之后到的 usage 已无处安放 —— 于是推迟到\n // usage 帧 / `[DONE]` / EOF (flush) 三者先到者。块照常在这一帧关闭。\n this.pendingStopReason = stopReason;\n }\n }\n }\n\n // [W-QUAD-CHAIN-20260914] 推迟收尾期间到达的带 choices 帧若携带 usage, 同样立即补发。\n if (frameUsage !== null && this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n\n return { events, done: false };\n }\n\n /**\n * 流在**没有** `[DONE]` 的情况下结束 (EOF) 时, 由驱动方在读循环结束后调用一次。\n *\n * 只补发「finish_reason 已到、仅因等待 usage 尾帧而推迟」的 message_delta + message_stop。\n * 从未收到 finish_reason 的流是被截断的流, 这里刻意**不**替它伪造正常结束 —— 一个\n * `end_turn` 的 message_stop 会把被截断的回答当成完整回答交给下游。\n * 已经收口 (usage 帧 / `[DONE]` / 上一次 flush) 后再调用返回空数组, 不会发出第二个 message_stop。\n */\n flush(): StreamEvent[] {\n const events: StreamEvent[] = [];\n if (this.pendingStopReason !== null) {\n this.emitMessageEnd(events, this.pendingStopReason);\n }\n return events;\n }\n}\n\nexport function newOpenAIStreamConverter(): OpenAIStreamConverter {\n return new OpenAIStreamConverter();\n}\n"]}
|
|
@@ -357,6 +357,28 @@ interface QuotaSummarySubscriptionPool {
|
|
|
357
357
|
expiresAt?: string;
|
|
358
358
|
/** 网关下发的单位标识 */
|
|
359
359
|
unit: string;
|
|
360
|
+
/**
|
|
361
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 池里由**加油包**贡献的剩余额度 (微 Credits)。
|
|
362
|
+
*
|
|
363
|
+
* 网关由 alive 的 COMMERCIAL 桶中 `sourceType === 'booster_pack'` 的那些行求和派生。
|
|
364
|
+
*
|
|
365
|
+
* **缺席 = 老网关, 或此刻一个加油包都没有 —— 两种都不可当 0 读。** 展示端的合同是
|
|
366
|
+
* 「三个 booster* 字段齐全才渲染副行」, 少一个就整条不画; 把缺席当 0 渲染, 等于在老网关上
|
|
367
|
+
* 向用户断言「你没有加油包」, 而真相是客户端不知道。
|
|
368
|
+
*/
|
|
369
|
+
boosterRemaining?: number;
|
|
370
|
+
/**
|
|
371
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包桶的**个数** (一行 = 一个加油包)。
|
|
372
|
+
* 缺席语义同 boosterRemaining。
|
|
373
|
+
*/
|
|
374
|
+
boosterCount?: number;
|
|
375
|
+
/**
|
|
376
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包中最早的到期时刻 (ISO-8601)。
|
|
377
|
+
*
|
|
378
|
+
* 缺席有第三种可能: 加油包确实存在、但全是永久桶 (不参与到期选举) —— 与 expiresAt 同口径。
|
|
379
|
+
* 所以它缺席时仍可能有 boosterRemaining / boosterCount, 展示端据此决定要不要说「最早 X 到期」。
|
|
380
|
+
*/
|
|
381
|
+
boosterNextExpiresAt?: string;
|
|
360
382
|
}
|
|
361
383
|
/**
|
|
362
384
|
* GET /api/v4/entitlements/quota-summary 返回体, 调用 client.getQuotaSummary 获取.
|
|
@@ -357,6 +357,28 @@ interface QuotaSummarySubscriptionPool {
|
|
|
357
357
|
expiresAt?: string;
|
|
358
358
|
/** 网关下发的单位标识 */
|
|
359
359
|
unit: string;
|
|
360
|
+
/**
|
|
361
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 池里由**加油包**贡献的剩余额度 (微 Credits)。
|
|
362
|
+
*
|
|
363
|
+
* 网关由 alive 的 COMMERCIAL 桶中 `sourceType === 'booster_pack'` 的那些行求和派生。
|
|
364
|
+
*
|
|
365
|
+
* **缺席 = 老网关, 或此刻一个加油包都没有 —— 两种都不可当 0 读。** 展示端的合同是
|
|
366
|
+
* 「三个 booster* 字段齐全才渲染副行」, 少一个就整条不画; 把缺席当 0 渲染, 等于在老网关上
|
|
367
|
+
* 向用户断言「你没有加油包」, 而真相是客户端不知道。
|
|
368
|
+
*/
|
|
369
|
+
boosterRemaining?: number;
|
|
370
|
+
/**
|
|
371
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包桶的**个数** (一行 = 一个加油包)。
|
|
372
|
+
* 缺席语义同 boosterRemaining。
|
|
373
|
+
*/
|
|
374
|
+
boosterCount?: number;
|
|
375
|
+
/**
|
|
376
|
+
* [W-RESET-CARD-WEEKLY-QUOTA-20260920 D-13] 上述 alive 加油包中最早的到期时刻 (ISO-8601)。
|
|
377
|
+
*
|
|
378
|
+
* 缺席有第三种可能: 加油包确实存在、但全是永久桶 (不参与到期选举) —— 与 expiresAt 同口径。
|
|
379
|
+
* 所以它缺席时仍可能有 boosterRemaining / boosterCount, 展示端据此决定要不要说「最早 X 到期」。
|
|
380
|
+
*/
|
|
381
|
+
boosterNextExpiresAt?: string;
|
|
360
382
|
}
|
|
361
383
|
/**
|
|
362
384
|
* GET /api/v4/entitlements/quota-summary 返回体, 调用 client.getQuotaSummary 获取.
|