@acosmi/sdk-ts 2.7.0 → 2.9.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 CHANGED
@@ -5,6 +5,17 @@ All notable changes to `@acosmi/sdk-ts` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.9.0] - 2026-06-20 — 向量 (Embedding) + 重排序 (Rerank) 端点 + listModels 全集模式
9
+
10
+ 托管模型网关新增向量与重排序两类模型(上游接阿里云百炼 DashScope),SDK 订阅会员可经现有会员计费体系(Hold→Settle→Release,按 `total_tokens` 套 input 费率)直接调用。具体上游模型名(`text-embedding-v4` / `gte-rerank-v2` / `qwen3-rerank` 等)由管理员在托管模型后台自填,不在 SDK / 网关硬编码。
11
+
12
+ ### Added
13
+
14
+ - **`client.embeddings(modelID, req)`** — 向量(同步,`POST /managed-models/:id/embeddings`):请求 `{ input: string | string[], dimensions?, encoding_format? }`,响应为 OpenAI `/v1/embeddings` 标准格式(`{ object, model, data: [{ embedding }], usage }`,网关直通无包装)。仅 `capabilities.supports_embedding=true` 的模型可用。
15
+ - **`client.rerank(modelID, req)`** — 重排序(同步,`POST /managed-models/:id/rerank`):统一扁平契约 `{ query, documents[], top_n?, return_documents?, instruct? }`,响应 `{ results: [{ index, relevance_score, document? }], usage, model }`(网关已把上游原生嵌套 / OpenAI 兼容扁平两线路归一化)。仅 `capabilities.supports_rerank=true` 的模型可用。
16
+ - **类型** — `EmbeddingRequest`/`EmbeddingData`/`EmbeddingResponse`/`RerankRequest`/`RerankResult`/`RerankResponse`。
17
+ - **`listModels` / `listModelsWithStatus` 新增 `opts.includeLocked`** — `true` 时请求 `/managed-models?picker=1` 全集模式:网关把越档模型作为 `Locked=true` 补回返全集,供 C 端选择器展示「付费订阅区」+ 置灰升级引导。缺省行为不变(只返有桶模型,向后兼容)。全集模式不写 `modelCache`,避免 locked 模型混入可用模型集。
18
+
8
19
  ## [2.7.0] - 2026-06-11 — 远控管理面补齐 + BYOK 密钥客户端 + Chat Bridge CRUD(Phase 7B)
9
20
 
10
21
  补齐远控核心交互之外的全部管理面(此前下游 CrabCode 只能裸 fetch),新增 BYO 模型密钥客户端,落地 chat-bridge integration/credential 管理面 CRUD——云端控制台与下游调用的是同一组端点同一份租户隔离数据,天然互通。全部方法逐字段对齐 nexus-v4 handler 真实 wire 形状(契约 §12/§14/§18 附录 A)。
package/README.md CHANGED
@@ -175,6 +175,46 @@ console.log(res.videoUrl); // VideoTaskResponse: taskId / status / videoUrl / er
175
175
 
176
176
  > 字段是网关**通用契约**;某厂商支持哪些取值由上游模型决定(如万相尺寸 `宽*高` 星号格式由网关代转)。网关适配范围:OpenAI 兼容图片 + 火山引擎(即梦/豆包)视频 + DashScope 通义万相(wanx)原生异步任务(图片+视频)。
177
177
 
178
+ ## 向量 / 重排序(托管模型网关,v2.9+)
179
+
180
+ 向量(embedding)与重排序(rerank)走与 chat 同一套**会员计费**(Hold→Settle→Release,按 `total_tokens` 套 input 费率),上游接阿里云百炼 DashScope。仅 `capabilities.supports_embedding` / `supports_rerank` 的托管模型可用;具体上游模型名由管理员在后台自填。
181
+
182
+ ### 向量(同步)
183
+
184
+ `embeddings` 一次调用拿向量,响应即 OpenAI `/v1/embeddings` 标准格式。
185
+
186
+ ```ts
187
+ const models = await client.listModels();
188
+ const embModel = models.find((m) => m.capabilities?.supports_embedding);
189
+
190
+ const resp = await client.embeddings(embModel!.id, {
191
+ input: ['第一段文本', '第二段文本'], // string 或 string[]
192
+ dimensions: 1024, // 可选
193
+ });
194
+ console.log(resp.data[0].embedding); // number[]
195
+ console.log(resp.usage.total_tokens);
196
+ ```
197
+
198
+ ### 重排序(同步)
199
+
200
+ `rerank` 传入 query + 候选文档,返回按相关性排序的下标与得分。
201
+
202
+ ```ts
203
+ const rerankModel = models.find((m) => m.capabilities?.supports_rerank);
204
+
205
+ const resp = await client.rerank(rerankModel!.id, {
206
+ query: '什么是文本排序模型',
207
+ documents: ['文档A', '文档B', '文档C'],
208
+ top_n: 3, // 可选
209
+ return_documents: true, // 可选:结果内回传文档原文
210
+ });
211
+ for (const r of resp.results) {
212
+ console.log(r.index, r.relevance_score, r.document);
213
+ }
214
+ ```
215
+
216
+ > 重排序对外是**统一扁平契约**;网关内部按模型绑定的线路(DashScope 原生嵌套 `gte-rerank-v2` / OpenAI 兼容扁平 `qwen3-rerank`)自动转换并归一化响应,SDK 侧无需关心。
217
+
178
218
  ## 双格式红线(设计核心)
