@acosmi/sdk-ts 1.4.0 → 1.4.1

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
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ---
11
11
 
12
+ ## [1.4.1] — 2026-05-22
13
+
14
+ ### Added
15
+
16
+ - **`Config.browserRefreshMode?: 'direct' | 'server-proxy' | 'none'`** 与
17
+ `refreshProxyURL` — 浏览器 Web OAuth token 刷新策略。默认 `direct` 保持既有行为;
18
+ `server-proxy` 可把刷新收口到同源 Route Handler,规避 OAuth issuer CORS 403;
19
+ `none` 用于产品自行处理过期登录态。新增错误码常量
20
+ `ErrOAuthCORSBlocked`、`ErrRefreshProxyFailed`、`ErrTokenExpired`。
21
+
22
+ ---
23
+
12
24
  ## [1.4.0] — 2026-05-21
13
25
 
14
26
  > csign `/login` Web OAuth 接入复核审计(`docs/audit/csign-login-oauth-audit-result-2026-05-21`)
@@ -2240,6 +2240,9 @@ var FilterStatusFallbackTkdistSkew = "fallback-tkdist-deployment-skew";
2240
2240
  var FilterStatusFallbackNoBuckets = "fallback-no-buckets";
2241
2241
  var FilterStatusFallbackMissingUser = "fallback-missing-userid";
2242
2242
  var FilterStatusUnknown = "";
2243
+ var ErrOAuthCORSBlocked = "oauth_cors_blocked";
2244
+ var ErrRefreshProxyFailed = "refresh_proxy_failed";
2245
+ var ErrTokenExpired = "token_expired";
2243
2246
  function newDeferred() {
2244
2247
  let resolve;
2245
2248
  let reject;
@@ -2257,6 +2260,10 @@ var Client = class _Client {
2257
2260
  complianceBaseURL;
2258
2261
  /** OAuth metadata profile — 刷新 token 时发现 metadata 用 (默认 'desktop') */
2259
2262
  oauthMetadataProfile;
2263
+ /** Browser Web OAuth refresh strategy (default 'direct') */
2264
+ browserRefreshMode;
2265
+ /** Same-origin refresh proxy URL for browserRefreshMode='server-proxy' */
2266
+ refreshProxyURL;
2260
2267
  /** OAuth metadata (lazy loaded) */
2261
2268
  meta = null;
2262
2269
  /** 当前 token (内存) */
@@ -2292,6 +2299,8 @@ var Client = class _Client {
2292
2299
  this.serverURL = (cfg.serverURL ?? "https://acosmi.com").replace(/\/+$/, "");
2293
2300
  this.complianceBaseURL = cfg.complianceBaseURL ? cfg.complianceBaseURL.replace(/\/+$/, "") : null;
2294
2301
  this.oauthMetadataProfile = cfg.oauthMetadataProfile ?? "desktop";
2302
+ this.browserRefreshMode = cfg.browserRefreshMode ?? "direct";
2303
+ this.refreshProxyURL = cfg.refreshProxyURL ?? null;
2295
2304
  this.store = cfg.store ?? defaultTokenStore();
2296
2305
  this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
2297
2306
  this.retryPolicy = effectivePolicy(cfg.retryPolicy ?? null);
@@ -2535,27 +2544,7 @@ var Client = class _Client {
2535
2544
  if (!tokenSetIsExpired(this.tokens)) {
2536
2545
  return this.tokens.access_token;
2537
2546
  }
2538
- if (this.meta == null) {
2539
- try {
2540
- this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2541
- } catch (e) {
2542
- throw new Error(
2543
- `discover for refresh: ${e instanceof Error ? e.message : String(e)}`
2544
- );
2545
- }
2546
- }
2547
- let tokenResp;
2548
- try {
2549
- tokenResp = await refreshToken(this.meta, this.tokens.client_id, this.tokens.refresh_token, signal);
2550
- } catch (e) {
2551
- throw new Error(`refresh token: ${e instanceof Error ? e.message : String(e)}`);
2552
- }
2553
- this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2554
- try {
2555
- await this.store.save(this.tokens);
2556
- } catch (e) {
2557
- console.warn(`[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`);
2558
- }
2547
+ await this.refreshCurrentToken(signal);
2559
2548
  return this.tokens.access_token;
2560
2549
  })
2561
2550
  );
@@ -2568,26 +2557,111 @@ var Client = class _Client {
2568
2557
  if (this.tokens == null) {
2569
2558
  throw new Error("no tokens to refresh");
2570
2559
  }
2571
- if (this.meta == null) {
2572
- this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2573
- }
2574
- const tokenResp = await refreshToken(
2575
- this.meta,
2576
- this.tokens.client_id,
2577
- this.tokens.refresh_token,
2578
- signal
2579
- );
2580
- this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2581
- try {
2582
- await this.store.save(this.tokens);
2583
- } catch (e) {
2584
- console.warn(
2585
- `[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`
2586
- );
2587
- }
2560
+ await this.refreshCurrentToken(signal);
2588
2561
  })
