@code-yeongyu/senpi-ai 2026.10.12 → 2026.10.15

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.
@@ -1,3 +1,5 @@
1
1
  import { type Provider } from "../models.ts";
2
- export declare function opencodeGoProvider(): Provider<"anthropic-messages" | "openai-completions" | "openai-responses">;
2
+ type OpenCodeGoApi = "anthropic-messages" | "google-generative-ai" | "openai-completions" | "openai-responses";
3
+ export declare function opencodeGoProvider(): Provider<OpenCodeGoApi>;
4
+ export {};
3
5
  //# sourceMappingURL=opencode-go.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.js";
2
+ import { googleGenerativeAIApi } from "../api/google-generative-ai.lazy.js";
2
3
  import { openAICompletionsApi } from "../api/openai-completions.lazy.js";
3
4
  import { openAIResponsesApi } from "../api/openai-responses.lazy.js";
4
5
  import { envApiKeyAuth } from "../auth/helpers.js";
@@ -13,6 +14,7 @@ export function opencodeGoProvider() {
13
14
  models: Object.values(OPENCODE_GO_MODELS),
14
15
  api: {
15
16
  "anthropic-messages": withOpenCodeSessionHeader(anthropicMessagesApi()),
17
+ "google-generative-ai": withOpenCodeSessionHeader(googleGenerativeAIApi()),
16
18
  "openai-completions": withOpenCodeSessionHeader(openAICompletionsApi()),
17
19
  "openai-responses": withOpenCodeSessionHeader(openAIResponsesApi()),
18
20
  },
@@ -1,4 +1,17 @@
1
- import { describeProviderStallForUser, formatStallDuration } from "./retry.js";
1
+ import { describeProviderStallForUser, formatStallDuration, isAnthropicThirdPartyBillingMessage, } from "./retry.js";
2
+ const CLAUDE_EXTRA_USAGE_URL = "https://claude.ai/settings/usage";
3
+ /**
4
+ * senpi#3259: the billing path is the whole story. On the direct `anthropic` provider a Claude
5
+ * subscription token is billed as a third-party app, so the fix is the Agent SDK lane; on the
6
+ * `anthropic-subscription` lane the request already ran through Claude Code, so only extra usage or
7
+ * another model can help.
8
+ */
9
+ function describeAnthropicThirdPartyBilling(provider) {
10
+ if (provider === "anthropic-subscription") {
11
+ return `Anthropic billed this Claude Code request to your extra usage instead of your plan limits, and your extra usage is used up. Add extra usage at ${CLAUDE_EXTRA_USAGE_URL}, or switch to another model with /model.`;
12
+ }
13
+ return `Anthropic billed this request to your extra usage instead of your Claude plan limits, because a Claude subscription used directly through the Anthropic API counts as a third-party app. Run /login and choose "Anthropic Subscription (Claude Pro/Max)" so requests run through Claude Code and use your plan limits, add extra usage at ${CLAUDE_EXTRA_USAGE_URL}, or switch to another model with /model.`;
14
+ }
2
15
  /**
3
16
  * Session-internal replay-suppression signal, read off the error text by the
4
17
  * retry predicates. A failover lane stamps it when replaying the turn could
@@ -44,6 +57,9 @@ export function describeProviderFailureForUser(errorMessage, options = {}) {
44
57
  const message = stripTurnRetrySuppressionPrefix(errorMessage).trim();
45
58
  if (!message)
46
59
  return undefined;
60
+ if (isAnthropicThirdPartyBillingMessage(message)) {
61
+ return describeAnthropicThirdPartyBilling(options.model?.split("/")[0]);
62
+ }
47
63
  if (options.usageLimit === "account") {
48
64
  const provider = options.model?.split("/")[0];
49
65
  const reset = /\bresets?\s+([^"}\n]+)/i
@@ -24,6 +24,14 @@ export declare const USAGE_LIMIT_EXHAUSTION: {
24
24
  /** Message markers: the codes appear verbatim in bodies, plus the sentence. */
25
25
  readonly markers: readonly ["usage_limit_reached", "usage_not_included", "usage limit has been reached"];
26
26
  };
27
+ /**
28
+ * Anthropic's rejection of a Claude subscription token used outside Claude Code (senpi#3259): a
29
+ * 400 invalid_request_error reading "Third-party apps now draw from your extra usage, not your plan
30
+ * limits. Add more at claude.ai/settings/usage and keep going." The request was billed to extra
31
+ * usage instead of the plan, so every model on the same credential is refused the same way.
32
+ */
33
+ export declare const ANTHROPIC_THIRD_PARTY_BILLING_MARKER = "Third-party apps now draw from your extra usage";
34
+ export declare function isAnthropicThirdPartyBillingMessage(errorMessage: string | undefined): boolean;
27
35
  export declare function isQuotaExhaustionMessage(errorMessage: string | undefined): boolean;