179
219
 
180
220
  SDK 同时提供 **Anthropic + OpenAI 两条 endpoint**,**等地位**,对应两个不同下游产品。
@@ -508,6 +548,7 @@ const client = new Client({ serverURL: process.env.ACOSMI_SERVER_URL!, store: ne
508
548
  | **Client 构造** | `new Client(cfg)`(同步),`Client.create(cfg)`(async;预加载已有 TokenStore) |
509
549
  | **Chat** | `chat`, `chatStream`, `chatStreamWithUsage`, `chatMessages`, `chatMessagesStream`, `buildChatRequest` |
510
550
  | **图片 / 视频生成**(v2.2.0+) | `generateImage`(同步), `generateVideo`(建异步任务), `pollVideoTask`(轮询);仅 `capabilities.supports_image_generation` / `supports_video_generation` 模型可用 |
551
+ | **向量 / 重排序**(v2.9.0+) | `embeddings`(同步), `rerank`(同步);仅 `capabilities.supports_embedding` / `supports_rerank` 模型可用 |
511
552
  | **Agent Runs** | `agentRuns.create`, `agentRuns.stream`, `agentRuns.run`, `agentRuns.cancel`, `agentRuns.get`, `agentRuns.listArtifacts`, `agentRuns.downloadArtifact`, `agentRuns.submitLocalToolResult`, `agentRuns.runWithLocalTools` |
512
553
  | **Agent Runs — 远程控制**(v2.1) | `agentRuns.createRemoteRun`, `agentRuns.streamRemoteControl`;helper:`parseRemoteControlEvent`, `isTerminalRemoteEvent`;scope:`remoteControlScopes()` / `ScopeRemoteControl`(不进 `allScopes()`) |
513
554
  | **Chat Bridge**(v2.1 · types-only) | 无 client 方法(Phase 7B 后端落地);导出类型守卫 `isPlatform`, `isRegion`, `isIntegrationStatus`, `isChannelInboundEvent`, `asCredentialRef` |
@@ -1259,10 +1259,17 @@ async function authorize(meta, clientID, scopes, opts = {}) {
1259
1259
  codeRejecter(new Error(`authorization denied: ${errMsg}`));
1260
1260
  return;
1261
1261
  }
1262
- res.setHeader("Content-Type", "text/html; charset=utf-8");
1263
- res.end(
1264
- `<!DOCTYPE html><html><head><meta charset="utf-8"><title>\u6388\u6743\u6210\u529F</title></head><body style="font-family:system-ui,sans-serif;text-align:center;padding:60px 20px"><h2>\u6388\u6743\u6210\u529F</h2><p>\u5DF2\u5B8C\u6210\u8EAB\u4EFD\u8BA4\u8BC1, \u8BF7\u8FD4\u56DE\u5E94\u7528\u7EE7\u7EED\u4F7F\u7528\u3002</p><p style="color:#888;font-size:14px">\u6B64\u7A97\u53E3\u5C06\u5728 3 \u79D2\u540E\u81EA\u52A8\u5173\u95ED\u2026</p><script>setTimeout(function(){window.close()},3000)<\/script></body></html>`
1265
- );
1262
+ const successRedirect = resolveSuccessRedirect(opts.successRedirectURL);
1263
+ if (successRedirect) {
1264
+ res.statusCode = 302;
1265
+ res.setHeader("Location", successRedirect);
1266
+ res.end();
1267
+ } else {
1268
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1269
+ res.end(
1270
+ `<!DOCTYPE html><html><head><meta charset="utf-8"><title>\u6388\u6743\u6210\u529F</title></head><body style="font-family:system-ui,sans-serif;text-align:center;padding:60px 20px"><h2>\u6388\u6743\u6210\u529F</h2><p>\u5DF2\u5B8C\u6210\u8EAB\u4EFD\u8BA4\u8BC1, \u8BF7\u8FD4\u56DE\u5E94\u7528\u7EE7\u7EED\u4F7F\u7528\u3002</p><p style="color:#888;font-size:14px">\u6B64\u7A97\u53E3\u5C06\u5728 3 \u79D2\u540E\u81EA\u52A8\u5173\u95ED\u2026</p><script>setTimeout(function(){window.close()},3000)<\/script></body></html>`
1271
+ );
1272
+ }
1266
1273
  codeResolver(code);
1267
1274
  });