2589
2562
  );
2590
2563
  }
2564
+ async refreshCurrentToken(signal) {
2565
+ if (this.tokens == null) {
2566
+ throw new Error("no tokens to refresh");
2567
+ }
2568
+ if (this.browserRefreshMode === "none") {
2569
+ throw new Error(`${ErrTokenExpired}: token refresh disabled`);
2570
+ }
2571
+ if (this.browserRefreshMode === "server-proxy") {
2572
+ await this.refreshCurrentTokenViaProxy(signal);
2573
+ return;
2574
+ }
2575
+ await this.refreshCurrentTokenDirect(signal);
2576
+ }
2577
+ async refreshCurrentTokenDirect(signal) {
2578
+ if (this.tokens == null) {
2579
+ throw new Error("no tokens to refresh");
2580
+ }
2581
+ if (this.meta == null) {
2582
+ try {
2583
+ this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2584
+ } catch (e) {
2585
+ throw new Error(
2586
+ `discover for refresh: ${e instanceof Error ? e.message : String(e)}`
2587
+ );
2588
+ }
2589
+ }
2590
+ let tokenResp;
2591
+ try {
2592
+ tokenResp = await refreshToken(
2593
+ this.meta,
2594
+ this.tokens.client_id,
2595
+ this.tokens.refresh_token,
2596
+ signal
2597
+ );
2598
+ } catch (e) {
2599
+ const message = e instanceof Error ? e.message : String(e);
2600
+ if (isLikelyBrowserOAuthCORSError(message)) {
2601
+ throw new Error(`${ErrOAuthCORSBlocked}: refresh token: ${message}`);
2602
+ }
2603
+ throw new Error(`refresh token: ${message}`);
2604
+ }
2605
+ this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2606
+ await this.saveRefreshedToken();
2607
+ }
2608
+ async refreshCurrentTokenViaProxy(signal) {
2609
+ if (this.tokens == null) {
2610
+ throw new Error("no tokens to refresh");
2611
+ }
2612
+ if (!this.refreshProxyURL) {
2613
+ throw new Error(`${ErrRefreshProxyFailed}: refreshProxyURL is required`);
2614
+ }
2615
+ let resp;
2616
+ try {
2617
+ resp = await this.fetchImpl(this.refreshProxyURL, {
2618
+ method: "POST",
2619
+ headers: { "Content-Type": "application/json" },
2620
+ body: JSON.stringify({
2621
+ client_id: this.tokens.client_id,
2622
+ refresh_token: this.tokens.refresh_token,
2623
+ server_url: this.tokens.server_url || this.serverURL
2624
+ }),
2625
+ signal
2626
+ });
2627
+ } catch (e) {
2628
+ throw new Error(
2629
+ `${ErrRefreshProxyFailed}: ${e instanceof Error ? e.message : String(e)}`
2630
+ );
2631
+ }
2632
+ if (!resp.ok) {
2633
+ let message = "";
2634
+ try {
2635
+ const body2 = await resp.json();
2636
+ if (typeof body2.error === "string") message = body2.error;
2637
+ } catch {
2638
+ }
2639
+ throw new Error(`${ErrRefreshProxyFailed}: HTTP ${resp.status}: ${message}`);
2640
+ }
2641
+ let body;
2642
+ try {
2643
+ body = await resp.json();
2644
+ } catch (e) {
2645
+ throw new Error(
2646
+ `${ErrRefreshProxyFailed}: decode: ${e instanceof Error ? e.message : String(e)}`
2647
+ );
2648
+ }
2649
+ if (!isTokenSetLike(body.tokenSet)) {
2650
+ throw new Error(`${ErrRefreshProxyFailed}: response missing tokenSet`);
2651
+ }
2652
+ this.tokens = body.tokenSet;
2653
+ await this.saveRefreshedToken();
2654
+ }
2655
+ async saveRefreshedToken() {
2656
+ if (this.tokens == null) return;
2657
+ try {
2658
+ await this.store.save(this.tokens);
2659
+ } catch (e) {
2660
+ console.warn(
2661
+ `[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`
2662
+ );
2663
+ }
2664
+ }
2591
2665
  /** 互斥锁 helper (替代 Go sync.Mutex) */
