copilot-api-plus 1.4.8 → 1.4.9

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/dist/main.js CHANGED
@@ -2763,9 +2763,19 @@ async function handleMultiAccountHttpError(error, account, retryContext) {
2763
2763
  consola.warn(`Account ${account.label}: 401, refreshing token...`);
2764
2764
  return tryRefreshAndRetry(account, retryContext.payload, retryContext.tokenSource);
2765
2765
  case 403:
2766
+ if (!retryContext.hasOtherAccount) {
2767
+ consola.warn(`Account ${account.label}: 403 — only account, propagating to client without marking`);
2768
+ error.__nonAccountError = true;
2769
+ return null;
2770
+ }
2766
2771
  accountManager.markAccountStatus(account.id, "banned", "403 Forbidden");
2767
2772
  return null;
2768
2773
  case 429:
2774
+ if (!retryContext.hasOtherAccount) {
2775
+ consola.warn(`Account ${account.label}: 429 — only account, propagating to client without marking`);
2776
+ error.__nonAccountError = true;
2777
+ return null;
2778
+ }
2769
2779
  accountManager.markAccountStatus(account.id, "rate_limited", "429 Rate limited");
2770
2780
  return null;
2771
2781
  case 408:
@@ -2880,7 +2890,8 @@ async function createWithMultiAccount$1(payload) {
2880
2890
  if (error instanceof HTTPError) {
2881
2891
  const retryResult = await handleMultiAccountHttpError(error, account, {
2882
2892
  payload,
2883
- tokenSource
2893
+ tokenSource,
2894
+ hasOtherAccount: hasAnotherAccountToTry(triedAccountIds)
2884
2895
  });
2885
2896
  if (retryResult) return retryResult;
2886
2897
  if (error.__nonAccountError) throw error;
@@ -3779,9 +3790,9 @@ async function createWithMultiAccount(payload, options$1) {
3779
3790
  } catch (error) {
3780
3791
  lastError = error;
3781
3792
  if (error instanceof HTTPError) {
3782
- if (error.response.status === 401) return handleMultiAccount401(ctx, account);
3783
- if (error.response.status >= 400 && error.response.status < 500) throw error;
3784
- consola.warn(`Account ${account.label}: 5xx from /v1/messages${hasAnotherAnthropicAccountToTry(triedAccountIds) ? ", trying next account" : " — no other accounts available, propagating error"}`);
3793
+ const action = handleAnthropicHttpError(error, account, triedAccountIds);
3794
+ if (action === "refresh401") return handleMultiAccount401(ctx, account);
3795
+ if (action === "throw") throw error;
3785
3796
  continue;
3786
3797
  }
3787
3798
  const errMsg = error.message || String(error);
@@ -3800,6 +3811,35 @@ async function createWithMultiAccount(payload, options$1) {
3800
3811
  throw new Error("No available accounts");
3801
3812
  }
3802
3813
  /**
3814
+ * Decide what to do for an HTTP error from a multi-account request attempt.
3815
+ *
3816
+ * Returns:
3817
+ * - "refresh401" — caller should run the 401-refresh-and-retry flow
3818
+ * - "throw" — caller should rethrow the error to the client
3819
+ * - "continue" — caller should try the next account
3820
+ *
3821
+ * Single-account guard: marking the only account as rate_limited / banned
3822
+ * would disable the proxy entirely, so 429 / 403 are propagated unchanged
3823
+ * to the client when no other account is available.
3824
+ */
3825
+ function handleAnthropicHttpError(error, account, triedAccountIds) {
3826
+ const status = error.response.status;
3827
+ if (status === 401) return "refresh401";
3828
+ if (status === 429 || status === 403) {
3829
+ const isRateLimit = status === 429;
3830
+ if (hasAnotherAnthropicAccountToTry(triedAccountIds)) {
3831
+ accountManager.markAccountStatus(account.id, isRateLimit ? "rate_limited" : "banned", isRateLimit ? "429 Rate limited" : "403 Forbidden");
3832
+ consola.warn(`Account ${account.label}: ${status} on /v1/messages, trying next account`);
3833
+ return "continue";
3834
+ }
3835
+ consola.warn(`Account ${account.label}: ${status} on /v1/messages — only account, propagating to client without marking`);
3836
+ return "throw";
3837
+ }
3838
+ if (status >= 400 && status < 500) return "throw";
3839
+ consola.warn(`Account ${account.label}: 5xx from /v1/messages${hasAnotherAnthropicAccountToTry(triedAccountIds) ? ", trying next account" : " — no other accounts available, propagating error"}`);
3840
+ return "continue";
3841
+ }
3842
+ /**
3803
3843
  * Peek at whether `getActiveAccount()` would return an untried account on the
3804
3844
  * next iteration. Used purely for honest log messaging — doesn't affect
3805
3845
  * routing.
@@ -4133,37 +4173,30 @@ async function handleCompletion(c) {
4133
4173
  if (state.manualApprove) await awaitApproval();
4134
4174
  const route = resolveAnthropicRoute(anthropicPayload.model);
4135
4175
  consola.debug(`Anthropic route resolved: ${route}`);
4136
- if (route === "native-anthropic" && !nativeBlockedModels.has(anthropicPayload.model)) return handleNativePassthrough(c, anthropicPayload);
4176
+ if (route === "native-anthropic") return handleNativePassthrough(c, anthropicPayload);
4137
4177
  return handleTranslatedCompletion(c, anthropicPayload);
4138
4178
  }
4139
- /**
4140
- * Models whose native /v1/messages path returned an unrecoverable upstream
4141
- * policy error (e.g. Vertex AI's `structured_outputs` GCP org policy).
4142
- * Once added, future requests for that model skip the native path and go
4143
- * straight to the translated /chat/completions path.
4144
- *
4145
- * Cleared on process restart — so a fixed Copilot routing self-heals.
4146
- */
4147
- const nativeBlockedModels = /* @__PURE__ */ new Set();
4148
- const VERTEX_STRUCTURED_OUTPUTS_PATTERN = /vertexai\.allowedPartnerModelFeatures.*?structured_outputs/i;
4149
- function isVertexStructuredOutputsBlock(error) {
4150
- const message = error instanceof Error ? error.message : String(error);
4151
- return VERTEX_STRUCTURED_OUTPUTS_PATTERN.test(message);
4152
- }
4153
4179
  async function handleNativePassthrough(c, anthropicPayload) {
4154
4180
  const anthropicBeta = c.req.header("anthropic-beta");
4181
+ const sanitized = injectIntoAnthropicPayload(stripSystemReminders(anthropicPayload));
4155
4182
  let result;
4156
4183
  try {
4157
- result = await createAnthropicMessages(injectIntoAnthropicPayload(stripSystemReminders(anthropicPayload)), { anthropicBeta });
4184
+ result = await createAnthropicMessages(sanitized, { anthropicBeta });
4158
4185
  } catch (error) {
4159
- if (isVertexStructuredOutputsBlock(error)) {
4160
- const firstHit = !nativeBlockedModels.has(anthropicPayload.model);
4161
- nativeBlockedModels.add(anthropicPayload.model);
4162
- if (firstHit) consola.debug(`Native /v1/messages blocked by Vertex GCP policy for "${anthropicPayload.model}" — falling back to translated path (cached for this process)`);
4163
- return handleTranslatedCompletion(c, anthropicPayload);
4186
+ const message = error.message || String(error);
4187
+ if (/vertexai\.allowedPartnerModelFeatures.*?structured_outputs/i.test(message)) {
4188
+ consola.debug(`Native /v1/messages: Vertex GCP policy 400, retrying once (Copilot will likely route to Anthropic-direct)`);
4189
+ try {
4190
+ result = await createAnthropicMessages(sanitized, { anthropicBeta });
4191
+ } catch (retryError) {
4192
+ const retryMessage = retryError.message || String(retryError);
4193
+ consola.warn(`Native /v1/messages: Vertex GCP policy 400 on both attempts, propagating to client: ${retryMessage}`);
4194
+ throw retryError;
4195
+ }
4196
+ } else {
4197
+ consola.warn(`Native /v1/messages failed: ${message}`);
4198
+ throw error;
4164
4199
  }
4165
- consola.warn(`Native /v1/messages failed: ${error.message || String(error)}`);
4166
- throw error;
4167
4200
  }
4168
4201
  if (!anthropicPayload.stream) return c.json(overrideAnthropicResponseModel(result, anthropicPayload.model));
4169
4202
  const stream = result;