28
36
  /**
29
37
  * Retry policy: bounded attempts with exponential backoff (`baseDelayMs * 2^(attempt-1)`).
@@ -38,6 +38,16 @@ export const USAGE_LIMIT_EXHAUSTION = {
38
38
  /** Message markers: the codes appear verbatim in bodies, plus the sentence. */
39
39
  markers: ["usage_limit_reached", "usage_not_included", "usage limit has been reached"],
40
40
  };
41
+ /**
42
+ * Anthropic's rejection of a Claude subscription token used outside Claude Code (senpi#3259): a
43
+ * 400 invalid_request_error reading "Third-party apps now draw from your extra usage, not your plan
44
+ * limits. Add more at claude.ai/settings/usage and keep going." The request was billed to extra
45
+ * usage instead of the plan, so every model on the same credential is refused the same way.
46
+ */
47
+ export const ANTHROPIC_THIRD_PARTY_BILLING_MARKER = "Third-party apps now draw from your extra usage";
48
+ export function isAnthropicThirdPartyBillingMessage(errorMessage) {
49
+ return errorMessage?.toLowerCase().includes(ANTHROPIC_THIRD_PARTY_BILLING_MARKER.toLowerCase()) === true;
50
+ }
41
51
  /**
42
52
  * Account quota, budget, credit, and billing exhaustion: the account cannot
43
53
  * serve more requests until the user pays or the quota resets. Shared by the
@@ -70,6 +80,8 @@ const QUOTA_EXHAUSTION_PATTERNS = [
70
80
  // stays dead until its quota resets — every same-account retry is guaranteed
71
81
  // to fail, so the failure is terminal, not rate-limited.
72
82
  ...USAGE_LIMIT_EXHAUSTION.markers,
83
+ // Anthropic billing a subscription token as a third-party app (senpi#3259).
84
+ escapeRegExp(ANTHROPIC_THIRD_PARTY_BILLING_MARKER),
73
85
  ];
74
86
  const QUOTA_EXHAUSTION_PATTERN = buildProviderErrorPattern(QUOTA_EXHAUSTION_PATTERNS);
75
87
  export function isQuotaExhaustionMessage(errorMessage) {
@@ -202,12 +202,17 @@ export function normalizeToolParametersForOpenAICompat(schema) {
202
202
  /**
203
203
  * Guarantee the wire shape every OpenAI-compatible backend requires for tool
204
204
  * parameters: a root object schema. A root union of object shapes is merged into
205
- * one object schema; a root that merely lost its `type` gets it restored.
205
+ * one object schema and a root that already declares a `type` is returned as-is.
206
+ * Every other root without a `type` gets `type: "object"`: a `$ref` root, a root
207
+ * `allOf`, and an `anyOf`/`oneOf` union that cannot merge because a branch is not
208
+ * an object shape. Any combiner is kept beside the added type.
206
209
  *
207
- * A root whose branches are not object shapes is left alone: forcing
208
- * `type: "object"` onto a scalar union would assert something the schema
209
- * contradicts, which is worse than the missing keyword. Tool parameters are
210
- * objects in practice, so this only guards against corrupting an exotic schema.
210
+ * DeepSeek and gateways forwarding to it reject a typeless root outright
211
+ * (`schema must be a JSON Schema of 'type: "object"', got 'type: null'`, #3245).
212
+ * The cost: for a union with no object branch the wire schema becomes stricter
213
+ * than the source (`type: "object"` beside `anyOf: [string, number]` is
214
+ * unsatisfiable). That is accepted because tool arguments are always JSON objects
215
+ * and are validated locally against the original, unnormalized schema.
211
216
  */
212
217
  function ensureRootObjectSchema(schema) {
213
218
  const merged = mergeRootObjectUnion(schema);
@@ -215,9 +220,6 @@ function ensureRootObjectSchema(schema) {
215
220
  return merged;
216
221
  if (schema.type !== undefined)
217
222
  return schema;
218
- const hasCombiner = COMBINER_KEYS.some((key) => Array.isArray(schema[key]));
219
- if (hasCombiner)
220
- return schema;
221
223
  return { ...schema, type: "object" };
222
224
  }
223
225
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-ai",
3
- "version": "2026.10.12",
3
+ "version": "2026.10.15",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -88,7 +88,7 @@
88
88
  "dependencies": {
89
89
  "@anthropic-ai/sdk": "0.127.0",
90
90
  "@aws-sdk/client-bedrock-runtime": "3.1136.0",
91
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.12",
91
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.15",
92
92
  "@google/genai": "2.23.0",
93
93
  "@smithy/node-http-handler": "4.12.1",
94
94
  "http-proxy-agent": "9.1.0",