dsh-tap 0.20.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/AGENTS.md +99 -0
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.en.md +59 -0
- package/README.md +256 -0
- package/cordis.patch.yml +385 -0
- package/core/bridge.js +698 -0
- package/core/json-store.js +91 -0
- package/core/rotation.js +108 -0
- package/core/usage-meter.js +176 -0
- package/docs/diagnosis-cache-quota.md +181 -0
- package/docs/diagnosis-qoder-flash.md +67 -0
- package/docs/diagnosis-trae-3003.md +302 -0
- package/docs/goals/bridge-port-host-split.md +91 -0
- package/docs/goals/desktop-adaptation.md +106 -0
- package/docs/goals/qoder-cn-provider-design.md +308 -0
- package/docs/goals/settings-card-ux-redesign-plan.md +1680 -0
- package/docs/goals/settings-card-ux-redesign.md +162 -0
- package/docs/goals/trae-agent-v3.md +41 -0
- package/docs/goals/trae-work-cn-repair.md +44 -0
- package/docs/goals/v0.8-/351/242/235/345/272/246/345/217/257/350/247/201-/346/250/241/345/236/213/345/212/250/346/200/201/345/214/226-/345/244/232/346/234/215/345/212/241/345/225/206.md +85 -0
- package/docs/pitfalls.md +105 -0
- package/docs/reverse/trae-cloud-api.md +218 -0
- package/docs/reverse/trae-model-catalog.md +129 -0
- package/docs/reverse/traework-cn.md +520 -0
- package/docs/rules/STATE.md +198 -0
- package/docs/rules/content-moderation.md +54 -0
- package/docs/rules/dev-role-boundary.md +94 -0
- package/docs/rules/extra-providers.md +42 -0
- package/docs/rules/gateway-facts.md +91 -0
- package/docs/rules/oauth-handshake.md +76 -0
- package/docs/rules/prompt-cache.md +93 -0
- package/docs/rules/quota-signals.md +125 -0
- package/docs/rules/routing.md +84 -0
- package/docs/rules/templates/oauth-reverse-checklist.md +42 -0
- package/docs/rules/trae-surface.md +242 -0
- package/docs/rules/ua-validation.md +81 -0
- package/host-config.js +282 -0
- package/index.js +2306 -0
- package/lib/client.js +2893 -0
- package/local-scan.js +104 -0
- package/package.json +82 -0
- package/providers/ark/index.js +11 -0
- package/providers/bailian/index.js +10 -0
- package/providers/bigmodel/index.js +11 -0
- package/providers/codebuddy/agenttool.js +122 -0
- package/providers/codebuddy/catalog.js +227 -0
- package/providers/codebuddy/errors.js +44 -0
- package/providers/codebuddy/headers.js +36 -0
- package/providers/codebuddy/images.js +125 -0
- package/providers/codebuddy/index.js +123 -0
- package/providers/codebuddy/oauth.js +279 -0
- package/providers/deepseek/index.js +11 -0
- package/providers/moonshot/index.js +11 -0
- package/providers/openai-compat.js +177 -0
- package/providers/openrouter/index.js +27 -0
- package/providers/qoder/catalog.js +145 -0
- package/providers/qoder/cosy.js +419 -0
- package/providers/qoder/gateway.js +563 -0
- package/providers/qoder/index.js +116 -0
- package/providers/qoder/oauth.js +364 -0
- package/providers/qoder/qoder_auth.wasm +0 -0
- package/providers/qoder/quota.js +56 -0
- package/providers/qwen/index.js +15 -0
- package/providers/tool-pairing.js +129 -0
- package/providers/trae/catalog.js +103 -0
- package/providers/trae/errors.js +85 -0
- package/providers/trae/gateway.js +853 -0
- package/providers/trae/index.js +126 -0
- package/providers/trae/oauth.js +443 -0
- package/providers/trae/quota.js +75 -0
- package/providers/trae/remote.js +365 -0
- package/scripts/capture-cache.mjs +65 -0
- package/scripts/capture-traffic.mjs +83 -0
- package/scripts/hermes-probe-dev-role.mjs +263 -0
- package/scripts/measure-latency.mjs +253 -0
- package/scripts/probe-ark-thinking.mjs +298 -0
- package/scripts/probe-cache-decline.mjs +292 -0
- package/scripts/probe-cache-ttl.mjs +221 -0
- package/scripts/probe-cache.mjs +156 -0
- package/scripts/probe-codebuddy-efforts.mjs +355 -0
- package/scripts/probe-codebuddy-tier-wiring.mjs +244 -0
- package/scripts/probe-effort-gaps.mjs +133 -0
- package/scripts/probe-media.mjs +115 -0
- package/scripts/probe-moderation.mjs +159 -0
- package/scripts/probe-oauth.mjs +617 -0
- package/scripts/probe-qoder-attribution-arm8.mjs +92 -0
- package/scripts/probe-qoder-attribution-arm9.mjs +102 -0
- package/scripts/probe-qoder-attribution.mjs +266 -0
- package/scripts/probe-qoder-flash-confirm.mjs +94 -0
- package/scripts/probe-qoder-live.mjs +344 -0
- package/scripts/probe-qoder-matrix.mjs +274 -0
- package/scripts/probe-qoder-null-content.mjs +153 -0
- package/scripts/probe-qoder-pairing.mjs +308 -0
- package/scripts/probe-qoder-quota.mjs +162 -0
- package/scripts/probe-qoder-thinking-config.mjs +52 -0
- package/scripts/probe-qoder-thinking-efforts.mjs +269 -0
- package/scripts/probe-quota.mjs +136 -0
- package/scripts/probe-quota2.mjs +144 -0
- package/scripts/probe-routing.mjs +226 -0
- package/scripts/probe-trae-3003-diagnosis.mjs +139 -0
- package/scripts/probe-trae-agent-v3.mjs +399 -0
- package/scripts/probe-trae-efforts.mjs +149 -0
- package/scripts/probe-trae-live.mjs +147 -0
- package/scripts/probe-trae-max-effort.mjs +179 -0
- package/scripts/probe-trae-model-routing.mjs +381 -0
- package/scripts/probe-trae-thinking-scene.mjs +238 -0
- package/scripts/probe-trae-transport-outage.mjs +176 -0
- package/scripts/probe-ua.mjs +508 -0
- package/scripts/trae-model-catalog.mjs +632 -0
- package/scripts/verify-agents-md.mjs +45 -0
- package/scripts/verify-bridge.mjs +1041 -0
- package/scripts/verify-core-generic.mjs +302 -0
- package/scripts/verify-desktop-acceptance.mjs +184 -0
- package/scripts/verify-host-config.mjs +265 -0
- package/scripts/verify-models.mjs +390 -0
- package/scripts/verify-providers.mjs +255 -0
- package/scripts/verify-qoder-provider.mjs +1020 -0
- package/scripts/verify-rotation.mjs +308 -0
- package/scripts/verify-trae-model-catalog.mjs +284 -0
- package/scripts/verify-trae-provider.mjs +1451 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* providers/qoder/cosy.js — Qoder COSY 签名运行时(WASM + 手写 wasm-bindgen 胶水)。
|
|
3
|
+
*
|
|
4
|
+
* 来源与版权边界:
|
|
5
|
+
* - qoder_auth.wasm:官方 CLI(@qodercn-ai/qoderclicn 1.1.57)bundle 内嵌
|
|
6
|
+
* base64(offset 25462)解出的原始字节,未修改——厂商专有组件,随官方
|
|
7
|
+
* 包分发;本插件不复刻其算法,仅在运行时加载调用(逆向校准证据见
|
|
8
|
+
* docs/goals/qoder-cn-provider-design.md §2.3 与 2026-09-20 探测)。
|
|
9
|
+
* - 本文件的胶水代码是按 wasm-bindgen ABI 惯例的干净实现(heap 表 /
|
|
10
|
+
* passString / stack-pointer 返回槽 / handleError),不复制 bundle 文本。
|
|
11
|
+
*
|
|
12
|
+
* 关键事实(2026-09-20 实测,证据 C:\tmp\qoder-re\*.mjs 本地留存):
|
|
13
|
+
* - prepareRequest(/algo 面)会把任意路径重写为 /algo 前缀 + ?Encode=1,
|
|
14
|
+
* 签名绑定改写后的 URL——聊天面绝不能走它(401/404 的根因)。
|
|
15
|
+
* - prepareInferRequest(endpoint, bodyJson, modelKey, modelSource) 是聊天面
|
|
16
|
+
* 签名入口:URL 恒映射到 infer 节点的
|
|
17
|
+
* /algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1,
|
|
18
|
+
* body 由 WASM 加密,头组含 Bearer COSY.* 与 Cosy-* 全家。
|
|
19
|
+
* - infer 节点由 region 发现服务给出(/api/v3/service/region/endpoints,
|
|
20
|
+
* sign 匿名模式可取,响应需 decrypt):CN = gateway.qoder.com.cn。
|
|
21
|
+
* - RequestResult.headers 是 JS Map(wasm 侧 __wbg_new(Map) + set 组装的)——
|
|
22
|
+
* 必须 Object.fromEntries 后才能进 fetch;直接展开 {...map} 会得到空头组
|
|
23
|
+
* (首次手写胶水实测:服务器直接断连,无错误响应)。
|
|
24
|
+
*
|
|
25
|
+
* 线程/并发:WASM 实例单例(模块级 promise 缓存);QoderContext 持有的是不可变
|
|
26
|
+
* 凭据快照,每次调用独立签名——凭据轮换(refresh 后 accessToken 变化)时
|
|
27
|
+
* ensureContext 检测令牌字符串变化并重建上下文(构造很便宜,WASM 不重载)。
|
|
28
|
+
*
|
|
29
|
+
* clientMetadata(2026-09-22 对齐官方):`{"client_type":5}`——wasm 把该字段映射成
|
|
30
|
+
* `Cosy-ClientType` 头,官方 CLI/IDE 实测线缆值恒为 **5**;我们早期传字符串
|
|
31
|
+
* `'qoder'` 会让出站头带上 `Cosy-ClientType: qoder`(与官方客户端签名不一致,
|
|
32
|
+
* 网关侧可据此分辨第三方客户端)。这是**头保真度**修正,与模型可用性无关
|
|
33
|
+
* (qfmodel 上游节点故障两种值下都复现)。
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { readFileSync } from 'node:fs'
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// wasm-bindgen ABI 最小胶水(标准惯例实现)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const heap = new Array(1024).fill(undefined)
|
|
43
|
+
heap.push(undefined, null, true, false)
|
|
44
|
+
let heapNext = heap.length
|
|
45
|
+
|
|
46
|
+
function addHeapObject(obj) {
|
|
47
|
+
if (heapNext === heap.length) heap.push(heap.length + 1)
|
|
48
|
+
const idx = heapNext
|
|
49
|
+
heapNext = heap[idx]
|
|
50
|
+
heap[idx] = obj
|
|
51
|
+
return idx
|
|
52
|
+
}
|
|
53
|
+
function getObject(idx) { return heap[idx] }
|
|
54
|
+
function dropObject(idx) {
|
|
55
|
+
if (idx < 1028) return
|
|
56
|
+
heap[idx] = heapNext
|
|
57
|
+
heapNext = idx
|
|
58
|
+
}
|
|
59
|
+
function takeObject(idx) {
|
|
60
|
+
const ret = getObject(idx)
|
|
61
|
+
dropObject(idx)
|
|
62
|
+
return ret
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let wasm = null
|
|
66
|
+
let cachedU8 = null
|
|
67
|
+
let cachedDV = null
|
|
68
|
+
const u8 = () => {
|
|
69
|
+
if (!cachedU8 || cachedU8.byteLength === 0) cachedU8 = new Uint8Array(wasm.memory.buffer)
|
|
70
|
+
return cachedU8
|
|
71
|
+
}
|
|
72
|
+
const dv = () => {
|
|
73
|
+
if (!cachedDV || cachedDV.buffer !== wasm.memory.buffer) cachedDV = new DataView(wasm.memory.buffer)
|
|
74
|
+
return cachedDV
|
|
75
|
+
}
|
|
76
|
+
const encoder = new TextEncoder()
|
|
77
|
+
const decoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: false })
|
|
78
|
+
|
|
79
|
+
let WASM_VECTOR_LEN = 0
|
|
80
|
+
function passString(str) {
|
|
81
|
+
const bytes = encoder.encode(str)
|
|
82
|
+
const ptr = wasm.__wbindgen_export2(bytes.length, 1) >>> 0
|
|
83
|
+
u8().subarray(ptr, ptr + bytes.length).set(bytes)
|
|
84
|
+
WASM_VECTOR_LEN = bytes.length
|
|
85
|
+
return ptr
|
|
86
|
+
}
|
|
87
|
+
const optString = (s) => (s == null ? 0 : passString(s))
|
|
88
|
+
const getString = (ptr, len) => decoder.decode(u8().subarray(ptr >>> 0, (ptr >>> 0) + len))
|
|
89
|
+
const getU8View = (ptr, len) => u8().subarray(ptr >>> 0, (ptr >>> 0) + len)
|
|
90
|
+
|
|
91
|
+
function handleError(fn, args) {
|
|
92
|
+
try {
|
|
93
|
+
return fn.apply(null, args)
|
|
94
|
+
} catch (err) {
|
|
95
|
+
wasm.__wbindgen_export(addHeapObject(err))
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nullOr = (x) => (typeof x === 'undefined' ? null : x)
|
|
101
|
+
const isNullish = (x) => x == null
|
|
102
|
+
|
|
103
|
+
function buildImports() {
|
|
104
|
+
return {
|
|
105
|
+
'./qoder_auth_wasm_bg.js': {
|
|
106
|
+
__wbindgen_object_drop_ref: (i) => dropObject(i),
|
|
107
|
+
__wbg_set_08463b1df38a7e29: (a, b, c) => addHeapObject(getObject(a).set(getObject(b), getObject(c))),
|
|
108
|
+
__wbg_getRandomValues_d49329ff89a07af1: (...args) => handleError((a, b) => globalThis.crypto.getRandomValues(getU8View(a, b)), args),
|
|
109
|
+
__wbg_crypto_38df2bab126b63dc: (a) => addHeapObject(getObject(a).crypto),
|
|
110
|
+
__wbg_process_44c7a14e11e9f69e: (a) => addHeapObject(getObject(a).process),
|
|
111
|
+
__wbg_versions_276b2795b1c6a219: (a) => addHeapObject(getObject(a).versions),
|
|
112
|
+
__wbg_node_84ea875411254db1: (a) => addHeapObject(getObject(a).node),
|
|
113
|
+
__wbg_require_b4edbdcf3e2a1ef0: (...args) => handleError(() => addHeapObject(module.require), args),
|
|
114
|
+
__wbg_msCrypto_bd5a034af96bcba6: (a) => addHeapObject(getObject(a).msCrypto),
|
|
115
|
+
__wbg_getRandomValues_c44a50d8cfdaebeb: (...args) => handleError((a, b) => getObject(a).getRandomValues(getObject(b)), args),
|
|
116
|
+
__wbg_randomFillSync_6c25eac9869eb53c: (...args) => handleError((a, b) => getObject(a).randomFillSync(takeObject(b)), args),
|
|
117
|
+
__wbg_call_d578befcc3145dee: (...args) => handleError((a, b, c) => addHeapObject(getObject(a).call(getObject(b), getObject(c))), args),
|
|
118
|
+
__wbindgen_object_clone_ref: (a) => addHeapObject(getObject(a)),
|
|
119
|
+
__wbg_new_with_length_9cedd08484b73942: (a) => addHeapObject(new Uint8Array(a >>> 0)),
|
|
120
|
+
__wbg_length_0c32cb8543c8e4c8: (a) => getObject(a).length,
|
|
121
|
+
__wbg_prototypesetcall_3e05eb9545565046: (a, b, c) => Uint8Array.prototype.set.call(getU8View(a, b), getObject(c)),
|
|
122
|
+
__wbg_subarray_0f98d3fb634508ad: (a, b, c) => addHeapObject(getObject(a).subarray(b >>> 0, c >>> 0)),
|
|
123
|
+
__wbg_new_99cabae501c0a8a0: () => addHeapObject(new Map()),
|
|
124
|
+
__wbg_now_88621c9c9a4f3ffc: () => Date.now(),
|
|
125
|
+
__wbg_static_accessor_GLOBAL_THIS_a1248013d790bf5f: () => { const x = nullOr(globalThis); return isNullish(x) ? 0 : addHeapObject(x) },
|
|
126
|
+
__wbg_static_accessor_SELF_24f78b6d23f286ea: () => { const x = typeof self === 'undefined' ? null : self; return isNullish(x) ? 0 : addHeapObject(x) },
|
|
127
|
+
__wbg_static_accessor_GLOBAL_f2e0f995a21329ff: () => { const x = typeof global === 'undefined' ? null : global; return isNullish(x) ? 0 : addHeapObject(x) },
|
|
128
|
+
__wbg_static_accessor_WINDOW_59fd959c540fe405: () => { const x = typeof window === 'undefined' ? null : window; return isNullish(x) ? 0 : addHeapObject(x) },
|
|
129
|
+
__wbg___wbindgen_throw_81fc77679af83bc6: (a, b) => { throw new Error(getString(a, b)) },
|
|
130
|
+
__wbg_Error_2e59b1b37a9a34c3: (a, b) => addHeapObject(Error(getString(a, b))),
|
|
131
|
+
__wbg___wbindgen_is_object_40c5a80572e8f9d3: (a) => { const v = getObject(a); return typeof v === 'object' && v !== null ? 1 : 0 },
|
|
132
|
+
__wbg___wbindgen_is_string_b29b5c5a8065ba1a: (a) => (typeof getObject(a) === 'string' ? 1 : 0),
|
|
133
|
+
__wbg___wbindgen_is_function_49868bde5eb1e745: (a) => (typeof getObject(a) === 'function' ? 1 : 0),
|
|
134
|
+
__wbg___wbindgen_is_undefined_c0cca72b82b86f4d: (a) => (getObject(a) === undefined ? 1 : 0),
|
|
135
|
+
__wbindgen_cast_0000000000000001: (a, b) => addHeapObject(getU8View(a, b)),
|
|
136
|
+
__wbindgen_cast_0000000000000002: (a, b) => addHeapObject(getString(a, b)),
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// 类封装(方法行为与官方胶水逐点对齐:栈指针返回槽 [ok, err, flag])
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
class RequestResult {
|
|
146
|
+
static __wrap(ptr) {
|
|
147
|
+
ptr >>>= 0
|
|
148
|
+
const o = Object.create(RequestResult.prototype)
|
|
149
|
+
o.__wbg_ptr = ptr
|
|
150
|
+
return o
|
|
151
|
+
}
|
|
152
|
+
/** 释放 wasm 侧对象(幂等)。wasm 堆对象不受 JS GC 管理,不释放即泄漏。 */
|
|
153
|
+
free() {
|
|
154
|
+
if (this.__wbg_ptr) wasm.__wbg_requestresult_free(this.__wbg_ptr)
|
|
155
|
+
this.__wbg_ptr = 0
|
|
156
|
+
}
|
|
157
|
+
/** 可能返回 undefined(无 body 的签名结果)。 */
|
|
158
|
+
get body() {
|
|
159
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
160
|
+
try {
|
|
161
|
+
wasm.requestresult_body(sp, this.__wbg_ptr)
|
|
162
|
+
const p = dv().getInt32(sp + 0, true)
|
|
163
|
+
const l = dv().getInt32(sp + 4, true)
|
|
164
|
+
let out
|
|
165
|
+
if (p !== 0) {
|
|
166
|
+
out = getString(p, l).slice()
|
|
167
|
+
wasm.__wbindgen_export4(p, l, 1)
|
|
168
|
+
}
|
|
169
|
+
return out
|
|
170
|
+
} finally {
|
|
171
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** JS Map(调用方负责 Object.fromEntries)。 */
|
|
175
|
+
get headers() {
|
|
176
|
+
return takeObject(wasm.requestresult_headers(this.__wbg_ptr))
|
|
177
|
+
}
|
|
178
|
+
get url() {
|
|
179
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
180
|
+
let p
|
|
181
|
+
let l
|
|
182
|
+
try {
|
|
183
|
+
wasm.requestresult_url(sp, this.__wbg_ptr)
|
|
184
|
+
p = dv().getInt32(sp + 0, true)
|
|
185
|
+
l = dv().getInt32(sp + 4, true)
|
|
186
|
+
return getString(p, l)
|
|
187
|
+
} finally {
|
|
188
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
189
|
+
wasm.__wbindgen_export4(p, l, 1)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class QoderContext {
|
|
195
|
+
constructor(machineId, cosyVersion, userInfoJson, clientMetaJson) {
|
|
196
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
197
|
+
try {
|
|
198
|
+
const p1 = passString(machineId)
|
|
199
|
+
const l1 = WASM_VECTOR_LEN
|
|
200
|
+
const p2 = passString(cosyVersion)
|
|
201
|
+
const l2 = WASM_VECTOR_LEN
|
|
202
|
+
const p3 = passString(userInfoJson)
|
|
203
|
+
const l3 = WASM_VECTOR_LEN
|
|
204
|
+
const p4 = optString(clientMetaJson)
|
|
205
|
+
const l4 = WASM_VECTOR_LEN
|
|
206
|
+
wasm.qodercontext_new(sp, p1, l1, p2, l2, p3, l3, p4, l4)
|
|
207
|
+
const ok = dv().getInt32(sp + 0, true)
|
|
208
|
+
const err = dv().getInt32(sp + 4, true)
|
|
209
|
+
if (dv().getInt32(sp + 8, true)) throw takeObject(err)
|
|
210
|
+
this.__wbg_ptr = ok >>> 0
|
|
211
|
+
} finally {
|
|
212
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** 聊天面签名:URL 由 WASM 路由表映射到 agent_chat_generation。 */
|
|
216
|
+
prepareInferRequest(endpoint, bodyJson, modelKey, modelSource) {
|
|
217
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
218
|
+
try {
|
|
219
|
+
const p1 = passString(endpoint)
|
|
220
|
+
const l1 = WASM_VECTOR_LEN
|
|
221
|
+
const p2 = passString(bodyJson)
|
|
222
|
+
const l2 = WASM_VECTOR_LEN
|
|
223
|
+
const p3 = optString(modelKey)
|
|
224
|
+
const l3 = WASM_VECTOR_LEN
|
|
225
|
+
const p4 = optString(modelSource)
|
|
226
|
+
const l4 = WASM_VECTOR_LEN
|
|
227
|
+
wasm.qodercontext_prepareInferRequest(sp, this.__wbg_ptr, p1, l1, p2, l2, p3, l3, p4, l4)
|
|
228
|
+
const ok = dv().getInt32(sp + 0, true)
|
|
229
|
+
const err = dv().getInt32(sp + 4, true)
|
|
230
|
+
if (dv().getInt32(sp + 8, true)) throw takeObject(err)
|
|
231
|
+
return RequestResult.__wrap(ok)
|
|
232
|
+
} finally {
|
|
233
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** 释放 wasm 侧上下文(凭据轮换时旧上下文必 free)。 */
|
|
237
|
+
free() {
|
|
238
|
+
if (this.__wbg_ptr) wasm.__wbg_qodercontext_free(this.__wbg_ptr)
|
|
239
|
+
this.__wbg_ptr = 0
|
|
240
|
+
}
|
|
241
|
+
/** /algo 面签名(目录等管理端点):path 会被重写为 /algo 前缀 + Encode=1。 */
|
|
242
|
+
prepareRequest(endpoint, path, method, mode, bodyJson, headersJson) {
|
|
243
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
244
|
+
try {
|
|
245
|
+
const p1 = passString(endpoint)
|
|
246
|
+
const l1 = WASM_VECTOR_LEN
|
|
247
|
+
const p2 = passString(path)
|
|
248
|
+
const l2 = WASM_VECTOR_LEN
|
|
249
|
+
const p3 = passString(method)
|
|
250
|
+
const l3 = WASM_VECTOR_LEN
|
|
251
|
+
const p4 = passString(mode)
|
|
252
|
+
const l4 = WASM_VECTOR_LEN
|
|
253
|
+
const p5 = optString(bodyJson)
|
|
254
|
+
const l5 = WASM_VECTOR_LEN
|
|
255
|
+
const p6 = optString(headersJson)
|
|
256
|
+
const l6 = WASM_VECTOR_LEN
|
|
257
|
+
wasm.qodercontext_prepareRequest(sp, this.__wbg_ptr, p1, l1, p2, l2, p3, l3, p4, l4, p5, l5, p6, l6)
|
|
258
|
+
const ok = dv().getInt32(sp + 0, true)
|
|
259
|
+
const err = dv().getInt32(sp + 4, true)
|
|
260
|
+
if (dv().getInt32(sp + 8, true)) throw takeObject(err)
|
|
261
|
+
return RequestResult.__wrap(ok)
|
|
262
|
+
} finally {
|
|
263
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** userInfoJson → { encrypt_user_info, key }(上下文构造的前置)。 */
|
|
269
|
+
function runtimeFields(userInfoJson) {
|
|
270
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
271
|
+
let p
|
|
272
|
+
let l
|
|
273
|
+
try {
|
|
274
|
+
const ps = passString(userInfoJson)
|
|
275
|
+
const ls = WASM_VECTOR_LEN
|
|
276
|
+
wasm.generate_runtime_auth_fields(sp, ps, ls)
|
|
277
|
+
const rp = dv().getInt32(sp + 0, true)
|
|
278
|
+
const rl = dv().getInt32(sp + 4, true)
|
|
279
|
+
const ep = dv().getInt32(sp + 8, true)
|
|
280
|
+
const el = dv().getInt32(sp + 12, true)
|
|
281
|
+
if (el) throw takeObject(ep)
|
|
282
|
+
p = rp
|
|
283
|
+
l = rl
|
|
284
|
+
return getString(rp, rl)
|
|
285
|
+
} finally {
|
|
286
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
287
|
+
wasm.__wbindgen_export4(p, l, 1)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Encode=1 响应体解密;失败原样返回(部分端点本就回明文)。
|
|
292
|
+
* ABI:retptr-first 三参(与同文件 generate_runtime_auth_fields 同型)——
|
|
293
|
+
* 栈槽布局 [resultPtr, resultLen, errPtr, errLen];错用 2 参会让 wasm 把
|
|
294
|
+
* 结果槽写进输入串的线性内存且 ret 为 undefined(2026-10-03 实测)。 */
|
|
295
|
+
function decrypt(text) {
|
|
296
|
+
let p
|
|
297
|
+
let l
|
|
298
|
+
const sp = wasm.__wbindgen_add_to_stack_pointer(-16)
|
|
299
|
+
try {
|
|
300
|
+
const ptr = passString(text)
|
|
301
|
+
wasm.decrypt_server_response(sp, ptr, WASM_VECTOR_LEN)
|
|
302
|
+
const rp = dv().getInt32(sp + 0, true)
|
|
303
|
+
const rl = dv().getInt32(sp + 4, true)
|
|
304
|
+
const ep = dv().getInt32(sp + 8, true)
|
|
305
|
+
const el = dv().getInt32(sp + 12, true)
|
|
306
|
+
if (el) throw takeObject(ep)
|
|
307
|
+
p = rp
|
|
308
|
+
l = rl
|
|
309
|
+
return getString(rp, rl)
|
|
310
|
+
} catch {
|
|
311
|
+
return text
|
|
312
|
+
} finally {
|
|
313
|
+
wasm.__wbindgen_add_to_stack_pointer(16)
|
|
314
|
+
if (p) wasm.__wbindgen_export4(p, l, 1)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// 运行时工厂
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
/** 官方 CLI 版本号(签名里的 cosyVersion,随提取的 wasm 钉住)。 */
|
|
323
|
+
export const QODER_COSY_VERSION = '1.1.57'
|
|
324
|
+
|
|
325
|
+
let wasmPromise = null
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @param {{ wasmPath: string }} opts
|
|
329
|
+
* @returns 单例运行时 {
|
|
330
|
+
* ensureContext(cred), prepareChat({endpoint,body,modelKey,modelSource}),
|
|
331
|
+
* prepareGet({endpoint,path}), prepareSigned({endpoint,path,method,mode,body}),
|
|
332
|
+
* decrypt(text)
|
|
333
|
+
* };cred = { accessToken, machineId, uid }。
|
|
334
|
+
*/
|
|
335
|
+
export function createCosyRuntime({ wasmPath }) {
|
|
336
|
+
if (!wasmPromise) {
|
|
337
|
+
wasmPromise = (async () => {
|
|
338
|
+
const bytes = readFileSync(wasmPath)
|
|
339
|
+
const { instance } = await WebAssembly.instantiate(bytes, buildImports())
|
|
340
|
+
wasm = instance.exports
|
|
341
|
+
return wasm
|
|
342
|
+
})()
|
|
343
|
+
// 失败不缓存 promise(下次调用重试),也不留 half-init 状态。
|
|
344
|
+
wasmPromise.catch(() => { wasmPromise = null })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let context = null
|
|
348
|
+
let contextKey = null
|
|
349
|
+
|
|
350
|
+
async function ensureWasm() {
|
|
351
|
+
await wasmPromise
|
|
352
|
+
if (!wasm) throw new Error('qoder cosy wasm 未加载')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** 凭据快照变化(refresh 轮换)时重建上下文;同快照复用。 */
|
|
356
|
+
async function ensureContext(cred) {
|
|
357
|
+
if (!cred?.accessToken || !cred?.machineId) throw new Error('qoder 凭据不完整(需登录)')
|
|
358
|
+
const key = `${cred.machineId}:${cred.accessToken}:${JSON.stringify(cred.extraUser ?? null)}`
|
|
359
|
+
if (context && contextKey === key) return context
|
|
360
|
+
await ensureWasm()
|
|
361
|
+
const baseUser = {
|
|
362
|
+
uid: cred.uid ?? '',
|
|
363
|
+
security_oauth_token: cred.accessToken,
|
|
364
|
+
organization_id: '',
|
|
365
|
+
organization_tags: [],
|
|
366
|
+
data_policy_agreed: true,
|
|
367
|
+
// 2026-09-22 归因课题:官方 cachedUserInfo 比最小形态多 login_method/
|
|
368
|
+
// user_type/plan/aid/yx_uid 等字段(bundle 实证),经 encrypt_user_info
|
|
369
|
+
// 进服务端计费归因上下文;调用方可经 cred.extraUser 注入。
|
|
370
|
+
...(cred.extraUser && typeof cred.extraUser === 'object' ? cred.extraUser : {}),
|
|
371
|
+
}
|
|
372
|
+
const rf = JSON.parse(runtimeFields(JSON.stringify(baseUser)))
|
|
373
|
+
const next = new QoderContext(cred.machineId, QODER_COSY_VERSION, JSON.stringify({
|
|
374
|
+
...baseUser,
|
|
375
|
+
access_token: cred.accessToken,
|
|
376
|
+
encrypt_user_info: rf.encrypt_user_info,
|
|
377
|
+
key: rf.key,
|
|
378
|
+
}), JSON.stringify({ client_type: 5 }))
|
|
379
|
+
// 旧上下文在其 wasm 实例上占内存;轮换成功后才释放,失败则保留旧上下文。
|
|
380
|
+
if (context) context.free()
|
|
381
|
+
context = next
|
|
382
|
+
contextKey = key
|
|
383
|
+
return context
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** 取签名结果后即释放 RequestResult——它的 url/headers/body 已快照到纯 JS。 */
|
|
387
|
+
function drain(req) {
|
|
388
|
+
try {
|
|
389
|
+
const headers = req.headers instanceof Map ? Object.fromEntries(req.headers) : req.headers
|
|
390
|
+
return { url: req.url, headers, body: req.body }
|
|
391
|
+
} finally {
|
|
392
|
+
req.free()
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
ensureContext,
|
|
398
|
+
/** 聊天请求签名 → { url, headers(已转 record), body(密文) }。 */
|
|
399
|
+
async prepareChat(cred, { endpoint, body, modelKey, modelSource }) {
|
|
400
|
+
const ctx = await ensureContext(cred)
|
|
401
|
+
return drain(ctx.prepareInferRequest(endpoint, body, modelKey ?? null, modelSource ?? null))
|
|
402
|
+
},
|
|
403
|
+
/** /algo 面 GET 签名(目录/区域发现等)。 */
|
|
404
|
+
async prepareGet(cred, { endpoint, path }) {
|
|
405
|
+
const ctx = await ensureContext(cred)
|
|
406
|
+
return drain(ctx.prepareRequest(endpoint, path, 'GET', 'auth', undefined, undefined))
|
|
407
|
+
},
|
|
408
|
+
/**
|
|
409
|
+
* prepareRequest 直通(上报/管理面 POST):mode 'auth'(/algo 重写 + 加密)
|
|
410
|
+
* 或 'sign'(仅签名)。官方客户端 business/finish 用 auth、/api/v1/tracking
|
|
411
|
+
* 用 sign(bundle g4i/aPl 调用点实证)。
|
|
412
|
+
*/
|
|
413
|
+
async prepareSigned(cred, { endpoint, path, method = 'POST', mode = 'auth', body }) {
|
|
414
|
+
const ctx = await ensureContext(cred)
|
|
415
|
+
return drain(ctx.prepareRequest(endpoint, path, method, mode, body ?? undefined, undefined))
|
|
416
|
+
},
|
|
417
|
+
decrypt,
|
|
418
|
+
}
|
|
419
|
+
}
|