2592
2666
  withMu(fn) {
2593
2667
  const next = this.mu.then(fn, fn);
@@ -3286,6 +3360,15 @@ function defaultTokenStore() {
3286
3360
  }
3287
3361
  return new InMemoryTokenStore();
3288
3362
  }
3363
+ function isLikelyBrowserOAuthCORSError(message) {
3364
+ const lower = message.toLowerCase();
3365
+ return lower.includes("failed to fetch") || lower.includes("networkerror") || lower.includes("cors") || lower.includes("http 403");
3366
+ }
3367
+ function isTokenSetLike(value) {
3368
+ if (value == null || typeof value !== "object") return false;
3369
+ const candidate = value;
3370
+ return typeof candidate.access_token === "string" && typeof candidate.refresh_token === "string" && typeof candidate.expires_at === "string" && typeof candidate.scope === "string" && typeof candidate.client_id === "string" && typeof candidate.server_url === "string";
3371
+ }
3289
3372
  function zeroModelCapabilities() {
3290
3373
  return {
3291
3374
  supports_thinking: false,
@@ -5462,6 +5545,6 @@ Client.prototype.getBugReport = async function(bugID, signal) {
5462
5545
  return resp.data;
5463
5546
  };
5464
5547
 
5465
- export { AgentRunStreamError, AgentRunsClient, AnthropicAdapter, BucketClassCommercial, BucketClassGeneric, BusinessError, Client, ComplianceClient, CompliancePollError, LocalStorageTokenStore as DefaultBrowserTokenStore, DefaultRetryPolicy, ErrAuthDenied, ErrBillingCallbackCannotCommit, ErrBillingCommitRequiresLocalVerify, ErrBillingS2sForbidden, ErrBrowserOpen, ErrComplianceStepUpRequired, ErrDiscovery, ErrEnvelopeGateClosed, ErrProviderNotConfigured, ErrProviderUnknownNoRetry, ErrRegistration, ErrSSLProxy, ErrSealApprovalContractHashMismatch, ErrSealApprovalExpired, ErrSealApprovalLocationMismatch, ErrSealApprovalNonceUsed, ErrSealApprovalNotApproved, ErrSealApprovalSealMismatch, ErrSealApprovalTransactorMismatch, ErrSealUseAlreadyConsumed, ErrStateMismatch, ErrTimeout, ErrTokenExchange, EventAuthURL, EventComplete, EventError, FileTokenStore, FilterStatusAdminBypass, FilterStatusDisabledByFlag, FilterStatusFallbackMissingUser, FilterStatusFallbackNoBuckets, FilterStatusFallbackTkdistError, FilterStatusFallbackTkdistSkew, FilterStatusInternalBypass, FilterStatusOK, FilterStatusUnknown, HTTPError, InMemoryTokenStore, LocalStorageTokenStore, ModelNotFoundError, NetworkError, OpenAIAdapter, OrderTerminalError, ProviderFormat, RateLimitError, ScopeAI, ScopeAccount, ScopeComplianceContractSigningRead, ScopeComplianceContractSigningWrite, ScopeComplianceEvidenceRead, ScopeComplianceEvidenceWrite, ScopeComplianceReportsPublish, ScopeComplianceReportsRead, ScopeComplianceReportsWrite, ScopeComplianceSealApprovalApprove, ScopeComplianceSealApprovalRequest, ScopeComplianceSealManage, ScopeComplianceSealUseExecute, ScopeComplianceTimestampIssue, ScopeComplianceTimestampVerify, ScopeEntitlements, ScopeModels, ScopeModelsChat, ScopeProfile, ScopeSkillStore, ScopeSkills, ScopeTokenPackages, ScopeTools, ScopeToolsExecute, ScopeWallet, ScopeWalletReadonly, ServerToolTypeWebSearch, StreamError, ThinkingHigh, ThinkingHighMinMaxTokens, ThinkingMax, ThinkingMaxFallbackMaxTokens, ThinkingOff, allScopes, anthropicResponseTextContent, anthropicResponseThinkingContent, anthropicResponseToolUseBlocks, apiResponseBusinessError, apiResponseGetMessage, authorize, bucketInfoIsCommercial, bucketRowIsCommercial, buildBetas, classifyComplianceError, commerceScopes, completeWebAuthorizationRequest, complianceScopes, computeBackoff, createWebAuthorizationRequest, defaultRetryable, defaultSafeToRetry, discover, discoverWebOAuthMetadata, discoverWithProfile, effectivePolicy, exchangeCode, extractAnthropicBlockMeta, fileLockDefaults, findDesktopVisualUnderstandingModel, findFirstModelByInputModality, generateState, getAdapter, getAdapterForModel, isBillingConfirmable, isComplianceBusinessError, isComplianceTerminalError, isSSLError, modelScopes, modelSupportsImageInput, modelSupportsInputModality, newFileTokenStore, newThinkingConfig, newTokenSet, newWebSearchTool, parseNotificationEvent, parseSettlement, parseSourcesEvent, refreshToken, register, registerWebOAuthClient, revokeToken, sanitize_exports as sanitize, skillScopes, tokenSetIsExpired, uniqueMerge };
5548
+ export { AgentRunStreamError, AgentRunsClient, AnthropicAdapter, BucketClassCommercial, BucketClassGeneric, BusinessError, Client, ComplianceClient, CompliancePollError, LocalStorageTokenStore as DefaultBrowserTokenStore, DefaultRetryPolicy, ErrAuthDenied, ErrBillingCallbackCannotCommit, ErrBillingCommitRequiresLocalVerify, ErrBillingS2sForbidden, ErrBrowserOpen, ErrComplianceStepUpRequired, ErrDiscovery, ErrEnvelopeGateClosed, ErrOAuthCORSBlocked, ErrProviderNotConfigured, ErrProviderUnknownNoRetry, ErrRefreshProxyFailed, ErrRegistration, ErrSSLProxy, ErrSealApprovalContractHashMismatch, ErrSealApprovalExpired, ErrSealApprovalLocationMismatch, ErrSealApprovalNonceUsed, ErrSealApprovalNotApproved, ErrSealApprovalSealMismatch, ErrSealApprovalTransactorMismatch, ErrSealUseAlreadyConsumed, ErrStateMismatch, ErrTimeout, ErrTokenExchange, ErrTokenExpired, EventAuthURL, EventComplete, EventError, FileTokenStore, FilterStatusAdminBypass, FilterStatusDisabledByFlag, FilterStatusFallbackMissingUser, FilterStatusFallbackNoBuckets, FilterStatusFallbackTkdistError, FilterStatusFallbackTkdistSkew, FilterStatusInternalBypass, FilterStatusOK, FilterStatusUnknown, HTTPError, InMemoryTokenStore, LocalStorageTokenStore, ModelNotFoundError, NetworkError, OpenAIAdapter, OrderTerminalError, ProviderFormat, RateLimitError, ScopeAI, ScopeAccount, ScopeComplianceContractSigningRead, ScopeComplianceContractSigningWrite, ScopeComplianceEvidenceRead, ScopeComplianceEvidenceWrite, ScopeComplianceReportsPublish, ScopeComplianceReportsRead, ScopeComplianceReportsWrite, ScopeComplianceSealApprovalApprove, ScopeComplianceSealApprovalRequest, ScopeComplianceSealManage, ScopeComplianceSealUseExecute, ScopeComplianceTimestampIssue, ScopeComplianceTimestampVerify, ScopeEntitlements, ScopeModels, ScopeModelsChat, ScopeProfile, ScopeSkillStore, ScopeSkills, ScopeTokenPackages, ScopeTools, ScopeToolsExecute, ScopeWallet, ScopeWalletReadonly, ServerToolTypeWebSearch, StreamError, ThinkingHigh, ThinkingHighMinMaxTokens, ThinkingMax, ThinkingMaxFallbackMaxTokens, ThinkingOff, allScopes, anthropicResponseTextContent, anthropicResponseThinkingContent, anthropicResponseToolUseBlocks, apiResponseBusinessError, apiResponseGetMessage, authorize, bucketInfoIsCommercial, bucketRowIsCommercial, buildBetas, classifyComplianceError, commerceScopes, completeWebAuthorizationRequest, complianceScopes, computeBackoff, createWebAuthorizationRequest, defaultRetryable, defaultSafeToRetry, discover, discoverWebOAuthMetadata, discoverWithProfile, effectivePolicy, exchangeCode, extractAnthropicBlockMeta, fileLockDefaults, findDesktopVisualUnderstandingModel, findFirstModelByInputModality, generateState, getAdapter, getAdapterForModel, isBillingConfirmable, isComplianceBusinessError, isComplianceTerminalError, isSSLError, modelScopes, modelSupportsImageInput, modelSupportsInputModality, newFileTokenStore, newThinkingConfig, newTokenSet, newWebSearchTool, parseNotificationEvent, parseSettlement, parseSourcesEvent, refreshToken, register, registerWebOAuthClient, revokeToken, sanitize_exports as sanitize, skillScopes, tokenSetIsExpired, uniqueMerge };
5466
5549
  //# sourceMappingURL=index.mjs.map
5467
5550
  //# sourceMappingURL=index.mjs.map