@acosmi/sdk-ts 2.19.4 → 2.19.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -150,6 +150,14 @@ var init_types = __esm({
150
150
  });
151
151
 
152
152
  // src/shared/errors.ts
153
+ function finiteNonNegative(value) {
154
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
155
+ }
156
+ function isStreamError(err) {
157
+ if (err == null || typeof err !== "object") return false;
158
+ const e = err;
159
+ return e.name === "StreamError" && typeof e.errorCode === "string";
160
+ }
153
161
  function nullableID(value) {
154
162
  return value === null || typeof value === "string";
155
163
  }
@@ -359,8 +367,23 @@ var init_errors = __esm({
359
367
  userMessage;
360
368
  /** gateway 原始 error 字符串 */
361
369
  rawError;
362
- /** 客户端是否值得重试 */
370
+ /** 客户端是否值得重试 (= 服务端 `retryable` **且** `requestDisposition === 'not_accepted'`) */
363
371
  retryable;
372
+ /**
373
+ * 服务端在帧上声明的 `retryable` **原值** (2.19.5 起) —— 不与 requestDisposition 合取,
374
+ * 缺席即 undefined。
375
+ *
376
+ * 为什么要并列存一份: 上面的 `retryable` 是两个判据的合取, 网关的流内错误帧并不总是
377
+ * 下发 `requestDisposition`, 于是"服务端明说可重试"会被合取成 false —— 两端各自正确,
378
+ * 合起来恒否。消费方要区分**「服务端明说不可重试」**(false, 一票否决) 与**「服务端没说」**
379
+ * (undefined, 回落到自己的本地分类表) 时, 只能读这个原值; 读 `retryable` 两种情形不可分。
380
+ */
381
+ serverRetryable;
382
+ /**
383
+ * 服务端建议的等待秒数 (2.19.5 起)。**有限非负数**才收, 其余 (null / 负数 /
384
+ * NaN / Infinity / 非数字) 一律 undefined —— 宁可不给, 绝不编造等待时间。
385
+ */
386
+ retryAfterSecs;
364
387
  errorContractVersion;
365
388
  faultDomain;
366
389
  requestDisposition;
@@ -383,6 +406,8 @@ var init_errors = __esm({
383
406
  this.userMessage = userMessage;
384
407
  this.rawError = rawError;
385
408
  this.retryable = retryable;
409
+ this.serverRetryable = opts.retryable;
410
+ this.retryAfterSecs = finiteNonNegative(opts.retryAfterSecs);
386
411
  this.errorContractVersion = opts.errorContractVersion;
387
412
  this.faultDomain = opts.faultDomain;
388
413
  this.requestDisposition = opts.requestDisposition;
@@ -2197,7 +2222,10 @@ var FileTokenStore = class {
2197
2222
  /**
2198
2223
  * 跨进程临界区. 用 sidecar `<path>.lock` 文件 + O_EXCL 创建语义实现互斥:
2199
2224
  * - 创建成功 = 持有锁; 写入 pid+timestamp 便于诊断
2200
- * - 创建失败 (EEXIST) = 别的进程持锁, backoff 重试
2225
+ * - 创建失败 (EEXIST) = 别的进程持锁, stale 检测后 backoff 重试
2226
+ * - 创建失败 (Windows 的 EPERM / EBUSY) = 前一持有者的 unlink 处于 delete-pending,
2227
+ * 同属争用; 跳过 stale 检测直接 backoff 重试 (见 catch 内注释)。EACCES 不在其中 —
2228
+ * 它是真权限错误, 立即抛
2201
2229
  * - 锁文件 mtime > staleMs = 进程崩溃残留, unlink 后重试
2202
2230
  * - acquireTimeoutMs 超时 = 抛错 (caller 应作为 transient error 处理, 上层 retry)
2203
2231
  *
@@ -2226,25 +2254,29 @@ ${Date.now()}
2226
2254
  release = async () => {
2227
2255
  try {
2228
2256
  await fs.unlink(lockPath);
2229
- } catch {
2257
+ } catch (e) {
2258
+ warnLockCleanupFailed("release", lockPath, e);
2230
2259
  }
2231
2260
  };
2232
2261
  break;
2233
2262
  } catch (e) {
2234
- if (!isAlreadyExistsError(e)) throw e;
2235
- let stale = false;
2236
- try {
2237
- const st = await fs.stat(lockPath);
2238
- if (Date.now() - st.mtimeMs > fileLockDefaults.staleMs) stale = true;
2239
- } catch {
2240
- continue;
2241
- }
2242
- if (stale) {
2263
+ if (!isLockContendedError(e)) throw e;
2264
+ if (!isWindowsLockBusyError(e)) {
2265
+ let stale = false;
2243
2266
  try {
2244
- await fs.unlink(lockPath);
2267
+ const st = await fs.stat(lockPath);
2268
+ if (Date.now() - st.mtimeMs > fileLockDefaults.staleMs) stale = true;
2245
2269
  } catch {
2270
+ continue;
2271
+ }
2272
+ if (stale) {
2273
+ try {
2274
+ await fs.unlink(lockPath);
2275
+ } catch (e2) {
2276
+ warnLockCleanupFailed("stale-break", lockPath, e2);
2277
+ }
2278
+ continue;
2246
2279
  }
2247
- continue;
2248
2280
  }
2249
2281
  if (Date.now() - startMs > fileLockDefaults.acquireTimeoutMs) {
2250
2282
  throw new Error(
@@ -2344,12 +2376,27 @@ ${Date.now()}
2344
2376
  });
2345
2377
  }
2346
2378
  };
2379
+ function warnLockCleanupFailed(stage, lockPath, e) {
2380
+ if (isNotExistError(e)) return;
2381
+ console.warn(
2382
+ `[acosmi-sdk] warning: unlink token file lock (${stage}) failed: ${lockPath}: ${e instanceof Error ? e.message : String(e)}`
2383
+ );
2384
+ }
2347
2385
  function isNotExistError(e) {
2348
2386
  if (typeof e === "object" && e !== null && "code" in e) {
2349
2387
  return e.code === "ENOENT";
2350
2388
  }
2351
2389
  return false;
2352
2390
  }
2391
+ function isWindowsLockBusyError(e) {
2392
+ if (typeof process === "undefined" || process.platform !== "win32") return false;
2393
+ if (typeof e !== "object" || e === null || !("code" in e)) return false;
2394
+ const code = e.code;
2395
+ return code === "EPERM" || code === "EBUSY";
2396
+ }
2397
+ function isLockContendedError(e) {
2398
+ return isAlreadyExistsError(e) || isWindowsLockBusyError(e);
2399
+ }
2353
2400
  function isAlreadyExistsError(e) {
2354
2401
  if (typeof e === "object" && e !== null && "code" in e) {
2355
2402
  return e.code === "EEXIST";
@@ -2943,7 +2990,7 @@ function parseStreamError(data) {
2943
2990
  let code = payload.errorCode ?? "";
2944
2991
  const stage = payload.stage ?? "";
2945
2992
  let message = payload.message ?? "";
2946
- const retryable = payload.retryable ?? false;
2993
+ const retryable = payload.retryable;
2947
2994
  let rawError = "";
2948
2995
  if (payload.error != null) {
2949
2996
  if (typeof payload.error === "string") {
@@ -2961,6 +3008,7 @@ function parseStreamError(data) {
2961
3008
  message,
2962
3009
  rawError,
2963
3010
  retryable,
3011
+ retryAfterSecs: payload.retryAfterSecs,
2964
3012
  errorContractVersion: payload.errorContractVersion,
2965
3013
  faultDomain: payload.faultDomain,
2966
3014
  requestDisposition: payload.requestDisposition,
@@ -3171,6 +3219,25 @@ function notifyGatewayRequestID(cb, headers) {
3171
3219
  } catch {
3172
3220
  }
3173
3221
  }
3222
+ function notifyResponseHeaders(cb, headers) {
3223
+ if (!cb) return;
3224
+ try {
3225
+ cb(headers);
3226
+ } catch {
3227
+ }
3228
+ }
3229
+ function armStreamRequestTimeout(opts, parent) {
3230
+ const ms = opts?.requestTimeoutMs;
3231
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms <= 0) return void 0;
3232
+ return withRequestTimeout(ms, parent);
3233
+ }
3234
+ async function* disposeTimeoutWhenSettled(inner, ctl) {
3235
+ try {
3236
+ yield* inner;
3237
+ } finally {
3238
+ ctl.dispose();
3239
+ }
3240
+ }
3174
3241
  var DEFAULT_API_TIMEOUT_MS = 6e4;
3175
3242
  function newDeferred() {
3176
3243
  let resolve;
@@ -4304,10 +4371,23 @@ var Client = class _Client {
4304
4371
  *
4305
4372
  * @param onUpstreamActivity 见 {@link UpstreamActivityCallback}
4306
4373
  * @param onGatewayRequestID 见 {@link GatewayRequestIDCallback}
4374
+ * @param opts 见 {@link ChatStreamOptions} (2.19.5 起)
4307
4375
  */
4308
- chatStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID) {
4376
+ chatStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID, opts) {
4309
4377
  return {
4310
- [Symbol.asyncIterator]: () => this.chatStreamGen(modelID, req, signal, false, onUpstreamActivity, onGatewayRequestID)
4378
+ [Symbol.asyncIterator]: () => {
4379
+ const ctl = armStreamRequestTimeout(opts, signal);
4380
+ const inner = this.chatStreamGen(
4381
+ modelID,
4382
+ req,
4383
+ ctl?.signal ?? signal,
4384
+ false,
4385
+ onUpstreamActivity,
4386
+ onGatewayRequestID,
4387
+ opts?.onResponseHeaders
4388
+ );
4389
+ return ctl ? disposeTimeoutWhenSettled(inner, ctl) : inner;
4390
+ }
4311
4391
  };
4312
4392
  }
4313
4393
  /**
@@ -4317,13 +4397,26 @@ var Client = class _Client {
4317
4397
  *
4318
4398
  * @param onUpstreamActivity 见 {@link UpstreamActivityCallback}
4319
4399
  * @param onGatewayRequestID 见 {@link GatewayRequestIDCallback}
4400
+ * @param opts 见 {@link ChatStreamOptions} (2.19.5 起)
4320
4401
  */
4321
- chatMessagesStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID) {
4402
+ chatMessagesStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID, opts) {
4322
4403
  return {
4323
- [Symbol.asyncIterator]: () => this.chatMessagesStreamGen(modelID, req, signal, false, onUpstreamActivity, onGatewayRequestID)
4404
+ [Symbol.asyncIterator]: () => {
4405
+ const ctl = armStreamRequestTimeout(opts, signal);
4406
+ const inner = this.chatMessagesStreamGen(
4407
+ modelID,
4408
+ req,
4409
+ ctl?.signal ?? signal,
4410
+ false,
4411
+ onUpstreamActivity,
4412
+ onGatewayRequestID,
4413
+ opts?.onResponseHeaders
4414
+ );
4415
+ return ctl ? disposeTimeoutWhenSettled(inner, ctl) : inner;
4416
+ }
4324
4417
  };
4325
4418
  }
4326
- async *chatStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID) {
4419
+ async *chatStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID, onResponseHeaders) {
4327
4420
  const r = { ...req, stream: true };
4328
4421
  const { body, adapter } = await this.buildChatRequest(modelID, r, signal);
4329
4422
  const token = await this.ensureToken(signal);
@@ -4355,10 +4448,11 @@ var Client = class _Client {
4355
4448
  `stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
4356
4449
  );
4357
4450
  }
4358
- yield* this.chatStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID);
4451
+ yield* this.chatStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID, onResponseHeaders);
4359
4452
  return;
4360
4453
  }
4361
4454
  notifyGatewayRequestID(onGatewayRequestID, resp.headers);
4455
+ notifyResponseHeaders(onResponseHeaders, resp.headers);
4362
4456
  if (!resp.ok) {
4363
4457
  const bodyBytes = await readLimited(resp.body, maxErrorBodySize);
4364
4458
  throw parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
@@ -4393,7 +4487,7 @@ var Client = class _Client {
4393
4487
  }
4394
4488
  }
4395
4489
  }
4396
- async *chatMessagesStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID) {
4490
+ async *chatMessagesStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID, onResponseHeaders) {
4397
4491
  const r = { ...req, stream: true };
4398
4492
  const { body, adapter } = await this.buildChatRequest(modelID, r, signal);
4399
4493
  const token = await this.ensureToken(signal);
@@ -4425,10 +4519,11 @@ var Client = class _Client {
4425
4519
  `messages stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
4426
4520
  );
4427
4521
  }
4428
- yield* this.chatMessagesStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID);
4522
+ yield* this.chatMessagesStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID, onResponseHeaders);
4429
4523
  return;
4430
4524
  }
4431
4525
  notifyGatewayRequestID(onGatewayRequestID, resp.headers);
4526
+ notifyResponseHeaders(onResponseHeaders, resp.headers);
4432
4527
  if (!resp.ok) {
4433
4528
  const bodyBytes = await readLimited(resp.body, maxErrorBodySize);
4434
4529
  throw parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
@@ -9114,6 +9209,7 @@ exports.isPlatform = isPlatform;
9114
9209
  exports.isRegion = isRegion;
9115
9210
  exports.isSSECommentLine = isSSECommentLine;
9116
9211
  exports.isSSLError = isSSLError;
9212
+ exports.isStreamError = isStreamError;
9117
9213
  exports.isTerminalRemoteEvent = isTerminalRemoteEvent;
9118
9214
  exports.isUserAccessTokenRejected = isUserAccessTokenRejected;
9119
9215
  exports.isValidTokenSet = isValidTokenSet;