@acosmi/sdk-ts 2.4.0 → 2.6.0
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 +77 -0
- package/README.md +40 -12
- package/dist/browser/index.mjs +293 -77
- package/dist/browser/index.mjs.map +1 -1
- package/dist/index.mjs +293 -77
- package/dist/index.mjs.map +1 -1
- package/dist/node/adapters/anthropic.cjs +13 -0
- 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 +13 -0
- package/dist/node/adapters/anthropic.mjs.map +1 -1
- package/dist/node/adapters/openai.cjs +17 -3
- 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 +17 -3
- package/dist/node/adapters/openai.mjs.map +1 -1
- package/dist/node/{index-CI7FM3xe.d.cts → index-BrbgHn0E.d.cts} +19 -1
- package/dist/node/{index-CI7FM3xe.d.ts → index-BrbgHn0E.d.ts} +19 -1
- package/dist/node/index.cjs +300 -76
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +254 -72
- package/dist/node/index.d.ts +254 -72
- package/dist/node/index.mjs +293 -77
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/{openai-xKHChcmG.d.cts → openai-CeM2k04b.d.cts} +4 -1
- package/dist/node/{openai-oeY7dGNW.d.ts → openai-DoPpzZmv.d.ts} +4 -1
- package/docs//345/274/200/345/217/221/344/270/216/345/217/221/345/270/203/346/211/213/345/206/214.md +27 -5
- package/package.json +1 -1
|
@@ -80,6 +80,13 @@ function uniqueMerge(base, extra) {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// src/models/adapters/anthropic.ts
|
|
83
|
+
var ANTHROPIC_SDK_MANAGED_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
84
|
+
"thinking",
|
|
85
|
+
"effort",
|
|
86
|
+
"max_tokens",
|
|
87
|
+
"temperature",
|
|
88
|
+
"betas"
|
|
89
|
+
]);
|
|
83
90
|
var AnthropicAdapter = class {
|
|
84
91
|
format() {
|
|
85
92
|
return 0 /* Anthropic */;
|
|
@@ -167,6 +174,12 @@ var AnthropicAdapter = class {
|
|
|
167
174
|
}
|
|
168
175
|
if (req.extraBody) {
|
|
169
176
|
for (const [k, v] of Object.entries(req.extraBody)) {
|
|
177
|
+
if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {
|
|
178
|
+
console.warn(
|
|
179
|
+
`acosmi-sdk: extraBody key "${k}" is SDK-managed and was ignored (use the dedicated request field instead).`
|
|
180
|
+
);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
170
183
|
body[k] = v;
|
|
171
184
|
}
|
|
172
185
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/betas.ts","../../../src/models/adapters/index.ts","../../../src/models/adapters/anthropic.ts"],"names":[],"mappings":"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","file":"anthropic.cjs","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/** 图片生成请求 (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/**\n * 模型可接收的用户输入模态 (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n */\nexport type InputModality = 'text' | 'image';\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 pricePerMTok?: number;\n isDefault?: boolean;\n contextWindow?: number;\n capabilities: ModelCapabilities;\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 * 当前用户在此模型上的桶余额聚合 (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 * 取值: 'text' | 'image' 的子集. 'image' 表示模型可直接接收 screenshot/image 输入.\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModality[];\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/**\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}\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 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/**\n * 从 StreamEvent 中解析搜索来源\n * 返回 null 表示该事件不是 sources 类型\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: { type?: string; sources?: WebSearchSource[]; session_id?: string };\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\n constructor(statusCode: number, opts: { type?: string; message?: string; retryAfter?: number; body?: string } = {}) {\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 }\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\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/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件解析得到。客户端可通过\n * instanceof 提取并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 */\n retryable: boolean;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean } = {}) {\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 ?? false;\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.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n }\n}\n","// betas.ts — 端口自 acosmi-sdk-go/betas.go\n//\n// Beta Header 常量 — 经联网验证的真实 Anthropic API beta 值。\n// 虚构/错误日期的 header 已剔除。\n\nimport type { ChatRequest, ModelCapabilities } from './types';\nimport { ThinkingOff } from './types';\n\n/** ISP: 交错思考 */\nconst betaInterleavedThinking = 'interleaved-thinking-2025-05-14';\n/** 1M 上下文 (retiring 2026-04-30) */\nconst betaContext1M = 'context-1m-2025-08-07';\n/** 上下文编辑 */\nconst betaContextManagement = 'context-management-2025-06-27';\n/** 结构化输出 */\nconst betaStructuredOutputs = 'structured-outputs-2025-11-13';\n/** Tool Search */\nconst betaAdvancedToolUse = 'advanced-tool-use-2025-11-20';\n/** Effort 控制 (Opus 4.5 需要, 4.6 stable) */\nconst betaEffort = 'effort-2025-11-24';\n/** 缓存作用域隔离 */\nconst betaPromptCachingScope = 'prompt-caching-scope-2026-01-05';\n/** 快速推理 (Opus 4.6) */\nconst betaFastMode = 'fast-mode-2026-02-01';\n/** 思考脱敏 */\nconst betaRedactThinking = 'redact-thinking-2026-02-12';\n/** 高效工具 (Claude 3.7, Claude 4 内置) */\nconst betaTokenEfficientTools = 'token-efficient-tools-2025-02-19';\n\n/** 根据模型能力和请求参数自动组装 beta header 列表 */\nexport function buildBetas(caps: ModelCapabilities, req: ChatRequest): string[] {\n const betas: string[] = [];\n\n // ── 思考相关 ──\n if (caps.supports_isp) {\n betas.push(betaInterleavedThinking);\n betas.push(betaContextManagement);\n }\n if (caps.supports_redact_thinking && req.thinking && req.thinking.display === 'summary') {\n betas.push(betaRedactThinking);\n }\n\n // ── 上下文 ──\n if (caps.supports_1m_context) {\n betas.push(betaContext1M);\n }\n\n // ── 输出控制 (互斥: structured-outputs ⊕ token-efficient-tools) ──\n const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;\n if (hasStructuredOutput) {\n betas.push(betaStructuredOutputs);\n } else if (caps.supports_token_efficient) {\n betas.push(betaTokenEfficientTools);\n }\n\n // ── Tool Search ──\n if (caps.supports_tool_search) {\n betas.push(betaAdvancedToolUse);\n }\n\n // ── 推理控制 ──\n // Level 模式下 resolveThinkingLevel 直接写 body[\"effort\"],req.effort 仍为 undefined,\n // 所以需额外判断 Level 是否激活了 effort\n const needsEffort =\n req.effort != null ||\n (req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff);\n if (caps.supports_effort && needsEffort) {\n betas.push(betaEffort);\n }\n if (caps.supports_fast_mode && req.speed === 'fast') {\n betas.push(betaFastMode);\n }\n\n // ── 缓存 ──\n if (caps.supports_prompt_cache) {\n betas.push(betaPromptCachingScope);\n }\n\n // ── 合并客户端显式传入 (去重) ──\n return uniqueMerge(betas, req.betas ?? []);\n}\n\n/** 合并两个字符串数组并去重, 保留顺序 */\nexport function uniqueMerge(base: string[], extra: string[]): string[] {\n if (extra.length === 0) return base;\n const seen = new Set<string>(base);\n for (const s of extra) {\n if (!seen.has(s)) {\n base.push(s);\n seen.add(s);\n }\n }\n return base;\n}\n","// adapters/index.ts — 端口自 acosmi-sdk-go/adapter.go (v0.19.0)\n//\n// SDK 层按上游托管模型元数据选路:\n// preferred_format == \"anthropic\" → AnthropicAdapter → POST /managed-models/:id/anthropic\n// preferred_format == \"openai\" → OpenAIAdapter → POST /managed-models/:id/chat\n//\n// 旧上游若未返回 preferred_format / supported_formats, SDK 才回落到 provider 名称:\n// provider == \"anthropic\" / \"acosmi\" → AnthropicAdapter\n// 其他 provider → OpenAIAdapter\n//\n// SDK 只负责: 格式路由 + 请求结构转换 + 响应结构转换\n// 厂商特定协议差异 (endpoint/auth/region/字段裁剪) 由 Nexus Gateway Profile 处理\n//\n// ============================================================================\n// 红线 (双产品消费): AnthropicAdapter + OpenAIAdapter 等地位, 不可合并/降级\n// ============================================================================\n\nimport type { ManagedModel, ChatRequest, ChatResponse, StreamEvent, ModelCapabilities } from '../types';\n\n/** 标识请求格式 */\nexport enum ProviderFormat {\n /** Anthropic 原生格式 */\n Anthropic = 0,\n /** OpenAI 兼容格式 */\n OpenAI = 1,\n}\n\n/** 将 ChatRequest 转换为特定格式的 adapter 接口 */\nexport interface ProviderAdapter {\n /** 此 adapter 使用的请求格式 */\n format(): ProviderFormat;\n\n /**\n * API 路径后缀\n * Anthropic: \"/anthropic\", OpenAI: \"/chat\"\n */\n endpointSuffix(): string;\n\n /**\n * 将 ChatRequest 转换为 HTTP body (object → JSON.stringify)\n * caps 用于条件化字段注入 (如 betas)\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;\n\n /** 解析同步响应 body 为 ChatResponse */\n parseResponse(body: Uint8Array | string): ChatResponse;\n\n /**\n * 解析一行 SSE data 为 StreamEvent\n * 返回 { event, done }; done=true 表示流结束 ([DONE] 或 message_stop)\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean };\n}\n\n// 实现 import 在文件末尾, 避免循环依赖(adapter impl 也需引用 ProviderFormat)\nimport { AnthropicAdapter } from './anthropic';\nimport { OpenAIAdapter } from './openai';\n\n/** 按 provider 名称映射 adapter */\nconst adapterRegistry: Record<string, ProviderAdapter> = {\n anthropic: new AnthropicAdapter(),\n /** Acosmi 自有模型走 Anthropic 格式 */\n acosmi: new AnthropicAdapter(),\n};\n\n/** 非 Anthropic 厂商的默认 adapter */\nconst defaultOpenAIAdapter: ProviderAdapter = new OpenAIAdapter();\n\n/**\n * 根据 provider 返回对应的 adapter (v0.5.0 遗留 API, 向后兼容)\n * 新代码应使用 getAdapterForModel\n */\nexport function getAdapter(provider: string): ProviderAdapter {\n const a = adapterRegistry[provider.toLowerCase()];\n if (a) return a;\n return defaultOpenAIAdapter;\n}\n\n/**\n * 按 ManagedModel 的 preferred_format / supported_formats 选择 adapter\n *\n * 决策顺序:\n * 1. preferred_format 非空 → 按其值返回 (anthropic | openai)\n * 2. supported_formats 含 \"anthropic\" → AnthropicAdapter\n * 3. supported_formats 含 \"openai\" → OpenAIAdapter\n * 4. 两字段均空 (旧上游) → 回落 provider 名硬编码 (原 getAdapter 行为)\n *\n * 这使得 dashscope / zhipu / deepseek 等 provider 的模型如果上游启用了\n * Anthropic 兼容端点, 也能走 /anthropic 路径, 不再被 provider 字符串硬编码到 /chat\n * 导致 tool_reference 400.\n */\nexport function getAdapterForModel(m: ManagedModel): ProviderAdapter {\n const pref = (m.preferred_format ?? '').trim().toLowerCase();\n switch (pref) {\n case 'anthropic':\n return new AnthropicAdapter();\n case 'openai':\n return new OpenAIAdapter();\n }\n\n let hasAnthropic = false;\n let hasOpenAI = false;\n for (const f of m.supported_formats ?? []) {\n switch (f.trim().toLowerCase()) {\n case 'anthropic':\n hasAnthropic = true;\n break;\n case 'openai':\n hasOpenAI = true;\n break;\n }\n }\n if (hasAnthropic) return new AnthropicAdapter();\n if (hasOpenAI) return new OpenAIAdapter();\n\n // 旧上游未填字段: 回落到 provider 名硬编码 (向后兼容)\n return getAdapter((m.provider ?? '').toLowerCase());\n}\n\nexport { AnthropicAdapter, OpenAIAdapter };\n","// adapters/anthropic.ts — 端口自 acosmi-sdk-go/adapter_anthropic.go (228 行)\n//\n// Anthropic 原生格式 adapter\n// 重构自 buildChatRequest 现有逻辑,功能完全等同。\n// 包含 buildBetas() 调用、serverTools 合入、extraBody 透传。\n\nimport {\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHighMinMaxTokens,\n ThinkingMax,\n ThinkingMaxFallbackMaxTokens,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { buildBetas } from '../betas';\nimport { ProviderFormat, type ProviderAdapter } from './index';\n\n/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */\nexport class AnthropicAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.Anthropic;\n }\n\n endpointSuffix(): string {\n return '/anthropic';\n }\n\n /**\n * 构建 Anthropic 格式请求体\n * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // 消息: rawMessages 优先于 messages\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\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n if (req.system != null) {\n body['system'] = req.system;\n }\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Thinking + Effort 组装 ──\n if (req.thinking && req.thinking.level && req.thinking.level !== '') {\n // Level 模式: SDK 接管 thinking + effort + maxTokens\n resolveThinkingLevel(body, req, caps);\n } else {\n // 兼容模式: 调用方自己拼 (保持 v0.8.0 行为)\n if (req.thinking) {\n body['thinking'] = req.thinking;\n }\n if (req.effort) {\n body['effort'] = req.effort;\n }\n }\n\n // ── Metadata + v1.6.0 endUserId 合并 ──\n // caller 显式 metadata 键 (含 user_id) 永远优先, endUserId 仅在 metadata 无 user_id 键时填入。\n // 不污染 caller 原始对象: 通过浅拷贝构造新 Record, 保留 caller 所有原有键。\n if (req.metadata || (req.endUserId && req.endUserId !== '')) {\n const meta: Record<string, unknown> = {};\n if (req.metadata) {\n for (const [k, v] of Object.entries(req.metadata)) {\n meta[k] = v;\n }\n }\n if (req.endUserId && req.endUserId !== '' && meta['user_id'] === undefined) {\n meta['user_id'] = req.endUserId;\n }\n body['metadata'] = meta;\n }\n\n // ── 合入 tools + serverTools ──\n const allTools: unknown[] = [];\n if (req.tools != null) {\n // tools 透传 — 在 Go 侧通过 json.Marshal+Unmarshal 实现深拷贝, TS 中我们直接拼接\n try {\n const parsed = JSON.parse(JSON.stringify(req.tools));\n if (Array.isArray(parsed)) {\n for (const t of parsed) allTools.push(t);\n }\n } catch {\n // ignore — 与 Go 侧 silent ignore 行为一致\n }\n }\n for (const st of req.serverTools ?? []) {\n const schema: Record<string, unknown> = {\n type: st.type,\n name: st.name,\n };\n if (st.config) {\n for (const [k, v] of Object.entries(st.config)) {\n schema[k] = v;\n }\n }\n allTools.push(schema);\n }\n if (allTools.length > 0) {\n body['tools'] = allTools;\n }\n\n // ── 推理控制 (非 Level 模式时的透传) ──\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n if (req.outputConfig) {\n body['output_config'] = req.outputConfig;\n }\n\n // ── Beta 自动组装 ──\n const betas = buildBetas(caps, req);\n if (betas.length > 0) {\n body['betas'] = betas;\n }\n\n // ── 透传 extraBody ──\n // 注意: extraBody 在 resolveThinkingLevel 之后执行,\n // Level 模式下 thinking / effort / max_tokens / temperature 已由 SDK 管理,\n // extraBody 中不应包含这些 key, 否则会覆盖 SDK 计算结果\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n body[k] = v;\n }\n }\n\n return body;\n }\n\n /**\n * 解析 Anthropic 格式同步响应\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 Anthropic JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n // 尝试 APIResponse 包装\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 // 非 JSON 或无 wrapper 格式 — 用原始 bodyStr\n }\n\n let resp: ChatResponse;\n try {\n resp = JSON.parse(raw) as ChatResponse;\n } catch (e) {\n throw new Error(\n `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n resp.tokenRemaining = -1;\n resp.callRemaining = -1;\n resp.modelTokenRemaining = -1;\n resp.modelTokenRemainingETU = -1;\n return resp;\n }\n\n /**\n * 解析 Anthropic SSE 行\n *\n * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),\n * 但 Nexus Gateway 的 ChatStream 路径会追加 [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 return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 根据 ThinkingConfig.level 自动组装请求参数\n *\n * off → thinking=disabled, 不设 effort, 不动 maxTokens\n * high → thinking=adaptive, effort=high, maxTokens 至少 32K\n * max → thinking=adaptive, effort=max, maxTokens 拉到模型上限\n *\n * 旧模型不支持 adaptive 时, 回退到 enabled + budget_tokens = maxTokens - 1\n */\nexport function resolveThinkingLevel(\n body: Record<string, unknown>,\n req: ChatRequest,\n caps: ModelCapabilities,\n): void {\n const level = req.thinking?.level ?? '';\n\n // ── off ──\n if (level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n return;\n }\n\n // ── 模型不支持任何形式的 thinking → 不动 maxTokens, 直接返回 ──\n if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {\n return;\n }\n\n // ── 确定 maxTokens ──\n let maxTokens = req.max_tokens ?? 0;\n if (maxTokens <= 0) {\n maxTokens = ThinkingHighMinMaxTokens;\n }\n\n if (level === ThinkingMax) {\n let modelMax = caps.max_output_tokens;\n if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;\n if (maxTokens < modelMax) maxTokens = modelMax;\n } else {\n if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;\n }\n body['max_tokens'] = maxTokens;\n\n // ── thinking ──\n // adaptive 优先 (Claude 4.x); 旧模型回退 enabled + full budget\n if (caps.supports_adaptive_thinking) {\n const thinking: Record<string, unknown> = { type: 'adaptive' };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n } else if (caps.supports_thinking) {\n let budget = maxTokens - 1;\n if (budget < 1024) budget = 1024;\n const thinking: Record<string, unknown> = {\n type: 'enabled',\n budget_tokens: budget,\n };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n }\n\n // ── effort ──\n // 仅支持 effort 的模型发送此参数\n if (caps.supports_effort) {\n let effortLevel = 'high';\n if (level === ThinkingMax && caps.supports_max_effort) {\n effortLevel = 'max';\n }\n body['effort'] = { level: effortLevel };\n }\n\n // ── API 约束: thinking 与 temperature 互斥 ──\n delete body['temperature'];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/betas.ts","../../../src/models/adapters/index.ts","../../../src/models/adapters/anthropic.ts"],"names":[],"mappings":"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","file":"anthropic.cjs","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/** 图片生成请求 (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/**\n * 模型可接收的用户输入模态 (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n */\nexport type InputModality = 'text' | 'image';\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 * 当前用户在此模型上的桶余额聚合 (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 * 取值: 'text' | 'image' 的子集. 'image' 表示模型可直接接收 screenshot/image 输入.\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModality[];\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/**\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}\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 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/**\n * 从 StreamEvent 中解析搜索来源\n * 返回 null 表示该事件不是 sources 类型\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: { type?: string; sources?: WebSearchSource[]; session_id?: string };\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\n constructor(statusCode: number, opts: { type?: string; message?: string; retryAfter?: number; body?: string } = {}) {\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 }\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\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/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件解析得到。客户端可通过\n * instanceof 提取并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 */\n retryable: boolean;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean } = {}) {\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 ?? false;\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.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n }\n}\n","// betas.ts — 端口自 acosmi-sdk-go/betas.go\n//\n// Beta Header 常量 — 经联网验证的真实 Anthropic API beta 值。\n// 虚构/错误日期的 header 已剔除。\n\nimport type { ChatRequest, ModelCapabilities } from './types';\nimport { ThinkingOff } from './types';\n\n/** ISP: 交错思考 */\nconst betaInterleavedThinking = 'interleaved-thinking-2025-05-14';\n/** 1M 上下文 (retiring 2026-04-30) */\nconst betaContext1M = 'context-1m-2025-08-07';\n/** 上下文编辑 */\nconst betaContextManagement = 'context-management-2025-06-27';\n/** 结构化输出 */\nconst betaStructuredOutputs = 'structured-outputs-2025-11-13';\n/** Tool Search */\nconst betaAdvancedToolUse = 'advanced-tool-use-2025-11-20';\n/** Effort 控制 (Opus 4.5 需要, 4.6 stable) */\nconst betaEffort = 'effort-2025-11-24';\n/** 缓存作用域隔离 */\nconst betaPromptCachingScope = 'prompt-caching-scope-2026-01-05';\n/** 快速推理 (Opus 4.6) */\nconst betaFastMode = 'fast-mode-2026-02-01';\n/** 思考脱敏 */\nconst betaRedactThinking = 'redact-thinking-2026-02-12';\n/** 高效工具 (Claude 3.7, Claude 4 内置) */\nconst betaTokenEfficientTools = 'token-efficient-tools-2025-02-19';\n\n/** 根据模型能力和请求参数自动组装 beta header 列表 */\nexport function buildBetas(caps: ModelCapabilities, req: ChatRequest): string[] {\n const betas: string[] = [];\n\n // ── 思考相关 ──\n if (caps.supports_isp) {\n betas.push(betaInterleavedThinking);\n betas.push(betaContextManagement);\n }\n if (caps.supports_redact_thinking && req.thinking && req.thinking.display === 'summary') {\n betas.push(betaRedactThinking);\n }\n\n // ── 上下文 ──\n if (caps.supports_1m_context) {\n betas.push(betaContext1M);\n }\n\n // ── 输出控制 (互斥: structured-outputs ⊕ token-efficient-tools) ──\n const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;\n if (hasStructuredOutput) {\n betas.push(betaStructuredOutputs);\n } else if (caps.supports_token_efficient) {\n betas.push(betaTokenEfficientTools);\n }\n\n // ── Tool Search ──\n if (caps.supports_tool_search) {\n betas.push(betaAdvancedToolUse);\n }\n\n // ── 推理控制 ──\n // Level 模式下 resolveThinkingLevel 直接写 body[\"effort\"],req.effort 仍为 undefined,\n // 所以需额外判断 Level 是否激活了 effort\n const needsEffort =\n req.effort != null ||\n (req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff);\n if (caps.supports_effort && needsEffort) {\n betas.push(betaEffort);\n }\n if (caps.supports_fast_mode && req.speed === 'fast') {\n betas.push(betaFastMode);\n }\n\n // ── 缓存 ──\n if (caps.supports_prompt_cache) {\n betas.push(betaPromptCachingScope);\n }\n\n // ── 合并客户端显式传入 (去重) ──\n return uniqueMerge(betas, req.betas ?? []);\n}\n\n/** 合并两个字符串数组并去重, 保留顺序 */\nexport function uniqueMerge(base: string[], extra: string[]): string[] {\n if (extra.length === 0) return base;\n const seen = new Set<string>(base);\n for (const s of extra) {\n if (!seen.has(s)) {\n base.push(s);\n seen.add(s);\n }\n }\n return base;\n}\n","// adapters/index.ts — 端口自 acosmi-sdk-go/adapter.go (v0.19.0)\n//\n// SDK 层按上游托管模型元数据选路:\n// preferred_format == \"anthropic\" → AnthropicAdapter → POST /managed-models/:id/anthropic\n// preferred_format == \"openai\" → OpenAIAdapter → POST /managed-models/:id/chat\n//\n// 旧上游若未返回 preferred_format / supported_formats, SDK 才回落到 provider 名称:\n// provider == \"anthropic\" / \"acosmi\" → AnthropicAdapter\n// 其他 provider → OpenAIAdapter\n//\n// SDK 只负责: 格式路由 + 请求结构转换 + 响应结构转换\n// 厂商特定协议差异 (endpoint/auth/region/字段裁剪) 由 Nexus Gateway Profile 处理\n//\n// ============================================================================\n// 红线 (双产品消费): AnthropicAdapter + OpenAIAdapter 等地位, 不可合并/降级\n// ============================================================================\n\nimport type { ManagedModel, ChatRequest, ChatResponse, StreamEvent, ModelCapabilities } from '../types';\n\n/** 标识请求格式 */\nexport enum ProviderFormat {\n /** Anthropic 原生格式 */\n Anthropic = 0,\n /** OpenAI 兼容格式 */\n OpenAI = 1,\n}\n\n/** 将 ChatRequest 转换为特定格式的 adapter 接口 */\nexport interface ProviderAdapter {\n /** 此 adapter 使用的请求格式 */\n format(): ProviderFormat;\n\n /**\n * API 路径后缀\n * Anthropic: \"/anthropic\", OpenAI: \"/chat\"\n */\n endpointSuffix(): string;\n\n /**\n * 将 ChatRequest 转换为 HTTP body (object → JSON.stringify)\n * caps 用于条件化字段注入 (如 betas)\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;\n\n /** 解析同步响应 body 为 ChatResponse */\n parseResponse(body: Uint8Array | string): ChatResponse;\n\n /**\n * 解析一行 SSE data 为 StreamEvent\n * 返回 { event, done }; done=true 表示流结束 ([DONE] 或 message_stop)\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean };\n}\n\n// 实现 import 在文件末尾, 避免循环依赖(adapter impl 也需引用 ProviderFormat)\nimport { AnthropicAdapter } from './anthropic';\nimport { OpenAIAdapter } from './openai';\n\n/** 按 provider 名称映射 adapter */\nconst adapterRegistry: Record<string, ProviderAdapter> = {\n anthropic: new AnthropicAdapter(),\n /** Acosmi 自有模型走 Anthropic 格式 */\n acosmi: new AnthropicAdapter(),\n};\n\n/** 非 Anthropic 厂商的默认 adapter */\nconst defaultOpenAIAdapter: ProviderAdapter = new OpenAIAdapter();\n\n/**\n * 根据 provider 返回对应的 adapter (v0.5.0 遗留 API, 向后兼容)\n * 新代码应使用 getAdapterForModel\n */\nexport function getAdapter(provider: string): ProviderAdapter {\n const a = adapterRegistry[provider.toLowerCase()];\n if (a) return a;\n return defaultOpenAIAdapter;\n}\n\n/**\n * 按 ManagedModel 的 preferred_format / supported_formats 选择 adapter\n *\n * 决策顺序:\n * 1. preferred_format 非空 **且** 该格式在 supported_formats 内 (或 supported_formats 未声明)\n * → 按其值返回 (anthropic | openai)\n * 2. supported_formats 含 \"anthropic\" → AnthropicAdapter\n * 3. supported_formats 含 \"openai\" → OpenAIAdapter\n * 4. 两字段均空 (旧上游) → 回落 provider 名硬编码 (原 getAdapter 行为)\n *\n * 这使得 dashscope / zhipu / deepseek 等 provider 的模型如果上游启用了\n * Anthropic 兼容端点, 也能走 /anthropic 路径, 不再被 provider 字符串硬编码到 /chat\n * 导致 tool_reference 400.\n *\n * [格式一致性护栏 2026-05-29] preferred_format 仅在确被 supported_formats 收录时才采信:\n * 防止上游元数据漂移 (preferred_format=anthropic 但 supported_formats=[openai]) 把 SDK\n * 路由到模型并不支持的格式端点 (撞 /anthropic \"未绑定 Anthropic\" 4xx)。这是网关侧\n * \"同 model_id 双 profile 选行\" 根因修复在 SDK 侧的同构护栏。\n */\nexport function getAdapterForModel(m: ManagedModel): ProviderAdapter {\n let hasAnthropic = false;\n let hasOpenAI = false;\n for (const f of m.supported_formats ?? []) {\n switch (f.trim().toLowerCase()) {\n case 'anthropic':\n hasAnthropic = true;\n break;\n case 'openai':\n hasOpenAI = true;\n break;\n }\n }\n const declared = hasAnthropic || hasOpenAI;\n\n const pref = (m.preferred_format ?? '').trim().toLowerCase();\n switch (pref) {\n case 'anthropic':\n if (!declared || hasAnthropic) return new AnthropicAdapter();\n break;\n case 'openai':\n if (!declared || hasOpenAI) return new OpenAIAdapter();\n break;\n }\n\n if (hasAnthropic) return new AnthropicAdapter();\n if (hasOpenAI) return new OpenAIAdapter();\n\n // 旧上游未填字段: 回落到 provider 名硬编码 (向后兼容)\n return getAdapter((m.provider ?? '').toLowerCase());\n}\n\nexport { AnthropicAdapter, OpenAIAdapter };\nexport { OpenAIStreamConverter, newOpenAIStreamConverter } from './openai';\n","// adapters/anthropic.ts — 端口自 acosmi-sdk-go/adapter_anthropic.go (228 行)\n//\n// Anthropic 原生格式 adapter\n// 重构自 buildChatRequest 现有逻辑,功能完全等同。\n// 包含 buildBetas() 调用、serverTools 合入、extraBody 透传。\n\nimport {\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHighMinMaxTokens,\n ThinkingMax,\n ThinkingMaxFallbackMaxTokens,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { buildBetas } from '../betas';\nimport { ProviderFormat, type ProviderAdapter } from './index';\n\n/**\n * SDK 在 buildRequestBody 中写入并管理的请求体字段 (精确 body key)。\n * extraBody 中若出现同名 key 会覆盖 SDK 计算结果 (如 Level 模式下的 thinking/effort/max_tokens、\n * 自动组装的 betas、被刻意删除的 temperature), 故透传时强制跳过这些 key。\n */\nconst ANTHROPIC_SDK_MANAGED_BODY_KEYS: ReadonlySet<string> = new Set([\n 'thinking',\n 'effort',\n 'max_tokens',\n 'temperature',\n 'betas',\n]);\n\n/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */\nexport class AnthropicAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.Anthropic;\n }\n\n endpointSuffix(): string {\n return '/anthropic';\n }\n\n /**\n * 构建 Anthropic 格式请求体\n * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // 消息: rawMessages 优先于 messages\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\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n if (req.system != null) {\n body['system'] = req.system;\n }\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Thinking + Effort 组装 ──\n if (req.thinking && req.thinking.level && req.thinking.level !== '') {\n // Level 模式: SDK 接管 thinking + effort + maxTokens\n resolveThinkingLevel(body, req, caps);\n } else {\n // 兼容模式: 调用方自己拼 (保持 v0.8.0 行为)\n if (req.thinking) {\n body['thinking'] = req.thinking;\n }\n if (req.effort) {\n body['effort'] = req.effort;\n }\n }\n\n // ── Metadata + v1.6.0 endUserId 合并 ──\n // caller 显式 metadata 键 (含 user_id) 永远优先, endUserId 仅在 metadata 无 user_id 键时填入。\n // 不污染 caller 原始对象: 通过浅拷贝构造新 Record, 保留 caller 所有原有键。\n if (req.metadata || (req.endUserId && req.endUserId !== '')) {\n const meta: Record<string, unknown> = {};\n if (req.metadata) {\n for (const [k, v] of Object.entries(req.metadata)) {\n meta[k] = v;\n }\n }\n if (req.endUserId && req.endUserId !== '' && meta['user_id'] === undefined) {\n meta['user_id'] = req.endUserId;\n }\n body['metadata'] = meta;\n }\n\n // ── 合入 tools + serverTools ──\n const allTools: unknown[] = [];\n if (req.tools != null) {\n // tools 透传 — 在 Go 侧通过 json.Marshal+Unmarshal 实现深拷贝, TS 中我们直接拼接\n try {\n const parsed = JSON.parse(JSON.stringify(req.tools));\n if (Array.isArray(parsed)) {\n for (const t of parsed) allTools.push(t);\n }\n } catch {\n // ignore — 与 Go 侧 silent ignore 行为一致\n }\n }\n for (const st of req.serverTools ?? []) {\n const schema: Record<string, unknown> = {\n type: st.type,\n name: st.name,\n };\n if (st.config) {\n for (const [k, v] of Object.entries(st.config)) {\n schema[k] = v;\n }\n }\n allTools.push(schema);\n }\n if (allTools.length > 0) {\n body['tools'] = allTools;\n }\n\n // ── 推理控制 (非 Level 模式时的透传) ──\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n if (req.outputConfig) {\n body['output_config'] = req.outputConfig;\n }\n\n // ── Beta 自动组装 ──\n const betas = buildBetas(caps, req);\n if (betas.length > 0) {\n body['betas'] = betas;\n }\n\n // ── 透传 extraBody ──\n // 注意: extraBody 在 resolveThinkingLevel 之后执行,\n // Level 模式下 thinking / effort / max_tokens / temperature / betas 已由 SDK 管理,\n // extraBody 中若含这些 key 会覆盖 SDK 计算结果 — 强制跳过并告警,\n // 但保留 extraBody 透传其它 (非管理) 字段的正当能力。\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {\n console.warn(\n `acosmi-sdk: extraBody key \"${k}\" is SDK-managed and was ignored (use the dedicated request field instead).`,\n );\n continue;\n }\n body[k] = v;\n }\n }\n\n return body;\n }\n\n /**\n * 解析 Anthropic 格式同步响应\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 Anthropic JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n // 尝试 APIResponse 包装\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 // 非 JSON 或无 wrapper 格式 — 用原始 bodyStr\n }\n\n let resp: ChatResponse;\n try {\n resp = JSON.parse(raw) as ChatResponse;\n } catch (e) {\n throw new Error(\n `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n resp.tokenRemaining = -1;\n resp.callRemaining = -1;\n resp.modelTokenRemaining = -1;\n resp.modelTokenRemainingETU = -1;\n return resp;\n }\n\n /**\n * 解析 Anthropic SSE 行\n *\n * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),\n * 但 Nexus Gateway 的 ChatStream 路径会追加 [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 return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 根据 ThinkingConfig.level 自动组装请求参数\n *\n * off → thinking=disabled, 不设 effort, 不动 maxTokens\n * high → thinking=adaptive, effort=high, maxTokens 至少 32K\n * max → thinking=adaptive, effort=max, maxTokens 拉到模型上限\n *\n * 旧模型不支持 adaptive 时, 回退到 enabled + budget_tokens = maxTokens - 1\n */\nexport function resolveThinkingLevel(\n body: Record<string, unknown>,\n req: ChatRequest,\n caps: ModelCapabilities,\n): void {\n const level = req.thinking?.level ?? '';\n\n // ── off ──\n if (level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n return;\n }\n\n // ── 模型不支持任何形式的 thinking → 不动 maxTokens, 直接返回 ──\n if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {\n return;\n }\n\n // ── 确定 maxTokens ──\n let maxTokens = req.max_tokens ?? 0;\n if (maxTokens <= 0) {\n maxTokens = ThinkingHighMinMaxTokens;\n }\n\n if (level === ThinkingMax) {\n let modelMax = caps.max_output_tokens;\n if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;\n if (maxTokens < modelMax) maxTokens = modelMax;\n } else {\n if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;\n }\n body['max_tokens'] = maxTokens;\n\n // ── thinking ──\n // adaptive 优先 (Claude 4.x); 旧模型回退 enabled + full budget\n if (caps.supports_adaptive_thinking) {\n const thinking: Record<string, unknown> = { type: 'adaptive' };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n } else if (caps.supports_thinking) {\n let budget = maxTokens - 1;\n if (budget < 1024) budget = 1024;\n const thinking: Record<string, unknown> = {\n type: 'enabled',\n budget_tokens: budget,\n };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n }\n\n // ── effort ──\n // 仅支持 effort 的模型发送此参数\n if (caps.supports_effort) {\n let effortLevel = 'high';\n if (level === ThinkingMax && caps.supports_max_effort) {\n effortLevel = 'max';\n }\n body['effort'] = { level: effortLevel };\n }\n\n // ── API 约束: thinking 与 temperature 互斥 ──\n delete body['temperature'];\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as ProviderAdapter, k as ProviderFormat, j as ModelCapabilities, e as ChatRequest, f as ChatResponse, n as StreamEvent } from '../index-
|
|
1
|
+
import { P as ProviderAdapter, k as ProviderFormat, j as ModelCapabilities, e as ChatRequest, f as ChatResponse, n as StreamEvent } from '../index-BrbgHn0E.cjs';
|
|
2
2
|
|
|
3
3
|
/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */
|
|
4
4
|
declare class AnthropicAdapter implements ProviderAdapter {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as ProviderAdapter, k as ProviderFormat, j as ModelCapabilities, e as ChatRequest, f as ChatResponse, n as StreamEvent } from '../index-
|
|
1
|
+
import { P as ProviderAdapter, k as ProviderFormat, j as ModelCapabilities, e as ChatRequest, f as ChatResponse, n as StreamEvent } from '../index-BrbgHn0E.js';
|
|
2
2
|
|
|
3
3
|
/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */
|
|
4
4
|
declare class AnthropicAdapter implements ProviderAdapter {
|
|
@@ -78,6 +78,13 @@ function uniqueMerge(base, extra) {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
// src/models/adapters/anthropic.ts
|
|
81
|
+
var ANTHROPIC_SDK_MANAGED_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
82
|
+
"thinking",
|
|
83
|
+
"effort",
|
|
84
|
+
"max_tokens",
|
|
85
|
+
"temperature",
|
|
86
|
+
"betas"
|
|
87
|
+
]);
|
|
81
88
|
var AnthropicAdapter = class {
|
|
82
89
|
format() {
|
|
83
90
|
return 0 /* Anthropic */;
|
|
@@ -165,6 +172,12 @@ var AnthropicAdapter = class {
|
|
|
165
172
|
}
|
|
166
173
|
if (req.extraBody) {
|
|
167
174
|
for (const [k, v] of Object.entries(req.extraBody)) {
|
|
175
|
+
if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {
|
|
176
|
+
console.warn(
|
|
177
|
+
`acosmi-sdk: extraBody key "${k}" is SDK-managed and was ignored (use the dedicated request field instead).`
|
|
178
|
+
);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
168
181
|
body[k] = v;
|
|
169
182
|
}
|
|
170
183
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/betas.ts","../../../src/models/adapters/index.ts","../../../src/models/adapters/anthropic.ts"],"names":[],"mappings":"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","file":"anthropic.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/** 图片生成请求 (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/**\n * 模型可接收的用户输入模态 (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n */\nexport type InputModality = 'text' | 'image';\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 pricePerMTok?: number;\n isDefault?: boolean;\n contextWindow?: number;\n capabilities: ModelCapabilities;\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 * 当前用户在此模型上的桶余额聚合 (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 * 取值: 'text' | 'image' 的子集. 'image' 表示模型可直接接收 screenshot/image 输入.\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModality[];\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/**\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}\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 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/**\n * 从 StreamEvent 中解析搜索来源\n * 返回 null 表示该事件不是 sources 类型\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: { type?: string; sources?: WebSearchSource[]; session_id?: string };\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\n constructor(statusCode: number, opts: { type?: string; message?: string; retryAfter?: number; body?: string } = {}) {\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 }\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\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/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件解析得到。客户端可通过\n * instanceof 提取并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 */\n retryable: boolean;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean } = {}) {\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 ?? false;\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.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n }\n}\n","// betas.ts — 端口自 acosmi-sdk-go/betas.go\n//\n// Beta Header 常量 — 经联网验证的真实 Anthropic API beta 值。\n// 虚构/错误日期的 header 已剔除。\n\nimport type { ChatRequest, ModelCapabilities } from './types';\nimport { ThinkingOff } from './types';\n\n/** ISP: 交错思考 */\nconst betaInterleavedThinking = 'interleaved-thinking-2025-05-14';\n/** 1M 上下文 (retiring 2026-04-30) */\nconst betaContext1M = 'context-1m-2025-08-07';\n/** 上下文编辑 */\nconst betaContextManagement = 'context-management-2025-06-27';\n/** 结构化输出 */\nconst betaStructuredOutputs = 'structured-outputs-2025-11-13';\n/** Tool Search */\nconst betaAdvancedToolUse = 'advanced-tool-use-2025-11-20';\n/** Effort 控制 (Opus 4.5 需要, 4.6 stable) */\nconst betaEffort = 'effort-2025-11-24';\n/** 缓存作用域隔离 */\nconst betaPromptCachingScope = 'prompt-caching-scope-2026-01-05';\n/** 快速推理 (Opus 4.6) */\nconst betaFastMode = 'fast-mode-2026-02-01';\n/** 思考脱敏 */\nconst betaRedactThinking = 'redact-thinking-2026-02-12';\n/** 高效工具 (Claude 3.7, Claude 4 内置) */\nconst betaTokenEfficientTools = 'token-efficient-tools-2025-02-19';\n\n/** 根据模型能力和请求参数自动组装 beta header 列表 */\nexport function buildBetas(caps: ModelCapabilities, req: ChatRequest): string[] {\n const betas: string[] = [];\n\n // ── 思考相关 ──\n if (caps.supports_isp) {\n betas.push(betaInterleavedThinking);\n betas.push(betaContextManagement);\n }\n if (caps.supports_redact_thinking && req.thinking && req.thinking.display === 'summary') {\n betas.push(betaRedactThinking);\n }\n\n // ── 上下文 ──\n if (caps.supports_1m_context) {\n betas.push(betaContext1M);\n }\n\n // ── 输出控制 (互斥: structured-outputs ⊕ token-efficient-tools) ──\n const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;\n if (hasStructuredOutput) {\n betas.push(betaStructuredOutputs);\n } else if (caps.supports_token_efficient) {\n betas.push(betaTokenEfficientTools);\n }\n\n // ── Tool Search ──\n if (caps.supports_tool_search) {\n betas.push(betaAdvancedToolUse);\n }\n\n // ── 推理控制 ──\n // Level 模式下 resolveThinkingLevel 直接写 body[\"effort\"],req.effort 仍为 undefined,\n // 所以需额外判断 Level 是否激活了 effort\n const needsEffort =\n req.effort != null ||\n (req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff);\n if (caps.supports_effort && needsEffort) {\n betas.push(betaEffort);\n }\n if (caps.supports_fast_mode && req.speed === 'fast') {\n betas.push(betaFastMode);\n }\n\n // ── 缓存 ──\n if (caps.supports_prompt_cache) {\n betas.push(betaPromptCachingScope);\n }\n\n // ── 合并客户端显式传入 (去重) ──\n return uniqueMerge(betas, req.betas ?? []);\n}\n\n/** 合并两个字符串数组并去重, 保留顺序 */\nexport function uniqueMerge(base: string[], extra: string[]): string[] {\n if (extra.length === 0) return base;\n const seen = new Set<string>(base);\n for (const s of extra) {\n if (!seen.has(s)) {\n base.push(s);\n seen.add(s);\n }\n }\n return base;\n}\n","// adapters/index.ts — 端口自 acosmi-sdk-go/adapter.go (v0.19.0)\n//\n// SDK 层按上游托管模型元数据选路:\n// preferred_format == \"anthropic\" → AnthropicAdapter → POST /managed-models/:id/anthropic\n// preferred_format == \"openai\" → OpenAIAdapter → POST /managed-models/:id/chat\n//\n// 旧上游若未返回 preferred_format / supported_formats, SDK 才回落到 provider 名称:\n// provider == \"anthropic\" / \"acosmi\" → AnthropicAdapter\n// 其他 provider → OpenAIAdapter\n//\n// SDK 只负责: 格式路由 + 请求结构转换 + 响应结构转换\n// 厂商特定协议差异 (endpoint/auth/region/字段裁剪) 由 Nexus Gateway Profile 处理\n//\n// ============================================================================\n// 红线 (双产品消费): AnthropicAdapter + OpenAIAdapter 等地位, 不可合并/降级\n// ============================================================================\n\nimport type { ManagedModel, ChatRequest, ChatResponse, StreamEvent, ModelCapabilities } from '../types';\n\n/** 标识请求格式 */\nexport enum ProviderFormat {\n /** Anthropic 原生格式 */\n Anthropic = 0,\n /** OpenAI 兼容格式 */\n OpenAI = 1,\n}\n\n/** 将 ChatRequest 转换为特定格式的 adapter 接口 */\nexport interface ProviderAdapter {\n /** 此 adapter 使用的请求格式 */\n format(): ProviderFormat;\n\n /**\n * API 路径后缀\n * Anthropic: \"/anthropic\", OpenAI: \"/chat\"\n */\n endpointSuffix(): string;\n\n /**\n * 将 ChatRequest 转换为 HTTP body (object → JSON.stringify)\n * caps 用于条件化字段注入 (如 betas)\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;\n\n /** 解析同步响应 body 为 ChatResponse */\n parseResponse(body: Uint8Array | string): ChatResponse;\n\n /**\n * 解析一行 SSE data 为 StreamEvent\n * 返回 { event, done }; done=true 表示流结束 ([DONE] 或 message_stop)\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean };\n}\n\n// 实现 import 在文件末尾, 避免循环依赖(adapter impl 也需引用 ProviderFormat)\nimport { AnthropicAdapter } from './anthropic';\nimport { OpenAIAdapter } from './openai';\n\n/** 按 provider 名称映射 adapter */\nconst adapterRegistry: Record<string, ProviderAdapter> = {\n anthropic: new AnthropicAdapter(),\n /** Acosmi 自有模型走 Anthropic 格式 */\n acosmi: new AnthropicAdapter(),\n};\n\n/** 非 Anthropic 厂商的默认 adapter */\nconst defaultOpenAIAdapter: ProviderAdapter = new OpenAIAdapter();\n\n/**\n * 根据 provider 返回对应的 adapter (v0.5.0 遗留 API, 向后兼容)\n * 新代码应使用 getAdapterForModel\n */\nexport function getAdapter(provider: string): ProviderAdapter {\n const a = adapterRegistry[provider.toLowerCase()];\n if (a) return a;\n return defaultOpenAIAdapter;\n}\n\n/**\n * 按 ManagedModel 的 preferred_format / supported_formats 选择 adapter\n *\n * 决策顺序:\n * 1. preferred_format 非空 → 按其值返回 (anthropic | openai)\n * 2. supported_formats 含 \"anthropic\" → AnthropicAdapter\n * 3. supported_formats 含 \"openai\" → OpenAIAdapter\n * 4. 两字段均空 (旧上游) → 回落 provider 名硬编码 (原 getAdapter 行为)\n *\n * 这使得 dashscope / zhipu / deepseek 等 provider 的模型如果上游启用了\n * Anthropic 兼容端点, 也能走 /anthropic 路径, 不再被 provider 字符串硬编码到 /chat\n * 导致 tool_reference 400.\n */\nexport function getAdapterForModel(m: ManagedModel): ProviderAdapter {\n const pref = (m.preferred_format ?? '').trim().toLowerCase();\n switch (pref) {\n case 'anthropic':\n return new AnthropicAdapter();\n case 'openai':\n return new OpenAIAdapter();\n }\n\n let hasAnthropic = false;\n let hasOpenAI = false;\n for (const f of m.supported_formats ?? []) {\n switch (f.trim().toLowerCase()) {\n case 'anthropic':\n hasAnthropic = true;\n break;\n case 'openai':\n hasOpenAI = true;\n break;\n }\n }\n if (hasAnthropic) return new AnthropicAdapter();\n if (hasOpenAI) return new OpenAIAdapter();\n\n // 旧上游未填字段: 回落到 provider 名硬编码 (向后兼容)\n return getAdapter((m.provider ?? '').toLowerCase());\n}\n\nexport { AnthropicAdapter, OpenAIAdapter };\n","// adapters/anthropic.ts — 端口自 acosmi-sdk-go/adapter_anthropic.go (228 行)\n//\n// Anthropic 原生格式 adapter\n// 重构自 buildChatRequest 现有逻辑,功能完全等同。\n// 包含 buildBetas() 调用、serverTools 合入、extraBody 透传。\n\nimport {\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHighMinMaxTokens,\n ThinkingMax,\n ThinkingMaxFallbackMaxTokens,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { buildBetas } from '../betas';\nimport { ProviderFormat, type ProviderAdapter } from './index';\n\n/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */\nexport class AnthropicAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.Anthropic;\n }\n\n endpointSuffix(): string {\n return '/anthropic';\n }\n\n /**\n * 构建 Anthropic 格式请求体\n * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // 消息: rawMessages 优先于 messages\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\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n if (req.system != null) {\n body['system'] = req.system;\n }\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Thinking + Effort 组装 ──\n if (req.thinking && req.thinking.level && req.thinking.level !== '') {\n // Level 模式: SDK 接管 thinking + effort + maxTokens\n resolveThinkingLevel(body, req, caps);\n } else {\n // 兼容模式: 调用方自己拼 (保持 v0.8.0 行为)\n if (req.thinking) {\n body['thinking'] = req.thinking;\n }\n if (req.effort) {\n body['effort'] = req.effort;\n }\n }\n\n // ── Metadata + v1.6.0 endUserId 合并 ──\n // caller 显式 metadata 键 (含 user_id) 永远优先, endUserId 仅在 metadata 无 user_id 键时填入。\n // 不污染 caller 原始对象: 通过浅拷贝构造新 Record, 保留 caller 所有原有键。\n if (req.metadata || (req.endUserId && req.endUserId !== '')) {\n const meta: Record<string, unknown> = {};\n if (req.metadata) {\n for (const [k, v] of Object.entries(req.metadata)) {\n meta[k] = v;\n }\n }\n if (req.endUserId && req.endUserId !== '' && meta['user_id'] === undefined) {\n meta['user_id'] = req.endUserId;\n }\n body['metadata'] = meta;\n }\n\n // ── 合入 tools + serverTools ──\n const allTools: unknown[] = [];\n if (req.tools != null) {\n // tools 透传 — 在 Go 侧通过 json.Marshal+Unmarshal 实现深拷贝, TS 中我们直接拼接\n try {\n const parsed = JSON.parse(JSON.stringify(req.tools));\n if (Array.isArray(parsed)) {\n for (const t of parsed) allTools.push(t);\n }\n } catch {\n // ignore — 与 Go 侧 silent ignore 行为一致\n }\n }\n for (const st of req.serverTools ?? []) {\n const schema: Record<string, unknown> = {\n type: st.type,\n name: st.name,\n };\n if (st.config) {\n for (const [k, v] of Object.entries(st.config)) {\n schema[k] = v;\n }\n }\n allTools.push(schema);\n }\n if (allTools.length > 0) {\n body['tools'] = allTools;\n }\n\n // ── 推理控制 (非 Level 模式时的透传) ──\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n if (req.outputConfig) {\n body['output_config'] = req.outputConfig;\n }\n\n // ── Beta 自动组装 ──\n const betas = buildBetas(caps, req);\n if (betas.length > 0) {\n body['betas'] = betas;\n }\n\n // ── 透传 extraBody ──\n // 注意: extraBody 在 resolveThinkingLevel 之后执行,\n // Level 模式下 thinking / effort / max_tokens / temperature 已由 SDK 管理,\n // extraBody 中不应包含这些 key, 否则会覆盖 SDK 计算结果\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n body[k] = v;\n }\n }\n\n return body;\n }\n\n /**\n * 解析 Anthropic 格式同步响应\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 Anthropic JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n // 尝试 APIResponse 包装\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 // 非 JSON 或无 wrapper 格式 — 用原始 bodyStr\n }\n\n let resp: ChatResponse;\n try {\n resp = JSON.parse(raw) as ChatResponse;\n } catch (e) {\n throw new Error(\n `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n resp.tokenRemaining = -1;\n resp.callRemaining = -1;\n resp.modelTokenRemaining = -1;\n resp.modelTokenRemainingETU = -1;\n return resp;\n }\n\n /**\n * 解析 Anthropic SSE 行\n *\n * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),\n * 但 Nexus Gateway 的 ChatStream 路径会追加 [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 return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 根据 ThinkingConfig.level 自动组装请求参数\n *\n * off → thinking=disabled, 不设 effort, 不动 maxTokens\n * high → thinking=adaptive, effort=high, maxTokens 至少 32K\n * max → thinking=adaptive, effort=max, maxTokens 拉到模型上限\n *\n * 旧模型不支持 adaptive 时, 回退到 enabled + budget_tokens = maxTokens - 1\n */\nexport function resolveThinkingLevel(\n body: Record<string, unknown>,\n req: ChatRequest,\n caps: ModelCapabilities,\n): void {\n const level = req.thinking?.level ?? '';\n\n // ── off ──\n if (level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n return;\n }\n\n // ── 模型不支持任何形式的 thinking → 不动 maxTokens, 直接返回 ──\n if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {\n return;\n }\n\n // ── 确定 maxTokens ──\n let maxTokens = req.max_tokens ?? 0;\n if (maxTokens <= 0) {\n maxTokens = ThinkingHighMinMaxTokens;\n }\n\n if (level === ThinkingMax) {\n let modelMax = caps.max_output_tokens;\n if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;\n if (maxTokens < modelMax) maxTokens = modelMax;\n } else {\n if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;\n }\n body['max_tokens'] = maxTokens;\n\n // ── thinking ──\n // adaptive 优先 (Claude 4.x); 旧模型回退 enabled + full budget\n if (caps.supports_adaptive_thinking) {\n const thinking: Record<string, unknown> = { type: 'adaptive' };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n } else if (caps.supports_thinking) {\n let budget = maxTokens - 1;\n if (budget < 1024) budget = 1024;\n const thinking: Record<string, unknown> = {\n type: 'enabled',\n budget_tokens: budget,\n };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n }\n\n // ── effort ──\n // 仅支持 effort 的模型发送此参数\n if (caps.supports_effort) {\n let effortLevel = 'high';\n if (level === ThinkingMax && caps.supports_max_effort) {\n effortLevel = 'max';\n }\n body['effort'] = { level: effortLevel };\n }\n\n // ── API 约束: thinking 与 temperature 互斥 ──\n delete body['temperature'];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/models/types.ts","../../../src/shared/errors.ts","../../../src/models/betas.ts","../../../src/models/adapters/index.ts","../../../src/models/adapters/anthropic.ts"],"names":[],"mappings":"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","file":"anthropic.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/** 图片生成请求 (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/**\n * 模型可接收的用户输入模态 (v1.2+).\n *\n * - 'text': 文本输入\n * - 'image': 截图 / 图片输入 (多模态)\n *\n * ManagedModel.inputModalities 包含 'image' 才允许向该模型直接发图;\n * 缺失字段时调用方应保守按 text-only / unknown 处理.\n */\nexport type InputModality = 'text' | 'image';\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 * 当前用户在此模型上的桶余额聚合 (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 * 取值: 'text' | 'image' 的子集. 'image' 表示模型可直接接收 screenshot/image 输入.\n *\n * 兼容上游字段名 input_modalities (snake_case) — listModels 在写缓存前会做归一化.\n *\n * **缺失语义**: 上游未下发时该字段为 undefined, 调用方必须保守按 text-only / unknown\n * 处理, 严禁默认假设支持 image. 同样严禁用模型名 substring 反推 modality.\n */\n inputModalities?: InputModality[];\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/**\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}\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 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/**\n * 从 StreamEvent 中解析搜索来源\n * 返回 null 表示该事件不是 sources 类型\n */\nexport function parseSourcesEvent(ev: StreamEvent): SourcesEvent | null {\n let wrapper: { type?: string; sources?: WebSearchSource[]; session_id?: string };\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\n constructor(statusCode: number, opts: { type?: string; message?: string; retryAfter?: number; body?: string } = {}) {\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 }\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\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/**\n * 流式失败事件的结构化表示。\n *\n * 由 gateway 的 `managed_model_stream_failed` 事件解析得到。客户端可通过\n * instanceof 提取并按 code/retryable 决策。\n */\nexport class StreamError extends Error {\n /** 例: \"empty_response\" / \"rate_limit\" / \"overloaded\" / \"\" */\n code: string;\n /** 例: \"provider\" / \"settlement\" */\n stage: string;\n /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */\n userMessage: string;\n /** gateway 原始 error 字符串 */\n rawError: string;\n /** 客户端是否值得重试 */\n retryable: boolean;\n\n constructor(opts: { code?: string; stage?: string; message?: string; rawError?: string; retryable?: boolean } = {}) {\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 ?? false;\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.stage = stage;\n this.userMessage = userMessage;\n this.rawError = rawError;\n this.retryable = retryable;\n }\n}\n","// betas.ts — 端口自 acosmi-sdk-go/betas.go\n//\n// Beta Header 常量 — 经联网验证的真实 Anthropic API beta 值。\n// 虚构/错误日期的 header 已剔除。\n\nimport type { ChatRequest, ModelCapabilities } from './types';\nimport { ThinkingOff } from './types';\n\n/** ISP: 交错思考 */\nconst betaInterleavedThinking = 'interleaved-thinking-2025-05-14';\n/** 1M 上下文 (retiring 2026-04-30) */\nconst betaContext1M = 'context-1m-2025-08-07';\n/** 上下文编辑 */\nconst betaContextManagement = 'context-management-2025-06-27';\n/** 结构化输出 */\nconst betaStructuredOutputs = 'structured-outputs-2025-11-13';\n/** Tool Search */\nconst betaAdvancedToolUse = 'advanced-tool-use-2025-11-20';\n/** Effort 控制 (Opus 4.5 需要, 4.6 stable) */\nconst betaEffort = 'effort-2025-11-24';\n/** 缓存作用域隔离 */\nconst betaPromptCachingScope = 'prompt-caching-scope-2026-01-05';\n/** 快速推理 (Opus 4.6) */\nconst betaFastMode = 'fast-mode-2026-02-01';\n/** 思考脱敏 */\nconst betaRedactThinking = 'redact-thinking-2026-02-12';\n/** 高效工具 (Claude 3.7, Claude 4 内置) */\nconst betaTokenEfficientTools = 'token-efficient-tools-2025-02-19';\n\n/** 根据模型能力和请求参数自动组装 beta header 列表 */\nexport function buildBetas(caps: ModelCapabilities, req: ChatRequest): string[] {\n const betas: string[] = [];\n\n // ── 思考相关 ──\n if (caps.supports_isp) {\n betas.push(betaInterleavedThinking);\n betas.push(betaContextManagement);\n }\n if (caps.supports_redact_thinking && req.thinking && req.thinking.display === 'summary') {\n betas.push(betaRedactThinking);\n }\n\n // ── 上下文 ──\n if (caps.supports_1m_context) {\n betas.push(betaContext1M);\n }\n\n // ── 输出控制 (互斥: structured-outputs ⊕ token-efficient-tools) ──\n const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;\n if (hasStructuredOutput) {\n betas.push(betaStructuredOutputs);\n } else if (caps.supports_token_efficient) {\n betas.push(betaTokenEfficientTools);\n }\n\n // ── Tool Search ──\n if (caps.supports_tool_search) {\n betas.push(betaAdvancedToolUse);\n }\n\n // ── 推理控制 ──\n // Level 模式下 resolveThinkingLevel 直接写 body[\"effort\"],req.effort 仍为 undefined,\n // 所以需额外判断 Level 是否激活了 effort\n const needsEffort =\n req.effort != null ||\n (req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff);\n if (caps.supports_effort && needsEffort) {\n betas.push(betaEffort);\n }\n if (caps.supports_fast_mode && req.speed === 'fast') {\n betas.push(betaFastMode);\n }\n\n // ── 缓存 ──\n if (caps.supports_prompt_cache) {\n betas.push(betaPromptCachingScope);\n }\n\n // ── 合并客户端显式传入 (去重) ──\n return uniqueMerge(betas, req.betas ?? []);\n}\n\n/** 合并两个字符串数组并去重, 保留顺序 */\nexport function uniqueMerge(base: string[], extra: string[]): string[] {\n if (extra.length === 0) return base;\n const seen = new Set<string>(base);\n for (const s of extra) {\n if (!seen.has(s)) {\n base.push(s);\n seen.add(s);\n }\n }\n return base;\n}\n","// adapters/index.ts — 端口自 acosmi-sdk-go/adapter.go (v0.19.0)\n//\n// SDK 层按上游托管模型元数据选路:\n// preferred_format == \"anthropic\" → AnthropicAdapter → POST /managed-models/:id/anthropic\n// preferred_format == \"openai\" → OpenAIAdapter → POST /managed-models/:id/chat\n//\n// 旧上游若未返回 preferred_format / supported_formats, SDK 才回落到 provider 名称:\n// provider == \"anthropic\" / \"acosmi\" → AnthropicAdapter\n// 其他 provider → OpenAIAdapter\n//\n// SDK 只负责: 格式路由 + 请求结构转换 + 响应结构转换\n// 厂商特定协议差异 (endpoint/auth/region/字段裁剪) 由 Nexus Gateway Profile 处理\n//\n// ============================================================================\n// 红线 (双产品消费): AnthropicAdapter + OpenAIAdapter 等地位, 不可合并/降级\n// ============================================================================\n\nimport type { ManagedModel, ChatRequest, ChatResponse, StreamEvent, ModelCapabilities } from '../types';\n\n/** 标识请求格式 */\nexport enum ProviderFormat {\n /** Anthropic 原生格式 */\n Anthropic = 0,\n /** OpenAI 兼容格式 */\n OpenAI = 1,\n}\n\n/** 将 ChatRequest 转换为特定格式的 adapter 接口 */\nexport interface ProviderAdapter {\n /** 此 adapter 使用的请求格式 */\n format(): ProviderFormat;\n\n /**\n * API 路径后缀\n * Anthropic: \"/anthropic\", OpenAI: \"/chat\"\n */\n endpointSuffix(): string;\n\n /**\n * 将 ChatRequest 转换为 HTTP body (object → JSON.stringify)\n * caps 用于条件化字段注入 (如 betas)\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;\n\n /** 解析同步响应 body 为 ChatResponse */\n parseResponse(body: Uint8Array | string): ChatResponse;\n\n /**\n * 解析一行 SSE data 为 StreamEvent\n * 返回 { event, done }; done=true 表示流结束 ([DONE] 或 message_stop)\n */\n parseStreamLine(eventType: string, data: string): { event: StreamEvent; done: boolean };\n}\n\n// 实现 import 在文件末尾, 避免循环依赖(adapter impl 也需引用 ProviderFormat)\nimport { AnthropicAdapter } from './anthropic';\nimport { OpenAIAdapter } from './openai';\n\n/** 按 provider 名称映射 adapter */\nconst adapterRegistry: Record<string, ProviderAdapter> = {\n anthropic: new AnthropicAdapter(),\n /** Acosmi 自有模型走 Anthropic 格式 */\n acosmi: new AnthropicAdapter(),\n};\n\n/** 非 Anthropic 厂商的默认 adapter */\nconst defaultOpenAIAdapter: ProviderAdapter = new OpenAIAdapter();\n\n/**\n * 根据 provider 返回对应的 adapter (v0.5.0 遗留 API, 向后兼容)\n * 新代码应使用 getAdapterForModel\n */\nexport function getAdapter(provider: string): ProviderAdapter {\n const a = adapterRegistry[provider.toLowerCase()];\n if (a) return a;\n return defaultOpenAIAdapter;\n}\n\n/**\n * 按 ManagedModel 的 preferred_format / supported_formats 选择 adapter\n *\n * 决策顺序:\n * 1. preferred_format 非空 **且** 该格式在 supported_formats 内 (或 supported_formats 未声明)\n * → 按其值返回 (anthropic | openai)\n * 2. supported_formats 含 \"anthropic\" → AnthropicAdapter\n * 3. supported_formats 含 \"openai\" → OpenAIAdapter\n * 4. 两字段均空 (旧上游) → 回落 provider 名硬编码 (原 getAdapter 行为)\n *\n * 这使得 dashscope / zhipu / deepseek 等 provider 的模型如果上游启用了\n * Anthropic 兼容端点, 也能走 /anthropic 路径, 不再被 provider 字符串硬编码到 /chat\n * 导致 tool_reference 400.\n *\n * [格式一致性护栏 2026-05-29] preferred_format 仅在确被 supported_formats 收录时才采信:\n * 防止上游元数据漂移 (preferred_format=anthropic 但 supported_formats=[openai]) 把 SDK\n * 路由到模型并不支持的格式端点 (撞 /anthropic \"未绑定 Anthropic\" 4xx)。这是网关侧\n * \"同 model_id 双 profile 选行\" 根因修复在 SDK 侧的同构护栏。\n */\nexport function getAdapterForModel(m: ManagedModel): ProviderAdapter {\n let hasAnthropic = false;\n let hasOpenAI = false;\n for (const f of m.supported_formats ?? []) {\n switch (f.trim().toLowerCase()) {\n case 'anthropic':\n hasAnthropic = true;\n break;\n case 'openai':\n hasOpenAI = true;\n break;\n }\n }\n const declared = hasAnthropic || hasOpenAI;\n\n const pref = (m.preferred_format ?? '').trim().toLowerCase();\n switch (pref) {\n case 'anthropic':\n if (!declared || hasAnthropic) return new AnthropicAdapter();\n break;\n case 'openai':\n if (!declared || hasOpenAI) return new OpenAIAdapter();\n break;\n }\n\n if (hasAnthropic) return new AnthropicAdapter();\n if (hasOpenAI) return new OpenAIAdapter();\n\n // 旧上游未填字段: 回落到 provider 名硬编码 (向后兼容)\n return getAdapter((m.provider ?? '').toLowerCase());\n}\n\nexport { AnthropicAdapter, OpenAIAdapter };\nexport { OpenAIStreamConverter, newOpenAIStreamConverter } from './openai';\n","// adapters/anthropic.ts — 端口自 acosmi-sdk-go/adapter_anthropic.go (228 行)\n//\n// Anthropic 原生格式 adapter\n// 重构自 buildChatRequest 现有逻辑,功能完全等同。\n// 包含 buildBetas() 调用、serverTools 合入、extraBody 透传。\n\nimport {\n type ChatRequest,\n type ChatResponse,\n type ModelCapabilities,\n type StreamEvent,\n ThinkingHighMinMaxTokens,\n ThinkingMax,\n ThinkingMaxFallbackMaxTokens,\n ThinkingOff,\n} from '../types';\nimport { BusinessError } from '../../shared/errors';\nimport { buildBetas } from '../betas';\nimport { ProviderFormat, type ProviderAdapter } from './index';\n\n/**\n * SDK 在 buildRequestBody 中写入并管理的请求体字段 (精确 body key)。\n * extraBody 中若出现同名 key 会覆盖 SDK 计算结果 (如 Level 模式下的 thinking/effort/max_tokens、\n * 自动组装的 betas、被刻意删除的 temperature), 故透传时强制跳过这些 key。\n */\nconst ANTHROPIC_SDK_MANAGED_BODY_KEYS: ReadonlySet<string> = new Set([\n 'thinking',\n 'effort',\n 'max_tokens',\n 'temperature',\n 'betas',\n]);\n\n/** 实现 ProviderAdapter, 用于 Anthropic 原生模型 */\nexport class AnthropicAdapter implements ProviderAdapter {\n format(): ProviderFormat {\n return ProviderFormat.Anthropic;\n }\n\n endpointSuffix(): string {\n return '/anthropic';\n }\n\n /**\n * 构建 Anthropic 格式请求体\n * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理\n */\n buildRequestBody(caps: ModelCapabilities, req: ChatRequest): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n\n // 消息: rawMessages 优先于 messages\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\n if (req.max_tokens && req.max_tokens > 0) {\n body['max_tokens'] = req.max_tokens;\n }\n if (req.system != null) {\n body['system'] = req.system;\n }\n if (req.temperature != null) {\n body['temperature'] = req.temperature;\n }\n\n // ── Thinking + Effort 组装 ──\n if (req.thinking && req.thinking.level && req.thinking.level !== '') {\n // Level 模式: SDK 接管 thinking + effort + maxTokens\n resolveThinkingLevel(body, req, caps);\n } else {\n // 兼容模式: 调用方自己拼 (保持 v0.8.0 行为)\n if (req.thinking) {\n body['thinking'] = req.thinking;\n }\n if (req.effort) {\n body['effort'] = req.effort;\n }\n }\n\n // ── Metadata + v1.6.0 endUserId 合并 ──\n // caller 显式 metadata 键 (含 user_id) 永远优先, endUserId 仅在 metadata 无 user_id 键时填入。\n // 不污染 caller 原始对象: 通过浅拷贝构造新 Record, 保留 caller 所有原有键。\n if (req.metadata || (req.endUserId && req.endUserId !== '')) {\n const meta: Record<string, unknown> = {};\n if (req.metadata) {\n for (const [k, v] of Object.entries(req.metadata)) {\n meta[k] = v;\n }\n }\n if (req.endUserId && req.endUserId !== '' && meta['user_id'] === undefined) {\n meta['user_id'] = req.endUserId;\n }\n body['metadata'] = meta;\n }\n\n // ── 合入 tools + serverTools ──\n const allTools: unknown[] = [];\n if (req.tools != null) {\n // tools 透传 — 在 Go 侧通过 json.Marshal+Unmarshal 实现深拷贝, TS 中我们直接拼接\n try {\n const parsed = JSON.parse(JSON.stringify(req.tools));\n if (Array.isArray(parsed)) {\n for (const t of parsed) allTools.push(t);\n }\n } catch {\n // ignore — 与 Go 侧 silent ignore 行为一致\n }\n }\n for (const st of req.serverTools ?? []) {\n const schema: Record<string, unknown> = {\n type: st.type,\n name: st.name,\n };\n if (st.config) {\n for (const [k, v] of Object.entries(st.config)) {\n schema[k] = v;\n }\n }\n allTools.push(schema);\n }\n if (allTools.length > 0) {\n body['tools'] = allTools;\n }\n\n // ── 推理控制 (非 Level 模式时的透传) ──\n if (req.speed && req.speed !== '') {\n body['speed'] = req.speed;\n }\n if (req.outputConfig) {\n body['output_config'] = req.outputConfig;\n }\n\n // ── Beta 自动组装 ──\n const betas = buildBetas(caps, req);\n if (betas.length > 0) {\n body['betas'] = betas;\n }\n\n // ── 透传 extraBody ──\n // 注意: extraBody 在 resolveThinkingLevel 之后执行,\n // Level 模式下 thinking / effort / max_tokens / temperature / betas 已由 SDK 管理,\n // extraBody 中若含这些 key 会覆盖 SDK 计算结果 — 强制跳过并告警,\n // 但保留 extraBody 透传其它 (非管理) 字段的正当能力。\n if (req.extraBody) {\n for (const [k, v] of Object.entries(req.extraBody)) {\n if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {\n console.warn(\n `acosmi-sdk: extraBody key \"${k}\" is SDK-managed and was ignored (use the dedicated request field instead).`,\n );\n continue;\n }\n body[k] = v;\n }\n }\n\n return body;\n }\n\n /**\n * 解析 Anthropic 格式同步响应\n * 兼容 APIResponse 包装 {\"code\":0,\"data\":{...}} 和裸 Anthropic JSON 两种格式\n */\n parseResponse(bodyInput: Uint8Array | string): ChatResponse {\n const bodyStr = typeof bodyInput === 'string' ? bodyInput : new TextDecoder().decode(bodyInput);\n\n // 尝试 APIResponse 包装\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 // 非 JSON 或无 wrapper 格式 — 用原始 bodyStr\n }\n\n let resp: ChatResponse;\n try {\n resp = JSON.parse(raw) as ChatResponse;\n } catch (e) {\n throw new Error(\n `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n resp.tokenRemaining = -1;\n resp.callRemaining = -1;\n resp.modelTokenRemaining = -1;\n resp.modelTokenRemainingETU = -1;\n return resp;\n }\n\n /**\n * 解析 Anthropic SSE 行\n *\n * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),\n * 但 Nexus Gateway 的 ChatStream 路径会追加 [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 return { event: { event: eventType, data }, done: false };\n }\n}\n\n/**\n * 根据 ThinkingConfig.level 自动组装请求参数\n *\n * off → thinking=disabled, 不设 effort, 不动 maxTokens\n * high → thinking=adaptive, effort=high, maxTokens 至少 32K\n * max → thinking=adaptive, effort=max, maxTokens 拉到模型上限\n *\n * 旧模型不支持 adaptive 时, 回退到 enabled + budget_tokens = maxTokens - 1\n */\nexport function resolveThinkingLevel(\n body: Record<string, unknown>,\n req: ChatRequest,\n caps: ModelCapabilities,\n): void {\n const level = req.thinking?.level ?? '';\n\n // ── off ──\n if (level === ThinkingOff) {\n body['thinking'] = { type: 'disabled' };\n return;\n }\n\n // ── 模型不支持任何形式的 thinking → 不动 maxTokens, 直接返回 ──\n if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {\n return;\n }\n\n // ── 确定 maxTokens ──\n let maxTokens = req.max_tokens ?? 0;\n if (maxTokens <= 0) {\n maxTokens = ThinkingHighMinMaxTokens;\n }\n\n if (level === ThinkingMax) {\n let modelMax = caps.max_output_tokens;\n if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;\n if (maxTokens < modelMax) maxTokens = modelMax;\n } else {\n if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;\n }\n body['max_tokens'] = maxTokens;\n\n // ── thinking ──\n // adaptive 优先 (Claude 4.x); 旧模型回退 enabled + full budget\n if (caps.supports_adaptive_thinking) {\n const thinking: Record<string, unknown> = { type: 'adaptive' };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n } else if (caps.supports_thinking) {\n let budget = maxTokens - 1;\n if (budget < 1024) budget = 1024;\n const thinking: Record<string, unknown> = {\n type: 'enabled',\n budget_tokens: budget,\n };\n if (req.thinking?.display) thinking['display'] = req.thinking.display;\n body['thinking'] = thinking;\n }\n\n // ── effort ──\n // 仅支持 effort 的模型发送此参数\n if (caps.supports_effort) {\n let effortLevel = 'high';\n if (level === ThinkingMax && caps.supports_max_effort) {\n effortLevel = 'max';\n }\n body['effort'] = { level: effortLevel };\n }\n\n // ── API 约束: thinking 与 temperature 互斥 ──\n delete body['temperature'];\n}\n"]}
|