1268
1275
  const authURL = new URL(meta.authorization_endpoint);
@@ -1324,6 +1331,16 @@ async function authorize(meta, clientID, scopes, opts = {}) {
1324
1331
  function htmlEscape(s) {
1325
1332
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1326
1333
  }
1334
+ function resolveSuccessRedirect(raw) {
1335
+ if (!raw) return null;
1336
+ try {
1337
+ const u = new URL(raw);
1338
+ if (u.protocol !== "http:" && u.protocol !== "https:") return null;
1339
+ return u.toString();
1340
+ } catch {
1341
+ return null;
1342
+ }
1343
+ }
1327
1344
  async function createWebAuthorizationRequest(meta, opts) {
1328
1345
  const verifier = await generateCodeVerifier();
1329
1346
  const challenge = await codeChallenge(verifier);
@@ -2662,8 +2679,8 @@ var Client = class _Client {
2662
2679
  * V30 二轮审计 D-P1-2: 此方法不返回 entitlement-filter-status header.
2663
2680
  * UI 想根据 fallback 状态显示降级提示, 请改调 listModelsWithStatus.
2664
2681
  */
2665
- async listModels(signal) {
2666
- const r = await this.listModelsWithStatus(signal);
2682
+ async listModels(signal, opts) {
2683
+ const r = await this.listModelsWithStatus(signal, opts);
2667
2684
  return r.models;
2668
2685
  }
2669
2686
  /**
@@ -2676,16 +2693,20 @@ var Client = class _Client {
2676
2693
  * - status === 'disabled-by-flag' → 不显示余量 (运维灰度中)
2677
2694
  * - status === '' (Unknown) → 老 nexus, 按老 v0.17 行为 (不显示 BucketInfo)
2678
2695
  */
2679
- async listModelsWithStatus(signal) {
2696
+ async listModelsWithStatus(signal, opts) {
2697
+ const includeLocked = opts?.includeLocked === true;
2698
+ const path = includeLocked ? "/managed-models?picker=1" : "/managed-models";
2680
2699
  const { result, headers } = await this.doJSONFull(
2681
2700
  "GET",
2682
- "/managed-models",
2701
+ path,
2683
2702
  null,
2684
2703
  signal
2685
2704
  );
2686
2705
  const normalized = normalizeInputModalities(result.data);
2687
- this.modelCache = normalized;
2688
- this.modelCacheTimeMs = Date.now();
2706
+ if (!includeLocked) {
2707
+ this.modelCache = normalized;
2708
+ this.modelCacheTimeMs = Date.now();
2709
+ }
2689
2710
  let status = "";
2690
2711
  if (headers) {
2691
2712
  const h = headers.get("X-Entitlement-Filter-Status");
@@ -2880,6 +2901,34 @@ var Client = class _Client {
2880
2901
  const { result } = await this.doJSONFullRaw("GET", endpoint, null, signal);
2881
2902
  return this.unwrapAPIResponse(result);
2882
2903
  }
2904
+ /**
2905
+ * 向量 (同步)。POST /managed-models/:id/embeddings
2906
+ *
2907
+ * 仅 capabilities.supports_embedding=true 的托管模型可用 (上游接 DashScope)。
2908
+ * 响应为 OpenAI /v1/embeddings 标准格式 (网关直通, 无 response.Success 包装)。
2909
+ *
2910
+ * @param modelID 向量托管模型 ID
2911
+ */
2912
+ async embeddings(modelID, req, signal) {
2913
+ const endpoint = `/managed-models/${encodeURIComponent(modelID)}/embeddings`;
2914
+ const { result } = await this.doJSONFullRaw("POST", endpoint, req, signal, CHAT_REQUEST_TIMEOUT_MS);
2915
+ return JSON.parse(new TextDecoder().decode(result));
2916
+ }
2917
+ /**
2918
+ * 重排序 (同步)。POST /managed-models/:id/rerank
2919
+ *
2920
+ * 仅 capabilities.supports_rerank=true 的托管模型可用 (上游接 DashScope)。
2921
+ * 统一扁平契约: { query, documents[], top_n?, return_documents?, instruct? };
2922
+ * 响应 { results: [{ index, relevance_score, document? }], usage, model }
2923
+ * (网关已把上游原生/OpenAI 兼容两线路归一化, 无 response.Success 包装)。
2924
+ *
2925
+ * @param modelID 重排序托管模型 ID
2926
+ */
2927
+ async rerank(modelID, req, signal) {
2928
+ const endpoint = `/managed-models/${encodeURIComponent(modelID)}/rerank`;
2929
+ const { result } = await this.doJSONFullRaw("POST", endpoint, req, signal, CHAT_REQUEST_TIMEOUT_MS);
2930
+ return JSON.parse(new TextDecoder().decode(result));
2931
+ }
2883
2932
  /**
2884
2933
  * Anthropic 原生格式同步聊天
2885
2934
  * v0.5.0: 根据 provider 自动路由